private async Task<MobileServicePushStatus> ExecuteBatchAsync(OperationBatch batch, List<MobileServiceTableOperationError> syncErrors)
        {
            // when cancellation is requested, abort the batch
            this.CancellationToken.Register(() => batch.Abort(MobileServicePushStatus.CancelledByToken));

            try
            {
                await ExecuteAllOperationsAsync(batch);
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            MobileServicePushStatus batchStatus = batch.AbortReason.GetValueOrDefault(MobileServicePushStatus.Complete);
            try
            {
                syncErrors.AddRange(await batch.LoadSyncErrorsAsync(this.client.SerializerSettings));
            }
            catch (Exception ex)
            {

                batch.OtherErrors.Add(new MobileServiceLocalStoreException("Failed to read errors from the local store.", ex));
            }
            return batchStatus;
        }
Beispiel #2
0
        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);
                }
            }
        }
Beispiel #3
0
        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 <MobileServicePushStatus> ExecuteBatchAsync(OperationBatch batch, List <MobileServiceTableOperationError> syncErrors)
        {
            // when cancellation is requested, abort the batch
            this.CancellationToken.Register(() => batch.Abort(MobileServicePushStatus.CancelledByToken));

            try
            {
                await ExecuteAllOperationsAsync(batch);
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            MobileServicePushStatus batchStatus = batch.AbortReason.GetValueOrDefault(MobileServicePushStatus.Complete);

            try
            {
                syncErrors.AddRange(await batch.LoadSyncErrorsAsync(this.client.SerializerSettings));
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(new MobileServiceLocalStoreException("Failed to read errors from the local store.", ex));
            }
            return(batchStatus);
        }
 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.");
     }
 }
 private static async Task TryStoreOperation(Func <Task> action, OperationBatch batch, string error)
 {
     try
     {
         await action();
     }
     catch (Exception ex)
     {
         batch.Abort(MobileServicePushStatus.CancelledBySyncStoreError);
         throw new MobileServiceLocalStoreException(error, ex);
     }
 }
        public override async Task ExecuteAsync()
        {
            var batch = new OperationBatch(this.syncHandler, this.Store, this.context);
            List<MobileServiceTableOperationError> syncErrors = new List<MobileServiceTableOperationError>();
            MobileServicePushStatus batchStatus = MobileServicePushStatus.InternalError;

            try
            {
                batchStatus = await ExecuteBatchAsync(batch, syncErrors);
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            await FinalizePush(batch, batchStatus, syncErrors);
        }
        public override async Task ExecuteAsync()
        {
            var batch = new OperationBatch(this.syncHandler, this.Store, this.context);
            List <MobileServiceTableOperationError> syncErrors = new List <MobileServiceTableOperationError>();
            MobileServicePushStatus batchStatus = MobileServicePushStatus.InternalError;

            try
            {
                batchStatus = await ExecuteBatchAsync(batch, syncErrors);
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            await FinalizePush(batch, batchStatus, syncErrors);
        }
        private bool TryAbortBatch(OperationBatch batch, Exception ex)
        {
            if (ex.IsNetworkError())
            {
                batch.Abort(MobileServicePushStatus.CancelledByNetworkError);
            }
            else if (ex.IsAuthenticationError())
            {
                batch.Abort(MobileServicePushStatus.CancelledByAuthenticationError);
            }
            else if (ex is MobileServicePushAbortException)
            {
                batch.Abort(MobileServicePushStatus.CancelledByOperation);
            }
            else
            {
                return(false); // not a known exception that should abort the batch
            }

            return(true);
        }
        private async Task FinalizePush(OperationBatch batch, MobileServicePushStatus batchStatus, IEnumerable <MobileServiceTableOperationError> syncErrors)
        {
            var result = new MobileServicePushCompletionResult(syncErrors, batchStatus);

            try
            {
                await batch.SyncHandler.OnPushCompleteAsync(result);

                // now that we've successfully given the errors to user, we can delete them from store
                await batch.DeleteErrorsAsync();
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            if (batchStatus != MobileServicePushStatus.Complete || batch.HasErrors(syncErrors))
            {
                List <MobileServiceTableOperationError> unhandledSyncErrors = syncErrors.Where(e => !e.Handled).ToList();

                Exception inner;
                if (batch.OtherErrors.Count == 1)
                {
                    inner = batch.OtherErrors[0];
                }
                else
                {
                    inner = batch.OtherErrors.Any() ? new AggregateException(batch.OtherErrors) : null;
                }

                // create a new result with only unhandled errors
                result = new MobileServicePushCompletionResult(unhandledSyncErrors, batchStatus);
                this.TaskSource.TrySetException(new MobileServicePushFailedException(result, inner));
            }
            else
            {
                this.TaskSource.SetResult(0);
            }
        }
        private bool TryAbortBatch(OperationBatch batch, Exception ex)
        {
            if (ex.IsNetworkError())
            {
                batch.Abort(MobileServicePushStatus.CancelledByNetworkError);
            }
            else if (ex.IsAuthenticationError())
            {
                batch.Abort(MobileServicePushStatus.CancelledByAuthenticationError);
            }
            else if (ex is MobileServicePushAbortException)
            {
                batch.Abort(MobileServicePushStatus.CancelledByOperation);
            }
            else
            {
                return false; // not a known exception that should abort the batch
            }

            return true;
        }
 private static async Task TryStoreOperation(Func<Task> action, OperationBatch batch, string error)
 {
     try
     {
         await action();
     }
     catch (Exception ex)
     {
         batch.Abort(MobileServicePushStatus.CancelledBySyncStoreError);
         throw new MobileServiceLocalStoreException(error, ex);
     }
 }
 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.");
     }
 }
 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;
        }
        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);
                }
            }
        }
        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;
        }
        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 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 FinalizePush(OperationBatch batch, MobileServicePushStatus batchStatus, IEnumerable<MobileServiceTableOperationError> syncErrors)
        {
            var result = new MobileServicePushCompletionResult(syncErrors, batchStatus);

            try
            {
                await batch.SyncHandler.OnPushCompleteAsync(result);

                // now that we've successfully given the errors to user, we can delete them from store
                await batch.DeleteErrorsAsync();
            }
            catch (Exception ex)
            {
                batch.OtherErrors.Add(ex);
            }

            if (batchStatus != MobileServicePushStatus.Complete || batch.HasErrors(syncErrors))
            {
                List<MobileServiceTableOperationError> unhandledSyncErrors = syncErrors.Where(e => !e.Handled).ToList();

                Exception inner;
                if (batch.OtherErrors.Count == 1)
                {
                    inner = batch.OtherErrors[0];
                }
                else
                {
                    inner = batch.OtherErrors.Any() ? new AggregateException(batch.OtherErrors) : null;
                }

                // create a new result with only unhandled errors
                result = new MobileServicePushCompletionResult(unhandledSyncErrors, batchStatus);
                this.TaskSource.TrySetException(new MobileServicePushFailedException(result, inner));
            }
            else
            {
                this.TaskSource.SetResult(0);
            }
        }
Beispiel #22
0
 private async Task TryUpdateOperationState(MobileServiceTableOperation operation, MobileServiceTableOperationState state, OperationBatch batch)
 {
     operation.State = state;
     await TryStoreOperation(() => this.OperationQueue.UpdateAsync(operation), batch, Resources.SyncStore_FailedToUpdateOperation);
 }