A model class which stores a list of updated dataset.
        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;
                }
示例#2
0
        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;
                }
            });
        }