/// <summary> /// Get the conflict item /// </summary> /// <returns>Conflict item</returns> public virtual Conflict GetConflict() { if (!HasConflict()) { return(null); } Conflict conflict = null; if (_currentEntryWrapper.IsConflict) { conflict = new SyncConflict { LiveEntity = _liveEntity, LosingEntity = CreateEntity(_currentEntryWrapper.ConflictWrapper, _knownTypesDict), Resolution = (SyncConflictResolution)Enum.Parse(FormatterConstants.SyncConflictResolutionType, _currentEntryWrapper.ConflictDesc, true) }; } else { conflict = new SyncError { LiveEntity = _liveEntity, ErrorEntity = CreateEntity(_currentEntryWrapper.ConflictWrapper, _knownTypesDict), Description = _currentEntryWrapper.ConflictDesc }; } return(conflict); }
/// <summary> /// Get the conflict item /// </summary> /// <returns>Conflict item</returns> public virtual Conflict GetConflict() { if (!HasConflict()) { return(null); } Conflict conflict; if (currentEntryWrapper.IsConflict) { conflict = new SyncConflict { LiveEntity = liveEntity, LosingEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Resolution = (SyncConflictResolution) Enum.Parse(FormatterConstants.SyncConflictResolutionType, currentEntryWrapper.ConflictDesc, true) }; } else { conflict = new SyncError { LiveEntity = liveEntity, ErrorEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Description = currentEntryWrapper.ConflictDesc }; } return(conflict); }
public async Task Save <T>(T item, ESyncStatus syncStatus = ESyncStatus.Success, Exception exception = null, bool update = false) where T : Entity, new() { var c = GetAsyncConnection(); item.SyncStatus = (byte)syncStatus; if (exception != null) { var error = new SyncError() { Date = DateTime.Now, Identity = Guid.NewGuid().ToString(), Message = exception.Message }; item.SyncErrorId = error.Identity; await c.InsertAsync(error); } if (!update && (await c.FindAsync <T>(item.Identity)) == null) { await c.InsertAsync(item); } else { await c.UpdateAsync(item); } }
/// <summary> /// Called by SyncErrorInfo from the ClearSyncError method. Used to remove the error /// from the collection and from the disk /// </summary> ///<param name="error">SyncError</param> internal void ClearSyncError(SyncError error) { using (_saveSyncLock.LockObject()) { _cacheData.RemoveSyncError(error); _storageHandler.ClearSyncError((IsolatedStorageSyncError)error); } }
public void AddSerializedError(SyncError error, IsolatedStorageOfflineContext context) { IsolatedStorageSyncError oldError = (IsolatedStorageSyncError)Collections[error.ErrorEntity.GetType()].MapSyncError((IsolatedStorageOfflineEntity)error.LiveEntity, error, context); SyncErrors.Add(error); if (oldError != null) { ClearSyncError(oldError, context); } }
public void AddSerializedError(SyncError error, WinEightContext context) { SyncError oldError = (SyncError)Collections[error.ErrorEntity.GetType()].MapSyncError((OfflineEntity)error.LiveEntity, error, context); SyncErrors.Add(error); if (oldError != null) { ClearSyncError(oldError, context); } }
/// <summary> /// Clears the sync error and removes it from the error list on the c. /// </summary> public void ClearSyncError() { if (syncError != null) { context.ClearSyncError(syncError); syncError = null; OnPropertyChanged("SyncError"); OnPropertyChanged("HasSyncError"); } }
/// <summary> /// Adds a error to the list of in-memory /// </summary> /// <param name="error"></param> /// <param name="context">Context for the conflict is being added</param> public void AddSyncError(SyncError error, WinEightContext context) { OfflineEntity entity = Collections[error.LiveEntity.GetType()].AddOrUpdateSyncEntity((OfflineEntity)error.LiveEntity); SyncError oldError = Collections[error.LiveEntity.GetType()].MapSyncError(entity, error, context); SyncErrors.Add(error); if (oldError != null) { ClearSyncError(oldError, context); } }
/// <summary> /// Clears the sync error and removes it from the error list on the c. /// </summary> public async Task ClearSyncError() { if (syncError != null) { await context.ClearSyncError(syncError); syncError = null; OnPropertyChanged("SyncError"); OnPropertyChanged("HasSyncError"); } }
/// <summary> /// Sets the sync error, providing the cache data so that the error can be removed if ClearSyncerror /// is called. /// </summary> ///<param name="c">IsolatedStorageOfflineContext</param> /// <param name="se">error to set.</param> internal void SetSyncError(WinEightContext c, SyncError se) { SyncError oldError = this.syncError; this.context = c; this.syncError = se; OnPropertyChanged("SyncError"); if (oldError == null) { OnPropertyChanged("HasSyncError"); } }
/// <summary> /// Sets the sync error, providing the cache data so that the error can be removed if ClearSyncerror /// is called. /// </summary> ///<param name="context">IsolatedStorageOfflineContext</param> /// <param name="syncError">error to set.</param> internal void SetSyncError(IsolatedStorageOfflineContext context, SyncError syncError) { SyncError oldError = this._syncError; this._context = context; this._syncError = syncError; OnPropertyChanged("SyncError"); if (oldError == null) { OnPropertyChanged("HasSyncError"); } }
/// <summary> /// This function loops the rejected entites and sends back a SyncError for each entity. For each entity it does the following /// 1. Retrieve the current version in server. /// 1.a If its null then it copies the primary key to a new object and marks it as tombstone. /// 2. Adds the SyncError to existing list of SyncErrors. /// </summary> /// <param name="sqlProvider"></param> private void ProcessRejectedEntities(SqlSyncProviderService sqlProvider) { if (this._rejectedEntities == null || this._rejectedEntities.Count == 0) { return; } try { List <IOfflineEntity> serverVersions = sqlProvider.GetCurrentServerVersionForEntities(this._rejectedEntities.Keys); if (serverVersions.Count != this._rejectedEntities.Count) { // Ensure we get a server version for each entity we passed throw new InvalidOperationException("Did not get server versions for all rejected entities."); } for (int i = 0; i < this._rejectedEntities.Keys.Count; i++) { IOfflineEntity server = serverVersions[i]; IOfflineEntity client = this._rejectedEntities.Keys.ElementAt(i); if (server == null) { // This means the server didnt contain a version for the entity. Need to send a tombstone back then // create a new object and copy the values over ConstructorInfo constructorInfo = client.GetType().GetConstructor(Type.EmptyTypes); server = (IOfflineEntity)constructorInfo.Invoke(null); // Copy the primary key values over foreach (PropertyInfo info in ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(client.GetType())) { info.SetValue(server, info.GetValue(client, null), null); } server.ServiceMetadata.IsTombstone = true; } SyncError error = new SyncError() { ErrorEntity = client, LiveEntity = server, Description = this._rejectedEntities[client] }; _applyChangesResponse.Errors.Add(error); } } catch (Exception e) { throw new InvalidOperationException("Error in reading server row values. " + e.Message); } }
/// <summary> /// Called by SyncErrorInfo from the ClearSyncError method. Used to remove the error /// from the collection and from the disk /// </summary> ///<param name="error">SyncError</param> internal async Task ClearSyncError(SyncError error) { using (saveSyncLock.LockObject()) { WinEightSyncError winEightSyncError = error as WinEightSyncError; if (winEightSyncError == null) { return; } cacheData.RemoveSyncError(error); await StorageHandler.ClearSyncError(winEightSyncError); } }
private void UpdateTimer_Tick(object sender, EventArgs e) { if (syncErrorText != null) { SyncError.SetError(TimeLabel, syncErrorText); if (syncErrorText.Length > 0) { SyncStatusLabel.Text = "Could not synchronize."; SyncStatusLabel.ForeColor = Color.Red; syncErrorText = null; prevSyncTime = DateTime.MinValue; } } if (prevSyncTime != DateTime.MinValue) { SyncStatusLabel.Text = string.Format("Sync at {0} ({1:0.000} sec)", FormatTime(prevSyncTime), prevAdjustment); SyncStatusLabel.ForeColor = Color.LightGreen; } }
public void AddSyncError(SyncError error, OfflineContext context) { }
public void AddSerializedError(SyncError error, IsolatedStorageOfflineContext context) { }
/// <summary> /// This function loops the rejected entites and sends back a SyncError for each entity. For each entity it does the following /// 1. Retrieve the current version in server. /// 1.a If its null then it copies the primary key to a new object and marks it as tombstone. /// 2. Adds the SyncError to existing list of SyncErrors. /// </summary> /// <param name="sqlProvider"></param> private void ProcessRejectedEntities(SqlSyncProviderService sqlProvider) { if (this._rejectedEntities == null || this._rejectedEntities.Count == 0) { return; } try { List<IOfflineEntity> serverVersions = sqlProvider.GetCurrentServerVersionForEntities(this._rejectedEntities.Keys); if (serverVersions.Count != this._rejectedEntities.Count) { // Ensure we get a server version for each entity we passed throw new InvalidOperationException("Did not get server versions for all rejected entities."); } for (int i = 0; i < this._rejectedEntities.Keys.Count; i++) { IOfflineEntity server = serverVersions[i]; IOfflineEntity client = this._rejectedEntities.Keys.ElementAt(i); if (server == null) { // This means the server didnt contain a version for the entity. Need to send a tombstone back then // create a new object and copy the values over ConstructorInfo constructorInfo = client.GetType().GetConstructor(Type.EmptyTypes); server = (IOfflineEntity)constructorInfo.Invoke(null); // Copy the primary key values over foreach (PropertyInfo info in ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(client.GetType())) { info.SetValue(server, info.GetValue(client, null), null); } server.ServiceMetadata.IsTombstone = true; } SyncError error = new SyncError() { ErrorEntity = client, LiveEntity = server, Description = this._rejectedEntities[client] }; _applyChangesResponse.Errors.Add(error); } } catch (Exception e) { throw new InvalidOperationException("Error in reading server row values. " + e.Message); } }
public abstract SyncError MapSyncError(IsolatedStorageOfflineEntity entity, SyncError error, IsolatedStorageOfflineContext context);
/// <summary> /// Removes the specified sync error /// </summary> /// <param name="error">Error to remove</param> public void RemoveSyncError(SyncError error) { SyncErrors.Remove(error); }
public WinEightSyncError(SyncError error) { this.LiveEntity = error.LiveEntity; this.ErrorEntity = error.ErrorEntity; this.Description = error.Description; }
public IsolatedStorageSyncError(SyncError error) { this.LiveEntity = error.LiveEntity; this.ErrorEntity = error.ErrorEntity; this.Description = error.Description; }
public SyncException(SyncError error, string message) : base(message) { mError = error; }
private async void ClearSyncError(SyncError syncError, WinEightContext context) { RemoveSyncError(syncError); await context.ClearSyncError(syncError); }
public SyncException(SyncError error) : base(error.getMessage()) { mError = error; }
public void RemoveSyncError(SyncError error) { }
public void AddSerializedError(SyncError error, WinEightContext context) { SyncError oldError = (SyncError)Collections[error.ErrorEntity.GetType()].MapSyncError((OfflineEntity)error.LiveEntity, error, context); SyncErrors.Add(error); if (oldError != null) { ClearSyncError(oldError,context); } }
public SyncException(SyncError error, Exception cause) : base(error.getMessage(), cause) { mError = error; }
public void AddSerializedError(SyncError error, OfflineContext context) { }
private void ClearSyncError(SyncError syncError, IsolatedStorageOfflineContext context) { RemoveSyncError(syncError); context.StorageHandler.ClearSyncError((IsolatedStorageSyncError)syncError); }
protected virtual void OnSyncErrorReceived(byte code, SyncError <T> command) { OnCommandReceived(code, command); SyncErrorReceived.Raise(this, new CommandReceivedEventArgs <T, SyncError <T> >(code, command)); }
/// <summary> /// /// </summary> /// <param name="asynchronousResult"></param> private void downloadCallback(IAsyncResult asynchronousResult) { DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; HttpWebRequest request = reqState.request; string callbackId = reqState.options.CallbackId; try { HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult); // send a progress change event DispatchSyncProgress(0, response.ContentLength, 0, callbackId); using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { // create any directories in the path that do not exist string directoryName = getDirectoryName(reqState.options.FilePath); if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName)) { isoFile.CreateDirectory(directoryName); } // make sure we delete the file if it exists if (isoFile.FileExists(reqState.options.FilePath)) { isoFile.DeleteFile(reqState.options.FilePath); } if (!isoFile.FileExists(reqState.options.FilePath)) { var file = isoFile.CreateFile(reqState.options.FilePath); file.Close(); } using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile)) { long totalBytes = response.ContentLength; int bytesRead = 0; using (BinaryReader reader = new BinaryReader(response.GetResponseStream())) { using (BinaryWriter writer = new BinaryWriter(fileStream)) { int BUFFER_SIZE = 1024; byte[] buffer; while (true) { buffer = reader.ReadBytes(BUFFER_SIZE); // fire a progress event ? bytesRead += buffer.Length; if (buffer.Length > 0 && !reqState.isCancelled) { writer.Write(buffer); DispatchSyncProgress(bytesRead, totalBytes, 1, callbackId); } else { writer.Close(); reader.Close(); fileStream.Close(); break; } System.Threading.Thread.Sleep(1); } } } } if (reqState.isCancelled) { isoFile.DeleteFile(reqState.options.FilePath); } } if (reqState.isCancelled) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(AbortError)), callbackId); } else { UnZip unzipper = new UnZip(); string destFilePath = "www/" + reqState.options.FilePath; // at this point, bytesLoaded = bytesTotal so we'll just put the as '1' DispatchSyncProgress(1, 1, 2, callbackId); unzipper.unzip(reqState.options.FilePath, destFilePath, reqState.options.Type); if (reqState.options.CopyCordovaAssets) { copyCordovaAssets(destFilePath); } DispatchSyncProgress(1, 1, 3, callbackId); string result = "{ \"localPath\": \"" + reqState.options.FilePath + "\" , \"Id\" : \"" + reqState.options.Id + "\"}"; DispatchCommandResult(new PluginResult(PluginResult.Status.OK, result), callbackId); } } catch (IsolatedStorageException) { // Trying to write the file somewhere within the IsoStorage. DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } catch (SecurityException) { // Trying to write the file somewhere not allowed. DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } catch (WebException webex) { // TODO: probably need better work here to properly respond with all http status codes back to JS // Right now am jumping through hoops just to detect 404. HttpWebResponse response = (HttpWebResponse)webex.Response; if ((webex.Status == WebExceptionStatus.ProtocolError && response.StatusCode == HttpStatusCode.NotFound) || webex.Status == WebExceptionStatus.UnknownError) { // Weird MSFT detection of 404... seriously... just give us the f(*&#$@ status code as a number ffs!!! // "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away." // FACEPALM // Or just cast it to an int, whiner ... -jm int statusCode = (int)response.StatusCode; string body = ""; using (Stream streamResponse = response.GetResponseStream()) { using (StreamReader streamReader = new StreamReader(streamResponse)) { body = streamReader.ReadToEnd(); } } SyncError ftError = new SyncError(ConnectionError, null, null, statusCode, body); DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError), callbackId); } else { lock (reqState) { if (!reqState.isCancelled) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(ConnectionError)), callbackId); } else { Debug.WriteLine("It happened"); } } } } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } //System.Threading.Thread.Sleep(1000); if (InProcDownloads.ContainsKey(reqState.options.Id)) { InProcDownloads.Remove(reqState.options.Id); } }
private void ResolveConflict(ConflictResult result, SaveData localSave, SaveData cloudSave) { LoadState cloudState; switch (result) { case ConflictResult.Cloud: Debug.Log("SaveGameManager (ResolveConflict) :: Resolving conflict with cloud save!"); if (OverrideLocalSave(cloudSave, localSave)) { m_comparator.ReconcileData(localSave, cloudSave); cloudSave.Save(); cloudState = LoadSystems(cloudSave); if (cloudState == LoadState.OK) { m_saveData = cloudSave; m_syncCompleteCallback(null, SyncState.Successful); } else { //Reset to local LoadSystems(localSave); m_saveData = localSave; //TODO may need more specific errors m_syncCompleteCallback(new SyncError("Failed to load resolved cloud save", ErrorCodes.SaveError), SyncState.Error); } } else { m_syncCompleteCallback(new SyncError("Failed to Override Local Save", ErrorCodes.SaveError), SyncState.Error); } break; case ConflictResult.Local: Debug.Log("SaveGameManager (ResolveConflict) :: Resolving conflict with local save!"); m_comparator.ReconcileData(localSave, cloudSave); localSave.Save(); LoadState localState = LoadSystems(localSave); if (localState == LoadState.OK) { UploadSave(m_syncUser, localSave, delegate(Error error) { if (error != null && error.GetType() != typeof(UploadDisallowedError)) { m_syncCompleteCallback(error, SyncState.Error); } else { m_saveData = localSave; m_syncCompleteCallback(null, SyncState.Successful); } }); } else { //TODO may need more specific errors m_syncCompleteCallback(new SyncError("Failed to load resolved cloud save", ErrorCodes.SaveError), SyncState.Error); } break; case ConflictResult.ForceCloud: SyncError forceCloudError = null; // Make sure we reset force override as otherwise user save file will get overriden again next time // If the flag doesn't exist (upgrading from old save?), just ignore the exception try { cloudSave["User.ForceOverride"] = false; localSave["User.ForceOverride"] = false; } catch (Exception e) { Debug.LogException(e); } if (OverrideLocalSave(cloudSave, localSave)) { cloudSave.Save(); cloudState = LoadSystems(cloudSave); if (cloudState == LoadState.OK) { m_saveData = cloudSave; m_saveData.Save(); } else { //Reset to local LoadSystems(localSave); m_saveData = localSave; //TODO may need more specific errors forceCloudError = new SyncError("Failed to load resolved cloud save", ErrorCodes.SaveError); } } else { m_saveData = localSave; forceCloudError = new SyncError("Failed to Override Local Save", ErrorCodes.SaveError); } // Upload the new save to cloud to delete forceOverride flag UploadSave(m_syncUser, m_saveData, delegate(Error error) { if (error != null) { m_syncCompleteCallback(error, SyncState.Error); } else { m_syncCompleteCallback(forceCloudError, forceCloudError == null ? SyncState.Successful : SyncState.Error); } }); break; } }
/// <summary> /// /// </summary> /// <param name="asynchronousResult"></param> private void downloadCallback(IAsyncResult asynchronousResult) { DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; HttpWebRequest request = reqState.request; string callbackId = reqState.options.CallbackId; try { HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult); // send a progress change event DispatchSyncProgress(0, response.ContentLength, 0, callbackId); using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { // create any directories in the path that do not exist string directoryName = getDirectoryName(reqState.options.FilePath); if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName)) { isoFile.CreateDirectory(directoryName); } // make sure we delete the file if it exists if(isoFile.FileExists(reqState.options.FilePath)) { isoFile.DeleteFile(reqState.options.FilePath); } if (!isoFile.FileExists(reqState.options.FilePath)) { var file = isoFile.CreateFile(reqState.options.FilePath); file.Close(); } using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile)) { long totalBytes = response.ContentLength; int bytesRead = 0; using (BinaryReader reader = new BinaryReader(response.GetResponseStream())) { using (BinaryWriter writer = new BinaryWriter(fileStream)) { int BUFFER_SIZE = 1024; byte[] buffer; while (true) { buffer = reader.ReadBytes(BUFFER_SIZE); // fire a progress event ? bytesRead += buffer.Length; if (buffer.Length > 0 && !reqState.isCancelled) { writer.Write(buffer); DispatchSyncProgress(bytesRead, totalBytes, 1, callbackId); } else { writer.Close(); reader.Close(); fileStream.Close(); break; } System.Threading.Thread.Sleep(1); } } } } if (reqState.isCancelled) { isoFile.DeleteFile(reqState.options.FilePath); } } if (reqState.isCancelled) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(AbortError)), callbackId); } else { UnZip unzipper = new UnZip(); string destFilePath = "www/" + reqState.options.FilePath; // at this point, bytesLoaded = bytesTotal so we'll just put the as '1' DispatchSyncProgress(1, 1, 2, callbackId); unzipper.unzip(reqState.options.FilePath, destFilePath, reqState.options.Type); if(reqState.options.CopyCordovaAssets) { copyCordovaAssets(destFilePath); } DispatchSyncProgress(1, 1, 3, callbackId); string result = "{ \"localPath\": \"" + reqState.options.FilePath + "\" , \"Id\" : \"" + reqState.options.Id + "\"}"; DispatchCommandResult(new PluginResult(PluginResult.Status.OK, result), callbackId); } } catch (IsolatedStorageException) { // Trying to write the file somewhere within the IsoStorage. DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } catch (SecurityException) { // Trying to write the file somewhere not allowed. DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } catch (WebException webex) { // TODO: probably need better work here to properly respond with all http status codes back to JS // Right now am jumping through hoops just to detect 404. HttpWebResponse response = (HttpWebResponse)webex.Response; if ((webex.Status == WebExceptionStatus.ProtocolError && response.StatusCode == HttpStatusCode.NotFound) || webex.Status == WebExceptionStatus.UnknownError) { // Weird MSFT detection of 404... seriously... just give us the f(*&#$@ status code as a number ffs!!! // "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away." // FACEPALM // Or just cast it to an int, whiner ... -jm int statusCode = (int)response.StatusCode; string body = ""; using (Stream streamResponse = response.GetResponseStream()) { using (StreamReader streamReader = new StreamReader(streamResponse)) { body = streamReader.ReadToEnd(); } } SyncError ftError = new SyncError(ConnectionError, null, null, statusCode, body); DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError), callbackId); } else { lock (reqState) { if (!reqState.isCancelled) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(ConnectionError)), callbackId); } else { Debug.WriteLine("It happened"); } } } } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new SyncError(UnzipError)), callbackId); } //System.Threading.Thread.Sleep(1000); if (InProcDownloads.ContainsKey(reqState.options.Id)) { InProcDownloads.Remove(reqState.options.Id); } }
public abstract SyncError MapSyncError(OfflineEntity entity, SyncError error, WinEightContext context);
/// <summary> /// Runs the synchronization attempt. /// Does not raise exceptions. /// </summary> public async Task <SyncResult> Synchronize(CancellationToken token, SyncPolicy policy = SyncPolicy.Default) { if (DateTime.UtcNow < NextUploadOpportunity && policy == SyncPolicy.Default) { Log.Debug("Can't sync: sync attempt too early, next upload scheduled after {0}", NextUploadOpportunity); return(new SyncResult(new InvalidOperationException("Sync attempt too early"))); } Settings.LastUploadAttempt = DateTime.UtcNow; if (!CheckSyncConditions(policy)) { return(new SyncResult(error: new InvalidOperationException("Sync conditions not met"))); } Log.Debug("Sync attempt started (policy {0})", policy); IsSyncing = true; StatusChanged.Raise(this); try { var ret = await SynchronizeInner(token, policy); Log.Debug("Sync process terminated normally"); Log.Event("Sync.terminate", new Dictionary <string, string>() { { "policy", policy.ToString() } }); if (ret.HasFailed) { UserLog.Add(UserLog.Icon.Error, LogStrings.FileUploadFailure, ret.Error.Message); SyncError.Raise(this, new SyncErrorEventArgs(ret.Error)); } else { if (ret.ChunksUploaded == 1) { UserLog.Add(LogStrings.FileUploadSummarySingular); } else { UserLog.Add(LogStrings.FileUploadSummaryPlural, ret.ChunksUploaded); } } return(ret); } catch (TaskCanceledException) { Log.Debug("Sync process was canceled"); return(new SyncResult(0, 0)); } catch (Exception ex) { Log.Error(ex, "Sync process failed with unforeseen error"); UserLog.Add(UserLog.Icon.Error, LogStrings.FileUploadFailure, ex.Message); return(new SyncResult(error: ex)); } finally { IsSyncing = false; StatusChanged.Raise(this); } }
/// <summary> /// Handler for the ApplyChangedFailed event of the SqlSyncProvider class. This is used to record /// conflict information and apply the service conflict resolution policy. /// </summary> /// <param name="sender">Sender object</param> /// <param name="e">Event args</param> private void SqlSyncProviderApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e) { ApplyAction applyAction = ApplyAction.Continue; // Note: LocalChange table name may be null if the record does not exist on the server. So use the remote table name. string tableName = e.Conflict.RemoteChange.TableName; Type entityType = _configuration.TableGlobalNameToTypeMapping[tableName]; ConstructorInfo constructorInfo = entityType.GetConstructor(Type.EmptyTypes); // Handle Errors first if (null != e.Error) { var syncError = new SyncError { LiveEntity = (IOfflineEntity)constructorInfo.Invoke(null), ErrorEntity = (IOfflineEntity)constructorInfo.Invoke(null) }; //Note: When Error is not null, the conflict type should be ErrorsOccurred. Assert, just to make sure this is always correct. Debug.Assert(e.Conflict.Type == DbConflictType.ErrorsOccurred, "Conflict.Type is not ErrorsOccurred."); syncError.Description = e.Error.Message; // Fill in the error entity. This is the value of the client entity. _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], syncError.ErrorEntity); // Mark the error entity as a tombstone, if the client sent a delete. // Note: The DataRow.RowState property is not marked as deleted, so we cannot use that // to determine if the client change was a delete. if (e.Conflict.Stage == DbSyncStage.ApplyingDeletes) { syncError.ErrorEntity.ServiceMetadata.IsTombstone = true; } // Get the current version from the server. IOfflineEntity serverVersion = GetCurrentServerVersionForEntities( new List<IOfflineEntity> { syncError.ErrorEntity }, (SqlConnection)e.Connection, (SqlTransaction)e.Transaction) .FirstOrDefault(); // There is no item corresponding to the item sent from the client. // Example is an INSERT which caused an RI error and server does not have the record. if (null == serverVersion) { // If there is no server record and the client changes is not a tombstone, then // set the LiveEntity as a compensating action which is a tombstone. // This means that the client has to apply the delete locally // for data convergence. // If there is no server record and the client is a tombstone, then we ideally should // just ackowledge the action and don't send any response but // for now we will keep the sync error as is. _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], syncError.LiveEntity); syncError.LiveEntity.ServiceMetadata.IsTombstone = true; } else { syncError.LiveEntity = serverVersion; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(syncError.ErrorEntity); } // This will add the item as an exception to the server knowledge and will also send the exception to the client. // However the exception will be cleared the next time the client uploads changes, since we increment tickcounts always. applyAction = ApplyAction.Continue; // The ApplyChangesFailed event is fired when a conflict is detected. During resolution, // if change application fails due to some errors (such as RI, connectivity issues etc), // the provider fires the ApplyChangesFailed event again // to report an error. If we save the error entity as is, then both the original conflict and this new error // will be sent back to the client in the response. Since this is not desirable, we first need to remove the // corresponding conflict entity from the _conflicts collection before recording the error. // Note: item versions are not bumped since the conflict has not yet been resolved. RemoveEntityFromConflictCollection(tableName, syncError.LiveEntity); _syncErrors.Add(syncError); e.Action = applyAction; return; } // Create instances of OfflineCapableEntities and initialize the WinningChange and LosingChange properties. var c = new SyncConflict { LiveEntity = (IOfflineEntity)constructorInfo.Invoke(null), LosingEntity = (IOfflineEntity)constructorInfo.Invoke(null) }; ConflictResolutionPolicy policyToUse = _conflictResolutionPolicy; SyncConflictResolution? userResolution = null; // Check and fire any Conflict interceptors if (_configuration.HasConflictInterceptors(this._scopeName) || _configuration.HasTypedConflictInterceptor(this._scopeName, entityType)) { userResolution = GetUserConflictResolution(e, constructorInfo, entityType); if (userResolution != null && userResolution == SyncConflictResolution.ServerWins) { policyToUse = ConflictResolutionPolicy.ServerWins; } else if (userResolution != null && (userResolution == SyncConflictResolution.ClientWins || userResolution == SyncConflictResolution.Merge)) { // If resolution is Merge or ClientWins, set the resolution to ClientWins so the runtime will // retry with force write and save the merged values back policyToUse = ConflictResolutionPolicy.ClientWins; } } // If there were no Errors, then act based on the service conflict resolution policy. switch (policyToUse) { // ServerWins policy... case ConflictResolutionPolicy.ServerWins: // For OCS, ApplyAction.Continue means ServerWins (local change will be maintained). applyAction = ApplyAction.Continue; // If the local change exists, then save it in the WinningChange property if (null != e.Conflict.LocalChange && 1 == e.Conflict.LocalChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.LocalChange.Columns, e.Conflict.LocalChange.Rows[0], c.LiveEntity); } // If local change does not exist else { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LiveEntity); c.LiveEntity.ServiceMetadata.IsTombstone = true; } // Save the remote change in the LosingChange property. if (1 == e.Conflict.RemoteChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LosingEntity); } // Save the conflict resolution policy. c.Resolution = WebUtil.GetSyncConflictResolution(ConflictResolutionPolicy.ServerWins); // Set the tombstone flag based on the type of the conflict. switch (e.Conflict.Type) { case DbConflictType.LocalDeleteRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalDeleteRemoteUpdate: c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalUpdateRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; break; // No changes to the tombstone flag for other cases. default: break; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(c.LosingEntity); } break; // ClientWins policy... case ConflictResolutionPolicy.ClientWins: // For OCS, client change can be kept by using ApplyAction.RetryWithForceWrite. applyAction = ApplyAction.RetryWithForceWrite; if (1 == e.Conflict.RemoteChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LiveEntity); } // If the local change exists, then save it in the WinningChange property if (1 == e.Conflict.LocalChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.LocalChange.Rows[0], c.LosingEntity); } // Save the conflict resolution policy. c.Resolution = userResolution ?? WebUtil.GetSyncConflictResolution(ConflictResolutionPolicy.ClientWins); // Set the tombstone flag based on the type of the conflict. switch (e.Conflict.Type) { case DbConflictType.LocalDeleteRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalDeleteRemoteUpdate: c.LosingEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalUpdateRemoteDelete: c.LiveEntity.ServiceMetadata.IsTombstone = true; break; // No changes to the tombstone flag for other cases. default: break; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(c.LiveEntity); } break; } // After deciding on the Live and the Losing entities for the conflict, we need to generate and save the SyncId // of the LiveEntity. This value is used later after all changes are applied to project on the latest // server knowledge and add positive exceptions to the updated client knowledge that is sent in the response. SyncId rowId = GenerateSyncIdForConflictingEntity(tableName, c.LiveEntity); if (!_conflictToSyncEntityIdMapping.ContainsKey(c)) { _conflictToSyncEntityIdMapping.Add(c, rowId); // Note: SyncId's are unique for each entity. Debug.Assert(!_syncEntityIdToConflictMapping.ContainsKey(rowId), "!_syncEntityIdToConflictMapping.ContainsKey(rowId)"); // Also fill the reverse mapping of syncId to the conflict entity. _syncEntityIdToConflictMapping.Add(rowId, c); } _conflicts.Add(c); e.Action = applyAction; }
public void AddSerializedError(SyncError error, IsolatedStorageOfflineContext context) { IsolatedStorageSyncError oldError = (IsolatedStorageSyncError)Collections[error.ErrorEntity.GetType()].MapSyncError((IsolatedStorageOfflineEntity)error.LiveEntity, error, context); SyncErrors.Add(error); if (oldError != null) { ClearSyncError(oldError,context); } }