diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs index 9d3ca76..e889d57 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs @@ -77,6 +77,29 @@ namespace Acacia.Features.SyncState } + [AcaciaOption("Enables or disables checking for stalled synchronsation. If this is detected, a full resynchronisation " + + "is suggested automatically")] + public bool CheckSyncStall + { + get { return GetOption(OPTION_CHECK_SYNC_STALL); } + set { SetOption(OPTION_CHECK_SYNC_STALL, value); } + } + private static readonly BoolOption OPTION_CHECK_SYNC_STALL = new BoolOption("CheckSyncStall", false); + + [AcaciaOption("Sets the period that triggers a full resynchronisation suggestion. Note that the check is only performed " + + "when the synchronisation state is being updated, so the effective period may be longer than this.")] + public TimeSpan CheckSyncStallPeriod + { + get { return GetOption(OPTION_CHECK_SYNC_STALL_PERIOD); } + set { SetOption(OPTION_CHECK_SYNC_STALL_PERIOD, value); } + } + private static readonly TimeSpanOption OPTION_CHECK_SYNC_STALL_PERIOD = new TimeSpanOption("CheckSyncStallPeriod", new TimeSpan(0, 10, 0)); + /// + /// The time at which a sync stall was first suspected, or null if this is not the case. + /// + private DateTime? _syncStallStarted; + private bool _syncStallAsked; + #endregion // TODO: this is largely about progress bars, separate that? @@ -223,9 +246,13 @@ namespace Acacia.Features.SyncState [SoapField(5)] public string id; + /// + /// The key, which is a short folder id. Set when checking the values. + /// public string Key { - get { return id; } + get; + set; } } @@ -320,8 +347,11 @@ namespace Acacia.Features.SyncState HashSet syncingNow = new HashSet(); // Check all syncing data - foreach (DeviceDetails.ContentData content in details.Content.Values) + foreach (KeyValuePair contentEntry in details.Content) { + DeviceDetails.ContentData content = contentEntry.Value; + content.Key = contentEntry.Key; + if (content.IsSyncing) { // If the current session is not syncing, this is a restart. Clear stat @@ -370,6 +400,15 @@ namespace Acacia.Features.SyncState debug.AppendLine(string.Format("Total: {0} / {1} ({2}%)", Done, Total, CalculatePercentage(Done, Total))); Logger.Instance.Trace(_feature, "Syncing account {0}:\n{1}", _account, debug); } + + public bool HasFolderSynced(string folderId) + { + DeviceDetails.ContentData content; + if (!_syncContent.TryGetValue(folderId, out content)) + return false; + + return !string.IsNullOrWhiteSpace(content.synckey); + } } private void CheckSyncState(ZPushAccount account) @@ -406,6 +445,9 @@ namespace Acacia.Features.SyncState // Update the total for all accounts UpdateTotalSyncState(); + + // Check for stalls + CheckSyncStalled(account); } private void UpdateTotalSyncState() @@ -442,6 +484,60 @@ namespace Acacia.Features.SyncState } } + private void CheckSyncStalled(ZPushAccount account) + { + if (!CheckSyncStall) + return; + + // Check the inbox folder + using (IFolder inbox = account.Account.Store.GetDefaultFolder(DefaultFolder.Inbox)) + { + string syncId = (string)inbox.GetProperty(OutlookConstants.PR_ZPUSH_SYNC_ID); + + // If it's syncing, it's not stalled + if (syncId != null && syncId != "0") + return; + + // Check if the folder has synced. In that case, it's not stalled. + string folderId = (string)inbox.GetProperty(OutlookConstants.PR_ZPUSH_FOLDER_ID); + SyncSession sync = account.GetFeatureData(this, null); + if (sync.HasFolderSynced(folderId)) + return; + } + + // It is not syncing, check for a stall + if (_syncStallStarted == null) + _syncStallStarted = DateTime.Now; + else if (_syncStallStarted.Value.Add(CheckSyncStallPeriod) <= DateTime.Now) + { + // We have a stall + if (!_syncStallAsked) + { + // Set the flag to prevent asking again + _syncStallAsked = true; + + // And alert the user + SyncStalled(account); + } + } + } + + private void SyncStalled(ZPushAccount account) + { + ThisAddIn.Instance.InUI(() => + { + if (MessageBox.Show(ThisAddIn.Instance.Window, + string.Format(Properties.Resources.SyncState_Stalled_Body, account.DisplayName), + string.Format(Properties.Resources.SyncState_Stalled_Caption, account.DisplayName), + MessageBoxButtons.YesNo, + MessageBoxIcon.Error + ) == DialogResult.Yes) + { + ThisAddIn.Instance.RestartResync(account); + } + }); + } + private static int CalculatePercentage(long done, long total) { if (total == 0 || done == total) diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs index b682be8..4e6cc5e 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs @@ -132,8 +132,9 @@ namespace Acacia #region EAS / ZPush - public const string PR_ZPUSH_MESSAGE_ID = PROP + "6B20" + PT_STRING8; + public const string PR_ZPUSH_SYNC_ID = PROP + "6A18" + PT_STRING8; public const string PR_ZPUSH_FOLDER_ID = PROP + "6A19" + PT_STRING8; + public const string PR_ZPUSH_MESSAGE_ID = PROP + "6B20" + PT_STRING8; // TODO: names for these, use MFCMAPI public const string PR_EAS_SYNC1 = PROP + "6A17" + PT_BOOLEAN; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs index e8a4977..dd440d8 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs @@ -1201,6 +1201,24 @@ namespace Acacia.Properties { } } + /// + /// Looks up a localized string similar to It appears synchronisation has stalled for account {0}. This can be fixed by performing a full resynchronisation. Would you like to perform this resynchronisation now?. + /// + internal static string SyncState_Stalled_Body { + get { + return ResourceManager.GetString("SyncState_Stalled_Body", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Synchronisation stalled - {0}. + /// + internal static string SyncState_Stalled_Caption { + get { + return ResourceManager.GetString("SyncState_Stalled_Caption", resourceCulture); + } + } + /// /// Looks up a localized string similar to Kopano. /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx index 4d3f06d..b519732 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx @@ -500,4 +500,12 @@ Global Address Book + + It appears synchronisation has stalled for account {0}. This can be fixed by performing a full resynchronisation. Would you like to perform this resynchronisation now? + {0} will be replaced with the account name + + + Synchronisation stalled - {0} + {0} will be replaced with the account name + \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs index 87d7110..473da0c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs @@ -71,7 +71,7 @@ namespace Acacia.Stubs /// Restarts the application and removes the specified account files. /// /// - void RestartResync(ZPushAccount[] accounts); + void RestartResync(params ZPushAccount[] accounts); void Quit(bool closeWindows);