You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Refactor attachment queue initialization and implemented default base path for local storage in the powersync_attachments_stream package. Simplified the attachment watching logic. Updated README for clarity on schema and attachment states.
2. Create an `AttachmentQueue` instance. This class provides default syncing utilities and implements a default sync strategy. This class is open and can be overridden for custom functionality:
50
44
51
45
```dart
46
+
final Directory appDocDir = await getApplicationDocumentsDirectory();
SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL
54
+
''').map((results) => results
55
+
.map((row) => WatchedAttachmentItem(
56
+
id: row['id'] as String,
57
+
fileExtension: 'jpg',
58
+
))
59
+
.toList()),
60
+
);
68
61
```
69
62
70
63
* The `attachmentsDirectory` specifies where local attachment files should be stored. This directory needs to be provided to the constructor. In Flutter, `path_provider`'s `getApplicationDocumentsDirectory()` with a subdirectory like `/attachments` is a good choice.
71
-
* The `remoteStorage` is responsible for connecting to the attachments backend. See the `RemoteStorageAdapter` interface definition [here](https://github.com/powersync-ja/powersync-dart/blob/main/core/lib/src/attachments/remote_storage_adapter.dart).
64
+
* The `remoteStorage` is responsible for connecting to the attachments backend. See the `RemoteStorageAdapter` interface definition [here](https://github.com/powersync-ja/powersync.dart/blob/main/packages/powersync_attachments_stream/lib/src/abstractions/remote_storage.dart).
72
65
*`watchAttachments` is a `Stream` of `WatchedAttachmentItem`. The `WatchedAttachmentItem`s represent the attachments which should be present in the application. We recommend using `PowerSync`'s `watch` query as shown above. In this example, we provide the `fileExtension` for all photos. This information could also be obtained from the query if necessary.
73
66
74
67
3. Implement a `RemoteStorageAdapter` which interfaces with a remote storage provider. This will be used for downloading, uploading, and deleting attachments:
@@ -78,13 +71,13 @@ final remote = _RemoteStorageAdapter();
78
71
79
72
class _RemoteStorageAdapter implements RemoteStorageAdapter {
* This callback is invoked in the same transaction which creates the attachment record.
113
-
* Assignments of the newly created photo_id should be done in the same transaction for maximum efficiency.
114
-
*/
115
-
await tx.execute(
116
-
'''
117
-
UPDATE checklists
118
-
SET photo_id = ?
119
-
WHERE id = ?
120
-
''',
121
-
[attachment.id, checklistId],
122
-
);
123
-
},
124
-
);
100
+
data: photoData,
101
+
mediaType: 'image/jpg',
102
+
fileExtension: 'jpg',
103
+
metaData: 'Test meta data',
104
+
updateHook: (context, attachment) async {
105
+
// Update the todo item to reference this attachment
106
+
await context.execute(
107
+
'UPDATE checklists SET photo_id = ? WHERE id = ?',
108
+
[attachment.id, checklistId],
109
+
);
110
+
},
111
+
);
125
112
```
126
113
127
114
## Implementation Details
128
115
129
116
### Attachment Table Structure
130
117
131
-
The `createAttachmentsTable` function creates a local-only table for tracking attachment states.
118
+
The `AttachmentsQueueTable` class creates a **local-only table** for tracking the states and metadata of file attachments. It allows customization of the table name, additional columns, indexes, and optionally a view name.
132
119
133
120
An attachments table definition can be created with the following options:
|`id`|`TEXT`| Unique identifier for the attachment |
144
-
|`filename`|`TEXT`| The filename of the attachment |
145
-
|`media_type`|`TEXT`| The media type of the attachment |
146
-
|`state`|`INTEGER`| Current state of the attachment (see `AttachmentState` enum) |
147
-
|`timestamp`|`INTEGER`| The timestamp of last update to the attachment |
148
-
|`size`|`INTEGER`| File size in bytes |
149
-
|`has_synced`|`INTEGER`| Internal flag tracking if the attachment has ever been synced (used for caching) |
150
-
|`meta_data`|`TEXT`| Additional metadata in JSON format |
142
+
The class extends a base `Table` class using a `localOnly` constructor, so this table exists **only locally** on the device and is not synchronized with a remote database.
143
+
144
+
This design allows flexible tracking and management of attachment syncing state and metadata within the local database. |
151
145
152
146
### Attachment States
153
147
154
-
Attachments are managed through the following states:
148
+
Attachments are managed through the following states, which represent their current synchronization status with remote storage:
|`QUEUED_UPLOAD`| Attachment is queued for upload to cloud storage |
159
-
|`QUEUED_DELETE`| Attachment is queued for deletion from cloud storage and local storage |
160
-
|`QUEUED_DOWNLOAD`| Attachment is queued for download from cloud storage |
161
-
|`SYNCED`| Attachment is fully synced |
162
-
|`ARCHIVED`| Attachment is orphaned - i.e., no longer referenced by any data |
162
+
-`AttachmentState.fromInt(int value)` — Constructs an `AttachmentState` from its corresponding integer index. Throws an `ArgumentError` if the value is out of range.
163
+
-`toInt()` — Returns the integer index of the current `AttachmentState` instance.
163
164
164
165
### Sync Process
165
166
166
167
The `AttachmentQueue` implements a sync process with these components:
167
168
168
-
1.**State Monitoring**: The queue watches the attachments table for records in `QUEUED_UPLOAD`, `QUEUED_DELETE`, and `QUEUED_DOWNLOAD` states. An event loop triggers calls to the remote storage for these operations.
169
+
1.**State Monitoring**: The queue watches the attachments table for records in `queuedUpload`, `queuedDelete`, and `queuedDownload` states. An event loop triggers calls to the remote storage for these operations.
169
170
170
171
2.**Periodic Sync**: By default, the queue triggers a sync every 30 seconds to retry failed uploads/downloads, in particular after the app was offline. This interval can be configured by setting `syncInterval` in the `AttachmentQueue` constructor options, or disabled by setting the interval to `0`.
171
172
@@ -178,27 +179,27 @@ The `AttachmentQueue` implements a sync process with these components:
178
179
The `saveFile` method handles attachment creation and upload:
179
180
180
181
1. The attachment is saved to local storage
181
-
2. An `AttachmentRecord` is created with `QUEUED_UPLOAD` state, linked to the local file using `localUri`
182
+
2. An `AttachmentRecord` is created with `queuedUpload` state, linked to the local file using `localUri`
182
183
3. The attachment must be assigned to relational data in the same transaction, since this data is constantly watched and should always represent the attachment queue state
183
184
4. The `RemoteStorageAdapter``uploadFile` function is called
184
-
5. On successful upload, the state changes to `SYNCED`
185
-
6. If upload fails, the record stays in `QUEUED_UPLOAD` state for retry
185
+
5. On successful upload, the state changes to `synced`
186
+
6. If upload fails, the record stays in `queuedUpload` state for retry
186
187
187
188
### Download Process
188
189
189
190
Attachments are scheduled for download when the stream from `watchAttachments` emits a new item that is not present locally:
190
191
191
-
1. An `AttachmentRecord` is created with `QUEUED_DOWNLOAD` state
192
+
1. An `AttachmentRecord` is created with `queuedDownload` state
192
193
2. The `RemoteStorageAdapter``downloadFile` function is called
193
194
3. The received data is saved to local storage
194
-
4. On successful download, the state changes to `SYNCED`
195
+
4. On successful download, the state changes to `synced`
195
196
5. If download fails, the operation is retried in the next sync cycle
196
197
197
198
### Delete Process
198
199
199
200
The `deleteFile` method deletes attachments from both local and remote storage:
200
201
201
-
1. The attachment record moves to `QUEUED_DELETE` state
202
+
1. The attachment record moves to `queuedDelete` state
202
203
2. The attachment must be unassigned from relational data in the same transaction, since this data is constantly watched and should always represent the attachment queue state
203
204
3. On successful deletion, the record is removed
204
205
4. If deletion fails, the operation is retried in the next sync cycle
@@ -207,7 +208,7 @@ The `deleteFile` method deletes attachments from both local and remote storage:
207
208
208
209
The `AttachmentQueue` implements a caching system for archived attachments:
209
210
210
-
1. Local attachments are marked as `ARCHIVED` if the stream from `watchAttachments` no longer references them
211
+
1. Local attachments are marked as `archived` if the stream from `watchAttachments` no longer references them
211
212
2. Archived attachments are kept in the cache for potential future restoration
212
213
3. The cache size is controlled by the `archivedCacheLimit` parameter in the `AttachmentQueue` constructor
213
214
4. By default, the queue keeps the last 100 archived attachment records
@@ -234,19 +235,19 @@ final errorHandler = _SyncErrorHandler();
234
235
235
236
class _SyncErrorHandler implements SyncErrorHandler {
0 commit comments