diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj index 438f191..0092d93 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj @@ -280,6 +280,7 @@ + @@ -288,7 +289,9 @@ + + @@ -296,6 +299,7 @@ + diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs index 64ad9fd..6855ad6 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs @@ -103,7 +103,7 @@ namespace Acacia.Features.FreeBusy set { - RegistryUtil.SetConfigValue(Name, REG_DEFAULTACCOUNT, value == null ? "" : value.SmtpAddress, RegistryValueKind.String); + RegistryUtil.SetConfigValue(Name, REG_DEFAULTACCOUNT, value == null ? "" : value.Account.SmtpAddress, RegistryValueKind.String); } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs index 9b40c43..5da8393 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/FeatureGAB.cs @@ -29,7 +29,6 @@ using System.ComponentModel; using System.Windows.Forms; using Acacia.UI; using static Acacia.DebugOptions; -using Microsoft.Office.Interop.Outlook; namespace Acacia.Features.GAB { @@ -474,7 +473,7 @@ namespace Acacia.Features.GAB private void AccountDiscovered(ZPushAccount zpush) { Logger.Instance.Info(this, "Account discovered: {0}", zpush.DisplayName); - _domains.Add(zpush.DomainName); + _domains.Add(zpush.Account.DomainName); zpush.ConfirmedChanged += (z) => { @@ -606,7 +605,7 @@ namespace Acacia.Features.GAB private void RegisterGABAccount(ZPushAccount account, IFolder folder) { // Determine the domain name - string domain = account.DomainName; + string domain = account.Account.DomainName; // Could already be registered if there are multiple accounts on the same domain GABHandler gab; @@ -637,7 +636,7 @@ namespace Acacia.Features.GAB private void ZPushChannelAvailable(IFolder folder) { - using (IStore store = folder.Store) + using (IStore store = folder.GetStore()) { Logger.Instance.Debug(this, "Z-Push channel available: {0} on {1}", folder, store.DisplayName); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs index 3240279..bb3ad52 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs @@ -121,7 +121,7 @@ namespace Acacia.Features.GAB { get { - using(IStore store = Folder.Store) + using(IStore store = Folder.GetStore()) return store.DisplayName; } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/FeatureOutOfOffice.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/FeatureOutOfOffice.cs index e019b6f..a9864dc 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/FeatureOutOfOffice.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/FeatureOutOfOffice.cs @@ -232,7 +232,7 @@ namespace Acacia.Features.OutOfOffice if (oof.State != ActiveSync.OOFState.Disabled) { if (MessageBox.Show( - string.Format(Properties.Resources.OOFStartup_Message, account.SmtpAddress), + string.Format(Properties.Resources.OOFStartup_Message, account.Account.SmtpAddress), Properties.Resources.OOFStartup_Title, MessageBoxButtons.YesNo, MessageBoxIcon.Question diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/OutOfOfficeDialog.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/OutOfOfficeDialog.cs index 44a3805..3e0a8f7 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/OutOfOfficeDialog.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/OutOfOffice/OutOfOfficeDialog.cs @@ -43,7 +43,7 @@ namespace Acacia.Features.OutOfOffice InitializeComponent(); // Add the email address to the title - Text = string.Format(Text, account.SmtpAddress); + Text = string.Format(Text, account.Account.SmtpAddress); // Set the time formats timeFrom.CustomFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SendAs/FeatureSendAs.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SendAs/FeatureSendAs.cs index a8d6d9c..912a597 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SendAs/FeatureSendAs.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SendAs/FeatureSendAs.cs @@ -71,7 +71,7 @@ namespace Acacia.Features.SendAs private void MailEvents_Respond(IMailItem mail, IMailItem response) { Logger.Instance.Trace(this, "Responding to mail, checking"); - using (IStore store = mail.Store) + using (IStore store = mail.GetStore()) { ZPushAccount zpush = Watcher.Accounts.GetAccount(store); Logger.Instance.Trace(this, "Checking ZPush: {0}", zpush); @@ -114,13 +114,13 @@ namespace Acacia.Features.SendAs private void MailEvents_ItemSend(IMailItem item, ref bool cancel) { - using (IStore store = item.Store) + using (IStore store = item.GetStore()) { ZPushAccount zpush = Watcher.Accounts.GetAccount(store); if (zpush != null) { string address = item.SenderEmailAddress; - if (address != null && address != zpush.SmtpAddress) + if (address != null && address != zpush.Account.SmtpAddress) { Logger.Instance.Trace(this, "SendAs: {0}: {1}", address, item.SenderName); item.SetProperty(Constants.ZPUSH_SEND_AS, address); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs index 364470b..b8cd460 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/SharedFolders/SharedFoldersDialog.cs @@ -71,7 +71,7 @@ namespace Acacia.Features.SharedFolders ).Images; // Add the email address to the title - Text = string.Format(Text, account.SmtpAddress); + Text = string.Format(Text, account.Account.SmtpAddress); // Set up options ShowOptions(new KTreeNode[0]); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs index 1be82ae..4ec7dc8 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs @@ -100,15 +100,15 @@ namespace Acacia.Features.WebApp // Perform a cached auto discover try { - Logger.Instance.Debug(this, "Starting kdiscover: {0}", account.DomainName); + Logger.Instance.Debug(this, "Starting kdiscover: {0}", account.Account.DomainName); string url = PerformAutoDiscover(account); - Logger.Instance.Debug(this, "Finished kdiscover: {0}: {1}", account.DomainName, url); + Logger.Instance.Debug(this, "Finished kdiscover: {0}: {1}", account.Account.DomainName, url); account.SetFeatureData(this, TXT_KDISCOVER, new URLCached(url)); return url; } catch (System.Exception e) { - Logger.Instance.Warning(this, "Exception during kdiscover: {0}: {1}", account.DomainName, e); + Logger.Instance.Warning(this, "Exception during kdiscover: {0}: {1}", account.Account.DomainName, e); account.SetFeatureData(this, TXT_KDISCOVER, null); return null; } @@ -117,7 +117,7 @@ namespace Acacia.Features.WebApp private string PerformAutoDiscover(ZPushAccount account) { // Fetch the txt record - IList txt = DnsUtil.GetTxtRecord(account.DomainName); + IList txt = DnsUtil.GetTxtRecord(account.Account.DomainName); if (txt == null) return null; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs index cc086ac..38c2714 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs @@ -45,4 +45,11 @@ namespace Acacia.Stubs ManagedEmail = 29, SuggestedContacts = 30 } + + public enum AccountType + { + // TODO + EAS, + Other + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAccount.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAccount.cs new file mode 100644 index 0000000..0a5e138 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAccount.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Stubs +{ + public interface IAccount : IDisposable + { + AccountType AccountType { get; } + + IStore Store { get; } + + string DisplayName { get; } + + string SmtpAddress { get; } + + string UserName { get; } + + string ServerURL { get; } + + string DeviceId { get; } + + SecureString Password { get; } + + bool HasPassword { get; } + + string StoreID { get; } + + string DomainName { get; } + + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs index 46bf344..a4448c0 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddIn.cs @@ -44,9 +44,9 @@ namespace Acacia.Stubs // TODO: clean this up /// - /// Sends and receives all accounts. + /// Sends and receives all accounts, or a specific account. /// - void SendReceive(); + void SendReceive(IAccount account = null); /// /// Restarts the application @@ -63,12 +63,13 @@ namespace Acacia.Stubs IRecipient ResolveRecipient(string name); - IStore AddFileStore(string path); - /// - /// Returns the stores. The caller is responsible for disposing. + /// Returns the store manager. This is a shared object and must NOT be disposed. /// - IEnumerable Stores { get; } + IStores Stores + { + get; + } #endregion } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IBase.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IBase.cs index bbf30ad..3a9aeee 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IBase.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IBase.cs @@ -42,14 +42,13 @@ namespace Acacia.Stubs /// /// Returns the store. The owner is responsible for disposing. - /// TODO: make method to make disposing clear /// - IStore Store { get; } + IStore GetStore(); /// /// Quick accessor to Store.Id, to prevent allocating a wrapper for it. /// - string StoreId { get; } + string StoreID { get; } /// /// Quick accessor to Store.DisplayName, to prevent allocating a wrapper for it. diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IItemEvents.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IItemEvents.cs index 76870f7..1ce223c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IItemEvents.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IItemEvents.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using NSOutlook = Microsoft.Office.Interop.Outlook; +using NSOutlookDelegates = Microsoft.Office.Interop.Outlook; namespace Acacia.Stubs { @@ -12,32 +12,32 @@ namespace Acacia.Stubs #region Event handlers // TODO: custom delegates - event NSOutlook.ItemEvents_10_AfterWriteEventHandler AfterWrite; - event NSOutlook.ItemEvents_10_AttachmentAddEventHandler AttachmentAdd; - event NSOutlook.ItemEvents_10_AttachmentReadEventHandler AttachmentRead; - event NSOutlook.ItemEvents_10_AttachmentRemoveEventHandler AttachmentRemove; - event NSOutlook.ItemEvents_10_BeforeAttachmentAddEventHandler BeforeAttachmentAdd; - event NSOutlook.ItemEvents_10_BeforeAttachmentPreviewEventHandler BeforeAttachmentPreview; - event NSOutlook.ItemEvents_10_BeforeAttachmentReadEventHandler BeforeAttachmentRead; - event NSOutlook.ItemEvents_10_BeforeAttachmentSaveEventHandler BeforeAttachmentSave; - event NSOutlook.ItemEvents_10_BeforeAttachmentWriteToTempFileEventHandler BeforeAttachmentWriteToTempFile; - event NSOutlook.ItemEvents_10_BeforeAutoSaveEventHandler BeforeAutoSave; - event NSOutlook.ItemEvents_10_BeforeCheckNamesEventHandler BeforeCheckNames; - event NSOutlook.ItemEvents_10_BeforeDeleteEventHandler BeforeDelete; - event NSOutlook.ItemEvents_10_BeforeReadEventHandler BeforeRead; - event NSOutlook.ItemEvents_10_CloseEventHandler Close; - event NSOutlook.ItemEvents_10_CustomActionEventHandler CustomAction; - event NSOutlook.ItemEvents_10_CustomPropertyChangeEventHandler CustomPropertyChange; - event NSOutlook.ItemEvents_10_ForwardEventHandler Forward; - event NSOutlook.ItemEvents_10_OpenEventHandler Open; - event NSOutlook.ItemEvents_10_PropertyChangeEventHandler PropertyChange; - event NSOutlook.ItemEvents_10_ReadEventHandler Read; - event NSOutlook.ItemEvents_10_ReadCompleteEventHandler ReadComplete; - event NSOutlook.ItemEvents_10_ReplyEventHandler Reply; - event NSOutlook.ItemEvents_10_ReplyAllEventHandler ReplyAll; - event NSOutlook.ItemEvents_10_SendEventHandler Send; - event NSOutlook.ItemEvents_10_UnloadEventHandler Unload; - event NSOutlook.ItemEvents_10_WriteEventHandler Write; + event NSOutlookDelegates.ItemEvents_10_AfterWriteEventHandler AfterWrite; + event NSOutlookDelegates.ItemEvents_10_AttachmentAddEventHandler AttachmentAdd; + event NSOutlookDelegates.ItemEvents_10_AttachmentReadEventHandler AttachmentRead; + event NSOutlookDelegates.ItemEvents_10_AttachmentRemoveEventHandler AttachmentRemove; + event NSOutlookDelegates.ItemEvents_10_BeforeAttachmentAddEventHandler BeforeAttachmentAdd; + event NSOutlookDelegates.ItemEvents_10_BeforeAttachmentPreviewEventHandler BeforeAttachmentPreview; + event NSOutlookDelegates.ItemEvents_10_BeforeAttachmentReadEventHandler BeforeAttachmentRead; + event NSOutlookDelegates.ItemEvents_10_BeforeAttachmentSaveEventHandler BeforeAttachmentSave; + event NSOutlookDelegates.ItemEvents_10_BeforeAttachmentWriteToTempFileEventHandler BeforeAttachmentWriteToTempFile; + event NSOutlookDelegates.ItemEvents_10_BeforeAutoSaveEventHandler BeforeAutoSave; + event NSOutlookDelegates.ItemEvents_10_BeforeCheckNamesEventHandler BeforeCheckNames; + event NSOutlookDelegates.ItemEvents_10_BeforeDeleteEventHandler BeforeDelete; + event NSOutlookDelegates.ItemEvents_10_BeforeReadEventHandler BeforeRead; + event NSOutlookDelegates.ItemEvents_10_CloseEventHandler Close; + event NSOutlookDelegates.ItemEvents_10_CustomActionEventHandler CustomAction; + event NSOutlookDelegates.ItemEvents_10_CustomPropertyChangeEventHandler CustomPropertyChange; + event NSOutlookDelegates.ItemEvents_10_ForwardEventHandler Forward; + event NSOutlookDelegates.ItemEvents_10_OpenEventHandler Open; + event NSOutlookDelegates.ItemEvents_10_PropertyChangeEventHandler PropertyChange; + event NSOutlookDelegates.ItemEvents_10_ReadEventHandler Read; + event NSOutlookDelegates.ItemEvents_10_ReadCompleteEventHandler ReadComplete; + event NSOutlookDelegates.ItemEvents_10_ReplyEventHandler Reply; + event NSOutlookDelegates.ItemEvents_10_ReplyAllEventHandler ReplyAll; + event NSOutlookDelegates.ItemEvents_10_SendEventHandler Send; + event NSOutlookDelegates.ItemEvents_10_UnloadEventHandler Unload; + event NSOutlookDelegates.ItemEvents_10_WriteEventHandler Write; #endregion } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStores.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStores.cs new file mode 100644 index 0000000..66b4e53 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IStores.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Stubs +{ + public delegate void IStores_AccountDiscovered(IAccount account); + public delegate void IStores_AccountRemoved(IAccount account); + + /// + /// Manages the stores and associated acounts. + /// + public interface IStores: IComWrapper, IEnumerable + { + /// + /// Returns the accounts. The accounts are shared objects and must not be disposed. + /// + IEnumerable Accounts { get; } + + event IStores_AccountDiscovered AccountDiscovered; + event IStores_AccountRemoved AccountRemoved; + + /// + /// Adds a file store to the current collection. If the store is already in the collection, an exception is thrown. + /// + /// The path. + /// The store. The caller is responsible for disposing. + IStore AddFileStore(string path); + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISyncObject.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISyncObject.cs index 1d3a16d..e064e2c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISyncObject.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISyncObject.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using NSOutlook = Microsoft.Office.Interop.Outlook; +using NSOutlookDelegates = Microsoft.Office.Interop.Outlook; namespace Acacia.Stubs { @@ -25,10 +25,10 @@ namespace Acacia.Stubs #region Events // TODO: custom delegates - event NSOutlook.SyncObjectEvents_OnErrorEventHandler OnError; - event NSOutlook.SyncObjectEvents_ProgressEventHandler Progress; - event NSOutlook.SyncObjectEvents_SyncEndEventHandler SyncEnd; - event NSOutlook.SyncObjectEvents_SyncStartEventHandler SyncStart; + event NSOutlookDelegates.SyncObjectEvents_OnErrorEventHandler OnError; + event NSOutlookDelegates.SyncObjectEvents_ProgressEventHandler Progress; + event NSOutlookDelegates.SyncObjectEvents_SyncEndEventHandler SyncEnd; + event NSOutlookDelegates.SyncObjectEvents_SyncStartEventHandler SyncStart; #endregion } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AccountWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AccountWrapper.cs new file mode 100644 index 0000000..7b711c9 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AccountWrapper.cs @@ -0,0 +1,153 @@ +using Acacia.Utils; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security; +using System.Text; +using System.Threading.Tasks; + +namespace Acacia.Stubs.OutlookWrappers +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + class AccountWrapper : DisposableWrapper, IAccount, LogContext + { + private readonly string _regPath; + private readonly IStore _store; + + internal AccountWrapper(string regPath, IStore store) + { + this._regPath = regPath; + this._store = store; + + // Cache the SmtpAddress, it is used as the key + SmtpAddress = RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EMAIL, null); + } + + protected override void DoRelease() + { + _store.Dispose(); + } + + [Browsable(false)] + public string LogContextId + { + get + { + return "ZPushAccount(" + SmtpAddress + ")"; + } + } + + public override string ToString() + { + return SmtpAddress; + } + + /// + /// Triggers an Outlook send/receive operation. + /// + public void SendReceive() + { + // TODO: ThisAddIn.Instance.SendReceive(); + throw new NotImplementedException(); + } + + #region Properties + + public AccountType AccountType + { + get + { + return (DeviceId == null) ? AccountType.Other : AccountType.EAS; + } + } + + [Browsable(false)] + public IStore Store + { + get + { + return _store; + } + } + + public string DisplayName + { + get + { + return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_DISPLAYNAME, null); + } + } + + public string SmtpAddress + { + get; + private set; + } + + public string UserName + { + get + { + return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_USERNAME, null); + } + } + + public string ServerURL + { + get + { + return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_SERVER, null); + } + } + + public string DeviceId + { + get + { + return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_DEVICEID, null); + } + } + + [Browsable(false)] + public SecureString Password + { + get + { + byte[] encrypted = (byte[])Registry.GetValue(_regPath, OutlookConstants.REG_VAL_EAS_PASSWORD, null); + return PasswordEncryption.Decrypt(encrypted); + } + } + + [Browsable(false)] + public bool HasPassword + { + get { return Registry.GetValue(_regPath, OutlookConstants.REG_VAL_EAS_PASSWORD, null) != null; } + } + + public string StoreID + { + get { return GetStoreId(_regPath); } + } + + public static string GetStoreId(string regPath) + { + return StringUtil.BytesToHex((byte[])Registry.GetValue(regPath, OutlookConstants.REG_VAL_EAS_STOREID, null)); + } + + public string DomainName + { + get + { + int index = SmtpAddress.IndexOf('@'); + if (index < 0) + return SmtpAddress; + else + return SmtpAddress.Substring(index + 1); + } + } + + #endregion + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs index 8ee6ddc..85d3994 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs @@ -17,17 +17,29 @@ namespace Acacia.Stubs.OutlookWrappers { public class AddInWrapper : IAddIn { + private readonly NSOutlook.Application _app; private readonly ThisAddIn _thisAddIn; - private NSOutlook.Application _app; + private readonly StoresWrapper _stores; public AddInWrapper(ThisAddIn thisAddIn) { this._thisAddIn = thisAddIn; this._app = thisAddIn.Application; + + NSOutlook.NameSpace session = _app.Session; + try + { + this._stores = new StoresWrapper(session.Stores); + } + finally + { + ComRelease.Release(session); + } } - public void SendReceive() + public void SendReceive(IAccount account) { + // TODO: send/receive specific account NSOutlook.NameSpace session = _app.Session; try { @@ -39,6 +51,11 @@ namespace Acacia.Stubs.OutlookWrappers } } + public void Start() + { + _stores.Start(); + } + public void Restart() { // Can not use the assembly location, as that is in the GAC @@ -224,35 +241,9 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore AddFileStore(string path) + public IStores Stores { - using (ComRelease com = new ComRelease()) - { - NSOutlook.NameSpace session = com.Add(_app.Session); - - // Add the store - session.AddStore(path); - - // And fetch it and wrap - NSOutlook.Stores stores = com.Add(session.Stores); - return Mapping.Wrap(stores[stores.Count]); - } - } - - public IEnumerable Stores - { - get - { - using (ComRelease com = new ComRelease()) - { - NSOutlook.NameSpace session = com.Add(_app.Session); - NSOutlook.Stores stores = com.Add(session.Stores); - foreach (NSOutlook.Store store in stores) - { - yield return Mapping.Wrap(store); - } - } - } + get { return _stores; } } } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs index df654ee..46ed301 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AppointmentItemWrapper.cs @@ -116,19 +116,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ContactItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ContactItemWrapper.cs index 223d4b7..b7cc7aa 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ContactItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ContactItemWrapper.cs @@ -241,19 +241,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs index 4e30707..5cb106c 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/DistributionListWrapper.cs @@ -257,19 +257,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs index ed55a67..f0bf96b 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/FolderWrapper.cs @@ -100,13 +100,13 @@ namespace Acacia.Stubs.OutlookWrappers public string EntryID { get { return _item.EntryID; } } - public IStore Store { get { return Mapping.Wrap(_item.Store); } } + public IStore GetStore() { return Mapping.Wrap(_item.Store); } - public string StoreId + public string StoreID { get { - using (IStore store = Store) + using (IStore store = GetStore()) { return store.StoreID; } @@ -116,7 +116,7 @@ namespace Acacia.Stubs.OutlookWrappers { get { - using (IStore store = Store) + using (IStore store = GetStore()) { return store.DisplayName; } @@ -241,7 +241,7 @@ namespace Acacia.Stubs.OutlookWrappers { try { - using (IStore store = Store) + using (IStore store = GetStore()) { return store.GetItemFromID(entryId); } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs index 9c4aa7a..9572987 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs @@ -150,19 +150,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/NoteItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/NoteItemWrapper.cs index 3ffee99..35f7212 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/NoteItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/NoteItemWrapper.cs @@ -97,19 +97,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StorageItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StorageItemWrapper.cs index dae5e8f..db349d6 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StorageItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StorageItemWrapper.cs @@ -95,19 +95,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoresWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoresWrapper.cs new file mode 100644 index 0000000..094bbd5 --- /dev/null +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/StoresWrapper.cs @@ -0,0 +1,270 @@ +using Acacia.Utils; +using Microsoft.Win32; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NSOutlook = Microsoft.Office.Interop.Outlook; + +namespace Acacia.Stubs.OutlookWrappers +{ + class StoresWrapper : ComWrapper, IStores + { + /// + /// Accounts indexed by store id. Null values are allowed, if a store has been + /// determined to not be associated with an Account. This is required to determine when a store is new. + /// + private readonly Dictionary _accountsByStoreId = new Dictionary(); + + /// + /// Accounts indexed by SMTPAddress. Null values are not allowed. + /// + private readonly Dictionary _accountsBySmtp = new Dictionary(); + + public StoresWrapper(NSOutlook.Stores item) : base(item) + { + } + + #region Events + + public event IStores_AccountDiscovered AccountDiscovered; + public event IStores_AccountRemoved AccountRemoved; + + private void OnAccountDiscovered(AccountWrapper account) + { + AccountDiscovered?.Invoke(account); + } + + private void OnAccountRemoved(AccountWrapper account) + { + AccountRemoved?.Invoke(account); + } + + #endregion + + #region Implementation + + public void Start() + { + // Check existing stores + // TODO: do this in background? + foreach(NSOutlook.Store store in _item) + { + StoreAdded(store); + } + + // Register for new stores + // The store remove event is not sent, so don't bother registering for that + _item.StoreAdd += StoreAdded; + + + if (GlobalOptions.INSTANCE.AccountTimer) + { + // Set up timer to check for removed accounts + Util.Timed(null, Config.ACCOUNT_CHECK_INTERVAL, CheckAccountsRemoved); + } + } + + /// + /// Event handler for Stores.StoreAdded event. + /// + private void StoreAdded(NSOutlook.Store s) + { + IStore store = null; + try + { + // Accessing the store object causes random crashes, simply iterate to find new stores + Logger.Instance.Trace(this, "StoreAdded: {0}", s.StoreID); + foreach (NSOutlook.Store rawStore in _item) + { + if (!_accountsByStoreId.ContainsKey(rawStore.StoreID)) + { + store = new StoreWrapper(rawStore); + Logger.Instance.Trace(this, "New store: {0}", rawStore.DisplayName); + AccountWrapper account = TryCreateFromRegistry(store); + if (account == null) + { + // Add it to the cache so it is not evaluated again. + _accountsByStoreId.Add(store.StoreID, null); + Logger.Instance.Trace(this, "Not an account store: {0}", store.DisplayName); + store.Dispose(); + } + else + { + // Account has taken ownership of the store + Logger.Instance.Trace(this, "New account store: {0}: {1}", store.DisplayName, account); + OnAccountDiscovered(account); + } + } + else ComRelease.Release(rawStore); + } + } + catch (System.Exception e) + { + Logger.Instance.Error(this, "StoreAdded Exception: {0}", e); + if (store != null) + store.Dispose(); + } + } + + private void CheckAccountsRemoved() + { + try + { + // Collect all the store ids + HashSet stores = new HashSet(); + foreach (NSOutlook.Store store in _item) + { + try + { + stores.Add(store.StoreID); + } + finally + { + ComRelease.Release(store); + } + } + + // Check if any relevant ones are removed + List> removed = new List>(); + foreach (KeyValuePair account in _accountsByStoreId) + { + if (!stores.Contains(account.Key)) + { + Logger.Instance.Trace(this, "Store not found: {0} - {1}", account.Value, account.Key); + removed.Add(account); + } + } + + // Process any removed stores + foreach (KeyValuePair remove in removed) + { + Logger.Instance.Debug(this, "Account removed: {0} - {1}", remove.Value, remove.Key); + _accountsBySmtp.Remove(remove.Value.SmtpAddress); + _accountsByStoreId.Remove(remove.Key); + OnAccountRemoved(remove.Value); + } + } + catch (System.Exception e) + { + Logger.Instance.Error(this, "Exception in CheckAccountsRemoved: {0}", e); + } + } + + #endregion + + #region Public interface + + public IStore AddFileStore(string path) + { + using (ComRelease com = new ComRelease()) + { + NSOutlook.NameSpace session = com.Add(_item.Session); + + // Add the store + session.AddStore(path); + + // And fetch it and wrap + return Mapping.Wrap(_item[_item.Count]); + } + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + foreach (NSOutlook.Store store in _item) + { + yield return Mapping.Wrap(store); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (NSOutlook.Store store in _item) + { + yield return Mapping.Wrap(store); + } + } + + public IEnumerable Accounts + { + get + { + return _accountsBySmtp.Values; + } + } + + #endregion + + #region Registry + + /// + /// Creates the AccountWrapper for the store, from the registry. + /// + /// The store. Ownership is transferred to the AccountWrapper. If the account is not created, the store is NOT disposed + /// The AccountWrapper, or null if no account is associated with the store + private AccountWrapper TryCreateFromRegistry(IStore store) + { + using (RegistryKey baseKey = FindRegistryKey(store)) + { + if (baseKey == null) + return null; + AccountWrapper account = new AccountWrapper(baseKey.Name, store); + Register(account); + return account; + } + } + + private void Register(AccountWrapper account) + { + // Register the new account + _accountsBySmtp.Add(account.SmtpAddress, account); + _accountsByStoreId.Add(account.StoreID, account); + Logger.Instance.Trace(this, "Account registered: {0} -> {1}", account.DisplayName, account.StoreID); + } + + /// + /// Finds the registry key for the account associated with the store. + /// + /// The registry key, or null if it cannot be found + private RegistryKey FindRegistryKey(IStore store) + { + // Find the registry key by store id + using (RegistryKey key = OpenBaseKey()) + { + if (key != null) + { + foreach (string subkey in key.GetSubKeyNames()) + { + RegistryKey accountKey = key.OpenSubKey(subkey); + string storeId = AccountWrapper.GetStoreId(accountKey.Name); + if (storeId != null && storeId == store.StoreID) + { + return accountKey; + } + accountKey.Dispose(); + } + } + } + return null; + } + + private RegistryKey OpenBaseKey() + { + NSOutlook.NameSpace session = _item.Session; + try + { + string path = string.Format(OutlookConstants.REG_SUBKEY_ACCOUNTS, session.CurrentProfileName); + return OutlookRegistryUtils.OpenOutlookKey(path); + } + finally + { + ComRelease.Release(session); + } + } + + #endregion + } +} diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/TaskItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/TaskItemWrapper.cs index be58bbe..5e87cb3 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/TaskItemWrapper.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/TaskItemWrapper.cs @@ -95,19 +95,16 @@ namespace Acacia.Stubs.OutlookWrappers } } - public IStore Store + public IStore GetStore() { - get + using (ComRelease com = new ComRelease()) { - using (ComRelease com = new ComRelease()) - { - NSOutlook.Folder parent = com.Add(_item.Parent); - return Mapping.Wrap(parent?.Store); - } + NSOutlook.Folder parent = com.Add(_item.Parent); + return Mapping.Wrap(parent?.Store); } } - public string StoreId + public string StoreID { get { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs index 36b9a77..7578bd4 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs @@ -146,7 +146,10 @@ namespace Acacia // Start watching events if (DebugOptions.GetOption(null, DebugOptions.WATCHER_ENABLED)) + { + ((AddInWrapper)Instance).Start(); Watcher.Start(); + } // Done Logger.Instance.Debug(this, "Startup done"); diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs index df4050e..dbef980 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/Util.cs @@ -88,5 +88,44 @@ namespace Acacia.Utils GC.WaitForPendingFinalizers(); } } + + + #region Timers + + public static void Delayed(LogContext log, int millis, System.Action action) + { + RegisterTimer(log, millis, action, false); + } + + public static void Timed(LogContext log, int millis, System.Action action) + { + RegisterTimer(log, millis, action, true); + } + + private static void RegisterTimer(LogContext log, int millis, System.Action action, bool repeat) + { + System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); + timer.Interval = millis; + timer.Tick += (s, eargs) => + { + try + { + action(); + if (!repeat) + { + timer.Enabled = false; + timer.Dispose(); + } + } + catch (System.Exception e) + { + Logger.Instance.Trace(log, "Exception in timer: {0}", e); + } + }; + timer.Start(); + } + + #endregion + } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushConnection.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushConnection.cs index 4ea9fc8..aa846de 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushConnection.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushConnection.cs @@ -147,9 +147,9 @@ namespace Acacia.ZPush.Connect // TODO: it would be nice to let the system handle the SecureString for the password. However, // when specifying credentials for an HttpClient, they are only used after a 401 is received // on the first request, basically doubling the number of requests. - using (SecureString pass = _account.Password) + using (SecureString pass = _account.Account.Password) { - var byteArray = Encoding.UTF8.GetBytes(_account.UserName + ":" + pass.ConvertToUnsecureString()); + var byteArray = Encoding.UTF8.GetBytes(_account.Account.UserName + ":" + pass.ConvertToUnsecureString()); var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); _client.DefaultRequestHeaders.Authorization = header; } @@ -278,8 +278,8 @@ namespace Acacia.ZPush.Connect public Response Execute(ActiveSync.RequestBase request) { - string url = string.Format(ACTIVESYNC_URL, _account.ServerURL, _account.DeviceId, - request.Command, _account.UserName, "WindowsOutlook"); + string url = string.Format(ACTIVESYNC_URL, _account.Account.ServerURL, _account.Account.DeviceId, + request.Command, _account.Account.UserName, "WindowsOutlook"); // Construct the body WBXMLDocument doc = new WBXMLDocument(); @@ -291,10 +291,10 @@ namespace Acacia.ZPush.Connect using (HttpContent content = new ByteArrayContent(contentBody)) { - Logger.Instance.Trace(this, "Sending request: {0} -> {1}", _account.ServerURL, doc.ToXMLString()); + Logger.Instance.Trace(this, "Sending request: {0} -> {1}", _account.Account.ServerURL, doc.ToXMLString()); content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.ms-sync.wbxml"); string caps = ZPushCapabilities.Client.ToString(); - Logger.Instance.Trace(this, "Sending request: {0} -> {1}: {2}", _account.ServerURL, caps, doc.ToXMLString()); + Logger.Instance.Trace(this, "Sending request: {0} -> {1}: {2}", _account.Account.ServerURL, caps, doc.ToXMLString()); content.Headers.Add(Constants.ZPUSH_HEADER_CLIENT_CAPABILITIES, caps); using (HttpResponseMessage response = _client.PostAsync(url, content, _cancel).Result) { diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs index 8110c4a..d0b0983 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/Connect/ZPushWebService.cs @@ -46,14 +46,14 @@ namespace Acacia.ZPush.Connect public ResponseType Execute(SoapRequest request) { // Create the url - string url = string.Format(ACTIVESYNC_URL, _connection.Account.ServerURL, "webservice", + string url = string.Format(ACTIVESYNC_URL, _connection.Account.Account.ServerURL, "webservice", ServiceName, // TODO: this username is a bit of a quick hack. - request.UserName ?? _connection.Account.UserName, + request.UserName ?? _connection.Account.Account.UserName, "webservice"); // Set up the encoding - SoapRequestEncoder encoder = new SoapRequestEncoder(_connection.Account.ServerURL, ServiceParameters, request); + SoapRequestEncoder encoder = new SoapRequestEncoder(_connection.Account.Account.ServerURL, ServiceParameters, request); encoder.ServiceName = ServiceName; // Execute the request @@ -85,7 +85,7 @@ namespace Acacia.ZPush.Connect get { SoapParameters parameters = new SoapParameters(); - parameters.Add("devid", _connection.Account.DeviceId.ToLower()); + parameters.Add("devid", _connection.Account.Account.DeviceId.ToLower()); return parameters; } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs index 41355ab..768c095 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccount.cs @@ -32,143 +32,41 @@ using System.Threading.Tasks; namespace Acacia.ZPush { [TypeConverter(typeof(ExpandableObjectConverter))] - public class ZPushAccount : DisposableWrapper, LogContext + public class ZPushAccount : LogContext { #region Miscellaneous - private readonly string _regPath; + private readonly IAccount _account; - private readonly IStore _store; - - /// - /// Constructor. - /// - /// They registry key containing the account settings. - /// The store this account represents. The new object takes ownership - internal ZPushAccount(string regPath, IStore store) + internal ZPushAccount(IAccount account) { - this._regPath = regPath; - this._store = store; - - // Cache the SmtpAddress, it is used as the key - SmtpAddress = RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EMAIL, null); + this._account = account; } - protected override void DoRelease() - { - _store.Dispose(); - } + [Browsable(false)] + public IAccount Account { get { return _account; } } + public String DisplayName { get { return _account.DisplayName; } } [Browsable(false)] public string LogContextId { get { - return "ZPushAccount(" + SmtpAddress + ")"; + return "ZPushAccount(" + _account.SmtpAddress + ")"; } } public override string ToString() { - return SmtpAddress; + return _account.SmtpAddress; } /// - /// Triggers an Outlook send/receive operation. + /// Triggers an Outlook send/receive operation for this account. /// public void SendReceive() { - // TODO: ThisAddIn.Instance.SendReceive(); - throw new NotImplementedException(); - } - - #endregion - - #region Properties - - [Browsable(false)] - public IStore Store - { - get - { - return _store; - } - } - - public string DisplayName - { - get - { - return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_DISPLAYNAME, null); - } - } - - public string SmtpAddress - { - get; - private set; - } - - public string UserName - { - get - { - return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_USERNAME, null); - } - } - - public string ServerURL - { - get - { - return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_SERVER, null); - } - } - - public string DeviceId - { - get - { - return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_EAS_DEVICEID, null); - } - } - - [Browsable(false)] - public SecureString Password - { - get - { - byte[] encrypted = (byte[])Registry.GetValue(_regPath, OutlookConstants.REG_VAL_EAS_PASSWORD, null); - return PasswordEncryption.Decrypt(encrypted); - } - } - - [Browsable(false)] - public bool HasPassword - { - get { return Registry.GetValue(_regPath, OutlookConstants.REG_VAL_EAS_PASSWORD, null) != null; } - } - - public string StoreID - { - get { return GetStoreId(_regPath); } - } - - public static string GetStoreId(string regPath) - { - return StringUtil.BytesToHex((byte[])Registry.GetValue(regPath, OutlookConstants.REG_VAL_EAS_STOREID, null)); - } - - public string DomainName - { - get - { - int index = SmtpAddress.IndexOf('@'); - if (index < 0) - return SmtpAddress; - else - return SmtpAddress.Substring(index + 1); - } + ThisAddIn.Instance.SendReceive(Account); } #endregion diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccounts.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccounts.cs index 22a50b1..164b8a9 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccounts.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushAccounts.cs @@ -23,21 +23,19 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using NSOutlook = Microsoft.Office.Interop.Outlook; namespace Acacia.ZPush { /// /// Maintains the mapping from Outlook accounts to ZPush accounts, which /// provide additional functionality and workarounds. + /// TODO: split into Outlook and Z-Push specific parts /// - public class ZPushAccounts + public class ZPushAccounts : DisposableWrapper { private readonly ZPushWatcher _watcher; - // TODO: wrap these - private readonly NSOutlook.Application _app; - private readonly NSOutlook.NameSpace _session; - private readonly NSOutlook.Stores _stores; + private readonly IAddIn _addIn; + private readonly IStores _stores; /// /// ZPushAccounts indexed by SMTPAddress. Null values are not allowed. @@ -45,351 +43,126 @@ namespace Acacia.ZPush private readonly Dictionary _accountsBySmtp = new Dictionary(); /// - /// ZPushAccounts indexed by store id. Null values are allowed, if a store has been - /// determined to not be associated with a ZPushAccount. This is required to determine when a store is new. + /// ZPushAccounts indexed by store id. Null values are noy allowed. /// private readonly Dictionary _accountsByStoreId = new Dictionary(); - public ZPushAccounts(ZPushWatcher watcher, NSOutlook.Application app) + public ZPushAccounts(ZPushWatcher watcher, IAddIn addIn) { this._watcher = watcher; - this._app = app; - this._session = app.Session; - this._stores = _session.Stores; + this._addIn = addIn; + this._stores = addIn.Stores; } - internal void Start() + protected override void DoRelease() { - // Register for new stores - // The store remove event is not sent, so don't register for that - _stores.StoreAdd += StoreAdded; + _stores.Dispose(); + } + #region Implementation + + public void Start() + { if (GlobalOptions.INSTANCE.ZPushCheck) { // Process existing accounts - using (ComRelease com = new ComRelease()) - foreach (NSOutlook.Account account in com.Add(_session.Accounts)) + foreach (IAccount account in _stores.Accounts) + { + Tasks.Task(null, "AccountCheck", () => { - Tasks.Task(null, "AccountCheck", () => - { - try - { - // TODO: check if EAS account - // account gets released by GetAccount, save DisplayName for log purposes. - string displayName = account.DisplayName; - Logger.Instance.Trace(this, "Checking account: {0}", displayName); - ZPushAccount zpush = GetAccount(account); - if (zpush == null) - { - Logger.Instance.Trace(this, "Not a ZPush account: {0}", displayName); - } - else - { - Logger.Instance.Trace(this, "ZPush account: {0}", zpush); - _watcher.OnAccountDiscovered(zpush, true); - } - } - catch (System.Exception e) - { - Logger.Instance.Error(this, "Exception processing account: {0}", e); - } - }); - } + AccountAdded(account); + }); + } Tasks.Task(null, "AccountCheckDone", () => { _watcher.OnAccountsScanned(); - - if (GlobalOptions.INSTANCE.AccountTimer) - { - // Set up timer to check for removed accounts - _watcher.Timed(Config.ACCOUNT_CHECK_INTERVAL, CheckAccountsRemoved); - } }); + + // Register for account changes + _stores.AccountDiscovered += AccountAdded; + _stores.AccountRemoved += AccountRemoved; } } - private void CheckAccountsRemoved() + private void AccountAdded(IAccount account) { try { - // Collect all the store ids - HashSet stores = new HashSet(); - foreach (NSOutlook.Store store in _stores) - { - try - { - stores.Add(store.StoreID); - } - finally - { - ComRelease.Release(store); - } - } + Logger.Instance.Trace(this, "Checking account: {0}", account); - // Check if any relevant ones are removed - List> removed = new List>(); - foreach(KeyValuePair account in _accountsByStoreId) + // Only EAS accounts can be zpush accounts + if (account.AccountType == AccountType.EAS) { - if (!stores.Contains(account.Key)) - { - Logger.Instance.Trace(this, "Store not found: {0} - {1}", account.Value, account.Key); - removed.Add(account); - } + ZPushAccount zpush = new ZPushAccount(account); + _accountsByStoreId.Add(account.StoreID, zpush); + _accountsBySmtp.Add(account.SmtpAddress, zpush); + Logger.Instance.Trace(this, "ZPush account: {0}", zpush); + _watcher.OnAccountDiscovered(zpush); } - - // Process any removed stores - foreach(KeyValuePair remove in removed) + else { - Logger.Instance.Debug(this, "Account removed: {0} - {1}", remove.Value, remove.Key); - _accountsBySmtp.Remove(remove.Value.SmtpAddress); - _accountsByStoreId.Remove(remove.Key); - _watcher.OnAccountRemoved(remove.Value); + Logger.Instance.Trace(this, "Not a ZPush account: {0}", account); } } - catch(System.Exception e) + catch (System.Exception e) { - Logger.Instance.Error(this, "Exception in CheckAccountsRemoved: {0}", e); + Logger.Instance.Error(this, "Exception processing account: {0}", e); } } - public IEnumerable GetAccounts() + private void AccountRemoved(IAccount account) { - return _accountsBySmtp.Values; + _accountsBySmtp.Remove(account.SmtpAddress); + _accountsByStoreId.Remove(account.StoreID); } - /// - /// Returns the ZPushAccount on which the folder is located. - /// - /// The ZPushAccount, or null if the folder is not on a zpush account + #endregion + + #region Account access + public ZPushAccount GetAccount(IFolder folder) { - ZPushAccount zpush = null; - using (IStore store = folder.Store) - _accountsByStoreId.TryGetValue(store.StoreID, out zpush); - return zpush; - } - public ZPushAccount GetAccount(IStore store) - { - ZPushAccount zpush = null; - _accountsByStoreId.TryGetValue(store.StoreID, out zpush); - return zpush; - } - public ZPushAccount GetAccount(IBase item) - { - if (item is IFolder) - return GetAccount((IFolder)item); - else if (item is IStore) - return GetAccount((IStore)item); - if (item.Parent != null) - return GetAccount(item.Parent); - return null; + ZPushAccount value = null; + _accountsByStoreId.TryGetValue(folder.StoreID, out value); + return value; } - public ZPushAccount GetAccount(NSOutlook.MAPIFolder folder) + public ZPushAccount GetAccount(IAccount account) { - using (ComRelease com = new ComRelease()) - { - ZPushAccount zpush = null; - NSOutlook.Store store = com.Add(folder.Store); - string storeId = store?.StoreID; - if (storeId == null) - return null; - _accountsByStoreId.TryGetValue(storeId, out zpush); - return zpush; - } + ZPushAccount value = null; + _accountsByStoreId.TryGetValue(account.StoreID, out value); + return value; + } + + public ZPushAccount GetAccount(IStore store) + { + ZPushAccount value = null; + _accountsByStoreId.TryGetValue(store.StoreID, out value); + return value; + } + + public ZPushAccount GetAccount(IBase obj) + { + if (obj is IFolder) + return GetAccount((IFolder)obj); + else if (obj is IStore) + return GetAccount((IStore)obj); + if (obj.Parent != null) + return GetAccount(obj.Parent); + return null; } public ZPushAccount GetAccount(string smtpAddress) { - ZPushAccount account = null; - _accountsBySmtp.TryGetValue(smtpAddress, out account); - return account; + ZPushAccount value = null; + _accountsBySmtp.TryGetValue(smtpAddress, out value); + return value; } - /// - /// Returns the ZPush account associated with the Outlook account. - /// - /// The account. This function will release the handle - /// The ZPushAccount, or null if not a ZPush account. - private ZPushAccount GetAccount(NSOutlook.Account account) + public IEnumerable GetAccounts() { - try - { - // Only EAS accounts can be zpush accounts - if (account.AccountType != NSOutlook.OlAccountType.olEas) - return null; - - // Check for a cached value - ZPushAccount zpush; - if (_accountsBySmtp.TryGetValue(account.SmtpAddress, out zpush)) - return zpush; - - // Create a new account - return CreateFromRegistry(account); - } - finally - { - ComRelease.Release(account); - } - } - - /// - /// Event handler for Stores.StoreAdded event. - /// - private void StoreAdded(NSOutlook.Store s) - { - IStore store = null; - try - { - // Accessing the store object causes random crashes, simply iterate to find new stores - Logger.Instance.Trace(this, "StoreAdded: {0}", s.StoreID); - foreach (NSOutlook.Store rawStore in _session.Stores.RawEnum(false)) - { - if (!_accountsByStoreId.ContainsKey(rawStore.StoreID)) - { - Logger.Instance.Trace(this, "New store: {0}", rawStore.DisplayName); - - store = Mapping.Wrap(rawStore); - ZPushAccount zpush = TryCreateFromRegistry(store); - if (zpush == null) - { - // Add it to the cache so it is not evaluated again. - _accountsByStoreId.Add(store.StoreID, null); - Logger.Instance.Trace(this, "Not a ZPush store: {0}", store.DisplayName); - store.Dispose(); - } - else - { - Logger.Instance.Trace(this, "New ZPush store: {0}: {1}", store.DisplayName, zpush); - _watcher.OnAccountDiscovered(zpush, false); - // zpush has taken ownership - } - } - else ComRelease.Release(rawStore); - } - } - catch(System.Exception e) - { - Logger.Instance.Error(this, "StoreAdded Exception: {0}", e); - if (store != null) - store.Dispose(); - } - } - - #region Registry - - private void Register(ZPushAccount zpush) - { - // Register the new account - _accountsBySmtp.Add(zpush.SmtpAddress, zpush); - _accountsByStoreId.Add(zpush.StoreID, zpush); - Logger.Instance.Trace(this, "Account registered: {0} -> {1}", zpush.DisplayName, zpush.Store.StoreID); - } - - /// - /// Creates the ZPushAccount for the account, from the registry values. - /// - /// The account. The caller is responsible for releasing this. - /// The associated ZPushAccount - /// If the registry key cannot be found - // TODO: check owner - private ZPushAccount CreateFromRegistry(NSOutlook.Account account) - { - // TODO: check that caller releases account everywhere - using (ComRelease com = new ComRelease()) - using (RegistryKey baseKey = FindRegistryKey(account)) - { - if (baseKey == null) - throw new System.Exception("Unknown account: " + account.SmtpAddress); - - // Get the store id - string storeId = ZPushAccount.GetStoreId(baseKey.Name); - - // Find the store - NSOutlook.Store store = _session.GetStoreFromID(storeId); - - // Done, create and register - ZPushAccount zpush = new ZPushAccount(baseKey.Name, Mapping.Wrap(store)); - Register(zpush); - return zpush; - } - } - - /// - /// Creates the ZPushAccount for the store, from the registry. - /// - /// The store. Ownership is transferred to the ZPushAccount. If the account is not created, the store is NOT disposed - /// The ZPushAccount, or null if no account is associated with the store - private ZPushAccount TryCreateFromRegistry(IStore store) - { - using (RegistryKey baseKey = FindRegistryKey(store)) - { - if (baseKey == null) - return null; - ZPushAccount zpush = new ZPushAccount(baseKey.Name, store); - Register(zpush); - return zpush; - } - } - - private RegistryKey OpenBaseKey() - { - NSOutlook.NameSpace session = _app.Session; - string path = string.Format(OutlookConstants.REG_SUBKEY_ACCOUNTS, session.CurrentProfileName); - ComRelease.Release(session); - return OutlookRegistryUtils.OpenOutlookKey(path); - } - - /// - /// Finds the registry key for the account. - /// - /// The registry key, or null if it cannot be found - private RegistryKey FindRegistryKey(NSOutlook.Account account) - { - // Find the registry key by email adddress - using (RegistryKey key = OpenBaseKey()) - { - if (key != null) - { - foreach (string subkey in key.GetSubKeyNames()) - { - RegistryKey accountKey = key.OpenSubKey(subkey); - if (accountKey.GetValueString(OutlookConstants.REG_VAL_EMAIL) == account.SmtpAddress) - { - return accountKey; - } - accountKey.Dispose(); - } - } - } - return null; - } - - /// - /// Finds the registry key for the account associated with the store. - /// - /// The registry key, or null if it cannot be found - private RegistryKey FindRegistryKey(IStore store) - { - // Find the registry key by store id - using (RegistryKey key = OpenBaseKey()) - { - if (key != null) - { - foreach (string subkey in key.GetSubKeyNames()) - { - RegistryKey accountKey = key.OpenSubKey(subkey); - string storeId = ZPushAccount.GetStoreId(accountKey.Name); - if (storeId != null && storeId == store.StoreID) - { - return accountKey; - } - accountKey.Dispose(); - } - } - } - return null; + return _accountsByStoreId.Values; } #endregion diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs index 8341a04..53dded5 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushLocalStore.cs @@ -23,7 +23,6 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using NSOutlook = Microsoft.Office.Interop.Outlook; namespace Acacia.ZPush { @@ -88,7 +87,7 @@ namespace Acacia.ZPush // Path found, create the store Logger.Instance.Info(typeof(ZPushLocalStore), "Creating new store: {0}", path); - store = addIn.AddFileStore(path); + store = addIn.Stores.AddFileStore(path); Logger.Instance.Debug(typeof(ZPushLocalStore), "Created new store: {0}", store.FilePath); // Set the display name diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs index c2fcd8c..a00e888 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/ZPushWatcher.cs @@ -53,7 +53,7 @@ namespace Acacia.ZPush this._addIn = addIn; this._app = addIn.RawApp; Sync = new ZPushSync(this, addIn); - Accounts = new ZPushAccounts(this, _app); + Accounts = new ZPushAccounts(this, addIn); // Need to keep a link to keep receiving events _explorer2 = _app.ActiveExplorer(); @@ -77,43 +77,6 @@ namespace Acacia.ZPush #endregion - #region Timers - - public void Delayed(int millis, System.Action action) - { - RegisterTimer(millis, action, false); - } - - public void Timed(int millis, System.Action action) - { - RegisterTimer(millis, action, true); - } - - private void RegisterTimer(int millis, System.Action action, bool repeat) - { - System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); - timer.Interval = millis; - timer.Tick += (s, eargs) => - { - try - { - action(); - if (!repeat) - { - timer.Enabled = false; - timer.Dispose(); - } - } - catch(System.Exception e) - { - Logger.Instance.Trace(this, "Exception in timer: {0}", e); - } - }; - timer.Start(); - } - - #endregion - #region Accounts public delegate void AccountHandler(ZPushAccount account); @@ -134,18 +97,15 @@ namespace Acacia.ZPush /// Handles a new account. /// /// The account. - /// True if the account is an existing account, false if - /// it is a new account - internal void OnAccountDiscovered(ZPushAccount account, bool isExisting) + internal void OnAccountDiscovered(ZPushAccount account) { // Notify any account listeners - if (AccountDiscovered != null) - AccountDiscovered(account); + AccountDiscovered?.Invoke(account); // Register any events HandleFolderWatchers(account); - if (account.HasPassword) + if (account.Account.HasPassword) { // Send an OOF request to get the OOF state and capabilities Tasks.Task(null, "ZPushCheck: " + account.DisplayName, () => @@ -274,7 +234,7 @@ namespace Acacia.ZPush private void HandleFolderWatchers(ZPushAccount account) { // We need to keep the object alive to keep receiving events - _rootFolder = new ZPushFolder(this, account.Store.GetRootFolder()); + _rootFolder = new ZPushFolder(this, account.Account.Store.GetRootFolder()); } public void WatchFolder(FolderRegistration folder, FolderEventHandler handler, FolderEventHandler changedHandler = null) @@ -367,7 +327,7 @@ namespace Acacia.ZPush string entryId = folder.EntryID; - using (IStore store = folder.Store) + using (IStore store = folder.GetStore()) { foreach(DefaultFolder defaultFolderId in BLACKLISTED_MAIL_FOLDERS) {