Skip to content

Commit c2086f2

Browse files
Refactor attachment handling and logging in the powersync_attachments_stream package. Removed unused imports, improved error handling during file uploads and downloads, and enhanced and renamed logging for better traceability of attachment operations.
1 parent f66fbdb commit c2086f2

File tree

7 files changed

+94
-68
lines changed

7 files changed

+94
-68
lines changed

demos/supabase-todolist-new-attachment/lib/attachments/photo_capture_widget.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import 'dart:typed_data';
44
import 'package:camera/camera.dart';
55
import 'package:flutter/material.dart';
66
import 'package:logging/logging.dart';
7-
import 'package:powersync/powersync.dart' as powersync;
87
import 'package:powersync_flutter_demo_new/attachments/queue.dart';
9-
import 'package:powersync_flutter_demo_new/models/todo_item.dart';
10-
import 'package:powersync_flutter_demo_new/powersync.dart';
118

129
class TakePhotoWidget extends StatefulWidget {
1310
final String todoId;

demos/supabase-todolist-new-attachment/lib/attachments/queue.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ Future<void> initializeAttachmentQueue(PowerSyncDatabase db) async {
3838
return items;
3939
}),
4040
localStorage: localStorage,
41-
errorHandler: null, // Optionally implement SyncErrorHandler
41+
errorHandler: null,
4242
);
43-
// await attachmentQueue.startSync();
4443
}
4544

4645
Future<Attachment> savePhotoAttachment(Stream<Uint8List> photoData, String todoId,

demos/supabase-todolist-new-attachment/lib/attachments/remote_storage_adapter.dart

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,57 @@ import 'dart:typed_data';
33
import 'package:powersync_attachments_stream/powersync_attachments_stream.dart';
44
import 'package:powersync_flutter_demo_new/app_config.dart';
55
import 'package:supabase_flutter/supabase_flutter.dart';
6-
import 'package:image/image.dart' as img;
6+
import 'package:logging/logging.dart';
77

88
class SupabaseStorageAdapter implements RemoteStorage {
9+
static final _log = Logger('SupabaseStorageAdapter');
10+
911
@override
1012
Future<void> uploadFile(
1113
Stream<List<int>> fileData, Attachment attachment) async {
1214
_checkSupabaseBucketIsConfigured();
13-
final tempFile =
14-
File('${Directory.systemTemp.path}/${attachment.filename}');
15-
final sink = tempFile.openWrite();
15+
16+
// Check if attachment size is specified (required for buffer allocation)
17+
final byteSize = attachment.size;
18+
if (byteSize == null) {
19+
throw Exception('Cannot upload a file with no byte size specified');
20+
}
21+
22+
_log.info('uploadFile: ${attachment.filename} (size: $byteSize bytes)');
23+
24+
// Collect all stream data into a single Uint8List buffer (like Kotlin version)
25+
final buffer = Uint8List(byteSize);
26+
var position = 0;
27+
1628
await for (final chunk in fileData) {
17-
sink.add(chunk);
29+
if (position + chunk.length > byteSize) {
30+
throw Exception('File data exceeds specified size');
31+
}
32+
buffer.setRange(position, position + chunk.length, chunk);
33+
position += chunk.length;
1834
}
19-
await sink.close();
20-
print('uploadFile: ${attachment.filename}');
35+
36+
if (position != byteSize) {
37+
throw Exception(
38+
'File data size ($position) does not match specified size ($byteSize)');
39+
}
40+
41+
// Create a temporary file from the buffer for upload
42+
final tempFile =
43+
File('${Directory.systemTemp.path}/${attachment.filename}');
2144
try {
45+
await tempFile.writeAsBytes(buffer);
46+
2247
await Supabase.instance.client.storage
2348
.from(AppConfig.supabaseStorageBucket)
2449
.upload(attachment.filename, tempFile,
2550
fileOptions: FileOptions(
2651
contentType:
2752
attachment.mediaType ?? 'application/octet-stream'));
2853

54+
_log.info('Successfully uploaded ${attachment.filename}');
2955
} catch (error) {
56+
_log.severe('Error uploading ${attachment.filename}', error);
3057
throw Exception(error);
3158
} finally {
3259
if (await tempFile.exists()) {
@@ -38,23 +65,26 @@ class SupabaseStorageAdapter implements RemoteStorage {
3865
@override
3966
Future<Stream<List<int>>> downloadFile(Attachment attachment) async {
4067
_checkSupabaseBucketIsConfigured();
41-
print('downloadFile: ${attachment.filename}');
4268
try {
69+
_log.info('downloadFile: ${attachment.filename}');
70+
4371
Uint8List fileBlob = await Supabase.instance.client.storage
4472
.from(AppConfig.supabaseStorageBucket)
4573
.download(attachment.filename);
46-
final image = img.decodeImage(fileBlob);
47-
Uint8List blob = img.JpegEncoder().encode(image!);
48-
print('downloadFile: ${blob.length}');
49-
return Stream.value(blob);
74+
75+
_log.info(
76+
'Successfully downloaded ${attachment.filename} (${fileBlob.length} bytes)');
77+
78+
// Return the raw file data as a stream
79+
return Stream.value(fileBlob);
5080
} catch (error) {
81+
_log.severe('Error downloading ${attachment.filename}', error);
5182
throw Exception(error);
5283
}
5384
}
5485

5586
@override
5687
Future<void> deleteFile(Attachment attachment) async {
57-
print('deleteFile: ${attachment.filename}');
5888
_checkSupabaseBucketIsConfigured();
5989
try {
6090
await Supabase.instance.client.storage

packages/powersync_attachments_stream/lib/src/attachment_queue_service.dart

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class AttachmentQueue {
7171
}) : logger = logger ?? Logger('AttachmentQueue') {
7272
attachmentsService = AttachmentServiceImpl(
7373
db: db,
74-
log: logger ?? Logger('AttachmentQueue'),
74+
logger: logger ?? Logger('AttachmentQueue'),
7575
maxArchivedCount: archivedCacheLimit,
7676
attachmentsQueueTableName: attachmentsQueueTableName,
7777
);
@@ -195,12 +195,6 @@ class AttachmentQueue {
195195
),
196196
);
197197

198-
// attachmentUpdates.add(
199-
// existingQueueItem.copyWith(
200-
// filename: filename,
201-
// state: AttachmentState.queuedDownload,
202-
// ),
203-
// );
204198
} else if (existingQueueItem.state == AttachmentState.archived) {
205199
// The attachment is present again. Need to queue it for sync.
206200
if (existingQueueItem.hasSynced) {

packages/powersync_attachments_stream/lib/src/implementations/attachment_context.dart

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,30 +101,35 @@ class AttachmentContextImpl implements AttachmentContext {
101101
Future<bool> deleteArchivedAttachments(
102102
Future<void> Function(List<Attachment>) callback,
103103
) async {
104-
// Find all archived attachments
105-
final results = await db.getAll('SELECT * FROM $table WHERE state = ?', [
106-
AttachmentState.archived.index,
107-
]);
108-
final archivedAttachments = results
109-
.map((row) => Attachment.fromRow(row))
110-
.toList();
104+
// Only delete archived attachments exceeding the maxArchivedCount, ordered by timestamp DESC
105+
const limit = 1000;
106+
final results = await db.getAll(
107+
'SELECT * FROM $table WHERE state = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?',
108+
[
109+
AttachmentState.archived.index,
110+
limit,
111+
maxArchivedCount,
112+
],
113+
);
114+
final archivedAttachments = results.map((row) => Attachment.fromRow(row)).toList();
111115

112116
if (archivedAttachments.isEmpty) {
113117
return false;
114118
}
115119

116-
log.info('Deleting ${archivedAttachments.length} archived attachments...');
120+
log.info('Deleting ${archivedAttachments.length} archived attachments (exceeding maxArchivedCount=$maxArchivedCount)...');
117121
// Call the callback with the list of archived attachments before deletion
118122
await callback(archivedAttachments);
119123

120124
// Delete the archived attachments from the table
121125
final ids = archivedAttachments.map((a) => a.id).toList();
122-
// Use a batch delete for efficiency
123-
final placeholders = List.filled(ids.length, '?').join(',');
124-
await db.execute('DELETE FROM $table WHERE id IN ($placeholders)', ids);
126+
if (ids.isNotEmpty) {
127+
final placeholders = List.filled(ids.length, '?').join(',');
128+
await db.execute('DELETE FROM $table WHERE id IN ($placeholders)', ids);
129+
}
125130

126131
log.info('Deleted ${archivedAttachments.length} archived attachments.');
127-
return true;
132+
return archivedAttachments.length < limit;
128133
}
129134

130135
@override

packages/powersync_attachments_stream/lib/src/implementations/attachment_service.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'attachment_context.dart';
99

1010
class AttachmentServiceImpl implements AttachmentService {
1111
final PowerSyncDatabase db;
12-
final Logger log;
12+
final Logger logger;
1313
final int maxArchivedCount;
1414
final String attachmentsQueueTableName;
1515
Future<void> _mutex = Future.value();
@@ -18,16 +18,16 @@ class AttachmentServiceImpl implements AttachmentService {
1818

1919
AttachmentServiceImpl({
2020
required this.db,
21-
required this.log,
21+
required this.logger,
2222
required this.maxArchivedCount,
2323
required this.attachmentsQueueTableName,
2424
}) {
25-
_context = AttachmentContextImpl(db, log, maxArchivedCount, attachmentsQueueTableName);
25+
_context = AttachmentContextImpl(db, logger, maxArchivedCount, attachmentsQueueTableName);
2626
}
2727

2828
@override
2929
Stream<void> watchActiveAttachments() async* {
30-
log.info('Watching attachments...');
30+
logger.info('Watching attachments...');
3131

3232
// Watch for attachments with active states (queued for upload, download, or delete)
3333
final stream = db.watch(

0 commit comments

Comments
 (0)