[KOE-117] Rewrote search query encoding in an attempt to prevent reminders from popping up. Also limiting rewriting of query to prevent writing back if unchanged. Added the "RemindersKeepRunning" option to make it possible to stop the query when updating.

This commit is contained in:
Patrick Simpson 2017-06-01 15:03:51 +02:00
parent b5ee982bc7
commit e806928db6
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;
}
}
}