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; + } } }