/// <summary> /// Write scope in the provider datasource /// </summary> public virtual async Task <SyncContext> WriteScopesAsync(SyncContext context, List <ScopeInfo> scopes) { // Open the connection using (var connection = this.CreateConnection()) { try { await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { var scopeBuilder = this.GetScopeBuilder(); var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(connection, transaction); var lstScopes = new List <ScopeInfo>(); foreach (var scope in scopes) { lstScopes.Add(scopeInfoBuilder.InsertOrUpdateScopeInfo(scope)); } context.SyncStage = SyncStage.ScopeSaved; // Event progress this.TryRaiseProgressEvent( new ScopeEventArgs(this.ProviderTypeName, context.SyncStage, lstScopes.FirstOrDefault(s => s.IsLocal)), ScopeSaved); transaction.Commit(); } } catch (DbException dbex) { throw SyncException.CreateDbException(context.SyncStage, dbex); } catch (Exception ex) { Debug.WriteLine(ex.Message); if (ex is SyncException) { throw; } else { throw SyncException.CreateUnknowException(context.SyncStage, ex); } throw; } finally { if (connection.State != ConnectionState.Closed) { connection.Close(); } } return(context); } }
/// <summary> /// Gets a batch of changes to synchronize when given batch size, /// destination knowledge, and change data retriever parameters. /// </summary> /// <returns>A DbSyncContext object that will be used to retrieve the modified data.</returns> public virtual async Task <(SyncContext, BatchInfo, ChangesSelected)> GetChangeBatchAsync(SyncContext context, ScopeInfo scopeInfo) { try { if (scopeInfo == null) { throw new ArgumentException("ClientScope is null"); } var configuration = GetCacheConfiguration(); // check batchSize if not > then Configuration.DownloadBatchSizeInKB if (configuration.DownloadBatchSizeInKB > 0) { Debug.WriteLine($"Enumeration data cache size selected: {configuration.DownloadBatchSizeInKB} Kb"); } // bacth info containing changes BatchInfo batchInfo; // Statistics about changes that are selected ChangesSelected changesSelected; (context, batchInfo, changesSelected) = await this.GetChanges(context, scopeInfo); // Check if the remote is not outdated var isOutdated = this.IsRemoteOutdated(); if (isOutdated) { throw new Exception("OutDatedPeer"); } return(context, batchInfo, changesSelected); } catch (DbException dbex) { throw SyncException.CreateDbException(context.SyncStage, dbex); } catch (Exception ex) { if (ex is SyncException) { throw; } else { throw SyncException.CreateUnknowException(context.SyncStage, ex); } } }
/// <summary> /// Read a scope info /// </summary> public virtual async Task <(SyncContext, long)> GetLocalTimestampAsync(SyncContext context) { // Open the connection using (var connection = this.CreateConnection()) { try { await connection.OpenAsync(); var scopeBuilder = this.GetScopeBuilder(); var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(connection); var localTime = scopeInfoBuilder.GetLocalTimestamp(); return(context, localTime); } catch (DbException dbex) { throw SyncException.CreateDbException(context.SyncStage, dbex); } catch (Exception ex) { if (ex is SyncException) { throw; } else { throw SyncException.CreateUnknowException(context.SyncStage, ex); } } finally { if (connection.State != ConnectionState.Closed) { connection.Close(); } } } }
/// <summary> /// Be sure all tables are ready and configured for sync /// the ScopeSet Configuration MUST be filled by the schema form Database /// </summary> public virtual async Task <SyncContext> EnsureDatabaseAsync(SyncContext context, ScopeInfo scopeInfo) { var configuration = GetCacheConfiguration(); // Event progress context.SyncStage = SyncStage.DatabaseApplying; DatabaseApplyingEventArgs beforeArgs = new DatabaseApplyingEventArgs(this.ProviderTypeName, context.SyncStage, configuration); this.TryRaiseProgressEvent(beforeArgs, this.DatabaseApplying); // if config has been editer by user in event, save again this.SetCacheConfiguration(configuration); // If scope exists and lastdatetime sync is present, so database exists // Check if we don't have an OverwriteConfiguration (if true, we force the check) if (scopeInfo.LastSync.HasValue && !beforeArgs.OverwriteConfiguration) { return(context); } StringBuilder script = new StringBuilder(); // Open the connection using (var connection = this.CreateConnection()) { try { await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { foreach (var dmTable in configuration) { var builder = GetDatabaseBuilder(dmTable); // adding filter this.AddFilters(configuration, dmTable, builder); context.SyncStage = SyncStage.DatabaseTableApplying; DatabaseTableApplyingEventArgs beforeTableArgs = new DatabaseTableApplyingEventArgs(this.ProviderTypeName, context.SyncStage, dmTable.TableName); this.TryRaiseProgressEvent(beforeTableArgs, this.DatabaseTableApplying); string currentScript = null; if (beforeArgs.GenerateScript) { currentScript = builder.ScriptTable(connection, transaction); currentScript += builder.ScriptForeignKeys(connection, transaction); script.Append(currentScript); } builder.Create(connection, transaction); builder.CreateForeignKeys(connection, transaction); context.SyncStage = SyncStage.DatabaseTableApplied; DatabaseTableAppliedEventArgs afterTableArgs = new DatabaseTableAppliedEventArgs(this.ProviderTypeName, context.SyncStage, dmTable.TableName, currentScript); this.TryRaiseProgressEvent(afterTableArgs, this.DatabaseTableApplied); } context.SyncStage = SyncStage.DatabaseApplied; var afterArgs = new DatabaseAppliedEventArgs(this.ProviderTypeName, context.SyncStage, script.ToString()); this.TryRaiseProgressEvent(afterArgs, this.DatabaseApplied); transaction.Commit(); } } catch (DbException dbex) { throw SyncException.CreateDbException(context.SyncStage, dbex); } catch (Exception ex) { if (ex is SyncException) { throw; } else { throw SyncException.CreateUnknowException(context.SyncStage, ex); } } finally { if (connection.State != ConnectionState.Closed) { connection.Close(); } } return(context); } }
/// <summary> /// Called when the sync ensure scopes are created /// </summary> public virtual async Task <(SyncContext, List <ScopeInfo>)> EnsureScopesAsync(SyncContext context, string scopeName, Guid?clientReferenceId = null) { try { if (string.IsNullOrEmpty(scopeName)) { throw SyncException.CreateArgumentException(SyncStage.ScopeSaved, "ScopeName"); } context.SyncStage = SyncStage.ScopeLoading; List <ScopeInfo> scopes = new List <ScopeInfo>(); // Open the connection using (var connection = this.CreateConnection()) { try { await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { var scopeBuilder = this.GetScopeBuilder(); var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(connection, transaction); var needToCreateScopeInfoTable = scopeInfoBuilder.NeedToCreateScopeInfoTable(); // create the scope info table if needed if (needToCreateScopeInfoTable) { scopeInfoBuilder.CreateScopeInfoTable(); } // not the first time we ensure scopes, so get scopes if (!needToCreateScopeInfoTable) { // get all scopes shared by all (identified by scopeName) var lstScopes = scopeInfoBuilder.GetAllScopes(scopeName); // try to get the scopes from database // could be two scopes if from server or a single scope if from client scopes = lstScopes.Where(s => (s.IsLocal == true || (clientReferenceId.HasValue && s.Id == clientReferenceId.Value))).ToList(); } // If no scope found, create it on the local provider if (scopes == null || scopes.Count <= 0) { scopes = new List <ScopeInfo>(); // create a new scope id for the current owner (could be server or client as well) var scope = new ScopeInfo(); scope.Id = Guid.NewGuid(); scope.Name = scopeName; scope.IsLocal = true; scope.IsNewScope = true; scope.LastSync = null; scope = scopeInfoBuilder.InsertOrUpdateScopeInfo(scope); scopes.Add(scope); } else { //check if we have alread a good last sync. if no, treat it as new scopes.ForEach(sc => sc.IsNewScope = sc.LastSync == null); } // if we are not on the server, we have to check that we only have one scope if (!clientReferenceId.HasValue && scopes.Count > 1) { throw SyncException.CreateNotSupportedException(SyncStage.ScopeSaved, "On Local provider, we should have only one scope info"); } // if we have a reference in args, we need to get this specific line from database // this happen only on the server side if (clientReferenceId.HasValue) { var refScope = scopes.FirstOrDefault(s => s.Id == clientReferenceId); if (refScope == null) { refScope = new ScopeInfo(); refScope.Id = clientReferenceId.Value; refScope.Name = scopeName; refScope.IsLocal = false; refScope.IsNewScope = true; refScope.LastSync = null; refScope = scopeInfoBuilder.InsertOrUpdateScopeInfo(refScope); scopes.Add(refScope); } else { refScope.IsNewScope = refScope.LastSync == null; } } transaction.Commit(); } } catch (DbException dbex) { throw SyncException.CreateDbException(context.SyncStage, dbex); } catch (Exception dbex) { throw SyncException.CreateUnknowException(context.SyncStage, dbex); } finally { if (connection.State != ConnectionState.Closed) { connection.Close(); } } } // Event progress this.TryRaiseProgressEvent( new ScopeEventArgs(this.ProviderTypeName, context.SyncStage, scopes.FirstOrDefault(s => s.IsLocal)), ScopeLoading); return(context, scopes); } catch (Exception ex) { if (ex is SyncException) { throw; } else { throw SyncException.CreateUnknowException(context.SyncStage, ex); } } }