This commit is contained in:
Patrick Simpson 2017-06-07 09:19:50 +02:00
commit dd837caba5
15 changed files with 552 additions and 423 deletions

View File

@ -310,13 +310,15 @@
<Compile Include="Logging.cs" />
<Compile Include="Native\IOleWindow.cs" />
<Compile Include="Native\IOlkAccount.cs" />
<Compile Include="Native\Kernel32.cs" />
<Compile Include="Native\MAPI\Binary.cs" />
<Compile Include="Native\MAPI\IMAPIContainer.cs" />
<Compile Include="Native\MAPI\IMAPIFolder.cs" />
<Compile Include="Native\MAPI\IMAPIProp.cs" />
<Compile Include="Native\MAPI\MAPI.cs" />
<Compile Include="Native\MAPI\Property.cs" />
<Compile Include="Native\MAPI\Restriction.cs" />
<Compile Include="Native\NativeEncoder.cs" />
<Compile Include="Native\MAPI\RestrictionEncoder.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="SearchQuery.cs" />
<Compile Include="Stubs\Enums.cs" />

View File

@ -46,6 +46,15 @@ namespace Acacia.Features.SharedFolders
}
private static readonly BoolOption OPTION_REMINDERS = new BoolOption("Reminders", true);
[AcaciaOption("If disabled, the reminders queyr will be explicitly stopped and started when update the query. " +
"This causes more effort to search again, but might prevent issues.")]
public bool RemindersKeepRunning
{
get { return GetOption(OPTION_REMINDERS_KEEP_RUNNING); }
set { SetOption(OPTION_REMINDERS_KEEP_RUNNING, value); }
}
private static readonly BoolOption OPTION_REMINDERS_KEEP_RUNNING = new BoolOption("RemindersKeepRunning", true);
#endregion
public override void Startup()

View File

@ -14,15 +14,16 @@ namespace Acacia.Features.SharedFolders
{
private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F));
private readonly LogContext _context;
private readonly FeatureSharedFolders _feature;
private readonly IFolder _folder;
private SearchQuery _queryRoot;
private SearchQuery.Or _queryCustom;
private bool _queryCustomModified;
public RemindersQuery(LogContext context, IStore store)
public RemindersQuery(FeatureSharedFolders feature, IStore store)
{
this._context = context;
_folder = store.GetSpecialFolder(SpecialFolder.Reminders);
this._feature = feature;
this._folder = store.GetSpecialFolder(SpecialFolder.Reminders);
}
public bool Open()
@ -31,10 +32,10 @@ namespace Acacia.Features.SharedFolders
return true;
try
{
_queryRoot = _folder.SearchCriteria;
_queryRoot = FolderQuery;
if (!(_queryRoot is SearchQuery.And))
return false;
Logger.Instance.Trace(this, "Current query1: {0}", _queryRoot.ToString());
Logger.Instance.Debug(this, "Current query:\n{0}", _queryRoot.ToString());
SearchQuery.And root = (SearchQuery.And)_queryRoot;
// TODO: more strict checking of query
@ -50,7 +51,6 @@ namespace Acacia.Features.SharedFolders
// We have the root, but not the custom query. Create it.
Logger.Instance.Debug(this, "Creating custom query");
Logger.Instance.Trace(this, "Current query: {0}", root.ToString());
_queryCustom = new SearchQuery.Or();
// Add the prefix exclusion for shared folders
@ -63,11 +63,10 @@ namespace Acacia.Features.SharedFolders
);
root.Operands.Add(_queryCustom);
Logger.Instance.Trace(this, "Modified query: {0}", root.ToString());
Logger.Instance.Debug(this, "Modified query:\n{0}", root.ToString());
// Store it
// TODO: could store it on change only
_folder.SearchCriteria = root;
Logger.Instance.Trace(this, "Modified query2: {0}", _folder.SearchCriteria.ToString());
FolderQuery = root;
Logger.Instance.Debug(this, "Modified query readback:\n{0}", FolderQuery);
}
catch (Exception e)
{
@ -80,7 +79,7 @@ namespace Acacia.Features.SharedFolders
{
get
{
return _context.LogContextId;
return _feature.LogContextId;
}
}
@ -91,7 +90,29 @@ namespace Acacia.Features.SharedFolders
public void Commit()
{
_folder.SearchCriteria = _queryRoot;
if (_queryCustomModified)
{
FolderQuery = _queryRoot;
_queryCustomModified = false;
}
}
private SearchQuery FolderQuery
{
get
{
return _folder.SearchCriteria;
}
set
{
if (!_feature.RemindersKeepRunning)
_folder.SearchRunning = false;
_folder.SearchCriteria = value;
if (!_feature.RemindersKeepRunning)
_folder.SearchRunning = true;
}
}
public void UpdateReminders(SyncId folderId, bool wantReminders)
@ -125,6 +146,7 @@ namespace Acacia.Features.SharedFolders
_queryCustom.Operands.Add(new SearchQuery.PropertyContent(
PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, prefix
));
_queryCustomModified = true;
}
}
@ -154,6 +176,7 @@ namespace Acacia.Features.SharedFolders
Logger.Instance.Trace(this, "Unwanted prefix at {0}: {1}", i, prefix);
_queryCustom.Operands.RemoveAt(i);
_queryCustomModified = true;
}
else ++i;
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native
{
public static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
// ZeroMemory is actually a macro
public static void ZeroMemory(IntPtr ptr, int length)
{
for (int i = 0; i < length / 8; i += 8)
{
Marshal.WriteInt64(ptr, i, 0x00);
}
for (int i = length % 8; i < -1; i--)
{
Marshal.WriteByte(ptr, length - i, 0x00);
}
}
}
}

View File

@ -24,35 +24,40 @@ using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
/// <summary>
/// Simple wrapper type to allow string encoding to work. Returned from SBinary.Unmarshall; this is needed
/// as a copy of the data ptr there must be managed.
/// </summary>
public struct SBinaryWrapper
{
public readonly byte[] Data;
public SBinaryWrapper(byte[] bytes)
{
this.Data = bytes;
}
public override string ToString()
{
return "cb: " + Data.Length.ToString() + " lpb: " + StringUtil.BytesToHex(Data);
}
}
unsafe public struct SBinary
{
public uint cb;
public byte* ptr;
public byte[] Unmarshal()
public SBinaryWrapper Unmarshal()
{
byte[] result = new byte[cb];
System.Runtime.InteropServices.Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
return result;
}
/// <summary>
/// Returns an instance with the data allocated in the enocder.
/// </summary>
public SBinary Marshal(NativeEncoder encoder)
{
return new SBinary()
{
cb = cb,
ptr = (byte*)encoder.Allocate(Unmarshal())
};
Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
return new SBinaryWrapper(result);
}
public override string ToString()
{
byte[] b = Unmarshal();
return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
return Unmarshal().ToString();
}
}
@ -60,15 +65,5 @@ namespace Acacia.Native.MAPI
{
public uint count;
public SBinary* ptr;
public byte[][] Unmarshal()
{
byte[][] result = new byte[count][];
for (uint i = 0; i < count; ++i)
{
result[i] = ptr[i].Unmarshal();
}
return result;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
public static class MAPI
{
[DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
public static extern IntPtr MAPIAllocateBuffer(uint cbSize, ref IntPtr lppBuffer);
[DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
public static extern IntPtr MAPIAllocateMore(uint cbSize, IntPtr lpObject, ref IntPtr lppBuffer);
[DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)]
public static extern uint MAPIFreeBuffer(IntPtr lpBuffer);
}
}

View File

@ -20,7 +20,6 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Acacia.Native.NativeEncoder;
namespace Acacia.Native.MAPI
{
@ -48,7 +47,7 @@ namespace Acacia.Native.MAPI
public override string ToString()
{
return "<" + prop.ToString("X4") + ":" + type + ">";
return string.Format("0x{0:X4}{1:X4} (PT_{2})", prop, (int)type, type);
}
public SearchQuery.PropertyIdentifier ToPropertyIdentifier()
@ -144,33 +143,42 @@ namespace Acacia.Native.MAPI
case PropType.UNICODE:
return new string(data.lpszW);
case PropType.BINARY:
return data.bin;
return data.bin.Unmarshal();
}
throw new NotImplementedException();
}
unsafe public static IntPtr MarshalFromObject(NativeEncoder encoder, PropTag prop, object value)
unsafe public static IntPtr MarshalFromObject(RestrictionEncoder encoder, PropTag prop, object value)
{
PropValue obj = new PropValue();
obj.header.ulPropTag = prop;
IntPtr ptr;
Data data = new Data();
switch (prop.type)
{
case PropType.BOOLEAN:
obj.data.b = (bool)value;
return encoder.Allocate(obj.header, obj.data.b);
data.b = (bool)value;
ptr = encoder.AllocateWithExtra<Header>(8, data.b);
break;
case PropType.STRING8:
IntPtr ptrA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), new byte[] { 0 });
return encoder.Allocate(obj.header, 8, ptrA);
IntPtr lpszA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), 1);
ptr = encoder.AllocateWithExtra<Header>(8, lpszA);
break;
case PropType.UNICODE:
IntPtr ptrW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), new byte[] { 0, 0 });
return encoder.Allocate(obj.header, 8, ptrW);
IntPtr lpszW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), 2);
ptr = encoder.AllocateWithExtra<Header>(8, lpszW);
break;
case PropType.BINARY:
obj.data.bin = ((SBinary)value).Marshal(encoder);
return encoder.Allocate(obj.header, 8, obj.data.bin);
data.bin.cb = (uint)((SBinaryWrapper)value).Data.Length;
data.bin.ptr = (byte*)encoder.Allocate(((SBinaryWrapper)value).Data);
ptr = encoder.AllocateWithExtra<Header>(8, data.bin);
break;
default:
throw new NotImplementedException();
}
// Initialise the header
PropValue* obj = (PropValue*)ptr;
obj->header.ulPropTag = prop;
return ptr;
}
}
}

View File

@ -39,20 +39,42 @@ namespace Acacia.Native.MAPI
public PropTag ulPropTag;
public PropValue* prop;
private static readonly string[] RELOP_NAMES =
{
"RELOP_LT", "RELOP_LE", "RELOP_GT", "RELOP_GE", "RELOP_EQ", "RELOP_NE", "RELOP_RE"
};
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + relop + ":" + ulPropTag.ToString();
s += ":" + prop->ToString();
s += "\n";
string s = string.Format(
"{0}lpRes->res.resProperty.relop = {2} = 0x{1:X8}\n" +
"{0}lpRes->res.resProperty.ulPropTag = {3}\n",
SRestriction.Indent(depth),
(int)relop,
RELOP_NAMES[(int)relop],
ulPropTag
);
s += string.Format("{0}lpRes->res.resProperty.lpProp->ulPropTag = {1}\n",
SRestriction.Indent(depth),
ulPropTag
);
s += string.Format("{0}lpRes->res.resProperty.lpProp->Value = {1}\n",
SRestriction.Indent(depth),
*prop
);
return s;
}
public SearchQuery ToSearchQuery()
{
object value = prop->ToObject();
return new SearchQuery.PropertyCompare(ulPropTag.ToPropertyIdentifier(),
(SearchQuery.ComparisonOperation)(int)relop,
prop->ToObject());
value
);
}
}
@ -76,10 +98,24 @@ namespace Acacia.Native.MAPI
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + ulFuzzyLevel + ":" + ulPropTag.ToString();
s += ":" + prop->ToString();
s += "\n";
string s = string.Format(
"{0}lpRes->res.resContent.ulFuzzyLevel = FL_{2} = 0x{1:X8}\n" +
"{0}lpRes->res.resContent.ulPropTag = {3}\n",
SRestriction.Indent(depth),
(int)ulFuzzyLevel,
ulFuzzyLevel,
ulPropTag
);
s += string.Format("{0}lpRes->res.resContent.lpProp->ulPropTag = {1}\n",
SRestriction.Indent(depth),
ulPropTag
);
s += string.Format("{0}lpRes->res.resContent.lpProp->Value = {1}\n",
SRestriction.Indent(depth),
*prop
);
return s;
}
@ -120,19 +156,20 @@ namespace Acacia.Native.MAPI
public uint cb;
public SRestriction* ptr;
public string ToString(int depth)
public string ToString(string name, int depth)
{
string s = "";
string s = string.Format("{0}lpRes->res.res{1}.cRes = 0x{2:X8}\n", SRestriction.Indent(depth), name, cb);
for (uint i = 0; i < cb; ++i)
{
s += ptr[i].ToString(depth);
s += string.Format("{0}lpRes->res.res{1}.lpRes[0x{2:X8}]\n", SRestriction.Indent(depth), name, i);
s += ptr[i].ToString(depth + 1);
}
return s;
}
public SearchQuery ToSearchQuery(bool and)
{
SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or(); ;
SearchQuery.MultiOperator oper = and ? (SearchQuery.MultiOperator)new SearchQuery.And() : new SearchQuery.Or();
for (uint i = 0; i < cb; ++i)
{
oper.Add(ptr[i].ToSearchQuery());
@ -148,7 +185,10 @@ namespace Acacia.Native.MAPI
public string ToString(int depth)
{
return ptr->ToString(depth);
string s = string.Format("{0}lpRes->res.resNot.ulReserved = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved);
s += string.Format("{0}lpRes->res.resNot.lpRes\n", SRestriction.Indent(depth));
s += ptr->ToString(depth + 1);
return s;
}
public SearchQuery ToSearchQuery()
@ -175,8 +215,10 @@ namespace Acacia.Native.MAPI
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
return indent + ToString() + "\n";
string s = string.Format("{0}lpRes->res.resBitMask.relBMR = BMR_{1} = 0x{2:X8}\n", SRestriction.Indent(depth), bmr, (int)bmr);
s += string.Format("{0}lpRes->res.resBitMask.ulMask = 0x{1:X8}\n", SRestriction.Indent(depth), mask);
s += string.Format("{0}lpRes->res.resBitMask.ulPropTag = 0x{1:X8}\n", SRestriction.Indent(depth), prop);
return s;
}
public SearchQuery ToSearchQuery()
@ -197,8 +239,10 @@ namespace Acacia.Native.MAPI
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
return indent + prop.ToString() + "\n";
string s = string.Format("{0}lpRes->res.resExist.ulPropTag = 0x{1:X8}\n", SRestriction.Indent(depth), prop);
s += string.Format("{0}lpRes->res.resExist.ulReserved1 = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved1);
s += string.Format("{0}lpRes->res.resExist.ulReserved2 = 0x{1:X8}\n", SRestriction.Indent(depth), dwReserved2);
return s;
}
public SearchQuery ToSearchQuery()
@ -281,30 +325,34 @@ namespace Acacia.Native.MAPI
return ToString(0);
}
internal static string Indent(int depth)
{
return new string('\t', depth);
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + header.rt.ToString() + "\n" + indent + "{\n";
string s = Indent(depth) + string.Format("lpRes->rt = 0x{0:X} = RES_{1}\n", (int)header.rt, header.rt);
switch (header.rt)
{
case RestrictionType.AND:
case RestrictionType.OR:
s += data.sub.ToString(depth + 1);
s += data.sub.ToString(header.rt.ToString().ToTitle(), depth);
break;
case RestrictionType.NOT:
s += data.not.ToString(depth + 1);
s += data.not.ToString(depth);
break;
case RestrictionType.CONTENT:
s += data.content.ToString(depth + 1);
s += data.content.ToString(depth);
break;
case RestrictionType.PROPERTY:
s += data.prop.ToString(depth + 1);
s += data.prop.ToString(depth);
break;
case RestrictionType.BITMASK:
s += data.bitMask.ToString(depth + 1);
s += data.bitMask.ToString(depth);
break;
case RestrictionType.EXIST:
s += data.exist.ToString(depth + 1);
s += data.exist.ToString(depth);
break;
/* TODO COMPAREPROPS,
@ -316,158 +364,7 @@ namespace Acacia.Native.MAPI
ANNOTATION*/
}
s += indent + "}\n";
return s;
}
}
/// <summary>
/// Encodes a search as an SRestriction. Note that as memory needs to be managed for the miscellaneous structures,
/// the SRestriction is only valid until RestrictionEncoder is disposed.
/// </summary>
unsafe public class RestrictionEncoder : NativeEncoder, ISearchEncoder
{
private class EncodingStack
{
public SRestriction[] array;
public int index;
public SRestriction* ptr;
public EncodingStack(int count, Allocation<SRestriction[]> alloc)
{
array = alloc.Object;
index = 0;
ptr = (SRestriction*)alloc.Pointer;
}
}
private readonly Stack<EncodingStack> _current = new Stack<EncodingStack>();
private readonly EncodingStack _root;
public RestrictionEncoder()
{
// Create an object for the root element
_root = Begin(1);
}
protected override void DoRelease()
{
base.DoRelease();
}
public SRestriction Restriction
{
get { return _root.array[0]; }
}
private SRestriction* Current
{
get
{
EncodingStack top = _current.Peek();
return top.ptr + top.index;
}
}
public void Encode(SearchQuery.PropertyExists part)
{
Current->header.rt = RestrictionType.EXIST;
Current->data.exist.prop = part.Property.Tag;
}
public void Encode(SearchQuery.Or part)
{
Current->header.rt = RestrictionType.OR;
Current->data.sub.cb = (uint)part.Operands.Count;
Current->data.sub.ptr = EncodePointer(part.Operands);
}
public void Encode(SearchQuery.PropertyIdentifier part)
{
// This should be unreachable
throw new InvalidProgramException();
}
public void Encode(SearchQuery.Not part)
{
Current->header.rt = RestrictionType.NOT;
Current->data.not.ptr = EncodePointer(new[] { part.Operand });
}
public void Encode(SearchQuery.And part)
{
Current->header.rt = RestrictionType.AND;
Current->data.sub.cb = (uint)part.Operands.Count;
Current->data.sub.ptr = EncodePointer(part.Operands);
}
private SRestriction* EncodePointer(IEnumerable<SearchQuery> operands)
{
EncodingStack alloc = Begin(operands.Count());
try
{
foreach (SearchQuery operand in operands)
{
operand.Encode(this);
++alloc.index;
}
}
finally
{
End();
}
return alloc.ptr;
}
private EncodingStack Begin(int count)
{
// Allocate and push the array
EncodingStack alloc = new EncodingStack(count, Allocate(new SRestriction[count]));
_current.Push(alloc);
return alloc;
}
private void End()
{
_current.Pop();
}
public void Encode(SearchQuery.PropertyContent part)
{
Current->header.rt = RestrictionType.CONTENT;
Current->data.content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part);
Current->data.content.ulPropTag = part.Property.Tag;
Current->data.content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content);
}
public void Encode(SearchQuery.PropertyCompare part)
{
Current->header.rt = RestrictionType.PROPERTY;
Current->data.prop.relop = (SearchOperation)part.Operation;
Current->data.prop.ulPropTag = part.Property.Tag;
Current->data.prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value);
}
public void Encode(SearchQuery.PropertyBitMask part)
{
Current->header.rt = RestrictionType.BITMASK;
Current->data.bitMask.bmr = (BMR)(int)part.Operation;
Current->data.bitMask.prop = part.Property.Tag;
Current->data.bitMask.mask = part.Mask;
}
}
public static class RestrictionExensions
{
/// <summary>
/// Encodes the search as an SRestriction.
/// </summary>
/// <returns>The encoder containing the restriction. The caller is responsible for disposing.</returns>
public static RestrictionEncoder ToRestriction(this SearchQuery search)
{
RestrictionEncoder encoder = new RestrictionEncoder();
search.Encode(encoder);
return encoder;
}
}
}

View File

@ -0,0 +1,249 @@
using Acacia.Stubs;
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
/// <summary>
/// Encodes a search as an SRestriction. Note that as memory needs to be managed for the miscellaneous structures,
/// the SRestriction is only valid until RestrictionEncoder is disposed.
/// </summary>
unsafe public class RestrictionEncoder : DisposableWrapper, ISearchEncoder
{
private class Allocation
{
public readonly IntPtr ptr;
private readonly int count;
private int index;
public SRestriction* Current
{
get
{
if (index >= count)
throw new InvalidProgramException();
return Pointer + index;
}
}
public SRestriction* Pointer { get { return (SRestriction*)ptr; } }
/// <summary>
/// Constructor. Allocates the memory for the object.
/// </summary>
/// <param name="root">The root allocation, or null if this is the root. All allocations will be added to the root.</param>
/// <param name="count">The number of SRestriction objects to allocate</param>
public Allocation(RestrictionEncoder encoder, int count)
{
this.count = count;
// Allocate the buffer
ptr = encoder.AllocateArray<SRestriction>(count);
}
public void Next()
{
++index;
}
}
private Allocation _root;
private readonly Stack<Allocation> _stack = new Stack<Allocation>();
public RestrictionEncoder()
{
// Push the root entry
_root = Push(1);
}
protected override void DoRelease()
{
if (_root != null)
{
uint res = MAPI.MAPIFreeBuffer(_root.ptr);
if (res != 0)
{
// TODO: log?
}
_root = null;
}
}
public SRestriction* Encoded
{
get { return _root.Pointer; }
}
public SRestriction* Current
{
get { return _stack.Peek().Current; }
}
private Allocation Push(int count = 1)
{
Allocation alloc = new Allocation(this, count);
_stack.Push(alloc);
return alloc;
}
private void Pop(Allocation expected)
{
Allocation alloc = _stack.Pop();
if (expected != alloc)
throw new InvalidProgramException();
}
public void Encode(SearchQuery.PropertyExists part)
{
Current->header.rt = RestrictionType.EXIST;
Current->data.exist.prop = part.Property.Tag;
}
public void Encode(SearchQuery.Or part)
{
Current->header.rt = RestrictionType.OR;
Current->data.sub.cb = (uint)part.Operands.Count;
Current->data.sub.ptr = EncodePointer(part.Operands);
}
public void Encode(SearchQuery.And part)
{
Current->header.rt = RestrictionType.AND;
Current->data.sub.cb = (uint)part.Operands.Count;
Current->data.sub.ptr = EncodePointer(part.Operands);
}
public void Encode(SearchQuery.PropertyIdentifier part)
{
// This should be unreachable
throw new InvalidProgramException();
}
public void Encode(SearchQuery.Not part)
{
Current->header.rt = RestrictionType.NOT;
Current->data.not.ptr = EncodePointer(new[] { part.Operand });
}
private SRestriction* EncodePointer(IEnumerable<SearchQuery> operands)
{
Allocation alloc = Push(operands.Count());
try
{
foreach (SearchQuery operand in operands)
{
operand.Encode(this);
alloc.Next();
}
}
finally
{
Pop(alloc);
}
return alloc.Pointer;
}
public void Encode(SearchQuery.PropertyContent part)
{
Current->header.rt = RestrictionType.CONTENT;
Current->data.content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part);
Current->data.content.ulPropTag = part.Property.Tag;
Current->data.content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content);
}
public void Encode(SearchQuery.PropertyCompare part)
{
Current->header.rt = RestrictionType.PROPERTY;
Current->data.prop.relop = (SearchOperation)part.Operation;
Current->data.prop.ulPropTag = part.Property.Tag;
Current->data.prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value);
}
public void Encode(SearchQuery.PropertyBitMask part)
{
Current->header.rt = RestrictionType.BITMASK;
Current->data.bitMask.bmr = (BMR)(int)part.Operation;
Current->data.bitMask.prop = part.Property.Tag;
Current->data.bitMask.mask = part.Mask;
}
public IntPtr AllocateArray<StructType>(int count)
{
// Try to just determine the size based on the type. If that fails, determine the size of a default object
int structSize = Marshal.SizeOf(typeof(StructType));
return AllocateRaw(structSize * count);
}
public IntPtr AllocateWithExtra<PrimaryType>(int alignExtra, object extra)
{
// Determine the size
int size = Marshal.SizeOf<PrimaryType>();
size = Util.Align(size, alignExtra);
int extraOffset = size;
size += Marshal.SizeOf(extra);
// Allocate
IntPtr ptr = AllocateRaw(size);
// Copy the extra structure
Marshal.StructureToPtr(extra, ptr + extraOffset, false);
return ptr;
}
private IntPtr AllocateRaw(int size)
{
IntPtr res;
IntPtr ptr = IntPtr.Zero;
if (_root == null)
{
res = MAPI.MAPIAllocateBuffer((uint)size, ref ptr);
}
else
{
res = MAPI.MAPIAllocateMore((uint)size, _root.ptr, ref ptr);
}
if (res != IntPtr.Zero)
throw new InvalidOperationException("MAPI Allocation failed: " + res);
// Zero it out to prevent issues
Kernel32.ZeroMemory(ptr, size);
return ptr;
}
/// <summary>
/// Allocates a copy of the byte array.
/// </summary>
/// <param name="bytes">The byte array</param>
/// <param name="zeros">Number of additional zero bytes, for padding</param>
public IntPtr Allocate(byte[] bytes, int zeros = 0)
{
IntPtr ptr = AllocateRaw(bytes.Length + zeros);
Marshal.Copy(bytes, 0, ptr, bytes.Length);
for (int i = 0; i < zeros; ++i)
{
((byte*)ptr)[bytes.Length + i] = 0;
}
return ptr;
}
}
public static class RestrictionExensions
{
/// <summary>
/// Encodes the search as an SRestriction.
/// </summary>
/// <returns>The encoder containing the restriction. The caller is responsible for disposing.</returns>
public static RestrictionEncoder ToRestriction(this SearchQuery search)
{
RestrictionEncoder encoder = new RestrictionEncoder();
search.Encode(encoder);
return encoder;
}
}
}

View File

@ -1,175 +0,0 @@
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Acacia.Native.MAPI;
namespace Acacia.Native
{
/// <summary>
/// Helper for encoding objects into native structures.
/// </summary>
abstract public class NativeEncoder : DisposableWrapper
{
protected class AllocationBase : IDisposable
{
protected readonly object _obj;
protected readonly GCHandle _handle;
protected readonly IntPtr _ptr;
public AllocationBase(Type type, int size)
{
_ptr = Marshal.AllocHGlobal(size);
}
public AllocationBase(object obj)
{
this._obj = obj;
_handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
_ptr = _handle.AddrOfPinnedObject();
}
public IntPtr Pointer { get { return _ptr; } }
public void Dispose()
{
if (_handle.IsAllocated)
_handle.Free();
else
Marshal.FreeHGlobal(_ptr);
}
}
unsafe protected class Allocation<ObjType> : AllocationBase
{
internal Allocation(int size) : base(typeof(ObjType), size)
{
}
internal Allocation(ObjType obj) : base(obj)
{
}
public ObjType Object
{
get
{
return (ObjType)_obj;
}
}
}
private readonly List<AllocationBase> _allocs = new List<AllocationBase>();
override protected void DoRelease()
{
foreach(AllocationBase alloc in _allocs)
alloc.Dispose();
}
protected AllocationBase Allocate(int size)
{
AllocationBase alloc = new AllocationBase(typeof(object), size);
_allocs.Add(alloc);
return alloc;
}
protected Allocation<ObjType> Allocate<ObjType>(ObjType obj)
{
Allocation<ObjType> alloc = new Allocation<ObjType>(obj);
_allocs.Add(alloc);
return alloc;
}
protected Allocation<ObjType> Allocate<ObjType>()
{
return Allocate<ObjType>(Activator.CreateInstance<ObjType>());
}
// TODO: put in lib
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public IntPtr Allocate<ElementType>(ElementType[] obj, params ElementType[][] additional)
{
ElementType[][] all = new ElementType[][] { obj }.Concat(additional).ToArray();
int size = 0;
int[] starts = new int[all.Length];
int[] sizes = new int[all.Length];
for (int i = 0; i < all.Length; ++i)
{
starts[i] = size;
int thisSize = ((Array)all[i]).Length * Marshal.SizeOf<ElementType>();
sizes[i] = thisSize;
size += thisSize;
}
AllocationBase alloc = Allocate(size);
IntPtr ptr = alloc.Pointer;
for (int i = 0; i < all.Length; ++i)
{
GCHandle handle = GCHandle.Alloc(all[i], GCHandleType.Pinned);
try
{
CopyMemory(ptr + starts[i], handle.AddrOfPinnedObject(), (uint)sizes[i]);
}
finally
{
handle.Free();
}
}
return alloc.Pointer;
}
/// <summary>
/// Returns a block of memory containing all specified objects sequentially.
/// </summary>
public IntPtr Allocate(object obj, params object[] additional)
{
object[] all = new object[] { obj }.Concat(additional).ToArray();
int size = 0;
int[] starts = new int[all.Length];
object[] encode = new object[all.Length];
int used = 0;
int align = -1;
for (int i = 0; i < all.Length; ++i)
{
if (all[i] is int)
{
align = (int)all[i];
++i;
}
starts[used] = Align(size, align);
int thisSize = Marshal.SizeOf(all[i]);
size = starts[used] + thisSize;
encode[used] = all[i];
align = -1;
++used;
}
AllocationBase alloc = Allocate(size);
IntPtr ptr = alloc.Pointer;
for (int i = 0; i < used; ++i)
{
Marshal.StructureToPtr(encode[i], ptr + starts[i], false);
}
return alloc.Pointer;
}
private int Align(int size, int align)
{
if (align < 0)
align = Marshal.SizeOf<IntPtr>();
int additional = (align - (size % align)) % align;
return size + additional;
}
}
}

View File

@ -121,6 +121,12 @@ namespace Acacia.Stubs
set;
}
bool SearchRunning
{
get;
set;
}
void Save();
void SetCustomIcon(IPicture icon);

View File

@ -392,7 +392,56 @@ namespace Acacia.Stubs.OutlookWrappers
set;
}
#region Search criteria
unsafe public SearchQuery SearchCriteria
{
get
{
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
SBinaryArray* sb1 = null;
SRestriction* restrict = null;
try
{
SearchCriteriaState state;
imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
Logger.Instance.Debug(this, "GetSearchCriteria: {0}: {1}\n{2}", Name, state,
restrict == null ? "<NODE>" : restrict->ToString());
return restrict->ToSearchQuery();
}
finally
{
MAPI.MAPIFreeBuffer((IntPtr)restrict);
MAPI.MAPIFreeBuffer((IntPtr)sb1);
ComRelease.Release(imapi);
}
}
set
{
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
try
{
using (RestrictionEncoder res = value.ToRestriction())
{
SRestriction* resEncoded = res.Encoded;
Logger.Instance.Debug(this, "SetSearchCriteria: {0}\n{1}", Name, resEncoded == null ? "<NODE>" : resEncoded->ToString());
imapi.SetSearchCriteria(resEncoded, null, SearchCriteriaFlags.NONE);
}
}
catch (Exception e)
{
Logger.Instance.Error(this, "Exception in SetSearchCriteria: {0}: {1}", Name, e);
}
finally
{
ComRelease.Release(imapi);
}
}
}
unsafe public bool SearchRunning
{
get
{
@ -400,10 +449,13 @@ namespace Acacia.Stubs.OutlookWrappers
try
{
SearchCriteriaState state;
SBinaryArray* sb1;
SRestriction* restrict;
imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
return restrict->ToSearchQuery();
imapi.GetSearchCriteria(0, null, null, out state);
return (state & SearchCriteriaState.SEARCH_RUNNING) != 0;
}
catch (Exception e)
{
Logger.Instance.Error(this, "Exception in GetSearchRunning: {0}: {1}", Name, e);
return true;
}
finally
{
@ -416,11 +468,11 @@ namespace Acacia.Stubs.OutlookWrappers
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
try
{
using (RestrictionEncoder res = value.ToRestriction())
{
SRestriction restrict = res.Restriction;
imapi.SetSearchCriteria(&restrict, null, SearchCriteriaFlags.NONE);
}
imapi.SetSearchCriteria(null, null, value ? SearchCriteriaFlags.RESTART_SEARCH : SearchCriteriaFlags.STOP_SEARCH);
}
catch (Exception e)
{
Logger.Instance.Error(this, "Exception in SetSearchRunning: {0}: {1}", Name, e);
}
finally
{
@ -428,5 +480,7 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
}
#endregion
}
}

View File

@ -1,4 +1,6 @@
/// Copyright 2017 Kopano b.v.

using Acacia.Native;
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -13,7 +15,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Drawing;
@ -27,9 +28,6 @@ namespace Acacia.Utils
{
public static class ImageUtils
{
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
@ -49,7 +47,7 @@ namespace Acacia.Utils
{
IntPtr target = bmpData2.Scan0 + bmpData2.Stride * y;
IntPtr source = bmpData.Scan0 + bmpData.Stride * y;
CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
Kernel32.CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
}
}
finally

View File

@ -49,6 +49,13 @@ namespace Acacia.Utils
return _this;
}
public static string ToTitle(this string _this)
{
if (string.IsNullOrWhiteSpace(_this))
return _this;
return _this.Substring(0, 1).ToUpper() + _this.Substring(1).ToLower();
}
#endregion

View File

@ -173,5 +173,11 @@ namespace Acacia.Utils
}
#endregion
public static int Align(int size, int align)
{
int additional = (align - (size % align)) % align;
return size + additional;
}
}
}