diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs index 1281880..4970a03 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs @@ -71,6 +71,8 @@ namespace Acacia.Features.SharedFolders // Private shared appointment SetupPrivateAppointmentSuppression(); + + SetupHierarchyChangeSuppression(); } #region UI @@ -288,5 +290,119 @@ namespace Acacia.Features.SharedFolders } #endregion + + #region Hierarchy changes + + private class SharedFolderRegistration : FolderRegistration + { + public SharedFolderRegistration(Feature feature) : base(feature) + { + } + + public override bool IsApplicable(IFolder folder) + { + if (folder.SyncId != null && folder.SyncId.IsShared) + return true; + + using (IFolder parent = folder.Parent) + { + if (parent != null) + return IsApplicable(parent); + } + + return false; + } + } + + private void SetupHierarchyChangeSuppression() + { + Watcher.WatchFolder(new SharedFolderRegistration(this), + OnSharedFolderDiscovered, + OnSharedFolderChanged, + OnSharedFolderRemoved); + } + + private void OnSharedFolderDiscovered(IFolder folder) + { + Logger.Instance.Trace(this, "Shared folder discovered: {0} - {1}", folder.Name, folder.SyncId); + if (folder.SyncId == null || !folder.SyncId.IsShared) + { + Logger.Instance.Warning(this, "Local folder created in shared folder, deleting: {0} - {1}", folder.Name, folder.SyncId); + // This is a new, locally created folder. Warn and remove + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.SharedFolders_LocalFolder_Body, + Properties.Resources.SharedFolders_LocalFolder_Title, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + folder.Delete(); + Logger.Instance.Warning(this, "Local folder created in shared folder, deleted: {0} - {1}", folder.Name, folder.SyncId); + } + else + { + folder.BeforeFolderMove += Folder_BeforeFolderMove; + + // Check if it was renamed before the events were fully set up + CheckSharedFolderRename(folder); + } + } + + private void Folder_BeforeFolderMove(IFolder src, IFolder moveTo, ref bool cancel) + { + Logger.Instance.Fatal(this, "SHARED FOLDER MOVE: {0}", moveTo.Name); + + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.SharedFolders_LocalFolder_Body, + Properties.Resources.SharedFolders_LocalFolder_Title, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + cancel = true; + } + + private void OnSharedFolderChanged(IFolder folder) + { + Logger.Instance.Trace(this, "Shared folder changed: {0} - {1}", folder.Name, folder.SyncId); + CheckSharedFolderRename(folder); + } + + private void CheckSharedFolderRename(IFolder folder) + { + if (folder.SyncId != null && folder.SyncId.IsShared) + { + string originalName = (string)folder.GetProperty(OutlookConstants.PR_ZPUSH_NAME); + // The folder.name property is sometimes cached, check against the MAPI property + string currentName = (string)folder.GetProperty(OutlookConstants.PR_DISPLAY_NAME_W); + if (currentName != originalName) + { + Logger.Instance.Warning(this, "Shared folder renamed, renaming back: {0} - {1} - {2}", folder.Name, folder.SyncId, originalName); + // This is a locally renamed folder. Warn and rename back + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.SharedFolders_LocalFolder_Body, + Properties.Resources.SharedFolders_LocalFolder_Title, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + // Update both name and display name + folder.Name = originalName; + folder.SetProperty(OutlookConstants.PR_DISPLAY_NAME_W, originalName); + Logger.Instance.Warning(this, "Shared folder renamed, renamed back: {0} - {1} - {2}", folder.Name, folder.SyncId, originalName); + } + } + } + + private void OnSharedFolderRemoved(IFolder folder) + { + Logger.Instance.Fatal(this, "Shared folder removed, undeleting: {0}", folder.Name); + MessageBox.Show(ThisAddIn.Instance.Window, + Properties.Resources.SharedFolders_LocalFolder_Body, + Properties.Resources.SharedFolders_LocalFolder_Title, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + //folder.Delete(); + } + + #endregion } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs index a6d0908..c3bcfe8 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs @@ -84,6 +84,7 @@ namespace Acacia public const string PR_ATTR_HIDDEN = PROP + "10F4" + PT_BOOLEAN; public const string PR_DISPLAY_NAME = PROP + "3001" + PT_STRING8; + public const string PR_DISPLAY_NAME_W = PROP + "3001" + PT_UNICODE; public const string PR_SUBJECT = PROP + "0037" + PT_UNICODE; @@ -138,6 +139,7 @@ namespace Acacia 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; + public const string PR_ZPUSH_NAME = PROP + "6915" + PT_UNICODE; // TODO: names for these, use MFCMAPI public const string PR_EAS_SYNC1 = PROP + "6A17" + PT_BOOLEAN; @@ -146,7 +148,6 @@ namespace Acacia public const string PR_EAS_SYNCTYPE = PROP + "6A1A" + PT_LONG; public const string PR_EAS_SYNC2 = PROP + "6A1D" + PT_BOOLEAN; public const string PR_NET_FOLDER_FLAGS = PROP + "36DE" + PT_LONG; - public const string PR_EAS_NAME = PROP + "6915" + PT_UNICODE; public enum SyncType { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs index fe4a3e3..1e86f86 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.Designer.cs @@ -1075,6 +1075,24 @@ namespace Acacia.Properties { } } + /// + /// Looks up a localized string similar to Modifying shared folders locally is not supported. Please use the 'Shared Folders' dialog to modify these folders.. + /// + internal static string SharedFolders_LocalFolder_Body { + get { + return ResourceManager.GetString("SharedFolders_LocalFolder_Body", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shared folders. + /// + internal static string SharedFolders_LocalFolder_Title { + get { + return ResourceManager.GetString("SharedFolders_LocalFolder_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to No shared folders are available or you do not have permissions to view the root of the inbox.. /// diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx index 3013c37..0c60b45 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Properties/Resources.resx @@ -514,4 +514,10 @@ Private event + + Modifying shared folders locally is not supported. Please use the 'Shared Folders' dialog to modify these folders. + + + Shared folders + \ No newline at end of file diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs index e79b7ff..5249477 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs @@ -24,6 +24,7 @@ using System.Threading.Tasks; namespace Acacia.Stubs { public delegate void IFolder_BeforeItemMove(IFolder src, IItem item, IFolder target, ref bool cancel); + public delegate void IFolder_BeforeFolderMove(IFolder src, IFolder moveTo, ref bool cancel); public interface IFolder : IBase { @@ -95,6 +96,7 @@ namespace Acacia.Stubs #region Events event IFolder_BeforeItemMove BeforeItemMove; + event IFolder_BeforeFolderMove BeforeFolderMove; #endregion diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs index 0fe7af6..e152ffd 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs @@ -341,6 +341,52 @@ namespace Acacia.Stubs.OutlookWrappers } } + private IFolder_BeforeFolderMove _beforeFolderMove; + public event IFolder_BeforeFolderMove BeforeFolderMove + { + add + { + if (_beforeFolderMove == null) + HookBeforeFolderMove(true); + _beforeFolderMove += value; + } + remove + { + _beforeFolderMove -= value; + if (_beforeFolderMove == null) + HookBeforeFolderMove(false); + } + } + + private void HookBeforeFolderMove(bool hook) + { + if (hook) + _item.BeforeFolderMove += HandleBeforeFolderMove; + else + _item.BeforeFolderMove -= HandleBeforeFolderMove; + } + + private void HandleBeforeFolderMove(NSOutlook.MAPIFolder target, ref bool cancel) + { + try + { + if (_beforeFolderMove != null) + { + using (IFolder targetWrapped = Mapping.Wrap(target, false)) + { + if (targetWrapped != null) + { + _beforeFolderMove(this, targetWrapped, ref cancel); + } + } + } + } + catch (System.Exception e) + { + Logger.Instance.Error(this, "Exception in HandleBeforeItemMove: {0}", e); + } + } + public void SetCustomIcon(IPicture icon) { _item.SetCustomIcon(((PictureWrapper)icon).RawItem as StdPicture); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs index 87bcb8a..f45cc70 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs @@ -194,6 +194,7 @@ namespace Acacia.ZPush foreach (var entry in remove) { Logger.Instance.Debug(this, "Removing subfolder {0}, {1}", Name, entry.Key); + _watcher.OnFolderRemoved(entry.Value); _children.Remove(entry.Key); entry.Value.Cleanup(); } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs index 219b4a0..f681a44 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushTypes.cs @@ -79,7 +79,7 @@ namespace Acacia.ZPush /// /// Checks if this is a SyncId for a shared folders /// - public bool IsShared { get { return _id.StartsWith("S"); } } + public bool IsShared { get { return _id.StartsWith("S") || _id.StartsWith("C") || _id.StartsWith("G"); } } #region Standard overrides diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs index 991116e..365a86d 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs @@ -212,10 +212,11 @@ namespace Acacia.ZPush #region Folders - private class FolderWatcher + public class FolderWatcher { - public FolderEventHandler Discovered; - public FolderEventHandler Changed; + public FolderEventHandler Discovered { get; set; } + public FolderEventHandler Changed { get; set; } + public FolderEventHandler Removed { get; set; } public void OnDiscovered(IFolder folder) { @@ -228,6 +229,28 @@ namespace Acacia.ZPush if (Changed != null) Changed(folder); } + + public void OnRemoved(IFolder folder) + { + if (Removed != null) + Removed(folder); + } + + internal void Dispatch(ZPushFolder folder, EventKind kind) + { + switch (kind) + { + case EventKind.Discovered: + OnDiscovered(folder.Folder); + break; + case EventKind.Changed: + OnChanged(folder.Folder); + break; + case EventKind.Removed: + OnRemoved(folder.Folder); + break; + } + } } private readonly ConcurrentDictionary _folderWatchers = new ConcurrentDictionary(); @@ -239,10 +262,12 @@ namespace Acacia.ZPush _rootFolder = new ZPushFolder(this, account.Account.Store.GetRootFolder()); } - public void WatchFolder(FolderRegistration folder, FolderEventHandler handler, FolderEventHandler changedHandler = null) + public FolderWatcher WatchFolder(FolderRegistration folder, FolderEventHandler handler, + FolderEventHandler changedHandler = null, + FolderEventHandler removedHandler = null) { if (!DebugOptions.GetOption(null, DebugOptions.WATCHER_ENABLED)) - return; + return null; FolderWatcher watcher; if (!_folderWatchers.TryGetValue(folder, out watcher)) @@ -254,15 +279,19 @@ namespace Acacia.ZPush watcher.Discovered += handler; if (changedHandler != null) watcher.Changed += changedHandler; + if (removedHandler != null) + watcher.Removed += removedHandler; // Check existing folders for events - foreach(ZPushFolder existing in _allFolders) + foreach (ZPushFolder existing in _allFolders) { if (folder.IsApplicable(existing.Folder)) { - DispatchFolderEvent(folder, watcher, existing, true); + DispatchFolderEvent(folder, watcher, existing, EventKind.Discovered); } } + + return watcher; } private readonly List _allFolders = new List(); @@ -271,34 +300,44 @@ namespace Acacia.ZPush { Logger.Instance.Trace(this, "Folder discovered: {0}", folder); _allFolders.Add(folder); - DispatchFolderEvents(folder, true); + DispatchFolderEvents(folder, EventKind.Discovered); } internal void OnFolderChanged(ZPushFolder folder) { Logger.Instance.Trace(this, "Folder changed: {0}", folder); - DispatchFolderEvents(folder, false); + DispatchFolderEvents(folder, EventKind.Changed); } - private void DispatchFolderEvents(ZPushFolder folder, bool isNew) + internal void OnFolderRemoved(ZPushFolder folder) + { + Logger.Instance.Trace(this, "Folder removed: {0}", folder); + DispatchFolderEvents(folder, EventKind.Removed); + } + + internal enum EventKind + { + Discovered, + Changed, + Removed + } + + private void DispatchFolderEvents(ZPushFolder folder, EventKind kind) { // See if anybody is interested foreach (KeyValuePair entry in _folderWatchers) { if (entry.Key.IsApplicable(folder.Folder)) { - DispatchFolderEvent(entry.Key, entry.Value, folder, isNew); + DispatchFolderEvent(entry.Key, entry.Value, folder, kind); } } } - private void DispatchFolderEvent(FolderRegistration reg, FolderWatcher watcher, ZPushFolder folder, bool isNew) + private void DispatchFolderEvent(FolderRegistration reg, FolderWatcher watcher, ZPushFolder folder, EventKind kind) { - Logger.Instance.Debug(this, "Folder event: {0}, {1}, {2}", folder, reg, isNew); - if (isNew) - watcher.OnDiscovered(folder.Folder); - else - watcher.OnChanged(folder.Folder); + Logger.Instance.Debug(this, "Folder event: {0}, {1}, {2}", folder, reg, kind); + watcher.Dispatch(folder, kind); } internal bool ShouldFolderBeWatched(ZPushFolder parent, IFolder child)