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; }
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 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; }
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 { } }
internal async Task SaveChangeAsync(RecordItemChange change) { await m_changeStore.PutAsync(change.ItemID, change); }
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; }
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); } }
async Task NotifySynchronizedTypesAsync(RecordItemChange change) { try { if (this.SynchronizedTypes != null) { await this.SynchronizedTypes.OnChangeCommittedAsync(change); } } catch { } }
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); } }
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; }
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; } } }
async Task CommitNewAsync(RecordItemChange change) { Debug.Assert(change.HasLocalData); change.UpdatedKey = await m_store.RemoteStore.NewAsync(change.LocalData); }
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); }
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; }
void NotifyCommitSuccess(RecordItemChange change) { Debug.WriteLine("Change Committed {0}", change); this.CommitSuccess.SafeInvokeInUIThread(this, 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); }
// 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; }