diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs
index f1d274d..09b6bc8 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs
@@ -262,6 +262,14 @@ namespace Acacia.Features.GAB
}
private static readonly BoolOption OPTION_FILE_AS_DISPLAY_NAME = new BoolOption("FileAsDisplayName", false);
+ [AcaciaOption("If enabled, an item cache will be used during GAB creation")]
+ public bool ItemCache
+ {
+ get { return GetOption(OPTION_ITEM_CACHE); }
+ set { SetOption(OPTION_ITEM_CACHE, value); }
+ }
+ private static readonly BoolOption OPTION_ITEM_CACHE = new BoolOption("ItemCache", false);
+
#endregion
#region Modification suppression
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
index 2cbff77..e6d604b 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
@@ -117,6 +117,8 @@ namespace Acacia.Features.GAB
this._feature = feature;
this._contactsProvider = contactsProvider;
this._contactsDisposer = contactsDisposer;
+ _items = new ItemCache(this);
+ _items.Enabled = feature.ItemCache;
}
public string DisplayName
@@ -130,15 +132,6 @@ namespace Acacia.Features.GAB
#region Processing
- private string[] _chunkStateStringCache;
- private int? _currentSequenceCache;
-
- private void ClearCache()
- {
- _chunkStateStringCache = null;
- _currentSequenceCache = null;
- }
-
public void FullResync(CompletionTracker completion)
{
ClearCache();
@@ -181,7 +174,18 @@ namespace Acacia.Features.GAB
/// If specified, this item has changed. If null, means a global check should be performed
public void Process(CompletionTracker completion, IZPushItem item)
{
- using (CompletionTracker.Step step = completion?.Begin())
+ var watch = System.Diagnostics.Stopwatch.StartNew();
+ if (completion == null)
+ completion = new CompletionTracker(null);
+
+ completion.AddCompletion(() =>
+ {
+ // Log time
+ watch.Stop();
+ Logger.Instance.Info(this, "GAB.Process done in {0}ms", watch.ElapsedMilliseconds);
+ });
+
+ using (CompletionTracker.Step step = completion.Begin())
{
try
{
@@ -238,7 +242,8 @@ namespace Acacia.Features.GAB
ProcessMessage(completion, (IZPushItem)item2);
}
watch.Stop();
- Logger.Instance.Warning(this, "ProcessChunk: {0}ms", watch.ElapsedMilliseconds);
+ _items.Clear();
+ Logger.Instance.Warning(this, "ProcessChunk: {0} in {1}ms", entryId, watch.ElapsedMilliseconds);
});
}
}
@@ -537,7 +542,7 @@ namespace Acacia.Features.GAB
CreateObject(index, id, value);
}
watch.Stop();
- Logger.Instance.Warning(this, "ProcessChunkBody: {0}ms", watch.ElapsedMilliseconds);
+ Logger.Instance.Warning(this, "ProcessChunkBody: {0} in {1}ms", index, watch.ElapsedMilliseconds);
}
private void CreateObject(ChunkIndex index, string id, Dictionary value)
@@ -546,6 +551,9 @@ namespace Acacia.Features.GAB
{
_feature?.BeginProcessing();
+ // Remove any cached entry
+ _items.Remove(id);
+
string type = Get(value, "type");
if (type == "contact")
CreateContact(id, value, index, 0);
@@ -655,10 +663,17 @@ namespace Acacia.Features.GAB
// Set the chunk data
SetItemStandard(index, id, com, com.Add(contact.UserProperties));
- // TODO: groups
-
// Done
contact.Save();
+
+ // Add to groups
+ if (_feature.GroupMembers && Get(value, "memberOf") != null)
+ {
+ using (IContactItem wrapped = Mapping.Wrap(contact, false))
+ {
+ AddItemToGroups(wrapped, id, value, index);
+ }
+ }
});
}
@@ -680,12 +695,9 @@ namespace Acacia.Features.GAB
if (!_feature.CreateGroups)
return;
- string smtpAddress = Get (value, "smtpAddress");
+ string smtpAddress = Get(value, "smtpAddress");
if (!string.IsNullOrEmpty(smtpAddress) && _feature.SMTPGroupsAsContacts)
{
- // TODO:
- throw new NotImplementedException();
- /*
// Create a contact
using (IContactItem contact = Contacts.Create())
{
@@ -705,7 +717,7 @@ namespace Acacia.Features.GAB
string membersBody = null;
foreach (string memberId in members)
{
- using (IItem item = FindItemById(memberId))
+ using (IItem item = _items.Find(memberId))
{
Logger.Instance.Debug(this, "Finding member {0} of {1}: {2}", memberId, id, item?.EntryID);
if (item != null)
@@ -741,21 +753,21 @@ namespace Acacia.Features.GAB
contact.Save();
AddItemToGroups(contact, id, value, index);
- }*/
+ }
}
else
{
// Create a proper group
- Contacts.GABCreate(NSOutlook.OlItemType.olDistributionListItem, (com, group) =>
+ using (IDistributionList group = Contacts.Create())
{
Logger.Instance.Debug(this, "Creating group: {0}", id);
group.DLName = Get(value, "displayName");
if (smtpAddress != null)
{
- // TODO? group.SMTPAddress = smtpAddress;
+ group.SMTPAddress = smtpAddress;
}
- SetItemStandard(index, id, com, com.Add(group.UserProperties));
+ SetItemStandard(group, id, value, index);
group.Save();
if (_feature.GroupMembers)
@@ -765,30 +777,19 @@ namespace Acacia.Features.GAB
{
foreach (string memberId in members)
{
- using (IItem item = FindItemById(memberId))
+ using (IItem item = _items.Find(memberId))
{
Logger.Instance.Debug(this, "Finding member {0} of {1}: {2}", memberId, id, item?.EntryID);
- //if (item != null)
- // AddGroupMember(group, item);
+ if (item != null)
+ AddGroupMember(group, item);
}
}
}
group.Save();
}
- // TODO AddItemToGroups(group, id, value, index);
-
- });
-
- }
- }
-
- private IItem FindItemById(string id)
- {
- using (ISearch search = Contacts.Search())
- {
- search.AddField(PROP_GAB_ID, true).SetOperation(SearchOperation.Equal, id);
- return search.SearchOne();
+ AddItemToGroups(group, id, value, index);
+ }
}
}
@@ -827,7 +828,7 @@ namespace Acacia.Features.GAB
string memberOf = memberOfObject as string;
if (memberOf != null)
{
- using (IItem groupItem = FindItemById(memberOf))
+ using (IItem groupItem = _items.Find(memberOf))
{
Logger.Instance.Debug(this, "Finding group {0} for {1}: {2}", memberOf, id, groupItem?.EntryID);
if (groupItem is IDistributionList)
@@ -847,6 +848,133 @@ namespace Acacia.Features.GAB
#endregion
+ #region Caching
+
+ private string[] _chunkStateStringCache;
+ private int? _currentSequenceCache;
+
+ private void ClearCache()
+ {
+ _chunkStateStringCache = null;
+ _currentSequenceCache = null;
+ }
+
+ private class ItemCache
+ {
+ private readonly GABHandler _gab;
+ private Dictionary _items;
+
+ public bool Enabled
+ {
+ get { return _items != null; }
+ set
+ {
+ if (value)
+ {
+ if (_items == null)
+ _items = new Dictionary();
+ }
+ else
+ {
+ _items = null;
+ }
+ }
+ }
+
+ public ItemCache(GABHandler gab)
+ {
+ this._gab = gab;
+ }
+
+ public IItem Find(string id)
+ {
+ // First try the item cache
+ if (Enabled)
+ {
+ IItem item;
+ if (_items.TryGetValue(id, out item))
+ {
+ bool ok = true;
+ try
+ {
+ if (item != null)
+ {
+ // Get the entry id to test if the underlying object is still valid
+ string s = item.EntryID;
+ if (string.IsNullOrEmpty(s))
+ {
+ ok = false;
+ }
+ }
+ }
+ catch (System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ Logger.Instance.Trace(this, "Cache item detached");
+ ok = false;
+ }
+
+ // If it's ok, we're done
+ if (ok)
+ return item;
+
+ // Otherwise clear the cache, as usually all items are stale
+ Clear();
+ System.GC.Collect();
+ // And fall through to fetch it properly
+ }
+ }
+
+ // Do a lookup.
+ using (ISearch search = _gab.Contacts.Search())
+ {
+ search.AddField(PROP_GAB_ID, true).SetOperation(SearchOperation.Equal, id);
+ IItem item = search.SearchOne();
+
+ // Add to cache. Also for failed lookups, will be updated when created
+ if (Enabled)
+ {
+ _items.Add(id, item);
+ }
+
+ return item;
+ }
+ }
+
+ public void Remove(string id)
+ {
+ _items?.Remove(id);
+ }
+
+ public void Clear()
+ {
+ Dictionary old = _items;
+ _items = null;
+ if (old != null)
+ {
+ _items = new Dictionary();
+
+ Logger.Instance.Info(this, "GAB ItemCache: {0} entries", old.Count);
+ foreach (IItem item in old.Values)
+ {
+ try
+ {
+ item?.Dispose();
+ }
+ catch(System.Runtime.InteropServices.InvalidComObjectException)
+ {
+ // Ignore silently, means it already got disposed
+ }
+ }
+ old.Clear();
+ }
+
+ }
+ }
+
+ private readonly ItemCache _items;
+
+ #endregion
+
#region Removal
public void Remove()
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs
index 50ae04f..a764b45 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/OutlookConstants.cs
@@ -1,6 +1,4 @@
-
-using Acacia.Native.MAPI;
-/// Copyright 2016 Kopano b.v.
+/// Copyright 2019 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@@ -20,6 +18,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Acacia.Native.MAPI;
+using Acacia.Utils;
+using Acacia.Stubs;
namespace Acacia
{
@@ -290,5 +291,52 @@ namespace Acacia
public const string MESSAGE_CLASS_NOTES = "IPM.StickyNote";
#endregion
+
+ #region Misc helpers
+
+
+ private static readonly byte[] PREFIX_MEMBER_ID =
+ {
+ 0x00, 0x00, 0x00, 0x00, 0xC0, 0x91, 0xAD, 0xD3, 0x51, 0x9D, 0xCF, 0x11, 0xA4, 0xA9, 0x00, 0xAA, 0x00, 0x47, 0xFA, 0xA4, 0xB4
+ };
+
+ public static byte[] CreateMemberId(IDistributionList member)
+ {
+ List id = new List();
+ id.AddRange(PREFIX_MEMBER_ID);
+ id.AddRange(StringUtil.HexToBytes(member.EntryID));
+ return id.ToArray();
+ }
+
+ private static readonly byte[] PREFIX_ONEOFFMEMBER_ID =
+ {
+ 0x00, 0x00, 0x00, 0x00, 0x81, 0x2B, 0x1F, 0xA4, 0xBE, 0xA3, 0x10, 0x19, 0x9D, 0x6E, 0x00, 0xDD, 0x01, 0x0F, 0x54, 0x02, 0x00, 0x00, 0x01, 0x80
+ };
+
+ public static byte[] CreateOneOffMemberId(IDistributionList member)
+ {
+ return CreateOneOffMemberId(member.DLName, "UNKNOWN", "UNKNOWN");
+ }
+
+ public static byte[] CreateOneOffMemberId(string displayName, string addressType, string address)
+ {
+ byte[] zeroes = { 0, 0 };
+ List id = new List();
+ id.AddRange(PREFIX_ONEOFFMEMBER_ID);
+
+ id.AddRange(Encoding.Unicode.GetBytes(displayName));
+ id.AddRange(zeroes);
+
+ id.AddRange(Encoding.Unicode.GetBytes(addressType));
+ id.AddRange(zeroes);
+
+ id.AddRange(Encoding.Unicode.GetBytes(address));
+ id.AddRange(zeroes);
+
+ id.AddRange(zeroes);
+ return id.ToArray();
+ }
+
+ #endregion
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs
index 7cd5cb5..3c89d8d 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs
@@ -43,7 +43,7 @@ namespace Acacia.Stubs.OutlookWrappers
set
{
string displayName = DLName + " (" + value + ")";
- byte[] oneOffId = CreateOneOffMemberId(DLName, "SMTP", value);
+ byte[] oneOffId = OutlookConstants.CreateOneOffMemberId(DLName, "SMTP", value);
SetProperties(
new string[]
@@ -111,8 +111,8 @@ namespace Acacia.Stubs.OutlookWrappers
object[] oneOffMembers = (object[])GetProperty(OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS);
// Create the new member ids
- byte[] memberId = CreateMemberId(member);
- byte[] oneOffMemberId = CreateOneOffMemberId(member);
+ byte[] memberId = OutlookConstants.CreateMemberId(member);
+ byte[] oneOffMemberId = OutlookConstants.CreateOneOffMemberId(member);
// See if it is already a member
// Compare on one-off member id, as memberId changes
@@ -151,47 +151,6 @@ namespace Acacia.Stubs.OutlookWrappers
);
}
- private static readonly byte[] PREFIX_MEMBER_ID =
- {
- 0x00, 0x00, 0x00, 0x00, 0xC0, 0x91, 0xAD, 0xD3, 0x51, 0x9D, 0xCF, 0x11, 0xA4, 0xA9, 0x00, 0xAA, 0x00, 0x47, 0xFA, 0xA4, 0xB4
- };
-
- private byte[] CreateMemberId(IDistributionList member)
- {
- List id = new List();
- id.AddRange(PREFIX_MEMBER_ID);
- id.AddRange(StringUtil.HexToBytes(member.EntryID));
- return id.ToArray();
- }
-
- private static readonly byte[] PREFIX_ONEOFFMEMBER_ID =
- {
- 0x00, 0x00, 0x00, 0x00, 0x81, 0x2B, 0x1F, 0xA4, 0xBE, 0xA3, 0x10, 0x19, 0x9D, 0x6E, 0x00, 0xDD, 0x01, 0x0F, 0x54, 0x02, 0x00, 0x00, 0x01, 0x80
- };
-
- private byte[] CreateOneOffMemberId(IDistributionList member)
- {
- return CreateOneOffMemberId(member.DLName, "UNKNOWN", "UNKNOWN");
- }
-
- private byte[] CreateOneOffMemberId(string displayName, string addressType, string address)
- {
- byte[] zeroes = { 0, 0 };
- List id = new List();
- id.AddRange(PREFIX_ONEOFFMEMBER_ID);
-
- id.AddRange(Encoding.Unicode.GetBytes(displayName));
- id.AddRange(zeroes);
-
- id.AddRange(Encoding.Unicode.GetBytes(addressType));
- id.AddRange(zeroes);
-
- id.AddRange(Encoding.Unicode.GetBytes(address));
- id.AddRange(zeroes);
-
- id.AddRange(zeroes);
- return id.ToArray();
- }
#endregion
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/CompletionTracker.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/CompletionTracker.cs
index 2a139f9..e99f84c 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/CompletionTracker.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/CompletionTracker.cs
@@ -24,12 +24,13 @@ namespace Acacia.Utils
}
}
- private readonly Action _completion;
+ private readonly List _completions = new List();
private int steps = 0;
public CompletionTracker(Action completion)
{
- this._completion = completion;
+ if (completion != null)
+ _completions.Add(completion);
}
///
@@ -47,8 +48,14 @@ namespace Acacia.Utils
if (Interlocked.Decrement(ref steps) == 0)
{
// Done
- _completion();
+ foreach(Action completion in _completions)
+ completion();
}
}
+
+ public void AddCompletion(Action completion)
+ {
+ _completions.Add(completion);
+ }
}
}