public virtual async Task <bool> UpdateAsync(string id, long version, JObject item) { try { MobileServiceTableOperation op = await GetOperationAsync(id); if (op == null || op.Version != version) { return(false); } op.Version++; // Change the operation state back to pending since this is a newly updated operation without any conflicts op.State = MobileServiceTableOperationState.Pending; // if the operation type is delete then set the item property in the Operation table if (op.Kind == MobileServiceTableOperationKind.Delete) { op.Item = item; } else { op.Item = null; } await this.UpdateAsync(op); return(true); } catch (Exception ex) { throw new MobileServiceLocalStoreException("Failed to update operation in the local store.", ex); } }
private async Task ExecuteAllOperationsAsync(OperationBatch batch) { MobileServiceTableOperation operation = await this.OperationQueue.PeekAsync(0, this.tableKind, this.tableNames); // keep taking out operations and executing them until queue is empty or operation finds the bookmark or batch is aborted while (operation != null) { using (await this.OperationQueue.LockItemAsync(operation.ItemId, this.CancellationToken)) { bool success = await this.ExecuteOperationAsync(operation, batch); if (batch.AbortReason.HasValue) { break; } if (success) { // we successfuly executed an operation so remove it from queue await this.OperationQueue.DeleteAsync(operation.Id, operation.Version); } // get next operation operation = await this.OperationQueue.PeekAsync(operation.Sequence, this.tableKind, this.tableNames); } } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); // we don't allow any more operations on object that has already been deleted throw new InvalidOperationException("A delete operation on the item is already in the queue."); }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); // we don't allow any more operations on object that has already been deleted throw new InvalidOperationException(Resources.SyncContext_DeletePending); }
public async virtual Task <MobileServiceTableOperation> PeekAsync(long prevSequenceId, MobileServiceTableKind tableKind, IEnumerable <string> tableNames) { MobileServiceTableQueryDescription query = CreateQuery(); var tableKindNode = Compare(BinaryOperatorKind.Equal, "tableKind", (int)tableKind); var sequenceNode = Compare(BinaryOperatorKind.GreaterThan, "sequence", prevSequenceId); query.Filter = new BinaryOperatorNode(BinaryOperatorKind.And, tableKindNode, sequenceNode); if (tableNames != null && tableNames.Any()) { BinaryOperatorNode nameInList = tableNames.Select(t => Compare(BinaryOperatorKind.Equal, "tableName", t)) .Aggregate((first, second) => new BinaryOperatorNode(BinaryOperatorKind.Or, first, second)); query.Filter = new BinaryOperatorNode(BinaryOperatorKind.And, query.Filter, nameInList); } query.Ordering.Add(new OrderByNode(new MemberAccessNode(null, "sequence"), OrderByDirection.Ascending)); query.Top = 1; JObject op = await this.store.FirstOrDefault(query); if (op == null) { return(null); } return(MobileServiceTableOperation.Deserialize(op)); }
public async Task EnqueueAsync(MobileServiceTableOperation op) { op.Sequence = Interlocked.Increment(ref this.sequenceId); await this.store.UpsertAsync(MobileServiceLocalSystemTables.OperationQueue, op.Serialize(), fromServer : false); Interlocked.Increment(ref this.pendingOperations); }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); // we don't allow any more operations on object that has already been deleted throw new InvalidOperationException("A delete operation on the item is already in the queue."); }
private async Task LoadOperationItem(MobileServiceTableOperation operation, OperationBatch batch) { // only read the item from store if it is not in the operation already if (operation.Item == null) { await TryStoreOperation(async() => { operation.Item = await this.Store.LookupAsync(operation.TableName, operation.ItemId) as JObject; }, batch, "Failed to read the item from local store."); // Add sync error if item is not found. if (operation.Item == null) { // Create an item with only id to be able handle error properly. var item = new JObject(new JProperty(MobileServiceSystemColumns.Id, operation.ItemId)); var syncError = new MobileServiceTableOperationError(operation.Id, operation.Version, operation.Kind, null, operation.TableName, item, null, null) { TableKind = this.tableKind, Context = this.context }; await batch.AddSyncErrorAsync(syncError); } } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException(Resources.SyncContext_UpdatePending); } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException("An update operation on the item is already in the queue."); } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException("An update operation on the item is already in the queue."); } }
public override void Validate(MobileServiceTableOperation newOperation) { if (newOperation.ItemId != ItemId) { throw new ArgumentException("ItemId does not match", nameof(newOperation)); } // we don't allow any more operations on object that has already been deleted throw new InvalidOperationException("A delete operation on the item is already in the queue."); }
public async Task <MobileServiceTableOperation> GetOperationAsync(string id) { JObject op = await this.store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, id); if (op == null) { return(null); } return(MobileServiceTableOperation.Deserialize(op)); }
private async Task LoadOperationItem(MobileServiceTableOperation operation, OperationBatch batch) { // only read the item from store if it is not in the operation already if (operation.Item == null) { await TryStoreOperation(async() => { operation.Item = await this.Store.LookupAsync(operation.TableName, operation.ItemId) as JObject; }, batch, "Failed to read the item from local store."); } }
public async Task <MobileServiceTableOperation> GetOperationByItemIdAsync(string tableName, string itemId) { MobileServiceTableQueryDescription query = CreateQuery(); query.Filter = new BinaryOperatorNode(BinaryOperatorKind.And, Compare(BinaryOperatorKind.Equal, "tableName", tableName), Compare(BinaryOperatorKind.Equal, "itemId", itemId)); JObject op = await this.store.FirstOrDefault(query); return(MobileServiceTableOperation.Deserialize(op)); }
public virtual async Task UpdateAsync(MobileServiceTableOperation op) { try { await this.store.UpsertAsync(MobileServiceLocalSystemTables.OperationQueue, op.Serialize(), fromServer : false); } catch (Exception ex) { throw new MobileServiceLocalStoreException("Failed to update operation in the local store.", ex); } }
public virtual async Task UpdateAsync(MobileServiceTableOperation op) { try { await this.store.UpsertAsync(MobileServiceLocalSystemTables.OperationQueue, op.Serialize(), fromServer : false); } catch (Exception ex) { throw new MobileServiceLocalStoreException(Resources.SyncStore_FailedToUpdateOperation, ex); } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException("An insert operation on the item is already in the queue."); } if (newOperation is DeleteOperation && this.State != MobileServiceTableOperationState.Pending) { // if insert was attempted then we can't be sure if it went through or not hence we can't collapse delete throw new InvalidOperationException("The item is in inconsistent state in the local store. Please complete the pending sync by calling PushAsync() before deleting the item."); } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException("An insert operation on the item is already in the queue."); } if (newOperation is DeleteOperation && this.State != MobileServiceTableOperationState.Pending) { // if insert was attempted then we can't be sure if it went through or not hence we can't collapse delete throw new InvalidOperationException("The item is in inconsistent state in the local store. Please complete the pending sync by calling PushAsync() before deleting the item."); } }
public override void Collapse(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is DeleteOperation) { this.Cancel(); newOperation.Cancel(); } else if (newOperation is UpdateOperation) { this.Update(); newOperation.Cancel(); } }
public override void Collapse(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is DeleteOperation) { this.Cancel(); newOperation.Cancel(); } else if (newOperation is UpdateOperation) { this.Update(); newOperation.Cancel(); } }
public override void Validate(MobileServiceTableOperation newOperation) { Debug.Assert(newOperation.ItemId == this.ItemId); if (newOperation is InsertOperation) { throw new InvalidOperationException(Resources.SyncContext_DuplicateInsert); } if (newOperation is DeleteOperation && this.State != MobileServiceTableOperationState.Pending) { // if insert was attempted then we can't be sure if it went through or not hence we can't collapse delete throw new InvalidOperationException(Resources.SyncContext_InsertAttempted); } }
private Task ExecuteOperationAsync(MobileServiceTableOperation operation, JObject item) { return(this.ExecuteOperationSafeAsync(operation.ItemId, operation.TableName, async() => { MobileServiceTableOperation existing = await this.opQueue.GetOperationByItemIdAsync(operation.TableName, operation.ItemId); if (existing != null) { existing.Validate(operation); // make sure this operation is legal and collapses after any previous operation on same item already in the queue } try { await operation.ExecuteLocalAsync(this.localOperationsStore, item); // first execute operation on local store } catch (Exception ex) { if (ex is MobileServiceLocalStoreException) { throw; } throw new MobileServiceLocalStoreException("Failed to perform operation on local store.", ex); } if (existing != null) { existing.Collapse(operation); // cancel either existing, new or both operation // delete error for collapsed operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, existing.Id); if (existing.IsCancelled) // if cancelled we delete it { await this.opQueue.DeleteAsync(existing.Id, existing.Version); } else if (existing.IsUpdated) { await this.opQueue.UpdateAsync(existing); } } // if validate didn't cancel the operation then queue it if (!operation.IsCancelled) { await this.opQueue.EnqueueAsync(operation); } })); }
public override void Collapse(MobileServiceTableOperation newOperation) { if (newOperation.ItemId != ItemId) { throw new ArgumentException("ItemId does not match", nameof(newOperation)); } if (newOperation is DeleteOperation) { this.Cancel(); newOperation.Cancel(); } else if (newOperation is UpdateOperation) { this.Update(); newOperation.Cancel(); } }
public virtual async Task <bool> DeleteAsync(string id, long version) { try { MobileServiceTableOperation op = await GetOperationAsync(id); if (op == null || op.Version != version) { return(false); } await this.store.DeleteAsync(MobileServiceLocalSystemTables.OperationQueue, id); Interlocked.Decrement(ref this.pendingOperations); return(true); } catch (Exception ex) { throw new MobileServiceLocalStoreException("Failed to delete operation from the local store.", ex); } }
internal static MobileServiceTableOperation Deserialize(JObject obj) { if (obj == null) { return(null); } var kind = (MobileServiceTableOperationKind)obj.Value <int>("kind"); string tableName = obj.Value <string>("tableName"); var tableKind = (MobileServiceTableKind)obj.Value <int?>("tableKind").GetValueOrDefault(); string itemId = obj.Value <string>("itemId"); MobileServiceTableOperation operation = null; switch (kind) { case MobileServiceTableOperationKind.Insert: operation = new InsertOperation(tableName, tableKind, itemId); break; case MobileServiceTableOperationKind.Update: operation = new UpdateOperation(tableName, tableKind, itemId); break; case MobileServiceTableOperationKind.Delete: operation = new DeleteOperation(tableName, tableKind, itemId); break; } if (operation != null) { operation.Id = obj.Value <string>(MobileServiceSystemColumns.Id); operation.Sequence = obj.Value <long?>("sequence").GetValueOrDefault(); operation.Version = obj.Value <long?>("version").GetValueOrDefault(); string itemJson = obj.Value <string>("item"); operation.Item = !String.IsNullOrEmpty(itemJson) ? JObject.Parse(itemJson) : null; operation.State = (MobileServiceTableOperationState)obj.Value <int?>("state").GetValueOrDefault(); } return(operation); }
private async Task<bool> ExecuteOperationAsync(MobileServiceTableOperation operation, OperationBatch batch) { if (operation.IsCancelled || this.CancellationToken.IsCancellationRequested) { return false; } operation.Table = await this.context.GetTable(operation.TableName, this.client); await this.LoadOperationItem(operation, batch); if (this.CancellationToken.IsCancellationRequested) { return false; } await TryUpdateOperationState(operation, MobileServiceTableOperationState.Attempted, batch); // strip out system properties before executing the operation operation.Item = MobileServiceSyncTable.RemoveSystemPropertiesKeepVersion(operation.Item); JObject result = null; Exception error = null; try { result = await batch.SyncHandler.ExecuteTableOperationAsync(operation); } catch (Exception ex) { error = ex; } if (error != null) { await TryUpdateOperationState(operation, MobileServiceTableOperationState.Failed, batch); if (TryAbortBatch(batch, error)) { // there is no error to save in sync error and no result to capture // this operation will be executed again next time the push happens return false; } } // save the result if ExecuteTableOperation did not throw if (error == null && result.IsValidItem() && operation.CanWriteResultToStore) { await TryStoreOperation(() => this.Store.UpsertAsync(operation.TableName, result, fromServer: true), batch, "Failed to update the item in the local store."); } else if (error != null) { HttpStatusCode? statusCode = null; string rawResult = null; var iox = error as MobileServiceInvalidOperationException; if (iox != null && iox.Response != null) { statusCode = iox.Response.StatusCode; Tuple<string, JToken> content = await MobileServiceTable.ParseContent(iox.Response, this.client.SerializerSettings); rawResult = content.Item1; result = content.Item2.ValidItemOrNull(); } var syncError = new MobileServiceTableOperationError(operation.Id, operation.Version, operation.Kind, statusCode, operation.TableName, operation.Item, rawResult, result) { TableKind = this.tableKind, Context = this.context }; await batch.AddSyncErrorAsync(syncError); } bool success = error == null; return success; }
private void TestDeleteValidateThrows(MobileServiceTableOperation tableOperation) { var ex = AssertEx.Throws<InvalidOperationException>(() => this.operation.Validate(tableOperation)); Assert.AreEqual("A delete operation on the item is already in the queue.", ex.Message); }
public override void Collapse(MobileServiceTableOperation other) { // nothing to collapse we don't allow any operation after delete }
private async Task TestResultSave(MobileServiceTableOperation op, HttpStatusCode? status, string resultId, bool saved) { var result = new JObject() { { "id", resultId } }; if (saved) { this.store.Setup(s => s.UpsertAsync("table", It.Is<JObject[]>(list => list.Any(o => o.ToString() == result.ToString())), true)) .Returns(Task.FromResult(0)); } await this.TestExecuteAsync(op, result, status); }
private async Task TestExecuteAsync(MobileServiceTableOperation op, JObject result, HttpStatusCode? errorCode) { op.Sequence = 1; // picks up the operation this.opQueue.Setup(q => q.PeekAsync(0, MobileServiceTableKind.Table, It.IsAny<IEnumerable<string>>())).Returns(() => Task.FromResult(op)); this.opQueue.Setup(q => q.PeekAsync(op.Sequence, MobileServiceTableKind.Table, It.IsAny<IEnumerable<string>>())).Returns(() => Task.FromResult<MobileServiceTableOperation>(null)); // executes the operation via handler if (errorCode == null) { this.handler.Setup(h => h.ExecuteTableOperationAsync(op)).Returns(Task.FromResult(result)); } else { this.handler.Setup(h => h.ExecuteTableOperationAsync(op)) .Throws(new MobileServiceInvalidOperationException("", null, new HttpResponseMessage(errorCode.Value) { Content = new StringContent(result.ToString()) })); } // removes the operation from queue only if there is no error if (errorCode == null) { this.opQueue.Setup(q => q.DeleteAsync(It.IsAny<string>(), It.IsAny<long>())).Returns(Task.FromResult(true)); } // loads sync errors string syncError = @"[]"; this.store.Setup(s => s.ReadAsync(It.Is<MobileServiceTableQueryDescription>(q => q.TableName == MobileServiceLocalSystemTables.SyncErrors))).Returns(Task.FromResult(JToken.Parse(syncError))); // calls push complete this.handler.Setup(h => h.OnPushCompleteAsync(It.IsAny<MobileServicePushCompletionResult>())).Returns(Task.FromResult(0)) .Callback<MobileServicePushCompletionResult>(r => { Assert.AreEqual(r.Status, MobileServicePushStatus.Complete); Assert.AreEqual(r.Errors.Count(), 0); }); // deletes the errors this.store.Setup(s => s.DeleteAsync(It.Is<MobileServiceTableQueryDescription>(q => q.TableName == MobileServiceLocalSystemTables.SyncErrors))).Returns(Task.FromResult(0)); await this.action.ExecuteAsync(); this.store.VerifyAll(); this.opQueue.VerifyAll(); this.handler.VerifyAll(); await action.CompletionTask; }
public override void Collapse(MobileServiceTableOperation other) { // nothing to collapse we don't allow any operation after delete }
/// <summary> /// Validates that the operation can collapse with the late operation /// </summary> /// <exception cref="InvalidOperationException">This method throws when the operation cannot collapse with new operation.</exception> public abstract void Validate(MobileServiceTableOperation newOperation);
/// <summary> /// Collapse this operation with the late operation by cancellation of either operation. /// </summary> public abstract void Collapse(MobileServiceTableOperation newOperation);
private async Task TryUpdateOperationState(MobileServiceTableOperation operation, MobileServiceTableOperationState state, OperationBatch batch) { operation.State = state; await TryStoreOperation(() => this.OperationQueue.UpdateAsync(operation), batch, "Failed to update operation in the local store."); }
private async Task TryUpdateOperationState(MobileServiceTableOperation operation, MobileServiceTableOperationState state, OperationBatch batch) { operation.State = state; await TryStoreOperation(() => this.OperationQueue.UpdateAsync(operation), batch, "Failed to update operation in the local store."); }
private async Task <bool> ExecuteOperationAsync(MobileServiceTableOperation operation, OperationBatch batch) { if (operation.IsCancelled || this.CancellationToken.IsCancellationRequested) { return(false); } operation.Table = await this.context.GetTable(operation.TableName, this.client); await this.LoadOperationItem(operation, batch); if (this.CancellationToken.IsCancellationRequested) { return(false); } await TryUpdateOperationState(operation, MobileServiceTableOperationState.Attempted, batch); // strip out system properties before executing the operation operation.Item = MobileServiceSyncTable.RemoveSystemPropertiesKeepVersion(operation.Item); JObject result = null; Exception error = null; try { result = await batch.SyncHandler.ExecuteTableOperationAsync(operation); } catch (Exception ex) { error = ex; } if (error != null) { await TryUpdateOperationState(operation, MobileServiceTableOperationState.Failed, batch); if (TryAbortBatch(batch, error)) { // there is no error to save in sync error and no result to capture // this operation will be executed again next time the push happens return(false); } } // save the result if ExecuteTableOperation did not throw if (error == null && result.IsValidItem() && operation.CanWriteResultToStore) { await TryStoreOperation(() => this.Store.UpsertAsync(operation.TableName, result, fromServer: true), batch, "Failed to update the item in the local store."); } else if (error != null) { HttpStatusCode?statusCode = null; string rawResult = null; var iox = error as MobileServiceInvalidOperationException; if (iox != null && iox.Response != null) { statusCode = iox.Response.StatusCode; Tuple <string, JToken> content = await MobileServiceTable.ParseContent(iox.Response, this.client.SerializerSettings); rawResult = content.Item1; result = content.Item2.ValidItemOrNull(); } var syncError = new MobileServiceTableOperationError(operation.Id, operation.Version, operation.Kind, statusCode, operation.TableName, operation.Item, rawResult, result) { TableKind = this.tableKind, Context = this.context }; await batch.AddSyncErrorAsync(syncError); } bool success = error == null; return(success); }
private async Task ExecuteAllOperationsAsync(OperationBatch batch) { long prevSeq = 0; if (this.context.BatchApiEndpoint == null) { MobileServiceTableOperation operation = await this.OperationQueue.PeekAsync(prevSeq, this.tableKind, this.tableNames); // keep taking out operations and executing them until queue is empty or operation finds the bookmark or batch is aborted while (operation != null) { using (await this.OperationQueue.LockItemAsync(operation.ItemId, this.CancellationToken)) { bool success = await this.ExecuteOperationAsync(operation, batch); if (batch.AbortReason.HasValue) { break; } if (success) { // we successfuly executed an operation so remove it from queue await this.OperationQueue.DeleteAsync(operation.Id, operation.Version); } // get next operation operation = await this.OperationQueue.PeekAsync(operation.Sequence, this.tableKind, this.tableNames); } } return; } var baseClient = this.client; // keep taking out operations and executing them until queue is empty or operation finds the bookmark or batch is aborted while (true) { var filter = new MultipartRequestHandler(); this.client = new MobileServiceClient(baseClient.MobileAppUri, filter); // Get X Operations off the queue List <MobileServiceTableOperation> items = new List <MobileServiceTableOperation>(); MobileServiceTableOperation operation = await this.OperationQueue.PeekAsync(prevSeq, this.tableKind, this.tableNames); while (operation != null && items.Count < context.BatchSize) { items.Add(operation); prevSeq = operation.Sequence; operation = await this.OperationQueue.PeekAsync(prevSeq, this.tableKind, this.tableNames); } if (items.Count == 0) { break; } filter.ExpectedRequests = items.Count; // TODO: LOCK OPERATIONS // TODO: Trigger pre send hooks... List <Task <bool> > runningOperations = new List <Task <bool> >(); MultipartContent finalContent = new MultipartContent("mixed", "batch_" + Guid.NewGuid().ToString()); foreach (var tableOperation in items) { Task <bool> tableOperationtask = this.ExecuteOperationAsync(tableOperation, batch); runningOperations.Add(tableOperationtask); } // TODO: Move the actual POST into the filter? // TODO: Adjust count if op task is cancelled/etc before hitting filter await filter.allOperationsQueued(); foreach (var content in filter.Parts) { finalContent.Add(content); } var batchedResponse = await baseClient.InvokeApiAsync(context.BatchApiEndpoint, finalContent, HttpMethod.Post, null, null, this.CancellationToken); MultipartMemoryStreamProvider responseContents = await batchedResponse.Content.ReadAsMultipartAsync(); // TODO: verify order on response == order on request for (int i = 0; i < responseContents.Contents.Count; i++) { var response = await responseContents.Contents[i].ReadAsHttpResponseMessageAsync(); var promise = filter.Promises[i]; promise.SetResult(response); } // Now review results of all operations in the batch for (int i = 0; i < runningOperations.Count; i++) { var tableOperationTask = runningOperations[i]; var success = await tableOperationTask; if (success) { // we successfuly executed an operation so remove it from queue var tableOperation = items[i]; await this.OperationQueue.DeleteAsync(tableOperation.Id, tableOperation.Version); } } if (batch.AbortReason.HasValue) { break; } } this.client = baseClient; }
/// <summary> /// Defines all the system tables on the store /// </summary> /// <param name="store">An instance of <see cref="IMobileServiceLocalStore"/></param> public static void DefineAll(MobileServiceLocalStore store) { MobileServiceTableOperation.DefineTable(store); MobileServiceTableOperationError.DefineTable(store); MobileServiceSyncSettingsManager.DefineTable(store); }
/// <summary> /// Collapse this operation with the late operation by cancellation of either operation. /// </summary> public abstract void Collapse(MobileServiceTableOperation newOperation);
private async Task LoadOperationItem(MobileServiceTableOperation operation, OperationBatch batch) { // only read the item from store if it is not in the operation already if (operation.Item == null) { await TryStoreOperation(async () => { operation.Item = await this.Store.LookupAsync(operation.TableName, operation.ItemId) as JObject; }, batch, "Failed to read the item from local store."); } }
public async Task EnqueueAsync(MobileServiceTableOperation op) { op.Sequence = Interlocked.Increment(ref this.sequenceId); await this.store.UpsertAsync(MobileServiceLocalSystemTables.OperationQueue, op.Serialize(), fromServer: false); Interlocked.Increment(ref this.pendingOperations); }
private Task ExecuteOperationAsync(MobileServiceTableOperation operation, JObject item) { return this.ExecuteOperationSafeAsync(operation.ItemId, operation.TableName, async () => { MobileServiceTableOperation existing = await this.opQueue.GetOperationByItemIdAsync(operation.TableName, operation.ItemId); if (existing != null) { existing.Validate(operation); // make sure this operation is legal and collapses after any previous operation on same item already in the queue } try { await operation.ExecuteLocalAsync(this.localOperationsStore, item); // first execute operation on local store } catch (Exception ex) { if (ex is MobileServiceLocalStoreException) { throw; } throw new MobileServiceLocalStoreException("Failed to perform operation on local store.", ex); } if (existing != null) { existing.Collapse(operation); // cancel either existing, new or both operation // delete error for collapsed operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, existing.Id); if (existing.IsCancelled) // if cancelled we delete it { await this.opQueue.DeleteAsync(existing.Id, existing.Version); } else if (existing.IsUpdated) { await this.opQueue.UpdateAsync(existing); } } // if validate didn't cancel the operation then queue it if (!operation.IsCancelled) { await this.opQueue.EnqueueAsync(operation); } }); }
public virtual async Task UpdateAsync(MobileServiceTableOperation op) { try { await this.store.UpsertAsync(MobileServiceLocalSystemTables.OperationQueue, op.Serialize(), fromServer: false); } catch (Exception ex) { throw new MobileServiceLocalStoreException("Failed to update operation in the local store.", ex); } }
/// <summary> /// Validates that the operation can collapse with the late operation /// </summary> /// <exception cref="InvalidOperationException">This method throws when the operation cannot collapse with new operation.</exception> public abstract void Validate(MobileServiceTableOperation newOperation);
private async Task TryUpdateOperationState(MobileServiceTableOperation operation, MobileServiceTableOperationState state, OperationBatch batch) { operation.State = state; await TryStoreOperation(() => this.OperationQueue.UpdateAsync(operation), batch, Resources.SyncStore_FailedToUpdateOperation); }