/// <summary> /// Create a response message content based on a requested index in a server batch info /// </summary> private HttpMessageSendChangesResponse GetChangesResponse(SyncContext syncContext, long remoteClientTimestamp, BatchInfo serverBatchInfo, DatabaseChangesSelected serverChangesSelected, int batchIndexRequested, ConflictResolutionPolicy policy) { // 1) Create the http message content response var changesResponse = new HttpMessageSendChangesResponse(syncContext); changesResponse.ChangesSelected = serverChangesSelected; changesResponse.ServerStep = HttpStep.GetChanges; changesResponse.ConflictResolutionPolicy = policy; // If nothing to do, just send back if (serverBatchInfo.InMemory || serverBatchInfo.BatchPartsInfo.Count == 0) { if (this.ClientConverter != null && serverBatchInfo.InMemoryData.HasRows) { BeforeSerializeRows(serverBatchInfo.InMemoryData, this.ClientConverter); } changesResponse.Changes = 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(Schema.ScopeName); foreach (var table in Schema.Tables) { DbSyncAdapter.CreateChangesTable(Schema.Tables[table.TableName, table.SchemaName], changesSet); } batchPartInfo.LoadBatch(changesSet, serverBatchInfo.GetDirectoryFullPath()); // 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.GetChanges : 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); }
ApplyThenGetChangesAsync(ScopeInfo scope, BatchInfo clientBatchInfo, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { SyncSet schema; // Get context or create a new one var ctx = this.GetContext(); if (!this.StartTime.HasValue) { this.StartTime = DateTime.UtcNow; } //// create the in memory changes set //var changesSet = new SyncSet(); // is it something that could happens ? if (scope.Schema == null) { // Make a remote call to get Schema from remote provider var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false); schema = serverScopeInfo.Schema; } else { schema = scope.Schema; } schema.EnsureSchema(); // if we don't have any BatchPartsInfo, just generate a new one to get, at least, something to send to the server // and get a response with new data from server if (clientBatchInfo == null) { clientBatchInfo = new BatchInfo(true, schema); } // Get sanitized schema, without readonly columns var sanitizedSchema = clientBatchInfo.SanitizedSchema; // -------------------------------------------------------------- // STEP 1 : Send everything to the server side // -------------------------------------------------------------- // response HttpMessageSendChangesResponse httpMessageContent = null; // If not in memory and BatchPartsInfo.Count == 0, nothing to send. // But we need to send something, so generate a little batch part if (clientBatchInfo.InMemory || (!clientBatchInfo.InMemory && clientBatchInfo.BatchPartsInfo.Count == 0)) { var changesToSend = new HttpMessageSendChangesRequest(ctx, scope); if (this.Converter != null && clientBatchInfo.InMemoryData != null && clientBatchInfo.InMemoryData.HasRows) { this.BeforeSerializeRows(clientBatchInfo.InMemoryData); } var containerSet = clientBatchInfo.InMemoryData == null ? new ContainerSet() : clientBatchInfo.InMemoryData.GetContainerSet(); changesToSend.Changes = containerSet; changesToSend.IsLastBatch = true; changesToSend.BatchIndex = 0; // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); await this.InterceptAsync(new HttpMessageSendChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse> (this.HttpClient, this.ServiceUri, binaryData, HttpStep.SendChanges, ctx.SessionId, scope.Name, this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false); } else { // Foreach part, will have to send them to the remote // once finished, return context foreach (var bpi in clientBatchInfo.BatchPartsInfo.OrderBy(bpi => bpi.Index)) { // If BPI is InMempory, no need to deserialize from disk // othewise load it await bpi.LoadBatchAsync(sanitizedSchema, clientBatchInfo.GetDirectoryFullPath(), this); var changesToSend = new HttpMessageSendChangesRequest(ctx, scope); if (this.Converter != null && bpi.Data.HasRows) { BeforeSerializeRows(bpi.Data); } // Set the change request properties changesToSend.Changes = bpi.Data.GetContainerSet(); changesToSend.IsLastBatch = bpi.IsLastBatch; changesToSend.BatchIndex = bpi.Index; // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); await this.InterceptAsync(new HttpMessageSendChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse> (this.HttpClient, this.ServiceUri, binaryData, HttpStep.SendChanges, ctx.SessionId, scope.Name, this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false); // for some reasons, if server don't want to wait for more, just break // That should never happened, actually if (httpMessageContent.ServerStep != HttpStep.SendChangesInProgress) { break; } } } // -------------------------------------------------------------- // STEP 2 : Receive everything from the server side // -------------------------------------------------------------- // Now we have sent all the datas to the server and now : // We have a FIRST response from the server with new datas // 1) Could be the only one response (enough or InMemory is set on the server side) // 2) Could bt the first response and we need to download all batchs // While we have an other batch to process var isLastBatch = false; // Get if we need to work in memory or serialize things var workInMemoryLocally = this.Options.BatchSize == 0; // Create the BatchInfo and SyncContext to return at the end // Set InMemory by default to "true", but the real value is coming from server side var serverBatchInfo = new BatchInfo(workInMemoryLocally, schema, this.Options.BatchDirectory); // stats DatabaseChangesSelected serverChangesSelected = null; DatabaseChangesApplied clientChangesApplied = null; //timestamp generated by the server, hold in the client db long remoteClientTimestamp = 0; // While we are not reaching the last batch from server do { // Check if we are at the last batch. // If so, we won't make another loop isLastBatch = httpMessageContent.IsLastBatch; serverChangesSelected = httpMessageContent.ServerChangesSelected; clientChangesApplied = httpMessageContent.ClientChangesApplied; ctx = httpMessageContent.SyncContext; remoteClientTimestamp = httpMessageContent.RemoteClientTimestamp; var changesSet = sanitizedSchema.Clone(); changesSet.ImportContainerSet(httpMessageContent.Changes, false); if (this.Converter != null && changesSet.HasRows) { AfterDeserializedRows(changesSet); } // Create a BatchPartInfo instance await serverBatchInfo.AddChangesAsync(changesSet, httpMessageContent.BatchIndex, isLastBatch, this); // free some memory if (!workInMemoryLocally && httpMessageContent.Changes != null) { httpMessageContent.Changes.Clear(); } if (!isLastBatch) { // Ask for the next batch index var requestBatchIndex = httpMessageContent.BatchIndex + 1; // Create the message enveloppe var httpMessage = new HttpMessageGetMoreChangesRequest(ctx, requestBatchIndex); // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData = await serializer.SerializeAsync(httpMessage); await this.InterceptAsync(new HttpMessageGetMoreChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>( this.HttpClient, this.ServiceUri, binaryData, HttpStep.GetMoreChanges, ctx.SessionId, scope.Name, this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false); } } while (!isLastBatch); // generate the new scope item this.CompleteTime = DateTime.UtcNow; // Reaffect context this.SetContext(httpMessageContent.SyncContext); return(remoteClientTimestamp, serverBatchInfo, httpMessageContent.ConflictResolutionPolicy, clientChangesApplied, serverChangesSelected); }