[KOE-12] Finished encoding of search criteria for COM. Added code to modify search query to exclude shared folders, still need to add code to allow for selected folders.

This commit is contained in:
Patrick Simpson 2017-02-27 19:04:41 +01:00
parent e0356b0321
commit 8f5c08e4a9
9 changed files with 554 additions and 121 deletions

View File

@ -274,6 +274,7 @@
</Compile>
<Compile Include="Features\SendAs\FeatureSendAs.cs" />
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
<Compile Include="Features\SharedFolders\SharedCalendarReminders.cs" />
<Compile Include="Features\Signatures\FeatureSignatures.cs" />
<Compile Include="Features\Signatures\SignaturesSettings.cs">
<SubType>UserControl</SubType>
@ -291,6 +292,7 @@
<Compile Include="Native\MAPI\IMAPIProp.cs" />
<Compile Include="Native\MAPI\Property.cs" />
<Compile Include="Native\MAPI\Restriction.cs" />
<Compile Include="Native\NativeEncoder.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="SearchQuery.cs" />
<Compile Include="Stubs\Enums.cs" />

View File

@ -0,0 +1,80 @@
using Acacia.Native.MAPI;
using Acacia.Stubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Features.SharedFolders
{
public class SharedCalendarReminders : LogContext
{
private static readonly SearchQuery.PropertyIdentifier PROP_FOLDER = new SearchQuery.PropertyIdentifier(PropTag.FromInt(0x6B20001F));
private readonly LogContext _context;
public string LogContextId
{
get
{
return _context.LogContextId;
}
}
public SharedCalendarReminders(LogContext context)
{
this._context = context;
}
public void Initialise(IStore store)
{
using (IFolder reminders = store.GetSpecialFolder(SpecialFolder.Reminders))
{
SearchQuery.Or custom = FindCustomQuery(reminders, true);
}
}
private SearchQuery.Or FindCustomQuery(IFolder reminders, bool addIfNeeded)
{
SearchQuery query = reminders.SearchCriteria;
if (!(query is SearchQuery.And))
return null;
Logger.Instance.Trace(this, "Current query1: {0}", query.ToString());
SearchQuery.And root = (SearchQuery.And)query;
// TODO: more strict checking of query
if (root.Operands.Count == 3)
{
SearchQuery.Or custom = root.Operands.ElementAt(2) as SearchQuery.Or;
if (custom != null)
{
// TODO: check property test
return custom;
}
}
// We have the root, but not the custom query. Create it if needed.
if (addIfNeeded)
{
Logger.Instance.Debug(this, "Creating custom query");
Logger.Instance.Trace(this, "Current query: {0}", root.ToString());
SearchQuery.Or custom = new SearchQuery.Or();
// Add the prefix exclusion for shared folders
custom.Add(
new SearchQuery.Not(
new SearchQuery.PropertyContent(
PROP_FOLDER, SearchQuery.ContentMatchOperation.Prefix, SearchQuery.ContentMatchModifiers.None, "S"
)
)
);
root.Operands.Add(custom);
Logger.Instance.Trace(this, "Modified query: {0}", root.ToString());
reminders.SearchCriteria = root;
Logger.Instance.Trace(this, "Modified query2: {0}", reminders.SearchCriteria.ToString());
}
return null;
}
}
}

View File

@ -122,7 +122,7 @@ namespace Acacia.Features.Signatures
/// <param name="serverSignatureHash">The signature hash. If null, the hash will not be checked and a hard sync will be done.</param>
private void SyncSignatures(ZPushAccount account, string serverSignatureHash)
{
if (account == null || !account.Capabilities.Has("signatures"))
if (account?.Capabilities == null || !account.Capabilities.Has("signatures"))
return;
// Check hash if needed

View File

@ -33,10 +33,22 @@ namespace Acacia.Native.MAPI
public byte[] Unmarshal()
{
byte[] result = new byte[cb];
Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
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())
};
}
public override string ToString()
{
byte[] b = Unmarshal();

View File

@ -20,6 +20,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Acacia.Native.NativeEncoder;
namespace Acacia.Native.MAPI
{
@ -52,7 +53,7 @@ namespace Acacia.Native.MAPI
public SearchQuery.PropertyIdentifier ToPropertyIdentifier()
{
return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)type);
return new SearchQuery.PropertyIdentifier(this);
}
public static PropTag FromInt(int v)
@ -65,80 +66,110 @@ namespace Acacia.Native.MAPI
}
}
[StructLayout(LayoutKind.Explicit)]
unsafe public struct PropValue
// TODO: align is probably wrong for 32-bit
[StructLayout(LayoutKind.Sequential)]
public struct PropValue
{
[FieldOffset(0)]
public PropTag ulPropTag;
[StructLayout(LayoutKind.Sequential)]
public struct Header
{
public PropTag ulPropTag;
}
[FieldOffset(4)]
public uint dwAlignPad;
[StructLayout(LayoutKind.Explicit)]
unsafe public struct Data
{
// 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(0), MarshalAs(UnmanagedType.U2)]
public bool b;
// 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 */
// CURRENCY cur; /* case PT_CURRENCY */
// double at; /* case PT_APPTIME */
// FILETIME ft; /* case PT_SYSTIME */
// LPSTR lpszA; /* case PT_STRING8 */
[FieldOffset(0), MarshalAs(UnmanagedType.LPStr)]
public sbyte* lpszA;
// LPSTR lpszA; /* case PT_STRING8 */
[FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
public sbyte* lpszA;
// SBinary bin; /* case PT_BINARY */
[FieldOffset(0)]
public SBinary bin;
// SBinary bin; /* case PT_BINARY */
[FieldOffset(8)]
public SBinary bin;
// LPWSTR lpszW; /* case PT_UNICODE */
[FieldOffset(0), MarshalAs(UnmanagedType.LPWStr)]
public char* lpszW;
// 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 */
// 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) */
}
// 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 Header header;
public Data data;
public override string ToString()
{
return ToObject()?.ToString() ?? "<unknown>";
}
public object ToObject()
unsafe public object ToObject()
{
switch (ulPropTag.type)
switch (header.ulPropTag.type)
{
case PropType.BOOLEAN:
return b;
return data.b;
case PropType.STRING8:
return new string(lpszA);
return new string(data.lpszA);
case PropType.UNICODE:
return new string(data.lpszW);
case PropType.BINARY:
return bin;
//case PropType.UNICODE:
// return lpszW.ToString();
return data.bin;
}
throw new NotImplementedException();
}
unsafe public static IntPtr MarshalFromObject(NativeEncoder encoder, PropTag prop, object value)
{
PropValue obj = new PropValue();
obj.header.ulPropTag = prop;
switch (prop.type)
{
case PropType.BOOLEAN:
obj.data.b = (bool)value;
return encoder.Allocate(obj.header, obj.data.b);
case PropType.STRING8:
IntPtr ptrA = encoder.Allocate(Encoding.ASCII.GetBytes((string)value), new byte[] { 0 });
return encoder.Allocate(obj.header, ptrA);
case PropType.UNICODE:
IntPtr ptrW = encoder.Allocate(Encoding.Unicode.GetBytes((string)value), new byte[] { 0, 0 });
return encoder.Allocate(obj.header, ptrW);
case PropType.BINARY:
obj.data.bin = ((SBinary)value).Marshal(encoder);
return encoder.Allocate(obj.header, obj.data.bin);
default:
throw new NotImplementedException();
}
return null;
}
}
}

View File

@ -1,5 +1,6 @@

using Acacia.Stubs;
using Acacia.Utils;
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
@ -69,14 +70,14 @@ namespace Acacia.Native.MAPI
unsafe public struct ContentRestriction
{
public FuzzyLevel fuzzy;
public FuzzyLevel ulFuzzyLevel;
public PropTag ulPropTag;
public PropValue* prop;
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + fuzzy + ":" + ulPropTag.ToString();
string s = indent + ulFuzzyLevel + ":" + ulPropTag.ToString();
s += ":" + prop->ToString();
s += "\n";
return s;
@ -85,9 +86,15 @@ namespace Acacia.Native.MAPI
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyContent(ulPropTag.ToPropertyIdentifier(),
(uint)fuzzy, // TODO
(SearchQuery.ContentMatchOperation)((uint)ulFuzzyLevel & 0xF),
(SearchQuery.ContentMatchModifiers)(((uint)ulFuzzyLevel & 0xF0000) >> 16),
prop->ToObject());
}
public static FuzzyLevel FuzzyLevelFromSearchQuery(SearchQuery.PropertyContent search)
{
return (FuzzyLevel)((int)search.Operation | ((int)search.Modifiers << 16));
}
}
// TODO: merge with ISearch
@ -174,7 +181,7 @@ namespace Acacia.Native.MAPI
public SearchQuery ToSearchQuery()
{
return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), bmr == BMR.EQZ, mask);
return new SearchQuery.PropertyBitMask(prop.ToPropertyIdentifier(), (SearchQuery.BitMaskOperation)(int)bmr, mask);
}
}
@ -305,28 +312,153 @@ namespace Acacia.Native.MAPI
}
}
/* Example search code
/// <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
{
MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders);
dynamic obj = folder.MAPIOBJECT;
IMAPIFolder imapi = obj as IMAPIFolder;
private class EncodingStack
{
public SRestriction[] array;
public int index;
public SRestriction* ptr;
//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());
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;
restrict->rt = RestrictionType.AND;
imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE);
public RestrictionEncoder()
{
// Create an object for the root element
_root = Begin(1);
}
protected override void DoRelease()
{
base.DoRelease();
}
//SBinaryArray sb = Marshal.PtrToStructure<SBinaryArray>(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);
} */
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->rt = RestrictionType.EXIST;
Current->exist.prop = part.Property.Tag;
}
public void Encode(SearchQuery.Or part)
{
Current->rt = RestrictionType.OR;
Current->sub.cb = (uint)part.Operands.Count;
Current->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->rt = RestrictionType.NOT;
Current->not.ptr = EncodePointer(new[] { part.Operand });
}
public void Encode(SearchQuery.And part)
{
Current->rt = RestrictionType.AND;
Current->sub.cb = (uint)part.Operands.Count;
Current->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->rt = RestrictionType.CONTENT;
Current->content.ulFuzzyLevel = ContentRestriction.FuzzyLevelFromSearchQuery(part);
Current->content.ulPropTag = part.Property.Tag;
Current->content.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Content);
}
public void Encode(SearchQuery.PropertyCompare part)
{
Current->rt = RestrictionType.PROPERTY;
Current->prop.relop = (SearchOperation)part.Operation;
Current->prop.ulPropTag = part.Property.Tag;
Current->prop.prop = (PropValue*)PropValue.MarshalFromObject(this, part.Property.Tag, part.Value);
}
public void Encode(SearchQuery.PropertyBitMask part)
{
Current->rt = RestrictionType.BITMASK;
Current->bitMask.bmr = (BMR)(int)part.Operation;
Current->bitMask.prop = part.Property.Tag;
Current->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

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Acacia.Native.MAPI;
namespace Acacia.Native
{
@ -13,7 +14,7 @@ namespace Acacia.Native
/// </summary>
abstract public class NativeEncoder : DisposableWrapper
{
abstract protected class AllocationBase
protected class AllocationBase : IDisposable
{
protected readonly object _obj;
protected readonly GCHandle _handle;
@ -31,15 +32,24 @@ namespace Acacia.Native
_ptr = _handle.AddrOfPinnedObject();
}
// TODO: release
public IntPtr Pointer { get { return _ptr; } }
public void Dispose()
{
if (_handle.IsAllocated)
_handle.Free();
else
Marshal.FreeHGlobal(_ptr);
}
}
unsafe protected class Allocation<ObjType> : AllocationBase
{
public Allocation(int size) : base(typeof(ObjType), size)
internal Allocation(int size) : base(typeof(ObjType), size)
{
}
public Allocation(ObjType obj) : base(obj)
internal Allocation(ObjType obj) : base(obj)
{
}
@ -50,26 +60,21 @@ namespace Acacia.Native
return (ObjType)_obj;
}
}
public IntPtr Pointer
{
get
{
return _ptr;
}
}
}
private readonly List<AllocationBase> _allocs = new List<AllocationBase>();
/// <summary>
/// Allocates an object of the specified type. The allocation is managed by this encoder.
/// </summary>
/// <param name="size">If larger than 0, the size to allocate. Otherwise, the size of the object is used.</param>
/// <returns>The allocated object.</returns>
protected Allocation<ObjType> Allocate<ObjType>(int size = -1)
override protected void DoRelease()
{
throw new NotImplementedException();
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)
@ -78,5 +83,80 @@ namespace Acacia.Native
_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];
for (int i = 0; i < all.Length; ++i)
{
starts[i] = Align(size);
int thisSize = Marshal.SizeOf(all[i]);
size = starts[i] + thisSize;
}
AllocationBase alloc = Allocate(size);
IntPtr ptr = alloc.Pointer;
for (int i = 0; i < all.Length; ++i)
{
Marshal.StructureToPtr(all[i], ptr + starts[i], false);
}
return alloc.Pointer;
}
private int Align(int size)
{
int align = Marshal.SizeOf<IntPtr>();
int additional = (align - (size % align)) % align;
return size + additional;
}
}
}

View File

@ -32,6 +32,7 @@ namespace Acacia
void Encode(SearchQuery.And part);
void Encode(SearchQuery.Or part);
void Encode(SearchQuery.Not part);
void Encode(SearchQuery.PropertyIdentifier part);
}
public class ToStringEncoder : ISearchEncoder
@ -46,21 +47,20 @@ namespace Acacia
public void Encode(SearchQuery.And part)
{
EncodeMulti(part, "AND");
EncodeMulti("AND", part.Operands);
}
public void Encode(SearchQuery.Or part)
{
EncodeMulti(part, "OR");
EncodeMulti("OR", part.Operands);
}
public void Encode(SearchQuery.Not part)
{
_builder.Append("NOT ");
part.Operand.Encode(this);
EncodeMulti("NOT", new[] { part.Operand });
}
private void EncodeMulti(SearchQuery.MultiOperator part, string oper)
private void EncodeMulti(string oper, IEnumerable<SearchQuery> parts)
{
Indent();
_builder.Append(oper).Append("\n");
@ -69,7 +69,7 @@ namespace Acacia
++_indent;
foreach (SearchQuery operand in part.Operands)
foreach (SearchQuery operand in parts)
operand.Encode(this);
--_indent;
@ -80,22 +80,59 @@ namespace Acacia
public void Encode(SearchQuery.PropertyBitMask part)
{
_builder.Append("BITMASK:").Append(part.Property); // TODO: operator/value
Indent();
_builder.Append("BITMASK{");
part.Property.Encode(this);
_builder.Append(" ").Append(part.Operation).Append(" ");
_builder.Append(part.Mask.ToString("X8"));
_builder.Append("}\n");
}
private static readonly string[] COMPARISON_OPERATORS = {"<", "<=", ">", ">=", "==", "!=", "LIKE"};
public void Encode(SearchQuery.PropertyCompare part)
{
_builder.Append("COMPARE:").Append(part.Property); // TODO: operator/value
Indent();
_builder.Append("COMPARE{");
part.Property.Encode(this);
_builder.Append(" ").Append(COMPARISON_OPERATORS[(int)part.Operation]).Append(" ");
_builder.Append(part.Value);
_builder.Append("}\n");
}
public void Encode(SearchQuery.PropertyContent part)
{
_builder.Append("CONTENT:").Append(part.Property); // TODO: operator/value
Indent();
_builder.Append("CONTENT{");
part.Property.Encode(this);
List<string> options = new List<string>();
if (part.Operation != SearchQuery.ContentMatchOperation.Full)
options.Add(part.Operation.ToString());
if (part.Modifiers != SearchQuery.ContentMatchModifiers.None)
{
options.Add(part.Modifiers.ToString());
}
string optionsString = options.Count == 0 ? "" : ("(" + string.Join(",", options) + ")");
_builder.Append(" ==").Append(optionsString).Append(" ");
_builder.Append(part.Content);
_builder.Append("}\n");
}
public void Encode(SearchQuery.PropertyExists part)
{
_builder.Append("EXISTS:").Append(part.Property);
Indent();
_builder.Append("EXISTS{");
part.Property.Encode(this);
_builder.Append("}\n");
}
public void Encode(SearchQuery.PropertyIdentifier part)
{
_builder.Append(part.Id);
}
public string GetValue()
@ -133,7 +170,7 @@ namespace Acacia
_operands.Add(operand);
}
public IEnumerable<SearchQuery> Operands
public ICollection<SearchQuery> Operands
{
get { return _operands; }
}
@ -180,16 +217,23 @@ namespace Acacia
/// </summary>
public class PropertyIdentifier
{
private string _id;
public string Id { get; private set; }
public PropTag Tag { get; private set; }
public PropertyIdentifier(string id)
public PropertyIdentifier(PropTag tag)
{
this._id = id;
this.Tag = tag;
Id = string.Format("{0:X4}{1:X4}", tag.prop, (int)tag.type);
}
public static PropertyIdentifier FromTag(ushort prop, ushort type)
public void Encode(ISearchEncoder encoder)
{
return new PropertyIdentifier(string.Format("{0:4X}{1:4X}", prop, type));
encoder.Encode(this);
}
public override string ToString()
{
return Id;
}
}
@ -247,11 +291,44 @@ namespace Acacia
}
}
public enum ContentMatchOperation
{
/// <summary>
/// Match full content
/// </summary>
Full,
/// <summary>
/// Match part of the content
/// </summary>
SubString,
/// <summary>
/// Match the start of the content
/// </summary>
Prefix
}
[Flags]
public enum ContentMatchModifiers
{
None = 0,
CaseInsensitive = 1,
IgnoreNonSpace = 2,
Loose = 4
}
public class PropertyContent : PropertyQuery
{
public PropertyContent(PropertyIdentifier property, uint options, object content) : base(property)
public ContentMatchOperation Operation { get; set; }
public ContentMatchModifiers Modifiers { get; set; }
public object Content { get; set; }
public PropertyContent(PropertyIdentifier property, ContentMatchOperation operation, ContentMatchModifiers modifiers, object content) : base(property)
{
// TODO
this.Operation = operation;
this.Modifiers = modifiers;
this.Content = content;
}
public override void Encode(ISearchEncoder encoder)
@ -260,11 +337,20 @@ namespace Acacia
}
}
public enum BitMaskOperation
{
EQZ, NEZ
}
public class PropertyBitMask : PropertyQuery
{
public PropertyBitMask(PropertyIdentifier property, bool wantZero, uint mask) : base(property)
public BitMaskOperation Operation { get; set; }
public uint Mask { get; set; }
public PropertyBitMask(PropertyIdentifier property, BitMaskOperation operation, uint mask) : base(property)
{
// TODO
this.Operation = operation;
this.Mask = mask;
}
public override void Encode(ISearchEncoder encoder)

View File

@ -403,7 +403,6 @@ namespace Acacia.Stubs.OutlookWrappers
SBinaryArray* sb1;
SRestriction* restrict;
imapi.GetSearchCriteria(0, &restrict, &sb1, out state);
Logger.Instance.Warning(this, "SEARCH:\n{0}", restrict->ToString());
return restrict->ToSearchQuery();
}
finally
@ -414,8 +413,19 @@ namespace Acacia.Stubs.OutlookWrappers
set
{
// TODO
throw new NotImplementedException();
IMAPIFolder imapi = _item.MAPIOBJECT as IMAPIFolder;
try
{
using (RestrictionEncoder res = value.ToRestriction())
{
SRestriction restrict = res.Restriction;
imapi.SetSearchCriteria(&restrict, null, SearchCriteriaFlags.NONE);
}
}
finally
{
ComRelease.Release(imapi);
}
}
}
}