LCOV - code coverage report
Current view: top level - service - sync_service_impl.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 2 334 0.6 %
Date: 2022-01-19 17:54:05 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.13