diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj index 4f0feb6..1f3a556 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj @@ -274,7 +274,8 @@ - + + UserControl diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs index 38e1617..e16abf6 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs @@ -58,7 +58,7 @@ namespace Acacia.Features.SharedFolders ZPushAccount account = Watcher.Accounts.GetAccount(folder); if (account != null) { - new SharedFoldersDialog(account, folder.SyncId).ShowDialog(); + new SharedFoldersDialog(this, account, folder.SyncId).ShowDialog(); } } @@ -67,12 +67,21 @@ namespace Acacia.Features.SharedFolders ZPushAccount account = Watcher.CurrentZPushAccount(); if (account != null) { - new SharedFoldersDialog(account).ShowDialog(); + new SharedFoldersDialog(this, account).ShowDialog(); } } #endregion + #region Folder management + + internal SharedFoldersManager Manage(ZPushAccount account) + { + return new SharedFoldersManager(this, account); + } + + #endregion + #region Shared folders sync private const string KEY_SHARES = "Shares"; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs new file mode 100644 index 0000000..9b15b0f --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs @@ -0,0 +1,161 @@ +using Acacia.Native.MAPI; +using Acacia.Stubs; +using Acacia.Utils; +using Acacia.ZPush; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Features.SharedFolders +{ + public class RemindersQuery : DisposableWrapper, LogContext + { + private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F)); + + private readonly LogContext _context; + private readonly IFolder _folder; + private SearchQuery _queryRoot; + private SearchQuery.Or _queryCustom; + + public RemindersQuery(LogContext context, IStore store) + { + this._context = context; + _folder = store.GetSpecialFolder(SpecialFolder.Reminders); + } + + public bool Open() + { + if (_queryCustom != null) + return true; + try + { + _queryRoot = _folder.SearchCriteria; + if (!(_queryRoot is SearchQuery.And)) + return false; + Logger.Instance.Trace(this, "Current query1: {0}", _queryRoot.ToString()); + + SearchQuery.And root = (SearchQuery.And)_queryRoot; + // TODO: more strict checking of query + if (root.Operands.Count == 3) + { + this._queryCustom = root.Operands.ElementAt(2) as SearchQuery.Or; + if (this._queryCustom != null) + { + // TODO: check property test + return true; + } + } + + // We have the root, but not the custom query. Create it. + Logger.Instance.Debug(this, "Creating custom query"); + Logger.Instance.Trace(this, "Current query: {0}", root.ToString()); + _queryCustom = new SearchQuery.Or(); + + // Add the prefix exclusion for shared folders + _queryCustom.Add( + new SearchQuery.Not( + new SearchQuery.PropertyContent( + PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, "S" + ) + ) + ); + + root.Operands.Add(_queryCustom); + Logger.Instance.Trace(this, "Modified query: {0}", root.ToString()); + // Store it + // TODO: could store it on change only + _folder.SearchCriteria = root; + Logger.Instance.Trace(this, "Modified query2: {0}", _folder.SearchCriteria.ToString()); + } + catch (Exception e) + { + Logger.Instance.Error(this, "Exception in Open: {0}", e); + } + return _queryCustom != null; + } + + public string LogContextId + { + get + { + return _context.LogContextId; + } + } + + protected override void DoRelease() + { + _folder.Dispose(); + } + + public void Commit() + { + _folder.SearchCriteria = _queryRoot; + } + + public void UpdateReminders(SyncId folderId, bool wantReminders) + { + Logger.Instance.Trace(this, "Setting reminders for folder {0}: {1}", wantReminders, folderId); + string prefix = MakeFolderPrefix(folderId); + + // Find existing + for (int i = 0; i < _queryCustom.Operands.Count;) + { + SearchQuery.PropertyContent element = _queryCustom.Operands[i] as SearchQuery.PropertyContent; + if (element != null && prefix == (string)element.Content) + { + Logger.Instance.Trace(this, "Found at {0}: {1}", i, folderId); + // Found it. If we want reminders, we're done + if (wantReminders) + return; + + // Otherwise remove it. Still continue looking for others, just in case of duplicates + _queryCustom.Operands.RemoveAt(i); + } + else ++i; + } + + // Not found, add if wanted + if (wantReminders) + { + Logger.Instance.Trace(this, "Adding reminders for {0}", folderId); + _queryCustom.Operands.Add(new SearchQuery.PropertyContent( + PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, prefix + )); + } + } + + public void RemoveStaleReminders(IEnumerable wanted) + { + // Collect the valid prefixes + HashSet prefixes = new HashSet(); + foreach (SyncId id in wanted) + prefixes.Add(MakeFolderPrefix(id)); + + // Remove all operands for which we do not want the prefix + for (int i = 0; i < _queryCustom.Operands.Count;) + { + SearchQuery.PropertyContent element = _queryCustom.Operands[i] as SearchQuery.PropertyContent; + if (element != null) + { + string prefix = (string)element.Content; + if (prefixes.Contains(prefix)) + { + ++i; + continue; + } + + Logger.Instance.Trace(this, "Unwanted prefix at {0}: {1}", i, prefix); + _queryCustom.Operands.RemoveAt(i); + } + else ++i; + } + } + + private string MakeFolderPrefix(SyncId folderId) + { + return folderId.ToString() + ":"; + } + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs deleted file mode 100644 index 7db25c1..0000000 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedCalendarReminders.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Acacia.Native.MAPI; -using Acacia.Stubs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Acacia.Features.SharedFolders -{ - public class SharedCalendarReminders : LogContext - { - private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F)); - - private readonly LogContext _context; - public string LogContextId - { - get - { - return _context.LogContextId; - } - } - - public SharedCalendarReminders(LogContext context) - { - this._context = context; - } - - public void Initialise(IStore store) - { - using (IFolder reminders = store.GetSpecialFolder(SpecialFolder.Reminders)) - { - SearchQuery.Or custom = FindCustomQuery(reminders, true); - } - } - - private SearchQuery.Or FindCustomQuery(IFolder reminders, bool addIfNeeded) - { - SearchQuery query = reminders.SearchCriteria; - if (!(query is SearchQuery.And)) - return null; - Logger.Instance.Trace(this, "Current query1: {0}", query.ToString()); - - SearchQuery.And root = (SearchQuery.And)query; - // TODO: more strict checking of query - if (root.Operands.Count == 3) - { - SearchQuery.Or custom = root.Operands.ElementAt(2) as SearchQuery.Or; - if (custom != null) - { - // TODO: check property test - return custom; - } - } - - // We have the root, but not the custom query. Create it if needed. - if (addIfNeeded) - { - Logger.Instance.Debug(this, "Creating custom query"); - Logger.Instance.Trace(this, "Current query: {0}", root.ToString()); - SearchQuery.Or custom = new SearchQuery.Or(); - - // Add the prefix exclusion for shared folders - custom.Add( - new SearchQuery.Not( - new SearchQuery.PropertyContent( - PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, "S" - ) - ) - ); - - root.Operands.Add(custom); - Logger.Instance.Trace(this, "Modified query: {0}", root.ToString()); - reminders.SearchCriteria = root; - Logger.Instance.Trace(this, "Modified query2: {0}", reminders.SearchCriteria.ToString()); - } - return null; - } - } -} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.Designer.cs index 7645b31..f93a21e 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.Designer.cs @@ -47,6 +47,8 @@ this._labelPermissions = new System.Windows.Forms.Label(); this.labelPermissionsValue = new System.Windows.Forms.Label(); this.dialogButtons = new Acacia.Controls.KDialogButtons(); + this._labelReminders = new System.Windows.Forms.Label(); + this.checkReminders = new System.Windows.Forms.CheckBox(); this._layout.SuspendLayout(); this._mainBusyHider.SuspendLayout(); this._layoutMain.SuspendLayout(); @@ -137,8 +139,10 @@ this._layoutOptions.Controls.Add(this.textName, 1, 0); this._layoutOptions.Controls.Add(this._labelSendAs, 0, 1); this._layoutOptions.Controls.Add(this.checkSendAs, 1, 1); - this._layoutOptions.Controls.Add(this._labelPermissions, 0, 2); - this._layoutOptions.Controls.Add(this.labelPermissionsValue, 1, 2); + this._layoutOptions.Controls.Add(this._labelPermissions, 0, 3); + this._layoutOptions.Controls.Add(this.labelPermissionsValue, 1, 3); + this._layoutOptions.Controls.Add(this._labelReminders, 0, 2); + this._layoutOptions.Controls.Add(this.checkReminders, 1, 2); this._layoutOptions.Name = "_layoutOptions"; // // _labelName @@ -185,6 +189,18 @@ this.dialogButtons.Name = "dialogButtons"; this.dialogButtons.Apply += new System.EventHandler(this.dialogButtons_Apply); // + // _labelReminders + // + resources.ApplyResources(this._labelReminders, "_labelReminders"); + this._labelReminders.Name = "_labelReminders"; + // + // checkReminders + // + resources.ApplyResources(this.checkReminders, "checkReminders"); + this.checkReminders.Name = "checkReminders"; + this.checkReminders.UseVisualStyleBackColor = true; + this.checkReminders.CheckedChanged += new System.EventHandler(this.checkReminders_CheckedChanged); + // // SharedFoldersDialog // resources.ApplyResources(this, "$this"); @@ -228,5 +244,7 @@ private Controls.KDialogButtons dialogButtons; private System.Windows.Forms.TableLayoutPanel _layoutCenterGABLookup; private UI.GABLookupControl gabLookup; + private System.Windows.Forms.Label _labelReminders; + private System.Windows.Forms.CheckBox checkReminders; } } \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs index e0fc7d7..091fdff 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs @@ -16,10 +16,8 @@ using Acacia.Controls; using Acacia.Features.GAB; -using Acacia.Stubs; using Acacia.UI; using Acacia.UI.Outlook; -using Acacia.Utils; using Acacia.ZPush; using Acacia.ZPush.API.SharedFolders; using System; @@ -34,21 +32,20 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; -using System.Xml; -using System.Xml.Serialization; -using static Acacia.ZPush.API.SharedFolders.SharedFoldersAPI; namespace Acacia.Features.SharedFolders { public partial class SharedFoldersDialog : KDialogNew { private readonly ZPushAccount _account; - private SyncId _initialSyncId; + private readonly SharedFoldersManager _folders; + private readonly SyncId _initialSyncId; private SharedFolder _initialFolder; - public SharedFoldersDialog(ZPushAccount account, SyncId initial = null) + public SharedFoldersDialog(FeatureSharedFolders feature, ZPushAccount account, SyncId initial = null) { this._account = account; + this._folders = feature.Manage(account); this._initialSyncId = initial; InitializeComponent(); @@ -77,7 +74,7 @@ namespace Acacia.Features.SharedFolders ShowOptions(new KTreeNode[0]); // Set up user selector - gabLookup.GAB = FeatureGAB.FindGABForAccount(_account); + gabLookup.GAB = FeatureGAB.FindGABForAccount(account); } #region Load and store @@ -88,21 +85,18 @@ namespace Acacia.Features.SharedFolders KUITask .New((ctx) => { - using (SharedFoldersAPI api = new SharedFoldersAPI(_account)) - { - // TODO: bind cancellation token to Cancel button - // Fetch current shares - ICollection folders = api.GetCurrentShares(ctx.CancellationToken); + // TODO: bind cancellation token to Cancel button + // Fetch current shares + ICollection folders = _folders.GetCurrentShares(ctx.CancellationToken); - // Find the initial folder if required - if (_initialSyncId != null) - _initialFolder = folders.FirstOrDefault(f => f.SyncId == _initialSyncId); + // Find the initial folder if required + if (_initialSyncId != null) + _initialFolder = folders.FirstOrDefault(f => f.SyncId == _initialSyncId); - // Group by store and folder id - return folders.GroupBy(f => f.Store) - .ToDictionary(group => group.Key, - group => group.ToDictionary(folder => folder.BackendId)); - } + // Group by store and folder id + return folders.GroupBy(f => f.Store) + .ToDictionary(group => group.Key, + group => group.ToDictionary(folder => folder.BackendId)); }) .OnSuccess(InitialiseTree, true) .OnError((e) => @@ -163,27 +157,25 @@ namespace Acacia.Features.SharedFolders BusyText = Properties.Resources.SharedFolders_Applying_Label; KUITask.New((ctx) => { - using (SharedFoldersAPI folders = new SharedFoldersAPI(_account)) + // We reuse the same busy indicationg for all calls. A count is kept to ensure it's removed. + int count = 0; + + foreach (StoreTreeNode storeNode in _userFolders.Values) { - // We reuse the same busy indicationg for all calls. A count is kept to ensure it's removed. - int count = 0; - - foreach (StoreTreeNode storeNode in _userFolders.Values) + if (storeNode.IsDirty) { - if (storeNode.IsDirty) - { - ctx.AddBusy(1); - ++count; + ctx.AddBusy(1); + ++count; - folders.SetCurrentShares(storeNode.User, storeNode.CurrentShares, ctx.CancellationToken); - } + _folders.SetSharesForStore(storeNode.User, storeNode.CurrentShares, ctx.CancellationToken); } - - return count; } + + return count; }) .OnSuccess((ctx, count) => { + // Update UI state foreach (StoreTreeNode storeNode in _userFolders.Values) if (storeNode.IsDirty) storeNode.ChangesApplied(); @@ -255,7 +247,7 @@ namespace Acacia.Features.SharedFolders } // Add the node - node = new StoreTreeNode(_account, user, user.DisplayName, currentShares ?? new Dictionary()); + node = new StoreTreeNode(_folders, user, user.DisplayName, currentShares ?? new Dictionary()); node.DirtyChanged += UserSharesChanged; _userFolders.Add(user, node); kTreeFolders.RootNodes.Add(node); @@ -333,6 +325,25 @@ namespace Acacia.Features.SharedFolders private readonly List _optionSendAsNodes = new List(); private readonly List _optionSendAsInitial = new List(); + private CheckState? OptionReminders + { + get + { + if (checkReminders.Visible) + return checkReminders.CheckState; + return null; + } + + set + { + _labelReminders.Visible = checkReminders.Visible = value != null; + if (value != null) + checkReminders.CheckState = value.Value; + } + } + private readonly List _optionRemindersNodes = new List(); + private readonly List _optionRemindersInitial = new List(); + private Permission? _optionPermissions; private Permission? OptionPermissions { @@ -376,10 +387,13 @@ namespace Acacia.Features.SharedFolders _optionNameNode = null; _optionSendAsNodes.Clear(); _optionSendAsInitial.Clear(); + _optionRemindersNodes.Clear(); + _optionRemindersInitial.Clear(); _optionPermissionNodes.Clear(); OptionName = null; OptionTrackName = null; OptionSendAs = null; + OptionReminders = null; OptionPermissions = null; foreach (KTreeNode node in nodes) @@ -399,12 +413,18 @@ namespace Acacia.Features.SharedFolders // Assume we will edit the name for this node; cleared below if there are multiple _optionNameNode = folderNode; - // Show send as if there are any mail folders - if (folder.IsMailFolder) + if (folder.Type.IsMail()) { + // Show send as if there are any mail folders _optionSendAsNodes.Add(folderNode); _optionSendAsInitial.Add(folderNode.SharedFolder.FlagSendAsOwner); } + else if (folder.Type.IsAppointment()) + { + // Show reminders for appointment folders + _optionRemindersNodes.Add(folderNode); + _optionRemindersInitial.Add(folderNode.SharedFolder.FlagCalendarReminders); + } // Show permissions for all shared nodes _optionPermissionNodes.Add(folderNode); @@ -448,6 +468,21 @@ namespace Acacia.Features.SharedFolders checkSendAs.ThreeState = true; } } + // Reminders shown if any node supports it + if (_optionRemindersNodes.Count > 0) + { + bool reminders = _optionRemindersNodes.First().SharedFolder.FlagCalendarReminders; + if (_optionRemindersNodes.All(x => x.SharedFolder.FlagCalendarReminders == reminders)) + { + OptionReminders = reminders ? CheckState.Checked : CheckState.Unchecked; + checkReminders.ThreeState = false; + } + else + { + OptionReminders = CheckState.Indeterminate; + checkReminders.ThreeState = true; + } + } } finally { @@ -477,7 +512,7 @@ namespace Acacia.Features.SharedFolders // If the share name matches the folder name, track update bool track = _optionNameNode.SharedFolder.Name == _optionNameNode.AvailableFolder.DefaultName; - _optionNameNode.SharedFolder = _optionNameNode.SharedFolder.WithFlagUpdateShareName(track); + _optionNameNode.SharedFolder = _optionNameNode.SharedFolder.WithFlagTrackShareName(track); } } @@ -507,6 +542,26 @@ namespace Acacia.Features.SharedFolders } } + private void checkReminders_CheckedChanged(object sender, EventArgs e) + { + for (int i = 0; i < _optionRemindersNodes.Count; ++i) + { + FolderTreeNode node = _optionRemindersNodes[i]; + bool reminders = false; + switch (checkReminders.CheckState) + { + case CheckState.Checked: reminders = true; break; + case CheckState.Indeterminate: reminders = _optionRemindersInitial[i]; break; + case CheckState.Unchecked: reminders = false; break; + } + + if (node.SharedFolder.FlagCalendarReminders != reminders) + { + node.SharedFolder = node.SharedFolder.WithFlagCalendarReminders(reminders); + } + } + } + #endregion } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.resx index ffdef70..256dd52 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.resx @@ -145,13 +145,10 @@ - 4, 0 - - - 4, 0, 4, 0 + 3, 0 - 143, 36 + 105, 31 0 @@ -190,16 +187,13 @@ NoControl - 510, 4 - - - 4, 4, 4, 4 + 380, 3 - 11, 0, 11, 0 + 8, 0, 8, 0 - 75, 28 + 59, 25 1 @@ -232,16 +226,13 @@ Popup - 4, 4 - - - 4, 4, 4, 4 + 3, 3 - 265, 0 + 200, 0 - 343, 24 + 256, 21 1 @@ -262,7 +253,7 @@ Fill - 153, 2 + 113, 2 2, 2, 2, 2 @@ -271,7 +262,7 @@ 3 - 351, 32 + 262, 27 2 @@ -295,16 +286,13 @@ Fill - 4, 4 - - - 4, 4, 4, 4 + 3, 3 1 - 589, 36 + 442, 31 0 @@ -322,19 +310,16 @@ 0 - <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="labelSelectUser" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="buttonOpenUser" Row="0" RowSpan="1" Column="2" ColumnSpan="1" /><Control Name="_layoutCenterGABLookup" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /></Controls><Columns Styles="AutoSize,0,Percent,100,AutoSize,0" /><Rows Styles="Percent,100,Absolute,37" /></TableLayoutSettings> + <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="labelSelectUser" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="buttonOpenUser" Row="0" RowSpan="1" Column="2" ColumnSpan="1" /><Control Name="_layoutCenterGABLookup" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /></Controls><Columns Styles="AutoSize,0,Percent,100,AutoSize,0" /><Rows Styles="Percent,100,Absolute,27" /></TableLayoutSettings> Fill - 4, 48 - - - 4, 4, 4, 4 + 3, 40 - 589, 377 + 442, 275 1 @@ -367,13 +352,10 @@ Fill - 4, 0 - - - 4, 0, 4, 0 + 3, 0 - 102, 30 + 82, 26 0 @@ -400,13 +382,13 @@ Fill - 118, 4 + 94, 3 - 8, 4, 4, 4 + 6, 3, 3, 3 - 475, 22 + 351, 20 1 @@ -430,13 +412,10 @@ Fill - 4, 30 - - - 4, 0, 4, 0 + 3, 26 - 102, 34 + 82, 27 2 @@ -466,16 +445,16 @@ Left - 118, 35 + 94, 30 - 8, 5, 4, 4 + 6, 4, 3, 3 - 0, 4, 0, 4 + 0, 3, 0, 3 - 18, 25 + 15, 20 3 @@ -499,16 +478,13 @@ Fill - 4, 64 - - - 4, 0, 4, 0 + 3, 80 - 0, 5, 0, 4 + 0, 4, 0, 3 - 102, 26 + 82, 20 4 @@ -541,16 +517,13 @@ MiddleLeft - 114, 64 - - - 4, 0, 4, 0 + 91, 80 - 0, 5, 0, 4 + 0, 4, 0, 3 - 479, 26 + 354, 20 5 @@ -573,20 +546,86 @@ 5 + + True + + + Fill + + + 3, 53 + + + 82, 27 + + + 6 + + + Show reminders + + + MiddleLeft + + + _labelReminders + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + _layoutOptions + + + 6 + + + True + + + Left + + + 94, 57 + + + 6, 4, 3, 3 + + + 0, 3, 0, 3 + + + 15, 20 + + + 7 + + + checkReminders + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + _layoutOptions + + + 7 + Fill - 0, 429 + 0, 318 0, 0, 0, 0 - 3 + 4 - 597, 90 + 448, 100 2 @@ -604,7 +643,7 @@ 2 - <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_labelName" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="textName" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /><Control Name="_labelSendAs" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="checkSendAs" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /><Control Name="_labelPermissions" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="labelPermissionsValue" Row="2" RowSpan="1" Column="1" ColumnSpan="1" /></Controls><Columns Styles="AutoSize,0,Percent,100" /><Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0" /></TableLayoutSettings> + <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_labelName" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="textName" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /><Control Name="_labelSendAs" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="checkSendAs" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /><Control Name="_labelPermissions" Row="3" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="labelPermissionsValue" Row="3" RowSpan="1" Column="1" ColumnSpan="1" /><Control Name="_labelReminders" Row="2" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="checkReminders" Row="2" RowSpan="1" Column="1" ColumnSpan="1" /></Controls><Columns Styles="AutoSize,0,Percent,100" /><Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0,AutoSize,0" /></TableLayoutSettings> Fill @@ -612,14 +651,11 @@ 0, 0 - - 4, 4, 4, 4 - 3 - 597, 519 + 448, 418 3 @@ -643,13 +679,10 @@ Fill - 4, 4 - - - 4, 4, 4, 4 + 3, 3 - 597, 519 + 448, 418 4 @@ -679,13 +712,13 @@ Fill - 2, 528 + 2, 425 2, 1, 2, 1 - 601, 39 + 450, 35 5 @@ -706,7 +739,7 @@ Fill - 8, 7 + 6, 6 0, 0, 0, 0 @@ -715,7 +748,7 @@ 2 - 605, 568 + 454, 461 0 @@ -733,19 +766,22 @@ 0 - <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_mainBusyHider" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="dialogButtons" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /></Controls><Columns Styles="Percent,100" /><Rows Styles="Percent,100,AutoSize,0,Absolute,25" /></TableLayoutSettings> + <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_mainBusyHider" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="dialogButtons" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /></Controls><Columns Styles="Percent,100" /><Rows Styles="Percent,100,AutoSize,0,Absolute,20" /></TableLayoutSettings> True - 8, 16 + 6, 13 - 621, 582 + 466, 473 + + + 2, 2, 2, 2 - 8, 7, 8, 7 + 6, 6, 6, 6 CenterParent diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersManager.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersManager.cs new file mode 100644 index 0000000..58cfa60 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersManager.cs @@ -0,0 +1,122 @@ +using Acacia.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Acacia.ZPush; +using Acacia.ZPush.API.SharedFolders; +using System.Threading; +using Acacia.Native.MAPI; + +namespace Acacia.Features.SharedFolders +{ + /// + /// Manages changes to shared folders. + /// + public class SharedFoldersManager : DisposableWrapper + { + /// + /// Contains 'folderid:itemid'. The folder id is used to detect shared folders. + /// TODO: put this in a shared lib somewhere + /// + private static readonly SearchQuery.PropertyIdentifier PROP_AS_ITEMID = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F)); + + private readonly ZPushAccount _account; + private readonly FeatureSharedFolders _feature; + private readonly SharedFoldersAPI _api; + private RemindersQuery _query; + + public SharedFoldersManager(FeatureSharedFolders featureSharedFolders, ZPushAccount account) + { + this._feature = featureSharedFolders; + this._account = account; + _api = new SharedFoldersAPI(account); + } + + protected override void DoRelease() + { + _api.Dispose(); + if (_query != null) + _query.Dispose(); + } + + #region API + + /// + /// Sets all shares for the specified store. + /// + public void SetSharesForStore(GABUser store, ICollection shares, CancellationToken? cancel) + { + // Make sure reminders are updated as soon as possible + UpdateReminders(shares); + _api.SetCurrentShares(store, shares, cancel); + + // Commit changes + if (_query != null) + _query.Commit(); + } + + public ICollection GetCurrentShares(CancellationToken? cancel) + { + // Fetch the shares + ICollection shares = _api.GetCurrentShares(cancel); + + // Make sure reminders are disabled as soon as possible + UpdateReminders(shares); + + // Remove any reminders from the shares that are not wanted, they are stale + OpenQuery()?.RemoveStaleReminders( + shares + .Where(x => x.IsSynced && x.SyncType.IsAppointment() && x.FlagCalendarReminders) + .Select(x => x.SyncId) + ); + + // Commit changes + if (_query != null) + _query.Commit(); + + return shares; + } + + public ICollection GetStoreFolders(GABUser store) + { + return _api.GetUserFolders(store); + } + + #endregion + + #region Reminders + + private void UpdateReminders(ICollection shares) + { + foreach(SharedFolder share in shares) + { + Logger.Instance.Debug(this, "UpdateReminders: {0}", share); + if (share.IsSynced && share.SyncType.IsAppointment()) + { + OpenQuery()?.UpdateReminders(share.SyncId, share.FlagCalendarReminders); + } + } + } + + private RemindersQuery OpenQuery() + { + if (_query == null) + { + RemindersQuery query = new RemindersQuery(_feature, _account.Account.Store); + if (query.Open()) + { + _query = query; + } + else + { + query.Dispose(); + } + } + return _query; + } + + #endregion + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/StoreTreeNode.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/StoreTreeNode.cs index 088e5e0..9a1610d 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/StoreTreeNode.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/StoreTreeNode.cs @@ -41,7 +41,7 @@ namespace Acacia.Features.SharedFolders private readonly Dictionary _initialShares; private readonly Dictionary _currentShares; - public StoreTreeNode(ZPushAccount account, GABUser user, string text, Dictionary currentFolders) + public StoreTreeNode(SharedFoldersManager folders, GABUser user, string text, Dictionary currentFolders) : base(text) { @@ -51,7 +51,7 @@ namespace Acacia.Features.SharedFolders // cleaning up automatically any obsolote shares. this._currentShares = new Dictionary(); - ChildLoader = new UserFolderLoader(this, account, user); + ChildLoader = new UserFolderLoader(this, folders, user); ChildLoader.ReloadOnCloseOpen = true; HasCheckBox = false; @@ -95,7 +95,7 @@ namespace Acacia.Features.SharedFolders SharedFolder share = new SharedFolder(folder); // Default send as for mail folders - if (folder.IsMailFolder) + if (folder.Type.IsMail()) share = share.WithFlagSendAsOwner(true); return share; @@ -167,21 +167,18 @@ namespace Acacia.Features.SharedFolders public class UserFolderLoader : KTreeNodeLoader { - private readonly ZPushAccount _account; + private readonly SharedFoldersManager _folders; public GABUser User { get; private set; } - public UserFolderLoader(StoreTreeNode parent, ZPushAccount account, GABUser user) : base(parent) + public UserFolderLoader(StoreTreeNode parent, SharedFoldersManager folders, GABUser user) : base(parent) { - this._account = account; + this._folders = folders; this.User = user; } protected override object DoLoadChildren(KTreeNode node) { - using (SharedFoldersAPI folders = new SharedFoldersAPI(_account)) - { - return folders.GetUserFolders(User); - } + return _folders.GetStoreFolders(User); } private class FolderComparer : IComparer diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs index 59ba465..c7f9000 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs @@ -199,7 +199,7 @@ namespace Acacia SyncType.RecipientCache, // RecipientCache = 19 }; - public static bool IsMailType(SyncType type) + public static bool IsMail(this SyncType type) { return USER_SYNC_TYPES[(int)type] == SyncType.UserMail; } @@ -245,6 +245,11 @@ namespace Acacia #endregion + public static bool IsAppointment(this SyncType type) + { + return USER_SYNC_TYPES[(int)type] == SyncType.UserAppointment; + } + #region Message classes public const string PR_MESSAGE_CLASS = PROP + "001A" + PT_UNICODE; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs index 45e9f5d..c983843 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs @@ -170,7 +170,7 @@ namespace Acacia _operands.Add(operand); } - public ICollection Operands + public IList Operands { get { return _operands; } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs index c3411a6..82e18ee 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/AvailableFolder.cs @@ -94,8 +94,6 @@ namespace Acacia.ZPush.API.SharedFolders public GABUser Store { get; private set; } - public bool IsMailFolder { get { return OutlookConstants.IsMailType(Type); } } - #endregion #region Tree structure diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/SharedFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/SharedFolder.cs index 9679e64..248424a 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/SharedFolder.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/SharedFolder.cs @@ -117,7 +117,7 @@ namespace Acacia.ZPush.API.SharedFolders parentid = folder.ParentIdAsBackend, name = folder.DefaultName, type = OutlookConstants.USER_SYNC_TYPES[(int)folder.Type], - flags = folder.IsMailFolder ? ShareFlags.SendAsOwner : ShareFlags.None + flags = folder.Type.IsMail() ? ShareFlags.SendAsOwner : ShareFlags.None }; } @@ -129,6 +129,7 @@ namespace Acacia.ZPush.API.SharedFolders public BackendId BackendId { get { return _data.folderid; } } public SyncId SyncId { get { return _data.syncfolderid; } } public bool IsSynced { get { return SyncId != null; } } + public OutlookConstants.SyncType SyncType { get { return _data.type; } } public Permission? Permissions { @@ -180,6 +181,7 @@ namespace Acacia.ZPush.API.SharedFolders public bool FlagSendAsOwner { get { return Flags.HasFlag(ShareFlags.SendAsOwner); } } public bool FlagUpdateShareName { get { return Flags.HasFlag(ShareFlags.TrackShareName); } } + public bool FlagCalendarReminders { get { return Flags.HasFlag(ShareFlags.CalendarReminders); } } /// /// Returns a copy with the specified 'send as owner' flag. @@ -192,11 +194,19 @@ namespace Acacia.ZPush.API.SharedFolders /// /// Returns a copy with the specified 'update share name' flag. /// - public SharedFolder WithFlagUpdateShareName(bool value) + public SharedFolder WithFlagTrackShareName(bool value) { return WithFlags(value ? (_data.flags | ShareFlags.TrackShareName) : (_data.flags & ~ShareFlags.TrackShareName)); } + /// + /// Returns a copy with the specified 'calendar reminders' flag. + /// + public SharedFolder WithFlagCalendarReminders(bool value) + { + return WithFlags(value ? (_data.flags | ShareFlags.CalendarReminders) : (_data.flags & ~ShareFlags.CalendarReminders)); + } + #endregion #region Standard overrides diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/Types.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/Types.cs index b1b7ee0..bac3ba4 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/Types.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/API/SharedFolders/Types.cs @@ -40,11 +40,16 @@ namespace Acacia.ZPush.API.SharedFolders /// TrackShareName = 2, + /// + /// Applicable to calendars only. Set to enable reminders on the shared calendar. + /// + CalendarReminders = 4, + /// /// The mask indicating which flag changes cause an Apply to become needed. I.e. flags not in the mask /// are updated only if other changes are made. /// - Mask_Apply = 1 + Mask_Apply = 0xFFFF & ~(TrackShareName) } ///