/***********************************************************************\ * 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 EventAddedEventHandler, EventDeletedEventHandler, BeforeEventAddedEventHandler; private MirandaEventHandler ContactAddedEventHandler, ContactDeletedEventHandler; private MirandaEventHandler ContactSettingChangedEventHandler; #endregion #region Events & Triggers private int RaiseDbEvent(MirandaEventHandler 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(handler, eventArgs); return Convert.ToInt32(retValue); } public event MirandaEventHandler EventAdded { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(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>(ref EventAddedEventHandler, value); LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_ADDED, EventAddedEventHandler); } } public event MirandaEventHandler EventDeleted { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(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>(ref EventDeletedEventHandler, value); LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_DELETED, EventDeletedEventHandler); } } /// /// Return TRUE to filter out the event, FALSE to pass the message along. /// public event MirandaEventHandler BeforeEventAdded { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(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>(ref BeforeEventAddedEventHandler, value); LazyEventBinder.UnhookMirandaEvent(ME_DB_EVENT_FILTER_ADD, BeforeEventAddedEventHandler); } } private int RaiseContactEvent(MirandaEventHandler handler, UIntPtr wParam) { if (handler == null) return 0; ContactInfo contactInfo = GetContactInfo(wParam); MirandaContactEventArgs eventArgs = new MirandaContactEventArgs(contactInfo); bool retValue = InvokeChainCancelable(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(ContactSettingChangedEventHandler, eventArgs); return Convert.ToInt32(retValue); } public event MirandaEventHandler ContactAdded { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(ref ContactAddedEventHandler, value); LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_ADDED, delegate(UIntPtr wParam, IntPtr lParam) { return RaiseContactEvent(ContactAddedEventHandler, wParam); }); } [MethodImpl(MethodImplOptions.Synchronized)] remove { LazyEventBinder.DetachDelegate>(ref ContactAddedEventHandler, value); LazyEventBinder.UnhookMirandaEvent(ME_DB_CONTACT_ADDED, ContactAddedEventHandler); } } public event MirandaEventHandler ContactDeleted { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(ref ContactDeletedEventHandler, value); LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_DELETED, delegate(UIntPtr wParam, IntPtr lParam) { return RaiseContactEvent(ContactDeletedEventHandler, wParam); }); } [MethodImpl(MethodImplOptions.Synchronized)] remove { LazyEventBinder.DetachDelegate>(ref ContactDeletedEventHandler, value); LazyEventBinder.UnhookMirandaEvent(ME_DB_CONTACT_DELETED, ContactDeletedEventHandler); } } public event MirandaEventHandler ContactSettingChanged { [MethodImpl(MethodImplOptions.Synchronized)] add { LazyEventBinder.AttachDelegate>(ref ContactSettingChangedEventHandler, value); LazyEventBinder.HookMirandaEvent(ME_DB_CONTACT_SETTINGCHANGED, delegate(UIntPtr wParam, IntPtr lParam) { return RaiseContactSettingEvent(wParam, lParam); }); } [MethodImpl(MethodImplOptions.Synchronized)] remove { LazyEventBinder.DetachDelegate>(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 /// /// Enumerates contact handles, excluding the Me contact. /// /// Contact handles. public IEnumerable 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 GetContacts() { return GetContacts(false); } public ReadOnlyCollection GetContacts(bool includeSelf) { MirandaContext context = MirandaContext.Current; Callback findNext = ServiceManager.GetService(MS_DB_CONTACT_FINDNEXT); List contacts = new List(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 results = new List(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 nativeStruct = UnmanagedStructHandle.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(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 GetEventHandles(ContactInfo owner) { if (owner == null) throw new ArgumentNullException("owner"); return GetEventHandles(owner.MirandaHandle); } public IEnumerable 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 } }