Skip to content

feat(attachments): add error handlers #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demos/supabase-simple-chat/lib/pages/login_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class LoginPageState extends State<LoginPage> {
} catch (_) {
context.showErrorSnackBar(message: unexpectedErrorMessage);
}
if (mounted) {
if (context.mounted) {
setState(() {
_isLoading = true;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
31 changes: 29 additions & 2 deletions demos/supabase-todolist/lib/attachments/photo_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:powersync_attachments_helper/powersync_attachments_helper.dart';
import 'package:powersync_flutter_demo/attachments/camera_helpers.dart';
import 'package:powersync_flutter_demo/attachments/photo_capture_widget.dart';
import 'package:powersync_flutter_demo/attachments/queue.dart';
Expand All @@ -23,8 +24,10 @@ class PhotoWidget extends StatefulWidget {
class _ResolvedPhotoState {
String? photoPath;
bool fileExists;
Attachment? attachment;

_ResolvedPhotoState({required this.photoPath, required this.fileExists});
_ResolvedPhotoState(
{required this.photoPath, required this.fileExists, this.attachment});
}

class _PhotoWidgetState extends State<PhotoWidget> {
Expand All @@ -38,7 +41,17 @@ class _PhotoWidgetState extends State<PhotoWidget> {

bool fileExists = await File(photoPath).exists();

return _ResolvedPhotoState(photoPath: photoPath, fileExists: fileExists);
final row = await attachmentQueue.db
.getOptional('SELECT * FROM attachments_queue WHERE id = ?', [photoId]);

if (row != null) {
Attachment attachment = Attachment.fromRow(row);
return _ResolvedPhotoState(
photoPath: photoPath, fileExists: fileExists, attachment: attachment);
}

return _ResolvedPhotoState(
photoPath: photoPath, fileExists: fileExists, attachment: null);
}

@override
Expand Down Expand Up @@ -84,6 +97,20 @@ class _PhotoWidgetState extends State<PhotoWidget> {

String? filePath = data.photoPath;
bool fileIsDownloading = !data.fileExists;
bool fileArchived =
data.attachment?.state == AttachmentState.archived.index;

if (fileArchived) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Unavailable"),
const SizedBox(height: 8),
takePhotoButton
],
);
}

if (fileIsDownloading) {
return const Text("Downloading...");
Expand Down
14 changes: 13 additions & 1 deletion demos/supabase-todolist/lib/attachments/queue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,21 @@ import 'package:powersync_flutter_demo/models/schema.dart';
late final PhotoAttachmentQueue attachmentQueue;
final remoteStorage = SupabaseStorageAdapter();

/// Function to handle errors when downloading attachments
/// Return false if you want to archive the attachment
Future<bool> onDownloadError(Attachment attachment, Object exception) async {
if (exception.toString().contains('Object not found')) {
return false;
}
return true;
}

class PhotoAttachmentQueue extends AbstractAttachmentQueue {
PhotoAttachmentQueue(db, remoteStorage)
: super(db: db, remoteStorage: remoteStorage);
: super(
db: db,
remoteStorage: remoteStorage,
onDownloadError: onDownloadError);

@override
init() async {
Expand Down
2 changes: 1 addition & 1 deletion demos/supabase-todolist/lib/widgets/lists_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _ListsWidgetState extends State<ListsWidget> {
super.initState();
final stream = TodoList.watchListsWithStats();
_subscription = stream.listen((data) {
if (!mounted) {
if (!context.mounted) {
return;
}
setState(() {
Expand Down
2 changes: 1 addition & 1 deletion demos/supabase-todolist/lib/widgets/query_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class QueryWidgetState extends State<QueryWidget> {
_subscription?.cancel();
final stream = db.watch(_query);
_subscription = stream.listen((data) {
if (!mounted) {
if (!context.mounted) {
return;
}
setState(() {
Expand Down
2 changes: 1 addition & 1 deletion demos/supabase-todolist/lib/widgets/todo_list_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class TodoListWidgetState extends State<TodoListWidget> {
super.initState();
final stream = widget.list.watchItems();
_subscription = stream.listen((data) {
if (!mounted) {
if (!context.mounted) {
return;
}
setState(() {
Expand Down
2 changes: 1 addition & 1 deletion demos/supabase-todolist/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ packages:
path: "../../packages/powersync_attachments_helper"
relative: true
source: path
version: "0.2.0"
version: "0.2.1"
realtime_client:
dependency: transitive
description:
Expand Down
5 changes: 5 additions & 0 deletions packages/powersync_attachments_helper/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.2.1

- Added `onUploadError` as an optional function that can be set when setting up the queue to handle upload errors
- Added `onDownloadError` as an optional function that can be set when setting up the queue to handle upload errors

## 0.2.0

- Potentially BREAKING CHANGE for users who rely on multiple attachment queues.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,28 @@ abstract class AbstractAttachmentQueue {
final LocalStorageAdapter localStorage = LocalStorageAdapter();
String attachmentsQueueTableName;

/// Function to handle errors when downloading attachments
/// Return true if you want to ignore attachment
Future<bool> Function(Attachment attachment, Object exception)?
onDownloadError;

/// Function to handle errors when uploading attachments
/// Return true if you want to ignore attachment
Future<bool> Function(Attachment attachment, Object exception)? onUploadError;

AbstractAttachmentQueue(
{required this.db,
required this.remoteStorage,
this.attachmentDirectoryName = 'attachments',
this.attachmentsQueueTableName = defaultAttachmentsQueueTableName,
this.onDownloadError,
this.onUploadError,
performInitialSync = true}) {
attachmentsService = AttachmentsService(
db, localStorage, attachmentDirectoryName, attachmentsQueueTableName);
syncingService = SyncingService(
db, remoteStorage, localStorage, attachmentsService, getLocalUri);
db, remoteStorage, localStorage, attachmentsService, getLocalUri,
onDownloadError: onDownloadError, onUploadError: onUploadError);
}

/// Create watcher to get list of ID's from a table to be used for syncing in the attachment queue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,8 @@ class Attachment {
/// 1. Attachment to be uploaded
/// 2. Attachment to be downloaded
/// 3. Attachment to be deleted
enum AttachmentState {
queuedUpload,
queuedDownload,
queuedDelete,
}
/// 4. Attachment to be archived
enum AttachmentState { queuedUpload, queuedDownload, queuedDelete, archived }

class AttachmentsQueueTable extends Table {
AttachmentsQueueTable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class AttachmentsService {
Future<void> deleteAttachment(String id) async =>
db.execute('DELETE FROM $table WHERE id = ?', [id]);

///Set the state of the attachment to ignore.
Future<void> ignoreAttachment(String id) async => db.execute(
'UPDATE $table SET state = ${AttachmentState.archived.index} WHERE id = ?',
[id]);

/// Get the attachment from the attachment queue using an ID.
Future<Attachment?> getAttachment(String id) async =>
db.getOptional('SELECT * FROM $table WHERE id = ?', [id]).then((row) {
Expand Down
26 changes: 24 additions & 2 deletions packages/powersync_attachments_helper/lib/src/syncing_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ class SyncingService {
final LocalStorageAdapter localStorage;
final AttachmentsService attachmentsService;
final Function getLocalUri;
final Future<bool> Function(Attachment attachment, Object exception)?
onDownloadError;
final Future<bool> Function(Attachment attachment, Object exception)?
onUploadError;

SyncingService(this.db, this.remoteStorage, this.localStorage,
this.attachmentsService, this.getLocalUri);
this.attachmentsService, this.getLocalUri,
{this.onDownloadError, this.onUploadError});

/// Upload attachment from local storage and to remote storage
/// then remove it from the queue.
/// If duplicate of the file is found uploading is ignored and
/// If duplicate of the file is found uploading is archived and
/// the attachment is removed from the queue.
Future<void> uploadAttachment(Attachment attachment) async {
if (attachment.localUri == null) {
Expand All @@ -44,6 +49,14 @@ class SyncingService {
}

log.severe('Upload attachment error for attachment $attachment', e);
if (onUploadError != null) {
bool shouldRetry = await onUploadError!(attachment, e);
if (!shouldRetry) {
log.info('Attachment with ID ${attachment.id} has been archived', e);
await attachmentsService.ignoreAttachment(attachment.id);
}
}

return;
}
}
Expand All @@ -63,6 +76,15 @@ class SyncingService {
await attachmentsService.deleteAttachment(attachment.id);
return;
} catch (e) {
if (onDownloadError != null) {
bool shouldRetry = await onDownloadError!(attachment, e);
if (!shouldRetry) {
log.info('Attachment with ID ${attachment.id} has been archived', e);
await attachmentsService.ignoreAttachment(attachment.id);
return;
}
}

log.severe('Download attachment error for attachment $attachment', e);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/powersync_attachments_helper/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: powersync_attachments_helper
description: A helper library for handling attachments when using PowerSync.
version: 0.2.0
version: 0.2.1
repository: https://github.com/powersync-ja/powersync.dart
homepage: https://www.powersync.com/
environment:
Expand Down