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;
}
#else