miranda/Hyphen/Plugins/Infrastructure/MirandaDatabase.cs
2013-06-25 22:53:41 +00:00

508 lines
21 KiB
C#

/***********************************************************************\
* Virtuoso.Miranda.Plugins (Hyphen) *
* Provides a managed wrapper for API of IM client Miranda. *
* Copyright (C) 2006-2009 virtuoso *
* deml.tomas@seznam.cz *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Lesser General Public License for more details. *
\***********************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using Virtuoso.Miranda.Plugins;
using System.Runtime.CompilerServices;
using Virtuoso.Miranda.Plugins.Native;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Virtuoso.Miranda.Plugins.Resources;
using System.Threading;
using System.Collections.ObjectModel;
using Virtuoso.Hyphen;
using Virtuoso.Miranda.Plugins.Helpers;
namespace Virtuoso.Miranda.Plugins.Infrastructure
{
public sealed class MirandaDatabase : EventPublisher
{
#region Constants
private const string ME_DB_EVENT_ADDED = "DB/Event/Added",
ME_DB_EVENT_DELETED = "DB/Event/Deleted",
ME_DB_EVENT_FILTER_ADD = "DB/Event/FilterAdd",
ME_DB_CONTACT_ADDED = "DB/Contact/Added",
ME_DB_CONTACT_DELETED = "DB/Contact/Deleted",
ME_DB_CONTACT_SETTINGCHANGED = "DB/Contact/SettingChanged";
private const string MS_DB_GETPROFILENAME = "DB/GetProfileName",
MS_DB_GETPROFILEPATH = "DB/GetProfilePath",
MS_DB_EVENT_ADD = "DB/Event/Add";
private const string MS_DB_CONTACT_GETCOUNT = "DB/Contact/GetCount",
MS_DB_CONTACT_FINDFIRST = "DB/Contact/FindFirst",
MS_DB_CONTACT_FINDNEXT = "DB/Contact/FindNext";
private const string MS_DB_EVENT_FINDFIRST = "DB/Event/FindFirst",
MS_DB_EVENT_FINDNEXT = "DB/Event/FindNext";
#endregion
#region .ctors
internal MirandaDatabase() { }
#endregion
#region Event handlers
private MirandaEventHandler<MirandaDatabaseEventArgs> EventAddedEventHandler,
EventDeletedEventHandler, BeforeEventAddedEventHandler;
private MirandaEventHandler<MirandaContactEventArgs> ContactAddedEventHandler,
ContactDeletedEventHandler;
private MirandaEventHandler<MirandaContactSettingEventArgs> ContactSettingChangedEventHandler;
#endregion
#region Events & Triggers
private int RaiseDbEvent(MirandaEventHandler<MirandaDatabaseEventArgs> handler, bool fromPointer, UIntPtr wParam, IntPtr lParam)
{
if (handler == null)
return 0;
ContactInfo contactInfo = ContactInfo.FromHandle(wParam);
DatabaseEventInfo eventInfo = fromPointer ? DatabaseEventInfo.FromPointer(lParam) : DatabaseEventInfo.FromHandle(lParam);
MirandaDatabaseEventArgs eventArgs = new MirandaDatabaseEventArgs(contactInfo, eventInfo);
bool retValue = InvokeChainCancelable<MirandaDatabaseEventArgs>(handler, eventArgs);
return Convert.ToInt32(retValue);
}
public event MirandaEventHandler<MirandaDatabaseEventArgs> EventAdded
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref EventAddedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_EVENT_ADDED,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseDbEvent(EventAddedEventHandler, false, wParam, lParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref EventAddedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_ADDED, EventAddedEventHandler);
}
}
public event MirandaEventHandler<MirandaDatabaseEventArgs> EventDeleted
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref EventDeletedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_EVENT_DELETED,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseDbEvent(EventDeletedEventHandler, false, wParam, lParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref EventDeletedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_DELETED, EventDeletedEventHandler);
}
}
/// <summary>
/// Return TRUE to filter out the event, FALSE to pass the message along.
/// </summary>
public event MirandaEventHandler<MirandaDatabaseEventArgs> BeforeEventAdded
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref BeforeEventAddedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_EVENT_FILTER_ADD,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseDbEvent(BeforeEventAddedEventHandler, true, wParam, lParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaDatabaseEventArgs>>(ref BeforeEventAddedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_FILTER_ADD, BeforeEventAddedEventHandler);
}
}
private int RaiseContactEvent(MirandaEventHandler<MirandaContactEventArgs> handler, UIntPtr wParam)
{
if (handler == null)
return 0;
ContactInfo contactInfo = GetContactInfo(wParam);
MirandaContactEventArgs eventArgs = new MirandaContactEventArgs(contactInfo);
bool retValue = InvokeChainCancelable<MirandaContactEventArgs>(handler, eventArgs);
return Convert.ToInt32(retValue);
}
private static ContactInfo GetContactInfo(UIntPtr wParam)
{
if (wParam == UIntPtr.Zero)
return ContactInfo.MeNeutral;
else
return ContactInfo.FromHandle(wParam);
}
private unsafe int RaiseContactSettingEvent(UIntPtr hContact, IntPtr pDbWriteSetting)
{
DBCONTACTWRITESETTING dbWriteSetting = *(DBCONTACTWRITESETTING*)pDbWriteSetting.ToPointer();
ContactInfo contactInfo = GetContactInfo(hContact);
string name = Translate.ToString(dbWriteSetting.Name, StringEncoding.Ansi);
string moduleName = Translate.ToString(dbWriteSetting.Module, StringEncoding.Ansi);
object value = null;
if ((DatabaseSettingType)dbWriteSetting.Value.Type != DatabaseSettingType.Blob)
value = DBCONTACTWRITESETTING.ExtractValue(pDbWriteSetting);
else
Debugger.Log(10, Loader.LogCategory, "Blob settings are not yet supported, the value will be null.");
MirandaContactSettingEventArgs eventArgs = new MirandaContactSettingEventArgs(contactInfo, name, moduleName, value, (DatabaseSettingType)dbWriteSetting.Value.Type);
bool retValue = InvokeChainCancelable<MirandaContactSettingEventArgs>(ContactSettingChangedEventHandler, eventArgs);
return Convert.ToInt32(retValue);
}
public event MirandaEventHandler<MirandaContactEventArgs> ContactAdded
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaContactEventArgs>>(ref ContactAddedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_ADDED,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseContactEvent(ContactAddedEventHandler, wParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaContactEventArgs>>(ref ContactAddedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_CONTACT_ADDED, ContactAddedEventHandler);
}
}
public event MirandaEventHandler<MirandaContactEventArgs> ContactDeleted
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaContactEventArgs>>(ref ContactDeletedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_DELETED,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseContactEvent(ContactDeletedEventHandler, wParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaContactEventArgs>>(ref ContactDeletedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_CONTACT_DELETED, ContactDeletedEventHandler);
}
}
public event MirandaEventHandler<MirandaContactSettingEventArgs> ContactSettingChanged
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
LazyEventBinder.AttachDelegate<MirandaEventHandler<MirandaContactSettingEventArgs>>(ref ContactSettingChangedEventHandler, value);
LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_SETTINGCHANGED,
delegate(UIntPtr wParam, IntPtr lParam)
{
return RaiseContactSettingEvent(wParam, lParam);
});
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
LazyEventBinder.DetachDelegate<MirandaEventHandler<MirandaContactSettingEventArgs>>(ref ContactSettingChangedEventHandler, value);
LazyEventBinder.UnhookMirandaEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChangedEventHandler);
}
}
#endregion
#region Properties
#region Profile
public string ProfileName
{
get
{
InteropBuffer buffer = InteropBufferPool.AcquireBuffer();
try
{
buffer.Lock();
int result = MirandaContext.Current.CallService(MS_DB_GETPROFILENAME, buffer.SizeAsUIntPtr, buffer.IntPtr);
Debug.Assert(result == 0);
if (result != 0) return null;
return Translate.ToString(buffer.IntPtr, StringEncoding.Ansi);
}
catch (Exception e)
{
throw new MirandaException(TextResources.ExceptionMsg_ErrorWhileCallingMirandaService, e);
}
finally
{
buffer.Unlock();
InteropBufferPool.ReleaseBuffer(buffer);
}
}
}
public string ProfilePath
{
get
{
InteropBuffer buffer = InteropBufferPool.AcquireBuffer();
try
{
buffer.Lock();
int result = MirandaContext.Current.CallService(MS_DB_GETPROFILEPATH, buffer.SizeAsUIntPtr, buffer.IntPtr);
Debug.Assert(result == 0);
if (result != 0) return null;
return Translate.ToString(buffer.IntPtr, StringEncoding.Ansi);
}
catch (Exception e)
{
throw new MirandaException(TextResources.ExceptionMsg_ErrorWhileCallingMirandaService, e);
}
finally
{
buffer.Unlock();
InteropBufferPool.ReleaseBuffer(buffer);
}
}
}
#endregion
#endregion
#region Methods
#region Contacts
/// <summary>
/// Enumerates contact handles, excluding the Me contact.
/// </summary>
/// <returns>Contact handles.</returns>
public IEnumerable<IntPtr> GetContactHandles()
{
MirandaContext context = MirandaContext.Current;
Callback findNext = ServiceManager.GetService(MS_DB_CONTACT_FINDNEXT);
UIntPtr handle = (UIntPtr)(uint)context.CallService(MS_DB_CONTACT_FINDFIRST);
do
{
if (handle != UIntPtr.Zero)
yield return Translate.ToHandle(handle);
}
while ((handle = (UIntPtr)(uint)findNext(handle, IntPtr.Zero)) != UIntPtr.Zero);
}
public ReadOnlyCollection<ContactInfo> GetContacts()
{
return GetContacts(false);
}
public ReadOnlyCollection<ContactInfo> GetContacts(bool includeSelf)
{
MirandaContext context = MirandaContext.Current;
Callback findNext = ServiceManager.GetService(MS_DB_CONTACT_FINDNEXT);
List<ContactInfo> contacts = new List<ContactInfo>(context.CallService(MS_DB_CONTACT_GETCOUNT));
if (includeSelf)
contacts.Add(ContactInfo.MeNeutral);
foreach (IntPtr handle in GetContactHandles())
contacts.Add(ContactInfo.FromHandle(handle));
return contacts.AsReadOnly();
}
public ContactInfo FindContact(string uuid)
{
return FindContact(uuid, ContactInfoProperty.UniqueID, StringEncoding.Ansi);
}
public ContactInfo FindContact(string searchValue, ContactInfoProperty searchCriterion, StringEncoding valueEncoding)
{
return FindContact(searchValue, searchCriterion, valueEncoding, StringComparison.Ordinal);
}
public ContactInfo FindContact(string searchValue, ContactInfoProperty searchCriterion, StringEncoding valueEncoding, StringComparison comparisonType)
{
if (searchValue == null)
throw new ArgumentNullException("searchValues");
foreach (IntPtr handle in GetContactHandles())
{
object value;
ContactInfoPropertyType type;
if (ContactInfo.GetProperty(handle, searchCriterion, out value, out type)
&& searchValue.Equals(value.ToString(), comparisonType))
return ContactInfo.FromHandle(handle);
}
Debug.Assert(false);
return null;
}
public ContactInfo[] FindContacts(params string[] uuids)
{
if (uuids == null)
throw new ArgumentNullException("uuids");
List<ContactInfo> results = new List<ContactInfo>(uuids.Length);
foreach (string uuid in uuids)
{
ContactInfo contact = FindContact(uuid, ContactInfoProperty.UniqueID, StringEncoding.Ansi);
if (contact != null)
results.Add(contact);
}
return results.ToArray();
}
#endregion
#region Events
public IntPtr AddEvent(ContactInfo associatedContact, object data, ISettingOwner owner, DatabaseEventType type, DatabaseEventProperties flags, DateTime? timestamp)
{
return AddEvent(associatedContact, data, owner, type, flags, timestamp, true);
}
public IntPtr AddEvent(ContactInfo associatedContact, object data, ISettingOwner owner, DatabaseEventType type, DatabaseEventProperties flags, DateTime? timestamp, bool throwOnFailure)
{
if (owner == null)
throw new ArgumentNullException("owner");
return AddEvent(associatedContact, data, owner.Name, type, flags, timestamp, throwOnFailure);
}
public IntPtr AddEvent(ContactInfo associatedContact, object data, string owner, DatabaseEventType type, DatabaseEventProperties flags, DateTime? timestamp, bool throwOnFailure)
{
if (associatedContact == null)
throw new ArgumentNullException("associatedContact");
if (String.IsNullOrEmpty(owner))
throw new ArgumentNullException("owner");
if (data == null)
throw new ArgumentNullException("data");
IntPtr pBlob = IntPtr.Zero;
UnmanagedStructHandle<DBEVENTINFO> nativeStruct = UnmanagedStructHandle<DBEVENTINFO>.Empty;
try
{
int totalBytes;
if (data is string)
{
totalBytes = DBEVENTINFO.LayoutAnsiUniString((string)data, out pBlob);
}
else if (data is byte[])
{
byte[] dataBytes = (byte[])data;
totalBytes = dataBytes.Length;
pBlob = Marshal.AllocHGlobal(totalBytes);
Marshal.Copy(dataBytes, 0, pBlob, dataBytes.Length);
}
else
throw new ArgumentOutOfRangeException("data");
DBEVENTINFO info = new DBEVENTINFO(0, IntPtr.Zero);
info.Module = Translate.ToHandle(owner, StringEncoding.Ansi).IntPtr;
info.BlobSize = (uint)totalBytes;
info.BlobPtr = pBlob;
info.EventType = (ushort)type;
info.Flags = (uint)flags;
info.Timestamp = Utilities.GetTimestamp(timestamp.HasValue ? timestamp.Value : DateTime.Now);
nativeStruct = new UnmanagedStructHandle<DBEVENTINFO>(ref info, pBlob, info.Module);
IntPtr eventHandle = (IntPtr)MirandaContext.Current.CallService(MS_DB_EVENT_ADD, associatedContact.MirandaHandle, nativeStruct.IntPtr);
if (eventHandle == IntPtr.Zero && throwOnFailure)
throw new MirandaException(String.Format(TextResources.ExceptionMsg_Formatable2_MirandaServiceReturnedFailure, MS_DB_EVENT_ADD, eventHandle.ToString()));
else
return eventHandle;
}
finally
{
nativeStruct.Free();
}
}
public IEnumerable<IntPtr> GetEventHandles(ContactInfo owner)
{
if (owner == null)
throw new ArgumentNullException("owner");
return GetEventHandles(owner.MirandaHandle);
}
public IEnumerable<IntPtr> GetEventHandles(IntPtr ownerHandle)
{
Callback findNext = ServiceManager.GetService(MS_DB_EVENT_FINDNEXT);
IntPtr pEvent = (IntPtr)MirandaContext.Current.CallService(MS_DB_EVENT_FINDFIRST, ownerHandle, IntPtr.Zero);
while (pEvent != IntPtr.Zero)
{
yield return pEvent;
pEvent = (IntPtr)findNext(Translate.ToHandle(pEvent), IntPtr.Zero);
}
}
#endregion
#endregion
}
}