Esempio n. 1
0
        async Task CommitUpdateAsync(RecordItemChange change)
        {
            Debug.Assert(change.HasLocalData);

            if (await this.DetectDuplicateAsync(change))
            {
                // Already applied this change
                return;
            }

            try
            {
                change.UpdatedKey = await m_store.RemoteStore.PutAsync(change.LocalData);

                return;
            }
            catch (Exception ex)
            {
                if (!m_errorHandler.ShouldCreateNewItemForConflict(change, ex))
                {
                    //
                    // Let the calling function decide how to handle all other scenarios
                    //
                    throw;
                }
            }
            //
            // Conflict resolution is simple. We don't want to lose data. If the data on the server changed from underneath us,
            // we will write this item as a NEW one, since the user did actually take the trouble to create the entry
            //
            await this.CommitNewAsync(change);
        }
Esempio n. 2
0
        async Task <bool> CommitPutAsync(RecordItemChange change, long itemLockID)
        {
            IItemDataTyped item = await m_store.Local.GetByIDAsync(change.ItemID);

            if (item == null)
            {
                return(false);
            }

            change.LocalData = item;
            if (item.Key.IsLocal)
            {
                await this.CommitNewAsync(change);
            }
            else
            {
                await this.CommitUpdateAsync(change);
            }
            //
            // Refetch the item from HealthVault, to get updated dates etc...
            //
            await this.RefreshItemAsync(change, itemLockID);

            return(true);
        }
Esempio n. 3
0
        // Are there any changes for the given type ID?
        public IAsyncOperation <bool> HasChangesForTypeAsync(string typeID)
        {
            if (string.IsNullOrEmpty(typeID))
            {
                throw new ArgumentException("typeID");
            }

            return(AsyncInfo.Run(async cancelToken =>
            {
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    await this.EnsureIndexAsync();
                    foreach (string itemID in m_itemIDIndex)
                    {
                        RecordItemChange change = await this.GetChangeAsync(itemID);
                        if (change != null && change.IsChangeForType(typeID))
                        {
                            return true;
                        }
                    }

                    return false;
                }
            }));
        }
Esempio n. 4
0
        // Get ids of items that changed for a given type ID
        public IAsyncOperation <IList <string> > GetIDsOfChangedItemsForTypeAsync(string typeID)
        {
            if (string.IsNullOrEmpty(typeID))
            {
                throw new ArgumentException("typeID");
            }

            return(AsyncInfo.Run(async cancelToken =>
            {
                List <string> ids = new List <string>();
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    await this.EnsureIndexAsync();
                    foreach (string itemID in m_itemIDIndex)
                    {
                        RecordItemChange change = await this.GetChangeAsync(itemID);
                        if (change != null && change.IsChangeForType(typeID))
                        {
                            ids.Add(itemID);
                        }
                    }

                    return (IList <string>)ids;
                }
            }));
        }
        public bool ShouldCreateNewItemForConflict(RecordItemChange change, Exception ex)
        {
            ServerException se = ex as ServerException;
            if (se != null)
            {
                return (this.IsItemKeyNotFound(se)); // Sometimes when the item is really missing, we get this error..
            }

            return false;
        }
Esempio n. 6
0
        public static int Compare(RecordItemChange x, RecordItemChange y)
        {
            int cmp = x.Timestamp.CompareTo(y.Timestamp);

            if (cmp == 0)
            {
                cmp = x.ItemID.CompareTo(y.ItemID);
            }
            return(cmp);
        }
        public bool ShouldCreateNewItemForConflict(RecordItemChange change, Exception ex)
        {
            ServerException se = ex as ServerException;

            if (se != null)
            {
                return(this.IsItemKeyNotFound(se));  // Sometimes when the item is really missing, we get this error..
            }

            return(false);
        }
Esempio n. 8
0
        async Task <bool> DetectDuplicateAsync(RecordItemChange change)
        {
            IList <RecordItem> results = await this.LookupCommitsInRemoteStoreAsync(change.ChangeID);

            if (results.IsNullOrEmpty())
            {
                return(false);
            }

            change.UpdatedKey = results[0].Key;
            return(true);
        }
Esempio n. 9
0
 async Task UpdateChangeAttemptCount(RecordItemChange change)
 {
     try
     {
         change.Attempt++;
         await m_changeTable.SaveChangeAsync(change);
     }
     catch (Exception ex)
     {
         this.NotifyError(ex);
     }
 }
Esempio n. 10
0
 async Task RefreshItemAsync(RecordItemChange change, long lockID)
 {
     change.UpdatedItem = null;
     try
     {
         change.UpdatedItem = await m_store.RemoteStore.GetItemAsync(change.UpdatedKey, m_store.SectionsToFetch);
     }
     catch (Exception ex)
     {
         this.NotifyError(ex);
     }
 }
Esempio n. 11
0
 async Task NotifySynchronizedTypesAsync(RecordItemChange change)
 {
     try
     {
         if (this.SynchronizedTypes != null)
         {
             await this.SynchronizedTypes.OnChangeCommittedAsync(change);
         }
     }
     catch
     {
     }
 }
Esempio n. 12
0
        // Assumes that you've acquire the item lock earlier!
        // Critical that the item lock be acquired
        async Task <bool> CommitChange(RecordItemChange change, long itemLockID)
        {
            bool dequeue = false;

            try
            {
                await this.UpdateChangeAttemptCount(change);

                if (change.ChangeType == RecordItemChangeType.Remove)
                {
                    await this.CommitRemoveAsync(change, itemLockID);
                }
                else
                {
                    if (await this.CommitPutAsync(change, itemLockID))
                    {
                        await this.NotifySynchronizedTypesAsync(change);
                    }
                }

                this.NotifyCommitSuccess(change);

                dequeue = true;
            }
            catch (Exception ex)
            {
                if (m_errorHandler.IsHaltingError(ex))
                {
                    this.NotifyError(ex);
                    return(false); // Stop processing items right away because of major Halting errors, such as Network
                }

                if (m_errorHandler.ShouldRetryCommit(change, ex))
                {
                    // Leave the change in the commit queue. try again later
                    this.NotifyError(ex);
                }
                else
                {
                    this.NotifyCommitFailed(change);
                    dequeue = true;
                }
            }

            if (dequeue)
            {
                await m_changeTable.RemoveChangeAsync(change.ItemID);
            }

            return(true);
        }
Esempio n. 13
0
        internal async Task OnPutCommittedAsync(RecordItemChange change)
        {
            Debug.Assert(change.HasUpdatedItem || change.HasLocalData);

            RecordItem updatedItem = change.UpdatedItem;

            if (updatedItem == null)
            {
                updatedItem     = change.LocalData.Item;
                updatedItem.Key = change.UpdatedKey;
            }

            await this.Data.Local.PutItemAsync(updatedItem);

            await this.UpdateKeyAsync(change.ItemID, updatedItem);
        }
        public bool ShouldRetryCommit(RecordItemChange change, Exception ex)
        {
            if (this.IsClientValidationError(ex) ||
                this.IsSerializationError(ex) ||
                !this.IsHttpError(ex)
            )
            {
                return false;
            }
            
            if (this.MaxAttemptsPerChange > 0 && change.Attempt >= this.MaxAttemptsPerChange)
            {
                return false;
            }

            return true;
        }
        public bool ShouldRetryCommit(RecordItemChange change, Exception ex)
        {
            if (this.IsClientValidationError(ex) ||
                this.IsSerializationError(ex) ||
                !this.IsHttpError(ex)
                )
            {
                return(false);
            }

            if (this.MaxAttemptsPerChange > 0 && change.Attempt >= this.MaxAttemptsPerChange)
            {
                return(false);
            }

            return(true);
        }
Esempio n. 16
0
        // Returns a list of item IDs... in "queue" order...with the oldest change first...
        public IAsyncOperation <IList <string> > GetChangeQueueAsync()
        {
            return(AsyncInfo.Run(async cancelToken =>
            {
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    List <RecordItemChange> changes = await this.GetAllChangesAsync();
                    changes.Sort((x, y) => RecordItemChange.Compare(x, y));
                    List <string> queue = (
                        from change in changes
                        select change.ItemID
                        ).ToList();

                    return (IList <string>)queue;
                }
            }));
        }
Esempio n. 17
0
        internal async Task <List <RecordItemChange> > GetAllChangesAsync()
        {
            List <RecordItemChange> changes = new List <RecordItemChange>();

            await this.EnsureIndexAsync();

            foreach (string itemID in m_itemIDIndex)
            {
                RecordItemChange change = await this.GetChangeAsync(itemID);

                if (change != null)
                {
                    changes.Add(change);
                }
            }

            return(changes);
        }
Esempio n. 18
0
 async Task CommitRemoveAsync(RecordItemChange change, long itemLockID)
 {
     try
     {
         if (change.Key.IsLocal)
         {
             return;
         }
         await m_store.RemoteStore.RemoveItemAsync(change.Key);
     }
     catch (ServerException se)
     {
         if (!m_errorHandler.IsItemKeyNotFound(se))
         {
             throw;
         }
     }
 }
Esempio n. 19
0
        internal async Task OnChangeCommittedAsync(RecordItemChange change)
        {
            if (change.ChangeType != RecordItemChangeType.Put)
            {
                return;
            }

            try
            {
                SynchronizedType sType = await this.GetAsync(change.TypeID);

                await sType.OnPutCommittedAsync(change);

                this.TypeUpdated.SafeInvokeInUIThread(this, change.TypeID);
            }
            catch
            {
            }
        }
Esempio n. 20
0
        /// <summary>
        /// Commit the given change batch.
        ///
        /// For simplicity and robustness, the current implementation commits each each change individually.
        ///
        /// This should be fine as the # of pending changes at any time should be moderate.
        /// And and most changes will be committed as they are made - ONLINE. In fact, the majority of batches will contain
        /// exactly 1 item.
        ///
        /// If necessary, we can optimize this behavior later by reducing round trips:
        ///     - Dupe Detection in batches
        ///     - New Items in a batch
        ///  However, since a single bad egg can ruin the entire batch, the error handling will be more complex.
        ///
        /// We can also consider introducing new platform methods that can essentially do all of this in a single call.
        ///
        /// </summary>
        async Task CommitChangesAsync(IList <string> changedItems)
        {
            foreach (string itemID in changedItems)
            {
                if (!this.IsCommitEnabled)
                {
                    break;
                }

                long itemLockID = 0;
                if ((itemLockID = this.AcquireItemLock(itemID)) == 0)
                {
                    // Item is being edited.. we'll wait until we try to commit the next batch
                    continue;
                }

                try
                {
                    bool shouldContinue = true;

                    RecordItemChange change = await m_changeTable.GetChangeForItemAsync(itemID);

                    if (change != null)
                    {
                        shouldContinue = await this.CommitChange(change, itemLockID);
                    }
                    if (!shouldContinue) // Was there a halting error?
                    {
                        return;
                    }
                }
                finally
                {
                    this.ReleaseItemLock(itemID, itemLockID);
                }
            }
        }
Esempio n. 21
0
        // Remove all pending changes for a given type ID
        public IAsyncAction RemoveAllChangesForTypeAsync(string typeID)
        {
            if (string.IsNullOrEmpty(typeID))
            {
                throw new ArgumentException("typeID");
            }

            return(AsyncInfo.Run(async cancelToken =>
            {
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    await this.EnsureIndexAsync();
                    string[] allIDs = m_itemIDIndex.ToArray();
                    foreach (string itemID in allIDs)
                    {
                        RecordItemChange change = await this.GetChangeAsync(itemID);
                        if (change != null && change.IsChangeForType(typeID))
                        {
                            await this.DeleteChangeAsync(itemID);
                        }
                    }
                }
            }));
        }
Esempio n. 22
0
        public IAsyncOperation <IList <RecordItemChange> > GetChangesForItemsAsync(IList <string> itemIDs)
        {
            if (itemIDs.IsNullOrEmpty())
            {
                throw new ArgumentException("itemID");
            }

            return(AsyncInfo.Run <IList <RecordItemChange> >(async cancelToken =>
            {
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    List <RecordItemChange> changes = new List <RecordItemChange>();
                    foreach (string id in itemIDs)
                    {
                        RecordItemChange change = await this.GetChangeAsync(id);
                        if (change != null)
                        {
                            changes.Add(change);
                        }
                    }
                    return changes;
                }
            }));
        }
Esempio n. 23
0
        // TRACK CHANGES for the given typeID & key
        public IAsyncAction TrackChangeAsync(string typeID, ItemKey key, RecordItemChangeType changeType)
        {
            if (string.IsNullOrEmpty(typeID))
            {
                throw new ArgumentException("typeID");
            }
            if (key == null)
            {
                throw new ArgumentException("key");
            }

            return(AsyncInfo.Run(async cancelToken =>
            {
                using (await CrossThreadLockScope.Enter(m_lock))
                {
                    RecordItemChange change = await this.GetChangeAsync(key.ID);
                    change = RecordItemChange.UpdateChange(typeID, key, changeType, change);

                    await this.SaveChangeAsync(change);

                    this.UpdateIndex(key.ID);
                }
            }));
        }
        // Assumes that you've acquire the item lock earlier! 
        // Critical that the item lock be acquired
        async Task<bool> CommitChange(RecordItemChange change, long itemLockID)
        { 
            bool dequeue = false;
            try
            {
                await this.UpdateChangeAttemptCount(change);

                if (change.ChangeType == RecordItemChangeType.Remove)
                {
                    await this.CommitRemoveAsync(change, itemLockID);
                }
                else
                {
                    if (await this.CommitPutAsync(change, itemLockID))
                    {
                        await this.NotifySynchronizedTypesAsync(change);
                    }
                }
                
                this.NotifyCommitSuccess(change);

                dequeue = true;
            }
            catch (Exception ex)
            {
                if (m_errorHandler.IsHaltingError(ex))
                {
                    this.NotifyError(ex);
                    return false; // Stop processing items right away because of major Halting errors, such as Network
                }

                if (m_errorHandler.ShouldRetryCommit(change, ex))
                {
                    // Leave the change in the commit queue. try again later
                    this.NotifyError(ex);
                }
                else
                {
                    this.NotifyCommitFailed(change);
                    dequeue = true;
                }
            }

            if (dequeue)
            {
                await m_changeTable.RemoveChangeAsync(change.ItemID);
            }

            return true;
        }
        async Task<bool> CommitPutAsync(RecordItemChange change, long itemLockID)
        {
            IItemDataTyped item = await m_store.Local.GetByIDAsync(change.ItemID);
            if (item == null)
            {
                return false;
            }

            change.LocalData = item;
            if (item.Key.IsLocal)
            {
                await this.CommitNewAsync(change);
            }
            else
            {
                await this.CommitUpdateAsync(change);
            }
            //
            // Refetch the item from HealthVault, to get updated dates etc...
            //
            await this.RefreshItemAsync(change, itemLockID);

            return true;
        }
        async Task CommitUpdateAsync(RecordItemChange change)
        {
            Debug.Assert(change.HasLocalData);

            if (await this.DetectDuplicateAsync(change))
            {
                // Already applied this change
                return;
            }                

            try
            {
                change.UpdatedKey = await m_store.RemoteStore.PutAsync(change.LocalData);
                return;
            }
            catch (Exception ex)
            {
                if (!m_errorHandler.ShouldCreateNewItemForConflict(change, ex))
                {
                    //
                    // Let the calling function decide how to handle all other scenarios
                    //
                    throw;
                }
            }
            //
            // Conflict resolution is simple. We don't want to lose data. If the data on the server changed from underneath us,
            // we will write this item as a NEW one, since the user did actually take the trouble to create the entry
            //
            await this.CommitNewAsync(change);
        }
 async Task CommitNewAsync(RecordItemChange change)
 {
     Debug.Assert(change.HasLocalData);
     change.UpdatedKey = await m_store.RemoteStore.NewAsync(change.LocalData);
 }
 async Task CommitRemoveAsync(RecordItemChange change, long itemLockID)
 {
     try
     {
         if (change.Key.IsLocal)
         {
             return;
         }
         await m_store.RemoteStore.RemoveItemAsync(change.Key);
     }
     catch(ServerException se)
     {
         if (!m_errorHandler.IsItemKeyNotFound(se))
         {
             throw;
         }
     }
 }
Esempio n. 29
0
 async Task CommitNewAsync(RecordItemChange change)
 {
     Debug.Assert(change.HasLocalData);
     change.UpdatedKey = await m_store.RemoteStore.NewAsync(change.LocalData);
 }
 internal async Task SaveChangeAsync(RecordItemChange change)
 {
     await m_changeStore.PutAsync(change.ItemID, change);
 }
        internal async Task OnPutCommittedAsync(RecordItemChange change)
        {
            Debug.Assert(change.HasUpdatedItem || change.HasLocalData);

            RecordItem updatedItem = change.UpdatedItem;
            if (updatedItem == null)
            {
                updatedItem = change.LocalData.Item;
                updatedItem.Key = change.UpdatedKey;
            }

            await this.Data.Local.PutItemAsync(updatedItem);
            await this.UpdateKeyAsync(change.ItemID, updatedItem);
        }
 async Task NotifySynchronizedTypesAsync(RecordItemChange change)
 {
     try
     {
         if (this.SynchronizedTypes != null)
         {
             await this.SynchronizedTypes.OnChangeCommittedAsync(change);
         }
     }
     catch
     {
     }
 }
Esempio n. 33
0
 void NotifyCommitSuccess(RecordItemChange change)
 {
     Debug.WriteLine("Change Committed {0}", change);
     this.CommitSuccess.SafeInvokeInUIThread(this, change);
 }
 void NotifyCommitSuccess(RecordItemChange change)
 {
     Debug.WriteLine("Change Committed {0}", change);
     this.CommitSuccess.SafeInvokeInUIThread(this, change);
 }
        internal async Task OnChangeCommittedAsync(RecordItemChange change)
        {
            if (change.ChangeType != RecordItemChangeType.Put)
            {
                return;
            }          
              
            try
            {
                SynchronizedType sType = await this.GetAsync(change.TypeID);

                await sType.OnPutCommittedAsync(change);
                
                this.TypeUpdated.SafeInvokeInUIThread(this, change.TypeID);
            }
            catch
            {
            }
        }
 void NotifyCommitFailed(RecordItemChange change)
 {
     Debug.WriteLine("Change Commit failed {0}", change);
     this.CommitFailed.SafeInvokeInUIThread(this, change);
 }
Esempio n. 37
0
 internal async Task SaveChangeAsync(RecordItemChange change)
 {
     await m_changeStore.PutAsync(change.ItemID, change);
 }
        public static RecordItemChange UpdateChange(string typeID, ItemKey key, RecordItemChangeType changeType, RecordItemChange existingChange)
        {
            if (existingChange == null)
            {
                return new RecordItemChange(typeID, key, changeType);
            }

            if (existingChange.ChangeType == RecordItemChangeType.Remove && changeType == RecordItemChangeType.Put)
            {
                // Can't put an item already marked for deletion
                throw new StoreException(StoreErrorNumber.ItemAlreadyDeleted);
            }

            if (existingChange.TypeID != typeID)
            {
                // Defensive code. Should never happen
                throw new StoreException(StoreErrorNumber.TypeIDMismatch);
            }

            existingChange.InitChange(key, changeType);

            return existingChange;
        }
Esempio n. 39
0
 void NotifyCommitFailed(RecordItemChange change)
 {
     Debug.WriteLine("Change Commit failed {0}", change);
     this.CommitFailed.SafeInvokeInUIThread(this, change);
 }
 async Task UpdateChangeAttemptCount(RecordItemChange change)
 {
     try
     {
         change.Attempt++;
         await m_changeTable.SaveChangeAsync(change);
     }
     catch(Exception ex)
     {
         this.NotifyError(ex);
     }
 }
Esempio n. 41
0
        public static RecordItemChange UpdateChange(string typeID, ItemKey key, RecordItemChangeType changeType, RecordItemChange existingChange)
        {
            if (existingChange == null)
            {
                return(new RecordItemChange(typeID, key, changeType));
            }

            if (existingChange.ChangeType == RecordItemChangeType.Remove && changeType == RecordItemChangeType.Put)
            {
                // Can't put an item already marked for deletion
                throw new StoreException(StoreErrorNumber.ItemAlreadyDeleted);
            }

            if (existingChange.TypeID != typeID)
            {
                // Defensive code. Should never happen
                throw new StoreException(StoreErrorNumber.TypeIDMismatch);
            }

            existingChange.InitChange(key, changeType);

            return(existingChange);
        }
        async Task<bool> DetectDuplicateAsync(RecordItemChange change)
        {            
            IList<RecordItem> results = await this.LookupCommitsInRemoteStoreAsync(change.ChangeID);
            if (results.IsNullOrEmpty())
            {
                return false;
            }

            change.UpdatedKey = results[0].Key;
            return true;
        }
 public static int Compare(RecordItemChange x, RecordItemChange y)
 {
     int cmp = x.Timestamp.CompareTo(y.Timestamp); 
     if (cmp == 0)
     {
         cmp = x.ItemID.CompareTo(y.ItemID);
     }
     return cmp;
 }
 async Task RefreshItemAsync(RecordItemChange change, long lockID)
 {
     change.UpdatedItem = null;
     try
     {
         change.UpdatedItem = await m_store.RemoteStore.GetItemAsync(change.UpdatedKey, m_store.SectionsToFetch);
     }
     catch(Exception ex)
     {
         this.NotifyError(ex);
     }
 }