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