@@ -53,7 +53,7 @@ class StreamingSyncImplementation implements StreamingSync {
5353
5454 late final http.Client _client;
5555
56- final StreamController <String ? > _localPingController =
56+ final StreamController <Null > _localPingController =
5757 StreamController .broadcast ();
5858
5959 final Duration retryDelay;
@@ -340,96 +340,19 @@ class StreamingSyncImplementation implements StreamingSync {
340340 }
341341
342342 _updateStatus (connected: true , connecting: false );
343- if (line is Checkpoint ) {
344- targetCheckpoint = line;
345- final Set <String > bucketsToDelete = {...bucketSet};
346- final Set <String > newBuckets = {};
347- for (final checksum in line.checksums) {
348- newBuckets.add (checksum.bucket);
349- bucketsToDelete.remove (checksum.bucket);
350- }
351- bucketSet = newBuckets;
352- await adapter.removeBuckets ([...bucketsToDelete]);
353- _updateStatus (downloading: true );
354- } else if (line is StreamingSyncCheckpointComplete ) {
355- final result = await adapter.syncLocalDatabase (targetCheckpoint! );
356- if (! result.checkpointValid) {
357- // This means checksums failed. Start again with a new checkpoint.
358- // TODO: better back-off
359- // await new Promise((resolve) => setTimeout(resolve, 50));
360- return false ;
361- } else if (! result.ready) {
362- // Checksums valid, but need more data for a consistent checkpoint.
363- // Continue waiting.
364- } else {
365- appliedCheckpoint = targetCheckpoint;
366-
367- _updateStatus (
368- downloading: false ,
369- downloadError: _noError,
370- lastSyncedAt: DateTime .now ());
371- }
372-
373- validatedCheckpoint = targetCheckpoint;
374- } else if (line is StreamingSyncCheckpointDiff ) {
375- // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint
376- if (targetCheckpoint == null ) {
377- throw PowerSyncProtocolException (
378- 'Checkpoint diff without previous checkpoint' );
379- }
380- _updateStatus (downloading: true );
381- final diff = line;
382- final Map <String , BucketChecksum > newBuckets = {};
383- for (var checksum in targetCheckpoint.checksums) {
384- newBuckets[checksum.bucket] = checksum;
385- }
386- for (var checksum in diff.updatedBuckets) {
387- newBuckets[checksum.bucket] = checksum;
388- }
389- for (var bucket in diff.removedBuckets) {
390- newBuckets.remove (bucket);
391- }
392-
393- final newCheckpoint = Checkpoint (
394- lastOpId: diff.lastOpId,
395- checksums: [...newBuckets.values],
396- writeCheckpoint: diff.writeCheckpoint);
397- targetCheckpoint = newCheckpoint;
398-
399- bucketSet = Set .from (newBuckets.keys);
400- await adapter.removeBuckets (diff.removedBuckets);
401- adapter.setTargetCheckpoint (targetCheckpoint);
402- } else if (line is SyncBucketData ) {
403- _updateStatus (downloading: true );
404- await adapter.saveSyncData (SyncDataBatch ([line]));
405- } else if (line is StreamingSyncKeepalive ) {
406- if (line.tokenExpiresIn == 0 ) {
407- // Token expired already - stop the connection immediately
408- invalidCredentialsCallback? .call ().ignore ();
409- break ;
410- } else if (line.tokenExpiresIn <= 30 ) {
411- // Token expires soon - refresh it in the background
412- if (credentialsInvalidation == null &&
413- invalidCredentialsCallback != null ) {
414- credentialsInvalidation = invalidCredentialsCallback !().then ((_) {
415- // Token has been refreshed - we should restart the connection.
416- haveInvalidated = true ;
417- // trigger next loop iteration ASAP, don't wait for another
418- // message from the server.
419- _localPingController.add (null );
420- }, onError: (_) {
421- // Token refresh failed - retry on next keepalive.
422- credentialsInvalidation = null ;
423- });
343+ switch (line) {
344+ case Checkpoint ():
345+ targetCheckpoint = line;
346+ final Set <String > bucketsToDelete = {...bucketSet};
347+ final Set <String > newBuckets = {};
348+ for (final checksum in line.checksums) {
349+ newBuckets.add (checksum.bucket);
350+ bucketsToDelete.remove (checksum.bucket);
424351 }
425- }
426- } else {
427- if (targetCheckpoint == appliedCheckpoint) {
428- _updateStatus (
429- downloading: false ,
430- downloadError: _noError,
431- lastSyncedAt: DateTime .now ());
432- } else if (validatedCheckpoint == targetCheckpoint) {
352+ bucketSet = newBuckets;
353+ await adapter.removeBuckets ([...bucketsToDelete]);
354+ _updateStatus (downloading: true );
355+ case StreamingSyncCheckpointComplete ():
433356 final result = await adapter.syncLocalDatabase (targetCheckpoint! );
434357 if (! result.checkpointValid) {
435358 // This means checksums failed. Start again with a new checkpoint.
@@ -447,7 +370,85 @@ class StreamingSyncImplementation implements StreamingSync {
447370 downloadError: _noError,
448371 lastSyncedAt: DateTime .now ());
449372 }
450- }
373+
374+ validatedCheckpoint = targetCheckpoint;
375+ case StreamingSyncCheckpointDiff ():
376+ // TODO: It may be faster to just keep track of the diff, instead of the entire checkpoint
377+ if (targetCheckpoint == null ) {
378+ throw PowerSyncProtocolException (
379+ 'Checkpoint diff without previous checkpoint' );
380+ }
381+ _updateStatus (downloading: true );
382+ final diff = line;
383+ final Map <String , BucketChecksum > newBuckets = {};
384+ for (var checksum in targetCheckpoint.checksums) {
385+ newBuckets[checksum.bucket] = checksum;
386+ }
387+ for (var checksum in diff.updatedBuckets) {
388+ newBuckets[checksum.bucket] = checksum;
389+ }
390+ for (var bucket in diff.removedBuckets) {
391+ newBuckets.remove (bucket);
392+ }
393+
394+ final newCheckpoint = Checkpoint (
395+ lastOpId: diff.lastOpId,
396+ checksums: [...newBuckets.values],
397+ writeCheckpoint: diff.writeCheckpoint);
398+ targetCheckpoint = newCheckpoint;
399+
400+ bucketSet = Set .from (newBuckets.keys);
401+ await adapter.removeBuckets (diff.removedBuckets);
402+ adapter.setTargetCheckpoint (targetCheckpoint);
403+ case SyncBucketData ():
404+ _updateStatus (downloading: true );
405+ await adapter.saveSyncData (SyncDataBatch ([line]));
406+ case StreamingSyncKeepalive ():
407+ if (line.tokenExpiresIn == 0 ) {
408+ // Token expired already - stop the connection immediately
409+ invalidCredentialsCallback? .call ().ignore ();
410+ break ;
411+ } else if (line.tokenExpiresIn <= 30 ) {
412+ // Token expires soon - refresh it in the background
413+ if (credentialsInvalidation == null &&
414+ invalidCredentialsCallback != null ) {
415+ credentialsInvalidation = invalidCredentialsCallback !().then ((_) {
416+ // Token has been refreshed - we should restart the connection.
417+ haveInvalidated = true ;
418+ // trigger next loop iteration ASAP, don't wait for another
419+ // message from the server.
420+ _localPingController.add (null );
421+ }, onError: (_) {
422+ // Token refresh failed - retry on next keepalive.
423+ credentialsInvalidation = null ;
424+ });
425+ }
426+ }
427+ case null : // Local ping
428+ if (targetCheckpoint == appliedCheckpoint) {
429+ _updateStatus (
430+ downloading: false ,
431+ downloadError: _noError,
432+ lastSyncedAt: DateTime .now ());
433+ } else if (validatedCheckpoint == targetCheckpoint) {
434+ final result = await adapter.syncLocalDatabase (targetCheckpoint! );
435+ if (! result.checkpointValid) {
436+ // This means checksums failed. Start again with a new checkpoint.
437+ // TODO: better back-off
438+ // await new Promise((resolve) => setTimeout(resolve, 50));
439+ return false ;
440+ } else if (! result.ready) {
441+ // Checksums valid, but need more data for a consistent checkpoint.
442+ // Continue waiting.
443+ } else {
444+ appliedCheckpoint = targetCheckpoint;
445+
446+ _updateStatus (
447+ downloading: false ,
448+ downloadError: _noError,
449+ lastSyncedAt: DateTime .now ());
450+ }
451+ }
451452 }
452453
453454 if (haveInvalidated) {
@@ -458,7 +459,8 @@ class StreamingSyncImplementation implements StreamingSync {
458459 return true ;
459460 }
460461
461- Stream <Object ?> streamingSyncRequest (StreamingSyncRequest data) async * {
462+ Stream <StreamingSyncLine ?> streamingSyncRequest (
463+ StreamingSyncRequest data) async * {
462464 final credentials = await credentialsCallback ();
463465 if (credentials == null ) {
464466 throw CredentialsException ('Not logged in' );
@@ -498,7 +500,7 @@ class StreamingSyncImplementation implements StreamingSync {
498500 if (aborted) {
499501 break ;
500502 }
501- yield parseStreamingSyncLine (line as Map <String , dynamic >);
503+ yield StreamingSyncLine . fromJson (line as Map <String , dynamic >);
502504 }
503505 }
504506
0 commit comments