/// <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)); }