示例#1
0
        /// <summary>
        /// Create a new BatchInfo, containing all BatchPartInfo
        /// </summary>
        public BatchInfo(bool isInMemory, SyncSet inSchema, string rootDirectory = null, string directoryName = null)
        {
            this.InMemory = isInMemory;

            // We need to create a change table set, containing table with columns not readonly
            foreach (var table in inSchema.Tables)
            {
                SyncAdapter.CreateChangesTable(inSchema.Tables[table.TableName, table.SchemaName], this.SanitizedSchema);
            }

            // If not in memory, generate a directory name and initialize batch parts list
            if (!this.InMemory)
            {
                this.DirectoryRoot  = rootDirectory;
                this.BatchPartsInfo = new List <BatchPartInfo>();
                this.DirectoryName  = string.IsNullOrEmpty(directoryName) ? string.Concat(DateTime.UtcNow.ToString("yyyy_MM_dd_ss"), Path.GetRandomFileName().Replace(".", "")) : directoryName;
            }
        }
        /// <summary>
        /// Create a response message content based on a requested index in a server batch info
        /// </summary>
        private async Task <HttpMessageSendChangesResponse> GetChangesResponseAsync(SyncContext syncContext, long remoteClientTimestamp, BatchInfo serverBatchInfo,
                                                                                    DatabaseChangesApplied clientChangesApplied, DatabaseChangesSelected serverChangesSelected, int batchIndexRequested)
        {
            // 1) Create the http message content response
            var changesResponse = new HttpMessageSendChangesResponse(syncContext);

            changesResponse.ServerChangesSelected    = serverChangesSelected;
            changesResponse.ClientChangesApplied     = clientChangesApplied;
            changesResponse.ServerStep               = HttpStep.GetMoreChanges;
            changesResponse.ConflictResolutionPolicy = this.Options.ConflictResolutionPolicy;

            // If nothing to do, just send back
            if (serverBatchInfo.InMemory || serverBatchInfo.BatchPartsInfo.Count == 0)
            {
                if (this.ClientConverter != null && serverBatchInfo.InMemoryData != null && serverBatchInfo.InMemoryData.HasRows)
                {
                    BeforeSerializeRows(serverBatchInfo.InMemoryData, this.ClientConverter);
                }

                changesResponse.Changes               = serverBatchInfo.InMemoryData == null ? new ContainerSet() : serverBatchInfo.InMemoryData.GetContainerSet();
                changesResponse.BatchIndex            = 0;
                changesResponse.IsLastBatch           = true;
                changesResponse.RemoteClientTimestamp = remoteClientTimestamp;
                return(changesResponse);
            }

            // Get the batch part index requested
            var batchPartInfo = serverBatchInfo.BatchPartsInfo.First(d => d.Index == batchIndexRequested);

            // if we are not in memory, we set the BI in session, to be able to get it back on next request

            // create the in memory changes set
            var changesSet = new SyncSet();

            foreach (var table in Schema.Tables)
            {
                SyncAdapter.CreateChangesTable(Schema.Tables[table.TableName, table.SchemaName], changesSet);
            }

            await batchPartInfo.LoadBatchAsync(changesSet, serverBatchInfo.GetDirectoryFullPath(), this);

            // if client request a conversion on each row, apply the conversion
            if (this.ClientConverter != null && batchPartInfo.Data.HasRows)
            {
                BeforeSerializeRows(batchPartInfo.Data, this.ClientConverter);
            }

            changesResponse.Changes = batchPartInfo.Data.GetContainerSet();

            changesResponse.BatchIndex            = batchIndexRequested;
            changesResponse.IsLastBatch           = batchPartInfo.IsLastBatch;
            changesResponse.RemoteClientTimestamp = remoteClientTimestamp;
            changesResponse.ServerStep            = batchPartInfo.IsLastBatch ? HttpStep.GetMoreChanges : HttpStep.GetChangesInProgress;

            // If we have only one bpi, we can safely delete it
            if (batchPartInfo.IsLastBatch)
            {
                // delete the folder (not the BatchPartInfo, because we have a reference on it)
                if (this.Options.CleanFolder)
                {
                    var shouldDeleteFolder = true;
                    if (!string.IsNullOrEmpty(this.Options.SnapshotsDirectory))
                    {
                        var dirInfo  = new DirectoryInfo(serverBatchInfo.DirectoryRoot);
                        var snapInfo = new DirectoryInfo(this.Options.SnapshotsDirectory);
                        shouldDeleteFolder = dirInfo.FullName != snapInfo.FullName;
                    }

                    if (shouldDeleteFolder)
                    {
                        serverBatchInfo.TryRemoveDirectory();
                    }
                }
            }

            return(changesResponse);
        }
        /// <summary>
        /// Get changes from
        /// </summary>
        internal async Task <HttpMessageSendChangesResponse> ApplyThenGetChangesAsync(HttpMessageSendChangesRequest httpMessage, SessionCache sessionCache,
                                                                                      int clientBatchSize, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null)
        {
            // Overriding batch size options value, coming from client
            // having changes from server in batch size or not is decided by the client.
            // Basically this options is not used on the server, since it's always overriden by the client
            this.Options.BatchSize = clientBatchSize;

            // Get if we need to serialize data or making everything in memory
            var clientWorkInMemory = clientBatchSize == 0;

            // Get context from request message
            var ctx = httpMessage.SyncContext;

            // Set the context coming from the client
            this.SetContext(ctx);

            // Check schema.
            // If client has stored the schema, the EnsureScope will not be called on server.
            if (this.Schema == null || !this.Schema.HasTables || !this.Schema.HasColumns)
            {
                var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false);

                this.Schema = serverScopeInfo.Schema;
                this.Schema.EnsureSchema();
            }

            // ------------------------------------------------------------
            // FIRST STEP : receive client changes
            // ------------------------------------------------------------

            // We are receiving changes from client
            // BatchInfo containing all BatchPartInfo objects
            // Retrieve batchinfo instance if exists

            // Get batch info from session cache if exists, otherwise create it
            if (sessionCache.ClientBatchInfo == null)
            {
                sessionCache.ClientBatchInfo = new BatchInfo(clientWorkInMemory, Schema, this.Options.BatchDirectory);
            }

            // create the in memory changes set
            var changesSet = new SyncSet();

            foreach (var table in httpMessage.Changes.Tables)
            {
                SyncAdapter.CreateChangesTable(Schema.Tables[table.TableName, table.SchemaName], changesSet);
            }

            changesSet.ImportContainerSet(httpMessage.Changes, false);

            // If client has made a conversion on each line, apply the reverse side of it
            if (this.ClientConverter != null && changesSet.HasRows)
            {
                AfterDeserializedRows(changesSet, this.ClientConverter);
            }

            // add changes to the batch info
            await sessionCache.ClientBatchInfo.AddChangesAsync(changesSet, httpMessage.BatchIndex, httpMessage.IsLastBatch, this);

            // Clear the httpMessage set
            if (!clientWorkInMemory && httpMessage.Changes != null)
            {
                httpMessage.Changes.Clear();
            }

            // Until we don't have received all the batches, wait for more
            if (!httpMessage.IsLastBatch)
            {
                return new HttpMessageSendChangesResponse(httpMessage.SyncContext)
                       {
                           ServerStep = HttpStep.SendChangesInProgress
                       }
            }
            ;

            // ------------------------------------------------------------
            // SECOND STEP : apply then return server changes
            // ------------------------------------------------------------

            // get changes
            var(remoteClientTimestamp, serverBatchInfo, _, clientChangesApplied, serverChangesSelected) =
                await this.ApplyThenGetChangesAsync(httpMessage.Scope, sessionCache.ClientBatchInfo, cancellationToken, progress).ConfigureAwait(false);


            // Save the server batch info object to cache if not working in memory
            if (!clientWorkInMemory)
            {
                sessionCache.RemoteClientTimestamp = remoteClientTimestamp;

                sessionCache.ServerBatchInfo       = serverBatchInfo;
                sessionCache.ServerChangesSelected = serverChangesSelected;
                sessionCache.ClientChangesApplied  = clientChangesApplied;
            }

            // Get the firt response to send back to client
            return(await GetChangesResponseAsync(ctx, remoteClientTimestamp, serverBatchInfo, clientChangesApplied, serverChangesSelected, 0));
        }