diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index ac56f78..ea3dcfe 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -310,13 +310,15 @@
+
+
-
+
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs
index 783ec46..a0d6c11 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/FeatureSharedFolders.cs
@@ -46,6 +46,15 @@ namespace Acacia.Features.SharedFolders
}
private static readonly BoolOption OPTION_REMINDERS = new BoolOption("Reminders", true);
+ [AcaciaOption("If disabled, the reminders queyr will be explicitly stopped and started when update the query. " +
+ "This causes more effort to search again, but might prevent issues.")]
+ public bool RemindersKeepRunning
+ {
+ get { return GetOption(OPTION_REMINDERS_KEEP_RUNNING); }
+ set { SetOption(OPTION_REMINDERS_KEEP_RUNNING, value); }
+ }
+ private static readonly BoolOption OPTION_REMINDERS_KEEP_RUNNING = new BoolOption("RemindersKeepRunning", true);
+
#endregion
public override void Startup()
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs
index 52504bb..dce9cf5 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/RemindersQuery.cs
@@ -14,15 +14,16 @@ namespace Acacia.Features.SharedFolders
{
private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F));
- private readonly LogContext _context;
+ private readonly FeatureSharedFolders _feature;
private readonly IFolder _folder;
private SearchQuery _queryRoot;
private SearchQuery.Or _queryCustom;
+ private bool _queryCustomModified;
- public RemindersQuery(LogContext context, IStore store)
+ public RemindersQuery(FeatureSharedFolders feature, IStore store)
{
- this._context = context;
- _folder = store.GetSpecialFolder(SpecialFolder.Reminders);
+ this._feature = feature;
+ this._folder = store.GetSpecialFolder(SpecialFolder.Reminders);
}
public bool Open()
@@ -31,10 +32,10 @@ namespace Acacia.Features.SharedFolders
return true;
try
{
- _queryRoot = _folder.SearchCriteria;
+ _queryRoot = FolderQuery;
if (!(_queryRoot is SearchQuery.And))
return false;
- Logger.Instance.Trace(this, "Current query1: {0}", _queryRoot.ToString());
+ Logger.Instance.Debug(this, "Current query:\n{0}", _queryRoot.ToString());
SearchQuery.And root = (SearchQuery.And)_queryRoot;
// TODO: more strict checking of query
@@ -50,7 +51,6 @@ namespace Acacia.Features.SharedFolders
// 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
@@ -63,11 +63,10 @@ namespace Acacia.Features.SharedFolders
);
root.Operands.Add(_queryCustom);
- Logger.Instance.Trace(this, "Modified query: {0}", root.ToString());
+ Logger.Instance.Debug(this, "Modified query:\n{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());
+ FolderQuery = root;
+ Logger.Instance.Debug(this, "Modified query readback:\n{0}", FolderQuery);
}
catch (Exception e)
{
@@ -80,7 +79,7 @@ namespace Acacia.Features.SharedFolders
{
get
{
- return _context.LogContextId;
+ return _feature.LogContextId;
}
}
@@ -91,7 +90,29 @@ namespace Acacia.Features.SharedFolders
public void Commit()
{
- _folder.SearchCriteria = _queryRoot;
+ if (_queryCustomModified)
+ {
+ FolderQuery = _queryRoot;
+ _queryCustomModified = false;
+ }
+ }
+
+ private SearchQuery FolderQuery
+ {
+ get
+ {
+ return _folder.SearchCriteria;
+ }
+ set
+ {
+ if (!_feature.RemindersKeepRunning)
+ _folder.SearchRunning = false;
+
+ _folder.SearchCriteria = value;
+
+ if (!_feature.RemindersKeepRunning)
+ _folder.SearchRunning = true;
+ }
}
public void UpdateReminders(SyncId folderId, bool wantReminders)
@@ -125,6 +146,7 @@ namespace Acacia.Features.SharedFolders
_queryCustom.Operands.Add(new SearchQuery.PropertyContent(
PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, prefix
));
+ _queryCustomModified = true;
}
}
@@ -154,6 +176,7 @@ namespace Acacia.Features.SharedFolders
Logger.Instance.Trace(this, "Unwanted prefix at {0}: {1}", i, prefix);
_queryCustom.Operands.RemoveAt(i);
+ _queryCustomModified = true;
}
else ++i;
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/Kernel32.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/Kernel32.cs
new file mode 100644
index 0000000..a34f22d
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/Kernel32.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native
+{
+ public static class Kernel32
+ {
+ [DllImport("kernel32.dll", SetLastError = false)]
+ public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
+
+ // ZeroMemory is actually a macro
+ public static void ZeroMemory(IntPtr ptr, int length)
+ {
+ for (int i = 0; i < length / 8; i += 8)
+ {
+ Marshal.WriteInt64(ptr, i, 0x00);
+ }
+
+ for (int i = length % 8; i < -1; i--)
+ {
+ Marshal.WriteByte(ptr, length - i, 0x00);
+ }
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
index ee8702b..ab7421f 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
@@ -24,35 +24,40 @@ using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
+ ///
+ /// Simple wrapper type to allow string encoding to work. Returned from SBinary.Unmarshall; this is needed
+ /// as a copy of the data ptr there must be managed.
+ ///
+ public struct SBinaryWrapper
+ {
+ public readonly byte[] Data;
+
+ public SBinaryWrapper(byte[] bytes)
+ {
+ this.Data = bytes;
+ }
+
+ public override string ToString()
+ {
+ return "cb: " + Data.Length.ToString() + " lpb: " + StringUtil.BytesToHex(Data);
+ }
+ }
unsafe public struct SBinary
{
public uint cb;
public byte* ptr;
- public byte[] Unmarshal()
+ public SBinaryWrapper Unmarshal()
{
byte[] result = new byte[cb];
- System.Runtime.InteropServices.Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
- return result;
- }
-
- ///
- /// Returns an instance with the data allocated in the enocder.
- ///
- public SBinary Marshal(NativeEncoder encoder)
- {
- return new SBinary()
- {
- cb = cb,
- ptr = (byte*)encoder.Allocate(Unmarshal())
- };
+ Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
+ return new SBinaryWrapper(result);
}
public override string ToString()
{
- byte[] b = Unmarshal();
- return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
+ return Unmarshal().ToString();
}
}
@@ -60,15 +65,5 @@ namespace Acacia.Native.MAPI
{
public uint count;
public SBinary* ptr;
-
- public byte[][] Unmarshal()
- {
- byte[][] result = new byte[count][];
- for (uint i = 0; i < count; ++i)
- {
- result[i] = ptr[i].Unmarshal();
- }
- return result;
- }
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/MAPI.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/MAPI.cs
new file mode 100644
index 0000000..74a6142
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/MAPI.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+ public static class MAPI
+ {
+ [DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
+ public static extern IntPtr MAPIAllocateBuffer(uint cbSize, ref IntPtr lppBuffer);
+
+ [DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
+ public static extern IntPtr MAPIAllocateMore(uint cbSize, IntPtr lpObject, ref IntPtr lppBuffer);
+
+ [DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
+ public static extern uint MAPIFreeBuffer(IntPtr lpBuffer);
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
index 7a20d48..2e9d401 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
@@ -20,7 +20,6 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-using static Acacia.Native.NativeEncoder;
namespace Acacia.Native.MAPI
{
@@ -48,7 +47,7 @@ namespace Acacia.Native.MAPI
public override string ToString()
{
- return "<" + prop.ToString("X4") + ":" + type + ">";
+ return string.Format("0x{0:X4}{1:X4} (PT_{2})", prop, (int)type, type);
}
public SearchQuery.PropertyIdentifier ToPropertyIdentifier()
@@ -144,33 +143,42 @@ namespace Acacia.Native.MAPI
case PropType.UNICODE:
return new string(data.lpszW);
case PropType.BINARY:
- return data.bin;
+ return data.bin.Unmarshal();
}
throw new NotImplementedException();
}
- unsafe public static IntPtr MarshalFromObject(NativeEncoder encoder, PropTag prop, object value)
+ unsafe public static IntPtr MarshalFromObject(RestrictionEncoder encoder, PropTag prop, object value)
{
- PropValue obj = new PropValue();
- obj.header.ulPropTag = prop;
-
+ IntPtr ptr;
+ Data data = new Data();
switch (prop.type)
{
case PropType.BOOLEAN:
- obj.data.b = (bool)value;
- return encoder.Allocate(obj.header, obj.data.b);
+ data.b = (bool)value;
+ ptr = encoder.AllocateWithExtra(8, data.b);
+ break;
case PropType.STRING8:
- IntPtr ptrA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), new byte[] { 0 });
- return encoder.Allocate(obj.header, 8, ptrA);
+ IntPtr lpszA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), 1);
+ ptr = encoder.AllocateWithExtra(8, lpszA);
+ break;
case PropType.UNICODE:
- IntPtr ptrW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), new byte[] { 0, 0 });
- return encoder.Allocate(obj.header, 8, ptrW);
+ IntPtr lpszW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), 2);
+ ptr = encoder.AllocateWithExtra(8, lpszW);
+ break;
case PropType.BINARY:
- obj.data.bin = ((SBinary)value).Marshal(encoder);
- return encoder.Allocate(obj.header, 8, obj.data.bin);
+ data.bin.cb = (uint)((SBinaryWrapper)value).Data.Length;
+ data.bin.ptr = (byte*)encoder.Allocate(((SBinaryWrapper)value).Data);
+ ptr = encoder.AllocateWithExtra(8, data.bin);
+ break;
default:
throw new NotImplementedException();
}
+
+ // Initialise the header
+ PropValue* obj = (PropValue*)ptr;
+ obj->header.ulPropTag = prop;
+ return ptr;
}
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
index 23c70c3..ea20923 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
@@ -39,20 +39,42 @@ namespace Acacia.Native.MAPI
public PropTag ulPropTag;
public PropValue* prop;
+ private static readonly string[] RELOP_NAMES =
+ {
+ "RELOP_LT", "RELOP_LE", "RELOP_GT", "RELOP_GE", "RELOP_EQ", "RELOP_NE", "RELOP_RE"
+ };
+
public string ToString(int depth)
{
- string indent = new string(' ', depth);
- string s = indent + relop + ":" + ulPropTag.ToString();
- s += ":" + prop->ToString();
- s += "\n";
+ string s = string.Format(
+ "{0}lpRes->res.resProperty.relop = {2} = 0x{1:X8}\n" +
+ "{0}lpRes->res.resProperty.ulPropTag = {3}\n",
+ SRestriction.Indent(depth),
+ (int)relop,
+ RELOP_NAMES[(int)relop],
+ ulPropTag
+ );
+
+ s += string.Format("{0}lpRes->res.resProperty.lpProp->ulPropTag = {1}\n",
+ SRestriction.Indent(depth),
+ ulPropTag
+ );
+
+ s += string.Format("{0}lpRes->res.resProperty.lpProp->Value = {1}\n",
+ SRestriction.Indent(depth),
+ *prop
+ );
+
return s;
}
public SearchQuery ToSearchQuery()
{
+ object value = prop->ToObject();
return new SearchQuery.PropertyCompare(ulPropTag.ToPropertyIdentifier(),
(SearchQuery.ComparisonOperation)(int)relop,
- prop->ToObject());
+ value
+ );
}
}
@@ -76,10 +98,24 @@ namespace Acacia.Native.MAPI
public string ToString(int depth)
{
- string indent = new string(' ', depth);
- string s = indent + ulFuzzyLevel + ":" + ulPropTag.ToString();
- s += ":" + prop->ToString();
- s += "\n";
+ string s = string.Format(
+ "{0}lpRes->res.resContent.ulFuzzyLevel = FL_{2} = 0x{1:X8}\n" +
+ "{0}lpRes->res.resContent.ulPropTag = {3}\n",
+ SRestriction.Indent(depth),
+ (int)ulFuzzyLevel,
+ ulFuzzyLevel,
+ ulPropTag
+ );
+
+ s += string.Format("{0}lpRes->res.resContent.lpProp->ulPropTag = {1}\n",
+ SRestriction.Indent(depth),
+ ulPropTag
+ );
+
+ s += string.Format("{0}lpRes->res.resContent.lpProp->Value = {1}\n",
+ SRestriction.Indent(depth),
+ *prop
+ );
return s;
}
@@ -120,19 +156,20 @@ namespace Acacia.Native.MAPI
public uint cb;
public SRestriction* ptr;
- public string ToString(int depth)
+ public string ToString(string name, int depth)
{
- string s = "";
+ string s = string.Format("{0}lpRes->res.res{1}.cRes = 0x{2:X8}\n", SRestriction.Indent(depth), name, cb);
for (uint i = 0; i < cb; ++i)
{
- s += ptr[i].ToString(depth);
+ s += string.Format("{0}lpRes->res.res{1}.lpRes[0x{2:X8}]\n", SRestriction.Indent(depth), name, i);
+ s += ptr[i].ToString(depth + 1);
}
return s;
}
public SearchQuery ToSearchQuery(bool and)
{
- SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or(); ;
+ SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or();
for (uint i = 0; i < cb; ++i)
{
oper.Add(ptr[i].ToSearchQuery());
@@ -148,7 +185,10 @@ namespace Acacia.Native.MAPI
public string ToString(int depth)
{
- return ptr->ToString(depth);
+ string s = string.Format("{0}lpRes->res.resNot.ulReserved = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved);
+ s += string.Format("{0}lpRes->res.resNot.lpRes\n", SRestriction.Indent(depth));
+ s += ptr->ToString(depth + 1);
+ return s;
}
public SearchQuery ToSearchQuery()
@@ -175,8 +215,10 @@ namespace Acacia.Native.MAPI
}
public string ToString(int depth)
{
- string indent = new string(' ', depth);
- return indent + ToString() + "\n";
+ string s = string.Format("{0}lpRes->res.resBitMask.relBMR = BMR_{1} = 0x{2:X8}\n", SRestriction.Indent(depth), bmr, (int)bmr);
+ s += string.Format("{0}lpRes->res.resBitMask.ulMask = 0x{1:X8}\n", SRestriction.Indent(depth), mask);
+ s += string.Format("{0}lpRes->res.resBitMask.ulPropTag = 0x{1:X8}\n", SRestriction.Indent(depth), prop);
+ return s;
}
public SearchQuery ToSearchQuery()
@@ -197,8 +239,10 @@ namespace Acacia.Native.MAPI
}
public string ToString(int depth)
{
- string indent = new string(' ', depth);
- return indent + prop.ToString() + "\n";
+ string s = string.Format("{0}lpRes->res.resExist.ulPropTag = 0x{1:X8}\n", SRestriction.Indent(depth), prop);
+ s += string.Format("{0}lpRes->res.resExist.ulReserved1 = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved1);
+ s += string.Format("{0}lpRes->res.resExist.ulReserved2 = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved2);
+ return s;
}
public SearchQuery ToSearchQuery()
@@ -281,30 +325,34 @@ namespace Acacia.Native.MAPI
return ToString(0);
}
+ internal static string Indent(int depth)
+ {
+ return new string('\t', depth);
+ }
+
public string ToString(int depth)
{
- string indent = new string(' ', depth);
- string s = indent + header.rt.ToString() + "\n" + indent + "{\n";
+ string s = Indent(depth) + string.Format("lpRes->rt = 0x{0:X} = RES_{1}\n", (int)header.rt, header.rt);
switch (header.rt)
{
case RestrictionType.AND:
case RestrictionType.OR:
- s += data.sub.ToString(depth + 1);
+ s += data.sub.ToString(header.rt.ToString().ToTitle(), depth);
break;
case RestrictionType.NOT:
- s += data.not.ToString(depth + 1);
+ s += data.not.ToString(depth);
break;
case RestrictionType.CONTENT:
- s += data.content.ToString(depth + 1);
+ s += data.content.ToString(depth);
break;
case RestrictionType.PROPERTY:
- s += data.prop.ToString(depth + 1);
+ s += data.prop.ToString(depth);
break;
case RestrictionType.BITMASK:
- s += data.bitMask.ToString(depth + 1);
+ s += data.bitMask.ToString(depth);
break;
case RestrictionType.EXIST:
- s += data.exist.ToString(depth + 1);
+ s += data.exist.ToString(depth);
break;
/* TODO COMPAREPROPS,
@@ -316,158 +364,7 @@ namespace Acacia.Native.MAPI
ANNOTATION*/
}
- s += indent + "}\n";
return s;
}
}
-
- ///
- /// Encodes a search as an SRestriction. Note that as memory needs to be managed for the miscellaneous structures,
- /// the SRestriction is only valid until RestrictionEncoder is disposed.
- ///
- unsafe public class RestrictionEncoder : NativeEncoder, ISearchEncoder
- {
- private class EncodingStack
- {
- public SRestriction[] array;
- public int index;
- public SRestriction* ptr;
-
- public EncodingStack(int count, Allocation alloc)
- {
- array = alloc.Object;
- index = 0;
- ptr = (SRestriction*)alloc.Pointer;
- }
- }
- private readonly Stack _current = new Stack();
- private readonly EncodingStack _root;
-
- public RestrictionEncoder()
- {
- // Create an object for the root element
- _root = Begin(1);
- }
-
- protected override void DoRelease()
- {
- base.DoRelease();
- }
-
- public SRestriction Restriction
- {
- get { return _root.array[0]; }
- }
-
- private SRestriction* Current
- {
- get
- {
- EncodingStack top = _current.Peek();
- return top.ptr + top.index;
- }
- }
-
- public void Encode(SearchQuery.PropertyExists part)
- {
- Current->header.rt = RestrictionType.EXIST;
- Current->data.exist.prop = part.Property.Tag;
- }
-
- public void Encode(SearchQuery.Or part)
- {
- Current->header.rt = RestrictionType.OR;
- Current->data.sub.cb = (uint)part.Operands.Count;
- Current->data.sub.ptr = EncodePointer(part.Operands);
- }
-
- public void Encode(SearchQuery.PropertyIdentifier part)
- {
- // This should be unreachable
- throw new InvalidProgramException();
- }
-
- public void Encode(SearchQuery.Not part)
- {
- Current->header.rt = RestrictionType.NOT;
- Current->data.not.ptr = EncodePointer(new[] { part.Operand });
- }
-
- public void Encode(SearchQuery.And part)
- {
- Current->header.rt = RestrictionType.AND;
- Current->data.sub.cb = (uint)part.Operands.Count;
- Current->data.sub.ptr = EncodePointer(part.Operands);
- }
-
- private SRestriction* EncodePointer(IEnumerable operands)
- {
- EncodingStack alloc = Begin(operands.Count());
- try
- {
- foreach (SearchQuery operand in operands)
- {
- operand.Encode(this);
- ++alloc.index;
- }
- }
- finally
- {
- End();
- }
- return alloc.ptr;
- }
-
- private EncodingStack Begin(int count)
- {
- // Allocate and push the array
- EncodingStack alloc = new EncodingStack(count, Allocate(new SRestriction[count]));
- _current.Push(alloc);
-
- return alloc;
- }
-
- private void End()
- {
- _current.Pop();
- }
-
- public void Encode(SearchQuery.PropertyContent part)
- {
- Current->header.rt = RestrictionType.CONTENT;
- Current->data.content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part);
- Current->data.content.ulPropTag = part.Property.Tag;
- Current->data.content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content);
- }
-
- public void Encode(SearchQuery.PropertyCompare part)
- {
- Current->header.rt = RestrictionType.PROPERTY;
- Current->data.prop.relop = (SearchOperation)part.Operation;
- Current->data.prop.ulPropTag = part.Property.Tag;
- Current->data.prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value);
- }
-
- public void Encode(SearchQuery.PropertyBitMask part)
- {
- Current->header.rt = RestrictionType.BITMASK;
- Current->data.bitMask.bmr = (BMR)(int)part.Operation;
- Current->data.bitMask.prop = part.Property.Tag;
- Current->data.bitMask.mask = part.Mask;
- }
- }
-
- public static class RestrictionExensions
- {
- ///
- /// Encodes the search as an SRestriction.
- ///
- /// The encoder containing the restriction. The caller is responsible for disposing.
- public static RestrictionEncoder ToRestriction(this SearchQuery search)
- {
- RestrictionEncoder encoder = new RestrictionEncoder();
- search.Encode(encoder);
- return encoder;
- }
- }
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/RestrictionEncoder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/RestrictionEncoder.cs
new file mode 100644
index 0000000..ea0b952
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/RestrictionEncoder.cs
@@ -0,0 +1,249 @@
+using Acacia.Stubs;
+using Acacia.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+
+ ///
+ /// Encodes a search as an SRestriction. Note that as memory needs to be managed for the miscellaneous structures,
+ /// the SRestriction is only valid until RestrictionEncoder is disposed.
+ ///
+ unsafe public class RestrictionEncoder : DisposableWrapper, ISearchEncoder
+ {
+ private class Allocation
+ {
+ public readonly IntPtr ptr;
+ private readonly int count;
+ private int index;
+
+ public SRestriction* Current
+ {
+ get
+ {
+ if (index >= count)
+ throw new InvalidProgramException();
+ return Pointer + index;
+ }
+ }
+ public SRestriction* Pointer { get { return (SRestriction*)ptr; } }
+
+ ///
+ /// Constructor. Allocates the memory for the object.
+ ///
+ /// The root allocation, or null if this is the root. All allocations will be added to the root.
+ /// The number of SRestriction objects to allocate
+ public Allocation(RestrictionEncoder encoder, int count)
+ {
+ this.count = count;
+ // Allocate the buffer
+ ptr = encoder.AllocateArray(count);
+ }
+
+ public void Next()
+ {
+ ++index;
+ }
+ }
+ private Allocation _root;
+ private readonly Stack _stack = new Stack();
+
+ public RestrictionEncoder()
+ {
+ // Push the root entry
+ _root = Push(1);
+ }
+
+ protected override void DoRelease()
+ {
+ if (_root != null)
+ {
+ uint res = MAPI.MAPIFreeBuffer(_root.ptr);
+ if (res != 0)
+ {
+ // TODO: log?
+ }
+ _root = null;
+ }
+ }
+
+ public SRestriction* Encoded
+ {
+ get { return _root.Pointer; }
+ }
+
+ public SRestriction* Current
+ {
+ get { return _stack.Peek().Current; }
+ }
+
+ private Allocation Push(int count = 1)
+ {
+ Allocation alloc = new Allocation(this, count);
+ _stack.Push(alloc);
+ return alloc;
+ }
+
+ private void Pop(Allocation expected)
+ {
+ Allocation alloc = _stack.Pop();
+ if (expected != alloc)
+ throw new InvalidProgramException();
+ }
+
+ public void Encode(SearchQuery.PropertyExists part)
+ {
+ Current->header.rt = RestrictionType.EXIST;
+ Current->data.exist.prop = part.Property.Tag;
+ }
+
+ public void Encode(SearchQuery.Or part)
+ {
+ Current->header.rt = RestrictionType.OR;
+ Current->data.sub.cb = (uint)part.Operands.Count;
+ Current->data.sub.ptr = EncodePointer(part.Operands);
+ }
+
+ public void Encode(SearchQuery.And part)
+ {
+ Current->header.rt = RestrictionType.AND;
+ Current->data.sub.cb = (uint)part.Operands.Count;
+ Current->data.sub.ptr = EncodePointer(part.Operands);
+ }
+
+ public void Encode(SearchQuery.PropertyIdentifier part)
+ {
+ // This should be unreachable
+ throw new InvalidProgramException();
+ }
+
+ public void Encode(SearchQuery.Not part)
+ {
+ Current->header.rt = RestrictionType.NOT;
+ Current->data.not.ptr = EncodePointer(new[] { part.Operand });
+ }
+
+ private SRestriction* EncodePointer(IEnumerable operands)
+ {
+ Allocation alloc = Push(operands.Count());
+ try
+ {
+ foreach (SearchQuery operand in operands)
+ {
+ operand.Encode(this);
+ alloc.Next();
+ }
+ }
+ finally
+ {
+ Pop(alloc);
+ }
+ return alloc.Pointer;
+ }
+
+ public void Encode(SearchQuery.PropertyContent part)
+ {
+ Current->header.rt = RestrictionType.CONTENT;
+ Current->data.content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part);
+ Current->data.content.ulPropTag = part.Property.Tag;
+ Current->data.content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content);
+ }
+
+ public void Encode(SearchQuery.PropertyCompare part)
+ {
+ Current->header.rt = RestrictionType.PROPERTY;
+ Current->data.prop.relop = (SearchOperation)part.Operation;
+ Current->data.prop.ulPropTag = part.Property.Tag;
+ Current->data.prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value);
+ }
+
+ public void Encode(SearchQuery.PropertyBitMask part)
+ {
+ Current->header.rt = RestrictionType.BITMASK;
+ Current->data.bitMask.bmr = (BMR)(int)part.Operation;
+ Current->data.bitMask.prop = part.Property.Tag;
+ Current->data.bitMask.mask = part.Mask;
+ }
+
+ public IntPtr AllocateArray(int count)
+ {
+ // Try to just determine the size based on the type. If that fails, determine the size of a default object
+ int structSize = Marshal.SizeOf(typeof(StructType));
+ return AllocateRaw(structSize * count);
+ }
+
+ public IntPtr AllocateWithExtra(int alignExtra, object extra)
+ {
+ // Determine the size
+ int size = Marshal.SizeOf();
+ size = Util.Align(size, alignExtra);
+
+ int extraOffset = size;
+ size += Marshal.SizeOf(extra);
+
+ // Allocate
+ IntPtr ptr = AllocateRaw(size);
+
+ // Copy the extra structure
+ Marshal.StructureToPtr(extra, ptr + extraOffset, false);
+ return ptr;
+ }
+
+ private IntPtr AllocateRaw(int size)
+ {
+ IntPtr res;
+ IntPtr ptr = IntPtr.Zero;
+ if (_root == null)
+ {
+ res = MAPI.MAPIAllocateBuffer((uint)size, ref ptr);
+ }
+ else
+ {
+ res = MAPI.MAPIAllocateMore((uint)size, _root.ptr, ref ptr);
+ }
+
+ if (res != IntPtr.Zero)
+ throw new InvalidOperationException("MAPI Allocation failed: " + res);
+
+ // Zero it out to prevent issues
+ Kernel32.ZeroMemory(ptr, size);
+
+ return ptr;
+ }
+
+ ///
+ /// Allocates a copy of the byte array.
+ ///
+ /// The byte array
+ /// Number of additional zero bytes, for padding
+ public IntPtr Allocate(byte[] bytes, int zeros = 0)
+ {
+ IntPtr ptr = AllocateRaw(bytes.Length + zeros);
+ Marshal.Copy(bytes, 0, ptr, bytes.Length);
+ for (int i = 0; i < zeros; ++i)
+ {
+ ((byte*)ptr)[bytes.Length + i] = 0;
+ }
+ return ptr;
+ }
+ }
+
+ public static class RestrictionExensions
+ {
+ ///
+ /// Encodes the search as an SRestriction.
+ ///
+ /// The encoder containing the restriction. The caller is responsible for disposing.
+ public static RestrictionEncoder ToRestriction(this SearchQuery search)
+ {
+ RestrictionEncoder encoder = new RestrictionEncoder();
+ search.Encode(encoder);
+ return encoder;
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs
deleted file mode 100644
index 1fe0ce5..0000000
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/NativeEncoder.cs
+++ /dev/null
@@ -1,175 +0,0 @@
-using Acacia.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using Acacia.Native.MAPI;
-
-namespace Acacia.Native
-{
- ///
- /// Helper for encoding objects into native structures.
- ///
- abstract public class NativeEncoder : DisposableWrapper
- {
- protected class AllocationBase : IDisposable
- {
- protected readonly object _obj;
- protected readonly GCHandle _handle;
- protected readonly IntPtr _ptr;
-
- public AllocationBase(Type type, int size)
- {
- _ptr = Marshal.AllocHGlobal(size);
- }
-
- public AllocationBase(object obj)
- {
- this._obj = obj;
- _handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
- _ptr = _handle.AddrOfPinnedObject();
- }
-
- public IntPtr Pointer { get { return _ptr; } }
-
- public void Dispose()
- {
- if (_handle.IsAllocated)
- _handle.Free();
- else
- Marshal.FreeHGlobal(_ptr);
- }
- }
-
- unsafe protected class Allocation : AllocationBase
- {
- internal Allocation(int size) : base(typeof(ObjType), size)
- {
- }
-
- internal Allocation(ObjType obj) : base(obj)
- {
- }
-
- public ObjType Object
- {
- get
- {
- return (ObjType)_obj;
- }
- }
- }
-
- private readonly List _allocs = new List();
-
- override protected void DoRelease()
- {
- foreach(AllocationBase alloc in _allocs)
- alloc.Dispose();
- }
-
- protected AllocationBase Allocate(int size)
- {
- AllocationBase alloc = new AllocationBase(typeof(object), size);
- _allocs.Add(alloc);
- return alloc;
- }
-
- protected Allocation Allocate(ObjType obj)
- {
- Allocation alloc = new Allocation(obj);
- _allocs.Add(alloc);
- return alloc;
- }
-
- protected Allocation Allocate()
- {
- return Allocate(Activator.CreateInstance());
- }
-
- // TODO: put in lib
- [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
- private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
-
- public IntPtr Allocate(ElementType[] obj, params ElementType[][] additional)
- {
- ElementType[][] all = new ElementType[][] { obj }.Concat(additional).ToArray();
-
- int size = 0;
- int[] starts = new int[all.Length];
- int[] sizes = new int[all.Length];
- for (int i = 0; i < all.Length; ++i)
- {
- starts[i] = size;
- int thisSize = ((Array)all[i]).Length * Marshal.SizeOf();
- sizes[i] = thisSize;
- size += thisSize;
- }
-
- AllocationBase alloc = Allocate(size);
- IntPtr ptr = alloc.Pointer;
- for (int i = 0; i < all.Length; ++i)
- {
- GCHandle handle = GCHandle.Alloc(all[i], GCHandleType.Pinned);
- try
- {
- CopyMemory(ptr + starts[i], handle.AddrOfPinnedObject(), (uint)sizes[i]);
- }
- finally
- {
- handle.Free();
- }
- }
- return alloc.Pointer;
- }
-
- ///
- /// Returns a block of memory containing all specified objects sequentially.
- ///
- public IntPtr Allocate(object obj, params object[] additional)
- {
- object[] all = new object[] { obj }.Concat(additional).ToArray();
-
- int size = 0;
- int[] starts = new int[all.Length];
- object[] encode = new object[all.Length];
- int used = 0;
- int align = -1;
- for (int i = 0; i < all.Length; ++i)
- {
- if (all[i] is int)
- {
- align = (int)all[i];
- ++i;
- }
-
- starts[used] = Align(size, align);
- int thisSize = Marshal.SizeOf(all[i]);
- size = starts[used] + thisSize;
- encode[used] = all[i];
- align = -1;
- ++used;
- }
-
- AllocationBase alloc = Allocate(size);
- IntPtr ptr = alloc.Pointer;
- for (int i = 0; i < used; ++i)
- {
- Marshal.StructureToPtr(encode[i], ptr + starts[i], false);
- }
- return alloc.Pointer;
- }
-
-
-
- private int Align(int size, int align)
- {
- if (align < 0)
- align = Marshal.SizeOf();
- int additional = (align - (size % align)) % align;
- return size + additional;
- }
- }
-}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
index 911f969..e79b7ff 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
@@ -121,6 +121,12 @@ namespace Acacia.Stubs
set;
}
+ bool SearchRunning
+ {
+ get;
+ set;
+ }
+
void Save();
void SetCustomIcon(IPicture icon);
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
index af92cbd..f7b3e6a 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
@@ -392,7 +392,56 @@ namespace Acacia.Stubs.OutlookWrappers
set;
}
+ #region Search criteria
+
unsafe public SearchQuery SearchCriteria
+ {
+ get
+ {
+ IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
+ SBinaryArray* sb1 = null;
+ SRestriction* restrict = null;
+ try
+ {
+ SearchCriteriaState state;
+ imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
+ Logger.Instance.Debug(this, "GetSearchCriteria: {0}: {1}\n{2}", Name, state,
+ restrict == null ? "" : restrict->ToString());
+ return restrict->ToSearchQuery();
+ }
+ finally
+ {
+ MAPI.MAPIFreeBuffer((IntPtr)restrict);
+ MAPI.MAPIFreeBuffer((IntPtr)sb1);
+ ComRelease.Release(imapi);
+ }
+ }
+
+ set
+ {
+ IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
+ try
+ {
+ using (RestrictionEncoder res = value.ToRestriction())
+ {
+ SRestriction* resEncoded = res.Encoded;
+ Logger.Instance.Debug(this, "SetSearchCriteria: {0}\n{1}", Name, resEncoded == null ? "" : resEncoded->ToString());
+ imapi.SetSearchCriteria(resEncoded, null, SearchCriteriaFlags.NONE);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Instance.Error(this, "Exception in SetSearchCriteria: {0}: {1}", Name, e);
+ }
+ finally
+ {
+ ComRelease.Release(imapi);
+ }
+ }
+
+ }
+
+ unsafe public bool SearchRunning
{
get
{
@@ -400,10 +449,13 @@ namespace Acacia.Stubs.OutlookWrappers
try
{
SearchCriteriaState state;
- SBinaryArray* sb1;
- SRestriction* restrict;
- imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
- return restrict->ToSearchQuery();
+ imapi.GetSearchCriteria(0, null, null, out state);
+ return (state & SearchCriteriaState.SEARCH_RUNNING) != 0;
+ }
+ catch (Exception e)
+ {
+ Logger.Instance.Error(this, "Exception in GetSearchRunning: {0}: {1}", Name, e);
+ return true;
}
finally
{
@@ -416,11 +468,11 @@ namespace Acacia.Stubs.OutlookWrappers
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
try
{
- using (RestrictionEncoder res = value.ToRestriction())
- {
- SRestriction restrict = res.Restriction;
- imapi.SetSearchCriteria(&restrict, null, SearchCriteriaFlags.NONE);
- }
+ imapi.SetSearchCriteria(null, null, value ? SearchCriteriaFlags.RESTART_SEARCH : SearchCriteriaFlags.STOP_SEARCH);
+ }
+ catch (Exception e)
+ {
+ Logger.Instance.Error(this, "Exception in SetSearchRunning: {0}: {1}", Name, e);
}
finally
{
@@ -428,5 +480,7 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
}
+
+ #endregion
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/ImageUtils.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/ImageUtils.cs
index 80fdfd5..0b595b0 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/ImageUtils.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/ImageUtils.cs
@@ -1,4 +1,6 @@
-/// Copyright 2017 Kopano b.v.
+
+using Acacia.Native;
+/// Copyright 2017 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,
@@ -13,7 +15,6 @@
/// along with this program.If not, see.
///
/// Consult LICENSE file for details
-
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -27,9 +28,6 @@ namespace Acacia.Utils
{
public static class ImageUtils
{
- [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
- private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
-
public static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
@@ -49,7 +47,7 @@ namespace Acacia.Utils
{
IntPtr target = bmpData2.Scan0 + bmpData2.Stride * y;
IntPtr source = bmpData.Scan0 + bmpData.Stride * y;
- CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
+ Kernel32.CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
}
}
finally
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
index aadf931..be87a90 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
@@ -49,6 +49,13 @@ namespace Acacia.Utils
return _this;
}
+ public static string ToTitle(this string _this)
+ {
+ if (string.IsNullOrWhiteSpace(_this))
+ return _this;
+ return _this.Substring(0, 1).ToUpper() + _this.Substring(1).ToLower();
+ }
+
#endregion
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs
index 84b17c1..27bb50c 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs
@@ -173,5 +173,11 @@ namespace Acacia.Utils
}
#endregion
+
+ public static int Align(int size, int align)
+ {
+ int additional = (align - (size % align)) % align;
+ return size + additional;
+ }
}
}