Exemple #1
0
        /// <summary>
        /// Apply changes : Insert / Updates Delete
        /// the fromScope is local client scope when this method is called from server
        /// the fromScope is server scope when this method is called from client
        /// </summary>
        public virtual async Task <(SyncContext, ChangesApplied)> ApplyChangesAsync(SyncContext context, MessageApplyChanges message)
        {
            ChangeApplicationAction changeApplicationAction;
            DbTransaction           applyTransaction = null;
            DbConnection            connection       = null;
            ChangesApplied          changesApplied   = new ChangesApplied();

            try
            {
                using (connection = this.CreateConnection())
                {
                    await connection.OpenAsync();

                    // Create a transaction
                    applyTransaction = connection.BeginTransaction();

                    // -----------------------------------------------------
                    // 0) Check if we are in a reinit mode
                    // -----------------------------------------------------
                    if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal)
                    {
                        changeApplicationAction = this.ResetInternal(context, message.Schema, connection, applyTransaction, message.FromScope);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw new SyncException("Rollback during reset tables", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                        }
                    }

                    // -----------------------------------------------------
                    // 1) Applying deletes. Do not apply deletes if we are in a new database
                    // -----------------------------------------------------
                    if (!message.FromScope.IsNewScope)
                    {
                        changeApplicationAction = this.ApplyChangesInternal(context, message, connection, applyTransaction, DmRowState.Deleted, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw new SyncException("Rollback during applying deletes", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                        }
                    }
                    // -----------------------------------------------------
                    // 2) Applying updates
                    // -----------------------------------------------------
                    changeApplicationAction = this.ApplyChangesInternal(context, message, connection, applyTransaction, DmRowState.Modified, changesApplied);

                    // Rollback
                    if (changeApplicationAction == ChangeApplicationAction.Rollback)
                    {
                        throw new SyncException("Rollback during applying updates", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                    }

                    // -----------------------------------------------------
                    // 3) Applying Inserts
                    // -----------------------------------------------------
                    changeApplicationAction = this.ApplyChangesInternal(context, message, connection, applyTransaction, DmRowState.Added, changesApplied);

                    // Rollback
                    if (changeApplicationAction == ChangeApplicationAction.Rollback)
                    {
                        throw new SyncException("Rollback during applying inserts", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                    }


                    applyTransaction.Commit();

                    return(context, changesApplied);
                }
            }
            catch (SyncException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new SyncException(ex, SyncStage.TableChangesApplying, this.ProviderTypeName);
            }
            finally
            {
                if (applyTransaction != null)
                {
                    applyTransaction.Dispose();
                    applyTransaction = null;
                }

                if (connection != null && connection.State == ConnectionState.Open)
                {
                    connection.Close();
                }

                if (message.Changes != null)
                {
                    message.Changes.Clear();
                }
            }
        }
Exemple #2
0
        /// <summary>
        /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter
        /// </summary>
        internal ChangeApplicationAction ApplyChangesInternal(
            SyncContext context,
            MessageApplyChanges message,
            DbConnection connection,
            DbTransaction transaction,
            DmRowState applyType,
            ChangesApplied changesApplied)
        {
            ChangeApplicationAction changeApplicationAction = ChangeApplicationAction.Continue;

            // for each adapters (Zero to End for Insert / Updates -- End to Zero for Deletes
            for (int i = 0; i < message.Schema.Tables.Count; i++)
            {
                // If we have a delete we must go from Up to Down, orthewise Dow to Up index
                var tableDescription = (applyType != DmRowState.Deleted ?
                                        message.Schema.Tables[i] :
                                        message.Schema.Tables[message.Schema.Tables.Count - i - 1]);

                // if we are in upload stage, so check if table is not download only
                if (context.SyncWay == SyncWay.Upload && tableDescription.SyncDirection == SyncDirection.DownloadOnly)
                {
                    continue;
                }

                // if we are in download stage, so check if table is not download only
                if (context.SyncWay == SyncWay.Download && tableDescription.SyncDirection == SyncDirection.UploadOnly)
                {
                    continue;
                }


                var builder     = this.GetDatabaseBuilder(tableDescription);
                var syncAdapter = builder.CreateSyncAdapter(connection, transaction);

                syncAdapter.ConflictApplyAction = SyncConfiguration.GetApplyAction(message.Policy);

                // Set syncAdapter properties
                syncAdapter.applyType = applyType;

                // Get conflict handler resolver
                if (syncAdapter.ConflictActionInvoker == null && this.ApplyChangedFailed != null)
                {
                    syncAdapter.ConflictActionInvoker = GetConflictAction;
                }

                if (message.Changes.BatchPartsInfo != null && message.Changes.BatchPartsInfo.Count > 0)
                {
                    // getting the table to be applied
                    // we may have multiple batch files, so we can have multipe dmTable with the same Name
                    // We can say that dmTable may be contained in several files
                    foreach (DmTable dmTablePart in message.Changes.GetTable(tableDescription.TableName))
                    {
                        if (dmTablePart == null || dmTablePart.Rows.Count == 0)
                        {
                            continue;
                        }

                        // check and filter
                        var dmChangesView = new DmView(dmTablePart, (r) => r.RowState == applyType);

                        if (dmChangesView.Count == 0)
                        {
                            dmChangesView.Dispose();
                            dmChangesView = null;
                            continue;
                        }

                        // Conflicts occured when trying to apply rows
                        List <SyncConflict> conflicts = new List <SyncConflict>();

                        // Raise event progress only if there are rows to be applied
                        context.SyncStage = SyncStage.TableChangesApplying;
                        var args = new TableChangesApplyingEventArgs(this.ProviderTypeName, context.SyncStage, tableDescription.TableName, applyType);
                        this.TryRaiseProgressEvent(args, this.TableChangesApplying);

                        int rowsApplied;
                        // applying the bulkchanges command
                        if (message.UseBulkOperations && this.SupportBulkOperations)
                        {
                            rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, message.FromScope, conflicts);
                        }
                        else
                        {
                            rowsApplied = syncAdapter.ApplyChanges(dmChangesView, message.FromScope, conflicts);
                        }

                        // If conflicts occured
                        // Eventuall, conflicts are resolved on server side.
                        if (conflicts != null && conflicts.Count > 0)
                        {
                            foreach (var conflict in conflicts)
                            {
                                //var scopeBuilder = this.GetScopeBuilder();
                                //var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(message.ScopeInfoTableName, connection, transaction);
                                //var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp();
                                var fromScopeLocalTimeStamp = message.FromScope.Timestamp;

                                changeApplicationAction = syncAdapter.HandleConflict(conflict, message.Policy, message.FromScope, fromScopeLocalTimeStamp, out DmRow resolvedRow);

                                if (changeApplicationAction == ChangeApplicationAction.Continue)
                                {
                                    // row resolved
                                    if (resolvedRow != null)
                                    {
                                        rowsApplied++;
                                    }
                                }
                                else
                                {
                                    context.TotalSyncErrors++;
                                    // TODO : Should we break at the first error ?
                                    return(ChangeApplicationAction.Rollback);
                                }
                            }
                        }

                        // Get all conflicts resolved
                        context.TotalSyncConflicts = conflicts.Where(c => c.Type != ConflictType.ErrorsOccurred).Sum(c => 1);

                        // Handle sync progress for this syncadapter (so this table)
                        var changedFailed = dmChangesView.Count - rowsApplied;

                        // raise SyncProgress Event

                        var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault(
                            sc => string.Equals(sc.TableName, tableDescription.TableName) && sc.State == applyType);

                        if (existAppliedChanges == null)
                        {
                            existAppliedChanges = new TableChangesApplied
                            {
                                TableName = tableDescription.TableName,
                                Applied   = rowsApplied,
                                Failed    = changedFailed,
                                State     = applyType
                            };
                            changesApplied.TableChangesApplied.Add(existAppliedChanges);
                        }
                        else
                        {
                            existAppliedChanges.Applied += rowsApplied;
                            existAppliedChanges.Failed  += changedFailed;
                        }

                        // Event progress
                        context.SyncStage = SyncStage.TableChangesApplied;
                        var progressEventArgs = new TableChangesAppliedEventArgs(this.ProviderTypeName, context.SyncStage, existAppliedChanges);
                        this.TryRaiseProgressEvent(progressEventArgs, this.TableChangesApplied);
                    }
                }

                // Dispose conflict handler resolver
                if (syncAdapter.ConflictActionInvoker != null)
                {
                    syncAdapter.ConflictActionInvoker = null;
                }
            }

            return(ChangeApplicationAction.Continue);
        }
Exemple #3
0
        /// <summary>
        /// Launch a synchronization with the specified mode
        /// </summary>
        public async Task <SyncContext> SynchronizeAsync(SyncType syncType, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(this.scopeName))
            {
                throw new ArgumentNullException("scopeName", "Scope Name is mandatory");
            }

            // Context, used to back and forth data between servers
            SyncContext context = new SyncContext(Guid.NewGuid())
            {
                // set start time
                StartTime = DateTime.Now,

                // if any parameters, set in context
                Parameters = this.Parameters,

                // set sync type (Normal, Reinitialize, ReinitializeWithUpload)
                SyncType = syncType
            };

            this.SessionState = SyncSessionState.Synchronizing;
            this.SessionStateChanged?.Invoke(this, this.SessionState);

            ScopeInfo localScopeInfo          = null,
                      serverScopeInfo         = null,
                      localScopeReferenceInfo = null,
                      scope = null;

            Guid fromId     = Guid.Empty;
            long lastSyncTS = 0L;
            bool isNew      = true;

            try
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Setting the cancellation token
                this.LocalProvider.SetCancellationToken(cancellationToken);
                this.RemoteProvider.SetCancellationToken(cancellationToken);

                // Begin Session / Read the adapters
                context = await this.RemoteProvider.BeginSessionAsync(context);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                context = await this.LocalProvider.BeginSessionAsync(context);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                // ----------------------------------------
                // 1) Read scope info
                // ----------------------------------------

                // get the scope from local provider
                List <ScopeInfo> localScopes;
                List <ScopeInfo> serverScopes;
                (context, localScopes) = await this.LocalProvider.EnsureScopesAsync(context, scopeName);

                if (localScopes.Count != 1)
                {
                    throw new Exception("On Local provider, we should have only one scope info");
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                localScopeInfo = localScopes[0];

                (context, serverScopes) = await this.RemoteProvider.EnsureScopesAsync(context, scopeName, localScopeInfo.Id);

                if (serverScopes.Count != 2)
                {
                    throw new Exception("On Remote provider, we should have two scopes (one for server and one for client side)");
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                serverScopeInfo         = serverScopes.First(s => s.Id != localScopeInfo.Id);
                localScopeReferenceInfo = serverScopes.First(s => s.Id == localScopeInfo.Id);

                // ----------------------------------------
                // 2) Build Configuration Object
                // ----------------------------------------

                // Get Configuration from remote provider
                (context, this.Configuration) = await this.RemoteProvider.EnsureConfigurationAsync(context, this.Configuration);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Invert policy on the client
                var configurationLocale = this.Configuration.Clone();
                var policy = this.Configuration.ConflictResolutionPolicy;
                if (policy == ConflictResolutionPolicy.ServerWins)
                {
                    configurationLocale.ConflictResolutionPolicy = ConflictResolutionPolicy.ClientWins;
                }
                if (policy == ConflictResolutionPolicy.ClientWins)
                {
                    configurationLocale.ConflictResolutionPolicy = ConflictResolutionPolicy.ServerWins;
                }

                // Apply on local Provider
                SyncConfiguration configuration;
                (context, configuration) = await this.LocalProvider.EnsureConfigurationAsync(context, configurationLocale);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // ----------------------------------------
                // 3) Ensure databases are ready
                // ----------------------------------------

                // Server should have already the schema
                context = await this.RemoteProvider.EnsureDatabaseAsync(context, serverScopeInfo);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Client could have, or not, the tables
                context = await this.LocalProvider.EnsureDatabaseAsync(context, localScopeInfo);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // ----------------------------------------
                // 5) Get changes and apply them
                // ----------------------------------------
                BatchInfo clientBatchInfo;
                BatchInfo serverBatchInfo;

                ChangesSelected clientChangesSelected = null;
                ChangesSelected serverChangesSelected = null;
                ChangesApplied  clientChangesApplied  = null;
                ChangesApplied  serverChangesApplied  = null;

                // fromId : not really needed on this case, since updated / inserted / deleted row has marked null
                // otherwise, lines updated by server or others clients are already syncked
                fromId = localScopeInfo.Id;
                // lastSyncTS : get lines inserted / updated / deteleted after the last sync commited
                lastSyncTS = localScopeInfo.LastTimestamp;
                // isNew : If isNew, lasttimestamp is not correct, so grab all
                isNew = localScopeInfo.IsNewScope;
                //Direction set to Upload
                context.SyncWay = SyncWay.Upload;

                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };
                (context, clientBatchInfo, clientChangesSelected) = await this.LocalProvider.GetChangeBatchAsync(context, scope);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Apply on the Server Side
                // Since we are on the server,
                // we need to check the server client timestamp (not the client timestamp which is completely different)

                // fromId : When applying rows, make sure it's identified as applied by this client scope
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                // isNew : not needed
                isNew = false;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };

                (context, serverChangesApplied) = await this.RemoteProvider.ApplyChangesAsync(context, scope, clientBatchInfo);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                // Get changes from server


                // get the archive if exists
                if (localScopeReferenceInfo.IsNewScope && !String.IsNullOrEmpty(this.Configuration.Archive))
                {
                    //// fromId : Make sure we don't select lines on server that has been already updated by the client
                    //fromId = localScopeInfo.Id;
                    //// lastSyncTS : apply lines only if thye are not modified since last client sync
                    //lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                    //// isNew : make sure we take all lines if it's the first time we get
                    //isNew = localScopeReferenceInfo.IsNewScope;
                    //scope = new ScopeInfo { Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS };
                    ////Direction set to Download
                    //context.SyncWay = SyncWay.Download;

                    //(context, serverBatchInfo, serverChangesSelected) = await this.RemoteProvider.GetArchiveAsync(context, scope);

                    //// fromId : When applying rows, make sure it's identified as applied by this server scope
                    //fromId = serverScopeInfo.Id;
                    //// lastSyncTS : apply lines only if they are not modified since last client sync
                    //lastSyncTS = localScopeInfo.LastTimestamp;
                    //// isNew : if IsNew, don't apply deleted rows from server
                    //isNew = localScopeInfo.IsNewScope;
                    //scope = new ScopeInfo { Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS };

                    //(context, clientChangesApplied) = await this.LocalProvider.ApplyArchiveAsync(context, scope, serverBatchInfo);

                    //// Here we have to change the localScopeInfo.LastTimestamp to the good one
                    //// last ts from archive
                    //localScopeReferenceInfo.LastTimestamp = [something from the archive];
                    //// we are not new anymore
                    //localScopeReferenceInfo.IsNewScope = false;
                }


                // fromId : Make sure we don't select lines on server that has been already updated by the client
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                // isNew : make sure we take all lines if it's the first time we get
                isNew = localScopeReferenceInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };
                //Direction set to Download
                context.SyncWay = SyncWay.Download;


                (context, serverBatchInfo, serverChangesSelected) = await this.RemoteProvider.GetChangeBatchAsync(context, scope);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Apply local changes

                // fromId : When applying rows, make sure it's identified as applied by this server scope
                fromId = serverScopeInfo.Id;
                // lastSyncTS : apply lines only if they are not modified since last client sync
                lastSyncTS = localScopeInfo.LastTimestamp;
                // isNew : if IsNew, don't apply deleted rows from server
                isNew = localScopeInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };

                (context, clientChangesApplied) = await this.LocalProvider.ApplyChangesAsync(context, scope, serverBatchInfo);

                context.TotalChangesDownloaded = clientChangesApplied.TotalAppliedChanges;
                context.TotalChangesUploaded   = clientChangesSelected.TotalChangesSelected;
                context.TotalSyncErrors        = clientChangesApplied.TotalAppliedChangesFailed;

                long serverTimestamp, clientTimestamp;

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                (context, serverTimestamp) = await this.RemoteProvider.GetLocalTimestampAsync(context);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                (context, clientTimestamp) = await this.LocalProvider.GetLocalTimestampAsync(context);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                context.CompleteTime = DateTime.Now;

                serverScopeInfo.IsNewScope         = false;
                localScopeReferenceInfo.IsNewScope = false;
                localScopeInfo.IsNewScope          = false;

                serverScopeInfo.LastSync         = context.CompleteTime;
                localScopeReferenceInfo.LastSync = context.CompleteTime;
                localScopeInfo.LastSync          = context.CompleteTime;

                serverScopeInfo.IsLocal         = true;
                localScopeReferenceInfo.IsLocal = false;

                context = await this.RemoteProvider.WriteScopesAsync(context, new List <ScopeInfo> {
                    serverScopeInfo, localScopeReferenceInfo
                });

                serverScopeInfo.IsLocal = false;
                localScopeInfo.IsLocal  = true;

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                context = await this.LocalProvider.WriteScopesAsync(context, new List <ScopeInfo> {
                    localScopeInfo, serverScopeInfo
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }
            catch (SyncException se)
            {
                Debug.WriteLine($"Sync Exception: {se.Message}. Type:{se.Type}. On provider: {se.ProviderName}.");
                throw;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Unknwon Exception: {ex.Message}.");
                throw new SyncException(ex, SyncStage.None, string.Empty);
            }
            finally
            {
                // End the current session
                context = await this.RemoteProvider.EndSessionAsync(context);

                context = await this.LocalProvider.EndSessionAsync(context);

                this.SessionState = SyncSessionState.Ready;
                this.SessionStateChanged?.Invoke(this, this.SessionState);
            }

            return(context);
        }
        /// <summary>
        /// Launch a synchronization with the specified mode
        /// </summary>
        public async Task <SyncContext> SynchronizeAsync(SyncType syncType, CancellationToken cancellationToken)
        {
            // Context, used to back and forth data between servers
            var context = new SyncContext(Guid.NewGuid())
            {
                // set start time
                StartTime = DateTime.Now,
                // if any parameters, set in context
                Parameters = this.Parameters,
                // set sync type (Normal, Reinitialize, ReinitializeWithUpload)
                SyncType = syncType
            };

            this.SessionState = SyncSessionState.Synchronizing;
            this.SessionStateChanged?.Invoke(this, this.SessionState);

            ScopeInfo localScopeInfo          = null,
                      serverScopeInfo         = null,
                      localScopeReferenceInfo = null,
                      scope = null;

            var fromId     = Guid.Empty;
            var lastSyncTS = 0L;
            var isNew      = true;

            try
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Setting the cancellation token
                this.LocalProvider.SetCancellationToken(cancellationToken);
                this.RemoteProvider.SetCancellationToken(cancellationToken);

                // ----------------------------------------
                // 0) Begin Session / Get the Configuration from remote provider
                //    If the configuration object is provided by the client, the server will be updated with it.
                // ----------------------------------------
                (context, this.Configuration) = await this.RemoteProvider.BeginSessionAsync(context,
                                                                                            new MessageBeginSession { SyncConfiguration = this.Configuration });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Locally, nothing really special. Eventually, editing the config object
                (context, this.Configuration) = await this.LocalProvider.BeginSessionAsync(context,
                                                                                           new MessageBeginSession { SyncConfiguration = this.Configuration });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // ----------------------------------------
                // 1) Read scope info
                // ----------------------------------------

                // get the scope from local provider
                List <ScopeInfo> localScopes;
                List <ScopeInfo> serverScopes;
                (context, localScopes) = await this.LocalProvider.EnsureScopesAsync(context,
                                                                                    new MessageEnsureScopes
                {
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    ScopeName           = this.Configuration.ScopeName,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (localScopes.Count != 1)
                {
                    throw new Exception("On Local provider, we should have only one scope info");
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                localScopeInfo = localScopes[0];

                (context, serverScopes) = await this.RemoteProvider.EnsureScopesAsync(context,
                                                                                      new MessageEnsureScopes
                {
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    ScopeName           = this.Configuration.ScopeName,
                    ClientReferenceId   = localScopeInfo.Id,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (serverScopes.Count != 2)
                {
                    throw new Exception("On Remote provider, we should have two scopes (one for server and one for client side)");
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                serverScopeInfo         = serverScopes.First(s => s.Id != localScopeInfo.Id);
                localScopeReferenceInfo = serverScopes.First(s => s.Id == localScopeInfo.Id);


                // ----------------------------------------
                // 2) Build Configuration Object
                // ----------------------------------------

                // Get Schema from remote provider
                (context, this.Configuration.Schema) = await this.RemoteProvider.EnsureSchemaAsync(context,
                                                                                                   new MessageEnsureSchema
                {
                    Schema = this.Configuration.Schema,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Apply on local Provider
                (context, this.Configuration.Schema) = await this.LocalProvider.EnsureSchemaAsync(context,
                                                                                                  new MessageEnsureSchema
                {
                    Schema = this.Configuration.Schema,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // ----------------------------------------
                // 3) Ensure databases are ready
                // ----------------------------------------

                // Server should have already the schema
                context = await this.RemoteProvider.EnsureDatabaseAsync(context,
                                                                        new MessageEnsureDatabase
                {
                    ScopeInfo           = serverScopeInfo,
                    Schema              = this.Configuration.Schema,
                    Filters             = this.Configuration.Filters,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Client could have, or not, the tables
                context = await this.LocalProvider.EnsureDatabaseAsync(context,
                                                                       new MessageEnsureDatabase
                {
                    ScopeInfo           = localScopeInfo,
                    Schema              = this.Configuration.Schema,
                    Filters             = this.Configuration.Filters,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // ----------------------------------------
                // 5) Get changes and apply them
                // ----------------------------------------
                BatchInfo clientBatchInfo;
                BatchInfo serverBatchInfo;

                ChangesSelected clientChangesSelected = null;
                ChangesSelected serverChangesSelected = null;
                ChangesApplied  clientChangesApplied  = null;
                ChangesApplied  serverChangesApplied  = null;

                // those timestamps will be registered as the "timestamp just before launch the sync"
                long serverTimestamp, clientTimestamp;

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Apply on the Server Side
                // Since we are on the server,
                // we need to check the server client timestamp (not the client timestamp which is completely different)
                var serverPolicy = this.Configuration.ConflictResolutionPolicy;
                var clientPolicy = serverPolicy == ConflictResolutionPolicy.ServerWins ? ConflictResolutionPolicy.ClientWins : ConflictResolutionPolicy.ServerWins;

                // We get from local provider all rows not last updated from the server
                fromId = serverScopeInfo.Id;
                // lastSyncTS : get lines inserted / updated / deteleted after the last sync commited
                lastSyncTS = localScopeInfo.LastSyncTimestamp;
                // isNew : If isNew, lasttimestamp is not correct, so grab all
                isNew = localScopeInfo.IsNewScope;
                //Direction set to Upload
                context.SyncWay = SyncWay.Upload;

                // JUST before the whole process, get the timestamp, to be sure to
                // get rows inserted / updated elsewhere since the sync is not over
                (context, clientTimestamp) = await this.LocalProvider.GetLocalTimestampAsync(context,
                                                                                             new MessageTimestamp
                {
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, Timestamp = lastSyncTS
                };
                (context, clientBatchInfo, clientChangesSelected) =
                    await this.LocalProvider.GetChangeBatchAsync(context,
                                                                 new MessageGetChangesBatch
                {
                    ScopeInfo             = scope,
                    Schema                = this.Configuration.Schema,
                    DownloadBatchSizeInKB = this.Configuration.DownloadBatchSizeInKB,
                    BatchDirectory        = this.Configuration.BatchDirectory,
                    Policy                = clientPolicy,
                    Filters               = this.Configuration.Filters,
                    SerializationFormat   = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }



                // fromId : When applying rows, make sure it's identified as applied by this client scope
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastSyncTimestamp;
                // isNew : not needed
                isNew = false;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, Timestamp = lastSyncTS
                };

                (context, serverChangesApplied) =
                    await this.RemoteProvider.ApplyChangesAsync(context,
                                                                new MessageApplyChanges
                {
                    FromScope           = scope,
                    Schema              = this.Configuration.Schema,
                    Policy              = serverPolicy,
                    UseBulkOperations   = this.Configuration.UseBulkOperations,
                    CleanMetadatas      = this.Configuration.CleanMetadatas,
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    Changes             = clientBatchInfo,
                    SerializationFormat = this.Configuration.SerializationFormat
                });


                // if ConflictResolutionPolicy.ClientWins or Handler set to Client wins
                // Conflict occurs here and server loose.
                // Conflicts count should be temp saved because applychanges on client side won't raise any conflicts (and so property Context.TotalSyncConflicts will be reset to 0)
                var conflictsOnRemoteCount = context.TotalSyncConflicts;

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                // Get changes from server


                // get the archive if exists
                if (localScopeReferenceInfo.IsNewScope && !string.IsNullOrEmpty(this.Configuration.Archive))
                {
                    //// fromId : Make sure we don't select lines on server that has been already updated by the client
                    //fromId = localScopeInfo.Id;
                    //// lastSyncTS : apply lines only if thye are not modified since last client sync
                    //lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                    //// isNew : make sure we take all lines if it's the first time we get
                    //isNew = localScopeReferenceInfo.IsNewScope;
                    //scope = new ScopeInfo { Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS };
                    ////Direction set to Download
                    //context.SyncWay = SyncWay.Download;

                    //(context, serverBatchInfo, serverChangesSelected) = await this.RemoteProvider.GetArchiveAsync(context, scope);

                    //// fromId : When applying rows, make sure it's identified as applied by this server scope
                    //fromId = serverScopeInfo.Id;
                    //// lastSyncTS : apply lines only if they are not modified since last client sync
                    //lastSyncTS = localScopeInfo.LastTimestamp;
                    //// isNew : if IsNew, don't apply deleted rows from server
                    //isNew = localScopeInfo.IsNewScope;
                    //scope = new ScopeInfo { Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS };

                    //(context, clientChangesApplied) = await this.LocalProvider.ApplyArchiveAsync(context, scope, serverBatchInfo);

                    //// Here we have to change the localScopeInfo.LastTimestamp to the good one
                    //// last ts from archive
                    //localScopeReferenceInfo.LastTimestamp = [something from the archive];
                    //// we are not new anymore
                    //localScopeReferenceInfo.IsNewScope = false;
                }


                // fromId : Make sure we don't select lines on server that has been already updated by the client
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastSyncTimestamp;
                // isNew : make sure we take all lines if it's the first time we get
                isNew = localScopeReferenceInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, Timestamp = lastSyncTS
                };
                //Direction set to Download
                context.SyncWay = SyncWay.Download;

                // JUST Before get changes, get the timestamp, to be sure to
                // get rows inserted / updated elsewhere since the sync is not over
                (context, serverTimestamp) = await this.RemoteProvider.GetLocalTimestampAsync(context,
                                                                                              new MessageTimestamp
                {
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                (context, serverBatchInfo, serverChangesSelected) =
                    await this.RemoteProvider.GetChangeBatchAsync(context,
                                                                  new MessageGetChangesBatch
                {
                    ScopeInfo             = scope,
                    Schema                = this.Configuration.Schema,
                    DownloadBatchSizeInKB = this.Configuration.DownloadBatchSizeInKB,
                    BatchDirectory        = this.Configuration.BatchDirectory,
                    Policy                = serverPolicy,
                    Filters               = this.Configuration.Filters,
                    SerializationFormat   = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }



                // Apply local changes

                // fromId : When applying rows, make sure it's identified as applied by this server scope
                fromId = serverScopeInfo.Id;
                // lastSyncTS : apply lines only if they are not modified since last client sync
                lastSyncTS = localScopeInfo.LastSyncTimestamp;
                // isNew : if IsNew, don't apply deleted rows from server
                isNew = localScopeInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, Timestamp = lastSyncTS
                };

                (context, clientChangesApplied) =
                    await this.LocalProvider.ApplyChangesAsync(context,
                                                               new MessageApplyChanges
                {
                    FromScope           = scope,
                    Schema              = this.Configuration.Schema,
                    Policy              = clientPolicy,
                    UseBulkOperations   = this.Configuration.UseBulkOperations,
                    CleanMetadatas      = this.Configuration.CleanMetadatas,
                    ScopeInfoTableName  = this.Configuration.ScopeInfoTableName,
                    Changes             = serverBatchInfo,
                    SerializationFormat = this.Configuration.SerializationFormat
                });


                context.TotalChangesDownloaded = clientChangesApplied.TotalAppliedChanges;
                context.TotalChangesUploaded   = clientChangesSelected.TotalChangesSelected;
                context.TotalSyncErrors        = clientChangesApplied.TotalAppliedChangesFailed;

                context.CompleteTime = DateTime.Now;

                serverScopeInfo.IsNewScope         = false;
                localScopeReferenceInfo.IsNewScope = false;
                localScopeInfo.IsNewScope          = false;

                serverScopeInfo.LastSync         = context.CompleteTime;
                localScopeReferenceInfo.LastSync = context.CompleteTime;
                localScopeInfo.LastSync          = context.CompleteTime;

                serverScopeInfo.LastSyncTimestamp         = serverTimestamp;
                localScopeReferenceInfo.LastSyncTimestamp = serverTimestamp;
                localScopeInfo.LastSyncTimestamp          = clientTimestamp;

                var duration = context.CompleteTime.Subtract(context.StartTime);
                serverScopeInfo.LastSyncDuration         = duration.Ticks;
                localScopeReferenceInfo.LastSyncDuration = duration.Ticks;
                localScopeInfo.LastSyncDuration          = duration.Ticks;

                serverScopeInfo.IsLocal         = true;
                localScopeReferenceInfo.IsLocal = false;

                context = await this.RemoteProvider.WriteScopesAsync(context,
                                                                     new MessageWriteScopes
                {
                    ScopeInfoTableName = this.Configuration.ScopeInfoTableName,
                    Scopes             = new List <ScopeInfo> {
                        serverScopeInfo, localScopeReferenceInfo
                    },
                    SerializationFormat = this.Configuration.SerializationFormat
                });


                serverScopeInfo.IsLocal = false;
                localScopeInfo.IsLocal  = true;

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                context = await this.LocalProvider.WriteScopesAsync(context,
                                                                    new MessageWriteScopes
                {
                    ScopeInfoTableName = this.Configuration.ScopeInfoTableName,
                    Scopes             = new List <ScopeInfo> {
                        localScopeInfo, serverScopeInfo
                    },
                    SerializationFormat = this.Configuration.SerializationFormat
                });

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }
            catch (SyncException se)
            {
                Console.WriteLine($"Sync Exception: {se.Message}. Type:{se.Type}. On provider: {se.ProviderName}.");
                throw;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Unknwon Exception: {ex.Message}.");
                throw new SyncException(ex, SyncStage.None, string.Empty);
            }
            finally
            {
                // End the current session
                context = await this.RemoteProvider.EndSessionAsync(context);

                context = await this.LocalProvider.EndSessionAsync(context);

                this.SessionState = SyncSessionState.Ready;
                this.SessionStateChanged?.Invoke(this, this.SessionState);
            }

            return(context);
        }
Exemple #5
0
        /// <summary>
        /// Apply changes : Insert / Updates Delete
        /// the fromScope is local client scope when this method is called from server
        /// the fromScope is server scope when this method is called from client
        /// </summary>
        public virtual async Task <(SyncContext, ChangesApplied)> ApplyChangesAsync(SyncContext context, ScopeInfo fromScope, BatchInfo changes)
        {
            try
            {
                ChangeApplicationAction changeApplicationAction;
                DbTransaction           applyTransaction = null;
                ChangesApplied          changesApplied   = new ChangesApplied();

                using (var connection = this.CreateConnection())
                {
                    try
                    {
                        await connection.OpenAsync();

                        // Create a transaction
                        applyTransaction = connection.BeginTransaction();

                        Debug.WriteLine($"----- Applying Changes for Scope \"{fromScope.Name}\" -----");
                        Debug.WriteLine("");

                        // -----------------------------------------------------
                        // 0) Check if we are in a reinit mode
                        // -----------------------------------------------------
                        if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal)
                        {
                            changeApplicationAction = this.ResetInternal(context, connection, applyTransaction, fromScope);

                            // Rollback
                            if (changeApplicationAction == ChangeApplicationAction.Rollback)
                            {
                                throw SyncException.CreateRollbackException(context.SyncStage);
                            }
                        }

                        // -----------------------------------------------------
                        // 1) Applying deletes. Do not apply deletes if we are in a new database
                        // -----------------------------------------------------
                        if (!fromScope.IsNewScope)
                        {
                            changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Deleted, changesApplied);

                            // Rollback
                            if (changeApplicationAction == ChangeApplicationAction.Rollback)
                            {
                                throw SyncException.CreateRollbackException(context.SyncStage);
                            }
                        }

                        // -----------------------------------------------------
                        // 1) Applying Inserts
                        // -----------------------------------------------------
                        changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Added, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw SyncException.CreateRollbackException(context.SyncStage);
                        }

                        // -----------------------------------------------------
                        // 1) Applying updates
                        // -----------------------------------------------------
                        changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Modified, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw SyncException.CreateRollbackException(context.SyncStage);
                        }

                        applyTransaction.Commit();

                        Debug.WriteLine($"--- End Applying Changes for Scope \"{fromScope.Name}\" ---");
                        Debug.WriteLine("");
                    }
                    catch (Exception exception)
                    {
                        Debug.WriteLine($"Caught exception while applying changes: {exception}");
                        throw;
                    }
                    finally
                    {
                        if (applyTransaction != null)
                        {
                            applyTransaction.Dispose();
                            applyTransaction = null;
                        }

                        if (connection != null && connection.State == ConnectionState.Open)
                        {
                            connection.Close();
                        }

                        if (changes != null)
                        {
                            changes.Clear();
                        }
                    }
                    return(context, changesApplied);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }