/// <summary> /// This method is only used when batch mode is enabled on server and we need to send back mor BatchPartInfo /// </summary> internal HttpMessageSendChangesResponse GetMoreChanges(HttpMessageGetMoreChangesRequest httpMessage, SessionCache sessionCache, CancellationToken cancellationToken) { if (sessionCache.ServerBatchInfo == null) { throw new ArgumentNullException("batchInfo stored in session can't be null if request more batch part info."); } return(GetChangesResponse(httpMessage.SyncContext, sessionCache.RemoteClientTimestamp, sessionCache.ServerBatchInfo, sessionCache.ServerChangesSelected, httpMessage.BatchIndexRequested, this.Options.ConflictResolutionPolicy)); }
/// <summary> /// This method is only used when batch mode is enabled on server and we need to send back mor BatchPartInfo /// </summary> internal Task <HttpMessageSendChangesResponse> GetMoreChangesAsync(HttpMessageGetMoreChangesRequest httpMessage, SessionCache sessionCache, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { if (sessionCache.ServerBatchInfo == null) { throw new ArgumentNullException("batchInfo stored in session can't be null if request more batch part info."); } return(GetChangesResponseAsync(httpMessage.SyncContext, sessionCache.RemoteClientTimestamp, sessionCache.ServerBatchInfo, sessionCache.ClientChangesApplied, sessionCache.ServerChangesSelected, httpMessage.BatchIndexRequested)); }
InternalApplyThenGetChangesAsync(ClientScopeInfo clientScopeInfo, SyncContext context, BatchInfo clientBatchInfo, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { await using var runner = await this.GetConnectionAsync(context, SyncMode.Reading, SyncStage.ChangesApplying, connection, transaction, cancellationToken, progress).ConfigureAwait(false); SyncSet schema; ServerScopeInfo serverScopeInfo; // is it something that could happens ? if (clientScopeInfo.Schema == null) { // Make a remote call to get Schema from remote provider (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync( context, null, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); schema = serverScopeInfo.Schema; } else { schema = clientScopeInfo.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(schema); } // -------------------------------------------------------------- // STEP 1 : Send everything to the server side // -------------------------------------------------------------- HttpResponseMessage response = 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.BatchPartsInfo.Count == 0) { var changesToSend = new HttpMessageSendChangesRequest(context, clientScopeInfo); var containerSet = new ContainerSet(); changesToSend.Changes = containerSet; changesToSend.IsLastBatch = true; changesToSend.BatchIndex = 0; changesToSend.BatchCount = clientBatchInfo.BatchPartsInfo == null ? 0 : clientBatchInfo.BatchPartsInfo.Count; var inMemoryRowsCount = changesToSend.Changes.RowsCount(); context.ProgressPercentage += 0.125; await this.InterceptAsync(new HttpSendingClientChangesRequestArgs(changesToSend, inMemoryRowsCount, inMemoryRowsCount, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); response = await this.httpRequestHandler.ProcessRequestAsync (this.HttpClient, context, this.ServiceUri, binaryData, HttpStep.SendChangesInProgress, this.SerializerFactory, this.Converter, this.Options.BatchSize, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); } else { int tmpRowsSendedCount = 0; // Foreach part, will have to send them to the remote // once finished, return context var initialPctProgress1 = context.ProgressPercentage; var localSerializer = new LocalJsonSerializer(); var interceptorsReading = this.interceptors.GetInterceptors <DeserializingRowArgs>(); if (interceptorsReading.Count > 0) { localSerializer.OnReadingRow(async(schemaTable, rowString) => { var args = new DeserializingRowArgs(context, schemaTable, rowString); await this.InterceptAsync(args); return(args.Result); }); } foreach (var bpi in clientBatchInfo.BatchPartsInfo.OrderBy(bpi => bpi.Index)) { // Get the updatable schema for the only table contained in the batchpartinfo var schemaTable = DbSyncAdapter.CreateChangesTable(schema.Tables[bpi.Tables[0].TableName, bpi.Tables[0].SchemaName]); // Generate the ContainerSet containing rows to send to the user var containerSet = new ContainerSet(); var containerTable = new ContainerTable(schemaTable); var fullPath = Path.Combine(clientBatchInfo.GetDirectoryFullPath(), bpi.FileName); containerSet.Tables.Add(containerTable); // read rows from file foreach (var row in localSerializer.ReadRowsFromFile(fullPath, schemaTable)) { containerTable.Rows.Add(row.ToArray()); } // Call the converter if needed if (this.Converter != null && containerTable.HasRows) { BeforeSerializeRows(containerTable, schemaTable, this.Converter); } // Create the send changes request var changesToSend = new HttpMessageSendChangesRequest(context, clientScopeInfo) { Changes = containerSet, IsLastBatch = bpi.IsLastBatch, BatchIndex = bpi.Index, BatchCount = clientBatchInfo.BatchPartsInfo.Count }; tmpRowsSendedCount += containerTable.Rows.Count; context.ProgressPercentage = initialPctProgress1 + ((changesToSend.BatchIndex + 1) * 0.2d / changesToSend.BatchCount); await this.InterceptAsync(new HttpSendingClientChangesRequestArgs(changesToSend, tmpRowsSendedCount, clientBatchInfo.RowsCount, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); response = await this.httpRequestHandler.ProcessRequestAsync (this.HttpClient, context, this.ServiceUri, binaryData, HttpStep.SendChangesInProgress, this.SerializerFactory, this.Converter, this.Options.BatchSize, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); // See #721 for issue and #721 for PR from slagtejn if (!bpi.IsLastBatch) { response.Dispose(); } } } // -------------------------------------------------------------- // 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 // 2) Could be the first response and we need to download all batchs context.SyncStage = SyncStage.ChangesSelecting; var initialPctProgress = 0.55; context.ProgressPercentage = initialPctProgress; // Create the BatchInfo var serverBatchInfo = new BatchInfo(schema); HttpMessageSummaryResponse summaryResponseContent = null; // Deserialize response incoming from server using (var streamResponse = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var responseSerializer = this.SerializerFactory.GetSerializer <HttpMessageSummaryResponse>(); summaryResponseContent = await responseSerializer.DeserializeAsync(streamResponse); } serverBatchInfo.RowsCount = summaryResponseContent.BatchInfo.RowsCount; serverBatchInfo.Timestamp = summaryResponseContent.RemoteClientTimestamp; context = summaryResponseContent.SyncContext; if (summaryResponseContent.BatchInfo.BatchPartsInfo != null) { foreach (var bpi in summaryResponseContent.BatchInfo.BatchPartsInfo) { serverBatchInfo.BatchPartsInfo.Add(bpi); } } // From here, we need to serialize everything on disk // Generate the batch directory var batchDirectoryRoot = this.Options.BatchDirectory; var batchDirectoryName = string.Concat(DateTime.UtcNow.ToString("yyyy_MM_dd_ss"), Path.GetRandomFileName().Replace(".", "")); serverBatchInfo.DirectoryRoot = batchDirectoryRoot; serverBatchInfo.DirectoryName = batchDirectoryName; if (!Directory.Exists(serverBatchInfo.GetDirectoryFullPath())) { Directory.CreateDirectory(serverBatchInfo.GetDirectoryFullPath()); } // If we have a snapshot we are raising the batches downloading process that will occurs await this.InterceptAsync(new HttpBatchesDownloadingArgs(context, serverBatchInfo, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // function used to download one part var dl = new Func <BatchPartInfo, Task>(async(bpi) => { if (cancellationToken.IsCancellationRequested) { return; } var changesToSend3 = new HttpMessageGetMoreChangesRequest(context, bpi.Index); var serializer3 = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData3 = await serializer3.SerializeAsync(changesToSend3).ConfigureAwait(false); var step3 = HttpStep.GetMoreChanges; await this.InterceptAsync(new HttpGettingServerChangesRequestArgs(bpi.Index, serverBatchInfo.BatchPartsInfo.Count, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // Raise get changes request context.ProgressPercentage = initialPctProgress + ((bpi.Index + 1) * 0.2d / serverBatchInfo.BatchPartsInfo.Count); var response = await this.httpRequestHandler.ProcessRequestAsync( this.HttpClient, context, this.ServiceUri, binaryData3, step3, this.SerializerFactory, this.Converter, 0, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); if (this.SerializerFactory.Key != "json") { var webSerializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesResponse>(); using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var getMoreChanges = await webSerializer.DeserializeAsync(responseStream); context = getMoreChanges.SyncContext; if (getMoreChanges != null && getMoreChanges.Changes != null && getMoreChanges.Changes.HasRows) { var localSerializer = new LocalJsonSerializer(); var interceptorsWriting = this.interceptors.GetInterceptors <SerializingRowArgs>(); if (interceptorsWriting.Count > 0) { localSerializer.OnWritingRow(async(syncTable, rowArray) => { var args = new SerializingRowArgs(context, syncTable, rowArray); await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); return(args.Result); }); } // Should have only one table var table = getMoreChanges.Changes.Tables[0]; var schemaTable = DbSyncAdapter.CreateChangesTable(schema.Tables[table.TableName, table.SchemaName]); var fullPath = Path.Combine(serverBatchInfo.GetDirectoryFullPath(), bpi.FileName); // open the file and write table header await localSerializer.OpenFileAsync(fullPath, schemaTable).ConfigureAwait(false); foreach (var row in table.Rows) { await localSerializer.WriteRowToFileAsync(new SyncRow(schemaTable, row), schemaTable).ConfigureAwait(false); } // Close file await localSerializer.CloseFileAsync(fullPath, schemaTable).ConfigureAwait(false); } } else { // Serialize await SerializeAsync(response, bpi.FileName, serverBatchInfo.GetDirectoryFullPath(), this).ConfigureAwait(false); } // Raise response from server containing a batch changes await this.InterceptAsync(new HttpGettingServerChangesResponseArgs(serverBatchInfo, bpi.Index, bpi.RowsCount, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); }); // Parrallel download of all bpis (which will launch the delete directory on the server side) await serverBatchInfo.BatchPartsInfo.ForEachAsync(bpi => dl(bpi), this.MaxDownladingDegreeOfParallelism).ConfigureAwait(false); // Send order of end of download var lastBpi = serverBatchInfo.BatchPartsInfo.FirstOrDefault(bpi => bpi.IsLastBatch); if (lastBpi != null) { var endOfDownloadChanges = new HttpMessageGetMoreChangesRequest(context, lastBpi.Index); var serializerEndOfDownloadChanges = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData3 = await serializerEndOfDownloadChanges.SerializeAsync(endOfDownloadChanges).ConfigureAwait(false); var endResponse = await this.httpRequestHandler.ProcessRequestAsync( this.HttpClient, context, this.ServiceUri, binaryData3, HttpStep.SendEndDownloadChanges, this.SerializerFactory, this.Converter, 0, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); // Deserialize response incoming from server // This is the last response // Should contains step HttpStep.SendEndDownloadChanges using var streamResponse = await endResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var endResponseSerializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesResponse>(); var endResponseContent = await endResponseSerializer.DeserializeAsync(streamResponse); context = endResponseContent.SyncContext; } // generate the new scope item this.CompleteTime = DateTime.UtcNow; await this.InterceptAsync(new HttpBatchesDownloadedArgs(summaryResponseContent, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); var serverSyncChanges = new ServerSyncChanges( summaryResponseContent.RemoteClientTimestamp, serverBatchInfo, summaryResponseContent.ServerChangesSelected ); return(context, serverSyncChanges, summaryResponseContent.ClientChangesApplied, summaryResponseContent.ConflictResolutionPolicy); }
GetChangesAsync(ClientScopeInfo clientScopeInfo, SyncParameters parameters = default, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { var context = new SyncContext(Guid.NewGuid(), clientScopeInfo.Name); if (parameters != null) { context.Parameters = parameters; } SyncSet schema; ServerScopeInfo serverScopeInfo; // Need the server scope (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync(context, clientScopeInfo.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false); schema = serverScopeInfo.Schema; schema.EnsureSchema(); clientScopeInfo.Schema = schema; clientScopeInfo.Setup = serverScopeInfo.Setup; clientScopeInfo.Version = serverScopeInfo.Version; var changesToSend = new HttpMessageSendChangesRequest(context, clientScopeInfo); var containerSet = new ContainerSet(); changesToSend.Changes = containerSet; changesToSend.IsLastBatch = true; changesToSend.BatchIndex = 0; changesToSend.BatchCount = 0; context.ProgressPercentage += 0.125; await this.InterceptAsync(new HttpSendingClientChangesRequestArgs(changesToSend, 0, 0, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // serialize message var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); var response = await this.httpRequestHandler.ProcessRequestAsync (this.HttpClient, context, this.ServiceUri, binaryData, HttpStep.SendChangesInProgress, this.SerializerFactory, this.Converter, this.Options.BatchSize, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); // -------------------------------------------------------------- // 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 context.SyncStage = SyncStage.ChangesSelecting; var initialPctProgress = 0.55; context.ProgressPercentage = initialPctProgress; // Create the BatchInfo var serverBatchInfo = new BatchInfo(schema); HttpMessageSummaryResponse summaryResponseContent = null; // Deserialize response incoming from server using (var streamResponse = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var responseSerializer = this.SerializerFactory.GetSerializer <HttpMessageSummaryResponse>(); summaryResponseContent = await responseSerializer.DeserializeAsync(streamResponse); } serverBatchInfo.RowsCount = summaryResponseContent.BatchInfo.RowsCount; serverBatchInfo.Timestamp = summaryResponseContent.RemoteClientTimestamp; context = summaryResponseContent.SyncContext; if (summaryResponseContent.BatchInfo.BatchPartsInfo != null) { foreach (var bpi in summaryResponseContent.BatchInfo.BatchPartsInfo) { serverBatchInfo.BatchPartsInfo.Add(bpi); } } //----------------------- // In Batch Mode //----------------------- // From here, we need to serialize everything on disk // Generate the batch directory var batchDirectoryRoot = this.Options.BatchDirectory; var batchDirectoryName = string.Concat(DateTime.UtcNow.ToString("yyyy_MM_dd_ss"), Path.GetRandomFileName().Replace(".", "")); serverBatchInfo.DirectoryRoot = batchDirectoryRoot; serverBatchInfo.DirectoryName = batchDirectoryName; if (!Directory.Exists(serverBatchInfo.GetDirectoryFullPath())) { Directory.CreateDirectory(serverBatchInfo.GetDirectoryFullPath()); } // hook to get the last batch part info at the end var bpis = serverBatchInfo.BatchPartsInfo.Where(bpi => !bpi.IsLastBatch); var lstbpi = serverBatchInfo.BatchPartsInfo.First(bpi => bpi.IsLastBatch); // function used to download one part var dl = new Func <BatchPartInfo, Task>(async(bpi) => { var changesToSend3 = new HttpMessageGetMoreChangesRequest(context, bpi.Index); var serializer3 = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData3 = await serializer3.SerializeAsync(changesToSend3).ConfigureAwait(false); var step3 = HttpStep.GetMoreChanges; await this.InterceptAsync(new HttpGettingServerChangesRequestArgs(bpi.Index, serverBatchInfo.BatchPartsInfo.Count, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); // Raise get changes request context.ProgressPercentage = initialPctProgress + ((bpi.Index + 1) * 0.2d / serverBatchInfo.BatchPartsInfo.Count); var response = await this.httpRequestHandler.ProcessRequestAsync( this.HttpClient, context, this.ServiceUri, binaryData3, step3, this.SerializerFactory, this.Converter, 0, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); // Serialize await SerializeAsync(response, bpi.FileName, serverBatchInfo.GetDirectoryFullPath(), this).ConfigureAwait(false); // Raise response from server containing a batch changes await this.InterceptAsync(new HttpGettingServerChangesResponseArgs(serverBatchInfo, bpi.Index, bpi.RowsCount, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); response.Dispose(); }); // Parrallel download of all bpis except the last one (which will launch the delete directory on the server side) await bpis.ForEachAsync(bpi => dl(bpi), this.MaxDownladingDegreeOfParallelism).ConfigureAwait(false); // Download last batch part that will launch the server deletion of the tmp dir await dl(lstbpi).ConfigureAwait(false); // generate the new scope item this.CompleteTime = DateTime.UtcNow; // Reaffect context context = summaryResponseContent.SyncContext; return(new ServerSyncChanges(summaryResponseContent.RemoteClientTimestamp, serverBatchInfo, summaryResponseContent.ServerChangesSelected)); }
InternalGetSnapshotAsync(ServerScopeInfo serverScopeInfo, SyncContext context, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { await using var runner = await this.GetConnectionAsync(context, SyncMode.Reading, SyncStage.ScopeLoading, connection, transaction, cancellationToken, progress).ConfigureAwait(false); // Make a remote call to get Schema from remote provider if (serverScopeInfo.Schema == null) { (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync( context, null, false, runner.Connection, runner.Transaction, runner.CancellationToken, runner.Progress).ConfigureAwait(false); serverScopeInfo.Schema.EnsureSchema(); } // Generate a batch directory var batchDirectoryRoot = this.Options.BatchDirectory; var batchDirectoryName = string.Concat(DateTime.UtcNow.ToString("yyyy_MM_dd_ss"), Path.GetRandomFileName().Replace(".", "")); var batchDirectoryFullPath = Path.Combine(batchDirectoryRoot, batchDirectoryName); if (!Directory.Exists(batchDirectoryFullPath)) { Directory.CreateDirectory(batchDirectoryFullPath); } // Create the BatchInfo serialized (forced because in a snapshot call, so we are obviously serialized on disk) var serverBatchInfo = new BatchInfo(serverScopeInfo.Schema, batchDirectoryRoot, batchDirectoryName); // Firstly, get the snapshot summary var changesToSend = new HttpMessageSendChangesRequest(context, null); var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); var response0 = await this.httpRequestHandler.ProcessRequestAsync( this.HttpClient, context, this.ServiceUri, binaryData, HttpStep.GetSummary, this.SerializerFactory, this.Converter, 0, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); HttpMessageSummaryResponse summaryResponseContent = null; using (var streamResponse = await response0.Content.ReadAsStreamAsync().ConfigureAwait(false)) { var responseSerializer = this.SerializerFactory.GetSerializer <HttpMessageSummaryResponse>(); if (streamResponse.CanRead) { summaryResponseContent = await responseSerializer.DeserializeAsync(streamResponse); serverBatchInfo.RowsCount = summaryResponseContent.BatchInfo?.RowsCount ?? 0; if (summaryResponseContent.BatchInfo?.BatchPartsInfo != null) { foreach (var bpi in summaryResponseContent.BatchInfo.BatchPartsInfo) { serverBatchInfo.BatchPartsInfo.Add(bpi); } } } } if (summaryResponseContent == null) { throw new Exception("Summary can't be null"); } context = summaryResponseContent.SyncContext; // no snapshot if ((serverBatchInfo.BatchPartsInfo == null || serverBatchInfo.BatchPartsInfo.Count <= 0) && serverBatchInfo.RowsCount <= 0) { return(context, 0, null, new DatabaseChangesSelected()); } // If we have a snapshot we are raising the batches downloading process that will occurs await this.InterceptAsync(new HttpBatchesDownloadingArgs(context, serverBatchInfo, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); await serverBatchInfo.BatchPartsInfo.ForEachAsync(async bpi => { var changesToSend3 = new HttpMessageGetMoreChangesRequest(context, bpi.Index); var serializer3 = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData3 = await serializer3.SerializeAsync(changesToSend3); await this.InterceptAsync(new HttpGettingServerChangesRequestArgs(bpi.Index, serverBatchInfo.BatchPartsInfo.Count, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); var response = await this.httpRequestHandler.ProcessRequestAsync( this.HttpClient, context, this.ServiceUri, binaryData3, HttpStep.GetMoreChanges, this.SerializerFactory, this.Converter, 0, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false); if (this.SerializerFactory.Key != "json") { var s = this.SerializerFactory.GetSerializer <HttpMessageSendChangesResponse>(); using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var getMoreChanges = await s.DeserializeAsync(responseStream); if (getMoreChanges != null && getMoreChanges.Changes != null && getMoreChanges.Changes.HasRows) { var localSerializer = new LocalJsonSerializer(); var interceptorsWriting = this.interceptors.GetInterceptors <SerializingRowArgs>(); if (interceptorsWriting.Count > 0) { localSerializer.OnWritingRow(async(syncTable, rowArray) => { var args = new SerializingRowArgs(context, syncTable, rowArray); await this.InterceptAsync(args, progress, cancellationToken).ConfigureAwait(false); return(args.Result); }); } // Should have only one table var table = getMoreChanges.Changes.Tables[0]; var schemaTable = DbSyncAdapter.CreateChangesTable(serverScopeInfo.Schema.Tables[table.TableName, table.SchemaName]); var fullPath = Path.Combine(batchDirectoryFullPath, bpi.FileName); // open the file and write table header await localSerializer.OpenFileAsync(fullPath, schemaTable).ConfigureAwait(false); foreach (var row in table.Rows) { await localSerializer.WriteRowToFileAsync(new SyncRow(schemaTable, row), schemaTable).ConfigureAwait(false); } // Close file await localSerializer.CloseFileAsync(fullPath, schemaTable).ConfigureAwait(false); } } else { // Serialize await SerializeAsync(response, bpi.FileName, batchDirectoryFullPath, this).ConfigureAwait(false); } // Raise response from server containing a batch changes await this.InterceptAsync(new HttpGettingServerChangesResponseArgs(serverBatchInfo, bpi.Index, bpi.RowsCount, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); }); await this.InterceptAsync(new HttpBatchesDownloadedArgs(summaryResponseContent, summaryResponseContent.SyncContext, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false); return(context, summaryResponseContent.RemoteClientTimestamp, serverBatchInfo, summaryResponseContent.ServerChangesSelected); }
GetChangesAsync(ScopeInfo clientScope, 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; } ServerScopeInfo serverScopeInfo; // Need the server scope serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false); schema = serverScopeInfo.Schema; schema.EnsureSchema(); clientScope.Schema = schema; clientScope.Setup = serverScopeInfo.Setup; clientScope.Version = serverScopeInfo.Version; // generate a message to send var changesToSend = new HttpMessageSendChangesRequest(ctx, clientScope) { Changes = null, IsLastBatch = true, BatchIndex = 0 }; var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); await this.InterceptAsync(new HttpMessageSendChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); // response var httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse> (this.HttpClient, this.ServiceUri, binaryData, HttpStep.GetChanges, ctx.SessionId, clientScope.Name, this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false); // if nothing available, return empty response if (httpMessageContent.Changes == null) { return(httpMessageContent.RemoteClientTimestamp, null, new DatabaseChangesSelected()); } // Get if we need to work in memory or serialize things var workInMemoryLocally = this.Options.BatchSize == 0; bool isLastBatch; //timestamp generated by the server, hold in the client db long remoteClientTimestamp; // Create the BatchInfo and SyncContext to return at the end var serverBatchInfo = new BatchInfo(workInMemoryLocally, schema, this.Options.BatchDirectory); // stats DatabaseChangesSelected serverChangesSelected; // 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; ctx = httpMessageContent.SyncContext; remoteClientTimestamp = httpMessageContent.RemoteClientTimestamp; serverChangesSelected = httpMessageContent.ServerChangesSelected; var changesSet = serverBatchInfo.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 (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 serializer2 = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData2 = await serializer2.SerializeAsync(httpMessage); await this.InterceptAsync(new HttpMessageGetMoreChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>( this.HttpClient, this.ServiceUri, binaryData2, HttpStep.GetMoreChanges, ctx.SessionId, this.ScopeName, this.SerializerFactory, this.Converter, 0, cancellationToken).ConfigureAwait(false); } } while (!isLastBatch); // generate the new scope item this.CompleteTime = DateTime.UtcNow; // Reaffect context this.SetContext(httpMessageContent.SyncContext); return(remoteClientTimestamp, serverBatchInfo, serverChangesSelected); }
GetSnapshotAsync(SyncSet schema = null, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { // Get context or create a new one var ctx = this.GetContext(); if (!this.StartTime.HasValue) { this.StartTime = DateTime.UtcNow; } // Make a remote call to get Schema from remote provider if (schema == null) { var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false); schema = serverScopeInfo.Schema; schema.EnsureSchema(); } // 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(false, schema, this.Options.BatchDirectory); bool isLastBatch; //timestamp generated by the server, hold in the client db long remoteClientTimestamp; // generate a message to send var changesToSend = new HttpMessageSendChangesRequest(ctx, null) { Changes = null, IsLastBatch = true, BatchIndex = 0 }; var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>(); var binaryData = await serializer.SerializeAsync(changesToSend); var httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>( this.HttpClient, this.ServiceUri, binaryData, HttpStep.GetSnapshot, ctx.SessionId, this.ScopeName, this.SerializerFactory, this.Converter, 0, cancellationToken).ConfigureAwait(false); // if no snapshot available, return empty response if (httpMessageContent.Changes == null) { return(httpMessageContent.RemoteClientTimestamp, null); } // 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; ctx = httpMessageContent.SyncContext; remoteClientTimestamp = httpMessageContent.RemoteClientTimestamp; var changesSet = serverBatchInfo.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 (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 serializer2 = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>(); var binaryData2 = await serializer2.SerializeAsync(httpMessage); await this.InterceptAsync(new HttpMessageGetMoreChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false); httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>( this.HttpClient, this.ServiceUri, binaryData2, HttpStep.GetMoreChanges, ctx.SessionId, this.ScopeName, this.SerializerFactory, this.Converter, 0, cancellationToken).ConfigureAwait(false); } } while (!isLastBatch); // Reaffect context this.SetContext(httpMessageContent.SyncContext); return(remoteClientTimestamp, serverBatchInfo); }
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); }