@@ -47,7 +47,9 @@ class StreamingSyncImplementation implements StreamingSync {
47
47
StreamController <Null >.broadcast ();
48
48
49
49
final http.Client _client;
50
- final SyncStatusStateStream _state = SyncStatusStateStream ();
50
+
51
+ @visibleForTesting
52
+ final SyncStatusStateStream state = SyncStatusStateStream ();
51
53
52
54
AbortController ? _abort;
53
55
@@ -79,13 +81,14 @@ class StreamingSyncImplementation implements StreamingSync {
79
81
Duration get _retryDelay => options.retryDelay;
80
82
81
83
@override
82
- Stream <SyncStatus > get statusStream => _state .statusStream;
84
+ Stream <SyncStatus > get statusStream => state .statusStream;
83
85
84
86
@override
85
87
Future <void > abort () async {
86
88
// If streamingSync() hasn't been called yet, _abort will be null.
87
89
if (_abort case final abort? ) {
88
90
final future = abort.abort ();
91
+ _internalCrudTriggerController.close ();
89
92
90
93
// This immediately triggers a new iteration in the merged stream, allowing us
91
94
// to break immediately.
@@ -95,11 +98,14 @@ class StreamingSyncImplementation implements StreamingSync {
95
98
96
99
// Wait for the abort to complete, which also guarantees that no requests
97
100
// are pending.
98
- await future;
101
+ await Future .wait ([
102
+ future,
103
+ if (_activeCrudUpload case final activeUpload? ) activeUpload.future,
104
+ ]);
99
105
await _nonLineSyncEvents.close ();
100
106
101
107
_client.close ();
102
- _state .close ();
108
+ state .close ();
103
109
}
104
110
}
105
111
@@ -115,7 +121,7 @@ class StreamingSyncImplementation implements StreamingSync {
115
121
_crudLoop ();
116
122
var invalidCredentials = false ;
117
123
while (! aborted) {
118
- _state .updateStatus ((s) => s.setConnectingIfNotConnected ());
124
+ state .updateStatus ((s) => s.setConnectingIfNotConnected ());
119
125
try {
120
126
if (invalidCredentials) {
121
127
// This may error. In that case it will be retried again on the next
@@ -137,7 +143,7 @@ class StreamingSyncImplementation implements StreamingSync {
137
143
logger.warning ('Sync error: $message ' , e, stacktrace);
138
144
invalidCredentials = true ;
139
145
140
- _state .updateStatus ((s) => s.applyDownloadError (e));
146
+ state .updateStatus ((s) => s.applyDownloadError (e));
141
147
142
148
// On error, wait a little before retrying
143
149
// When aborting, don't wait
@@ -183,7 +189,7 @@ class StreamingSyncImplementation implements StreamingSync {
183
189
// This is the first item in the FIFO CRUD queue.
184
190
CrudEntry ? nextCrudItem = await adapter.nextCrudItem ();
185
191
if (nextCrudItem != null ) {
186
- _state .updateStatus ((s) => s.uploading = true );
192
+ state .updateStatus ((s) => s.uploading = true );
187
193
if (nextCrudItem.clientId == checkedCrudItem? .clientId) {
188
194
// This will force a higher log level than exceptions which are caught here.
189
195
logger.warning (
@@ -196,7 +202,7 @@ class StreamingSyncImplementation implements StreamingSync {
196
202
197
203
checkedCrudItem = nextCrudItem;
198
204
await connector.uploadCrud ();
199
- _state .updateStatus ((s) => s.uploadError = null );
205
+ state .updateStatus ((s) => s.uploadError = null );
200
206
} else {
201
207
// Uploading is completed
202
208
await adapter.updateLocalTarget (() => getWriteCheckpoint ());
@@ -205,10 +211,10 @@ class StreamingSyncImplementation implements StreamingSync {
205
211
} catch (e, stacktrace) {
206
212
checkedCrudItem = null ;
207
213
logger.warning ('Data upload error' , e, stacktrace);
208
- _state .updateStatus ((s) => s.applyUploadError (e));
214
+ state .updateStatus ((s) => s.applyUploadError (e));
209
215
await _delayRetry ();
210
216
211
- if (! _state .status.connected) {
217
+ if (! state .status.connected) {
212
218
// Exit the upload loop if the sync stream is no longer connected
213
219
break ;
214
220
}
@@ -217,12 +223,15 @@ class StreamingSyncImplementation implements StreamingSync {
217
223
e,
218
224
stacktrace);
219
225
} finally {
220
- _state .updateStatus ((s) => s.uploading = false );
226
+ state .updateStatus ((s) => s.uploading = false );
221
227
}
222
228
}
223
229
}, timeout: _retryDelay).whenComplete (() {
230
+ if (! aborted) {
231
+ _nonLineSyncEvents.add (const UploadCompleted ());
232
+ }
233
+
224
234
assert (identical (_activeCrudUpload, completer));
225
- _nonLineSyncEvents.add (const UploadCompleted ());
226
235
_activeCrudUpload = null ;
227
236
completer.complete ();
228
237
});
@@ -255,7 +264,7 @@ class StreamingSyncImplementation implements StreamingSync {
255
264
}
256
265
257
266
void _updateStatusForPriority (SyncPriorityStatus completed) {
258
- _state .updateStatus ((s) {
267
+ state .updateStatus ((s) {
259
268
// All status entries with a higher priority can be deleted since this
260
269
// partial sync includes them.
261
270
s.priorityStatusEntries = [
@@ -316,7 +325,7 @@ class StreamingSyncImplementation implements StreamingSync {
316
325
bucketMap = newBuckets;
317
326
await adapter.removeBuckets ([...bucketsToDelete]);
318
327
final initialProgress = await adapter.getBucketOperationProgress ();
319
- _state .updateStatus (
328
+ state .updateStatus (
320
329
(s) => s.applyCheckpointStarted (initialProgress, line));
321
330
case StreamingSyncCheckpointComplete ():
322
331
final result = await _applyCheckpoint (targetCheckpoint! , _abort);
@@ -367,7 +376,7 @@ class StreamingSyncImplementation implements StreamingSync {
367
376
writeCheckpoint: diff.writeCheckpoint);
368
377
targetCheckpoint = newCheckpoint;
369
378
final initialProgress = await adapter.getBucketOperationProgress ();
370
- _state .updateStatus (
379
+ state .updateStatus (
371
380
(s) => s.applyCheckpointStarted (initialProgress, newCheckpoint));
372
381
373
382
bucketMap = newBuckets.map ((name, checksum) =>
@@ -377,7 +386,7 @@ class StreamingSyncImplementation implements StreamingSync {
377
386
case SyncDataBatch ():
378
387
// TODO: This increments the counters before actually saving sync
379
388
// data. Might be fine though?
380
- _state .updateStatus ((s) => s.applyBatchReceived (line));
389
+ state .updateStatus ((s) => s.applyBatchReceived (line));
381
390
await adapter.saveSyncData (line);
382
391
case StreamingSyncKeepalive (: final tokenExpiresIn):
383
392
if (tokenExpiresIn == 0 ) {
@@ -392,7 +401,9 @@ class StreamingSyncImplementation implements StreamingSync {
392
401
haveInvalidated = true ;
393
402
// trigger next loop iteration ASAP, don't wait for another
394
403
// message from the server.
395
- _nonLineSyncEvents.add (TokenRefreshComplete ());
404
+ if (! aborted) {
405
+ _nonLineSyncEvents.add (TokenRefreshComplete ());
406
+ }
396
407
}, onError: (_) {
397
408
// Token refresh failed - retry on next keepalive.
398
409
credentialsInvalidation = null ;
@@ -422,7 +433,7 @@ class StreamingSyncImplementation implements StreamingSync {
422
433
throw AssertionError ('unreachable' );
423
434
}
424
435
425
- _state .updateStatus ((s) => s.setConnected ());
436
+ state .updateStatus ((s) => s.setConnected ());
426
437
if (haveInvalidated) {
427
438
// Stop this connection, so that a new one will be started
428
439
break ;
@@ -462,7 +473,7 @@ class StreamingSyncImplementation implements StreamingSync {
462
473
if (result.checkpointValid && result.ready) {
463
474
logger.fine ('validated checkpoint: $targetCheckpoint ' );
464
475
465
- _state .updateStatus ((s) => s.applyCheckpointReached (targetCheckpoint));
476
+ state .updateStatus ((s) => s.applyCheckpointReached (targetCheckpoint));
466
477
467
478
return const (abort: false , didApply: true );
468
479
} else {
@@ -501,22 +512,18 @@ class StreamingSyncImplementation implements StreamingSync {
501
512
return res;
502
513
}
503
514
504
- Stream <String > _rawStreamingSyncRequest (Object ? data) {
505
- return Stream .fromFuture (_postStreamRequest (data)).asyncExpand ((stream) {
506
- if (stream == null ) {
507
- return const Stream .empty ();
508
- }
509
-
510
- return stream.stream.lines;
511
- });
515
+ Stream <String > _rawStreamingSyncRequest (Object ? data) async * {
516
+ final response = await _postStreamRequest (data);
517
+ if (response != null ) {
518
+ yield * response.stream.lines;
519
+ }
512
520
}
513
521
514
522
Stream <StreamingSyncLine > _streamingSyncRequest (StreamingSyncRequest data) {
515
523
return _rawStreamingSyncRequest (data)
516
524
.parseJson
517
525
.cast <Map <String , dynamic >>()
518
- .transform (StreamingSyncLine .reader)
519
- .takeWhile ((_) => ! aborted);
526
+ .transform (StreamingSyncLine .reader);
520
527
}
521
528
522
529
/// Delays the standard `retryDelay` Duration, but exits early if
0 commit comments