Esempio n. 1
0
        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);
            }
        }
Esempio n. 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);
                }
            }
        }
Esempio n. 3
0
        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.");
        }
Esempio n. 8
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);
                }
            }
        }
        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.");
            }
        }
Esempio n. 19
0
        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.");
            }
        }
Esempio n. 20
0
        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);
            }
        }
Esempio n. 26
0
        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
 }
Esempio n. 33
0
 /// <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);
Esempio n. 34
0
 /// <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);
Esempio n. 46
0
 private async Task TryUpdateOperationState(MobileServiceTableOperation operation, MobileServiceTableOperationState state, OperationBatch batch)
 {
     operation.State = state;
     await TryStoreOperation(() => this.OperationQueue.UpdateAsync(operation), batch, Resources.SyncStore_FailedToUpdateOperation);
 }