Reimplements folders and items events

This commit is contained in:
Patrick Simpson 2017-02-14 18:05:25 +01:00
parent cbf9207ff7
commit dc12b73bbc
13 changed files with 680 additions and 145 deletions

View File

@ -286,7 +286,9 @@
<Compile Include="Stubs\ICommandBars.cs" />
<Compile Include="Stubs\IComWrapper.cs" />
<Compile Include="Stubs\IExplorer.cs" />
<Compile Include="Stubs\IFolders.cs" />
<Compile Include="Stubs\IItemEvents.cs" />
<Compile Include="Stubs\IItems.cs" />
<Compile Include="Stubs\IOutlookWindow.cs" />
<Compile Include="Stubs\IRecipient.cs" />
<Compile Include="Stubs\IStores.cs" />
@ -296,7 +298,9 @@
<Compile Include="Stubs\OutlookWrappers\AddressEntryWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\CommandBarsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ExplorerWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\FoldersWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ItemEventsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ItemsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\RecipientWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoresWrapper.cs" />

View File

@ -336,7 +336,7 @@ namespace Acacia.Features.GAB
// Scan a few of the newest items, in case there is some junk in the ZPush folder
// TODO: this shouldn't happen in production.
int i = 0;
foreach(IItem item in Folder.ItemsSorted("LastModificationTime", true))
foreach(IItem item in Folder.Items.Sort("LastModificationTime", true))
{
using (item)
{

View File

@ -35,9 +35,7 @@ namespace Acacia.Stubs
bool ShowAsOutlookAB { get; set; }
IEnumerable<IItem> Items { get; }
IEnumerable<IItem> ItemsSorted(string field, bool descending);
IItems Items { get; }
IItem GetItemById(string id);
@ -58,7 +56,11 @@ namespace Acacia.Stubs
IEnumerable<FolderType> GetSubFolders<FolderType>()
where FolderType : IFolder;
IEnumerable<IFolder> GetSubFolders();
IFolders SubFolders
{
get;
}
FolderType GetSubFolder<FolderType>(string name)
where FolderType : IFolder;

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public delegate void IFolders_FolderEventHandler(IFolder folder);
public delegate void IFolders_EventHandler();
public interface IFolders_Events : IDisposable
{
event IFolders_FolderEventHandler FolderAdd;
event IFolders_FolderEventHandler FolderChange;
event IFolders_EventHandler FolderRemove;
}
public interface IFolders : IEnumerable<IFolder>
{
#region Events
/// <summary>
/// Returns an events subscribption object.
/// </summary>
/// <returns>The events. The caller is responsible for disposing</returns>
IFolders_Events GetEvents();
#endregion
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public delegate void IItems_ItemEventHandler(IItem item);
public interface IItems_Events : IDisposable
{
event IItems_ItemEventHandler ItemAdd;
event IItems_ItemEventHandler ItemChange;
}
public interface IItems : IEnumerable<IItem>
{
/// <summary>
/// Sorts the items.
/// </summary>
/// <param name="field"></param>
/// <param name="descending"></param>
/// <returns>The current collection, which will be sorted</returns>
IItems Sort(string field, bool descending);
/// <summary>
/// Returns an events subscribption object.
/// </summary>
/// <returns>The events. The caller is responsible for disposing</returns>
IItems_Events GetEvents();
}
}

View File

@ -27,7 +27,7 @@ namespace Acacia.Stubs.OutlookWrappers
{
abstract class ComWrapper<ItemType> : DisposableWrapper, IComWrapper
{
protected ItemType _item { get; private set; }
protected readonly ItemType _item;
/// <summary>
/// Creates a wrapper.
@ -49,7 +49,6 @@ namespace Acacia.Stubs.OutlookWrappers
if (MustRelease)
{
ComRelease.Release(_item);
_item = default(ItemType);
}
}

View File

@ -34,6 +34,13 @@ namespace Acacia.Stubs.OutlookWrappers
{
}
protected override void DoRelease()
{
base.DoRelease();
}
internal NSOutlook.Folder RawItem { get { return _item; } }
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
@ -125,118 +132,15 @@ namespace Acacia.Stubs.OutlookWrappers
public ItemType ItemType { get { return (ItemType)(int)_item.DefaultItemType; } }
#region Enumeration
public class ItemsEnumerator<ItemType> : ComWrapper<NSOutlook.Items>, IEnumerator<ItemType>
where ItemType : IItem
{
private IEnumerator _enum;
private ItemType _last;
public ItemsEnumerator(NSOutlook.Folder folder, string field, bool descending) : base(folder.Items)
{
// TODO: can _items be released here already?
if (field != null)
{
this._item.Sort("[" + field + "]", descending);
}
this._enum = _item.GetEnumerator();
}
protected override void DoRelease()
{
CleanLast();
if (_enum != null)
{
if (_enum is IDisposable)
((IDisposable)_enum).Dispose();
ComRelease.Release(_enum);
_enum = null;
}
base.DoRelease();
}
public ItemType Current
{
get
{
CleanLast();
_last = Mapping.Wrap<ItemType>(_enum.Current);
return _last;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
private void CleanLast()
{
if (_last != null)
{
_last.Dispose();
_last = default(ItemType);
}
}
public bool MoveNext()
{
CleanLast();
return _enum.MoveNext();
}
public void Reset()
{
CleanLast();
_enum.Reset();
}
}
public class ItemsEnumerable<ItemType> : IEnumerable<ItemType>
where ItemType : IItem
{
// Managed by the caller, not released here
private readonly NSOutlook.Folder _folder;
private readonly string _field;
private readonly bool _descending;
public ItemsEnumerable(NSOutlook.Folder folder, string field, bool descending)
{
this._folder = folder;
this._field = field;
this._descending = descending;
}
public IEnumerator<ItemType> GetEnumerator()
{
return new ItemsEnumerator<ItemType>(_folder, _field, _descending);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public IEnumerable<IItem> Items
public IItems Items
{
get
{
return new ItemsEnumerable<IItem>(_item, null, false);
return new ItemsWrapper(this);
}
}
public IEnumerable<IItem> ItemsSorted(string field, bool descending)
{
return new ItemsEnumerable<IItem>(_item, field, descending);
}
#endregion
public IItem GetItemById(string entryId)
{
try
@ -289,13 +193,16 @@ namespace Acacia.Stubs.OutlookWrappers
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _item.Folders.RawEnum(false))
{
yield return WrapFolder<FolderType>(folder);
yield return folder.Wrap<FolderType>();
};
}
public IEnumerable<IFolder> GetSubFolders()
public IFolders SubFolders
{
return GetSubFolders<IFolder>();
get
{
return new FoldersWrapper(this);
}
}
public FolderType GetSubFolder<FolderType>(string name)
@ -319,7 +226,7 @@ namespace Acacia.Stubs.OutlookWrappers
}
if (sub == null)
return default(FolderType);
return WrapFolder<FolderType>(sub);
return sub.Wrap<FolderType>();
}
public FolderType CreateFolder<FolderType>(string name)
@ -330,37 +237,19 @@ namespace Acacia.Stubs.OutlookWrappers
NSOutlook.Folders folders = com.Add(_item.Folders);
if (typeof(FolderType) == typeof(IFolder))
{
return WrapFolder<FolderType>(folders.Add(name));
return folders.Add(name).Wrap<FolderType>();
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
NSOutlook.MAPIFolder newFolder = folders.Add(name, NSOutlook.OlDefaultFolders.olFolderContacts);
newFolder.ShowAsOutlookAB = true;
return WrapFolder<FolderType>(newFolder);
return newFolder.Wrap<FolderType>();
}
else
throw new NotSupportedException();
}
}
private FolderType WrapFolder<FolderType>(NSOutlook.MAPIFolder folder)
where FolderType : IFolder
{
if (typeof(FolderType) == typeof(IFolder))
{
return (FolderType)(IFolder)new FolderWrapper(folder);
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
return (FolderType)(IFolder)new AddressBookWrapper(folder);
}
else
{
ComRelease.Release(folder);
throw new NotSupportedException();
}
}
#endregion
public IStorageItem GetStorageItem(string name)

View File

@ -0,0 +1,199 @@
using Acacia.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class FoldersWrapper : IFolders
{
// Managed by the caller, not released here
private readonly FolderWrapper _folder;
public FoldersWrapper(FolderWrapper folder)
{
this._folder = folder;
}
public IEnumerator<IFolder> GetEnumerator()
{
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _folder.RawItem.Folders.RawEnum(false))
{
yield return folder.Wrap<IFolder>();
};
}
IEnumerator IEnumerable.GetEnumerator()
{
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _folder.RawItem.Folders.RawEnum(false))
{
yield return folder.Wrap<IFolder>();
};
}
#region Events
private class EventsWrapper : ComWrapper<NSOutlook.Folders>, IFolders_Events
{
public EventsWrapper(NSOutlook.Folders item) : base(item)
{
}
#region FolderAdd
private IFolders_FolderEventHandler _folderAdd;
public event IFolders_FolderEventHandler FolderAdd
{
add
{
if (_folderAdd == null)
HookFolderAdd(true);
_folderAdd += value;
}
remove
{
_folderAdd -= value;
if (_folderAdd == null)
HookFolderAdd(false);
}
}
private void HookFolderAdd(bool hook)
{
if (hook)
_item.FolderAdd += HandleFolderAdd;
else
_item.FolderAdd -= HandleFolderAdd;
}
private void HandleFolderAdd(NSOutlook.MAPIFolder folder)
{
try
{
if (_folderAdd != null)
{
using (IFolder folderWrapped = Mapping.Wrap<IFolder>(folder, false))
{
if (folderWrapped != null)
{
_folderAdd(folderWrapped);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderAdd: {0}", e);
}
}
#endregion
#region FolderChange
private IFolders_FolderEventHandler _folderChange;
public event IFolders_FolderEventHandler FolderChange
{
add
{
if (_folderChange == null)
HookFolderChange(true);
_folderChange += value;
}
remove
{
_folderChange -= value;
if (_folderChange == null)
HookFolderChange(false);
}
}
private void HookFolderChange(bool hook)
{
if (hook)
_item.FolderChange += HandleFolderChange;
else
_item.FolderChange -= HandleFolderChange;
}
private void HandleFolderChange(NSOutlook.MAPIFolder folder)
{
try
{
if (_folderChange != null)
{
using (IFolder folderWrapped = Mapping.Wrap<IFolder>(folder, false))
{
if (folderWrapped != null)
{
_folderChange(folderWrapped);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderChange: {0}", e);
}
}
#endregion
#region FolderRemove
private IFolders_EventHandler _folderRemove;
public event IFolders_EventHandler FolderRemove
{
add
{
if (_folderRemove == null)
HookFolderRemove(true);
_folderRemove += value;
}
remove
{
_folderRemove -= value;
if (_folderRemove == null)
HookFolderRemove(false);
}
}
private void HookFolderRemove(bool hook)
{
if (hook)
_item.FolderRemove += HandleFolderRemove;
else
_item.FolderRemove -= HandleFolderRemove;
}
private void HandleFolderRemove()
{
try
{
if (_folderRemove != null)
{
_folderRemove();
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderRemove: {0}", e);
}
}
#endregion
}
public IFolders_Events GetEvents()
{
return new EventsWrapper(_folder.RawItem.Folders);
}
#endregion
}
}

View File

@ -0,0 +1,235 @@
using Acacia.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class ItemsWrapper : IItems
{
// Managed by the caller, not released here
private readonly FolderWrapper _folder;
private string _field;
private bool _descending;
public ItemsWrapper(FolderWrapper folder)
{
this._folder = folder;
}
public IItems Sort(string field, bool descending)
{
this._field = field;
this._descending = descending;
return this;
}
private NSOutlook.Items GetItems()
{
return _folder.RawItem.Items;
}
public IEnumerator<IItem> GetEnumerator()
{
return new ItemsEnumerator<IItem>(this, _field, _descending);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#region Enumeration
public class ItemsEnumerator<ItemType> : ComWrapper<NSOutlook.Items>, IEnumerator<ItemType>
where ItemType : IItem
{
private IEnumerator _enum;
private ItemType _last;
public ItemsEnumerator(ItemsWrapper items, string field, bool descending) : base(items.GetItems())
{
// TODO: can _items be released here already?
if (field != null)
{
this._item.Sort("[" + field + "]", descending);
}
this._enum = _item.GetEnumerator();
}
protected override void DoRelease()
{
CleanLast();
if (_enum != null)
{
if (_enum is IDisposable)
((IDisposable)_enum).Dispose();
ComRelease.Release(_enum);
_enum = null;
}
base.DoRelease();
}
public ItemType Current
{
get
{
CleanLast();
_last = Mapping.Wrap<ItemType>(_enum.Current);
return _last;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
private void CleanLast()
{
if (_last != null)
{
_last.Dispose();
_last = default(ItemType);
}
}
public bool MoveNext()
{
CleanLast();
return _enum.MoveNext();
}
public void Reset()
{
CleanLast();
_enum.Reset();
}
}
#endregion
#region Events
private class EventsWrapper : ComWrapper<NSOutlook.Items>, IItems_Events
{
public EventsWrapper(NSOutlook.Items item) : base(item)
{
}
#region ItemAdd
private IItems_ItemEventHandler _itemAdd;
public event IItems_ItemEventHandler ItemAdd
{
add
{
if (_itemAdd == null)
HookItemAdd(true);
_itemAdd += value;
}
remove
{
_itemAdd -= value;
if (_itemAdd == null)
HookItemAdd(false);
}
}
private void HookItemAdd(bool hook)
{
if (hook)
_item.ItemAdd += HandleItemAdd;
else
_item.ItemAdd -= HandleItemAdd;
}
private void HandleItemAdd(object objItem)
{
try
{
if (_itemAdd != null)
{
using (IItem item = Mapping.Wrap<IItem>(objItem, false))
{
if (item != null)
{
_itemAdd(item);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleItemAdd: {0}", e);
}
}
#endregion
#region ItemChange
private IItems_ItemEventHandler _itemChange;
public event IItems_ItemEventHandler ItemChange
{
add
{
if (_itemChange == null)
HookItemChange(true);
_itemChange += value;
}
remove
{
_itemChange -= value;
if (_itemChange == null)
HookItemChange(false);
}
}
private void HookItemChange(bool hook)
{
if (hook)
_item.ItemChange += HandleItemChange;
else
_item.ItemChange -= HandleItemChange;
}
private void HandleItemChange(object objItem)
{
try
{
if (_itemChange != null)
{
using (IItem item = Mapping.Wrap<IItem>(objItem, false))
{
if (item != null)
{
_itemChange(item);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleItemChange: {0}", e);
}
}
#endregion
}
public IItems_Events GetEvents()
{
return new EventsWrapper(GetItems());
}
#endregion
}
}

View File

@ -1,4 +1,5 @@
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@ -15,6 +16,25 @@ namespace Acacia.Stubs
return Mapping.WrapOrDefault<IFolder>(obj);
}
public static FolderType Wrap<FolderType>(this NSOutlook.MAPIFolder folder)
where FolderType : IFolder
{
if (typeof(FolderType) == typeof(IFolder))
{
return (FolderType)(IFolder)new FolderWrapper(folder);
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
return (FolderType)(IFolder)new AddressBookWrapper(folder);
}
else
{
ComRelease.Release(folder);
throw new NotSupportedException();
}
}
public static WrapType Wrap<WrapType>(this object o, bool mustRelease = true)
where WrapType : IBase
{

View File

@ -62,7 +62,6 @@ namespace Acacia.Utils
ComRelease.Release(source);
}
// TODO: check this
public static IEnumerable RawEnum(this IEnumerable source, bool releaseItems = true)
{
foreach (object item in source)

View File

@ -27,10 +27,12 @@ namespace Acacia.ZPush
{
public class ZPushFolder : DisposableWrapper
{
private IFolder _folder;
private ZPushFolder _parent;
private readonly IFolder _folder;
private readonly IItems_Events _items;
private readonly IFolders_Events _subFolders;
private readonly ZPushFolder _parent;
private readonly ZPushWatcher _watcher;
private List<ItemsWatcher> _itemsWatchers = new List<ItemsWatcher>();
private readonly List<ItemsWatcher> _itemsWatchers = new List<ItemsWatcher>();
/// <summary>
/// Children folders indexed by EntryID
@ -50,6 +52,9 @@ namespace Acacia.ZPush
this._parent = parent;
this._watcher = watcher;
this._folder = folder;
// We need to keep links to these objects to keep getting events.
this._items = folder.Items.GetEvents();
this._subFolders = folder.SubFolders.GetEvents();
folder.ZPush = this;
}
@ -57,6 +62,13 @@ namespace Acacia.ZPush
{
Cleanup();
_folder.Dispose();
_items.Dispose();
_subFolders.Dispose();
}
public override string ToString()
{
return Name;
}
public IFolder Folder { get { return _folder; } }
@ -71,7 +83,7 @@ namespace Acacia.ZPush
_watcher.OnFolderDiscovered(this);
// Recurse the children
foreach (IFolder subfolder in _folder.GetSubFolders())
foreach (IFolder subfolder in _folder.SubFolders)
{
Tasks.Task(null, "WatchChild", () => WatchChild(subfolder));
}
@ -94,10 +106,10 @@ namespace Acacia.ZPush
}
}
#region Event handlers
private void HookEvents(bool register)
{
// TODO
/*
if (register)
{
// Item events
@ -119,9 +131,119 @@ namespace Acacia.ZPush
_subFolders.FolderAdd -= SubFolders_FolderAdd;
_subFolders.FolderRemove -= SubFolders_FolderRemove;
_subFolders.FolderChange -= SubFolders_FolderChange;
}*/
}
}
#region Event handlers
private void SubFolders_FolderAdd(IFolder folder)
{
try
{
Logger.Instance.Debug(this, "Folder added in {0}: {1}", Name, folder.Name);
WatchChild(folder);
}
catch (System.Exception e) { Logger.Instance.Error(this, "Exception in SubFolders_FolderAdd: {0}: {1}", Name, e); }
}
private void SubFolders_FolderRemove()
{
try
{
Logger.Instance.Debug(this, "Folder removed from {0}", Name);
// Helpfully, Outlook doesn't tell us which folder was removed. Could use the BeforeFolderMove event instead,
// but that doesn't fire if a folder was removed on the server.
// Hence, fetch all the remaining folder ids, and remove any folder that no longer exists.
// TODO: move this logic into IFolders?
HashSet<string> remaining = new HashSet<string>();
foreach (IFolder child in _folder.SubFolders)
{
try
{
remaining.Add(child.EntryID);
}
catch (System.Exception e) { Logger.Instance.Warning(this, "Ignoring failed child: {0}", e); }
}
// Find the folders that need to be removed. There should be only one, but with Outlook we can never be sure,
// so compare all. We cannot modify the dictionary during iteration, so store entries to be removed in a
// temporary list
List<KeyValuePair<string, ZPushFolder>> remove = new List<KeyValuePair<string, ZPushFolder>>();
foreach (var entry in _children)
{
if (!remaining.Contains(entry.Key))
{
remove.Add(entry);
}
}
// Actually remove the folders
foreach (var entry in remove)
{
Logger.Instance.Debug(this, "Removing subfolder {0}, {1}", Name, entry.Key);
_children.Remove(entry.Key);
entry.Value.Cleanup();
}
}
catch (System.Exception e) { Logger.Instance.Error(this, "Exception in SubFolders_FolderRemove: {0}: {1}", Name, e); }
}
private void SubFolders_FolderChange(IFolder folder)
{
try
{
Logger.Instance.Debug(this, "Folder changed in {0}: {1}", Name, folder.Name);
ZPushFolder child;
if (_children.TryGetValue(folder.EntryID, out child))
{
_watcher.OnFolderChanged(child);
// TODO: release folder?
}
else
{
// On a clean profile, we sometimes get a change notification, but not an add notification
// Create it now
// This will send a discover notification if required, which is just as good as a change notification
Logger.Instance.Debug(this, "Folder change on unreported folder in {0}: {1}, {2}, {3}", Name, folder.Name, folder.EntryID, folder.StoreDisplayName);
WatchChild(folder);
}
}
catch (System.Exception e) { Logger.Instance.Error(this, "Exception in SubFolders_FolderChange: {0}: {1}", Name, e); }
}
private void Items_ItemAdd(IItem item)
{
try
{
Logger.Instance.Trace(this, "New item {0}: {1}", Name, item.EntryID);
foreach (ItemsWatcher watcher in _itemsWatchers)
watcher.OnItemAdd(this, item);
}
catch (System.Exception e)
{
Logger.Instance.Trace(this, "ItemAdd exception: {0}: {1}", Name, e);
}
}
private void Items_ItemChange(IItem item)
{
try
{
Logger.Instance.Trace(this, "Changed item {0}", Name);
foreach (ItemsWatcher watcher in _itemsWatchers)
watcher.OnItemChange(this, item);
}
catch (System.Exception e)
{
Logger.Instance.Trace(this, "ItemChange exception: {0}: {1}", Name, e);
}
}
#endregion
#endregion
private void Cleanup()
{
Logger.Instance.Trace(this, "Unwatching folder: {0}", _folder.Name);

View File

@ -135,7 +135,7 @@ namespace Acacia.ZPush
// Hide the folders that are not custom folders
using (IFolder root = store.GetRootFolder())
{
foreach(IFolder sub in root.GetSubFolders())
foreach(IFolder sub in root.SubFolders)
{
using (sub)
{