public async Task InitializeAsync(IMobileServiceLocalStore store, IMobileServiceSyncHandler handler, StoreTrackingOptions trackingOptions)
        {
            if (store == null)
            {
                throw new ArgumentNullException("store");
            }
            handler = handler ?? new MobileServiceSyncHandler();

            this.initializeTask = new TaskCompletionSource <object>();

            using (await this.storeQueueLock.WriterLockAsync())
            {
                this.Handler = handler;
                this.Store   = store;
                this.storeTrackingOptions = trackingOptions;

                this.syncQueue = new ActionBlock();
                await this.Store.InitializeAsync();

                this.opQueue = await OperationQueue.LoadAsync(store);

                this.settings             = new MobileServiceSyncSettingsManager(store);
                this.localOperationsStore = StoreChangeTrackerFactory.CreateTrackedStore(store, StoreOperationSource.Local, trackingOptions, this.client.EventManager, this.settings);

                this.initializeTask.SetResult(null);
            }
        }
        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 async Task PurgeAsync(string tableName, MobileServiceTableKind tableKind, string queryId, string query, bool force, CancellationToken cancellationToken)
        {
            await this.EnsureInitializedAsync();

            var table = await this.GetTable(tableName);

            var queryDescription = MobileServiceTableQueryDescription.Parse(tableName, query);

            using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalPurge, this.storeTrackingOptions, this.client.EventManager, this.settings))
            {
                var action = new PurgeAction(table, tableKind, queryId, queryDescription, force, this, this.opQueue, this.client.EventManager, this.settings, this.Store, cancellationToken);
                await this.ExecuteSyncAction(action);
            }
        }
        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);
                    }
                }
            }));
        }
        public async Task PushAsync(CancellationToken cancellationToken, MobileServiceTableKind tableKind, params string[] tableNames)
        {
            await this.EnsureInitializedAsync();

            // use empty handler if its not a standard table push
            var handler = tableKind == MobileServiceTableKind.Table ? this.Handler : new MobileServiceSyncHandler();

            using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.ServerPush, this.storeTrackingOptions, this.client.EventManager, this.settings))
            {
                var action = new PushAction(this.opQueue,
                                            trackedStore,
                                            tableKind,
                                            tableNames,
                                            handler,
                                            this.client,
                                            this,
                                            cancellationToken);

                await this.ExecuteSyncAction(action);
            }
        }
        /// <summary>
        /// Pulls all items that match the given query from the associated remote table.
        /// </summary>
        /// <param name="tableName">The name of table to pull</param>
        /// <param name="tableKind">The kind of table</param>
        /// <param name="queryId">A string that uniquely identifies this query and is used to keep track of its sync state.</param>
        /// <param name="query">An OData query that determines which items to
        /// pull from the remote table.</param>
        /// <param name="options">An instance of <see cref="MobileServiceRemoteTableOptions"/></param>
        /// <param name="parameters">A dictionary of user-defined parameters and values to include in
        /// the request URI query string.</param>
        /// <param name="relatedTables">
        /// List of tables that may have related records that need to be push before this table is pulled down.
        /// When no table is specified, all tables are considered related.
        /// </param>
        /// <param name="reader">An instance of <see cref="MobileServiceObjectReader"/></param>
        /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> token to observe
        /// </param>
        /// <param name="pullOptions">
        /// PullOptions that determine how to pull data from the remote table
        /// </param>
        /// <returns>
        /// A task that completes when pull operation has finished.
        /// </returns>
        public async Task PullAsync(string tableName, MobileServiceTableKind tableKind, string queryId, string query, MobileServiceRemoteTableOptions options, IDictionary <string, string> parameters, IEnumerable <string> relatedTables, MobileServiceObjectReader reader, CancellationToken cancellationToken, PullOptions pullOptions)
        {
            await this.EnsureInitializedAsync();

            if (parameters != null)
            {
                if (parameters.Keys.Any(k => k.Equals(MobileServiceTable.IncludeDeletedParameterName, StringComparison.OrdinalIgnoreCase)))
                {
                    throw new ArgumentException("The key '{0}' is reserved and cannot be specified as a query parameter.".FormatInvariant(MobileServiceTable.IncludeDeletedParameterName));
                }
            }

            var table = await this.GetTable(tableName);

            var queryDescription = MobileServiceTableQueryDescription.Parse(this.client.MobileAppUri, tableName, query);

            // local schema should be same as remote schema otherwise push can't function
            if (queryDescription.Selection.Any() || queryDescription.Projections.Any())
            {
                throw new ArgumentException("Pull query with select clause is not supported.", "query");
            }

            bool isIncrementalSync = !String.IsNullOrEmpty(queryId);

            if (isIncrementalSync)
            {
                if (queryDescription.Ordering.Any())
                {
                    throw new ArgumentException("Incremental pull query must not have orderby clause.", "query");
                }
                if (queryDescription.Top.HasValue || queryDescription.Skip.HasValue)
                {
                    throw new ArgumentException("Incremental pull query must not have skip or top specified.", "query");
                }
            }

            if (!options.HasFlag(MobileServiceRemoteTableOptions.OrderBy) && queryDescription.Ordering.Any())
            {
                throw new ArgumentException("The supported table options does not include orderby.", "query");
            }

            if (!options.HasFlag(MobileServiceRemoteTableOptions.Skip) && queryDescription.Skip.HasValue)
            {
                throw new ArgumentException("The supported table options does not include skip.", "query");
            }

            if (!options.HasFlag(MobileServiceRemoteTableOptions.Top) && queryDescription.Top.HasValue)
            {
                throw new ArgumentException("The supported table options does not include top.", "query");
            }

            // let us not burden the server to calculate the count when we don't need it for pull
            queryDescription.IncludeTotalCount = false;

            using (var store = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.ServerPull, this.storeTrackingOptions, this.client.EventManager, this.settings))
            {
                var action = new PullAction(table, tableKind, this, queryId, queryDescription, parameters, relatedTables,
                                            this.opQueue, this.settings, store, options, pullOptions, reader, cancellationToken);
                await this.ExecuteSyncAction(action);
            }
        }