[KOE-142] Added suppression and undo of local hierarchy changes to shared folders

This commit is contained in:
Patrick Simpson 2017-09-18 15:47:20 +03:00
parent 0da8086fa7
commit 47d83ce19b
9 changed files with 248 additions and 19 deletions

View File

@ -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
}
}

View File

@ -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
{

View File

@ -1075,6 +1075,24 @@ namespace Acacia.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Modifying shared folders locally is not supported. Please use the &apos;Shared Folders&apos; dialog to modify these folders..
/// </summary>
internal static string SharedFolders_LocalFolder_Body {
get {
return ResourceManager.GetString("SharedFolders_LocalFolder_Body", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shared folders.
/// </summary>
internal static string SharedFolders_LocalFolder_Title {
get {
return ResourceManager.GetString("SharedFolders_LocalFolder_Title", resourceCulture);
}
}
/// <summary>
/// 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..
/// </summary>

View File

@ -514,4 +514,10 @@
<data name="SharedFolders_PrivateEvent_Title" xml:space="preserve">
<value>Private event</value>
</data>
<data name="SharedFolders_LocalFolder_Body" xml:space="preserve">
<value>Modifying shared folders locally is not supported. Please use the 'Shared Folders' dialog to modify these folders.</value>
</data>
<data name="SharedFolders_LocalFolder_Title" xml:space="preserve">
<value>Shared folders</value>
</data>
</root>

View File

@ -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

View File

@ -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<IFolder>(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);

View File

@ -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();
}

View File

@ -79,7 +79,7 @@ namespace Acacia.ZPush
/// <summary>
/// Checks if this is a SyncId for a shared folders
/// </summary>
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

View File

@ -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<FolderRegistration, FolderWatcher> _folderWatchers = new ConcurrentDictionary<FolderRegistration, FolderWatcher>();
@ -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<ZPushFolder> _allFolders = new List<ZPushFolder>();
@ -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<FolderRegistration, FolderWatcher> 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)