From f5ea05189cc150da95603f30eab31a5df294da68 Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 22 Feb 2017 10:20:20 +0100
Subject: [PATCH 1/3] Partial implementation of raw MAPI access.
---
.../AcaciaZPushPlugin.csproj | 8 +-
.../AcaciaZPushPlugin/Native/MAPI.cs | 486 ------------------
.../AcaciaZPushPlugin/Native/MAPI/Binary.cs | 62 +++
.../Native/MAPI/IMAPIContainer.cs | 74 +++
.../Native/MAPI/IMAPIFolder.cs | 64 +++
.../Native/MAPI/IMAPIProp.cs | 53 ++
.../AcaciaZPushPlugin/Native/MAPI/Property.cs | 135 +++++
.../Native/MAPI/Restriction.cs | 331 ++++++++++++
.../AcaciaZPushPlugin/SearchQuery.cs | 278 ++++++++++
.../AcaciaZPushPlugin/Stubs/Enums.cs | 7 +
.../AcaciaZPushPlugin/Stubs/IFolder.cs | 6 +
.../AcaciaZPushPlugin/Stubs/ISearch.cs | 8 +-
.../AcaciaZPushPlugin/Stubs/IStore.cs | 6 +
.../Stubs/OutlookWrappers/FolderWrapper.cs | 27 +
.../Stubs/OutlookWrappers/StoreWrapper.cs | 12 +
15 files changed, 1068 insertions(+), 489 deletions(-)
delete mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIContainer.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIFolder.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIProp.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index 8b97a68..e80e9ea 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -276,9 +276,15 @@
-
+
+
+
+
+
+
+
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
deleted file mode 100644
index 15392a5..0000000
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI.cs
+++ /dev/null
@@ -1,486 +0,0 @@
-/// 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,
-/// as published by the Free Software Foundation.
-///
-/// This program is distributed in the hope that it will be useful,
-/// but WITHOUT ANY WARRANTY; without even the implied warranty of
-/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-/// GNU Affero General Public License for more details.
-///
-/// You should have received a copy of the GNU Affero General Public License
-/// along with this program.If not, see.
-///
-/// Consult LICENSE file for details
-
-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
-{
- [Flags]
- public enum SaveChangesFlags : UInt32
- {
- NONE = 0,
- KEEP_OPEN_READONLY = 1,
- KEEP_OPEN_READWRITE = 2,
- FORCE_SAVE = 4,
- MAPI_DEFERRED_ERRORS = 8
- }
-
- [ComImport]
- [Guid("00020303-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMAPIProp
- {
- void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
- void SaveChanges(SaveChangesFlags flags);
- void GetProps();
- void GetPropList();
- void OpenProperty();
- void SetProps();
- void DeleteProps();
- void CopyTo();
- void CopyProps();
- void GetNamesFromIDs();
- void GetIDsFromNames();
- }
-
- unsafe public struct SBinary
- {
- public uint cb;
- public byte* ptr;
-
- public byte[] Unmarshal()
- {
- byte[] result = new byte[cb];
- Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
- return result;
- }
-
- public override string ToString()
- {
- byte[] b = Unmarshal();
- return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
- }
- }
-
- unsafe public struct SBinaryArray
- {
- 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;
- }
- }
-
- public enum PropType : ushort
- {
- BOOLEAN = 0x000B,
- BINARY = 0x0102,
- MV_BINARY = 1102,
- DOUBLE = 0x0005,
- LONG = 0x0003,
- OBJECT = 0x000D,
- STRING8 = 0x001E,
- MV_STRING8 = 0x101E,
- SYSTIME = 0x0040,
- UNICODE = 0x001F,
- MV_UNICODE = 0x101f
- }
-
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct PropTag
- {
- public PropType type;
- public ushort prop;
-
- public override string ToString()
- {
- return "<" + prop.ToString("X4") + ":" + type + ">";
- }
- }
-
-
-
- [StructLayout(LayoutKind.Explicit)]
- unsafe public struct PropValue
- {
- [FieldOffset(0)]
- public PropTag ulPropTag;
-
- [FieldOffset(4)]
- public uint dwAlignPad;
-
- // short int i; /* case PT_I2 */
- // LONG l; /* case PT_LONG */
- // ULONG ul; /* alias for PT_LONG */
- // LPVOID lpv; /* alias for PT_PTR */
- // float flt; /* case PT_R4 */
- // double dbl; /* case PT_DOUBLE */
- // unsigned short int b; /* case PT_BOOLEAN */
- [FieldOffset(8), MarshalAs(UnmanagedType.U2)]
- public bool b;
-
- // CURRENCY cur; /* case PT_CURRENCY */
- // double at; /* case PT_APPTIME */
- // FILETIME ft; /* case PT_SYSTIME */
-
- // LPSTR lpszA; /* case PT_STRING8 */
- [FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
- public sbyte* lpszA;
-
- // SBinary bin; /* case PT_BINARY */
- [FieldOffset(8)]
- public SBinary bin;
-
- // LPWSTR lpszW; /* case PT_UNICODE */
- [FieldOffset(8), MarshalAs(UnmanagedType.LPWStr)]
- public char* lpszW;
-
- // LPGUID lpguid; /* case PT_CLSID */
- // LARGE_INTEGER li; /* case PT_I8 */
- // SShortArray MVi; /* case PT_MV_I2 */
- // SLongArray MVl; /* case PT_MV_LONG */
- // SRealArray MVflt; /* case PT_MV_R4 */
- // SDoubleArray MVdbl; /* case PT_MV_DOUBLE */
- // SCurrencyArray MVcur; /* case PT_MV_CURRENCY */
- // SAppTimeArray MVat; /* case PT_MV_APPTIME */
- // SDateTimeArray MVft; /* case PT_MV_SYSTIME */
- // SBinaryArray MVbin; /* case PT_MV_BINARY */
- // SLPSTRArray MVszA; /* case PT_MV_STRING8 */
- // SWStringArray MVszW; /* case PT_MV_UNICODE */
-
- // SGuidArray MVguid; /* case PT_MV_CLSID */
- // SLargeIntegerArray MVli; /* case PT_MV_I8 */
- // SCODE err; /* case PT_ERROR */
- // LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */
-
- public override string ToString()
- {
- switch(ulPropTag.type)
- {
- case PropType.BOOLEAN:
- return b.ToString();
- case PropType.STRING8:
- return new string(lpszA);
- case PropType.BINARY:
- return bin.ToString();
- //case PropType.UNICODE:
- // return lpszW.ToString();
- }
- return "";
- }
- }
-
- unsafe public struct CommentRestriction
- {
- public uint cValues;
- public SRestriction* res;
- public PropValue* prop;
- }
-
- unsafe public struct PropertyRestriction
- {
- public Acacia.Stubs.SearchOperation relop;
- public PropTag ulPropTag;
- public PropValue* prop;
-
- public string ToString(int depth)
- {
- string indent = new string(' ', depth);
- string s = indent + relop + ":" + ulPropTag.ToString();
- s += ":" + prop->ToString();
- s += "\n";
- return s;
- }
- }
-
- [Flags]
- public enum FuzzyLevel : uint
- {
- FULLSTRING = 0,
- SUBSTRING = 1,
- PREFIX = 2,
-
- IGNORECASE = 0x00010000,
- IGNORENONSPACE = 0x00020000,
- LOOSE = 0x00040000
- }
-
- unsafe public struct ContentRestriction
- {
- public FuzzyLevel fuzzy;
- public PropTag ulPropTag;
- public PropValue* prop;
-
- public string ToString(int depth)
- {
- string indent = new string(' ', depth);
- string s = indent + fuzzy + ":" + ulPropTag.ToString();
- s += ":" + prop->ToString();
- s += "\n";
- return s;
- }
- }
-
- // TODO: merge with ISearch
- public enum RestrictionType : UInt32
- {
- AND,
- OR,
- NOT,
- CONTENT,
- PROPERTY,
- COMPAREPROPS,
- BITMASK,
- SIZE,
- EXIST,
- SUBRESTRICTION,
- COMMENT,
- COUNT,
- ANNOTATION
- }
-
- unsafe public struct SubRestriction
- {
- public uint cb;
- public SRestriction* ptr;
-
- public string ToString(int depth)
- {
- string s = "";
- for (uint i = 0; i < cb; ++i)
- {
- s += ptr[i].ToString(depth);
- }
- return s;
- }
- }
-
- unsafe public struct NotRestriction
- {
- public uint dwReserved;
- public SRestriction* ptr;
-
- public string ToString(int depth)
- {
- return ptr->ToString(depth);
- }
- }
-
- public enum BMR : uint
- {
- EQZ = 0,
- NEZ = 1
- }
-
- unsafe public struct BitMaskRestriction
- {
- public BMR bmr;
- public PropTag prop;
- public uint mask;
-
- override public string ToString()
- {
- return bmr.ToString() + ":" + prop + mask.ToString("X8");
- }
- public string ToString(int depth)
- {
- string indent = new string(' ', depth);
- return indent + ToString() + "\n";
- }
- }
-
- unsafe public struct ExistRestriction
- {
- public uint dwReserved1;
- public PropTag prop;
- public uint dwReserved2;
-
- override public string ToString()
- {
- return prop.ToString();
- }
- public string ToString(int depth)
- {
- string indent = new string(' ', depth);
- return indent + prop.ToString() + "\n";
- }
- }
-
- [StructLayout(LayoutKind.Explicit)]
- unsafe public struct SRestriction
- {
- [FieldOffset(0)]
- public RestrictionType rt;
-
- [FieldOffset(8)]
- public SubRestriction sub;
-
- [FieldOffset(8)]
- public NotRestriction not;
-
- [FieldOffset(8)]
- public ContentRestriction content;
-
- [FieldOffset(8)]
- public PropertyRestriction prop;
-
- [FieldOffset(8)]
- public BitMaskRestriction bitMask;
-
- [FieldOffset(8)]
- public ExistRestriction exist;
-
- [FieldOffset(8)]
- public CommentRestriction comment;
-
- public override string ToString()
- {
- return ToString(0);
- }
-
- public string ToString(int depth)
- {
- string indent = new string(' ', depth);
- string s = indent + rt.ToString() + "\n" + indent + "{\n";
- switch(rt)
- {
- case RestrictionType.AND:
- case RestrictionType.OR:
- s += sub.ToString(depth + 1);
- break;
- case RestrictionType.NOT:
- s += not.ToString(depth + 1);
- break;
- case RestrictionType.CONTENT:
- s += content.ToString(depth + 1);
- break;
- case RestrictionType.PROPERTY:
- s += prop.ToString(depth + 1);
- break;
- case RestrictionType.BITMASK:
- s += bitMask.ToString(depth + 1);
- break;
- case RestrictionType.EXIST:
- s += exist.ToString(depth + 1);
- break;
-
- /* TODO COMPAREPROPS,
- BITMASK,
- SIZE,
- SUBRESTRICTION,
- COMMENT,
- COUNT,
- ANNOTATION*/
-
- }
- s += indent + "}\n";
- return s;
- }
- }
-
- [Flags]
- public enum GetSearchCriteriaState : UInt32
- {
- NONE = 0,
- SEARCH_RUNNING = 1,
- SEARCH_REBUILD = 2,
- SEARCH_RECURSIVE = 4,
- SEARCH_FOREGROUND = 8
- }
-
- [Flags]
- public enum SetSearchCriteriaFlags : UInt32
- {
- NONE = 0,
- STOP_SEARCH = 0x00000001,
- RESTART_SEARCH = 0x00000002,
- RECURSIVE_SEARCH = 0x00000004,
- SHALLOW_SEARCH = 0x00000008,
- FOREGROUND_SEARCH = 0x00000010,
- BACKGROUND_SEARCH = 0x00000020,
- }
-
- [ComImport]
- [Guid("0002030B-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- unsafe public interface IMAPIContainer// TODO : IMAPIProp
- {
- // IMAPIProp
- void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
- void SaveChanges(SaveChangesFlags flags);
- void GetProps();
- void GetPropList();
- void OpenProperty();
- void SetProps();
- void DeleteProps();
- void CopyTo();
- void CopyProps();
- void GetNamesFromIDs();
- void GetIDsFromNames();
-
- void GetContentsTable(UInt32 flags, out IntPtr table);
- void GetHierarchyTable();
- void OpenEntry();
- void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SetSearchCriteriaFlags flags);
- void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out GetSearchCriteriaState state);
- }
-
- [ComImport]
- [Guid("0002030C-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMAPIFolder : IMAPIContainer
- {
- void CreateMessage();
- void CopyMessages();
- void DeleteMessages();
- void CreateFolder();
- void CopyFolder();
- void DeleteFolder();
- void SetReadFlags();
- void GetMessageStatus();
- void SetMessageStatus();
- void SaveContentsSort();
- void EmptyFolder();
- }
-
- /* Example search code
- {
- MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders);
- dynamic obj = folder.MAPIOBJECT;
- IMAPIFolder imapi = obj as IMAPIFolder;
-
- //imapi.GetSearchCriteria(0, IntPtr.Zero, IntPtr.Zero, ref state);
- GetSearchCriteriaState state;
- //imapi.GetContentsTable(0, out p);
- SBinaryArray* sb1;
- SRestriction* restrict;
- imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
- Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString());
-
- restrict->rt = RestrictionType.AND;
- imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE);
-
-
- //SBinaryArray sb = Marshal.PtrToStructure(p2);
- //byte[][] ids = sb.Unmarshal();
- //Logger.Instance.Warning(this, "SEARCH: {0}", StringUtil.BytesToHex(ids[0]));
- //imapi.GetLastError(0, 0, out p2);
- //imapi.SaveChanges(SaveChangesFlags.FORCE_SAVE);
- } */
-}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
new file mode 100644
index 0000000..9cbf51e
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Binary.cs
@@ -0,0 +1,62 @@
+
+using Acacia.Utils;
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+
+ unsafe public struct SBinary
+ {
+ public uint cb;
+ public byte* ptr;
+
+ public byte[] Unmarshal()
+ {
+ byte[] result = new byte[cb];
+ Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
+ return result;
+ }
+
+ public override string ToString()
+ {
+ byte[] b = Unmarshal();
+ return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
+ }
+ }
+
+ unsafe public struct SBinaryArray
+ {
+ 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/IMAPIContainer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIContainer.cs
new file mode 100644
index 0000000..08b5d9d
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIContainer.cs
@@ -0,0 +1,74 @@
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+
+ [Flags]
+ public enum SearchCriteriaState : UInt32
+ {
+ NONE = 0,
+ SEARCH_RUNNING = 1,
+ SEARCH_REBUILD = 2,
+ SEARCH_RECURSIVE = 4,
+ SEARCH_FOREGROUND = 8
+ }
+
+ [Flags]
+ public enum SearchCriteriaFlags : UInt32
+ {
+ NONE = 0,
+ STOP_SEARCH = 0x00000001,
+ RESTART_SEARCH = 0x00000002,
+ RECURSIVE_SEARCH = 0x00000004,
+ SHALLOW_SEARCH = 0x00000008,
+ FOREGROUND_SEARCH = 0x00000010,
+ BACKGROUND_SEARCH = 0x00000020,
+ }
+
+ [ComImport]
+ [Guid("0002030B-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ unsafe public interface IMAPIContainer : IMAPIProp
+ {
+ // IMAPIProp
+ new void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
+ new void SaveChanges(SaveChangesFlags flags);
+ new void GetProps();
+ new void GetPropList();
+ new void OpenProperty();
+ new void SetProps();
+ new void DeleteProps();
+ new void CopyTo();
+ new void CopyProps();
+ new void GetNamesFromIDs();
+ new void GetIDsFromNames();
+
+ // IMAPIContainer
+ void GetContentsTable(UInt32 flags, out IntPtr table);
+ void GetHierarchyTable();
+ void OpenEntry();
+ void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SearchCriteriaFlags flags);
+ void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out SearchCriteriaState state);
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIFolder.cs
new file mode 100644
index 0000000..c9e8bcf
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIFolder.cs
@@ -0,0 +1,64 @@
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+ [ComImport]
+ [Guid("0002030C-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ unsafe public interface IMAPIFolder : IMAPIContainer
+ {
+ // IMAPIProp
+ new void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
+ new void SaveChanges(SaveChangesFlags flags);
+ new void GetProps();
+ new void GetPropList();
+ new void OpenProperty();
+ new void SetProps();
+ new void DeleteProps();
+ new void CopyTo();
+ new void CopyProps();
+ new void GetNamesFromIDs();
+ new void GetIDsFromNames();
+
+ // IMAPIContainer
+ new void GetContentsTable(UInt32 flags, out IntPtr table);
+ new void GetHierarchyTable();
+ new void OpenEntry();
+ new void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SearchCriteriaFlags flags);
+ new void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out SearchCriteriaState state);
+
+ // IMAPIFolder
+ void CreateMessage();
+ void CopyMessages();
+ void DeleteMessages();
+ void CreateFolder();
+ void CopyFolder();
+ void DeleteFolder();
+ void SetReadFlags();
+ void GetMessageStatus();
+ void SetMessageStatus();
+ void SaveContentsSort();
+ void EmptyFolder();
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIProp.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIProp.cs
new file mode 100644
index 0000000..9c275bc
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/IMAPIProp.cs
@@ -0,0 +1,53 @@
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+ [Flags]
+ public enum SaveChangesFlags : UInt32
+ {
+ NONE = 0,
+ KEEP_OPEN_READONLY = 1,
+ KEEP_OPEN_READWRITE = 2,
+ FORCE_SAVE = 4,
+ MAPI_DEFERRED_ERRORS = 8
+ }
+
+ [ComImport]
+ [Guid("00020303-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IMAPIProp
+ {
+ void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
+ void SaveChanges(SaveChangesFlags flags);
+ void GetProps();
+ void GetPropList();
+ void OpenProperty();
+ void SetProps();
+ void DeleteProps();
+ void CopyTo();
+ void CopyProps();
+ void GetNamesFromIDs();
+ void GetIDsFromNames();
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
new file mode 100644
index 0000000..1dd1b10
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Property.cs
@@ -0,0 +1,135 @@
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+
+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 enum PropType : ushort
+ {
+ BOOLEAN = 0x000B,
+ BINARY = 0x0102,
+ MV_BINARY = 1102,
+ DOUBLE = 0x0005,
+ LONG = 0x0003,
+ OBJECT = 0x000D,
+ STRING8 = 0x001E,
+ MV_STRING8 = 0x101E,
+ SYSTIME = 0x0040,
+ UNICODE = 0x001F,
+ MV_UNICODE = 0x101f
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct PropTag
+ {
+ public PropType type;
+ public ushort prop;
+
+ public override string ToString()
+ {
+ return "<" + prop.ToString("X4") + ":" + type + ">";
+ }
+
+ public SearchQuery.PropertyIdentifier ToPropertyIdentifier()
+ {
+ return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)type);
+ }
+ }
+
+
+
+ [StructLayout(LayoutKind.Explicit)]
+ unsafe public struct PropValue
+ {
+ [FieldOffset(0)]
+ public PropTag ulPropTag;
+
+ [FieldOffset(4)]
+ public uint dwAlignPad;
+
+ // short int i; /* case PT_I2 */
+ // LONG l; /* case PT_LONG */
+ // ULONG ul; /* alias for PT_LONG */
+ // LPVOID lpv; /* alias for PT_PTR */
+ // float flt; /* case PT_R4 */
+ // double dbl; /* case PT_DOUBLE */
+ // unsigned short int b; /* case PT_BOOLEAN */
+ [FieldOffset(8), MarshalAs(UnmanagedType.U2)]
+ public bool b;
+
+ // CURRENCY cur; /* case PT_CURRENCY */
+ // double at; /* case PT_APPTIME */
+ // FILETIME ft; /* case PT_SYSTIME */
+
+ // LPSTR lpszA; /* case PT_STRING8 */
+ [FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
+ public sbyte* lpszA;
+
+ // SBinary bin; /* case PT_BINARY */
+ [FieldOffset(8)]
+ public SBinary bin;
+
+ // LPWSTR lpszW; /* case PT_UNICODE */
+ [FieldOffset(8), MarshalAs(UnmanagedType.LPWStr)]
+ public char* lpszW;
+
+ // LPGUID lpguid; /* case PT_CLSID */
+ // LARGE_INTEGER li; /* case PT_I8 */
+ // SShortArray MVi; /* case PT_MV_I2 */
+ // SLongArray MVl; /* case PT_MV_LONG */
+ // SRealArray MVflt; /* case PT_MV_R4 */
+ // SDoubleArray MVdbl; /* case PT_MV_DOUBLE */
+ // SCurrencyArray MVcur; /* case PT_MV_CURRENCY */
+ // SAppTimeArray MVat; /* case PT_MV_APPTIME */
+ // SDateTimeArray MVft; /* case PT_MV_SYSTIME */
+ // SBinaryArray MVbin; /* case PT_MV_BINARY */
+ // SLPSTRArray MVszA; /* case PT_MV_STRING8 */
+ // SWStringArray MVszW; /* case PT_MV_UNICODE */
+
+ // SGuidArray MVguid; /* case PT_MV_CLSID */
+ // SLargeIntegerArray MVli; /* case PT_MV_I8 */
+ // SCODE err; /* case PT_ERROR */
+ // LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */
+
+ public override string ToString()
+ {
+ return ToObject()?.ToString() ?? "";
+ }
+
+ public object ToObject()
+ {
+ switch (ulPropTag.type)
+ {
+ case PropType.BOOLEAN:
+ return b;
+ case PropType.STRING8:
+ return new string(lpszA);
+ case PropType.BINARY:
+ return bin;
+ //case PropType.UNICODE:
+ // return lpszW.ToString();
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
new file mode 100644
index 0000000..c8ac761
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/MAPI/Restriction.cs
@@ -0,0 +1,331 @@
+
+using Acacia.Stubs;
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Native.MAPI
+{
+
+ unsafe public struct CommentRestriction
+ {
+ public uint cValues;
+ public SRestriction* res;
+ public PropValue* prop;
+ }
+
+ unsafe public struct PropertyRestriction
+ {
+ public Acacia.Stubs.SearchOperation relop;
+ public PropTag ulPropTag;
+ public PropValue* prop;
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + relop + ":" + ulPropTag.ToString();
+ s += ":" + prop->ToString();
+ s += "\n";
+ return s;
+ }
+
+ public SearchQuery ToSearchQuery()
+ {
+ return new SearchQuery.PropertyCompare(ulPropTag.ToPropertyIdentifier(),
+ (SearchQuery.ComparisonOperation)(int)relop,
+ prop->ToObject());
+ }
+ }
+
+ [Flags]
+ public enum FuzzyLevel : uint
+ {
+ FULLSTRING = 0,
+ SUBSTRING = 1,
+ PREFIX = 2,
+
+ IGNORECASE = 0x00010000,
+ IGNORENONSPACE = 0x00020000,
+ LOOSE = 0x00040000
+ }
+
+ unsafe public struct ContentRestriction
+ {
+ public FuzzyLevel fuzzy;
+ public PropTag ulPropTag;
+ public PropValue* prop;
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + fuzzy + ":" + ulPropTag.ToString();
+ s += ":" + prop->ToString();
+ s += "\n";
+ return s;
+ }
+
+ public SearchQuery ToSearchQuery()
+ {
+ return new SearchQuery.PropertyContent(ulPropTag.ToPropertyIdentifier(),
+ (uint)fuzzy, // TODO
+ prop->ToObject());
+ }
+ }
+
+ // TODO: merge with ISearch
+ public enum RestrictionType : UInt32
+ {
+ AND,
+ OR,
+ NOT,
+ CONTENT,
+ PROPERTY,
+ COMPAREPROPS,
+ BITMASK,
+ SIZE,
+ EXIST,
+ SUBRESTRICTION,
+ COMMENT,
+ COUNT,
+ ANNOTATION
+ }
+
+ unsafe public struct SubRestriction
+ {
+ public uint cb;
+ public SRestriction* ptr;
+
+ public string ToString(int depth)
+ {
+ string s = "";
+ for (uint i = 0; i < cb; ++i)
+ {
+ s += ptr[i].ToString(depth);
+ }
+ return s;
+ }
+
+ public SearchQuery ToSearchQuery(bool and)
+ {
+ SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or(); ;
+ for (uint i = 0; i < cb; ++i)
+ {
+ oper.Add(ptr[i].ToSearchQuery());
+ }
+ return oper;
+ }
+ }
+
+ unsafe public struct NotRestriction
+ {
+ public uint dwReserved;
+ public SRestriction* ptr;
+
+ public string ToString(int depth)
+ {
+ return ptr->ToString(depth);
+ }
+
+ public SearchQuery ToSearchQuery()
+ {
+ return new SearchQuery.Not(ptr->ToSearchQuery());
+ }
+ }
+
+ public enum BMR : uint
+ {
+ EQZ = 0,
+ NEZ = 1
+ }
+
+ unsafe public struct BitMaskRestriction
+ {
+ public BMR bmr;
+ public PropTag prop;
+ public uint mask;
+
+ override public string ToString()
+ {
+ return bmr.ToString() + ":" + prop + mask.ToString("X8");
+ }
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ return indent + ToString() + "\n";
+ }
+
+ public SearchQuery ToSearchQuery()
+ {
+ return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), bmr == BMR.EQZ, mask);
+ }
+ }
+
+ unsafe public struct ExistRestriction
+ {
+ public uint dwReserved1;
+ public PropTag prop;
+ public uint dwReserved2;
+
+ override public string ToString()
+ {
+ return prop.ToString();
+ }
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ return indent + prop.ToString() + "\n";
+ }
+
+ public SearchQuery ToSearchQuery()
+ {
+ return new SearchQuery.PropertyExists(prop.ToPropertyIdentifier());
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ unsafe public struct SRestriction
+ {
+ [FieldOffset(0)]
+ public RestrictionType rt;
+
+ // And/Or
+ [FieldOffset(8)]
+ public SubRestriction sub;
+
+ [FieldOffset(8)]
+ public NotRestriction not;
+
+ [FieldOffset(8)]
+ public ContentRestriction content;
+
+ [FieldOffset(8)]
+ public PropertyRestriction prop;
+
+ [FieldOffset(8)]
+ public BitMaskRestriction bitMask;
+
+ [FieldOffset(8)]
+ public ExistRestriction exist;
+
+ [FieldOffset(8)]
+ public CommentRestriction comment;
+
+ public SearchQuery ToSearchQuery()
+ {
+ switch (rt)
+ {
+ case RestrictionType.AND:
+ return sub.ToSearchQuery(true);
+ case RestrictionType.OR:
+ return sub.ToSearchQuery(false);
+ case RestrictionType.NOT:
+ return not.ToSearchQuery();
+ case RestrictionType.CONTENT:
+ return content.ToSearchQuery();
+ case RestrictionType.PROPERTY:
+ return prop.ToSearchQuery();
+ case RestrictionType.BITMASK:
+ return bitMask.ToSearchQuery();
+ case RestrictionType.EXIST:
+ return exist.ToSearchQuery();
+
+ /* TODO COMPAREPROPS,
+ BITMASK,
+ SIZE,
+ SUBRESTRICTION,
+ COMMENT,
+ COUNT,
+ ANNOTATION*/
+
+ }
+ return null;
+ }
+
+ public override string ToString()
+ {
+ return ToString(0);
+ }
+
+ public string ToString(int depth)
+ {
+ string indent = new string(' ', depth);
+ string s = indent + rt.ToString() + "\n" + indent + "{\n";
+ switch (rt)
+ {
+ case RestrictionType.AND:
+ case RestrictionType.OR:
+ s += sub.ToString(depth + 1);
+ break;
+ case RestrictionType.NOT:
+ s += not.ToString(depth + 1);
+ break;
+ case RestrictionType.CONTENT:
+ s += content.ToString(depth + 1);
+ break;
+ case RestrictionType.PROPERTY:
+ s += prop.ToString(depth + 1);
+ break;
+ case RestrictionType.BITMASK:
+ s += bitMask.ToString(depth + 1);
+ break;
+ case RestrictionType.EXIST:
+ s += exist.ToString(depth + 1);
+ break;
+
+ /* TODO COMPAREPROPS,
+ BITMASK,
+ SIZE,
+ SUBRESTRICTION,
+ COMMENT,
+ COUNT,
+ ANNOTATION*/
+
+ }
+ s += indent + "}\n";
+ return s;
+ }
+ }
+
+ /* Example search code
+ {
+ MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders);
+ dynamic obj = folder.MAPIOBJECT;
+ IMAPIFolder imapi = obj as IMAPIFolder;
+
+ //imapi.GetSearchCriteria(0, IntPtr.Zero, IntPtr.Zero, ref state);
+ GetSearchCriteriaState state;
+ //imapi.GetContentsTable(0, out p);
+ SBinaryArray* sb1;
+ SRestriction* restrict;
+ imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
+ Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString());
+
+ restrict->rt = RestrictionType.AND;
+ imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE);
+
+
+ //SBinaryArray sb = Marshal.PtrToStructure(p2);
+ //byte[][] ids = sb.Unmarshal();
+ //Logger.Instance.Warning(this, "SEARCH: {0}", StringUtil.BytesToHex(ids[0]));
+ //imapi.GetLastError(0, 0, out p2);
+ //imapi.SaveChanges(SaveChangesFlags.FORCE_SAVE);
+ } */
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs
new file mode 100644
index 0000000..c010631
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/SearchQuery.cs
@@ -0,0 +1,278 @@
+/// 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,
+/// as published by the Free Software Foundation.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU Affero General Public License for more details.
+///
+/// You should have received a copy of the GNU Affero General Public License
+/// along with this program.If not, see.
+///
+/// Consult LICENSE file for details
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Acacia.Native.MAPI;
+
+namespace Acacia
+{
+ public interface ISearchEncoder
+ {
+ void Encode(SearchQuery.PropertyBitMask part);
+ void Encode(SearchQuery.PropertyCompare part);
+ void Encode(SearchQuery.PropertyContent part);
+ void Encode(SearchQuery.PropertyExists part);
+ void Encode(SearchQuery.And part);
+ void Encode(SearchQuery.Or part);
+ void Encode(SearchQuery.Not part);
+ }
+
+ public class ToStringEncoder : ISearchEncoder
+ {
+ private readonly StringBuilder _builder = new StringBuilder();
+ private int _indent;
+
+ private void Indent()
+ {
+ _builder.Append(new String(' ', _indent));
+ }
+
+ public void Encode(SearchQuery.And part)
+ {
+ EncodeMulti(part, "AND");
+ }
+
+ public void Encode(SearchQuery.Or part)
+ {
+ EncodeMulti(part, "OR");
+ }
+
+ public void Encode(SearchQuery.Not part)
+ {
+ _builder.Append("NOT ");
+ part.Operand.Encode(this);
+ }
+
+ private void EncodeMulti(SearchQuery.MultiOperator part, string oper)
+ {
+ Indent();
+ _builder.Append(oper).Append("\n");
+ Indent();
+ _builder.Append("{\n");
+
+ ++_indent;
+
+ foreach (SearchQuery operand in part.Operands)
+ operand.Encode(this);
+
+ --_indent;
+
+ Indent();
+ _builder.Append("}\n");
+ }
+
+ public void Encode(SearchQuery.PropertyBitMask part)
+ {
+ _builder.Append("BITMASK:").Append(part.Property); // TODO: operator/value
+ }
+
+ public void Encode(SearchQuery.PropertyCompare part)
+ {
+ _builder.Append("COMPARE:").Append(part.Property); // TODO: operator/value
+ }
+
+ public void Encode(SearchQuery.PropertyContent part)
+ {
+ _builder.Append("CONTENT:").Append(part.Property); // TODO: operator/value
+ }
+
+ public void Encode(SearchQuery.PropertyExists part)
+ {
+ _builder.Append("EXISTS:").Append(part.Property);
+ }
+
+ public string GetValue()
+ {
+ return _builder.ToString();
+ }
+ }
+
+ abstract public class SearchQuery
+ {
+ #region Interface
+
+ override public string ToString()
+ {
+ ToStringEncoder encoder = new ToStringEncoder();
+ Encode(encoder);
+ return encoder.GetValue();
+ }
+
+ abstract public void Encode(ISearchEncoder encoder);
+
+ #endregion
+
+ #region Implementations
+
+ abstract public class MultiOperator : SearchQuery
+ {
+ private readonly List _operands = new List();
+
+ public void Add(SearchQuery operand)
+ {
+ if (operand == null)
+ throw new ArgumentNullException();
+
+ _operands.Add(operand);
+ }
+
+ public IEnumerable Operands
+ {
+ get { return _operands; }
+ }
+ }
+
+ public class And : MultiOperator
+ {
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ public class Or : MultiOperator
+ {
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ public class Not : SearchQuery
+ {
+ public SearchQuery Operand
+ {
+ get;
+ set;
+ }
+
+ public Not(SearchQuery operand)
+ {
+ this.Operand = operand;
+ }
+
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+
+ ///
+ /// TODO: this is globally useful
+ ///
+ public class PropertyIdentifier
+ {
+ private string _id;
+
+ public PropertyIdentifier(string id)
+ {
+ this._id = id;
+ }
+
+ public static PropertyIdentifier FromTag(ushort prop, ushort type)
+ {
+ return new PropertyIdentifier(string.Format("{0:4X}{1:4X}", prop, type));
+ }
+ }
+
+ abstract public class PropertyQuery : SearchQuery
+ {
+ public PropertyIdentifier Property { get; set; }
+
+ protected PropertyQuery(PropertyIdentifier property)
+ {
+ this.Property = property;
+ }
+
+ }
+
+ ///
+ /// Order matches MAPI RELOP_ constants
+ ///
+ public enum ComparisonOperation : uint
+ {
+ Smaller,
+ SmallerEqual,
+ Greater,
+ GreaterEqual,
+ Equal,
+ NotEqual,
+ Like
+ }
+
+ public class PropertyCompare : PropertyQuery
+ {
+ public ComparisonOperation Operation { get; set; }
+ public object Value { get; set; }
+
+ public PropertyCompare(PropertyIdentifier property, ComparisonOperation operation, object value) : base(property)
+ {
+ this.Operation = operation;
+ this.Value = value;
+ }
+
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ public class PropertyExists : PropertyQuery
+ {
+ public PropertyExists(PropertyIdentifier property) : base(property)
+ {
+ }
+
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ public class PropertyContent : PropertyQuery
+ {
+ public PropertyContent(PropertyIdentifier property, uint options, object content) : base(property)
+ {
+ // TODO
+ }
+
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ public class PropertyBitMask : PropertyQuery
+ {
+ public PropertyBitMask(PropertyIdentifier property, bool wantZero, uint mask) : base(property)
+ {
+ // TODO
+ }
+
+ public override void Encode(ISearchEncoder encoder)
+ {
+ encoder.Encode(this);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
index 7f6598c..4c13a37 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
@@ -62,6 +62,13 @@ namespace Acacia.Stubs
SuggestedContacts = 30
}
+ // Replacement for OlSpecialFolders
+ public enum SpecialFolder
+ {
+ AllTasks = 0,
+ Reminders = 1
+ }
+
public enum AccountType
{
// TODO
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
index 6ff6308..7b3848e 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
@@ -114,5 +114,11 @@ namespace Acacia.Stubs
ZPushFolder ZPush { get; set; }
IFolder Clone();
+
+ SearchQuery SearchCriteria
+ {
+ get;
+ set;
+ }
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
index 385b676..efbdc90 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISearch.cs
@@ -53,10 +53,14 @@ namespace Acacia.Stubs
ISearchField AddField(string name, bool isUserField = false);
}
- public interface ISearch : ISearchOperator, IDisposable
- where ItemType : IItem
+ public interface ISearchQuery : ISearchOperator
{
ISearchOperator AddOperator(SearchOperator oper);
+ }
+
+ public interface ISearch : ISearchQuery, IDisposable
+ where ItemType : IItem
+ {
IEnumerable Search(int maxResults = int.MaxValue);
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStore.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStore.cs
index 5d4c04e..f4dd47f 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStore.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStore.cs
@@ -40,6 +40,12 @@ namespace Acacia.Stubs
///
string GetDefaultFolderId(DefaultFolder folder);
+ ///
+ /// Returns a special folder.
+ ///
+ /// The special folder. The caller is responsible for disposing.
+ IFolder GetSpecialFolder(SpecialFolder folder);
+
IItem GetItemFromID(string id);
string DisplayName { get; }
string StoreID { get; }
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
index 78b56ae..8b0b159 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
@@ -23,6 +23,7 @@ using System.Collections;
using Acacia.Utils;
using Acacia.ZPush;
using NSOutlook = Microsoft.Office.Interop.Outlook;
+using Acacia.Native.MAPI;
namespace Acacia.Stubs.OutlookWrappers
{
@@ -371,5 +372,31 @@ namespace Acacia.Stubs.OutlookWrappers
get;
set;
}
+
+ unsafe public SearchQuery SearchCriteria
+ {
+ get
+ {
+ IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
+ try
+ {
+ SearchCriteriaState state;
+ SBinaryArray* sb1;
+ SRestriction* restrict;
+ imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
+ Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString());
+ return restrict->ToSearchQuery();
+ }
+ finally
+ {
+ ComRelease.Release(imapi);
+ }
+ }
+
+ set
+ {
+
+ }
+ }
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoreWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoreWrapper.cs
index 11d4499..6f671c1 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoreWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoreWrapper.cs
@@ -67,6 +67,18 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
+ public IFolder GetSpecialFolder(SpecialFolder folder)
+ {
+ try
+ {
+ return _item.GetSpecialFolder((NSOutlook.OlSpecialFolders)(int)folder).Wrap();
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
public IItem GetItemFromID(string id)
{
using (ComRelease com = new ComRelease())
From 4a57e014c2ce84d01110898c143c5a47373b4575 Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 22 Feb 2017 11:56:55 +0100
Subject: [PATCH 2/3] Fixed undisposed wrapper on folder remove event
---
src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
index 5c22348..77f20c5 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushFolder.cs
@@ -157,7 +157,7 @@ namespace Acacia.ZPush
// Hence, fetch all the remaining folder ids, and remove any folder that no longer exists.
// TODO: move this logic into IFolders?
HashSet remaining = new HashSet();
- foreach (IFolder child in _folder.SubFolders)
+ foreach (IFolder child in _folder.SubFolders.DisposeEnum())
{
try
{
From 86dbc32b51fd2fc487def0f3bd0ec79cbf5391a5 Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 22 Feb 2017 15:17:25 +0100
Subject: [PATCH 3/3] [KOE-14] Added possibility to set icon for folder, though
this coomes out looking weird. Leaving for now.
---
.../AcaciaZPushPlugin.csproj | 2 +
.../FeatureSecondaryContacts.cs | 58 ++++++++++++-------
.../AcaciaZPushPlugin/Stubs/ICommandBars.cs | 1 +
.../AcaciaZPushPlugin/Stubs/IFolder.cs | 4 ++
.../AcaciaZPushPlugin/Stubs/IPicture.cs | 12 ++++
.../OutlookWrappers/CommandBarsWrapper.cs | 5 ++
.../Stubs/OutlookWrappers/FolderWrapper.cs | 22 ++++++-
.../Stubs/OutlookWrappers/Mapping.cs | 9 +++
.../Stubs/OutlookWrappers/PictureWrapper.cs | 17 ++++++
.../AcaciaZPushPlugin/Stubs/Wrappers.cs | 5 ++
10 files changed, 112 insertions(+), 23 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IPicture.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/PictureWrapper.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index e80e9ea..24c8ae6 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -296,6 +296,7 @@
+
@@ -308,6 +309,7 @@
+
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SecondaryContacts/FeatureSecondaryContacts.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SecondaryContacts/FeatureSecondaryContacts.cs
index d79feb4..691eb2d 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SecondaryContacts/FeatureSecondaryContacts.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SecondaryContacts/FeatureSecondaryContacts.cs
@@ -22,6 +22,7 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -68,8 +69,7 @@ namespace Acacia.Features.SecondaryContacts
public override void Startup()
{
- Watcher.WatchFolder(new FolderRegistrationSecondaryContacts(this),
- OnUnpatchedFolderDiscovered);
+ Watcher.WatchFolder(new FolderRegistrationSecondaryContacts(this), OnUnpatchedFolderDiscovered);
}
private void OnUnpatchedFolderDiscovered(IFolder folder)
@@ -93,7 +93,7 @@ namespace Acacia.Features.SecondaryContacts
// Stage 1
// Sync type
- Logger.Instance.Trace(this, "Setting sync type");
+ Logger.Instance.Trace(this, "Settin7g sync type");
folder.SetProperty(OutlookConstants.PR_EAS_SYNCTYPE, (int)OutlookConstants.SyncType.UserContact);
// Container type
@@ -102,15 +102,42 @@ namespace Acacia.Features.SecondaryContacts
// Make it invisible.
folder.AttrHidden = true;
+ folder.Save();
Logger.Instance.Debug(this, "Patched secondary contacts folder: {0}", strippedName);
- // Register and show a warning, if not already done.
- // Note that patching may be done multiple times.
- if (!_warnedFolders.Contains(folder.EntryID))
- {
- _warnedFolders.Add(folder.EntryID);
+ WarnRestart(folder);
+ }
+ // If _warnedFolders does not contain the folder (and it's hidden), this means Outlook was restarted.
+ else if (!_warnedFolders.Contains(folder.EntryID))
+ {
+ // Stage 2
- if (MessageBox.Show(StringUtil.GetResourceString("SecondaryContactsPatched_Body", strippedName),
+ // Patch the name
+ Logger.Instance.Trace(this, "Patching name");
+ folder.Name = strippedName;
+
+ // Show it
+ folder.AttrHidden = false;
+ Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
+ }
+ Logger.Instance.Debug(this, "Patching done: {0}: {1}", strippedName, folder.AttrHidden);
+ }
+
+ private DateTime? _lastWarning;
+
+ private void WarnRestart(IFolder folder)
+ {
+ // Register and show a warning, if not already done.
+ // Note that patching may be done multiple times.
+ if (!_warnedFolders.Contains(folder.EntryID))
+ {
+ _warnedFolders.Add(folder.EntryID);
+
+ // TODO: configurable constant for warning time
+ if (_lastWarning == null || DateTime.Now - _lastWarning >= TimeSpan.FromHours(1))
+ {
+ _lastWarning = DateTime.Now;
+ if (MessageBox.Show(StringUtil.GetResourceString("SecondaryContactsPatched_Body", folder.Name),
StringUtil.GetResourceString("SecondaryContactsPatched_Title"),
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
@@ -120,19 +147,6 @@ namespace Acacia.Features.SecondaryContacts
}
}
}
- // If _warnedFolders does not contain the folder (and it's hidden), this means Outlook was restarted.
- else if (!_warnedFolders.Contains(folder.EntryID))
- {
- // Stage 2
-
- // Patch the name
- Logger.Instance.Trace(this, "Patching name");
- folder.Name = strippedName;
-
- // Show it
- folder.AttrHidden = false;
- Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
- }
}
}
}
\ No newline at end of file
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ICommandBars.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ICommandBars.cs
index bb3f908..ee9f588 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ICommandBars.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ICommandBars.cs
@@ -25,6 +25,7 @@ namespace Acacia.Stubs
{
public interface IMSOCommand
{
+ IPicture GetPicture(Size imageSize);
Bitmap GetImage(Size imageSize);
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
index 7b3848e..911f969 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IFolder.cs
@@ -120,5 +120,9 @@ namespace Acacia.Stubs
get;
set;
}
+
+ void Save();
+
+ void SetCustomIcon(IPicture icon);
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IPicture.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IPicture.cs
new file mode 100644
index 0000000..8a9486c
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IPicture.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Stubs
+{
+ public interface IPicture : IComWrapper
+ {
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/CommandBarsWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/CommandBarsWrapper.cs
index cae203a..7c7a83f 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/CommandBarsWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/CommandBarsWrapper.cs
@@ -39,6 +39,11 @@ namespace Acacia.Stubs.OutlookWrappers
this._id = id;
}
+ public IPicture GetPicture(Size imageSize)
+ {
+ return _commands._item.GetImageMso(_id, imageSize.Width, imageSize.Height).Wrap();
+ }
+
public Bitmap GetImage(Size imageSize)
{
IPictureDisp pict = _commands._item.GetImageMso(_id, imageSize.Width, imageSize.Height);
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
index 8b0b159..ff8499f 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs
@@ -24,6 +24,7 @@ using Acacia.Utils;
using Acacia.ZPush;
using NSOutlook = Microsoft.Office.Interop.Outlook;
using Acacia.Native.MAPI;
+using stdole;
namespace Acacia.Stubs.OutlookWrappers
{
@@ -56,6 +57,19 @@ namespace Acacia.Stubs.OutlookWrappers
return new FolderWrapper(CloneComObject());
}
+ public void Save()
+ {
+ IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
+ try
+ {
+ imapi.SaveChanges(SaveChangesFlags.FORCE_SAVE);
+ }
+ finally
+ {
+ ComRelease.Release(imapi);
+ }
+ }
+
internal NSOutlook.Folder RawItem { get { return _item; } }
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
@@ -360,6 +374,11 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
+ public void SetCustomIcon(IPicture icon)
+ {
+ _item.SetCustomIcon(((PictureWrapper)icon).RawItem as StdPicture);
+ }
+
#endregion
public ItemType DefaultItemType
@@ -395,7 +414,8 @@ namespace Acacia.Stubs.OutlookWrappers
set
{
-
+ // TODO
+ throw new NotImplementedException();
}
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/Mapping.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/Mapping.cs
index 3bddb65..3aae7e4 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/Mapping.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/Mapping.cs
@@ -87,6 +87,15 @@ namespace Acacia.Stubs.OutlookWrappers
return wrapped;
}
+ public static IPicture Wrap(stdole.IPictureDisp obj, bool mustRelease = true)
+ {
+ if (obj == null)
+ return null;
+ PictureWrapper wrapped = new PictureWrapper(obj);
+ wrapped.MustRelease = mustRelease;
+ return wrapped;
+ }
+
// TODO: extension methods for this
public static IStore Wrap(NSOutlook.Store obj, bool mustRelease = true)
{
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/PictureWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/PictureWrapper.cs
new file mode 100644
index 0000000..2c180ec
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/PictureWrapper.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Stubs.OutlookWrappers
+{
+ class PictureWrapper : ComWrapper, IPicture
+ {
+ internal PictureWrapper(stdole.IPictureDisp item) : base(item)
+ {
+ }
+
+ internal stdole.IPictureDisp RawItem { get { return _item; } }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Wrappers.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Wrappers.cs
index db4a234..bc83232 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Wrappers.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Wrappers.cs
@@ -62,5 +62,10 @@ namespace Acacia.Stubs
{
return Mapping.WrapOrDefault(o, mustRelease);
}
+
+ public static IPicture Wrap(this stdole.IPictureDisp picture)
+ {
+ return Mapping.Wrap(picture);
+ }
}
}