/// <summary> /// Called by the CacheController when it wants this CacheRequest to be processed asynchronously. /// </summary> /// <param name="request">CacheRequest to be processed</param> /// <param name="state">User state object</param> /// <param name="cancellationToken"> </param> public async Task<CacheRequestResult> ProcessCacheRequestAsync(CacheRequest request, object state, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); var wrapper = new AsyncArgsWrapper { UserPassedState = state, CacheRequest = request, Step = HttpState.Start }; wrapper = await ProcessRequest(wrapper, cancellationToken); CacheRequestResult cacheRequestResult; if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.UploadResponse, wrapper.CacheRequest.Changes.Count, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } else { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.DownloadResponse, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } return cacheRequestResult; }
/// <summary> /// Called by the CacheController when it wants this CacheRequest to be processed asynchronously. /// </summary> /// <param name="request">CacheRequest to be processed</param> /// <param name="state">User state object</param> public override void ProcessCacheRequestAsync(CacheRequest request, object state) { this._workerManager.AddWorkRequest(new AsyncWorkRequest(ProcessCacheRequestWorker, CacheRequestCompleted, request, state)); }
/// <summary> /// Method that will contain the actual implementation of the cache request processing. /// </summary> /// <param name="request">CacheRequest object</param> /// <param name="state">User state object</param> public abstract void ProcessCacheRequestAsync(CacheRequest request, object state);
/// <summary> /// Method that will contain the actual implementation of the cache request processing. /// </summary> /// <param name="request">CacheRequest object</param> /// <returns>ChangeSet for a dowload request or ChangeSetResponse for an upload request</returns> public abstract object ProcessCacheRequest(CacheRequest request);
/// <summary> /// Method that performs a download. It gets the server blob anchor from the local provider and then creates an /// CacheRequest object for that download request. It then passes the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> private async Task <CacheRefreshStatistics> EnqueueDownloadRequest(CacheRefreshStatistics statistics, CancellationToken cancellationToken, IProgress <SyncProgressEvent> progress = null) { try { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } Boolean isLastBatch = false; while (!isLastBatch) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } // Create a SyncRequest for download. CacheRequest request = new CacheRequest { Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.localProvider.GetServerBlob() }; // Get Changes DateTime durationStartDate = DateTime.Now; var requestResult = await this.cacheRequestHandler.ProcessCacheRequestAsync( request, null, cancellationToken); statistics = await this.ProcessCacheRequestResults(statistics, requestResult, cancellationToken); // Check if we are at the end if (requestResult.ChangeSet == null || requestResult.ChangeSet.IsLastBatch) { isLastBatch = true; } // Reporting progress after get changes from local store if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.DownloadingChanges, DateTime.Now.Subtract(durationStartDate), true, (requestResult.ChangeSet != null ? requestResult.ChangeSet.Data : null))); } } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } statistics.Error = e; } return(statistics); }
/// <summary> /// Method that performs an upload. It gets the ChangeSet from the local provider and then creates an /// CacheRequest object for that ChangeSet and then passed the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> private async Task <CacheRefreshStatistics> EnqueueUploadRequest(CacheRefreshStatistics statistics, CancellationToken cancellationToken, IProgress <SyncProgressEvent> progress = null) { this.changeSetId = Guid.NewGuid(); try { // Check if cancellation has occured if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } // Get Changes DateTime durationStartDate = DateTime.Now; ChangeSet changeSet = await this.localProvider.GetChangeSet(this.changeSetId); // Reporting progress after get changes from local store if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.GetChanges, DateTime.Now.Subtract(durationStartDate), true, (changeSet != null ? changeSet.Data : null))); } // No data to upload. Skip upload phase. if (changeSet == null || changeSet.Data == null || changeSet.Data.Count == 0) { return(statistics); } // Create a SyncRequest out of this. CacheRequest request = new CacheRequest { RequestId = this.changeSetId, Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; // Upload changes to server durationStartDate = DateTime.Now; var requestResult = await this.cacheRequestHandler.ProcessCacheRequestAsync( request, changeSet.IsLastBatch, cancellationToken); // Get response from server if mb any conflicts or updated items statistics = await this.ProcessCacheRequestResults(statistics, requestResult, cancellationToken); // Reporting progress after uploading changes, and mb get back Conflicts and new Id from insterted items if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.UploadingChanges, DateTime.Now.Subtract(durationStartDate), true, changeSet.Data, requestResult.ChangeSetResponse.Conflicts, requestResult.ChangeSetResponse.UpdatedItems)); } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } statistics.Error = e; } return(statistics); }
private object ProcessUploadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in request.Changes) { // Skip tombstones that dont have a ID element. if (entity.ServiceMetadata.IsTombstone && string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { continue; } string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { if (TempIdToEntityMapping == null) { TempIdToEntityMapping = new Dictionary <string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); } this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); // Set the content length webRequest.ContentLength = memoryStream.Position; using (Stream requestStream = webRequest.GetRequestStream()) { CopyStreamContent(memoryStream, requestStream); // Close the request stream requestStream.Flush(); requestStream.Close(); } } // Fire the Before request handler this.FirePreRequestHandler(webRequest); // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSetResponse changeSetResponse = new ChangeSetResponse(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = this._syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (this._syncReader.HasTempId() && this._syncReader.HasConflictTempId()) { throw new CacheControllerException(string.Format("Service returned a TempId '{0}' in both live and conflicting entities.", this._syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (this._syncReader.HasTempId()) { tempId = this._syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(TempIdToEntityMapping, entity, tempId, changeSetResponse); } // If conflict if (this._syncReader.HasConflict()) { Conflict conflict = this._syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (this._syncReader.HasConflictTempId()) { tempId = this._syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(TempIdToEntityMapping, conflictEntity, tempId, changeSetResponse); } // Add conflict changeSetResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (this._syncReader.HasConflictTempId() && entity.ServiceMetadata.IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.ServiceMetadata.IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { changeSetResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: changeSetResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } } if (TempIdToEntityMapping != null && TempIdToEntityMapping.Count != 0) { // The client sent some inserts which werent ack'd by the service. Throw. StringBuilder builder = new StringBuilder("Server did not acknowledge with a permanant Id for the following tempId's: "); builder.Append(string.Join(",", TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } this.FirePostResponseHandler(webResponse); webResponse.Close(); return(changeSetResponse); } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }
private object ProcessDownloadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); webRequest.ContentLength = memoryStream.Position; Stream requestStream = webRequest.GetRequestStream(); CopyStreamContent(memoryStream, requestStream); requestStream.Flush(); requestStream.Close(); // Fire the Before request handler this.FirePreRequestHandler(webRequest); } // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSet changeSet = new ChangeSet(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: changeSet.AddItem(this._syncReader.GetItem()); break; case ReaderItemType.SyncBlob: changeSet.ServerBlob = this._syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: changeSet.IsLastBatch = !this._syncReader.GetHasMoreChangesValue(); break; } } this.FirePostResponseHandler(webResponse); } webResponse.Close(); return(changeSet); } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }
/// <summary> /// Method that performs a download. It gets the server blob anchor from the local provider and then creates an /// CacheRequest object for that download request. It then passes the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> void EnqueueDownloadRequest() { try { // Create a SyncRequest for download. CacheRequest request = new CacheRequest() { Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.LocalProvider.GetServerBlob() }; this._cacheRequestHandler.ProcessCacheRequestAsync(request, null /*state is null*/); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } // Error. EndSession refresh and post callback CompleteAsyncWithException(e); } }
/// <summary> /// Method that performs an upload. It gets the ChangeSet from the local provider and then creates an /// CacheRequest object for that ChangeSet and then passed the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> void EnqueueUploadRequest() { this.changeSetId = Guid.NewGuid(); try { ChangeSet changeSet = this._localProvider.GetChangeSet(this.changeSetId); if (changeSet == null || changeSet.Data == null || changeSet.Data.Count == 0) { // No data to upload. Skip upload phase. this.EnqueueDownloadRequest(); } else { // Create a SyncRequest out of this. CacheRequest request = new CacheRequest() { RequestId = this.changeSetId, Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; this._cacheRequestHandler.ProcessCacheRequestAsync(request, changeSet.IsLastBatch /*state is the IsLastBatch flag*/); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } // Error. EndSession refresh and post callback CompleteAsyncWithException(e); } }
/// <summary> /// Method that refreshes the Cache by uploading all modified changes and then downloading the /// server changes. /// </summary> /// <returns>A CacheRefreshStatistics object denoting the statistics from the Refresh call</returns> public CacheRefreshStatistics Refresh() { this._controllerBehavior.Locked = true; try { // First create the CacheRequestHandler this._cacheRequestHandler = CacheRequestHandler.CreateRequestHandler(this._serviceUri, this._controllerBehavior); CacheRefreshStatistics refreshStats = new CacheRefreshStatistics(); refreshStats.StartTime = DateTime.Now; bool uploadComplete = false; bool downloadComplete = false; // Start sync by executin an Upload request while (!uploadComplete || !downloadComplete) { if (!uploadComplete) { Guid changeSetId = Guid.NewGuid(); ChangeSet changeSet = this._localProvider.GetChangeSet(changeSetId); if (changeSet.Data == null || changeSet.Data.Count == 0) { // No data to upload. Skip upload phase. uploadComplete = true; } else { // Create a SyncRequest out of this. CacheRequest request = new CacheRequest() { RequestId = changeSetId, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; // Increment the stats refreshStats.TotalChangeSetsUploaded++; ChangeSetResponse response = (ChangeSetResponse)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the stats refreshStats.TotalUploads += (uint)request.Changes.Count; response.ConflictsInternal.ForEach((e1) => { if (e1 is SyncConflict) { refreshStats.TotalSyncConflicts++; } else { refreshStats.TotalSyncErrors++; } }); // Send the response to the local provider this._localProvider.OnChangeSetUploaded(changeSetId, response); uploadComplete = request.IsLastBatch; } } else if (!downloadComplete) { // Create a SyncRequest for download. CacheRequest request = new CacheRequest() { RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.LocalProvider.GetServerBlob() }; ChangeSet changeSet = (ChangeSet)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the refresh stats refreshStats.TotalChangeSetsDownloaded++; refreshStats.TotalDownloads += (uint)changeSet.Data.Count; // Call the SaveChangeSet method on local provider. this.LocalProvider.SaveChangeSet(changeSet); downloadComplete = changeSet.IsLastBatch; } } refreshStats.EndTime = DateTime.Now; // Finally return the statistics object return(refreshStats); } finally { // Unlock the ControllerBehavior object this._controllerBehavior.Locked = false; } }
/// <summary> /// Method that performs a download. It gets the server blob anchor from the local provider and then creates an /// CacheRequest object for that download request. It then passes the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> private async Task<CacheRefreshStatistics> EnqueueDownloadRequest(CacheRefreshStatistics statistics, CancellationToken cancellationToken, IProgress<SyncProgressEvent> progress = null) { try { if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); Boolean isLastBatch = false; while (!isLastBatch) { if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); // Create a SyncRequest for download. CacheRequest request = new CacheRequest { Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.localProvider.GetServerBlob() }; // Get Changes DateTime durationStartDate = DateTime.Now; var args = await this.cacheRequestHandler.ProcessCacheRequestAsync(request, null, cancellationToken); statistics = await this.ProcessCacheRequestResults(statistics, args, cancellationToken); // Check if we are at the end if (args.ChangeSet == null || args.ChangeSet.IsLastBatch) isLastBatch = true; // Reporting progress after get changes from local store if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.DownloadingChanges, DateTime.Now.Subtract(durationStartDate), true, (args.ChangeSet != null ? args.ChangeSet.Data : null))); } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) throw; statistics.Error = e; } return statistics; }
/// <summary> /// Method that performs an upload. It gets the ChangeSet from the local provider and then creates an /// CacheRequest object for that ChangeSet and then passed the processing asynchronously to the underlying /// CacheRequestHandler. /// </summary> private async Task<CacheRefreshStatistics> EnqueueUploadRequest(CacheRefreshStatistics statistics, CancellationToken cancellationToken, IProgress<SyncProgressEvent> progress = null) { this.changeSetId = Guid.NewGuid(); try { // Check if cancellation has occured if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); // Get Changes DateTime durationStartDate = DateTime.Now; ChangeSet changeSet = await this.localProvider.GetChangeSet(this.changeSetId); // Reporting progress after get changes from local store if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.GetChanges, DateTime.Now.Subtract(durationStartDate), true, (changeSet != null ? changeSet.Data : null))); // No data to upload. Skip upload phase. if (changeSet == null || changeSet.Data == null || changeSet.Data.Count == 0) return statistics; // Create a SyncRequest out of this. CacheRequest request = new CacheRequest { RequestId = this.changeSetId, Format = this.ControllerBehavior.SerializationFormat, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; // Upload changes to server durationStartDate = DateTime.Now; var args = await this.cacheRequestHandler.ProcessCacheRequestAsync(request, changeSet.IsLastBatch, cancellationToken); // Get response from server if mb any conflicts or updated items statistics = await this.ProcessCacheRequestResults(statistics, args, cancellationToken); // Reporting progress after uploading changes, and mb get back Conflicts and new Id from insterted items if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.UploadingChanges, DateTime.Now.Subtract(durationStartDate), true, changeSet.Data, args.ChangeSetResponse.Conflicts, args.ChangeSetResponse.UpdatedItems)); } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) throw; statistics.Error = e; } return statistics; }