public ProcessCacheRequestCompletedEventArgs(Guid id, ChangeSetResponse response, Exception error, object state) { this.ChangeSetResponse = response; this.Error = error; this.State = state; this.Id = id; // Check that error is carried over to the response if (this.Error != null) { if (this.ChangeSetResponse == null) { this.ChangeSetResponse = new ChangeSetResponse(); } this.ChangeSetResponse.Error = this.Error; } }
public CacheRequestResult(Guid id, ChangeSetResponse response, int uploadCount, Exception error, object state) { this.ChangeSetResponse = response; this.Error = error; this.State = state; this.Id = id; this.BatchUploadCount = (uint)uploadCount; // Check that error is carried over to the response if (this.Error == null) { return; } if (this.ChangeSetResponse == null) { this.ChangeSetResponse = new ChangeSetResponse(); } this.ChangeSetResponse.Error = this.Error; }
/// <summary> /// OnChangeSetUploaded, fired when changeset is uploaded /// </summary> /// <param name="state"></param> /// <param name="response"></param> public abstract void OnChangeSetUploaded(Guid state, ChangeSetResponse response);
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 void CheckEntityServiceMetadataAndTempIds(Dictionary <string, IOfflineEntity> tempIdToEntityMapping, IOfflineEntity entity, string tempId, ChangeSetResponse response) { // Check service ID if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw new CacheControllerException(string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.ServiceMetadata.IsTombstone) { throw new CacheControllerException(string.Format("Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!tempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException("Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Add the entity to the Updated list. response.AddUpdatedItem(entity); // Once received, remove the tempId from the mapping list. tempIdToEntityMapping.Remove(tempId); }
/// <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; } }