Line data Source code
1 : import 'dart:async';
2 : import 'dart:collection';
3 : import 'dart:convert';
4 :
5 : import 'package:at_client/at_client.dart';
6 : import 'package:at_client/src/exception/at_client_error_codes.dart';
7 : import 'package:at_client/src/listener/at_sign_change_listener.dart';
8 : import 'package:at_client/src/listener/switch_at_sign_event.dart';
9 : import 'package:at_client/src/response/default_response_parser.dart';
10 : import 'package:at_client/src/response/json_utils.dart';
11 : import 'package:at_client/src/service/notification_service_impl.dart';
12 : import 'package:at_client/src/service/sync_service.dart';
13 : import 'package:at_client/src/util/network_util.dart';
14 : import 'package:at_client/src/util/sync_util.dart';
15 : import 'package:at_commons/at_builders.dart';
16 : import 'package:at_commons/at_commons.dart';
17 : import 'package:at_lookup/at_lookup.dart';
18 : import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
19 : import 'package:at_utils/at_logger.dart';
20 : import 'package:at_utils/at_utils.dart';
21 : import 'package:cron/cron.dart';
22 : import 'package:uuid/uuid.dart';
23 :
24 : ///A [SyncService] object is used to ensure data in local secondary(e.g mobile device) and cloud secondary are in sync.
25 : class SyncServiceImpl implements SyncService, AtSignChangeListener {
26 : static const _syncRequestThreshold = 3,
27 : _syncRequestTriggerInSeconds = 3,
28 : _syncRunIntervalSeconds = 5,
29 : _queueSize = 5;
30 : late final AtClient _atClient;
31 : late final RemoteSecondary _remoteSecondary;
32 : late final NotificationServiceImpl _statsNotificationListener;
33 : late final Cron _cron;
34 : final _syncRequests = ListQueue<SyncRequest>(_queueSize);
35 0 : static final Map<String, SyncService> _syncServiceMap = {};
36 : bool _syncInProgress = false;
37 :
38 0 : @override
39 0 : bool get isSyncInProgress => _syncInProgress;
40 :
41 : Function? onDone;
42 :
43 : final _logger = AtSignLogger('SyncService');
44 :
45 : late AtClientManager _atClientManager;
46 :
47 0 : static Future<SyncService> create(AtClient atClient,
48 : {required AtClientManager atClientManager}) async {
49 0 : if (_syncServiceMap.containsKey(atClient.getCurrentAtSign())) {
50 0 : return _syncServiceMap[atClient.getCurrentAtSign()]!;
51 : }
52 0 : final syncService = SyncServiceImpl._(atClientManager, atClient);
53 0 : await syncService._statsServiceListener();
54 0 : syncService._scheduleSyncRun();
55 0 : _syncServiceMap[atClient.getCurrentAtSign()!] = syncService;
56 0 : return _syncServiceMap[atClient.getCurrentAtSign()]!;
57 : }
58 :
59 0 : SyncServiceImpl._(AtClientManager atClientManager, AtClient atClient) {
60 0 : _atClientManager = atClientManager;
61 0 : _atClient = atClient;
62 0 : _remoteSecondary = RemoteSecondary(
63 0 : _atClient.getCurrentAtSign()!, _atClient.getPreferences()!);
64 0 : _atClientManager.listenToAtSignChange(this);
65 : }
66 :
67 0 : void _scheduleSyncRun() {
68 0 : _cron = Cron();
69 :
70 0 : _cron.schedule(Schedule.parse('*/$_syncRunIntervalSeconds * * * * *'),
71 0 : () async {
72 : try {
73 0 : await _processSyncRequests();
74 0 : } on Exception catch (e, trace) {
75 0 : _logger.finest(trace);
76 0 : _logger.severe('exception while running process sync: $e');
77 0 : _syncInProgress = false;
78 : }
79 : });
80 : }
81 :
82 0 : @override
83 : void sync({Function? onDone}) {
84 0 : final syncRequest = SyncRequest();
85 0 : syncRequest.onDone = onDone;
86 0 : syncRequest.requestSource = SyncRequestSource.app;
87 0 : syncRequest.requestedOn = DateTime.now().toUtc();
88 0 : syncRequest.result = SyncResult();
89 0 : _addSyncRequestToQueue(syncRequest);
90 : return;
91 : }
92 :
93 : /// Listens on stats notification sent by the cloud secondary server
94 0 : Future<void> _statsServiceListener() async {
95 0 : _statsNotificationListener = await NotificationServiceImpl.create(_atClient)
96 : as NotificationServiceImpl;
97 : // Setting the regex to 'statsNotification' to receive only the notifications
98 : // from stats notification service.
99 0 : _statsNotificationListener
100 0 : .subscribe(regex: 'statsNotification')
101 0 : .listen((notification) async {
102 0 : _logger.finer('got stats notification in sync: ${notification.value}');
103 0 : final serverCommitId = notification.value;
104 : if (serverCommitId != null &&
105 0 : int.parse(serverCommitId) > await _getLocalCommitId()) {
106 0 : final syncRequest = SyncRequest();
107 0 : syncRequest.onDone = _onDone;
108 0 : syncRequest.onError = _onError;
109 0 : syncRequest.requestSource = SyncRequestSource.system;
110 0 : syncRequest.requestedOn = DateTime.now().toUtc();
111 0 : syncRequest.result = SyncResult();
112 0 : _addSyncRequestToQueue(syncRequest);
113 : }
114 : });
115 : }
116 :
117 0 : Future<void> _processSyncRequests() async {
118 0 : _logger.finest('in _processSyncRequests');
119 0 : if (_syncInProgress) {
120 0 : _logger.finer('**** another sync in progress');
121 : return;
122 : }
123 0 : if (!await NetworkUtil.isNetworkAvailable()) {
124 0 : _logger.finer('skipping sync due to network unavailability');
125 : return;
126 : }
127 0 : if (_syncRequests.isEmpty ||
128 0 : (_syncRequests.length < _syncRequestThreshold &&
129 0 : (_syncRequests.isNotEmpty &&
130 0 : DateTime.now()
131 0 : .toUtc()
132 0 : .difference(_syncRequests.elementAt(0).requestedOn)
133 0 : .inSeconds <
134 : _syncRequestTriggerInSeconds))) {
135 0 : _logger.finest('skipping sync - queue length ${_syncRequests.length}');
136 : return;
137 : }
138 0 : final syncRequest = _getSyncRequest();
139 : try {
140 0 : if (await isInSync()) {
141 0 : _logger.finer('server and local are in sync - ${syncRequest._id}');
142 0 : syncRequest.result!
143 0 : ..syncStatus = SyncStatus.success
144 0 : ..lastSyncedOn = DateTime.now().toUtc()
145 0 : ..dataChange = false;
146 0 : _syncComplete(syncRequest);
147 0 : _syncInProgress = false;
148 : return;
149 : }
150 0 : } on AtLookUpException catch (e) {
151 0 : _logger.severe(
152 0 : 'Atlookup exception for sync ${syncRequest._id}. Reason ${e.toString()}');
153 0 : syncRequest.result!.atClientException =
154 0 : AtClientException(e.errorCode, e.errorMessage);
155 0 : _syncError(syncRequest);
156 0 : _syncInProgress = false;
157 : return;
158 : }
159 0 : _syncInProgress = true;
160 0 : final serverCommitId = await _getServerCommitId();
161 0 : await _sync(serverCommitId, syncRequest);
162 0 : _syncComplete(syncRequest);
163 0 : _syncInProgress = false;
164 : }
165 :
166 : /// Fetches the first app request from the queue. If there are no app requests, the first element of the
167 : /// queue is returned.
168 0 : SyncRequest _getSyncRequest() {
169 0 : return _syncRequests.firstWhere(
170 0 : (syncRequest) => syncRequest.requestSource == SyncRequestSource.app,
171 0 : orElse: () => _syncRequests.removeFirst());
172 : }
173 :
174 0 : void _syncError(SyncRequest syncRequest) {
175 0 : if (syncRequest.onError != null) {
176 0 : syncRequest.onError!(syncRequest.result);
177 : }
178 : }
179 :
180 0 : void _syncComplete(SyncRequest syncRequest) {
181 0 : syncRequest.result!.lastSyncedOn = DateTime.now().toUtc();
182 : // If specific onDone callback is set, call specific onDone callback,
183 : // else call the global onDone callback.
184 0 : if (syncRequest.onDone != null &&
185 0 : syncRequest.requestSource == SyncRequestSource.app) {
186 0 : syncRequest.onDone!(syncRequest.result);
187 0 : } else if (onDone != null) {
188 0 : onDone!(syncRequest.result);
189 : }
190 0 : _clearQueue();
191 : }
192 :
193 0 : void _onDone(SyncResult syncResult) {
194 0 : _logger.finer('system sync completed on ${syncResult.lastSyncedOn}');
195 : }
196 :
197 0 : void _onError(SyncResult syncResult) {
198 0 : _logger.severe(
199 0 : 'system sync error ${syncResult.atClientException?.errorMessage}');
200 : }
201 :
202 0 : void _addSyncRequestToQueue(SyncRequest syncRequest) {
203 0 : if (_syncRequests.length == _queueSize) {
204 0 : _syncRequests.removeLast();
205 : }
206 0 : _syncRequests.addLast(syncRequest);
207 : }
208 :
209 0 : void _clearQueue() {
210 0 : _logger.finer('clearing sync queue');
211 0 : _syncRequests.clear();
212 : }
213 :
214 0 : Future<SyncResult> _sync(int serverCommitId, SyncRequest syncRequest) async {
215 0 : var syncResult = syncRequest.result!;
216 : try {
217 0 : _logger.finer('Sync in progress');
218 0 : var lastSyncedEntry = await SyncUtil.getLastSyncedEntry(
219 0 : _atClient.getPreferences()!.syncRegex,
220 0 : atSign: _atClient.getCurrentAtSign()!);
221 : // Get lastSyncedLocalSeq to get the list of uncommitted entries.
222 : var lastSyncedLocalSeq =
223 0 : lastSyncedEntry != null ? lastSyncedEntry.key : -1;
224 0 : var unCommittedEntries = await SyncUtil.getChangesSinceLastCommit(
225 0 : lastSyncedLocalSeq, _atClient.getPreferences()!.syncRegex,
226 0 : atSign: _atClient.getCurrentAtSign()!);
227 0 : var localCommitId = await _getLocalCommitId();
228 0 : if (serverCommitId > localCommitId) {
229 0 : _logger.finer('syncing to local');
230 0 : await _syncFromServer(serverCommitId, localCommitId);
231 : }
232 0 : if (unCommittedEntries.isNotEmpty) {
233 0 : _logger.finer(
234 0 : 'syncing to remote. Total uncommitted entries: ${unCommittedEntries.length}');
235 0 : await _syncToRemote(unCommittedEntries);
236 : }
237 0 : syncResult.lastSyncedOn = DateTime.now().toUtc();
238 0 : syncResult.syncStatus = SyncStatus.success;
239 0 : } on Exception catch (e) {
240 0 : syncResult.atClientException =
241 0 : AtClientException(atClientErrorCodes['SyncException'], e.toString());
242 0 : syncResult.syncStatus = SyncStatus.failure;
243 : }
244 : return syncResult;
245 : }
246 :
247 : /// Syncs the local entries to cloud secondary.
248 0 : Future<void> _syncToRemote(List<CommitEntry> unCommittedEntries) async {
249 0 : var uncommittedEntryBatch = _getUnCommittedEntryBatch(unCommittedEntries);
250 0 : for (var unCommittedEntryList in uncommittedEntryBatch) {
251 : try {
252 0 : var batchRequests = await _getBatchRequests(unCommittedEntryList);
253 0 : var batchResponse = await _sendBatch(batchRequests);
254 0 : for (var entry in batchResponse) {
255 : try {
256 0 : var batchId = entry['id'];
257 0 : var serverResponse = entry['response'];
258 0 : var responseObject = Response.fromJson(serverResponse);
259 0 : var commitId = -1;
260 0 : if (responseObject.data != null) {
261 0 : commitId = int.parse(responseObject.data!);
262 : }
263 0 : var commitEntry = unCommittedEntryList.elementAt(batchId - 1);
264 0 : if (commitId == -1) {
265 0 : _logger.severe(
266 0 : 'update/delete for key ${commitEntry.atKey} failed. Error code ${responseObject.errorCode} error message ${responseObject.errorMessage}');
267 : }
268 :
269 0 : _logger.finer('***batchId:$batchId key: ${commitEntry.atKey}');
270 0 : await SyncUtil.updateCommitEntry(
271 0 : commitEntry, commitId, _atClient.getCurrentAtSign()!);
272 0 : } on Exception catch (e) {
273 0 : _logger.severe(
274 0 : 'exception while updating commit entry for entry:$entry ${e.toString()}');
275 : }
276 : }
277 0 : } on Exception catch (e) {
278 0 : _logger.severe(
279 0 : 'exception while syncing batch: ${e.toString()} batch commit entries: $unCommittedEntryList');
280 : }
281 : }
282 : }
283 :
284 : /// Syncs the cloud secondary changes to local secondary.
285 0 : Future<void> _syncFromServer(int serverCommitId, int localCommitId) async {
286 : // Iterates until serverCommitId is greater than localCommitId are equal.
287 0 : while (serverCommitId > localCommitId) {
288 0 : var syncBuilder = SyncVerbBuilder()
289 0 : ..commitId = localCommitId
290 0 : ..regex = _atClient.getPreferences()!.syncRegex
291 0 : ..limit = _atClient.getPreferences()!.syncPageLimit
292 0 : ..isPaginated = true;
293 0 : _logger.finer('** syncBuilder ${syncBuilder.buildCommand()}');
294 0 : var syncResponse = DefaultResponseParser()
295 0 : .parse(await _remoteSecondary.executeVerb(syncBuilder));
296 :
297 0 : var syncResponseJson = JsonUtils.decodeJson(syncResponse.response);
298 0 : _logger.finest('** syncResponse $syncResponseJson');
299 : // Iterates over each commit
300 0 : await Future.forEach(syncResponseJson,
301 0 : (dynamic serverCommitEntry) => _syncLocal(serverCommitEntry));
302 : // assigning the lastSynced local commit id.
303 0 : localCommitId = await _getLocalCommitId();
304 0 : _logger.finest('**localCommitId $localCommitId');
305 : }
306 : }
307 :
308 0 : Future<List<BatchRequest>> _getBatchRequests(
309 : List<CommitEntry> uncommittedEntries) async {
310 0 : var batchRequests = <BatchRequest>[];
311 : var batchId = 1;
312 0 : for (var entry in uncommittedEntries) {
313 0 : var command = await _getCommand(entry);
314 0 : command = command.replaceAll('cached:', '');
315 0 : command = VerbUtil.replaceNewline(command);
316 0 : var batchRequest = BatchRequest(batchId, command);
317 0 : _logger.finer('batchId:$batchId key:${entry.atKey}');
318 0 : batchRequests.add(batchRequest);
319 0 : batchId++;
320 : }
321 : return batchRequests;
322 : }
323 :
324 0 : Future<String> _getCommand(CommitEntry entry) async {
325 : late String command;
326 : // ignore: missing_enum_constant_in_switch
327 0 : switch (entry.operation) {
328 0 : case CommitOp.UPDATE:
329 0 : var key = entry.atKey;
330 0 : var value = await _atClient.getLocalSecondary()!.keyStore!.get(key);
331 0 : command = 'update:$key ${value?.data}';
332 : break;
333 0 : case CommitOp.DELETE:
334 0 : var key = entry.atKey;
335 0 : command = 'delete:$key';
336 : break;
337 0 : case CommitOp.UPDATE_META:
338 0 : var key = entry.atKey;
339 : var metaData =
340 0 : await _atClient.getLocalSecondary()!.keyStore!.getMeta(key);
341 : if (metaData != null) {
342 0 : key = '$key$_metadataToString(metaData)';
343 : }
344 0 : command = 'update:meta:$key';
345 : break;
346 0 : case CommitOp.UPDATE_ALL:
347 0 : var key = entry.atKey;
348 0 : var value = await _atClient.getLocalSecondary()!.keyStore!.get(key);
349 : var metaData =
350 0 : await _atClient.getLocalSecondary()!.keyStore!.getMeta(key);
351 : var keyGen = '';
352 : if (metaData != null) {
353 0 : keyGen = _metadataToString(metaData);
354 : }
355 0 : keyGen += ':$key';
356 0 : value?.metaData = metaData;
357 0 : command = 'update$keyGen ${value?.data}';
358 : break;
359 : }
360 0 : return command;
361 : }
362 :
363 0 : String _metadataToString(dynamic metadata) {
364 : var metadataStr = '';
365 0 : if (metadata.ttl != null) metadataStr += ':ttl:${metadata.ttl}';
366 0 : if (metadata.ttb != null) metadataStr += ':ttb:${metadata.ttb}';
367 0 : if (metadata.ttr != null) metadataStr += ':ttr:${metadata.ttr}';
368 0 : if (metadata.isCascade != null) {
369 0 : metadataStr += ':ccd:${metadata.isCascade}';
370 : }
371 0 : if (metadata.dataSignature != null) {
372 0 : metadataStr += ':dataSignature:${metadata.dataSignature}';
373 : }
374 0 : if (metadata.isBinary != null) {
375 0 : metadataStr += ':isBinary:${metadata.isBinary}';
376 : }
377 0 : if (metadata.isEncrypted != null) {
378 0 : metadataStr += ':isEncrypted:${metadata.isEncrypted}';
379 : }
380 : return metadataStr;
381 : }
382 :
383 : ///Verifies if local secondary are cloud secondary are in sync.
384 : ///Returns true if local secondary and cloud secondary are in sync; else false.
385 : ///Throws [AtLookUpException] if cloud secondary is not reachable
386 : @override
387 0 : Future<bool> isInSync() async {
388 0 : if (_syncInProgress) {
389 0 : _logger.finest('*** isInSync..sync in progress');
390 : return true;
391 : }
392 0 : var serverCommitId = await _getServerCommitId();
393 0 : var lastSyncedEntry = await SyncUtil.getLastSyncedEntry(
394 0 : _atClient.getPreferences()!.syncRegex,
395 0 : atSign: _atClient.getCurrentAtSign()!);
396 0 : var lastSyncedCommitId = lastSyncedEntry?.commitId;
397 0 : _logger.finest(
398 0 : 'server commit id: $serverCommitId last synced commit id: $lastSyncedCommitId');
399 0 : var lastSyncedLocalSeq = lastSyncedEntry != null ? lastSyncedEntry.key : -1;
400 0 : var unCommittedEntries = await SyncUtil.getChangesSinceLastCommit(
401 0 : lastSyncedLocalSeq, _atClient.getPreferences()!.syncRegex,
402 0 : atSign: _atClient.getCurrentAtSign()!);
403 0 : return SyncUtil.isInSync(
404 : unCommittedEntries, serverCommitId, lastSyncedCommitId);
405 : }
406 :
407 : /// Returns the cloud secondary latest commit id. if null, returns -1.
408 : ///Throws [AtLookUpException] if secondary is not reachable
409 0 : Future<int> _getServerCommitId() async {
410 0 : var _serverCommitId = await SyncUtil.getLatestServerCommitId(
411 0 : _remoteSecondary, _atClient.getPreferences()!.syncRegex);
412 : // If server commit id is null, set to -1;
413 0 : _serverCommitId ??= -1;
414 0 : _logger.info('Returning the serverCommitId $_serverCommitId');
415 : return _serverCommitId;
416 : }
417 :
418 : /// Returns the local commit id. If null, returns -1.
419 0 : Future<int> _getLocalCommitId() async {
420 : // Get lastSynced local commit id.
421 0 : var lastSyncEntry = await SyncUtil.getLastSyncedEntry(
422 0 : _atClient.getPreferences()!.syncRegex,
423 0 : atSign: _atClient.getCurrentAtSign()!);
424 : int localCommitId;
425 : // If lastSyncEntry not null, set localCommitId to lastSyncedEntry.commitId
426 : // Else set to -1.
427 0 : (lastSyncEntry != null && lastSyncEntry.commitId != null)
428 0 : ? localCommitId = lastSyncEntry.commitId!
429 0 : : localCommitId = -1;
430 : return localCommitId;
431 : }
432 :
433 0 : dynamic _sendBatch(List<BatchRequest> requests) async {
434 : var command = 'batch:';
435 0 : command += jsonEncode(requests);
436 0 : command += '\n';
437 0 : var verbResult = await _remoteSecondary.executeCommand(command, auth: true);
438 0 : _logger.finer('batch result:$verbResult');
439 : if (verbResult != null) {
440 0 : verbResult = verbResult.replaceFirst('data:', '');
441 : }
442 0 : return jsonDecode(verbResult!);
443 : }
444 :
445 0 : Future<void> _syncLocal(serverCommitEntry) async {
446 0 : switch (serverCommitEntry['operation']) {
447 0 : case '+':
448 0 : case '#':
449 0 : case '*':
450 0 : var builder = UpdateVerbBuilder()
451 0 : ..atKey = serverCommitEntry['atKey']
452 0 : ..value = serverCommitEntry['value'];
453 0 : builder.operation = UPDATE_ALL;
454 0 : _setMetaData(builder, serverCommitEntry);
455 0 : _logger.finest(
456 0 : 'syncing to local: ${serverCommitEntry['atKey']} commitId:${serverCommitEntry['commitId']}');
457 0 : await _pullToLocal(builder, serverCommitEntry, CommitOp.UPDATE_ALL);
458 : break;
459 0 : case '-':
460 0 : var builder = DeleteVerbBuilder()..atKey = serverCommitEntry['atKey'];
461 0 : _logger.finest(
462 0 : 'syncing to local delete: ${serverCommitEntry['atKey']} commitId:${serverCommitEntry['commitId']}');
463 0 : await _pullToLocal(builder, serverCommitEntry, CommitOp.DELETE);
464 : break;
465 : }
466 : }
467 :
468 0 : List<dynamic> _getUnCommittedEntryBatch(
469 : List<CommitEntry?> uncommittedEntries) {
470 0 : var unCommittedEntryBatch = [];
471 0 : var batchSize = _atClient.getPreferences()!.syncBatchSize, i = 0;
472 0 : var totalEntries = uncommittedEntries.length;
473 0 : var totalBatch = (totalEntries % batchSize == 0)
474 0 : ? totalEntries / batchSize
475 0 : : (totalEntries / batchSize).floor() + 1;
476 : var startIndex = i;
477 0 : while (i < totalBatch) {
478 0 : var endIndex = startIndex + batchSize < totalEntries
479 0 : ? startIndex + batchSize
480 : : totalEntries;
481 0 : var currentBatch = uncommittedEntries.sublist(startIndex, endIndex);
482 0 : unCommittedEntryBatch.add(currentBatch);
483 0 : startIndex += batchSize;
484 0 : i++;
485 : }
486 : return unCommittedEntryBatch;
487 : }
488 :
489 0 : void _setMetaData(builder, serverCommitEntry) {
490 0 : var metaData = serverCommitEntry['metadata'];
491 0 : if (metaData != null && metaData.isNotEmpty) {
492 0 : if (metaData[AT_TTL] != null) builder.ttl = int.parse(metaData[AT_TTL]);
493 0 : if (metaData[AT_TTB] != null) builder.ttb = int.parse(metaData[AT_TTB]);
494 0 : if (metaData[AT_TTR] != null) builder.ttr = int.parse(metaData[AT_TTR]);
495 0 : if (metaData[CCD] != null) {
496 0 : (metaData[CCD].toLowerCase() == 'true')
497 0 : ? builder.ccd = true
498 0 : : builder.ccd = false;
499 : }
500 0 : if (metaData[PUBLIC_DATA_SIGNATURE] != null) {
501 0 : builder.dataSignature = metaData[PUBLIC_DATA_SIGNATURE];
502 : }
503 0 : if (metaData[IS_BINARY] != null) {
504 0 : (metaData[IS_BINARY].toLowerCase() == 'true')
505 0 : ? builder.isBinary = true
506 0 : : builder.isBinary = false;
507 : }
508 0 : if (metaData[IS_ENCRYPTED] != null) {
509 0 : (metaData[IS_ENCRYPTED].toLowerCase() == 'true')
510 0 : ? builder.isEncrypted = true
511 0 : : builder.isEncrypted = false;
512 : }
513 : }
514 : }
515 :
516 0 : Future<void> _pullToLocal(
517 : VerbBuilder builder, serverCommitEntry, CommitOp operation) async {
518 : var verbResult =
519 0 : await _atClient.getLocalSecondary()!.executeVerb(builder, sync: false);
520 : if (verbResult == null) {
521 : return;
522 : }
523 0 : var sequenceNumber = int.parse(verbResult.split(':')[1]);
524 0 : var commitEntry = await (SyncUtil.getCommitEntry(
525 0 : sequenceNumber, _atClient.getCurrentAtSign()!));
526 : if (commitEntry == null) {
527 : return;
528 : }
529 0 : commitEntry.operation = operation;
530 0 : _logger.finest(
531 0 : '*** updating commitId to local ${serverCommitEntry['commitId']}');
532 0 : await SyncUtil.updateCommitEntry(commitEntry, serverCommitEntry['commitId'],
533 0 : _atClient.getCurrentAtSign()!);
534 : }
535 :
536 0 : @override
537 : void listenToAtSignChange(SwitchAtSignEvent switchAtSignEvent) {
538 0 : if (switchAtSignEvent.previousAtClient?.getCurrentAtSign() ==
539 0 : _atClient.getCurrentAtSign()) {
540 : // actions for previous atSign
541 0 : _syncRequests.clear();
542 0 : _logger.finer(
543 0 : 'stopping stats notificationlistener for ${_atClient.getCurrentAtSign()}');
544 0 : _statsNotificationListener.stopAllSubscriptions();
545 0 : _cron.close();
546 0 : _syncServiceMap.remove(_atClient.getCurrentAtSign());
547 : }
548 : }
549 :
550 0 : @override
551 : void setOnDone(Function onDone) {
552 0 : this.onDone = onDone;
553 : }
554 : }
555 :
556 : ///Class to represent sync response.
557 : class SyncResult {
558 : SyncStatus syncStatus = SyncStatus.notStarted;
559 : AtClientException? atClientException;
560 : DateTime? lastSyncedOn;
561 : bool dataChange = true;
562 :
563 0 : @override
564 : String toString() {
565 0 : return 'Sync status: $syncStatus lastSyncedOn: $lastSyncedOn Exception: $atClientException';
566 : }
567 : }
568 :
569 : ///Enum to represent the sync status
570 7 : enum SyncStatus { notStarted, success, failure }
571 :
572 7 : enum SyncRequestSource { app, system }
573 :
574 : class SyncRequest {
575 : late String _id;
576 : SyncRequestSource requestSource = SyncRequestSource.app;
577 : late DateTime requestedOn;
578 : Function? onDone;
579 : Function? onError;
580 : SyncResult? result;
581 :
582 0 : SyncRequest({this.onDone, this.onError}) {
583 0 : _id = Uuid().v4();
584 0 : requestedOn = DateTime.now().toUtc();
585 : }
586 : }
|