private async Task RunSyncOperationAsync(int retry, CancellationToken cancellationToken) #endif { long lastSyncCount = Local.GetLastSyncCount(IdentityId, DatasetName); #if !(BCL35 || UNITY) ExceptionDispatchInfo capturedException = null; #endif // if dataset is deleted locally, push it to remote if (lastSyncCount == -1) { try { #if BCL35 || UNITY Remote.DeleteDataset(DatasetName); #else await Remote.DeleteDatasetAsync(DatasetName, cancellationToken).ConfigureAwait(false); #endif } catch (DatasetNotFoundException) { //Ignore the exception here, since the dataset was local only } catch (Exception e) { _logger.InfoFormat("{0} , dataset : {1}", e.Message, this.DatasetName); EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(e, options); #else FireSyncFailureEvent(e); #endif return; } Local.PurgeDataset(IdentityId, DatasetName); _logger.InfoFormat("OnSyncSuccess: dataset delete is pushed to remote - {0}", this.DatasetName); EndSynchronizeAndCleanup(); #if UNITY FireSyncSuccessEvent(new List <Record>(), options); #else FireSyncSuccessEvent(new List <Record>()); #endif return; } // get latest modified records from remote _logger.InfoFormat("Get latest modified records since {0} for dataset {1}", lastSyncCount, this.DatasetName); DatasetUpdates datasetUpdates = null; try { #if BCL35 || UNITY datasetUpdates = Remote.ListUpdates(DatasetName, lastSyncCount); #else datasetUpdates = await Remote.ListUpdatesAsync(DatasetName, lastSyncCount, cancellationToken).ConfigureAwait(false); #endif } catch (Exception listUpdatesException) { _logger.Error(listUpdatesException, string.Empty); EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(listUpdatesException, options); #else FireSyncFailureEvent(listUpdatesException); #endif return; } if (datasetUpdates != null && datasetUpdates.MergedDatasetNameList.Count != 0 && this.OnDatasetMerged != null) { bool resume = this.OnDatasetMerged(this, datasetUpdates.MergedDatasetNameList); if (resume) { if (retry == 0) { EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(new SyncManagerException("Out of retries"), options); #else FireSyncFailureEvent(new SyncManagerException("Out of retries")); #endif } else { #if BCL35 this.RunSyncOperation(--retry); #elif UNITY this.RunSyncOperation(--retry, options); #else await this.RunSyncOperationAsync(--retry, cancellationToken).ConfigureAwait(false); #endif } return; } else { _logger.InfoFormat("OnSyncFailure: Manual Cancel"); EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(new SyncManagerException("Manual cancel"), options); #else FireSyncFailureEvent(new SyncManagerException("Manual cancel")); #endif return; } } // if the dataset doesn't exist or is deleted, trigger onDelete if (lastSyncCount != 0 && !datasetUpdates.Exists || datasetUpdates.Deleted && this.OnDatasetDeleted != null) { bool resume = this.OnDatasetDeleted(this); if (resume) { // remove both records and metadata Local.DeleteDataset(IdentityId, DatasetName); Local.PurgeDataset(IdentityId, DatasetName); _logger.InfoFormat("OnSyncSuccess"); EndSynchronizeAndCleanup(); #if UNITY FireSyncSuccessEvent(new List <Record>(), options); #else FireSyncSuccessEvent(new List <Record>()); #endif return; } else { _logger.InfoFormat("OnSyncFailure"); EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(new SyncManagerException("Manual cancel"), options); #else FireSyncFailureEvent(new SyncManagerException("Manual cancel")); #endif return; } } lastSyncCount = datasetUpdates.SyncCount; List <Record> remoteRecords = datasetUpdates.Records; if (remoteRecords.Count != 0) { // if conflict, prompt developer/user with callback List <SyncConflict> conflicts = new List <SyncConflict>(); List <Record> conflictRecords = new List <Record>(); foreach (Record remoteRecord in remoteRecords) { Record localRecord = Local.GetRecord(IdentityId, DatasetName, remoteRecord.Key); // only when local is changed and its value is different if (localRecord != null && localRecord.IsModified && !StringUtils.Equals(localRecord.Value, remoteRecord.Value)) { conflicts.Add(new SyncConflict(remoteRecord, localRecord)); conflictRecords.Add(remoteRecord); } } // retaining only non-conflict records remoteRecords.RemoveAll(t => conflictRecords.Contains(t)); if (conflicts.Count > 0) { _logger.InfoFormat("{0} records in conflict!", conflicts.Count); bool syncConflictResult = false; if (this.OnSyncConflict == null) { // delegate is not implemented so the conflict resolution is applied syncConflictResult = this.ResolveConflictsWithDefaultPolicy(conflicts); } else { syncConflictResult = this.OnSyncConflict(this, conflicts); } if (!syncConflictResult) { _logger.InfoFormat("User cancelled conflict resolution"); EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(new OperationCanceledException("User cancelled conflict resolution"), options); #else FireSyncFailureEvent(new OperationCanceledException("User cancelled conflict resolution")); #endif return; } } // save to local if (remoteRecords.Count > 0) { _logger.InfoFormat("Save {0} records to local", remoteRecords.Count); Local.PutRecords(IdentityId, DatasetName, remoteRecords); } // new last sync count _logger.InfoFormat("Updated sync count {0}", datasetUpdates.SyncCount); Local.UpdateLastSyncCount(IdentityId, DatasetName, datasetUpdates.SyncCount); } // push changes to remote List <Record> localChanges = this.ModifiedRecords; long minPatchSyncCount = lastSyncCount; foreach (Record r in localChanges) { //track the max sync count if (r.SyncCount < minPatchSyncCount) { minPatchSyncCount = r.SyncCount; } } if (localChanges.Count != 0) { _logger.InfoFormat("Push {0} records to remote", localChanges.Count); try { #if BCL35 || UNITY List <Record> result = Remote.PutRecords(DatasetName, localChanges, datasetUpdates.SyncSessionToken); #else List <Record> result = await Remote.PutRecordsAsync(DatasetName, localChanges, datasetUpdates.SyncSessionToken, cancellationToken).ConfigureAwait(false); #endif // update local meta data Local.ConditionallyPutRecords(IdentityId, DatasetName, result, localChanges); // verify the server sync count is increased exactly by one, aka no // other updates were made during this update. long newSyncCount = 0; foreach (Record record in result) { newSyncCount = newSyncCount < record.SyncCount ? record.SyncCount : newSyncCount; } if (newSyncCount == lastSyncCount + 1) { _logger.InfoFormat("Updated sync count {0}", newSyncCount); Local.UpdateLastSyncCount(IdentityId, DatasetName, newSyncCount); } _logger.InfoFormat("OnSyncSuccess"); EndSynchronizeAndCleanup(); #if UNITY FireSyncSuccessEvent(remoteRecords, options); #else FireSyncSuccessEvent(remoteRecords); #endif return; } catch (DataConflictException e) { _logger.InfoFormat("Conflicts detected when pushing changes to remote: {0}", e.Message); if (retry == 0) { EndSynchronizeAndCleanup(); #if UNITY FireSyncFailureEvent(e, options); #else FireSyncFailureEvent(e); #endif } else { //it's possible there is a local dirty record with a stale sync count this will fix it if (lastSyncCount > minPatchSyncCount) { Local.UpdateLastSyncCount(IdentityId, DatasetName, minPatchSyncCount); } #if BCL35 RunSyncOperation(--retry); } return; } #elif UNITY RunSyncOperation(--retry, options); } return; }
private async Task<DatasetUpdates> PopulateListUpdatesAsync(string datasetName, long lastSyncCount, List<Record> records, string nextToken, CancellationToken cancellationToken) { ListRecordsRequest request = new ListRecordsRequest(); request.IdentityPoolId = identityPoolId; request.IdentityId = this.GetCurrentIdentityId(); request.DatasetName = datasetName; request.LastSyncCount = lastSyncCount; // mark it large enough to reduce # of requests request.MaxResults = 1024; request.NextToken = nextToken; ListRecordsResponse listRecordsResponse = await client.ListRecordsAsync(request, cancellationToken).ConfigureAwait(false); foreach (Amazon.CognitoSync.Model.Record remoteRecord in listRecordsResponse.Records) { records.Add(ModelToRecord(remoteRecord)); } // update last evaluated key nextToken = listRecordsResponse.NextToken; if (nextToken != null) await PopulateListUpdatesAsync(datasetName, lastSyncCount, records, nextToken, cancellationToken).ConfigureAwait(false); DatasetUpdates updates = new DatasetUpdates( datasetName, records, listRecordsResponse.DatasetSyncCount, listRecordsResponse.SyncSessionToken, listRecordsResponse.DatasetExists, listRecordsResponse.DatasetDeletedAfterRequestedSyncCount, listRecordsResponse.MergedDatasetNames ); return updates; }
private void RunSyncOperationAsync(int retry) { long lastSyncCount = _local.GetLastSyncCount(GetIdentityId(), _datasetName); // if dataset is deleted locally, push it to remote if (lastSyncCount == -1) { _remote.DeleteDatasetAsync(_datasetName, (cognitoResult) => { if (cognitoResult.Exception != null) { if (cognitoResult.Exception is DatasetNotFoundException) { _logger.Error(cognitoResult.Exception, "Dataset was local-only, it's safe to ignore the service exception. Continuing."); } else { Exception e = cognitoResult.Exception; _logger.InfoFormat("{0} , dataset : {1}", e.Message, this._datasetName); EndSynchronizeAndCleanup(); FireSyncFailureEvent(e); return; } } _local.PurgeDataset(GetIdentityId(), _datasetName); _logger.InfoFormat("OnSyncSuccess: dataset delete is pushed to remote - {0}", this._datasetName); EndSynchronizeAndCleanup(); FireSyncSuccessEvent(new List <Record>()); return; }); return; } // get latest modified records from remote _logger.InfoFormat("get latest modified records since {0} for dataset {1}", lastSyncCount, this._datasetName); _remote.ListUpdatesAsync(_datasetName, lastSyncCount, (cognitoResult) => { DatasetUpdates datasetUpdates = cognitoResult.Response; Exception listUpdatesException = cognitoResult.Exception; if (datasetUpdates == null || listUpdatesException != null) { _logger.Error(listUpdatesException, string.Empty); EndSynchronizeAndCleanup(); FireSyncFailureEvent(listUpdatesException); return; } if (datasetUpdates.MergedDatasetNameList.Count != 0 && this.OnDatasetMerged != null) { bool resume = this.OnDatasetMerged(this, datasetUpdates.MergedDatasetNameList); if (resume) { if (retry == 0) { EndSynchronizeAndCleanup(); FireSyncFailureEvent(new SyncManagerException("Out of retries")); } else { this.RunSyncOperationAsync(--retry); } return; } else { _logger.InfoFormat("OnSyncFailure: Manual Cancel"); EndSynchronizeAndCleanup(); FireSyncFailureEvent(new SyncManagerException("Manual cancel")); return; } } // if the dataset doesn't exist or is deleted, trigger onDelete if (lastSyncCount != 0 && !datasetUpdates.Exists || datasetUpdates.Deleted && this.OnDatasetDeleted != null) { bool resume = this.OnDatasetDeleted(this); if (resume) { // remove both records and metadata _local.DeleteDataset(GetIdentityId(), _datasetName); _local.PurgeDataset(GetIdentityId(), _datasetName); _logger.InfoFormat("OnSyncSuccess"); EndSynchronizeAndCleanup(); FireSyncSuccessEvent(new List <Record>()); return; } else { _logger.InfoFormat("OnSyncFailure"); EndSynchronizeAndCleanup(); FireSyncFailureEvent(new SyncManagerException("Manual cancel")); return; } } lastSyncCount = datasetUpdates.SyncCount; List <Record> remoteRecords = datasetUpdates.Records; if (remoteRecords.Count != 0) { // if conflict, prompt developer/user with callback List <SyncConflict> conflicts = new List <SyncConflict>(); List <Record> conflictRecords = new List <Record>(); foreach (Record remoteRecord in remoteRecords) { Record localRecord = _local.GetRecord(GetIdentityId(), _datasetName, remoteRecord.Key); // only when local is changed and its value is different if (localRecord != null && localRecord.IsModified && !StringUtils.Equals(localRecord.Value, remoteRecord.Value)) { conflicts.Add(new SyncConflict(remoteRecord, localRecord)); conflictRecords.Add(remoteRecord); } } // retaining only non-conflict records remoteRecords.RemoveAll(t => conflictRecords.Contains(t)); if (conflicts.Count > 0) { _logger.InfoFormat("{0} records in conflict!", conflicts.Count); bool syncConflictResult = false; if (this.OnSyncConflict == null) { // delegate is not implemented so the conflict resolution is applied syncConflictResult = this.ResolveConflictsWithDefaultPolicy(conflicts); } else { syncConflictResult = this.OnSyncConflict(this, conflicts); } if (!syncConflictResult) { _logger.InfoFormat("User cancelled conflict resolution"); EndSynchronizeAndCleanup(); FireSyncFailureEvent(new OperationCanceledException("User cancelled conflict resolution")); return; } } // save to local if (remoteRecords.Count > 0) { _logger.InfoFormat("save {0} records to local", remoteRecords.Count); _local.PutRecords(GetIdentityId(), _datasetName, remoteRecords); } // new last sync count _logger.InfoFormat("updated sync count {0}", datasetUpdates.SyncCount); _local.UpdateLastSyncCount(GetIdentityId(), _datasetName, datasetUpdates.SyncCount); } // push changes to remote List <Record> localChanges = this.GetModifiedRecords(); long maxPatchSyncCount = 0; foreach (Record r in localChanges) { //track the max sync count if (r.SyncCount > maxPatchSyncCount) { maxPatchSyncCount = r.SyncCount; } } if (localChanges.Count != 0) { _logger.InfoFormat("push {0} records to remote", localChanges.Count); _remote.PutRecordsAsync(_datasetName, localChanges, datasetUpdates.SyncSessionToken, (putRecordsResult) => { Exception putRecordsException = putRecordsResult.Exception; if (putRecordsException != null) { if (putRecordsException is DataConflictException) { _logger.InfoFormat("Conflicts detected when pushing changes to remote: {0}", putRecordsException.Message); if (retry == 0) { EndSynchronizeAndCleanup(); FireSyncFailureEvent(putRecordsResult.Exception); } else { //it's possible there is a local dirty record with a stale sync count this will fix it if (lastSyncCount > maxPatchSyncCount) { _local.UpdateLastSyncCount(GetIdentityId(), _datasetName, maxPatchSyncCount); } this.RunSyncOperationAsync(--retry); } return; } else { _logger.InfoFormat("OnSyncFailure {0}", putRecordsException.Message); EndSynchronizeAndCleanup(); FireSyncFailureEvent(putRecordsException); return; } } List <Record> result = putRecordsResult.Response; // update local meta data _local.ConditionallyPutRecords(GetIdentityId(), _datasetName, result, localChanges); // verify the server sync count is increased exactly by one, aka no // other updates were made during this update. long newSyncCount = 0; foreach (Record record in result) { newSyncCount = newSyncCount < record.SyncCount ? record.SyncCount : newSyncCount; } if (newSyncCount == lastSyncCount + 1) { _logger.InfoFormat("updated sync count {0}", newSyncCount); _local.UpdateLastSyncCount(GetIdentityId(), _datasetName, newSyncCount); } _logger.InfoFormat("OnSyncSuccess"); EndSynchronizeAndCleanup(); FireSyncSuccessEvent(remoteRecords); return; }); } else { _logger.InfoFormat("OnSyncSuccess"); EndSynchronizeAndCleanup(); FireSyncSuccessEvent(remoteRecords); return; } }); }