diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs index 7a527c2..e922b2c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs @@ -361,7 +361,7 @@ namespace Acacia.Features.GAB { try { - if (IsGABContactsFolder(folder)) + if (IsGABContactsFolder(folder, accounts)) { Logger.Instance.Debug(this, "FullResync: Deleting contacts folder: {0}", folder.Name); folder.Delete(); @@ -378,16 +378,27 @@ namespace Acacia.Features.GAB } // Do the resync - int remaining = _gabsByDomainName.Count; - foreach (GABHandler gab in _gabsByDomainName.Values) + using (completion.Begin()) { - CompletionTracker partCompletion = new CompletionTracker(() => OnGabSyncFinished(gab)); - // TODO: merge partCompletion into total completion - Logger.Instance.Debug(this, "FullResync: Starting resync: {0}", gab.DisplayName); - Tasks.Task(partCompletion, this, "FullResync", () => + foreach (GABHandler gab in _gabsByDomainName.Values) { - gab.FullResync(partCompletion); - }); + // Check if the gab is appropriate for the accounts + if (accounts == null || accounts.Contains(gab.ActiveAccount)) + { + completion.Begin(); + CompletionTracker partCompletion = new CompletionTracker(() => + { + OnGabSyncFinished(gab); + completion.End(); + }); + + Logger.Instance.Debug(this, "FullResync: Starting resync: {0}", gab.DisplayName); + Tasks.Task(partCompletion, this, "FullResync", () => + { + gab.FullResync(partCompletion); + }); + } + } } } finally @@ -499,9 +510,30 @@ namespace Acacia.Features.GAB return GABInfo.Get(folder); } - public static bool IsGABContactsFolder(IFolder folder) + /// + /// Checks if the folder is a relevant GAB contacts folder. + /// + /// The folder. + /// If specified, the folder is considered relevant only if it is the GAB for one of the specified + /// accounts. Otherwise, any GAB folder is considered relevant. + public static bool IsGABContactsFolder(IFolder folder, ZPushAccount[] accounts) { - return GetGABContactsFolderInfo(folder) != null; + // Check if this is a GAB folder at all + GABInfo gab = GetGABContactsFolderInfo(folder); + if (gab == null) + return false; + + // If we don't have a list of accounts, it is what we're looking for + if (accounts == null) + return true; + + // Check if the domain is specified + foreach(ZPushAccount account in accounts) + { + if (account.Account.DomainName == gab.Domain) + return true; + } + return false; } private void AccountDiscovered(ZPushAccount zpush) diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs index b1c48de..48142a6 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/FeatureSyncState.cs @@ -41,6 +41,39 @@ namespace Acacia.Features.SyncState public class FeatureSyncState : FeatureDisabled, FeatureWithRibbon { + + #region Sync configuration + + [AcaciaOption("Sets the period to check synchronisation state if a sync is in progress.")] + public TimeSpan CheckPeriod + { + get { return GetOption(OPTION_CHECK_PERIOD); } + set { SetOption(OPTION_CHECK_PERIOD, value); } + } + private static readonly TimeSpanOption OPTION_CHECK_PERIOD = new TimeSpanOption("CheckPeriod", new TimeSpan(0, 5, 0)); + + [AcaciaOption("Sets the period to check synchronisation state if a sync is in progress and the dialog is open.")] + public TimeSpan CheckPeriodDialog + { + get { return GetOption(OPTION_CHECK_PERIOD_DIALOG); } + set { SetOption(OPTION_CHECK_PERIOD_DIALOG, value); } + } + private static readonly TimeSpanOption OPTION_CHECK_PERIOD_DIALOG = new TimeSpanOption("CheckPeriodDialog", new TimeSpan(0, 1, 0)); + + private TimeSpan DelayTime + { + get + { + if (_dialogOpen) + return CheckPeriodDialog; + else + return CheckPeriod; + } + } + private bool _dialogOpen; + + #endregion + // TODO: this is largely about progress bars, separate that? private class SyncStateData : DataProvider { @@ -152,7 +185,7 @@ namespace Acacia.Features.SyncState public override void Startup() { _state = new SyncStateData(this); - _button = RegisterButton(this, "Progress", true, ShowSyncState, ZPushBehaviour.Disable); + _button = RegisterButton(this, "Progress", true, ShowSyncState, ZPushBehaviour.None); _button.DataProvider = _state; // Add a sync task to start checking. If this finds it's not fully synchronised, it will check more often Watcher.Sync.AddTask(this, Name, CheckSyncState); @@ -306,10 +339,14 @@ namespace Acacia.Features.SyncState DeviceDetails details = deviceService.Execute(new GetDeviceDetailsRequest()); // Determine the totals - details.Calculate(); + details?.Calculate(); // And store with the account account.SetFeatureData(this, null, details); + + // If syncing, check again soon. + if (details?.IsSyncing == true) + Util.Delayed(this, (int)DelayTime.TotalMilliseconds, () => CheckSyncState(account)); } // Update the total for all accounts @@ -340,7 +377,15 @@ namespace Acacia.Features.SyncState private void ShowSyncState() { - new SyncStateDialog(this).ShowDialog(); + _dialogOpen = true; + try + { + new SyncStateDialog(this).ShowDialog(); + } + finally + { + _dialogOpen = false; + } } private class SyncStateImpl : SyncState @@ -362,7 +407,6 @@ namespace Acacia.Features.SyncState _canResync[(int)ResyncOption.Signatures] = _featureSignatures != null; _canResync[(int)ResyncOption.ServerData] = ThisAddIn.Instance.Watcher.Sync.Enabled; _canResync[(int)ResyncOption.Full] = true; - Update(); } public long Done @@ -399,14 +443,29 @@ namespace Acacia.Features.SyncState { if (!CanResync(option)) return true; - _canResync [(int)option] = false; switch(option) { case ResyncOption.GAB: - // TODO: use completion tracker if not synching - _featureGAB.FullResync(null, _accounts); - return false; + if (IsSyncing) + { + // Already syncing, resync GAB asynchronously + _featureGAB.FullResync(null, _accounts); + // Cannot resync again until the dialog is reopened + _canResync[(int)ResyncOption.GAB] = false; + return false; + } + else + { + ProgressDialog.Execute("GABSync", + (ct, tracker) => + { + _featureGAB.FullResync(tracker, _accounts); + return 0; + } + ); + return true; + } case ResyncOption.Signatures: ProgressDialog.Execute("SignaturesSync", (ct) => diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/SyncStateDialog.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/SyncStateDialog.cs index 608621a..573c5fa 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/SyncStateDialog.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SyncState/SyncStateDialog.cs @@ -50,6 +50,15 @@ namespace Acacia.Features.SyncState // Add the accounts foreach (ZPushAccount account in ThisAddIn.Instance.Watcher.Accounts.GetAccounts()) comboAccounts.Items.Add(account); + + // Add a timer to update the UI + Timer timer = new Timer(); + timer.Interval = 2500; + timer.Tick += (o, args) => + { + UpdateUI(); + }; + timer.Start(); } private void ShowHint(object sender, KHintButton.HintEventArgs e) @@ -119,6 +128,8 @@ namespace Acacia.Features.SyncState private void UpdateUI() { + _syncState.Update(); + // Set up the UI foreach (ResyncOption option in Enum.GetValues(typeof(ResyncOption))) { @@ -127,7 +138,7 @@ namespace Acacia.Features.SyncState if (_syncState.IsSyncing) { - textRemaining.Text = _syncState.Remaining.ToString(); + textRemaining.Text = _syncState.Remaining.ToString() + " / " + _syncState.Total.ToString(); progress.Value = (int)(_syncState.Done * 100.0 / _syncState.Total); } else diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs index 2ee2e88..e8a4977 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs @@ -168,6 +168,24 @@ namespace Acacia.Properties { } } + /// + /// Looks up a localized string similar to The global address book is being synchronised.. + /// + internal static string GABSync_Label { + get { + return ResourceManager.GetString("GABSync_Label", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Global Address Book. + /// + internal static string GABSync_Title { + get { + return ResourceManager.GetString("GABSync_Title", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx index 8c1e697..4d3f06d 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx @@ -494,4 +494,10 @@ Server data + + The global address book is being synchronised. + + + Global Address Book + \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/ProgressDialog.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/ProgressDialog.cs index e65be5f..fbc06b5 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/ProgressDialog.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/UI/ProgressDialog.cs @@ -89,6 +89,61 @@ namespace Acacia.UI return task.Result; } + public static ResultType Execute(string resourcePrefix, Func action) + { + // TODO: merge with above + + Logger.Instance.Info(typeof(ProgressDialog), "Opening"); + // Determine the UI context, creating a new one if required + if (SynchronizationContext.Current == null) + SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); + var context = TaskScheduler.FromCurrentSynchronizationContext(); + + // Create the dialog, so it is available for the task + ProgressDialog dlg = new ProgressDialog(); + // Set the strings + dlg.Text = StringUtil.GetResourceString(resourcePrefix + "_Title"); + dlg.labelMessage.Text = StringUtil.GetResourceString(resourcePrefix + "_Label"); + + // Start the task + Exception caught = null; + Task task = null; + // And close the dialog when done + CompletionTracker tracker = new CompletionTracker(() => + { + // This extra step is needed to go back into the thread context + task.ContinueWith(_ => { dlg._isComplete = true; dlg.DialogResult = DialogResult.OK; }, context); + }); + + task = Task.Factory.StartNew( + () => + { + try + { + return action(dlg.cancel.Token, tracker); + } + catch (Exception e) + { + caught = e; + return default(ResultType); + } + }, + dlg.cancel.Token); + dlg.task = task; + + // Show the dialog + if (dlg.ShowDialog() != DialogResult.OK) + return default(ResultType); + + // Rethrow any exception. + // The framework already handles this, but that causes breaks into the debugger + if (caught != null) + throw caught; + + // Result the result + return task.Result; + } + private void ProgressDialog_FormClosing(object sender, FormClosingEventArgs e) { if (!_isComplete) diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs index 268ca1f..7465625 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs @@ -125,7 +125,7 @@ namespace Acacia.ZPush private static bool IsCustomFolder(IFolder folder) { - return Features.GAB.FeatureGAB.IsGABContactsFolder(folder); + return Features.GAB.FeatureGAB.IsGABContactsFolder(folder, null); } private static void HideAllFolders(IStore store)