1
1
import 'dart:async' ;
2
2
import 'dart:ffi' ;
3
3
import 'dart:isolate' ;
4
- import 'dart:typed_data' show Uint8List ;
4
+ import 'dart:typed_data' ;
5
5
import 'dart:convert' show utf8;
6
6
7
7
import 'package:ffi/ffi.dart' ;
8
8
9
+ import '../objectbox.dart' ;
9
10
import 'store.dart' ;
10
11
import 'util.dart' ;
11
12
import 'bindings/bindings.dart' ;
@@ -68,6 +69,15 @@ enum SyncConnectionEvent { connected, disconnected }
68
69
69
70
enum SyncLoginEvent { loggedIn, credentialsRejected, unknownError }
70
71
72
+ class SyncChange {
73
+ final int entityId;
74
+ final Type entity;
75
+ final List <int > puts;
76
+ final List <int > removals;
77
+
78
+ SyncChange (this .entityId, this .entity, this .puts, this .removals);
79
+ }
80
+
71
81
/// Sync client is used to provide ObjectBox Sync client capabilities to your application.
72
82
class SyncClient {
73
83
final Store _store;
@@ -107,6 +117,7 @@ class SyncClient {
107
117
_connectionEvents? ._stop ();
108
118
_loginEvents? ._stop ();
109
119
_completionEvents? ._stop ();
120
+ _changeEvents? ._stop ();
110
121
final err = C .sync_close (_cSync);
111
122
_cSync = nullptr;
112
123
syncClientsStorage.remove (_store);
@@ -229,7 +240,7 @@ class SyncClient {
229
240
/// Subscribe (listen) to the stream to actually start listening to events.
230
241
Stream <SyncConnectionEvent > get connectionEvents {
231
242
if (_connectionEvents == null ) {
232
- // This stream combines events from two C listeners: connect & disconnect.
243
+ // Combine events from two C listeners: connect & disconnect.
233
244
_connectionEvents =
234
245
_SyncListenerGroup <SyncConnectionEvent >('sync-connection' );
235
246
@@ -253,7 +264,7 @@ class SyncClient {
253
264
/// Subscribe (listen) to the stream to actually start listening to events.
254
265
Stream <SyncLoginEvent > get loginEvents {
255
266
if (_loginEvents == null ) {
256
- // This stream combines events from two C listeners: connect & disconnect .
267
+ // Combine events from two C listeners: login & login-failure .
257
268
_loginEvents = _SyncListenerGroup <SyncLoginEvent >('sync-login' );
258
269
259
270
_loginEvents.add (_SyncListenerConfig (
@@ -296,6 +307,78 @@ class SyncClient {
296
307
}
297
308
return _completionEvents.stream;
298
309
}
310
+
311
+ _SyncListenerGroup <List <SyncChange >> /*?*/ _changeEvents;
312
+
313
+ /// Get a broadcast stream of incoming synced data changes.
314
+ ///
315
+ /// Subscribe (listen) to the stream to actually start listening to events.
316
+ Stream <List <SyncChange >> get changeEvents {
317
+ if (_changeEvents == null ) {
318
+ // This stream combines events from two C listeners: connect & disconnect.
319
+ _changeEvents = _SyncListenerGroup <List <SyncChange >>('sync-change' );
320
+
321
+ // create a map from Entity ID to Entity type (dart class)
322
+ final entityTypesById = < int , Type > {};
323
+ _store.defs.bindings.forEach ((Type entity, EntityDefinition entityDef) =>
324
+ entityTypesById[entityDef.model.id.id] = entity);
325
+
326
+ _changeEvents.add (_SyncListenerConfig (
327
+ (int nativePort) => C .dart_sync_listener_change (ptr, nativePort),
328
+ (syncChanges, controller) {
329
+ if (syncChanges is ! List ) {
330
+ controller.addError (Exception (
331
+ 'Received invalid data type from the core notification: (${syncChanges .runtimeType }) $syncChanges ' ));
332
+ return ;
333
+ }
334
+
335
+ // List<SyncChange> is flattened to List<dynamic>, with SyncChange object
336
+ // properties always coming in groups of three (entityId, puts, removals)
337
+ const numProperties = 3 ;
338
+ if (syncChanges.length % numProperties != 0 ) {
339
+ controller.addError (Exception (
340
+ 'Received invalid list length from the core notification: (${syncChanges .runtimeType }) $syncChanges ' ));
341
+ return ;
342
+ }
343
+
344
+ final changes = < SyncChange > [];
345
+ for (var i = 0 ; i < syncChanges.length / numProperties; i++ ) {
346
+ final entityId = syncChanges[i * numProperties + 0 ];
347
+ final putsBytes = syncChanges[i * numProperties + 1 ];
348
+ final removalsBytes = syncChanges[i * numProperties + 2 ];
349
+
350
+ final entityType = entityTypesById[entityId];
351
+ if (entityType == null ) {
352
+ controller.addError (Exception (
353
+ 'Received sync change notification for an unknown entity ID $entityId ' ));
354
+ return ;
355
+ }
356
+
357
+ if (entityId is ! int ||
358
+ putsBytes is ! Uint8List ||
359
+ removalsBytes is ! Uint8List ) {
360
+ controller.addError (Exception (
361
+ 'Received invalid list items format from the core notification at i=${i }: '
362
+ 'entityId = (${entityId .runtimeType }) $entityId ; '
363
+ 'putsBytes = (${putsBytes .runtimeType }) $putsBytes ; '
364
+ 'removalsBytes = (${removalsBytes .runtimeType }) $removalsBytes ' ));
365
+ return ;
366
+ }
367
+
368
+ changes.add (SyncChange (
369
+ entityId,
370
+ entityType,
371
+ Uint64List .view (putsBytes.buffer).toList (),
372
+ Uint64List .view (removalsBytes.buffer).toList ()));
373
+ }
374
+
375
+ controller.add (changes);
376
+ }));
377
+
378
+ _changeEvents.finish ();
379
+ }
380
+ return _changeEvents.stream;
381
+ }
299
382
}
300
383
301
384
/// Configuration for _SyncListenerGroup, setting up a single native listener.
@@ -368,7 +451,7 @@ class _SyncListenerGroup<StreamValueType> {
368
451
369
452
// Start the native listener.
370
453
final cListener = config.cListenerInit (receivePort.sendPort.nativePort);
371
- if (cListener == null ) {
454
+ if (cListener == null || cListener == nullptr ) {
372
455
hasError = true ;
373
456
} else {
374
457
_cListeners.add (cListener);
@@ -447,44 +530,3 @@ class Sync {
447
530
return client;
448
531
}
449
532
}
450
-
451
- /* BACKUP: Sync change listener async callback message handling
452
- ReceivePort()..listen((syncChanges) {
453
- if (syncChanges is! List) {
454
- observer.controller.addError(Exception(
455
- 'Received invalid data type from the core notification: (${syncChanges.runtimeType}) $syncChanges'));
456
- return;
457
- }
458
-
459
- // List<SyncChange> is flattened to List<dynamic>, with SyncChange object
460
- // properties always coming in groups of three (entityId, puts, removals)
461
- const numProperties = 3;
462
- if (syncChanges.length % numProperties != 0) {
463
- observer.controller.addError(Exception(
464
- 'Received invalid list length from the core notification: (${syncChanges.runtimeType}) $syncChanges'));
465
- return;
466
- }
467
-
468
- for (var i = 0; i < syncChanges.length / numProperties; i++) {
469
- final entityId = syncChanges[i * numProperties + 0];
470
- final putsBytes = syncChanges[i * numProperties + 1];
471
- final removalsBytes = syncChanges[i * numProperties + 2];
472
-
473
- if (entityId is! int ||
474
- putsBytes is! Uint8List ||
475
- removalsBytes is! Uint8List) {
476
- observer.controller.addError(Exception(
477
- 'Received invalid list items format from the core notification at i=${i}: '
478
- 'entityId = (${entityId.runtimeType}) $entityId; '
479
- 'putsBytes = (${putsBytes.runtimeType}) $putsBytes; '
480
- 'removalsBytes = (${removalsBytes.runtimeType}) $removalsBytes'));
481
- return;
482
- }
483
-
484
- final puts = Uint64List.view(putsBytes.buffer).toList();
485
- final removals = Uint64List.view(removalsBytes.buffer).toList();
486
-
487
- // forward the event with entityId, puts & removals
488
- }
489
- });
490
- */
0 commit comments