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); } } }
private async Task TryCancelOperation(MobileServiceTableOperationError error) { if (!await this.opQueue.DeleteAsync(error.Id, error.OperationVersion)) { throw new InvalidOperationException("The operation has been updated and cannot be cancelled."); } // delete errors for cancelled operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id); }
public Task CancelAndUpdateItemAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id); return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() => { await this.TryCancelOperation(error); await this.Store.UpsertAsync(error.TableName, item, fromServer: true); })); }
public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error) { string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id); return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() => { await this.TryCancelOperation(error); await this.Store.DeleteAsync(error.TableName, itemId); })); }
private async Task TryUpdateOperation(MobileServiceTableOperationError error, JObject item) { if (!await this.opQueue.UpdateAsync(error.Id, error.OperationVersion, item)) { throw new InvalidOperationException("The operation has been updated and cannot be updated again"); } // delete errors for updated operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id); }
public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id); return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() => { await this.TryUpdateOperation(error, item); if (error.OperationKind != MobileServiceTableOperationKind.Delete) { await this.Store.UpsertAsync(error.TableName, item, fromServer: true); } })); }
public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error) { string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id); return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() => { await this.TryCancelOperation(error); using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings)) { await trackedStore.DeleteAsync(error.TableName, itemId); } })); }
public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id); return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() => { await this.TryUpdateOperation(error, item); if (error.OperationKind != MobileServiceTableOperationKind.Delete) { using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings)) { await trackedStore.UpsertAsync(error.TableName, item, fromServer: true); } } })); }
/// <summary> /// Loads all the sync errors in local store that are recorded for this batch. /// </summary> /// <param name="serializerSettings">the serializer settings to use for reading the errors.</param> /// <returns>List of sync errors.</returns> public async Task <IList <MobileServiceTableOperationError> > LoadSyncErrorsAsync(MobileServiceJsonSerializerSettings serializerSettings) { var errors = new List <MobileServiceTableOperationError>(); JToken result = await this.Store.ReadAsync(new MobileServiceTableQueryDescription(MobileServiceLocalSystemTables.SyncErrors)); if (result is JArray) { foreach (JObject error in result) { var obj = MobileServiceTableOperationError.Deserialize(error, serializerSettings); obj.Context = this.context; errors.Add(obj); } } return(errors); }
public async Task PushAsync_ReplaysStoredErrors_IfTheyAreInStore() { var error = new MobileServiceTableOperationError("abc", 1, MobileServiceTableOperationKind.Update, HttpStatusCode.PreconditionFailed, "test", new JObject(), "{}", new JObject()); var store = new MobileServiceLocalStoreMock(); await store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false); var hijack = new TestHttpHandler(); IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack); await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler()); var ex = await ThrowsAsync<MobileServicePushFailedException>(service.SyncContext.PushAsync); }
/// <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); }
private async Task TryCancelOperation(MobileServiceTableOperationError error) { if (!await this.opQueue.DeleteAsync(error.Id, error.OperationVersion)) { throw new InvalidOperationException("The operation has been updated and cannot be cancelled."); } // delete errors for cancelled operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id); }
public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error) { string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id); return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () => { await this.TryCancelOperation(error); using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings)) { await trackedStore.DeleteAsync(error.TableName, itemId); } }); }
public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id); return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () => { await this.TryUpdateOperation(error, item); if (error.OperationKind != MobileServiceTableOperationKind.Delete) { using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings)) { await trackedStore.UpsertAsync(error.TableName, item, fromServer: true); } } }); }
private async Task UpdateOperationAsync_ConflictOperation_WithNotificationsEnabled_UsesTheCorrectStoreSource() { var client = new MobileServiceClient("http://www.test.com"); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); await context.InitializeAsync(store, StoreTrackingOptions.NotifyLocalConflictResolutionOperations); var manualResetEvent = new ManualResetEventSlim(); string operationId = "abc"; string itemId = "def"; string tableName = "test"; store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } }; store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 } }); store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } }); // operation exists before cancel Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // item exists before upsert Assert.IsNotNull(await store.LookupAsync(tableName, itemId)); var error = new MobileServiceTableOperationError(operationId, 1, MobileServiceTableOperationKind.Update, HttpStatusCode.Conflict, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); var item = new JObject() { { "id", itemId }, { "name", "unknown" } }; bool sourceIsLocalConflict = false; IDisposable subscription = client.EventManager.Subscribe<StoreOperationCompletedEvent>(o => { sourceIsLocalConflict = o.Operation.Source == StoreOperationSource.LocalConflictResolution; manualResetEvent.Set(); }); await context.UpdateOperationAsync(error, item); bool resetEventSignaled = manualResetEvent.Wait(1000); subscription.Dispose(); Assert.IsTrue(resetEventSignaled); Assert.IsTrue(sourceIsLocalConflict); }
public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStore_AndDeletesTheOperationAndError() { var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); await context.InitializeAsync(store); string operationId = "abc"; string itemId = "def"; string tableName = "test"; store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() } }; store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject()); store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } }); // operation exists before cancel Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // item exists before upsert Assert.IsNotNull(await store.LookupAsync(tableName, itemId)); var error = new MobileServiceTableOperationError(operationId, 0, MobileServiceTableOperationKind.Update, HttpStatusCode.Conflict, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); await context.CancelAndDiscardItemAsync(error); // operation is deleted Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // error is deleted Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId)); // item is upserted Assert.IsNull(await store.LookupAsync(tableName, itemId)); }
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); }
public Task CancelAndUpdateItemAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id); return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () => { await this.TryCancelOperation(error); await this.Store.UpsertAsync(error.TableName, item, fromServer: true); }); }
/// <summary> /// Adds a sync error to local store for this batch /// </summary> /// <param name="error">The sync error that occurred.</param> public Task AddSyncErrorAsync(MobileServiceTableOperationError error) { return(this.Store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false)); }
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; }
public async Task UpdateOperationAsync_UpsertsTheItemInLocalStore_AndDeletesTheError_AndUpdatesTheOperation() { var client = new MobileServiceClient("http://www.test.com"); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); await context.InitializeAsync(store); string operationId = "abc"; string itemId = "def"; string tableName = "test"; store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } }; store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 } }); store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } }); // operation exists before cancel Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // item exists before upsert Assert.IsNotNull(await store.LookupAsync(tableName, itemId)); var error = new MobileServiceTableOperationError(operationId, 1, MobileServiceTableOperationKind.Update, HttpStatusCode.Conflict, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); var item = new JObject() { { "id", itemId }, { "name", "unknown" } }; await context.UpdateOperationAsync(error, item); // operation is updated Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // error is deleted Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId)); JObject upserted = await store.LookupAsync(tableName, itemId); // item is upserted Assert.IsNotNull(upserted); Assert.AreEqual(item, upserted); }
/// <summary> /// Adds a sync error to local store for this batch /// </summary> /// <param name="error">The sync error that occurred.</param> public Task AddSyncErrorAsync(MobileServiceTableOperationError error) { return this.Store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false); }
public async Task UpdateOperationAsync_UpsertTheItemInOperation_AndDeletesTheError() { var client = new MobileServiceClient("http://www.test.com"); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); await context.InitializeAsync(store); string operationId = "abc"; string itemId = "def"; string tableName = "test"; var item = new JObject() { { "id", itemId }, { "name", "unknown" } }; store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } }; store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 }, { "item", item.ToString() }, { "kind", (int)MobileServiceTableOperationKind.Delete } }); // operation exists before cancel Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); // item does not exist Assert.IsNull(await store.LookupAsync(tableName, itemId)); var error = new MobileServiceTableOperationError(operationId, 1, MobileServiceTableOperationKind.Delete, HttpStatusCode.PreconditionFailed, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); var item2 = new JObject() { { "id", itemId }, { "name", "unknown" }, { "version", 2 } }; await context.UpdateOperationAsync(error, item2); var operation = await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId); // operation is updated Assert.IsNotNull(operation); // error is deleted Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId)); Assert.AreEqual(operation.GetValue("item").ToString(), item2.ToString(Formatting.None)); }
public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item) { string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id); return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () => { await this.TryUpdateOperation(error, item); if (error.OperationKind != MobileServiceTableOperationKind.Delete) { await this.Store.UpsertAsync(error.TableName, item, fromServer: true); } }); }
private async Task TestOperationModifiedException(bool operationExists, Func<MobileServiceTableOperationError, MobileServiceSyncContext, Task> action, String errorMessage) { var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); await context.InitializeAsync(store); string operationId = "abc"; string itemId = "def"; string tableName = "test"; if (operationExists) { store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "version", 3 } }); } else { // operation exists before cancel Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId)); } var error = new MobileServiceTableOperationError(operationId, 1, MobileServiceTableOperationKind.Update, HttpStatusCode.Conflict, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); var ex = await ThrowsAsync<InvalidOperationException>(() => action(error, context)); Assert.AreEqual(ex.Message, errorMessage); }
private async Task TryUpdateOperation(MobileServiceTableOperationError error, JObject item) { if (!await this.opQueue.UpdateAsync(error.Id, error.OperationVersion, item)) { throw new InvalidOperationException("The operation has been updated and cannot be updated again"); } // delete errors for updated operation await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id); }
private async Task ConflictOperation_WithNotificationsEnabled_UsesTheCorrectStoreOperationSource(Func<MobileServiceSyncContext, MobileServiceTableOperationError, Task> handler) { var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp); var store = new MobileServiceLocalStoreMock(); var context = new MobileServiceSyncContext(client); var manualResetEvent = new ManualResetEventSlim(); await context.InitializeAsync(store, StoreTrackingOptions.NotifyLocalConflictResolutionOperations); string operationId = "abc"; string itemId = string.Empty; string tableName = "test"; store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "version", 1 } }); var error = new MobileServiceTableOperationError(operationId, 1, MobileServiceTableOperationKind.Update, HttpStatusCode.Conflict, tableName, item: new JObject() { { "id", itemId } }, rawResult: "{}", result: new JObject()); bool sourceIsLocalConflict = false; IDisposable subscription = client.EventManager.Subscribe<StoreOperationCompletedEvent>(o => { sourceIsLocalConflict = o.Operation.Source == StoreOperationSource.LocalConflictResolution; manualResetEvent.Set(); }); await handler(context, error); bool resetEventSignaled = manualResetEvent.Wait(1000); subscription.Dispose(); Assert.IsTrue(resetEventSignaled); Assert.IsTrue(sourceIsLocalConflict); }
public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error) { string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id); return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () => { await this.TryCancelOperation(error); await this.Store.DeleteAsync(error.TableName, itemId); }); }