Merge branch 'master' into release_1_2

# Conflicts:
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/WebApp/FeatureWebApp.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IAddressEntry.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IComWrapper.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ICommandBars.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ComWrapper.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/ExplorerWrapper.cs
#	src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientWrapper.cs
#	src/AcaciaZPushPlugin/OutlookRestarter/Properties/AssemblyInfo.cs
#	translations/KOE.pot
#	translations/en.po
This commit is contained in:
Patrick Simpson 2017-02-15 13:24:40 +01:00
commit c55091bcc7
117 changed files with 6463 additions and 2005 deletions

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
# How to contribute to the Kopano OL Extension
If you have found an issue and want to report an issue, either reach out to us
in our [forum](http://forum.kopano.com), or, if you have a subscription, open
up a [support case](https://kopano.com/support/).
To provide changesets,
- Clone the repository from https://stash.kopano.io/ or
https://github.com/Kopano-mirror/ .
- Commit and [sign your work](
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v4.10-rc4#n416)
(```git commit -s```).
- Upload commits to a git store of your choosing, or export the series as a
patchset using [git format-patch](https://git-scm.com/docs/git-format-patch).
- Send the patch(es) or git link to
[contributing@kopano.io](mailto:contributing@kopano.io) and we will consider
the submission.
## Additional notes
- Please only work on one issue per commit.
- Before implementing a new feature, get in contact with us, so we can
determine the impact.

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# Kopano OL Extension
We live in a world where it is a commodity to work with apps or web interfaces just have a look at the big amounts of HTML5-apps on your tablets and smartphones. However, there are situations in which a rich application is needed. Think about working offline during a five hour flight or deeper integrations like writing series of letters in your Office suite.
This is exactly why we developed the Kopano Outlook Extension (KOE). If you are on the road a lot and thus without an internet connection, or if you rely heavily on specialised plugins in Outlook, then the Kopano OL Extension might just be the thing for you.
# Documentation
In-depth documentation, such as administration and user manuals, about our
products be be found on our [Documentation Portal](https://documentation.kopano.io/). Additionally a [Knowledge Base](https://kb.kopano.io/) is available for quick start guides, handy code
snippets, and troubleshooting help.
# Contributing
The main development of Kopano Core takes place in a [private Bitbucket
instance](https://stash.kopano.io/projects/KOE/repos/kopano_ol_extension_source/)
with development tickets organised in [Jira](https://jira.kopano.io/projects/KC/). Please see
[CONTRIBUTING.md](CONTRIBUTING.md) for steps on how to contribute patches.
# Downloading compiled packages
Compiled packages are only available to subscription
holders from the the [Kopano Portal](https://portal.kopano.com/) and a
[package repository](
https://download.kopano.io/supported/olextension:/).
# Support
Community Support is available through the [Kopano Forum](
https://forum.kopano.io/) and through the #Kopano channel on Freenode IRC
network. [Additional support options](https://kopano.com/support/) are
available for subscription holders.

View File

@ -1,12 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcaciaZPushPlugin", "AcaciaZPushPlugin\AcaciaZPushPlugin.csproj", "{1A7427A5-F814-4B07-98B2-C67D758B65D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginDebugger", "PluginDebugger\PluginDebugger.csproj", "{9258AD17-0A25-4669-A95C-93EC70882551}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OutlookRestarter", "OutlookRestarter\OutlookRestarter.csproj", "{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -41,6 +43,18 @@ Global
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x64.Build.0 = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x86.ActiveCfg = Release|Any CPU
{9258AD17-0A25-4669-A95C-93EC70882551}.Release|x86.Build.0 = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|x64.ActiveCfg = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|x64.Build.0 = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|x86.ActiveCfg = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Debug|x86.Build.0 = Debug|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|Any CPU.Build.0 = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|x64.ActiveCfg = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|x64.Build.0 = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|x86.ActiveCfg = Release|Any CPU
{222C4DA5-FA31-471A-B127-5E0C6AD2CB3C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -125,7 +125,7 @@
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<DefineConstants>VSTO40</DefineConstants>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!--
@ -241,6 +241,7 @@
<Compile Include="Controls\KUITask.cs" />
<Compile Include="Controls\KUIUtil.cs" />
<Compile Include="DebugOptions.cs" />
<Compile Include="Features\SecondaryContacts\FeatureSecondaryContacts.cs" />
<Compile Include="Features\DebugSupport\AboutDialog.cs">
<SubType>Form</SubType>
</Compile>
@ -275,13 +276,44 @@
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
<Compile Include="GlobalOptions.cs" />
<Compile Include="Logging.cs" />
<Compile Include="Native\MAPI.cs" />
<Compile Include="Native\IOleWindow.cs" />
<Compile Include="OutlookConstants.cs" />
<Compile Include="Stubs\Enums.cs" />
<Compile Include="Stubs\IAccount.cs" />
<Compile Include="Stubs\IAddIn.cs" />
<Compile Include="Stubs\IAddressEntry.cs" />
<Compile Include="Stubs\ICommandBars.cs" />
<Compile Include="Stubs\IComWrapper.cs" />
<Compile Include="Stubs\IExplorer.cs" />
<Compile Include="Stubs\IFolders.cs" />
<Compile Include="Stubs\IItemEvents.cs" />
<Compile Include="Stubs\IItems.cs" />
<Compile Include="Stubs\IOutlookWindow.cs" />
<Compile Include="Stubs\IRecipient.cs" />
<Compile Include="Stubs\IStores.cs" />
<Compile Include="Stubs\ISyncObject.cs" />
<Compile Include="Stubs\OutlookWrappers\AccountWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\AddInWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\AddressEntryWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\CommandBarsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ExplorerWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\FoldersWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ItemEventsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ItemsWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\RecipientWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoresWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\SyncObjectWrapper.cs" />
<Compile Include="Stubs\Wrappers.cs" />
<Compile Include="UI\Outlook\OutlookImageList.cs" />
<Compile Include="UI\Outlook\RibbonToggleButton.cs" />
<Compile Include="UI\Outlook\RibbonButton.cs" />
<Compile Include="UI\Outlook\CommandElement.cs" />
<Compile Include="UI\Outlook\MenuItem.cs" />
<Compile Include="UI\Outlook\Types.cs" />
<Compile Include="Utils\DisposableWrapper.cs" />
<Compile Include="Utils\ImageUtils.cs" />
<Compile Include="Utils\RegistryUtil.cs" />
<Compile Include="ZPush\API\SharedFolders\AvailableFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" />
@ -306,8 +338,7 @@
<Compile Include="ZPush\Connect\ZPushRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequest.cs" />
<Compile Include="Stubs\ItemType.cs" />
<Compile Include="Stubs\OutlookWrappers\DisposableWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\ComWrapper.cs" />
<Compile Include="UI\FeatureSettings.cs">
<SubType>UserControl</SubType>
</Compile>
@ -393,7 +424,6 @@
<Compile Include="Stubs\INoteItem.cs" />
<Compile Include="Stubs\ISearch.cs" />
<Compile Include="Stubs\IStorageItem.cs" />
<Compile Include="Stubs\IUserProperty.cs" />
<Compile Include="Stubs\IZPushItem.cs" />
<Compile Include="Stubs\OutlookWrappers\AddressBookWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\DistributionListWrapper.cs" />
@ -408,7 +438,6 @@
<Compile Include="Stubs\OutlookWrappers\SearchWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StorageItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoreWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\UserPropertyWrapper.cs" />
<Compile Include="Stubs\IStore.cs" />
<Compile Include="UI\ProgressDialog.cs">
<SubType>Form</SubType>

View File

@ -71,6 +71,7 @@ namespace Acacia
public const string ZPUSH_HEADER_GAB_NAME = "X-Push-GAB-Name";
public const string ZPUSH_HEADER_CAPABILITIES = "X-Push-Capabilities";
public const string ZPUSH_HEADER_CLIENT_CAPABILITIES = "X-Push-Plugin-Capabilities";
public const string ZPUSH_HEADER_PLUGIN = "X-Push-Plugin";
public const string ZPUSH_HEADER_VERSION = "X-Z-Push-Version";

View File

@ -68,19 +68,25 @@ namespace Acacia
}
public class EnumOption<EnumType> : Option<EnumType>
where EnumType : struct
{
private readonly EnumType? _defaultValue;
private EnumType DefaultValue
{
get
{
if (_defaultValue.HasValue)
return (EnumType)_defaultValue;
return (EnumType)typeof(EnumType).GetEnumValues().GetValue(0);
}
}
public EnumOption(string token)
public EnumOption(string token, EnumType? defaultValue = null)
:
base(token)
{
this._defaultValue = defaultValue;
}
public override string GetToken(EnumType value)

View File

@ -47,6 +47,76 @@ namespace Acacia.Features.DebugSupport
Properties.Refresh();
}
private class DebugCycleInfo
{
private int cycleIndex = 0;
private int cycleCount;
private Timer timer = new Timer();
private GAB.FeatureGAB gab;
private int zeroCount = 1000;
public DebugCycleInfo(DebugDialog dlg, GAB.FeatureGAB gab, int count)
{
this.cycleCount = count;
this.gab = gab;
timer.Interval = 1000;
timer.Tick += (a, b) =>
{
dlg.Text = string.Format("Cycle {0} of {1}", cycleIndex + 1, cycleCount);
dlg.GarbageCollect();
if (((DebugInfo)dlg.Properties.SelectedObject).ActiveTasks == 0)
{
// Make sure the value is stable at zero
++zeroCount;
if (zeroCount >= 3)
{
zeroCount = 0;
Logger.Instance.Debug(this, "CYCLER");
++cycleIndex;
if (cycleIndex >= cycleCount)
{
timer.Stop();
dlg.Hide();
ThisAddIn.Instance.Quit();
}
else
{
DebugCycle();
}
}
}
};
}
public void Run()
{
timer.Start();
}
private void DebugCycle()
{
Tasks.Task(gab, "DebugCycle", () =>
{
gab.FullResync();
});
}
}
private DebugCycleInfo cycle;
internal void DebugCycle(int count)
{
GAB.FeatureGAB gab = ThisAddIn.Instance.GetFeature<GAB.FeatureGAB>();
if (gab != null)
{
cycle = new DebugCycleInfo(this, gab, count);
cycle.Run();
}
}
#region Logging
private const string INDENT = "+";
@ -89,11 +159,12 @@ namespace Acacia.Features.DebugSupport
private void buttonGC_Click(object sender, EventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GarbageCollect();
}
private void GarbageCollect()
{
Util.GarbageCollect();
UpdateFields();
}

View File

@ -32,6 +32,7 @@ namespace Acacia.Features.DebugSupport
{
Version,
Memory,
Tasks,
Wrappers,
Misc,
System,
@ -101,9 +102,9 @@ namespace Acacia.Features.DebugSupport
}
// Add Add-ins
foreach (COMAddIn addin in ThisAddIn.Instance.Application.COMAddIns)
foreach (KeyValuePair<string,string> addin in ThisAddIn.Instance.COMAddIns)
{
PropertyDescriptor p = new CustomPropertyDescriptor<string, DebugInfo>(addin.ProgId, DebugCategory.AddIns, addin.Description);
PropertyDescriptor p = new CustomPropertyDescriptor<string, DebugInfo>(addin.Key, DebugCategory.AddIns, addin.Value);
properties.Add(p);
}
}
@ -133,6 +134,25 @@ namespace Acacia.Features.DebugSupport
#endregion
#region Tasks
[DebugCategory(DebugCategory.Tasks)]
public string Threading
{
get { return Tasks.Executor.Name; }
}
[DebugCategory(DebugCategory.Tasks)]
public long ActiveTasks { get { return Statistics.StartedTasks - Statistics.FinishedTasks; } }
[DebugCategory(DebugCategory.Tasks)]
public long StartedTasks { get { return Statistics.StartedTasks; } }
[DebugCategory(DebugCategory.Tasks)]
public long FinishedTasks { get { return Statistics.FinishedTasks; } }
#endregion
#region Wrappers
[DebugCategory(DebugCategory.Wrappers)]
@ -163,12 +183,6 @@ namespace Acacia.Features.DebugSupport
}
}
[DebugCategory(DebugCategory.Misc)]
public string Threading
{
get { return Tasks.Executor.Name; }
}
[DebugCategory(DebugCategory.Misc)]
public bool ZPushSync
{
@ -227,14 +241,14 @@ namespace Acacia.Features.DebugSupport
#endregion
#region Outlook
#region Outlook
[DebugCategory(DebugCategory.System)]
public string OutlookVersion
{
get
{
return ThisAddIn.Instance.Application.Version;
return ThisAddIn.Instance.Version;
}
}
@ -247,7 +261,7 @@ namespace Acacia.Features.DebugSupport
}
}
#endregion
#endregion
#region Helpers

View File

@ -13,8 +13,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using Acacia.Features.ReplyFlags;
@ -47,8 +45,16 @@ namespace Acacia.Features.DebugSupport
RegisterButton(this, "Settings", false, ShowSettings);
}
public override void AfterStartup()
{
/*DebugDialog dd = new DebugDialog();
dd.Show();
dd.DebugCycle(5);*/
}
#region About dialog
public void ShowAbout()
{
new AboutDialog().ShowDialog();

View File

@ -25,6 +25,9 @@ namespace Acacia.Features.DebugSupport
{
public static class Statistics
{
public static long StartedTasks;
public static long FinishedTasks;
public static long CreatedWrappers;
public static long DeletedWrappers;
public static long DisposedWrappers;

View File

@ -14,7 +14,6 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -58,9 +57,9 @@ namespace Acacia.Features
return null;
}
protected static Microsoft.Office.Interop.Outlook.Application App
virtual public void GetCapabilities(ZPushCapabilities caps)
{
get { return ThisAddIn.Instance.Application; }
caps.Add(Name.ToLower());
}
#region Debug options
@ -195,14 +194,11 @@ namespace Acacia.Features
#region Event helpers
private static MailEvents _mailEvents;
protected static MailEvents MailEvents
{
get
{
if (_mailEvents == null)
_mailEvents = new MailEvents(App);
return _mailEvents;
return ThisAddIn.Instance.MailEvents;
}
}
@ -227,6 +223,11 @@ namespace Acacia.Features
}
public virtual void AfterStartup()
{
}
#endregion
#region Z-Push channels

View File

@ -33,6 +33,7 @@ namespace Acacia.Features
typeof(FreeBusy.FeatureFreeBusy),
typeof(GAB.FeatureGAB),
typeof(Notes.FeatureNotes),
typeof(SecondaryContacts.FeatureSecondaryContacts),
typeof(SendAs.FeatureSendAs),
typeof(DebugSupport.FeatureDebugSupport)
};

View File

@ -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);
}
}
@ -190,12 +190,14 @@ namespace Acacia.Features.FreeBusy
if (account != null && handler.Contacts != null)
{
// Look for the email address. If found, use the account associated with the GAB
ISearch<IContactItem> search = handler.Contacts.Search<IContactItem>();
search.AddField("urn:schemas:contacts:email1").SetOperation(SearchOperation.Equal, username);
using (IItem result = search.SearchOne())
using (ISearch<IContactItem> search = handler.Contacts.Search<IContactItem>())
{
if (result != null)
return account;
search.AddField("urn:schemas:contacts:email1").SetOperation(SearchOperation.Equal, username);
using (IItem result = search.SearchOne())
{
if (result != null)
return account;
}
}
}
}

View File

@ -14,7 +14,6 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -39,7 +38,7 @@ namespace Acacia.Features.GAB
private readonly Dictionary<string, GABHandler> _gabsByDomainName = new Dictionary<string, GABHandler>();
private readonly HashSet<string> _gabFolders = new HashSet<string>();
private readonly HashSet<string> _domains = new HashSet<string>();
private ZPushLocalStore _store;
private IStore _store;
private int _processing;
public FeatureGAB()
@ -64,8 +63,11 @@ namespace Acacia.Features.GAB
public override void Startup()
{
MailEvents.BeforeDelete += SuppressEventHandler_Delete;
MailEvents.Write += SuppressEventHandler_Modify;
if (SuppressModifications && MailEvents != null)
{
MailEvents.BeforeDelete += SuppressEventHandler_Delete;
MailEvents.Write += SuppressEventHandler_Modify;
}
Watcher.AccountDiscovered += AccountDiscovered;
Watcher.AccountRemoved += AccountRemoved;
Watcher.AccountsScanned += AccountsScanned;
@ -118,6 +120,15 @@ namespace Acacia.Features.GAB
}
private static readonly BoolOption OPTION_PROCESS_MESSAGE = new BoolOption("ProcessMessage", true);
[AcaciaOption("If disabled, existing contacts are not deleted when a chunk is processed. " +
"This should only be disabled for debug purposes.")]
public bool ProcessMessageDeleteExisting
{
get { return GetOption(OPTION_PROCESS_MESSAGE_DELETE_EXISTING); }
set { SetOption(OPTION_PROCESS_MESSAGE_DELETE_EXISTING, value); }
}
private static readonly BoolOption OPTION_PROCESS_MESSAGE_DELETE_EXISTING = new BoolOption("ProcessMessageDeleteExisting", true);
[AcaciaOption("If disabled, contacts are not created from incoming GAB messages. " +
"This should only be disabled for debug purposes.")]
public bool CreateContacts
@ -173,6 +184,42 @@ namespace Acacia.Features.GAB
}
private static readonly BoolOption OPTION_CHECK_UNUSED = new BoolOption("CheckUnused", true);
[AcaciaOption("If disabled, existing contacts are not cleared before new contacts are created. " +
"This should only be disabled for debug purposes.")]
public bool ClearContacts
{
get { return GetOption(OPTION_CLEAR_CONTACTS); }
set { SetOption(OPTION_CLEAR_CONTACTS, value); }
}
private static readonly BoolOption OPTION_CLEAR_CONTACTS = new BoolOption("ClearContacts", true);
[AcaciaOption("If disabled, existing contact folders are not deleted before new contacts are created. " +
"This should only be disabled for debug purposes.")]
public bool DeleteExistingFolder
{
get { return GetOption(OPTION_DELETE_EXISTING_FOLDER); }
set { SetOption(OPTION_DELETE_EXISTING_FOLDER, value); }
}
private static readonly BoolOption OPTION_DELETE_EXISTING_FOLDER = new BoolOption("DeleteExistingFolder", true);
[AcaciaOption("If disabled, deleted items are not removed from the waste basket. " +
"This should only be disabled for debug purposes.")]
public bool EmptyDeletedItems
{
get { return GetOption(OPTION_EMPTY_DELETED_ITEMS); }
set { SetOption(OPTION_EMPTY_DELETED_ITEMS, value); }
}
private static readonly BoolOption OPTION_EMPTY_DELETED_ITEMS = new BoolOption("EmptyDeletedItems", true);
[AcaciaOption("If enabled, modifications to the GAB folder are suppressed. " +
"This should only be disabled for debug purposes.")]
public bool SuppressModifications
{
get { return GetOption(OPTION_SUPPRESS_MODIFICATIONS); }
set { SetOption(OPTION_SUPPRESS_MODIFICATIONS, value); }
}
private static readonly BoolOption OPTION_SUPPRESS_MODIFICATIONS = new BoolOption("SuppressModifications", true);
#endregion
#region Modification suppression
@ -203,7 +250,7 @@ namespace Acacia.Features.GAB
if (_processing == 0)
{
// Check parent folder is a GAB contacts folder
if (_gabFolders.Contains(item.ParentEntryId) && IsGABItem(item))
if (_gabFolders.Contains(item.ParentEntryID) && IsGABItem(item))
{
DoSuppressEvent(findInspector ? item : null, ref cancel);
}
@ -233,16 +280,19 @@ namespace Acacia.Features.GAB
/// <param name="cancel"></param>
private void DoSuppressEvent(IItem item, ref bool cancel)
{
if (item != null)
// TODO: Find and close the inspector
/*if (item != null)
{
foreach (Inspector inspector in App.Inspectors)
foreach (Inspector inspector in ThisAddIn.Instance.Inspectors)
{
if (item.EntryId == inspector.CurrentItem.EntryID)
{
break;
}
}
}
}*/
// Show message and cancel event
MessageBox.Show(StringUtil.GetResourceString("GABEvent_Body"),
StringUtil.GetResourceString("GABEvent_Title"),
MessageBoxButtons.OK,
@ -263,16 +313,15 @@ namespace Acacia.Features.GAB
BeginProcessing();
// Delete any contacts folders in the local store
using (ZPushLocalStore store = ZPushLocalStore.GetInstance(App))
if (DeleteExistingFolder)
{
if (store != null)
using (IStore store = ZPushLocalStore.GetInstance(ThisAddIn.Instance))
{
using (IFolder root = store.RootFolder)
if (store != null)
{
foreach (IFolder folder in root.GetSubFolders<IFolder>())
using (IFolder root = store.GetRootFolder())
{
// TODO: let enumerator handle this
using (folder)
foreach (IFolder folder in root.GetSubFolders<IFolder>().DisposeEnum())
{
try
{
@ -293,10 +342,14 @@ namespace Acacia.Features.GAB
}
// Do the resync
int remaining = _gabsByDomainName.Count;
foreach (GABHandler gab in _gabsByDomainName.Values)
{
Logger.Instance.Debug(this, "FullResync: Starting resync: {0}", gab.DisplayName);
Tasks.Task(this, "FullResync", () => gab.FullResync());
Tasks.Task(this, "FullResync", () =>
{
gab.FullResync();
});
}
}
finally
@ -353,12 +406,12 @@ namespace Acacia.Features.GAB
_store.Dispose();
_store = null;
}
_store = ZPushLocalStore.GetInstance(App);
_store = ZPushLocalStore.GetInstance(ThisAddIn.Instance);
if (_store == null)
return null;
// Try to find the existing GAB
using (IFolder root = _store.RootFolder)
using (IFolder root = _store.GetRootFolder())
{
IAddressBook gab = FindGABForDomain(root, domainName);
if (gab == null)
@ -376,13 +429,16 @@ namespace Acacia.Features.GAB
gab.AttrHidden = false;
// Update admin
_gabFolders.Add(gab.EntryId);
_gabFolders.Add(gab.EntryID);
GABInfo gabInfo = GABInfo.Get(gab, domainName);
gabInfo.Store(gab);
// Hook BeforeMove event to prevent modifications
// TODO: use ZPushWatcher for this?
gab.BeforeItemMove += SuppressMoveEventHandler;
if (SuppressModifications)
{
// Hook BeforeMove event to prevent modifications
// TODO: use ZPushWatcher for this?
gab.BeforeItemMove += SuppressMoveEventHandler;
}
return gab;
}
@ -390,8 +446,11 @@ namespace Acacia.Features.GAB
private void DisposeGABContacts(IAddressBook gab)
{
// Unhook the event to prevent the gab lingering in memory
gab.BeforeItemMove -= SuppressMoveEventHandler;
if (SuppressModifications)
{
// Unhook the event to prevent the gab lingering in memory
gab.BeforeItemMove -= SuppressMoveEventHandler;
}
}
public static GABInfo GetGABContactsFolderInfo(IFolder folder)
@ -410,7 +469,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) =>
{
@ -469,12 +528,12 @@ namespace Acacia.Features.GAB
_store.Dispose();
_store = null;
}
_store = ZPushLocalStore.GetInstance(App);
_store = ZPushLocalStore.GetInstance(ThisAddIn.Instance);
if (_store == null)
return;
bool deletedSomething = false;
using (IFolder root = _store.RootFolder)
using (IFolder root = _store.GetRootFolder())
{
foreach (IFolder subfolder in root.GetSubFolders<IFolder>())
{
@ -484,7 +543,7 @@ namespace Acacia.Features.GAB
GABInfo info = GetGABContactsFolderInfo(subfolder);
if (info != null && !_domains.Contains(info.Domain))
{
Logger.Instance.Info(this, "Unused GAB folder: {0} - {1}", subfolder.EntryId, subfolder.Name);
Logger.Instance.Info(this, "Unused GAB folder: {0} - {1}", subfolder.EntryID, subfolder.Name);
try
{
deletedSomething = true;
@ -500,7 +559,7 @@ namespace Acacia.Features.GAB
}
if (deletedSomething)
EmptyDeletedItems();
DoEmptyDeletedItems();
}
private void CheckGABRemoved()
@ -535,14 +594,14 @@ namespace Acacia.Features.GAB
}
}
EmptyDeletedItems();
DoEmptyDeletedItems();
}
}
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;
@ -573,7 +632,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);
@ -606,7 +665,7 @@ namespace Acacia.Features.GAB
try
{
gab.Process(item);
EmptyDeletedItems();
DoEmptyDeletedItems();
}
finally
{
@ -615,8 +674,11 @@ namespace Acacia.Features.GAB
}
}
private void EmptyDeletedItems()
private void DoEmptyDeletedItems()
{
if (!EmptyDeletedItems)
return;
if (_store != null)
_store.EmptyDeletedItems();
}

View File

@ -121,7 +121,7 @@ namespace Acacia.Features.GAB
{
get
{
using(IStore store = Folder.Store)
using(IStore store = Folder.GetStore())
return store.DisplayName;
}
}
@ -136,6 +136,13 @@ namespace Acacia.Features.GAB
private void ClearContacts()
{
if (!_feature.ClearContacts)
{
using (IStorageItem item = GetIndexItem())
item?.Delete();
return;
}
if (Contacts != null)
{
try
@ -193,24 +200,21 @@ namespace Acacia.Features.GAB
return;
// Process the messages
foreach (IItem item in Folder.Items)
foreach (IZPushItem item in Folder.Items.Typed<IZPushItem>())
{
// TODO: make type-checking iterator?
if (item is IZPushItem)
// Store the entry id to fetch again later, the item will be disposed
string entryId = item.EntryID;
Logger.Instance.Trace(this, "Checking chunk: {0}", item.Subject);
if (_feature.ProcessItems2)
{
string entryId = item.EntryId;
Logger.Instance.Trace(this, "Checking chunk: {0}", item.Subject);
if (_feature.ProcessItems2)
Tasks.Task(_feature, "ProcessChunk", () =>
{
Tasks.Task(_feature, "ProcessChunk", () =>
using (IItem item2 = Folder.GetItemById(entryId))
{
using (IItem item2 = Folder.GetItemById(entryId))
{
if (item2 != null)
ProcessMessage((IZPushItem)item2);
}
});
}
if (item2 != null)
ProcessMessage((IZPushItem)item2);
}
});
}
}
}
@ -261,17 +265,18 @@ namespace Acacia.Features.GAB
_feature?.BeginProcessing();
try
{
// Delete the old contacts from this chunk
ISearch<IItem> search = Contacts.Search<IItem>();
search.AddField(PROP_SEQUENCE, true).SetOperation(SearchOperation.Equal, index.numberOfChunks);
search.AddField(PROP_CHUNK, true).SetOperation(SearchOperation.Equal, index.chunk);
foreach (IItem oldItem in search.Search())
if (_feature.ProcessMessageDeleteExisting)
{
// TODO: Search should handle this, like folder enumeration
using (oldItem)
// Delete the old contacts from this chunk
using (ISearch<IItem> search = Contacts.Search<IItem>())
{
Logger.Instance.Trace(this, "Deleting GAB entry: {0}", oldItem.Subject);
oldItem.Delete();
search.AddField(PROP_SEQUENCE, true).SetOperation(SearchOperation.Equal, index.numberOfChunks);
search.AddField(PROP_CHUNK, true).SetOperation(SearchOperation.Equal, index.chunk);
foreach (IItem oldItem in search.Search())
{
Logger.Instance.Trace(this, "Deleting GAB entry: {0}", oldItem.Subject);
oldItem.Delete();
}
}
}
@ -297,7 +302,7 @@ namespace Acacia.Features.GAB
{
using (IStorageItem index = GetIndexItem())
{
return index?.GetUserProperty<int>(PROP_CURRENT_SEQUENCE)?.Value;
return index?.GetUserProperty<int?>(PROP_CURRENT_SEQUENCE);
}
}
set
@ -306,7 +311,7 @@ namespace Acacia.Features.GAB
{
if (value != null)
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = value.Value;
index.SetUserProperty<int>(PROP_CURRENT_SEQUENCE, value.Value);
}
else
{
@ -316,22 +321,25 @@ namespace Acacia.Features.GAB
}
}
private IItem FindNewestChunk()
private ChunkIndex? FindNewestChunkIndex()
{
if (Folder == null)
return null;
// Scan a few of the newest items, in case there is some junk in the ZPush folder
// TODO: this shouldn't happen in production.
// This shouldn't happen in production, but check anyway.
int i = 0;
foreach(IItem item in Folder.ItemsSorted("LastModificationTime", true))
foreach(IItem item in Folder.Items.Sort("LastModificationTime", true))
{
if (ChunkIndex.Parse(item.Subject) != null)
return item;
item.Dispose();
if (i > Constants.ZPUSH_GAB_NEWEST_MAX_CHECK)
return null;
++i;
using (item)
{
ChunkIndex? index = ChunkIndex.Parse(item.Subject);
if (index != null)
return index;
if (i > Constants.ZPUSH_GAB_NEWEST_MAX_CHECK)
return null;
++i;
}
}
return null;
}
@ -341,39 +349,39 @@ namespace Acacia.Features.GAB
try
{
// Find the newest chunk
using (IItem newest = FindNewestChunk())
ChunkIndex? newestChunkIndex = FindNewestChunkIndex();
if (newestChunkIndex == null)
{
if (newest == null)
CurrentSequence = null;
else
CurrentSequence = null;
}
else
{
Logger.Instance.Trace(this, "Newest chunk: {0}", newestChunkIndex.Value);
int? currentSequence = CurrentSequence;
if (!currentSequence.HasValue || currentSequence.Value != newestChunkIndex?.numberOfChunks)
{
Logger.Instance.Trace(this, "Newest chunk: {0}", newest.Subject);
ChunkIndex? newestChunkIndex = ChunkIndex.Parse(newest.Subject);
// Sequence has changed. Delete contacts
Logger.Instance.Trace(this, "Rechunked, deleting contacts");
ClearContacts();
if (!CurrentSequence.HasValue || CurrentSequence.Value != newestChunkIndex?.numberOfChunks)
// Determine new sequence
if (newestChunkIndex == null)
{
// Sequence has changed. Delete contacts
Logger.Instance.Trace(this, "Rechunked, deleting contacts");
ClearContacts();
// Determine new sequence
if (newestChunkIndex == null)
using (IStorageItem index = GetIndexItem())
{
using (IStorageItem index = GetIndexItem())
{
if (index != null)
index.Delete();
}
if (index != null)
index.Delete();
}
else
}
else
{
int numberOfChunks = newestChunkIndex.Value.numberOfChunks;
using (IStorageItem index = GetIndexItem())
{
int numberOfChunks = newestChunkIndex.Value.numberOfChunks;
using (IStorageItem index = GetIndexItem())
{
index.GetUserProperty<int>(PROP_CURRENT_SEQUENCE, true).Value = numberOfChunks;
index.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = CreateChunkStateString(numberOfChunks);
index.Save();
}
index.SetUserProperty(PROP_CURRENT_SEQUENCE, numberOfChunks);
index.SetUserProperty(PROP_LAST_PROCESSED, CreateChunkStateString(numberOfChunks));
index.Save();
}
}
}
@ -402,7 +410,7 @@ namespace Acacia.Features.GAB
{
if (item == null)
return null;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED);
if (string.IsNullOrEmpty(state))
return null;
@ -420,7 +428,7 @@ namespace Acacia.Features.GAB
{
using (IStorageItem item = GetIndexItem())
{
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED)?.Value;
string state = item.GetUserProperty<string>(PROP_LAST_PROCESSED);
string[] parts;
if (string.IsNullOrEmpty(state))
parts = new string[index.numberOfChunks];
@ -434,7 +442,7 @@ namespace Acacia.Features.GAB
parts[index.chunk] = partState;
string combined = string.Join(";", parts);
item.GetUserProperty<string>(PROP_LAST_PROCESSED, true).Value = combined;
item.SetUserProperty(PROP_LAST_PROCESSED, combined);
item.Save();
}
}
@ -598,7 +606,7 @@ namespace Acacia.Features.GAB
{
using (IItem item = FindItemById(memberId))
{
Logger.Instance.Debug(this, "Finding member {0} of {1}: {2}", memberId, id, item?.EntryId);
Logger.Instance.Debug(this, "Finding member {0} of {1}: {2}", memberId, id, item?.EntryID);
if (item != null)
AddGroupMember(group, item);
}
@ -613,17 +621,19 @@ namespace Acacia.Features.GAB
private IItem FindItemById(string id)
{
ISearch<IItem> search = Contacts.Search<IItem>();
search.AddField(PROP_GAB_ID, true).SetOperation(SearchOperation.Equal, id);
return search.SearchOne();
using (ISearch<IItem> search = Contacts.Search<IItem>())
{
search.AddField(PROP_GAB_ID, true).SetOperation(SearchOperation.Equal, id);
return search.SearchOne();
}
}
private void SetItemStandard(IItem item, string id, Dictionary<string, object> value, ChunkIndex index)
{
// Set the chunk data
item.GetUserProperty<int>(PROP_SEQUENCE, true).Value = index.numberOfChunks;
item.GetUserProperty<int>(PROP_CHUNK, true).Value = index.chunk;
item.GetUserProperty<string>(PROP_GAB_ID, true).Value = id;
item.SetUserProperty(PROP_SEQUENCE, index.numberOfChunks);
item.SetUserProperty(PROP_CHUNK, index.chunk);
item.SetUserProperty(PROP_GAB_ID, id);
}
private void AddGroupMember(IDistributionList group, IItem item)
@ -653,7 +663,7 @@ namespace Acacia.Features.GAB
{
using (IItem groupItem = FindItemById(memberOf))
{
Logger.Instance.Debug(this, "Finding group {0} for {1}: {2}", memberOf, id, groupItem?.EntryId);
Logger.Instance.Debug(this, "Finding group {0} for {1}: {2}", memberOf, id, groupItem?.EntryID);
if (groupItem is IDistributionList)
{
AddGroupMember((IDistributionList)groupItem, item);

View File

@ -18,7 +18,6 @@ using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -84,7 +83,7 @@ namespace Acacia.Features.Notes
PatchIfConfirmed(folder);
}
private bool IsNotesFolder(OutlookConstants.SyncType type)
private bool IsNotesFolder(OutlookConstants.SyncType? type)
{
return type == OutlookConstants.SyncType.Note || type == OutlookConstants.SyncType.UserNote;
}
@ -93,7 +92,7 @@ namespace Acacia.Features.Notes
{
// Only patch if on a ZPush server that supports notes. Store the folder as entryId, there have been some
// issues with the folder object being disposed in the past
string folderId = folder.EntryId;
string folderId = folder.EntryID;
ZPushAccount zpush = Watcher.Accounts.GetAccount(folder);
if (zpush != null)
{
@ -122,13 +121,13 @@ namespace Acacia.Features.Notes
Logger.Instance.Trace(this, "PatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
using (IFolder folder = ThisAddIn.Instance.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Patch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder);
OutlookConstants.SyncType? type = FolderUtils.GetFolderSyncType(folder);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
if (IsNotesFolder(type))
{
@ -168,13 +167,13 @@ namespace Acacia.Features.Notes
Logger.Instance.Trace(this, "UnpatchFolder: {0}", folderId);
try
{
using (IFolder folder = Mapping.GetFolderFromID(folderId))
using (IFolder folder = ThisAddIn.Instance.GetFolderFromID(folderId))
{
if (folder == null)
return;
// Unpatch if needed
OutlookConstants.SyncType type = FolderUtils.GetFolderSyncType(folder, true);
OutlookConstants.SyncType? type = FolderUtils.GetFolderSyncType(folder, true);
Logger.Instance.Trace(this, "Notes folder type: {0}", type);
// Unpatch only if the original type is a notes folder, but the current type isn't
if (IsNotesFolder(type) && !IsNotesFolder(FolderUtils.GetFolderSyncType(folder)))
@ -220,7 +219,7 @@ namespace Acacia.Features.Notes
{
if ((int)item.GetProperty(OutlookConstants.PR_ICON_INDEX) != 771)
{
Logger.Instance.Trace(this, "Patching item: {0}", item.EntryId);
Logger.Instance.Trace(this, "Patching item: {0}", item.EntryID);
// Patch standard properties
item.SetProperties(

View File

@ -19,7 +19,6 @@ using Acacia.UI.Outlook;
using Acacia.Utils;
using Acacia.ZPush;
using Acacia.ZPush.Connect;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -48,6 +47,12 @@ namespace Acacia.Features.OutOfOffice
Watcher.ZPushAccountChange += Watcher_ZPushAccountChange;
}
override public void GetCapabilities(ZPushCapabilities caps)
{
caps.Add("oof");
caps.Add("ooftime");
}
private static bool IsOOFEnabled(ActiveSync.SettingsOOF settings)
{
if (settings == null)
@ -227,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

View File

@ -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;

View File

@ -22,7 +22,6 @@ using System.Threading.Tasks;
using Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using static Acacia.DebugOptions;
namespace Acacia.Features.ReplyFlags
@ -48,18 +47,30 @@ namespace Acacia.Features.ReplyFlags
if (ReadEvent)
{
// As a fallback, add an event handler to update the message when displaying it
MailEvents.Read += UpdateReplyStatus;
if (MailEvents != null)
{
MailEvents.Read += UpdateReplyStatus;
}
}
if (SendEvents)
{
// Hook reply and send events to update local state to server
MailEvents.Reply += OnReply;
MailEvents.ReplyAll += OnReplyAll;
MailEvents.Forward += OnForwarded;
if (MailEvents != null)
{
MailEvents.Reply += OnReply;
MailEvents.ReplyAll += OnReplyAll;
MailEvents.Forward += OnForwarded;
}
}
}
override public void GetCapabilities(ZPushCapabilities caps)
{
caps.Add("receiveflags");
caps.Add("sendflags");
}
[AcaciaOption("Enables or disables the handling of update events to mail items. When a mail item is " +
"updated, it is checked to see if the reply flags are up to date. This is the main " +
"mechanism for updating reply flags that change on the server")]

View File

@ -0,0 +1,138 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using Acacia.ZPush;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static Acacia.DebugOptions;
namespace Acacia.Features.SecondaryContacts
{
[AcaciaOption("Provides the possibility to synchronise multiple contacts folders to and from a Z-Push server.")]
public class FeatureSecondaryContacts : Feature
{
private const string SUFFIX_CONTACTS = "\x200B";
private class FolderRegistrationSecondaryContacts : FolderRegistration
{
public FolderRegistrationSecondaryContacts(Feature feature) : base(feature)
{
}
public override bool IsApplicable(IFolder folder)
{
// Check the sync type.
// Also allow again if the sync type is user contact, it may not have been fully patched.
if (FolderUtils.GetFolderSyncType(folder) != OutlookConstants.SyncType.Unknown &&
FolderUtils.GetFolderSyncType(folder) != OutlookConstants.SyncType.UserContact)
return false;
// Check the hidden suffix
if (!folder.Name.EndsWith(SUFFIX_CONTACTS))
return false;
return true;
}
}
// Contains the ids of folders for which we've shown a warning. This is both to prevent
// warning multiple times and to detect the case when the app has been restarted.
private readonly HashSet<string> _warnedFolders = new HashSet<string>();
public FeatureSecondaryContacts()
{
}
public override void Startup()
{
Watcher.WatchFolder(new FolderRegistrationSecondaryContacts(this),
OnUnpatchedFolderDiscovered);
}
private void OnUnpatchedFolderDiscovered(IFolder folder)
{
string strippedName = folder.Name.StripSuffix(SUFFIX_CONTACTS);
Logger.Instance.Debug(this, "Patching secondary contacts folder: {0}", strippedName);
// To patch we need to do the following
// 1) Update the sync type from 18 to 14
// 2) Update the container class from Note to Contact
// 3) Patch the name
// Note that the above steps need to be done in this order and individually for this to work.
//
// At some point after 2 we also need to restart Outlook to make it appear in the list of contact folders.
// So, when the folder is detected, we make it invisible and perform steps 1 and 2. We issue a warning
// that Outlook must be restarted. When the folder is detected again and is invisible, that means we've restarted
// At this point the name is patched and the folder is made visible.
if (!folder.AttrHidden)
{
// Stage 1
// Sync type
Logger.Instance.Trace(this, "Setting sync type");
folder.SetProperty(OutlookConstants.PR_EAS_SYNCTYPE, (int)OutlookConstants.SyncType.UserContact);
// Container type
Logger.Instance.Trace(this, "Setting container class");
folder.SetProperty(OutlookConstants.PR_CONTAINER_CLASS, "IPF.Contact");
// Make it invisible.
folder.AttrHidden = true;
Logger.Instance.Debug(this, "Patched secondary contacts folder: {0}", strippedName);
// Register and show a warning, if not already done.
// Note that patching may be done multiple times.
if (!_warnedFolders.Contains(folder.EntryID))
{
_warnedFolders.Add(folder.EntryID);
if (MessageBox.Show(StringUtil.GetResourceString("SecondaryContactsPatched_Body", strippedName),
StringUtil.GetResourceString("SecondaryContactsPatched_Title"),
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning
) == DialogResult.Yes)
{
ThisAddIn.Instance.Restart();
}
}
}
// If _warnedFolders does not contain the folder (and it's hidden), this means Outlook was restarted.
else if (!_warnedFolders.Contains(folder.EntryID))
{
// Stage 2
// Patch the name
Logger.Instance.Trace(this, "Patching name");
folder.Name = strippedName;
// Show it
folder.AttrHidden = false;
Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
}
}
}
}

View File

@ -22,7 +22,6 @@ using System.Threading.Tasks;
using Acacia.Stubs;
using Acacia.Utils;
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using Acacia.Features.SharedFolders;
using Acacia.ZPush.API.SharedFolders;
using static Acacia.DebugOptions;
@ -30,7 +29,7 @@ using static Acacia.DebugOptions;
namespace Acacia.Features.SendAs
{
[AcaciaOption("Provides the ability to select different senders for Z-Push accounts.")]
public class FeatureSendAs : FeatureDisabled
public class FeatureSendAs : Feature
{
private FeatureSharedFolders _sharedFolders;
@ -50,7 +49,10 @@ namespace Acacia.Features.SendAs
public override void Startup()
{
MailEvents.ItemSend += MailEvents_ItemSend;
if (MailEvents != null)
{
MailEvents.ItemSend += MailEvents_ItemSend;
}
if (SendAsOwner)
{
@ -58,7 +60,10 @@ namespace Acacia.Features.SendAs
_sharedFolders = ThisAddIn.Instance.GetFeature<FeatureSharedFolders>();
if (_sharedFolders != null)
{
MailEvents.Respond += MailEvents_Respond;
if (MailEvents != null)
{
MailEvents.Respond += MailEvents_Respond;
}
}
}
}
@ -66,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);
@ -85,17 +90,21 @@ namespace Acacia.Features.SendAs
{
Logger.Instance.Trace(this, "Checking, Shared folder owner: {0}", shared.Store.UserName);
// It's a shared folder, use the owner as the sender if possible
// TODO: make a wrapper for this
var recip = ThisAddIn.Instance.Application.Session.CreateRecipient(shared.Store.UserName);
Logger.Instance.Trace(this, "Checking, Shared folder owner recipient: {0}", recip.Name);
if (recip != null && recip.Resolve())
using (IRecipient recip = ThisAddIn.Instance.ResolveRecipient(shared.Store.UserName))
{
Logger.Instance.Trace(this, "Sending as: {0}", recip.AddressEntry.Address);
response.SetSender(recip.AddressEntry);
}
else
{
Logger.Instance.Trace(this, "Unable to resolve sender");
Logger.Instance.Trace(this, "Checking, Shared folder owner recipient: {0}", recip.Name);
if (recip != null && recip.IsResolved)
{
Logger.Instance.Trace(this, "Sending as: {0}", recip.Address);
using (IAddressEntry address = recip.GetAddressEntry())
{
response.SetSender(address);
}
}
else
{
Logger.Instance.Trace(this, "Unable to resolve sender");
}
}
}
}
@ -105,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);

View File

@ -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]);
@ -191,7 +191,7 @@ namespace Acacia.Features.SharedFolders
ctx.AddBusy(-count);
// Sync account
_account.SendReceive();
_account.Account.SendReceive();
// Show success
ShowCompletion(Properties.Resources.SharedFolders_Applying_Success);

View File

@ -1,4 +1,6 @@
/// Copyright 2016 Kopano b.v.

using Acacia.Native.MAPI;
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -13,7 +15,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.UI;
using Acacia.UI.Outlook;
using Acacia.Utils;
@ -73,14 +74,6 @@ namespace Acacia.Features.WebApp
}
}
private void Check_AutoDiscover(ZPushAccount account)
{
AutoDiscover(account);
// Update button state
AccountChange(account);
}
private void OpenWebApp()
{
ZPushAccount account = Watcher.CurrentZPushAccount();
@ -107,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 (Exception e)
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;
}
@ -124,7 +117,7 @@ namespace Acacia.Features.WebApp
private string PerformAutoDiscover(ZPushAccount account)
{
// Fetch the txt record
IList<string> txt = DnsUtil.GetTxtRecord(account.DomainName);
IList<string> txt = DnsUtil.GetTxtRecord(account.Account.DomainName);
if (txt == null)
return null;

View File

@ -51,7 +51,7 @@ namespace Acacia
get { return GetOption(null, THREADING); }
set { SetOption(null, THREADING, value); }
}
private static readonly EnumOption<Threading> THREADING = new EnumOption<Threading>("Threading");
private static readonly EnumOption<Threading> THREADING = new EnumOption<Threading>("Threading", Threading.Background);
[AcaciaOption("Enables or disables ZPush account checking. To enable advanced features, it must be known " +
"which accounts use ZPush servers. This option checks responses from ActiveSync servers to " +
@ -118,6 +118,15 @@ namespace Acacia
}
}
[AcaciaOption("Enables or disables item event hooking." +
"Note that if this is disabled, several features may not work correctly.")]
virtual public bool HookItemEvents
{
get { return GetOption(null, HOOK_ITEM_EVENTS); }
set { SetOption(null, HOOK_ITEM_EVENTS, value); }
}
private static readonly BoolOption HOOK_ITEM_EVENTS = new BoolOption("HookItemEvents", true);
#region UI Options
[AcaciaOption("Completely enables or disables modifications to the Outlook UI." +

View File

@ -0,0 +1,34 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native
{
[ComImport]
[Guid("00000114-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleWindow
{
void GetWindow(out IntPtr phwnd);
void ContextSensitiveHelp([In, MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
}
}

View File

@ -0,0 +1,486 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Native.MAPI
{
[Flags]
public enum SaveChangesFlags : UInt32
{
NONE = 0,
KEEP_OPEN_READONLY = 1,
KEEP_OPEN_READWRITE = 2,
FORCE_SAVE = 4,
MAPI_DEFERRED_ERRORS = 8
}
[ComImport]
[Guid("00020303-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMAPIProp
{
void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
void SaveChanges(SaveChangesFlags flags);
void GetProps();
void GetPropList();
void OpenProperty();
void SetProps();
void DeleteProps();
void CopyTo();
void CopyProps();
void GetNamesFromIDs();
void GetIDsFromNames();
}
unsafe public struct SBinary
{
public uint cb;
public byte* ptr;
public byte[] Unmarshal()
{
byte[] result = new byte[cb];
Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
return result;
}
public override string ToString()
{
byte[] b = Unmarshal();
return b.Length.ToString() + ":" + StringUtil.BytesToHex(b);
}
}
unsafe public struct SBinaryArray
{
public uint count;
public SBinary* ptr;
public byte[][] Unmarshal()
{
byte[][] result = new byte[count][];
for (uint i = 0; i < count; ++i)
{
result[i] = ptr[i].Unmarshal();
}
return result;
}
}
public enum PropType : ushort
{
BOOLEAN = 0x000B,
BINARY = 0x0102,
MV_BINARY = 1102,
DOUBLE = 0x0005,
LONG = 0x0003,
OBJECT = 0x000D,
STRING8 = 0x001E,
MV_STRING8 = 0x101E,
SYSTIME = 0x0040,
UNICODE = 0x001F,
MV_UNICODE = 0x101f
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PropTag
{
public PropType type;
public ushort prop;
public override string ToString()
{
return "<" + prop.ToString("X4") + ":" + type + ">";
}
}
[StructLayout(LayoutKind.Explicit)]
unsafe public struct PropValue
{
[FieldOffset(0)]
public PropTag ulPropTag;
[FieldOffset(4)]
public uint dwAlignPad;
// 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 */
// LPSTR lpszA; /* case PT_STRING8 */
[FieldOffset(8), MarshalAs(UnmanagedType.LPStr)]
public sbyte* lpszA;
// SBinary bin; /* case PT_BINARY */
[FieldOffset(8)]
public SBinary bin;
// 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 */
// 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 override string ToString()
{
switch(ulPropTag.type)
{
case PropType.BOOLEAN:
return b.ToString();
case PropType.STRING8:
return new string(lpszA);
case PropType.BINARY:
return bin.ToString();
//case PropType.UNICODE:
// return lpszW.ToString();
}
return "<unknown>";
}
}
unsafe public struct CommentRestriction
{
public uint cValues;
public SRestriction* res;
public PropValue* prop;
}
unsafe public struct PropertyRestriction
{
public Acacia.Stubs.SearchOperation relop;
public PropTag ulPropTag;
public PropValue* prop;
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + relop + ":" + ulPropTag.ToString();
s += ":" + prop->ToString();
s += "\n";
return s;
}
}
[Flags]
public enum FuzzyLevel : uint
{
FULLSTRING = 0,
SUBSTRING = 1,
PREFIX = 2,
IGNORECASE = 0x00010000,
IGNORENONSPACE = 0x00020000,
LOOSE = 0x00040000
}
unsafe public struct ContentRestriction
{
public FuzzyLevel fuzzy;
public PropTag ulPropTag;
public PropValue* prop;
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + fuzzy + ":" + ulPropTag.ToString();
s += ":" + prop->ToString();
s += "\n";
return s;
}
}
// TODO: merge with ISearch
public enum RestrictionType : UInt32
{
AND,
OR,
NOT,
CONTENT,
PROPERTY,
COMPAREPROPS,
BITMASK,
SIZE,
EXIST,
SUBRESTRICTION,
COMMENT,
COUNT,
ANNOTATION
}
unsafe public struct SubRestriction
{
public uint cb;
public SRestriction* ptr;
public string ToString(int depth)
{
string s = "";
for (uint i = 0; i < cb; ++i)
{
s += ptr[i].ToString(depth);
}
return s;
}
}
unsafe public struct NotRestriction
{
public uint dwReserved;
public SRestriction* ptr;
public string ToString(int depth)
{
return ptr->ToString(depth);
}
}
public enum BMR : uint
{
EQZ = 0,
NEZ = 1
}
unsafe public struct BitMaskRestriction
{
public BMR bmr;
public PropTag prop;
public uint mask;
override public string ToString()
{
return bmr.ToString() + ":" + prop + mask.ToString("X8");
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
return indent + ToString() + "\n";
}
}
unsafe public struct ExistRestriction
{
public uint dwReserved1;
public PropTag prop;
public uint dwReserved2;
override public string ToString()
{
return prop.ToString();
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
return indent + prop.ToString() + "\n";
}
}
[StructLayout(LayoutKind.Explicit)]
unsafe public struct SRestriction
{
[FieldOffset(0)]
public RestrictionType rt;
[FieldOffset(8)]
public SubRestriction sub;
[FieldOffset(8)]
public NotRestriction not;
[FieldOffset(8)]
public ContentRestriction content;
[FieldOffset(8)]
public PropertyRestriction prop;
[FieldOffset(8)]
public BitMaskRestriction bitMask;
[FieldOffset(8)]
public ExistRestriction exist;
[FieldOffset(8)]
public CommentRestriction comment;
public override string ToString()
{
return ToString(0);
}
public string ToString(int depth)
{
string indent = new string(' ', depth);
string s = indent + rt.ToString() + "\n" + indent + "{\n";
switch(rt)
{
case RestrictionType.AND:
case RestrictionType.OR:
s += sub.ToString(depth + 1);
break;
case RestrictionType.NOT:
s += not.ToString(depth + 1);
break;
case RestrictionType.CONTENT:
s += content.ToString(depth + 1);
break;
case RestrictionType.PROPERTY:
s += prop.ToString(depth + 1);
break;
case RestrictionType.BITMASK:
s += bitMask.ToString(depth + 1);
break;
case RestrictionType.EXIST:
s += exist.ToString(depth + 1);
break;
/* TODO COMPAREPROPS,
BITMASK,
SIZE,
SUBRESTRICTION,
COMMENT,
COUNT,
ANNOTATION*/
}
s += indent + "}\n";
return s;
}
}
[Flags]
public enum GetSearchCriteriaState : UInt32
{
NONE = 0,
SEARCH_RUNNING = 1,
SEARCH_REBUILD = 2,
SEARCH_RECURSIVE = 4,
SEARCH_FOREGROUND = 8
}
[Flags]
public enum SetSearchCriteriaFlags : UInt32
{
NONE = 0,
STOP_SEARCH = 0x00000001,
RESTART_SEARCH = 0x00000002,
RECURSIVE_SEARCH = 0x00000004,
SHALLOW_SEARCH = 0x00000008,
FOREGROUND_SEARCH = 0x00000010,
BACKGROUND_SEARCH = 0x00000020,
}
[ComImport]
[Guid("0002030B-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe public interface IMAPIContainer// TODO : IMAPIProp
{
// IMAPIProp
void GetLastError(Int32 hResult, UInt32 flags, out IntPtr ptr);
void SaveChanges(SaveChangesFlags flags);
void GetProps();
void GetPropList();
void OpenProperty();
void SetProps();
void DeleteProps();
void CopyTo();
void CopyProps();
void GetNamesFromIDs();
void GetIDsFromNames();
void GetContentsTable(UInt32 flags, out IntPtr table);
void GetHierarchyTable();
void OpenEntry();
void SetSearchCriteria(SRestriction* lppRestriction, SBinaryArray* lppContainerList, SetSearchCriteriaFlags flags);
void GetSearchCriteria(UInt32 flags, SRestriction** lppRestriction, SBinaryArray** lppContainerList, out GetSearchCriteriaState state);
}
[ComImport]
[Guid("0002030C-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMAPIFolder : IMAPIContainer
{
void CreateMessage();
void CopyMessages();
void DeleteMessages();
void CreateFolder();
void CopyFolder();
void DeleteFolder();
void SetReadFlags();
void GetMessageStatus();
void SetMessageStatus();
void SaveContentsSort();
void EmptyFolder();
}
/* Example search code
{
MAPIFolder folder = (MAPIFolder)account.Store.GetSpecialFolder(Microsoft.Office.Interop.Outlook.OlSpecialFolders.olSpecialFolderReminders);
dynamic obj = folder.MAPIOBJECT;
IMAPIFolder imapi = obj as IMAPIFolder;
//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());
restrict->rt = RestrictionType.AND;
imapi.SetSearchCriteria(restrict, sb1, SetSearchCriteriaFlags.NONE);
//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);
} */
}

View File

@ -83,6 +83,8 @@ namespace Acacia
public const string PR_SUBJECT = PROP + "0037" + PT_UNICODE;
public const string PR_CONTAINER_CLASS = PROP + "3613" + PT_UNICODE;
#endregion
#region Email specific
@ -120,6 +122,7 @@ namespace Acacia
public const string PR_EAS_SYNCTYPE = PROP + "6A1A" + PT_LONG;
public const string PR_EAS_SYNC2 = PROP + "6A1D" + PT_BOOLEAN;
public const string PR_NET_FOLDER_FLAGS = PROP + "36DE" + PT_LONG;
public const string PR_EAS_NAME = PROP + "6915" + PT_UNICODE;
public enum SyncType
{

View File

@ -60,6 +60,24 @@ namespace Acacia.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to The password for account &apos;{0}&apos; is not available. Advanced Z-Push features will not work..
/// </summary>
internal static string AccountNoPassword_Body {
get {
return ResourceManager.GetString("AccountNoPassword_Body", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password unavailable.
/// </summary>
internal static string AccountNoPassword_Title {
get {
return ResourceManager.GetString("AccountNoPassword_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Support.
/// </summary>
@ -785,6 +803,24 @@ namespace Acacia.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to To synchronise the contacts folder &apos;{0}&apos;, Outlook must be restarted. Click &apos;Yes&apos; to restart Outlook now, or &apos;No&apos; if you plan to restart Outlook later..
/// </summary>
internal static string SecondaryContactsPatched_Body {
get {
return ResourceManager.GetString("SecondaryContactsPatched_Body", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Contacts folder.
/// </summary>
internal static string SecondaryContactsPatched_Title {
get {
return ResourceManager.GetString("SecondaryContactsPatched_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to open the shared folder. Please ensure you have permission to open the shared folder..
/// </summary>

View File

@ -431,4 +431,18 @@
<data name="Ribbon_About_Supertip" xml:space="preserve">
<value>Shows the about dialog, which contains licensing and version information.</value>
</data>
<data name="SecondaryContactsPatched_Body" xml:space="preserve">
<value>To synchronise the contacts folder '{0}', Outlook must be restarted. Click 'Yes' to restart Outlook now, or 'No' if you plan to restart Outlook later.</value>
<comment>Shown when a secondary contact folder is detected, to inform the user that a restart is required</comment>
</data>
<data name="SecondaryContactsPatched_Title" xml:space="preserve">
<value>Contacts folder</value>
<comment>Shown when a secondary contact folder is detected, to inform the user that a restart is required</comment>
</data>
<data name="AccountNoPassword_Body" xml:space="preserve">
<value>The password for account '{0}' is not available. Advanced Z-Push features will not work.</value>
</data>
<data name="AccountNoPassword_Title" xml:space="preserve">
<value>Password unavailable</value>
</data>
</root>

View File

@ -0,0 +1,71 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
// Replacement for olItemType
public enum ItemType
{
MailItem = 0,
AppointmentItem = 1,
ContactItem = 2,
TaskItem = 3,
JournalItem = 4,
NoteItem = 5,
PostItem = 6,
DistributionListItem = 7,
MobileItemSMS = 11,
MobileItemMMS = 12
}
// Replacement for olDefaultFolders
public enum DefaultFolder
{
DeletedItems = 3,
Outbox = 4,
SentMail = 5,
Inbox = 6,
Calendar = 9,
Contacts = 10,
Journal = 11,
Notes = 12,
Tasks = 13,
Drafts = 16,
FoldersAllPublicFolders = 18,
Conflicts = 19,
SyncIssues = 20,
LocalFailures = 21,
ServerFailures = 22,
Junk = 23,
RssFeeds = 25,
ToDo = 28,
ManagedEmail = 29,
SuggestedContacts = 30
}
public enum AccountType
{
// TODO
EAS,
Other
}
}

View File

@ -0,0 +1,53 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IAccount : IDisposable
{
AccountType AccountType { get; }
IStore Store { get; }
void SendReceive();
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; }
}
}

View File

@ -0,0 +1,90 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Features;
using Acacia.UI.Outlook;
using Acacia.Utils;
using Acacia.ZPush;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSOutlookDelegates = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
public interface IAddIn
{
ZPushWatcher Watcher { get; }
MailEvents MailEvents { get; }
IEnumerable<Feature> Features { get; }
IEnumerable<KeyValuePair<string,string>> COMAddIns { get; }
string Version { get; }
ISyncObject GetSyncObject();
#region UI
OutlookUI OutlookUI { get; }
IWin32Window Window { get; }
IExplorer GetActiveExplorer();
#endregion
#region Event handlers
// TODO: custom event types
event NSOutlookDelegates.ApplicationEvents_11_ItemLoadEventHandler ItemLoad;
event NSOutlookDelegates.ApplicationEvents_11_ItemSendEventHandler ItemSend;
#endregion
#region Miscellaneous methods
// TODO: clean this up
/// <summary>
/// Sends and receives all accounts, or a specific account.
/// </summary>
void SendReceive(IAccount account = null);
/// <summary>
/// Restarts the application
/// </summary>
void Restart();
void Quit();
void InvokeUI(Action action);
IFolder GetFolderFromID(string folderId);
FeatureType GetFeature<FeatureType>()
where FeatureType : Feature;
IRecipient ResolveRecipient(string name);
/// <summary>
/// Returns the store manager. This is a shared object and must NOT be disposed.
/// </summary>
IStores Stores
{
get;
}
#endregion
}
}

View File

@ -29,5 +29,6 @@ namespace Acacia.Stubs
/// </summary>
void Clear();
new IAddressBook Clone();
}
}

View File

@ -1,4 +1,4 @@
/// Copyright 2016 Kopano b.v.
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -22,17 +22,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IUserProperty<Type>
public interface IAddressEntry : IComWrapper
{
#region Properties
Type Value
{
get;
set;
}
#endregion
}
}

View File

@ -22,9 +22,9 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IBase : IDisposable
public interface IBase : IComWrapper
{
#region MAPI properties
#region Properties
bool AttrHidden { get; set; }
@ -34,23 +34,35 @@ namespace Acacia.Stubs
#endregion
string EntryId { get; }
IFolder Parent { get; }
string ParentEntryId { get; }
#region Ids and hierarchy
string EntryID { get; }
IFolder Parent { get; }
string ParentEntryID { get; }
IStore Store { get; }
/// <summary>
/// Quick accessor to Store.Id, to prevent allocation a wrapper for it.
/// Returns the store. The owner is responsible for disposing.
/// </summary>
string StoreId { get; }
IStore GetStore();
/// <summary>
/// Quick accessor to Store.DisplayName, to prevent allocation a wrapper for it.
/// Quick accessor to Store.Id, to prevent allocating a wrapper for it.
/// </summary>
string StoreID { get; }
/// <summary>
/// Quick accessor to Store.DisplayName, to prevent allocating a wrapper for it.
/// </summary>
string StoreDisplayName { get; }
#endregion
#region Methods
void Delete();
bool MustRelease { get; set; }
string ToString();
#endregion
}
}

View File

@ -0,0 +1,28 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IComWrapper : IDisposable
{
bool MustRelease { get; set; }
}
}

View File

@ -0,0 +1,40 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IMSOCommand
{
Bitmap GetImage(Size imageSize);
}
public interface ICommandBars : IComWrapper
{
/// <summary>
/// Returns the command with the specified id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>The command, or null if it does not exist.</returns>
IMSOCommand GetMso(string id);
}
}

View File

@ -27,6 +27,11 @@ namespace Acacia.Stubs
string DLName { get; set; }
string SMTPAddress { get; set; }
/// <summary>
/// Adds a member to the distribution list.
/// </summary>
/// <param name="item">The item. This is not disposed or released.</param>
/// <exception cref="NotSupportedException">If the item is not a contact or distribution list</exception>
void AddMember(IItem item);
}
}

View File

@ -0,0 +1,46 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlookDelegates = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
public interface IExplorer : IOutlookWindow
{
/// <summary>
/// Returns the command bars.
/// </summary>
/// <returns>The command bars. The caller is responsible for disposing.</returns>
ICommandBars GetCommandBars();
/// <summary>
/// Returns the currently selected folder, or null if no folder is selected.
/// </summary>
/// <returns>The folder. The caller is responsible for disposing.</returns>
IFolder GetCurrentFolder();
#region Events
// TODO: custom delegates
event NSOutlookDelegates.ExplorerEvents_10_SelectionChangeEventHandler SelectionChange;
#endregion
}
}

View File

@ -35,12 +35,14 @@ namespace Acacia.Stubs
bool ShowAsOutlookAB { get; set; }
IEnumerable<IItem> Items { get; }
IEnumerable<IItem> ItemsSorted(string field, bool descending);
IItems Items { get; }
IItem GetItemById(string id);
string FullFolderPath { get; }
ItemType DefaultItemType { get; }
#endregion
#region Searching
@ -55,6 +57,11 @@ namespace Acacia.Stubs
IEnumerable<FolderType> GetSubFolders<FolderType>()
where FolderType : IFolder;
IFolders SubFolders
{
get;
}
FolderType GetSubFolder<FolderType>(string name)
where FolderType : IFolder;
@ -101,5 +108,11 @@ namespace Acacia.Stubs
/// function prevents creating lots of wrappers.
/// </summary>
bool IsAtDepth(int depth);
// TODO: remove this. It's a quick hack to find the events associated with this folder for ZPushWatcher.
// make event watching part of the folder instead
ZPushFolder ZPush { get; set; }
IFolder Clone();
}
}

View File

@ -0,0 +1,48 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public delegate void IFolders_FolderEventHandler(IFolder folder);
public delegate void IFolders_EventHandler();
public interface IFolders_Events : IDisposable
{
event IFolders_FolderEventHandler FolderAdd;
event IFolders_FolderEventHandler FolderChange;
event IFolders_EventHandler FolderRemove;
}
public interface IFolders : IEnumerable<IFolder>
{
#region Events
/// <summary>
/// Returns an events subscribption object.
/// </summary>
/// <returns>The events. The caller is responsible for disposing</returns>
IFolders_Events GetEvents();
#endregion
}
}

View File

@ -34,16 +34,31 @@ namespace Acacia.Stubs
string Body { get; set; }
string Subject { get; set; }
/// <summary>
/// Returns the events for the item. The caller is responsible for disposing.
/// </summary>
IItemEvents GetEvents();
#endregion
#region User properties
/// <summary>
/// Retrieves the user property with the specified name.
/// Retrieves the item's user property with the specified name.
/// </summary>
/// <param name="create">If true, the property is created if it does not exist.
/// If false, null is returned in this case</param>
IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false);
/// <typeparam name="Type">The property type.</typeparam>
/// <param name="name">The name of the property.</param>
/// <returns>The property's value, if it exists. If it does not exist, the type's default value is returned.
/// A nullable type can be specified to return null if the property does not exist.</returns>
Type GetUserProperty<Type>(string name);
/// <summary>
/// Sets the property.
/// </summary>
/// <typeparam name="Type">The property type</typeparam>
/// <param name="name"></param>
/// <param name="value"></param>
void SetUserProperty<Type>(string name, Type value);
#endregion

View File

@ -0,0 +1,60 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlookDelegates = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
public interface IItemEvents : IComWrapper
{
#region Event handlers
// TODO: custom delegates
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
}
}

View File

@ -0,0 +1,56 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public delegate void IItems_ItemEventHandler(IItem item);
public interface IItems_Events : IDisposable
{
event IItems_ItemEventHandler ItemAdd;
event IItems_ItemEventHandler ItemChange;
}
public interface IItems : IEnumerable<IItem>
{
/// <summary>
/// Sorts the items.
/// </summary>
/// <param name="field"></param>
/// <param name="descending"></param>
/// <returns>The current collection, which will be sorted</returns>
IItems Sort(string field, bool descending);
/// <summary>
/// Filters the items for a specific type.
/// </summary>
/// <returns>An enumerable for items of the specified type.</returns>
IEnumerable<T> Typed<T>() where T : IItem;
/// <summary>
/// Returns an events subscribption object.
/// </summary>
/// <returns>The events. The caller is responsible for disposing</returns>
IItems_Events GetEvents();
}
}

View File

@ -19,7 +19,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
@ -34,6 +33,10 @@ namespace Acacia.Stubs
string SenderEmailAddress { get; }
string SenderName { get; }
void SetSender(AddressEntry addressEntry);
/// <summary>
/// Sets the sender.
/// </summary>
/// <param name="addressEntry">The address. The caller is responsible for disposing.</param>
void SetSender(IAddressEntry addressEntry);
}
}

View File

@ -1,4 +1,4 @@
/// Copyright 2016 Kopano b.v.
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -22,17 +22,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public enum ItemType
public interface IOutlookWindow : IComWrapper
{
MailItem = 0,
AppointmentItem = 1,
ContactItem = 2,
TaskItem = 3,
JournalItem = 4,
NoteItem = 5,
PostItem = 6,
DistributionListItem = 7,
MobileItemSMS = 11,
MobileItemMMS = 12
}
}

View File

@ -0,0 +1,37 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Stubs
{
public interface IRecipient : IComWrapper
{
bool IsResolved { get; }
string Name { get; }
string Address { get; }
/// <summary>
/// Returns the address entry. The caller is responsible for disposing it.
/// </summary>
IAddressEntry GetAddressEntry();
}
}

View File

@ -22,14 +22,17 @@ using System.Threading.Tasks;
namespace Acacia.Stubs
{
public enum SearchOperation
/// <summary>
/// Order matches MAPI RELOP_ constants
/// </summary>
public enum SearchOperation : uint
{
Smaller,
SmallerEqual,
Greater,
GreaterEqual,
Equal,
NotEqual,
SmallerEqual,
Smaller,
GreaterEqual,
Greater,
Like
}
@ -41,7 +44,8 @@ namespace Acacia.Stubs
public enum SearchOperator
{
Or,
And
And,
Not
}
public interface ISearchOperator
@ -49,8 +53,7 @@ namespace Acacia.Stubs
ISearchField AddField(string name, bool isUserField = false);
}
public interface ISearch<ItemType>
: ISearchOperator
public interface ISearch<ItemType> : ISearchOperator, IDisposable
where ItemType : IItem
{
ISearchOperator AddOperator(SearchOperator oper);

View File

@ -24,9 +24,29 @@ namespace Acacia.Stubs
{
public interface IStore : IDisposable
{
/// <summary>
/// Returns the root folder.
/// </summary>
/// <returns>The root folder. The caller is responsible for disposing.</returns>
IFolder GetRootFolder();
/// <summary>
/// Returns a default folder.
/// </summary>
/// <returns>The default folder. The caller is responsible for disposing.</returns>
IFolder GetDefaultFolder(DefaultFolder folder);
/// <summary>
/// Returns GetDefaultFolder.EntryID, for simplified memory manaement
/// </summary>
string GetDefaultFolderId(DefaultFolder folder);
IItem GetItemFromID(string id);
string DisplayName { get; }
string StoreID { get; }
bool IsFileStore { get; }
string FilePath { get; }
void EmptyDeletedItems();
}
}

View File

@ -0,0 +1,48 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.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);
/// <summary>
/// Manages the stores and associated acounts.
/// </summary>
public interface IStores: IComWrapper, IEnumerable<IStore>
{
/// <summary>
/// Returns the accounts. The accounts are shared objects and must not be disposed.
/// </summary>
IEnumerable<IAccount> Accounts { get; }
event IStores_AccountDiscovered AccountDiscovered;
event IStores_AccountRemoved AccountRemoved;
/// <summary>
/// Adds a file store to the current collection. If the store is already in the collection, an exception is thrown.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>The store. The caller is responsible for disposing.</returns>
IStore AddFileStore(string path);
}
}

View File

@ -0,0 +1,50 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlookDelegates = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
public interface ISyncObject : IComWrapper
{
#region Properties
string Name { get; }
#endregion
#region Methods
void Start();
void Stop();
#endregion
#region Events
// TODO: custom delegates
event NSOutlookDelegates.SyncObjectEvents_OnErrorEventHandler OnError;
event NSOutlookDelegates.SyncObjectEvents_ProgressEventHandler Progress;
event NSOutlookDelegates.SyncObjectEvents_SyncEndEventHandler SyncEnd;
event NSOutlookDelegates.SyncObjectEvents_SyncStartEventHandler SyncStart;
#endregion
}
}

View File

@ -0,0 +1,168 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
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;
}
/// <summary>
/// Triggers an Outlook send/receive operation for this account.
/// </summary>
public void SendReceive()
{
ThisAddIn.Instance.SendReceive(this);
}
#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
}
}

View File

@ -0,0 +1,257 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Features;
using Acacia.Native;
using Acacia.UI.Outlook;
using Acacia.Utils;
using Acacia.ZPush;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class AddInWrapper : IAddIn
{
private readonly NSOutlook.Application _app;
private readonly ThisAddIn _thisAddIn;
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(IAccount account)
{
// TODO: send/receive specific account
NSOutlook.NameSpace session = _app.Session;
try
{
session.SendAndReceive(false);
}
finally
{
ComRelease.Release(session);
}
}
public void Start()
{
_stores.Start();
}
public void Restart()
{
// Can not use the assembly location, as that is in the GAC
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
// Create the path to the restarter
path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(path), "OutlookRestarter.exe");
// Run that
Process process = new Process();
process.StartInfo = new ProcessStartInfo(path, Environment.CommandLine);
process.Start();
// And close us
_app.Quit();
}
public void Quit()
{
_app.Quit();
}
public event NSOutlook.ApplicationEvents_11_ItemLoadEventHandler ItemLoad
{
add { _app.ItemLoad += value; }
remove { _app.ItemLoad -= value; }
}
public event NSOutlook.ApplicationEvents_11_ItemSendEventHandler ItemSend
{
add { _app.ItemSend += value; }
remove { _app.ItemSend -= value; }
}
public ISyncObject GetSyncObject()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.NameSpace session = com.Add(_app.Session);
NSOutlook.SyncObjects syncObjects = com.Add(session.SyncObjects);
return new SyncObjectWrapper(syncObjects.AppFolders);
}
}
#region UI
public OutlookUI OutlookUI { get { return _thisAddIn.OutlookUI; } }
public IExplorer GetActiveExplorer()
{
return new ExplorerWrapper(_app.ActiveExplorer());
}
#region Window handle
private class WindowHandle : IWin32Window
{
private IntPtr hWnd;
public WindowHandle(IntPtr hWnd)
{
this.hWnd = hWnd;
}
public IntPtr Handle
{
get
{
return hWnd;
}
}
}
public IWin32Window Window
{
get
{
IOleWindow win = _app.ActiveWindow() as IOleWindow;
if (win == null)
return null;
try
{
IntPtr hWnd;
win.GetWindow(out hWnd);
return new WindowHandle(hWnd);
}
finally
{
ComRelease.Release(win);
}
}
}
#endregion
#endregion
public ZPushWatcher Watcher { get { return _thisAddIn.Watcher; } }
public MailEvents MailEvents { get { return _thisAddIn.MailEvents; } }
public IEnumerable<Feature> Features { get { return _thisAddIn.Features; } }
public IEnumerable<KeyValuePair<string, string>> COMAddIns
{
get
{
Microsoft.Office.Core.COMAddIns addIns = _app.COMAddIns;
try
{
foreach (Microsoft.Office.Core.COMAddIn comAddin in addIns)
{
try
{
yield return new KeyValuePair<string, string>(comAddin.ProgId, comAddin.Description);
}
finally
{
ComRelease.Release(comAddin);
}
}
}
finally
{
ComRelease.Release(addIns);
}
}
}
public string Version
{
get { return _app.Version; }
}
public FeatureType GetFeature<FeatureType>()
where FeatureType : Feature
{
foreach (Feature feature in Features)
{
if (feature is FeatureType)
return (FeatureType)feature;
}
return default(FeatureType);
}
public void InvokeUI(Action action)
{
// [ZP-992] For some reason using the dispatcher causes a deadlock
// since switching to UI-chunked tasks. Running directly works.
action();
}
public IFolder GetFolderFromID(string folderId)
{
using (ComRelease com = new ComRelease())
{
NSOutlook.NameSpace nmspace = com.Add(_app.Session);
NSOutlook.Folder f = (NSOutlook.Folder)nmspace.GetFolderFromID(folderId);
return Mapping.Wrap<IFolder>(f);
}
}
public IRecipient ResolveRecipient(string name)
{
using (ComRelease com = new ComRelease())
{
NSOutlook.NameSpace session = com.Add(_app.Session);
// Add recipient, unlock after Resolve (which might throw) to wrap
NSOutlook.Recipient recipient = com.Add(session.CreateRecipient(name));
if (recipient == null)
return null;
recipient.Resolve();
return Mapping.Wrap(com.Remove(recipient));
}
}
public IStores Stores
{
get { return _stores; }
}
}
}

View File

@ -15,30 +15,39 @@
/// Consult LICENSE file for details
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
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 AddressBookWrapper : FolderWrapper, IAddressBook
{
public AddressBookWrapper(Folder folder)
public AddressBookWrapper(NSOutlook.MAPIFolder folder)
:
base(folder)
{
}
public override IFolder Clone()
{
return new AddressBookWrapper(CloneComObject());
}
IAddressBook IAddressBook.Clone()
{
return new AddressBookWrapper(CloneComObject());
}
public void Clear()
{
foreach(dynamic item in _item.Items)
foreach(dynamic item in _item.Items.ComEnum())
{
item.Delete();
ComRelease.Release(item);
}
}
}

View File

@ -0,0 +1,35 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
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 AddressEntryWrapper : ComWrapper<NSOutlook.AddressEntry>, IAddressEntry
{
internal AddressEntryWrapper(NSOutlook.AddressEntry item) : base(item)
{
}
internal NSOutlook.AddressEntry RawItem { get { return _item; } }
}
}

View File

@ -19,27 +19,21 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class AppointmentItemWrapper : OutlookWrapper<AppointmentItem>, IAppointmentItem, IZPushItem
class AppointmentItemWrapper : OutlookItemWrapper<NSOutlook.AppointmentItem>, IAppointmentItem, IZPushItem
{
internal AppointmentItemWrapper(AppointmentItem item)
internal AppointmentItemWrapper(NSOutlook.AppointmentItem item)
:
base(item)
{
}
public override string ToString() { return "Appointment: " + Subject; }
protected override PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
#region Properties
#region IAppointmentItem implementation
public DateTime Start
{
@ -58,6 +52,29 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Location = value; }
}
#endregion
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString()
{
return "Appointment:" + Subject;
}
#endregion
#region IItem implementation
public string Body
{
get { return _item.Body; }
@ -70,45 +87,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; }
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
// TODO: release needed
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public void Delete() { _item.Delete(); }
#endregion
}
}

View File

@ -0,0 +1,56 @@
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Features.DebugSupport;
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
{
abstract class ComWrapper<ItemType> : DisposableWrapper, IComWrapper
{
protected readonly ItemType _item;
/// <summary>
/// Creates a wrapper.
/// </summary>
protected ComWrapper(ItemType item)
{
this._item = item;
MustRelease = true;
}
public bool MustRelease
{
get;
set;
}
override protected void DoRelease()
{
if (MustRelease)
{
ComRelease.Release(_item);
}
}
}
}

View File

@ -0,0 +1,65 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOffice = Microsoft.Office.Core;
using System.Drawing;
using stdole;
namespace Acacia.Stubs.OutlookWrappers
{
class CommandBarsWrapper : ComWrapper<NSOffice.CommandBars>, ICommandBars
{
private class MSOCommand : IMSOCommand
{
private readonly CommandBarsWrapper _commands;
private readonly string _id;
public MSOCommand(CommandBarsWrapper commands, string id)
{
this._commands = commands;
this._id = id;
}
public Bitmap GetImage(Size imageSize)
{
IPictureDisp pict = _commands._item.GetImageMso(_id, imageSize.Width, imageSize.Height);
try
{
return ImageUtils.GetBitmapFromHBitmap(new IntPtr(pict.Handle));
}
finally
{
ComRelease.Release(pict);
}
}
}
public CommandBarsWrapper(NSOffice.CommandBars item) : base(item)
{
}
public IMSOCommand GetMso(string id)
{
return new MSOCommand(this, id);
}
}
}

View File

@ -15,30 +15,23 @@
/// Consult LICENSE file for details
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
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 ContactItemWrapper : OutlookWrapper<ContactItem>, IContactItem
class ContactItemWrapper : OutlookItemWrapper<NSOutlook.ContactItem>, IContactItem
{
internal ContactItemWrapper(ContactItem item)
internal ContactItemWrapper(NSOutlook.ContactItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "Contact: " + Subject; }
#region IContactItem implementation
public string CustomerID
@ -179,6 +172,30 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Language = value; }
}
public void SetPicture(string path)
{
_item.AddPicture(path);
}
#endregion
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString()
{
return "Contact:" + Subject;
}
#endregion
#region IItem implementation
@ -195,49 +212,71 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; }
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
// TODO: release needed
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
public void SetPicture(string path)
{
_item.AddPicture(path);
}
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public void Delete() { _item.Delete(); }
#endregion

View File

@ -19,95 +19,64 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class DistributionListWrapper : OutlookWrapper<DistListItem>, IDistributionList
class DistributionListWrapper : OutlookItemWrapper<NSOutlook.DistListItem>, IDistributionList
{
internal DistributionListWrapper(DistListItem item)
internal DistributionListWrapper(NSOutlook.DistListItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
#region Properties
#region IDistributionList implementation
public string SMTPAddress
{
get
{
PropertyAccessor props = _item.PropertyAccessor;
try
{
return (string)props.GetProperty(OutlookConstants.PR_EMAIL1EMAILADDRESS);
}
finally
{
ComRelease.Release(props);
}
return (string)GetProperty(OutlookConstants.PR_EMAIL1EMAILADDRESS);
}
set
{
string displayName = DLName + " (" + value + ")";
byte[] oneOffId = CreateOneOffMemberId(DLName, "SMTP", value);
PropertyAccessor props = _item.PropertyAccessor;
try
{
props.SetProperties(
new string[]
{
OutlookConstants.PR_EMAIL1DISPLAYNAME,
OutlookConstants.PR_EMAIL1EMAILADDRESS,
OutlookConstants.PR_EMAIL1ADDRESSTYPE,
OutlookConstants.PR_EMAIL1ORIGINALDISPLAYNAME,
OutlookConstants.PR_EMAIL1ORIGINALENTRYID
},
new object[]
{
DLName,
value,
"SMTP",
value,
oneOffId
}
);
}
finally
{
ComRelease.Release(props);
}
SetProperties(
new string[]
{
OutlookConstants.PR_EMAIL1DISPLAYNAME,
OutlookConstants.PR_EMAIL1EMAILADDRESS,
OutlookConstants.PR_EMAIL1ADDRESSTYPE,
OutlookConstants.PR_EMAIL1ORIGINALDISPLAYNAME,
OutlookConstants.PR_EMAIL1ORIGINALENTRYID
},
new object[]
{
DLName,
value,
"SMTP",
value,
oneOffId
}
);
}
}
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
public string DLName
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
get { return _item.DLName; }
set { _item.DLName = value; }
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
public void AddMember(IItem item)
{
if (item is IContactItem)
{
string email = ((IContactItem)item).Email1Address;
Recipient recipient = ThisAddIn.Instance.Application.Session.CreateRecipient(email);
if (recipient.Resolve())
_item.AddMember(recipient);
else
Logger.Instance.Warning(this, "Unable to resolve recipient: {0}", email);
AddContactMember((IContactItem)item);
}
else if (item is IDistributionList)
{
@ -115,8 +84,22 @@ namespace Acacia.Stubs.OutlookWrappers
}
else
{
Logger.Instance.Warning(this, "Unknown item type when adding to distlist: {0}", item);
}
throw new NotSupportedException("Unknown item type when adding to distlist: " + item.GetType());
}
}
private void AddContactMember(IContactItem member)
{
string email = member.Email1Address;
using (IRecipient recipient = ThisAddIn.Instance.ResolveRecipient(email))
{
if (recipient.IsResolved)
{
_item.AddMember(((RecipientWrapper)recipient).RawItem);
}
else
Logger.Instance.Warning(this, "Unable to resolve recipient: {0}", email);
}
}
private void AddDistributionListMember(IDistributionList member)
@ -124,9 +107,8 @@ namespace Acacia.Stubs.OutlookWrappers
// Resolving a distribution list can only be done by name. This fails if the name is in multiple
// groups (e.g. 'Germany' and 'Sales Germany' fails to find Germany). Patch the member
// tables explicitly.
PropertyAccessor props = _item.PropertyAccessor;
object[] members = props.GetProperty(OutlookConstants.PR_DISTLIST_MEMBERS);
object[] oneOffMembers = props.GetProperty(OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS);
object[] members = (object[])GetProperty(OutlookConstants.PR_DISTLIST_MEMBERS);
object[] oneOffMembers = (object[])GetProperty(OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS);
// Create the new member ids
byte[] memberId = CreateMemberId(member);
@ -163,7 +145,7 @@ namespace Acacia.Stubs.OutlookWrappers
newOneOffMembers[existingIndex] = oneOffMemberId;
// Write back
props.SetProperties(
SetProperties(
new string[] { OutlookConstants.PR_DISTLIST_MEMBERS, OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS },
new object[] { newMembers, newOneOffMembers }
);
@ -178,7 +160,7 @@ namespace Acacia.Stubs.OutlookWrappers
{
List<byte> id = new List<byte>();
id.AddRange(PREFIX_MEMBER_ID);
id.AddRange(StringUtil.HexToBytes(member.EntryId));
id.AddRange(StringUtil.HexToBytes(member.EntryID));
return id.ToArray();
}
@ -213,14 +195,27 @@ namespace Acacia.Stubs.OutlookWrappers
#endregion
public override string ToString() { return "DistributionList: " + DLName; }
#region Wrapper methods
public string DLName
protected override NSOutlook.UserProperties GetUserProperties()
{
get { return _item.DLName; }
set { _item.DLName = value; }
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString()
{
return "DistributionList: " + DLName;
}
#endregion
#region IItem implementation
public string Body
{
get { return _item.Body; }
@ -233,40 +228,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; }
}
public IFolder Parent { get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); } }
public string ParentEntryId
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
public string StoreId
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
// TODO: release needed
return _item.Parent?.Store?.StoreID;
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
// TODO: release needed
return _item.Parent?.Store?.DisplayName;
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string EntryId { get { return _item.EntryID; } }
public void Delete() { _item.Delete(); }
#endregion
}
}

View File

@ -0,0 +1,56 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
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 ExplorerWrapper : ComWrapper<NSOutlook.Explorer>, IExplorer
{
public ExplorerWrapper(NSOutlook.Explorer item) : base(item)
{
}
public event NSOutlook.ExplorerEvents_10_SelectionChangeEventHandler SelectionChange
{
add { _item.SelectionChange += value; }
remove { _item.SelectionChange -= value; }
}
protected override void DoRelease()
{
base.DoRelease();
}
public ICommandBars GetCommandBars()
{
return new CommandBarsWrapper(_item.CommandBars);
}
public IFolder GetCurrentFolder()
{
return _item.CurrentFolder.Wrap();
}
}
}

View File

@ -14,7 +14,6 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -23,39 +22,66 @@ using System.Threading.Tasks;
using System.Collections;
using Acacia.Utils;
using Acacia.ZPush;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class FolderWrapper : OutlookWrapper<Folder>, IFolder
class FolderWrapper : OutlookWrapper<NSOutlook.Folder>, IFolder
{
public FolderWrapper(Folder folder)
public FolderWrapper(NSOutlook.MAPIFolder folder)
:
base(folder)
base((NSOutlook.Folder)folder)
{
}
protected override PropertyAccessor GetPropertyAccessor()
protected override void DoRelease()
{
base.DoRelease();
}
protected NSOutlook.MAPIFolder CloneComObject()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Application app = com.Add(_item.Application);
NSOutlook.NameSpace session = com.Add(app.Session);
NSOutlook.MAPIFolder folder = session.GetFolderFromID(EntryID);
return folder;
}
}
virtual public IFolder Clone()
{
return new FolderWrapper(CloneComObject());
}
internal NSOutlook.Folder RawItem { get { return _item; } }
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public string FullFolderPath { get { return _item.FullFolderPath; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
@ -69,17 +95,20 @@ namespace Acacia.Stubs.OutlookWrappers
using (ComRelease com = new ComRelease())
{
// The parent of the root item is a session, not null. Hence the explicit type checks.
Folder current = _item;
// _item is managed by this wrapper and does not need to be released.
NSOutlook.Folder current = _item;
for (int i = 0; i < depth; ++i)
{
object parent = current.Parent;
com.Add(parent);
if (!(parent is Folder))
object parent = com.Add(current.Parent);
current = parent as NSOutlook.Folder;
if (current == null)
return false;
current = (Folder)parent;
}
return !(com.Add(current.Parent) is Folder);
// Check if the remaining parent is a folder
object finalParent = com.Add(current.Parent);
return !(finalParent is NSOutlook.Folder);
}
}
@ -92,14 +121,15 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
public string EntryId { get { return _item.EntryID; } }
public string EntryID { get { return _item.EntryID; } }
public IStore Store { get { return StoreWrapper.Wrap(_item.Store); } }
public string StoreId
public IStore GetStore() { return Mapping.Wrap(_item.Store); }
public string StoreID
{
get
{
using (IStore store = Store)
using (IStore store = GetStore())
{
return store.StoreID;
}
@ -109,7 +139,7 @@ namespace Acacia.Stubs.OutlookWrappers
{
get
{
using (IStore store = Store)
using (IStore store = GetStore())
{
return store.DisplayName;
}
@ -118,119 +148,20 @@ namespace Acacia.Stubs.OutlookWrappers
public ItemType ItemType { get { return (ItemType)(int)_item.DefaultItemType; } }
public class IItemsEnumerator<ItemType> : IEnumerator<ItemType>
where ItemType : IItem
{
private Items _items;
private IEnumerator _enum;
private ItemType _last;
public IItemsEnumerator(Folder _folder, string field, bool descending)
{
this._items = _folder.Items;
if (field != null)
{
this._items.Sort("[" + field + "]", descending);
}
this._enum = _items.GetEnumerator();
}
public ItemType Current
{
get
{
if (_last != null)
{
_last.Dispose();
_last = default(ItemType);
}
_last = Mapping.Wrap<ItemType>(_enum.Current);
return _last;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
if (_enum != null)
{
if (_enum is IDisposable)
((IDisposable)_enum).Dispose();
_enum = null;
}
if (_items != null)
{
ComRelease.Release(_items);
_items = null;
}
}
public bool MoveNext()
{
if (_last != null)
{
_last.Dispose();
_last = default(ItemType);
}
return _enum.MoveNext();
}
public void Reset()
{
_enum.Reset();
}
}
public class IItemsEnumerable<ItemType> : IEnumerable<ItemType>
where ItemType : IItem
{
private readonly Folder _folder;
private readonly string _field;
private readonly bool _descending;
public IItemsEnumerable(Folder folder, string field, bool descending)
{
this._folder = folder;
this._field = field;
this._descending = descending;
}
public IEnumerator<ItemType> GetEnumerator()
{
return new IItemsEnumerator<ItemType>(_folder, _field, _descending);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public IEnumerable<IItem> Items
public IItems Items
{
get
{
return new IItemsEnumerable<IItem>(_item, null, false);
return new ItemsWrapper(this);
}
}
public IEnumerable<IItem> ItemsSorted(string field, bool descending)
{
return new IItemsEnumerable<IItem>(_item, field, descending);
}
public IItem GetItemById(string entryId)
{
try
{
using (IStore store = Store)
using (IStore store = GetStore())
{
return store.GetItemFromID(entryId);
}
@ -270,77 +201,76 @@ namespace Acacia.Stubs.OutlookWrappers
return new SearchWrapper<ItemType>(_item.Items);
}
#region Subfolders
public IEnumerable<FolderType> GetSubFolders<FolderType>()
where FolderType : IFolder
{
foreach (MAPIFolder folder in _item.Folders)
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _item.Folders.ComEnum(false))
{
yield return WrapFolder<FolderType>(folder);
yield return folder.Wrap<FolderType>();
};
}
public IFolders SubFolders
{
get
{
return new FoldersWrapper(this);
}
}
public FolderType GetSubFolder<FolderType>(string name)
where FolderType : IFolder
{
// Fetching the folder by name throws an exception if not found, loop and find
// to prevent exceptions in the log
MAPIFolder sub = null;
foreach(MAPIFolder folder in _item.Folders)
// to prevent exceptions in the log.
// Don't release the items in RawEnum, they are release manually or handed to WrapFolders.
NSOutlook.Folder sub = null;
foreach(NSOutlook.Folder folder in _item.Folders.ComEnum(false))
{
if (folder.Name == name)
{
sub = folder;
break;
break; // TODO: does this prevent the rest of the objects from getting released?
}
else
{
ComRelease.Release(folder);
}
}
if (sub == null)
return default(FolderType);
return WrapFolder<FolderType>(sub);
return sub.Wrap<FolderType>();
}
public FolderType CreateFolder<FolderType>(string name)
where FolderType : IFolder
{
Folders folders = _item.Folders;
try
using (ComRelease com = new ComRelease())
{
NSOutlook.Folders folders = com.Add(_item.Folders);
if (typeof(FolderType) == typeof(IFolder))
{
return WrapFolder<FolderType>(folders.Add(name));
return folders.Add(name).Wrap<FolderType>();
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
MAPIFolder newFolder = folders.Add(name, OlDefaultFolders.olFolderContacts);
NSOutlook.MAPIFolder newFolder = folders.Add(name, NSOutlook.OlDefaultFolders.olFolderContacts);
newFolder.ShowAsOutlookAB = true;
return WrapFolder<FolderType>(newFolder);
return newFolder.Wrap<FolderType>();
}
else
throw new NotSupportedException();
}
finally
{
ComRelease.Release(folders);
}
}
private FolderType WrapFolder<FolderType>(MAPIFolder folder)
where FolderType : IFolder
{
if (typeof(FolderType) == typeof(IFolder))
{
return (FolderType)(IFolder)new FolderWrapper((Folder)folder);
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
return (FolderType)(IFolder)new AddressBookWrapper((Folder)folder);
}
else
throw new NotSupportedException();
}
#endregion
public IStorageItem GetStorageItem(string name)
{
StorageItem item = _item.GetStorage(name, OlStorageIdentifierType.olIdentifyBySubject);
NSOutlook.StorageItem item = _item.GetStorage(name, NSOutlook.OlStorageIdentifierType.olIdentifyBySubject);
if (item == null)
return null;
return new StorageItemWrapper(item);
@ -355,16 +285,12 @@ namespace Acacia.Stubs.OutlookWrappers
public ItemType Create<ItemType>()
where ItemType : IItem
{
Items items = _item.Items;
try
using (ComRelease com = new ComRelease())
{
NSOutlook.Items items = com.Add(_item.Items);
object item = items.Add(Mapping.OutlookItemType<ItemType>());
return Mapping.Wrap<ItemType>(item);
}
finally
{
ComRelease.Release(items);
}
}
#endregion
@ -411,14 +337,14 @@ namespace Acacia.Stubs.OutlookWrappers
_item.BeforeItemMove -= HandleBeforeItemMove;
}
private void HandleBeforeItemMove(object item, MAPIFolder target, ref bool cancel)
private void HandleBeforeItemMove(object item, NSOutlook.MAPIFolder target, ref bool cancel)
{
try
{
if (_beforeItemMove != null)
{
using (IItem itemWrapped = Mapping.Wrap<IItem>(item))
using (IFolder targetWrapped = Mapping.Wrap<IFolder>(target))
using (IItem itemWrapped = Mapping.Wrap<IItem>(item, false))
using (IFolder targetWrapped = Mapping.Wrap<IFolder>(target, false))
{
if (itemWrapped != null && targetWrapped != null)
{
@ -435,5 +361,15 @@ namespace Acacia.Stubs.OutlookWrappers
#endregion
public ItemType DefaultItemType
{
get { return (ItemType)(int)_item.DefaultItemType; }
}
public ZPushFolder ZPush
{
get;
set;
}
}
}

View File

@ -0,0 +1,215 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
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 FoldersWrapper : IFolders
{
// Managed by the caller, not released here
private readonly FolderWrapper _folder;
public FoldersWrapper(FolderWrapper folder)
{
this._folder = folder;
}
public IEnumerator<IFolder> GetEnumerator()
{
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _folder.RawItem.Folders.ComEnum(false))
{
yield return folder.Wrap<IFolder>();
};
}
IEnumerator IEnumerable.GetEnumerator()
{
// Don't release the items, the wrapper manages them
foreach (NSOutlook.Folder folder in _folder.RawItem.Folders.ComEnum(false))
{
yield return folder.Wrap<IFolder>();
};
}
#region Events
private class EventsWrapper : ComWrapper<NSOutlook.Folders>, IFolders_Events
{
public EventsWrapper(NSOutlook.Folders item) : base(item)
{
}
#region FolderAdd
private IFolders_FolderEventHandler _folderAdd;
public event IFolders_FolderEventHandler FolderAdd
{
add
{
if (_folderAdd == null)
HookFolderAdd(true);
_folderAdd += value;
}
remove
{
_folderAdd -= value;
if (_folderAdd == null)
HookFolderAdd(false);
}
}
private void HookFolderAdd(bool hook)
{
if (hook)
_item.FolderAdd += HandleFolderAdd;
else
_item.FolderAdd -= HandleFolderAdd;
}
private void HandleFolderAdd(NSOutlook.MAPIFolder folder)
{
try
{
if (_folderAdd != null)
{
using (IFolder folderWrapped = Mapping.Wrap<IFolder>(folder, false))
{
if (folderWrapped != null)
{
_folderAdd(folderWrapped);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderAdd: {0}", e);
}
}
#endregion
#region FolderChange
private IFolders_FolderEventHandler _folderChange;
public event IFolders_FolderEventHandler FolderChange
{
add
{
if (_folderChange == null)
HookFolderChange(true);
_folderChange += value;
}
remove
{
_folderChange -= value;
if (_folderChange == null)
HookFolderChange(false);
}
}
private void HookFolderChange(bool hook)
{
if (hook)
_item.FolderChange += HandleFolderChange;
else
_item.FolderChange -= HandleFolderChange;
}
private void HandleFolderChange(NSOutlook.MAPIFolder folder)
{
try
{
if (_folderChange != null)
{
using (IFolder folderWrapped = Mapping.Wrap<IFolder>(folder, false))
{
if (folderWrapped != null)
{
_folderChange(folderWrapped);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderChange: {0}", e);
}
}
#endregion
#region FolderRemove
private IFolders_EventHandler _folderRemove;
public event IFolders_EventHandler FolderRemove
{
add
{
if (_folderRemove == null)
HookFolderRemove(true);
_folderRemove += value;
}
remove
{
_folderRemove -= value;
if (_folderRemove == null)
HookFolderRemove(false);
}
}
private void HookFolderRemove(bool hook)
{
if (hook)
_item.FolderRemove += HandleFolderRemove;
else
_item.FolderRemove -= HandleFolderRemove;
}
private void HandleFolderRemove()
{
try
{
if (_folderRemove != null)
{
_folderRemove();
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleFolderRemove: {0}", e);
}
}
#endregion
}
public IFolders_Events GetEvents()
{
return new EventsWrapper(_folder.RawItem.Folders);
}
#endregion
}
}

View File

@ -0,0 +1,193 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class ItemEventsWrapper : ComWrapper<NSOutlook.ItemEvents_10_Event>, IItemEvents
{
internal ItemEventsWrapper(NSOutlook.ItemEvents_10_Event item) : base(item)
{
MustRelease = false;
}
#region Events
public event NSOutlook.ItemEvents_10_AfterWriteEventHandler AfterWrite
{
add { _item.AfterWrite += value; }
remove { _item.AfterWrite -= value; }
}
public event NSOutlook.ItemEvents_10_AttachmentAddEventHandler AttachmentAdd
{
add { _item.AttachmentAdd += value; }
remove { _item.AttachmentAdd -= value; }
}
public event NSOutlook.ItemEvents_10_AttachmentReadEventHandler AttachmentRead
{
add { _item.AttachmentRead += value; }
remove { _item.AttachmentRead -= value; }
}
public event NSOutlook.ItemEvents_10_AttachmentRemoveEventHandler AttachmentRemove
{
add { _item.AttachmentRemove += value; }
remove { _item.AttachmentRemove -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAttachmentAddEventHandler BeforeAttachmentAdd
{
add { _item.BeforeAttachmentAdd += value; }
remove { _item.BeforeAttachmentAdd -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAttachmentPreviewEventHandler BeforeAttachmentPreview
{
add { _item.BeforeAttachmentPreview += value; }
remove { _item.BeforeAttachmentPreview -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAttachmentReadEventHandler BeforeAttachmentRead
{
add { _item.BeforeAttachmentRead += value; }
remove { _item.BeforeAttachmentRead -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAttachmentSaveEventHandler BeforeAttachmentSave
{
add { _item.BeforeAttachmentSave += value; }
remove { _item.BeforeAttachmentSave -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAttachmentWriteToTempFileEventHandler BeforeAttachmentWriteToTempFile
{
add { _item.BeforeAttachmentWriteToTempFile += value; }
remove { _item.BeforeAttachmentWriteToTempFile -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeAutoSaveEventHandler BeforeAutoSave
{
add { _item.BeforeAutoSave += value; }
remove { _item.BeforeAutoSave -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeCheckNamesEventHandler BeforeCheckNames
{
add { _item.BeforeCheckNames += value; }
remove { _item.BeforeCheckNames -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeDeleteEventHandler BeforeDelete
{
add { _item.BeforeDelete += value; }
remove { _item.BeforeDelete -= value; }
}
public event NSOutlook.ItemEvents_10_BeforeReadEventHandler BeforeRead
{
add { _item.BeforeRead += value; }
remove { _item.BeforeRead -= value; }
}
public event NSOutlook.ItemEvents_10_CloseEventHandler Close
{
add { _item.Close += value; }
remove { _item.Close -= value; }
}
public event NSOutlook.ItemEvents_10_CustomActionEventHandler CustomAction
{
add { _item.CustomAction += value; }
remove { _item.CustomAction -= value; }
}
public event NSOutlook.ItemEvents_10_CustomPropertyChangeEventHandler CustomPropertyChange
{
add { _item.CustomPropertyChange += value; }
remove { _item.CustomPropertyChange -= value; }
}
public event NSOutlook.ItemEvents_10_ForwardEventHandler Forward
{
add { _item.Forward += value; }
remove { _item.Forward -= value; }
}
public event NSOutlook.ItemEvents_10_OpenEventHandler Open
{
add { _item.Open += value; }
remove { _item.Open -= value; }
}
public event NSOutlook.ItemEvents_10_PropertyChangeEventHandler PropertyChange
{
add { _item.PropertyChange += value; }
remove { _item.PropertyChange -= value; }
}
public event NSOutlook.ItemEvents_10_ReadEventHandler Read
{
add { _item.Read += value; }
remove { _item.Read -= value; }
}
public event NSOutlook.ItemEvents_10_ReadCompleteEventHandler ReadComplete
{
add { _item.ReadComplete += value; }
remove { _item.ReadComplete -= value; }
}
public event NSOutlook.ItemEvents_10_ReplyEventHandler Reply
{
add { _item.Reply += value; }
remove { _item.Reply -= value; }
}
public event NSOutlook.ItemEvents_10_ReplyAllEventHandler ReplyAll
{
add { _item.ReplyAll += value; }
remove { _item.ReplyAll -= value; }
}
public event NSOutlook.ItemEvents_10_SendEventHandler Send
{
add { _item.Send += value; }
remove { _item.Send -= value; }
}
public event NSOutlook.ItemEvents_10_UnloadEventHandler Unload
{
add { _item.Unload += value; }
remove { _item.Unload -= value; }
}
public event NSOutlook.ItemEvents_10_WriteEventHandler Write
{
add { _item.Write += value; }
remove { _item.Write -= value; }
}
#endregion
}
}

View File

@ -0,0 +1,269 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
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 ItemsWrapper : IItems
{
// Managed by the caller, not released here
private readonly FolderWrapper _folder;
private string _field;
private bool _descending;
public ItemsWrapper(FolderWrapper folder)
{
this._folder = folder;
}
public IItems Sort(string field, bool descending)
{
this._field = field;
this._descending = descending;
return this;
}
public IEnumerable<T> Typed<T>() where T: IItem
{
foreach(IItem item in this)
{
if (typeof(T).IsInstanceOfType(item))
{
yield return (T)item;
}
else
{
item.Dispose();
}
}
}
private NSOutlook.Items GetItems()
{
return _folder.RawItem.Items;
}
public IEnumerator<IItem> GetEnumerator()
{
return new ItemsEnumerator<IItem>(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#region Enumeration
public class ItemsEnumerator<ItemType> : ComWrapper<NSOutlook.Items>, IEnumerator<ItemType>
where ItemType : IItem
{
private IEnumerator _enum;
private ItemType _last;
public ItemsEnumerator(ItemsWrapper items) : base(items.GetItems())
{
// Apply any sort options
if (items._field != null)
{
this._item.Sort("[" + items._field + "]", items._descending);
}
// Get the enumerator
this._enum = _item.GetEnumerator();
}
protected override void DoRelease()
{
CleanLast();
if (_enum != null)
{
if (_enum is IDisposable)
((IDisposable)_enum).Dispose();
ComRelease.Release(_enum);
_enum = null;
}
base.DoRelease();
}
public ItemType Current
{
get
{
CleanLast();
_last = Mapping.Wrap<ItemType>(_enum.Current);
return _last;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
private void CleanLast()
{
if (_last != null)
{
_last.Dispose();
_last = default(ItemType);
}
}
public bool MoveNext()
{
CleanLast();
return _enum.MoveNext();
}
public void Reset()
{
CleanLast();
_enum.Reset();
}
}
#endregion
#region Events
private class EventsWrapper : ComWrapper<NSOutlook.Items>, IItems_Events
{
public EventsWrapper(NSOutlook.Items item) : base(item)
{
}
#region ItemAdd
private IItems_ItemEventHandler _itemAdd;
public event IItems_ItemEventHandler ItemAdd
{
add
{
if (_itemAdd == null)
HookItemAdd(true);
_itemAdd += value;
}
remove
{
_itemAdd -= value;
if (_itemAdd == null)
HookItemAdd(false);
}
}
private void HookItemAdd(bool hook)
{
if (hook)
_item.ItemAdd += HandleItemAdd;
else
_item.ItemAdd -= HandleItemAdd;
}
private void HandleItemAdd(object objItem)
{
try
{
if (_itemAdd != null)
{
using (IItem item = Mapping.Wrap<IItem>(objItem, false))
{
if (item != null)
{
_itemAdd(item);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleItemAdd: {0}", e);
}
}
#endregion
#region ItemChange
private IItems_ItemEventHandler _itemChange;
public event IItems_ItemEventHandler ItemChange
{
add
{
if (_itemChange == null)
HookItemChange(true);
_itemChange += value;
}
remove
{
_itemChange -= value;
if (_itemChange == null)
HookItemChange(false);
}
}
private void HookItemChange(bool hook)
{
if (hook)
_item.ItemChange += HandleItemChange;
else
_item.ItemChange -= HandleItemChange;
}
private void HandleItemChange(object objItem)
{
try
{
if (_itemChange != null)
{
using (IItem item = Mapping.Wrap<IItem>(objItem, false))
{
if (item != null)
{
_itemChange(item);
}
}
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Exception in HandleItemChange: {0}", e);
}
}
#endregion
}
public IItems_Events GetEvents()
{
return new EventsWrapper(GetItems());
}
#endregion
}
}

View File

@ -19,27 +19,95 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class MailItemWrapper : OutlookWrapper<MailItem>, IMailItem
class MailItemWrapper : OutlookItemWrapper<NSOutlook.MailItem>, IMailItem
{
internal MailItemWrapper(MailItem item)
internal MailItemWrapper(NSOutlook.MailItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
#region IMailItem implementation
public DateTime? AttrLastVerbExecutionTime
{
get
{
return GetProperty(OutlookConstants.PR_LAST_VERB_EXECUTION_TIME) as DateTime?;
}
set
{
SetProperty(OutlookConstants.PR_LAST_VERB_EXECUTION_TIME, value);
}
}
public int AttrLastVerbExecuted
{
get
{
return (int)GetProperty(OutlookConstants.PR_LAST_VERB_EXECUTED);
}
set
{
SetProperty(OutlookConstants.PR_LAST_VERB_EXECUTED, value);
}
}
public string SenderEmailAddress
{
get
{
using (ComRelease com = new ComRelease())
{
return com.Add(_item.Sender)?.Address;
}
}
}
public string SenderName
{
get
{
using (ComRelease com = new ComRelease())
{
return com.Add(_item.Sender)?.Name;
}
}
}
public void SetSender(IAddressEntry addressEntry)
{
_item.Sender = ((AddressEntryWrapper)addressEntry).RawItem;
}
#endregion
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "Mail: " + Subject; }
public override string ToString()
{
return "Mail:" + Subject;
}
#region Properties
#endregion
#region IItem implementation
public string Body
{
@ -53,37 +121,53 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; }
}
public IStore Store
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get
{
Folder parent = (Folder)_item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
return StoreWrapper.Wrap(parent?.Store);
}
finally
{
ComRelease.Release(parent);
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
}
}
public string StoreId
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
Folder parent = (Folder)_item.Parent;
Store store = null;
try
using (ComRelease com = new ComRelease())
{
store = parent?.Store;
return store?.StoreID;
}
finally
{
ComRelease.Release(parent);
ComRelease.Release(store);
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
@ -92,76 +176,17 @@ namespace Acacia.Stubs.OutlookWrappers
{
get
{
Folder parent = (Folder)_item.Parent;
Store store = null;
try
using (ComRelease com = new ComRelease())
{
store = parent?.Store;
return store?.DisplayName;
}
finally
{
ComRelease.Release(parent);
ComRelease.Release(store);
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string SenderEmailAddress
{
get
{
// TODO: should Sender be released?
return _item.Sender?.Address;
}
}
public string SenderName
{
get { return _item.Sender?.Name; }
}
public void SetSender(AddressEntry addressEntry)
{
_item.Sender = addressEntry;
}
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
{
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
}
}

View File

@ -15,15 +15,16 @@
/// Consult LICENSE file for details
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
// TODO: a clean up is needed, move as much as possible to Wrappers.cs
public static class Mapping
{
@ -32,40 +33,42 @@ namespace Acacia.Stubs.OutlookWrappers
/// </summary>
/// <param name="o">The Outlook object.</param>
/// <returns>The IItem wrapper, or null if the object could not be wrapped</returns>
public static IBase Wrap(object o, bool mustRelease = true)
private static IBase Wrap(object o, bool mustRelease = true)
{
if (o == null)
return null;
IBase wrapper = CreateWrapper(o);
IBase wrapper = CreateWrapper(o, mustRelease);
if (wrapper != null)
wrapper.MustRelease = mustRelease;
ComRelease.LogWrapper(o, wrapper);
return wrapper;
}
private static IBase CreateWrapper(object o)
private static IBase CreateWrapper(object o, bool mustRelease)
{
// TODO: switch on o.Class
if (o is MailItem)
return new MailItemWrapper((MailItem)o);
if (o is AppointmentItem)
return new AppointmentItemWrapper((AppointmentItem)o);
if (o is Folder)
return new FolderWrapper((Folder)o);
if (o is ContactItem)
return new ContactItemWrapper((ContactItem)o);
if (o is DistListItem)
return new DistributionListWrapper((DistListItem)o);
if (o is NoteItem)
return new NoteItemWrapper((NoteItem)o);
if (o is TaskItem)
return new TaskItemWrapper((TaskItem)o);
// TODO: support this?
if (o is ReportItem)
return null;
if (o is NSOutlook.MailItem)
return new MailItemWrapper((NSOutlook.MailItem)o);
if (o is NSOutlook.AppointmentItem)
return new AppointmentItemWrapper((NSOutlook.AppointmentItem)o);
if (o is NSOutlook.Folder)
return new FolderWrapper((NSOutlook.Folder)o);
if (o is NSOutlook.ContactItem)
return new ContactItemWrapper((NSOutlook.ContactItem)o);
if (o is NSOutlook.DistListItem)
return new DistributionListWrapper((NSOutlook.DistListItem)o);
if (o is NSOutlook.NoteItem)
return new NoteItemWrapper((NSOutlook.NoteItem)o);
if (o is NSOutlook.TaskItem)
return new TaskItemWrapper((NSOutlook.TaskItem)o);
// TODO: support others?
if (mustRelease)
{
// The caller assumes a wrapper will be returned, so any lingering object here will never be released.
ComRelease.Release(o);
}
return null;
}
@ -75,59 +78,64 @@ namespace Acacia.Stubs.OutlookWrappers
return (Type)Wrap(o, mustRelease);
}
public static IRecipient Wrap(NSOutlook.Recipient r, bool mustRelease = true)
{
if (r == null)
return null;
RecipientWrapper wrapped = new RecipientWrapper(r);
wrapped.MustRelease = mustRelease;
return wrapped;
}
// TODO: extension methods for this
public static IStore Wrap(NSOutlook.Store obj, bool mustRelease = true)
{
if (obj == null)
return null;
StoreWrapper wrapped = new StoreWrapper(obj);
wrapped.MustRelease = mustRelease;
return wrapped;
}
// TODO: are these not the same now? Differ only on wrong type?
public static Type WrapOrDefault<Type>(object o, bool mustRelease = true)
where Type : IBase
{
IBase wrapped = Wrap(o, mustRelease);
if (wrapped is Type)
return (Type)wrapped;
// Release if required
if (wrapped != null)
wrapped.Dispose();
return default(Type);
}
public static OlItemType OutlookItemType<ItemType>()
public static NSOutlook.OlItemType OutlookItemType<ItemType>()
where ItemType: IItem
{
Type type = typeof(ItemType);
if (type == typeof(IContactItem))
return OlItemType.olContactItem;
return NSOutlook.OlItemType.olContactItem;
if (type == typeof(IDistributionList))
return OlItemType.olDistributionListItem;
return NSOutlook.OlItemType.olDistributionListItem;
throw new NotImplementedException(); // TODO
}
public static OlUserPropertyType OutlookPropertyType<PropType>()
public static NSOutlook.OlUserPropertyType OutlookPropertyType<PropType>()
{
Type type = typeof(PropType);
if (type == typeof(string))
return OlUserPropertyType.olText;
return NSOutlook.OlUserPropertyType.olText;
if (type == typeof(DateTime))
return OlUserPropertyType.olDateTime;
return NSOutlook.OlUserPropertyType.olDateTime;
if (type == typeof(int))
return OlUserPropertyType.olInteger;
return NSOutlook.OlUserPropertyType.olInteger;
if (type.IsEnum)
return OlUserPropertyType.olInteger;
return NSOutlook.OlUserPropertyType.olInteger;
if (type == typeof(string[]))
return OlUserPropertyType.olKeywords;
return NSOutlook.OlUserPropertyType.olKeywords;
throw new NotImplementedException(); // TODO
}
// TODO: this needs to go elsewhere
public static IFolder GetFolderFromID(string folderId)
{
NameSpace nmspace = ThisAddIn.Instance.Application.Session;
try
{
Folder f = (Folder)nmspace.GetFolderFromID(folderId);
return Wrap<IFolder>(f);
}
finally
{
ComRelease.Release(nmspace);
}
}
}
}

View File

@ -14,32 +14,44 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using Acacia.Utils;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class NoteItemWrapper : OutlookWrapper<NoteItem>, INoteItem
class NoteItemWrapper : OutlookItemWrapper<NSOutlook.NoteItem>, INoteItem
{
internal NoteItemWrapper(NoteItem item)
internal NoteItemWrapper(NSOutlook.NoteItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
throw new NotSupportedException("NoteItem does not support user properties");
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "Note: " + Subject; }
public override string ToString()
{
return "Note:" + Subject;
}
#region Properties
#endregion
#region IItem implementation
public string Body
{
@ -50,48 +62,78 @@ namespace Acacia.Stubs.OutlookWrappers
public string Subject
{
get { return _item.Subject; }
set { throw new NotSupportedException(); }
set
{
throw new NotSupportedException("NoteItem does not support setting body");
}
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
// TODO: release needed
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
throw new NotSupportedException();
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public void Delete() { _item.Delete(); }
#endregion
}
}

View File

@ -0,0 +1,82 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
abstract class OutlookItemWrapper<ItemType> : OutlookWrapper<ItemType>
{
public OutlookItemWrapper(ItemType item)
:
base(item)
{
}
public IItemEvents GetEvents()
{
return new ItemEventsWrapper((NSOutlook.ItemEvents_10_Event)_item);
}
public Type GetUserProperty<Type>(string name)
{
using (ComRelease com = new ComRelease())
{
NSOutlook.UserProperties userProperties = com.Add(GetUserProperties());
NSOutlook.UserProperty prop = com.Add(userProperties.Find(name, true));
if (prop == null)
return default(Type);
if (typeof(Type).IsEnum)
return typeof(Type).GetEnumValues().GetValue(prop.Value);
return prop.Value;
}
}
public void SetUserProperty<Type>(string name, Type value)
{
using (ComRelease com = new ComRelease())
{
NSOutlook.UserProperties userProperties = com.Add(GetUserProperties());
NSOutlook.UserProperty prop = com.Add(userProperties.Find(name, true));
if (prop == null)
prop = userProperties.Add(name, Mapping.OutlookPropertyType<Type>());
if (typeof(Type).IsEnum)
{
int i = Array.FindIndex(typeof(Type).GetEnumNames(), n => n.Equals(value.ToString()));
prop.Value = typeof(Type).GetEnumValues().GetValue(i);
}
else
{
prop.Value = value;
}
}
}
/// <summary>
/// Returns the UserProperties associated with the current item.
/// </summary>
/// <returns>An unwrapped UserProperties object. The caller is responsible for releasing this.</returns>
abstract protected NSOutlook.UserProperties GetUserProperties();
}
}

View File

@ -15,7 +15,6 @@
/// Consult LICENSE file for details
using Acacia.Features.DebugSupport;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -23,56 +22,45 @@ using Acacia.Utils;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
/// <summary>
/// Helper for Outlook wrapper implementations
/// </summary>
abstract public class OutlookWrapper<ItemType> : DisposableWrapper
abstract class OutlookWrapper<ItemType> : ComWrapper<ItemType>
{
#region Construction / Destruction
protected ItemType _item;
/// <summary>
/// Creates a wrapper.
/// </summary>
internal OutlookWrapper(ItemType item)
{
this._item = item;
}
~OutlookWrapper()
internal OutlookWrapper(ItemType item) : base(item)
{
}
protected override void DoRelease()
{
// Always release props, as we allocated that
if (_props != null)
{
ComRelease.Release(_props);
_props = null;
}
if (MustRelease)
{
if (_item != null)
{
ComRelease.Release(_item);
_item = default(ItemType);
}
}
base.DoRelease();
}
#endregion
#region Properties implementation
private PropertyAccessor _props;
// Assigned in Props, released in DoRelease
private NSOutlook.PropertyAccessor _props;
private PropertyAccessor Props
private NSOutlook.PropertyAccessor Props
{
get
{
@ -88,7 +76,7 @@ namespace Acacia.Stubs.OutlookWrappers
/// Returns the wrapped item's property accessor.
/// </summary>
/// <returns>The property accessor. The caller is responsible for disposing this.</returns>
abstract protected PropertyAccessor GetPropertyAccessor();
abstract protected NSOutlook.PropertyAccessor GetPropertyAccessor();
#endregion
@ -112,7 +100,14 @@ namespace Acacia.Stubs.OutlookWrappers
{
get
{
return Props.GetProperty(OutlookConstants.PR_ATTR_HIDDEN);
try
{
return Props.GetProperty(OutlookConstants.PR_ATTR_HIDDEN);
}
catch(System.Exception)
{
return false;
}
}
set
{
@ -120,30 +115,6 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
public DateTime? AttrLastVerbExecutionTime
{
get
{
return Props.GetProperty(OutlookConstants.PR_LAST_VERB_EXECUTION_TIME) as DateTime?;
}
set
{
Props.SetProperty(OutlookConstants.PR_LAST_VERB_EXECUTION_TIME, value);
}
}
public int AttrLastVerbExecuted
{
get
{
return Props.GetProperty(OutlookConstants.PR_LAST_VERB_EXECUTED);
}
set
{
Props.SetProperty(OutlookConstants.PR_LAST_VERB_EXECUTED, value);
}
}
public object GetProperty(string property)
{
try
@ -153,7 +124,7 @@ namespace Acacia.Stubs.OutlookWrappers
return null;
return val;
}
catch(System.Exception) { return null; } // TODO: is this fine everywhere?
catch(System.Exception) { return null; }
}
public void SetProperty(string property, object value)

View File

@ -0,0 +1,64 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Utils;
using System;
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 RecipientWrapper : ComWrapper<NSOutlook.Recipient>, IRecipient
{
internal RecipientWrapper(NSOutlook.Recipient item) : base(item)
{
}
internal NSOutlook.Recipient RawItem { get { return _item; } }
public bool IsResolved
{
get
{
return _item.Resolved;
}
}
public string Name
{
get
{
return _item.Name;
}
}
public string Address
{
get
{
return _item.Address;
}
}
public IAddressEntry GetAddressEntry()
{
return new AddressEntryWrapper(_item.AddressEntry);
}
}
}

View File

@ -19,12 +19,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class SearchWrapper<ItemType> : ISearch<ItemType>
class SearchWrapper<ItemType> : ComWrapper<NSOutlook.Items>, ISearch<ItemType>
where ItemType : IItem
{
private interface SearchTerm
@ -151,11 +151,13 @@ namespace Acacia.Stubs.OutlookWrappers
}
private readonly List<SearchTerm> terms = new List<SearchTerm>();
private readonly Items _items;
public SearchWrapper(Items items)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="items">The items to search. The new object takes ownership</param>
public SearchWrapper(NSOutlook.Items items) : base(items)
{
this._items = items;
}
public ISearchOperator AddOperator(SearchOperator oper)
@ -174,31 +176,42 @@ namespace Acacia.Stubs.OutlookWrappers
public IEnumerable<ItemType> Search(int maxResults)
{
List<ItemType> values = new List<ItemType>();
string filter = MakeFilter();
object value = _items.Find(filter);
int count = 0;
object value = _item.Find(filter);
while(value != null)
{
if (values.Count < maxResults)
if (count < maxResults)
{
// Wrap and add if it returns an object. If not, WrapOrDefault will release it
ItemType wrapped = Mapping.WrapOrDefault<ItemType>(value);
if (wrapped != null)
{
values.Add(wrapped);
try
{
yield return wrapped;
}
finally
{
wrapped.Dispose();
}
}
}
else
{
// Release if not returned. Keep looping to release any others
ComRelease.Release(value);
}
value = _items.FindNext();
value = _item.FindNext();
++count;
}
return values;
}
public ItemType SearchOne()
{
object value = _items.Find(MakeFilter());
// Wrap manages com object in value
object value = _item.Find(MakeFilter());
if (value == null)
return default(ItemType);
return Mapping.Wrap<ItemType>(value);

View File

@ -15,31 +15,44 @@
/// Consult LICENSE file for details
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class StorageItemWrapper : OutlookWrapper<StorageItem>, IStorageItem
class StorageItemWrapper : OutlookItemWrapper<NSOutlook.StorageItem>, IStorageItem
{
public StorageItemWrapper(StorageItem item)
public StorageItemWrapper(NSOutlook.StorageItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "StorageItem"; }
public override string ToString()
{
return "StorageItem";
}
#region Properties
#endregion
#region IItem implementation
public string Body
{
@ -53,45 +66,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; }
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
// TODO: release needed
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
return UserPropertyWrapper<Type>.Get(_item.UserProperties, name, create);
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public void Delete() { _item.Delete(); }
#endregion
}
}

View File

@ -19,51 +19,91 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class StoreWrapper : DisposableWrapper, IStore
class StoreWrapper : ComWrapper<NSOutlook.Store>, IStore
{
public static IStore Wrap(Store store)
internal StoreWrapper(NSOutlook.Store store) : base(store)
{
return store == null ? null : new StoreWrapper(store);
}
private Store _store;
private StoreWrapper(Store store)
{
this._store = store;
}
protected override void DoRelease()
{
ComRelease.Release(_store);
_store = null;
}
public IFolder GetRootFolder()
{
return new FolderWrapper((Folder)_store.GetRootFolder());
// FolderWrapper manages the returned Folder
return new FolderWrapper((NSOutlook.Folder)_item.GetRootFolder());
}
private NSOutlook.MAPIFolder GetDefaultFolderObj(DefaultFolder folder)
{
try
{
return (NSOutlook.Folder)_item.GetDefaultFolder((NSOutlook.OlDefaultFolders)(int)folder);
}
catch(Exception)
{
return null;
}
}
public IFolder GetDefaultFolder(DefaultFolder folder)
{
// FolderWrapper manages the returned Folder
return GetDefaultFolderObj(folder).Wrap();
}
public string GetDefaultFolderId(DefaultFolder folder)
{
NSOutlook.MAPIFolder mapiFolder = GetDefaultFolderObj(folder);
try
{
return mapiFolder?.EntryID;
}
finally
{
ComRelease.Release(mapiFolder);
}
}
public IItem GetItemFromID(string id)
{
NameSpace nmspace = _store.Session;
try
{
using (ComRelease com = new ComRelease())
{
NSOutlook.NameSpace nmspace = com.Add(_item.Session);
// Get the item; the wrapper manages it
object o = nmspace.GetItemFromID(id);
return Mapping.Wrap<IItem>(o);
}
finally
{
ComRelease.Release(nmspace);
}
}
public string DisplayName { get { return _store.DisplayName; } }
public string StoreID { get { return _store.StoreID; } }
public string DisplayName { get { return _item.DisplayName; } }
public string StoreID { get { return _item.StoreID; } }
public bool IsFileStore { get { return _item.IsDataFileStore; } }
public string FilePath { get { return _item.FilePath; } }
public void EmptyDeletedItems()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.MAPIFolder f = _item.GetDefaultFolder(NSOutlook.OlDefaultFolders.olFolderDeletedItems);
if (f != null)
{
com.Add(f);
// Normal enumeration fails when deleting. Do it like this.
NSOutlook.Folders folders = com.Add(f.Folders);
for (int i = folders.Count; i > 0; --i)
com.Add(folders[i]).Delete();
NSOutlook.Items items = com.Add(f.Items);
for (int i = items.Count; i > 0; --i)
com.Add(items[i]).Delete();
}
}
}
}
}

View File

@ -0,0 +1,306 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
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<NSOutlook.Stores>, IStores
{
/// <summary>
/// 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.
/// </summary>
private readonly Dictionary<string, AccountWrapper> _accountsByStoreId = new Dictionary<string, AccountWrapper>();
/// <summary>
/// Accounts indexed by SMTPAddress. Null values are not allowed.
/// </summary>
private readonly Dictionary<string, AccountWrapper> _accountsBySmtp = new Dictionary<string, AccountWrapper>();
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
foreach(NSOutlook.Store store in _item)
{
Tasks.Task(null, "AddStore", () =>
{
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);
}
}
/// <summary>
/// Event handler for Stores.StoreAdded event.
/// </summary>
private void Event_StoreAdded(NSOutlook.Store _)
{
try
{
// Accessing the store object causes random crashes, simply iterate to find new stores
Logger.Instance.Trace(this, "StoreAdded");
foreach (NSOutlook.Store rawStore in _item)
{
if (!_accountsByStoreId.ContainsKey(rawStore.StoreID))
{
StoreAdded(rawStore);
}
else ComRelease.Release(rawStore);
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Event_StoreAdded Exception: {0}", e);
}
}
/// <summary>
/// Performs the actions required to handle a new store.
/// </summary>
/// <param name="rawStore">The new store. Ownership is transferred</param>
private void StoreAdded(NSOutlook.Store rawStore)
{
IStore store = new StoreWrapper(rawStore);
try
{
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);
}
else
{
Logger.Instance.Trace(this, "New account store: {0}: {1}", store.DisplayName, account);
// Account has taken ownership of the store
store = null;
OnAccountDiscovered(account);
}
}
catch (System.Exception e)
{
Logger.Instance.Error(this, "Event_StoreAdded Exception: {0}", e);
}
finally
{
store?.Dispose();
}
}
private void CheckAccountsRemoved()
{
try
{
// Collect all the store ids
HashSet<string> stores = new HashSet<string>();
foreach (NSOutlook.Store store in _item)
{
try
{
stores.Add(store.StoreID);
}
finally
{
ComRelease.Release(store);
}
}
// Check if any relevant ones are removed
List<KeyValuePair<string, AccountWrapper>> removed = new List<KeyValuePair<string, AccountWrapper>>();
foreach (KeyValuePair<string, AccountWrapper> 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<string, AccountWrapper> remove in removed)
{
Logger.Instance.Debug(this, "Account removed: {0} - {1}", remove.Value, remove.Key);
_accountsByStoreId.Remove(remove.Key);
if (remove.Value != null)
{
_accountsBySmtp.Remove(remove.Value.SmtpAddress);
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]);
}
}
public IEnumerator<IStore> 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<IAccount> Accounts
{
get
{
return _accountsBySmtp.Values;
}
}
#endregion
#region Registry
/// <summary>
/// Creates the AccountWrapper for the store, from the registry.
/// </summary>
/// <param name="store">The store. Ownership is transferred to the AccountWrapper. If the account is not created, the store is NOT disposed</param>
/// <returns>The AccountWrapper, or null if no account is associated with the store</returns>
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);
}
/// <summary>
/// Finds the registry key for the account associated with the store.
/// </summary>
/// <returns>The registry key, or null if it cannot be found</returns>
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
}
}

View File

@ -0,0 +1,80 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class SyncObjectWrapper : ComWrapper<NSOutlook.SyncObject>, ISyncObject
{
public SyncObjectWrapper(NSOutlook.SyncObject item) : base(item)
{
}
#region Properties
public string Name { get { return _item.Name; } }
#endregion
#region Methods
public void Start()
{
_item.Start();
}
public void Stop()
{
_item.Stop();
}
#endregion
#region Events
public event NSOutlook.SyncObjectEvents_OnErrorEventHandler OnError
{
add { _item.OnError += value; }
remove { _item.OnError -= value; }
}
public event NSOutlook.SyncObjectEvents_ProgressEventHandler Progress
{
add { _item.Progress += value; }
remove { _item.Progress -= value; }
}
public event NSOutlook.SyncObjectEvents_SyncEndEventHandler SyncEnd
{
add { _item.SyncEnd += value; }
remove { _item.SyncEnd -= value; }
}
public event NSOutlook.SyncObjectEvents_SyncStartEventHandler SyncStart
{
add { _item.SyncStart += value; }
remove { _item.SyncStart -= value; }
}
#endregion
}
}

View File

@ -15,31 +15,44 @@
/// Consult LICENSE file for details
using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
public class TaskItemWrapper : OutlookWrapper<TaskItem>, ITaskItem
class TaskItemWrapper : OutlookItemWrapper<NSOutlook.TaskItem>, ITaskItem
{
internal TaskItemWrapper(TaskItem item)
internal TaskItemWrapper(NSOutlook.TaskItem item)
:
base(item)
{
}
protected override PropertyAccessor GetPropertyAccessor()
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
{
return _item.UserProperties;
}
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "Task: " + Subject; }
public override string ToString()
{
return "Task:" + Subject;
}
#region Properties
#endregion
#region IItem implementation
public string Body
{
@ -50,48 +63,75 @@ namespace Acacia.Stubs.OutlookWrappers
public string Subject
{
get { return _item.Subject; }
set { throw new NotSupportedException(); }
set { _item.Subject = value; }
}
public IStore Store { get { return StoreWrapper.Wrap(_item.Parent?.Store); } }
// TODO: release needed
public string StoreId { get { return _item.Parent?.Store?.StoreID; } }
public string StoreDisplayName { get { return _item.Parent?.Store?.DisplayName; } }
#endregion
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{
throw new NotSupportedException();
}
public void Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{
get
{
Folder parent = _item.Parent;
try
// The wrapper manages the returned folder
return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return parent?.EntryID;
}
finally
{
ComRelease.Release(parent);
}
}
}
public string EntryId { get { return _item.EntryID; } }
public IStore GetStore()
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
return Mapping.Wrap(parent?.Store);
}
}
public string StoreID
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public string StoreDisplayName
{
get
{
using (ComRelease com = new ComRelease())
{
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
}
}
public void Delete() { _item.Delete(); }
#endregion
}
}

View File

@ -1,76 +0,0 @@
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers
{
class UserPropertyWrapper<PropType> : IUserProperty<PropType>
{
private readonly UserProperty _prop;
private UserPropertyWrapper(UserProperty prop)
{
this._prop = prop;
}
#region IUserProperty implementation
public PropType Value
{
get
{
if (typeof(PropType).IsEnum)
return typeof(PropType).GetEnumValues().GetValue(_prop.Value);
return _prop.Value;
}
set
{
if (typeof(PropType).IsEnum)
{
int i = Array.FindIndex(typeof(PropType).GetEnumNames(), n => n.Equals(value.ToString()));
_prop.Value = typeof(PropType).GetEnumValues().GetValue(i);
}
else
_prop.Value = value;
}
}
#endregion
#region Helpers
internal static IUserProperty<PropType> Get(UserProperties userProperties, string name, bool create)
{
UserProperty prop = userProperties.Find(name, true);
if (prop == null)
{
if (!create)
return null;
prop = userProperties.Add(name, Mapping.OutlookPropertyType<PropType>());
}
return new UserPropertyWrapper<PropType>(prop);
}
#endregion
}
}

View File

@ -0,0 +1,66 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Acacia.Stubs.OutlookWrappers;
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs
{
public static class Wrappers
{
public static IFolder Wrap(this NSOutlook.MAPIFolder obj)
{
return Mapping.WrapOrDefault<IFolder>(obj);
}
public static FolderType Wrap<FolderType>(this NSOutlook.MAPIFolder folder)
where FolderType : IFolder
{
if (typeof(FolderType) == typeof(IFolder))
{
return (FolderType)(IFolder)new FolderWrapper(folder);
}
else if (typeof(FolderType) == typeof(IAddressBook))
{
return (FolderType)(IFolder)new AddressBookWrapper(folder);
}
else
{
ComRelease.Release(folder);
throw new NotSupportedException();
}
}
public static WrapType Wrap<WrapType>(this object o, bool mustRelease = true)
where WrapType : IBase
{
return Mapping.Wrap<WrapType>(o, mustRelease);
}
public static WrapType WrapOrDefault<WrapType>(this object o, bool mustRelease = true)
where WrapType : IBase
{
return Mapping.WrapOrDefault<WrapType>(o, mustRelease);
}
}
}

View File

@ -19,8 +19,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using Acacia.Features;
using System.Threading;
using System.Windows.Forms;
@ -29,27 +27,23 @@ using Acacia.UI;
using Acacia.ZPush;
using System.Globalization;
using Acacia.UI.Outlook;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
using Acacia.Native;
using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
namespace Acacia
{
public partial class ThisAddIn
{
public static ThisAddIn Instance
public static IAddIn Instance
{
get;
private set;
}
private Control _dispatcher;
public void InvokeUI(Action action)
{
// [ZP-992] For some reason using the dispatcher causes a deadlock
// since switching to UI-chunked tasks. Running directly works.
action();
}
#region Features
/// <summary>
@ -69,15 +63,20 @@ namespace Acacia
private set;
}
public FeatureType GetFeature<FeatureType>()
where FeatureType : Feature
private MailEvents _mailEvents;
public MailEvents MailEvents
{
foreach(Feature feature in Features)
get
{
if (feature is FeatureType)
return (FeatureType)feature;
if (_mailEvents == null)
{
if (GlobalOptions.INSTANCE.HookItemEvents)
{
_mailEvents = new MailEvents(Instance);
}
}
return _mailEvents;
}
return default(FeatureType);
}
#region Startup / Shutdown
@ -100,16 +99,12 @@ namespace Acacia
return;
}
Instance = this;
Instance = new AddInWrapper(this);
// Set the culture info from Outlook's language setting rather than the OS setting
int lcid = Application.LanguageSettings.get_LanguageID(Office.MsoAppLanguageID.msoLanguageIDUI);
int lcid = Application.LanguageSettings.get_LanguageID(Microsoft.Office.Core.MsoAppLanguageID.msoLanguageIDUI);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lcid);
// Create a dispatcher
_dispatcher = new Control();
_dispatcher.CreateControl();
// The synchronization context is needed to allow background tasks to jump back to the UI thread.
// It's null in older versions of .Net, this fixes that
if (SynchronizationContext.Current == null)
@ -118,7 +113,7 @@ namespace Acacia
}
// Create the watcher
Watcher = new ZPushWatcher(Application);
Watcher = new ZPushWatcher(Instance);
OutlookUI.Watcher = Watcher;
// Allow to features to register whatever they need
@ -151,11 +146,17 @@ namespace Acacia
// Start watching events
if (DebugOptions.GetOption(null, DebugOptions.WATCHER_ENABLED))
{
((AddInWrapper)Instance).Start();
Watcher.Start();
}
// Done
Logger.Instance.Debug(this, "Startup done");
Acacia.Features.DebugSupport.Statistics.StartupTime.Stop();
foreach (Feature feature in Features)
feature.AfterStartup();
}
catch (System.Exception e)
{
@ -172,6 +173,7 @@ namespace Acacia
{
try
{
// TODO: is any management of Pages needed here?
Pages.Add(new SettingsPage(Features.ToArray()), Properties.Resources.ThisAddIn_Title);
}
catch(System.Exception e)

View File

@ -224,23 +224,22 @@ namespace Acacia.UI
public List<GABUser> Lookup(string text, int max)
{
// Begin GAB lookup, search on full name or username
ISearch<IContactItem> search = _gab.Contacts.Search<IContactItem>();
ISearchOperator oper = search.AddOperator(SearchOperator.Or);
oper.AddField("urn:schemas:contacts:cn").SetOperation(SearchOperation.Like, text + "%");
oper.AddField("urn:schemas:contacts:customerid").SetOperation(SearchOperation.Like, text + "%");
// Fetch the results up to the limit.
// TODO: make limit a property
List<GABUser> users = new List<GABUser>();
foreach (IContactItem result in search.Search(max))
using (ISearch<IContactItem> search = _gab.Contacts.Search<IContactItem>())
{
using (result)
ISearchOperator oper = search.AddOperator(SearchOperator.Or);
oper.AddField("urn:schemas:contacts:cn").SetOperation(SearchOperation.Like, text + "%");
oper.AddField("urn:schemas:contacts:customerid").SetOperation(SearchOperation.Like, text + "%");
// Fetch the results up to the limit.
// TODO: make limit a property?
List<GABUser> users = new List<GABUser>();
foreach (IContactItem result in search.Search(max))
{
users.Add(new GABUser(result.FullName, result.CustomerID));
}
}
return users;
return users;
}
}
public GABUser LookupExact(string username)
@ -252,13 +251,11 @@ namespace Acacia.UI
search.AddField("urn:schemas:contacts:customerid").SetOperation(SearchOperation.Equal, username);
// Fetch the result, if any.
// TODO: make a SearchOne method?
List<GABUser> users = new List<GABUser>();
foreach (IContactItem result in search.Search(1))
{
using (result)
{
return new GABUser(result.FullName, result.CustomerID);
}
return new GABUser(result.FullName, result.CustomerID);
}
}

View File

@ -1,4 +1,7 @@
/// Copyright 2016 Kopano b.v.

using Acacia.Stubs;
using Acacia.Utils;
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -13,7 +16,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using Microsoft.Office.Core;
using stdole;
using System;
@ -35,105 +37,19 @@ namespace Acacia.UI.Outlook
{
public ImageList Images { get; private set; }
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
private Bitmap GetBitmapFromHBitmap2(IntPtr nativeHBitmap)
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
// Special handling is required to convert a bitmap with alpha channel, FromHBitmap doesn't
// set the correct pixel format
Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap bmp2 = new Bitmap(bmpData.Width, bmpData.Height, PixelFormat.Format32bppArgb);
BitmapData bmpData2 = bmp2.LockBits(bmBounds, ImageLockMode.WriteOnly, bmp2.PixelFormat);
try
{
for (int y = 0; y < bmp.Height; ++y)
{
IntPtr target = bmpData2.Scan0 + bmpData2.Stride * y;
IntPtr source = bmpData.Scan0 + bmpData.Stride * y;
CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
}
}
finally
{
bmp2.UnlockBits(bmpData2);
bmp.UnlockBits(bmpData);
}
return bmp2;
}
private static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
BitmapData bmpData;
if (IsAlphaBitmap(bmp, out bmpData))
return GetlAlphaBitmapFromBitmapData(bmpData);
return bmp;
}
private static Bitmap GetlAlphaBitmapFromBitmapData(BitmapData bmpData)
{
return new Bitmap(
bmpData.Width,
bmpData.Height,
bmpData.Stride,
PixelFormat.Format32bppArgb,
bmpData.Scan0);
}
private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
{
Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
for (int y = 0; y <= bmpData.Height - 1; y++)
{
for (int x = 0; x <= bmpData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));
if (pixelColor.A > 0 & pixelColor.A < 255)
{
return true;
}
}
}
}
finally
{
bmp.UnlockBits(bmpData);
}
return false;
}
public OutlookImageList(params string[] icons)
{
Images = new ImageList();
Images.ColorDepth = ColorDepth.Depth32Bit;
Images.ImageSize = new Size(16, 16);
CommandBars cmdBars = ThisAddIn.Instance.Application.ActiveWindow().CommandBars;
foreach (string id in icons)
using (IExplorer explorer = ThisAddIn.Instance.GetActiveExplorer())
using (ICommandBars cmdBars = explorer.GetCommandBars())
{
IPictureDisp pict = cmdBars.GetImageMso(id, Images.ImageSize.Width, Images.ImageSize.Height);
var img = GetBitmapFromHBitmap2(new IntPtr(pict.Handle));
Images.Images.Add(img);
foreach (string id in icons)
{
Images.Images.Add(cmdBars.GetMso(id).GetImage(Images.ImageSize));
}
}
}
}

View File

@ -14,7 +14,6 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Drawing;

View File

@ -23,7 +23,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Office.Interop.Outlook;
namespace Acacia.UI
{
@ -48,19 +47,19 @@ namespace Acacia.UI
{
get
{
return ThisAddIn.Instance.Application;
return null;
}
}
public OlObjectClass Class
public Microsoft.Office.Interop.Outlook.OlObjectClass Class
{
get
{
return OlObjectClass.olApplication;
return Microsoft.Office.Interop.Outlook.OlObjectClass.olApplication;
}
}
public NameSpace Session
public Microsoft.Office.Interop.Outlook.NameSpace Session
{
get
{
@ -68,7 +67,7 @@ namespace Acacia.UI
}
}
dynamic PropertyPageSite.Parent
dynamic Microsoft.Office.Interop.Outlook.PropertyPageSite.Parent
{
get
{

View File

@ -25,11 +25,12 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Acacia.Features;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.UI
{
[ComVisible(true)]
public partial class SettingsPage : UserControl, Microsoft.Office.Interop.Outlook.PropertyPage
public partial class SettingsPage : UserControl, NSOutlook.PropertyPage
{
private readonly Dictionary<FeatureSettings, bool> _featuresDirty = new Dictionary<FeatureSettings, bool>();
@ -78,8 +79,8 @@ namespace Acacia.UI
Dirty = _featuresDirty.Values.Aggregate((a, b) => a | b);
}
private Microsoft.Office.Interop.Outlook.PropertyPageSite _propertyPageSite;
public Microsoft.Office.Interop.Outlook.PropertyPageSite PropertyPageSite
private NSOutlook.PropertyPageSite _propertyPageSite;
public NSOutlook.PropertyPageSite PropertyPageSite
{
get
{
@ -96,7 +97,8 @@ namespace Acacia.UI
System.Reflection.MethodInfo methodInfo = oleObjectType.GetMethod("GetClientSite");
Object propertyPageSite = methodInfo.Invoke(this, null);
_propertyPageSite = (Microsoft.Office.Interop.Outlook.PropertyPageSite)propertyPageSite;
// TODO: does this need to be released?
_propertyPageSite = (NSOutlook.PropertyPageSite)propertyPageSite;
}
return _propertyPageSite;
}

View File

@ -34,6 +34,12 @@ namespace Acacia.Utils
return t;
}
public Type Remove<Type>(Type t)
{
objects.Remove(t);
return t;
}
public void Dispose()
{
foreach (object o in objects)
@ -55,11 +61,17 @@ namespace Acacia.Utils
}
}
public static void Release(params object[] objs)
{
foreach (object o in objs)
Release(o);
}
public static void Release(object o)
{
if (!Enabled)
return;
if (o == null)
if (o == null || !Marshal.IsComObject(o))
return;
if (Logger.Instance.IsLevelEnabled(LogLevel.TraceExtra))

View File

@ -1,4 +1,4 @@
/// Copyright 2016 Kopano b.v.
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -15,7 +15,6 @@
/// Consult LICENSE file for details
using Acacia.Features.DebugSupport;
using Acacia.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@ -23,15 +22,13 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Acacia.Stubs.OutlookWrappers
namespace Acacia.Utils
{
public abstract class DisposableWrapper : IDisposable
abstract public class DisposableWrapper : IDisposable
{
private static Dictionary<Type, int> typeCounts = new Dictionary<Type, int>();
/// <summary>
/// Creates a wrapper.
/// </summary>
internal DisposableWrapper()
protected DisposableWrapper()
{
Interlocked.Increment(ref Statistics.CreatedWrappers);
this._createdTrace = new System.Diagnostics.StackTrace();
@ -43,9 +40,12 @@ namespace Acacia.Stubs.OutlookWrappers
if (!_isDisposed)
{
Logger.Instance.Warning(this, "Undisposed wrapper: {0}", _createdTrace);
Dispose();
// Don't count auto disposals
Interlocked.Decrement(ref Statistics.DisposedWrappers);
// Dispose, but don't count auto disposals, so the stats show it.
DoRelease();
}
else
{
--typeCounts[GetType()];
}
}
@ -56,6 +56,11 @@ namespace Acacia.Stubs.OutlookWrappers
{
if (!_isDisposed)
{
if (!typeCounts.ContainsKey(GetType()))
typeCounts.Add(GetType(), 1);
else
++typeCounts[GetType()];
Logger.Instance.TraceExtra(this, "Disposing wrapper: {0}", new System.Diagnostics.StackTrace());
_isDisposed = true;
Interlocked.Increment(ref Statistics.DisposedWrappers);
@ -63,12 +68,7 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
public bool MustRelease
{
get;
set;
}
abstract protected void DoRelease();
}
}

View File

@ -28,17 +28,19 @@ namespace Acacia.Utils
{
public static class FolderUtils
{
public static OutlookConstants.SyncType GetFolderSyncType(IFolder folder, bool orig = false)
public static OutlookConstants.SyncType? GetFolderSyncType(IFolder folder, bool orig = false)
{
if (orig)
{
string type = (string)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE_ORIG);
if (string.IsNullOrEmpty(type))
return null;
return (OutlookConstants.SyncType)int.Parse(type);
}
else
{
int type = (int)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE);
return (OutlookConstants.SyncType)type;
int? type = (int?)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE);
return (OutlookConstants.SyncType?)type;
}
}

View File

@ -0,0 +1,63 @@
/// Copyright 2017 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
/// as published by the Free Software Foundation.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Acacia.Utils
{
public static class ImageUtils
{
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
// Special handling is required to convert a bitmap with alpha channel, FromHBitmap doesn't
// set the correct pixel format
Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap bmp2 = new Bitmap(bmpData.Width, bmpData.Height, PixelFormat.Format32bppArgb);
BitmapData bmpData2 = bmp2.LockBits(bmBounds, ImageLockMode.WriteOnly, bmp2.PixelFormat);
try
{
for (int y = 0; y < bmp.Height; ++y)
{
IntPtr target = bmpData2.Scan0 + bmpData2.Stride * y;
IntPtr source = bmpData.Scan0 + bmpData.Stride * y;
CopyMemory(target, source, (uint)Math.Abs(bmpData2.Stride));
}
}
finally
{
bmp2.UnlockBits(bmpData2);
bmp.UnlockBits(bmpData);
}
return bmp2;
}
}
}

View File

@ -14,7 +14,6 @@
///
/// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;
@ -47,20 +46,16 @@ namespace Acacia.Utils
public event MailResponseEventHandler Respond;
public event MailResponseEventHandler Reply;
private void OnReply(MailItem mail, MailItem response)
private void OnReply(IMailItem mail, IMailItem response)
{
try
{
if ((Reply != null || Respond != null) && mail != null)
if ((Reply != null || Respond != null) && mail != null && response != null)
{
using (IMailItem mailWrapped = Mapping.Wrap<IMailItem>(mail, false),
responseWrapped = Mapping.Wrap<IMailItem>(response))
{
if (Reply != null)
Reply(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
if (Reply != null)
Reply(mail, response);
if (Respond != null)
Respond(mail, response);
}
}
catch (System.Exception e)
@ -70,20 +65,16 @@ namespace Acacia.Utils
}
public event MailResponseEventHandler ReplyAll;
private void OnReplyAll(MailItem mail, MailItem response)
private void OnReplyAll(IMailItem mail, IMailItem response)
{
try
{
if ((ReplyAll != null || Respond != null) && mail != null)
if ((ReplyAll != null || Respond != null) && mail != null && response != null)
{
using (IMailItem mailWrapped = Mapping.Wrap<IMailItem>(mail, false),
responseWrapped = Mapping.Wrap<IMailItem>(response))
{
if (ReplyAll != null)
ReplyAll(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
if (ReplyAll != null)
ReplyAll(mail, response);
if (Respond != null)
Respond(mail, response);
}
}
catch (System.Exception e)
@ -93,20 +84,16 @@ namespace Acacia.Utils
}
public event MailResponseEventHandler Forward;
private void OnForward(MailItem mail, MailItem response)
private void OnForward(IMailItem mail, IMailItem response)
{
try
{
if ((Forward != null || Respond != null) && mail != null)
if ((Forward != null || Respond != null) && mail != null && response != null)
{
using (IMailItem mailWrapped = Mapping.Wrap<IMailItem>(mail, false),
responseWrapped = Mapping.Wrap<IMailItem>(response))
{
if (Forward != null)
Forward(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
if (Forward != null)
Forward(mail, response);
if (Respond != null)
Respond(mail, response);
}
}
catch (System.Exception e)
@ -116,16 +103,13 @@ namespace Acacia.Utils
}
public event MailEventHandler Read;
private void OnRead(MailItem mail)
private void OnRead(IMailItem mail)
{
try
{
if (Read != null && mail != null)
{
using (IMailItem wrapped = Mapping.Wrap<IMailItem>(mail, false))
{
Read(wrapped);
}
Read(mail);
}
}
catch (System.Exception e)
@ -135,17 +119,13 @@ namespace Acacia.Utils
}
public event CancellableItemEventHandler BeforeDelete;
private void OnBeforeDelete(object item, ref bool cancel)
private void OnBeforeDelete(IItem item, ref bool cancel)
{
try
{
if (BeforeDelete != null && item != null)
{
using (IItem wrapped = Mapping.Wrap<IItem>(item, false))
{
if (wrapped != null)
BeforeDelete(wrapped, ref cancel);
}
BeforeDelete(item, ref cancel);
}
}
catch(System.Exception e)
@ -156,17 +136,13 @@ namespace Acacia.Utils
// TODO: should this be CancellableMailItemEventHandler?
public event CancellableItemEventHandler Write;
private void OnWrite(object item, ref bool cancel)
private void OnWrite(IItem item, ref bool cancel)
{
try
{
if (Write != null && item != null)
{
using (IItem wrapped = Mapping.Wrap<IItem>(item, false))
{
if (wrapped != null)
Write(wrapped, ref cancel);
}
Write(item, ref cancel);
}
}
catch (System.Exception e)
@ -199,93 +175,102 @@ namespace Acacia.Utils
#region Implementation
public MailEvents(Application app)
public MailEvents(IAddIn app)
{
app.ItemLoad += OnItemLoad;
app.ItemSend += OnItemSend;
}
void OnItemLoad(object item)
private void OnItemLoad(object item)
{
ItemEvents_10_Event hasEvents = item as ItemEvents_10_Event;
if (hasEvents != null)
IItem wrapped = Wrappers.Wrap<IItem>(item, false);
// TODO: only register for desired types
if (wrapped != null)
{
new MailEventHooker(hasEvents, this);
new MailEventHooker(wrapped, this);
}
}
private class MailEventHooker
private class MailEventHooker : DisposableWrapper
{
private readonly ItemEvents_10_Event item;
private readonly MailEvents events;
private IItem _item;
private readonly MailEvents _events;
public MailEventHooker(ItemEvents_10_Event item, MailEvents events)
public MailEventHooker(IItem item, MailEvents events)
{
this.item = item;
this.events = events;
this._item = item;
this._events = events;
HookEvents(true);
}
protected override void DoRelease()
{
_item.Dispose();
}
private void HookEvents(bool add)
{
ItemEvents_10_Event events = this.item;
using (IItemEvents events = _item.GetEvents())
{
if (add)
{
if (add)
{
events.BeforeDelete += HandleBeforeDelete;
events.Forward += HandleForward;
events.Read += HandleRead;
events.Reply += HandleReply;
events.ReplyAll += HandleReplyAll;
events.Unload += HandleUnload;
events.Write += HandleWrite;
}
else
{
events.BeforeDelete -= HandleBeforeDelete;
events.Forward -= HandleForward;
events.Read -= HandleRead;
events.Reply -= HandleReply;
events.ReplyAll -= HandleReplyAll;
events.Unload -= HandleUnload;
events.Write -= HandleWrite;
events.BeforeDelete += HandleBeforeDelete;
events.Forward += HandleForward;
events.Read += HandleRead;
events.Reply += HandleReply;
events.ReplyAll += HandleReplyAll;
events.Unload += HandleUnload;
events.Write += HandleWrite;
}
else
{
events.BeforeDelete -= HandleBeforeDelete;
events.Forward -= HandleForward;
events.Read -= HandleRead;
events.Reply -= HandleReply;
events.ReplyAll -= HandleReplyAll;
events.Unload -= HandleUnload;
events.Write -= HandleWrite;
}
}
}
private void HandleBeforeDelete(object item, ref bool cancel)
{
events.OnBeforeDelete(item, ref cancel);
_events.OnBeforeDelete(item.WrapOrDefault<IItem>(), ref cancel);
}
private void HandleForward(object response, ref bool cancel)
{
events.OnForward(item as MailItem, response as MailItem);
_events.OnForward(_item as IMailItem, response.WrapOrDefault<IMailItem>());
}
private void HandleRead()
{
events.OnRead(item as MailItem);
_events.OnRead(_item as IMailItem);
}
private void HandleReply(object response, ref bool cancel)
{
events.OnReply(item as MailItem, response as MailItem);
_events.OnReply(_item as IMailItem, response.WrapOrDefault<IMailItem>());
}
private void HandleReplyAll(object response, ref bool cancel)
{
events.OnReplyAll(item as MailItem, response as MailItem);
_events.OnReplyAll(_item as IMailItem, response.WrapOrDefault<IMailItem>());
}
private void HandleUnload()
{
// All events must be unhooked on unload, otherwise a resource leak is created.
HookEvents(false);
Dispose();
}
private void HandleWrite(ref bool cancel)
{
events.OnWrite(item, ref cancel);
_events.OnWrite(_item, ref cancel);
}
}

View File

@ -29,7 +29,7 @@ namespace Acacia.Utils
public static RegistryKey OpenOutlookKey(string suffix = null, RegistryKeyPermissionCheck permissions = RegistryKeyPermissionCheck.Default)
{
// Determine the base path
string[] versionParts = ThisAddIn.Instance.Application.Version.Split('.');
string[] versionParts = ThisAddIn.Instance.Version.Split('.');
string versionString = versionParts[0] + "." + versionParts[1];
string baseKeyPath = string.Format(OutlookConstants.REG_KEY_BASE, versionString);
return RegistryUtil.OpenKeyImpl(baseKeyPath, suffix, false, permissions);

View File

@ -15,9 +15,11 @@
/// Consult LICENSE file for details
using Acacia.Features;
using Acacia.Features.DebugSupport;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace Acacia.Utils
@ -64,10 +66,29 @@ namespace Acacia.Utils
}
public interface TaskExecutor
public abstract class TaskExecutor
{
string Name { get; }
void ExecuteTask(AcaciaTask task);
public abstract string Name { get; }
public void AddTask(AcaciaTask task)
{
Interlocked.Increment(ref Statistics.StartedTasks);
EnqueueTask(task);
}
abstract protected void EnqueueTask(AcaciaTask task);
protected void PerformTask(AcaciaTask task)
{
try
{
task.Execute();
}
finally
{
Interlocked.Increment(ref Statistics.FinishedTasks);
}
}
}
public static class Tasks
@ -103,12 +124,12 @@ namespace Acacia.Utils
public static void Task(Feature owner, string name, Action action)
{
Executor.ExecuteTask(new AcaciaTask(owner, name, action));
Task(new AcaciaTask(owner, name, action));
}
public static void Task(AcaciaTask task)
{
Executor.ExecuteTask(task);
Executor.AddTask(task);
}
}
}

View File

@ -1,4 +1,6 @@
/// Copyright 2016 Kopano b.v.

using Acacia.Features.DebugSupport;
/// Copyright 2016 Kopano b.v.
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License, version 3,
@ -13,7 +15,6 @@
/// along with this program.If not, see<http://www.gnu.org/licenses/>.
///
/// Consult LICENSE file for details
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -40,15 +41,15 @@ namespace Acacia.Utils
while (!_tasks.IsCompleted)
{
AcaciaTask task = _tasks.Take();
task.Execute();
PerformTask(task);
}
}
public void ExecuteTask(AcaciaTask task)
protected override void EnqueueTask(AcaciaTask task)
{
_tasks.Add(task);
}
public string Name { get { return "Background"; } }
override public string Name { get { return "Background"; } }
}
}

View File

@ -73,7 +73,7 @@ namespace Acacia.Utils
{
AcaciaTask task = _tasks.Dequeue();
Logger.Instance.Trace(task.Id, "Beginning task");
task.Execute();
PerformTask(task);
Logger.Instance.Info(task.Id, "Ending task: {0}ms", timer.ElapsedMilliseconds);
// Execute another task if available and we haven't taken too long.
} while (_tasks.Count > 0 && timer.ElapsedMilliseconds < 50);
@ -99,7 +99,7 @@ namespace Acacia.Utils
/// </summary>
/// <param name="name">The name, for debugging and logging.</param>
/// <param name="action">The action to execute</param>
public void ExecuteTask(AcaciaTask task)
override protected void EnqueueTask(AcaciaTask task)
{
if (_init == InitState.Uninitialised)
{
@ -112,6 +112,6 @@ namespace Acacia.Utils
_tasks.Enqueue(task);
}
public string Name { get { return "MainThread"; } }
override public string Name { get { return "MainThread"; } }
}
}

View File

@ -24,11 +24,11 @@ namespace Acacia.Utils
{
public class TasksSynchronous : TaskExecutor
{
public void ExecuteTask(AcaciaTask task)
protected override void EnqueueTask(AcaciaTask task)
{
task.Execute();
PerformTask(task);
}
public string Name { get { return "Synchronous"; } }
override public string Name { get { return "Synchronous"; } }
}
}

View File

@ -15,8 +15,8 @@
/// Consult LICENSE file for details
using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -44,5 +44,123 @@ namespace Acacia.Utils
return a.Equals(b);
}
#region Enumeration helpers
/// <summary>
/// Extension for a Com enumeration. Disposes the enumerated object and - optionally - the returned elements.
/// </summary>
/// <param name="source">The object to be enumerated. This will be released.</param>
/// <param name="releaseElements">If true (the default), elements will also be released.</param>
/// <returns></returns>
public static IEnumerable<T> ComEnum<T>(this IEnumerable<T> source, bool releaseElements = true)
{
foreach (T item in source)
{
try
{
yield return item;
}
finally
{
if (releaseElements)
ComRelease.Release(item);
}
}
ComRelease.Release(source);
}
/// <summary>
/// Extension for a Com enumeration. Disposes the enumerated object and - optionally - the returned elements.
/// </summary>
/// <param name="source">The object to be enumerated. This will be released.</param>
/// <param name="releaseElements">If true (the default), elements will also be released.</param>
/// <returns></returns>
public static IEnumerable ComEnum(this IEnumerable source, bool releaseElements = true)
{
foreach (object item in source)
{
try
{
yield return item;
}
finally
{
if (releaseElements)
ComRelease.Release(item);
}
}
ComRelease.Release(source);
}
/// <summary>
/// Helper for enumeration that disposes the returned items. Note that source will not be disposed,
/// as that is normally done by foreach.
/// </summary>
public static IEnumerable<T> DisposeEnum<T>(this IEnumerable<T> source)
where T : IDisposable
{
foreach (T item in source)
{
try
{
yield return item;
}
finally
{
item.Dispose();
}
}
}
#endregion
public static void GarbageCollect()
{
for (int i = 0; i < 4; ++i)
{
GC.Collect();
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
}
}

View File

@ -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,10 +278,10 @@ 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");
// Parse the body
// Construct the body
WBXMLDocument doc = new WBXMLDocument();
doc.LoadXml(request.Body);
doc.VersionNumber = 1.3;
@ -291,8 +291,11 @@ 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.Account.ServerURL, caps, doc.ToXMLString());
content.Headers.Add(Constants.ZPUSH_HEADER_CLIENT_CAPABILITIES, caps);
using (HttpResponseMessage response = _client.PostAsync(url, content, _cancel).Result)
{
return new Response(response);

View File

@ -46,14 +46,14 @@ namespace Acacia.ZPush.Connect
public ResponseType Execute<ResponseType>(SoapRequest<ResponseType> 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;
}
}

View File

@ -16,7 +16,6 @@
using Acacia.Features;
using Acacia.Stubs;
using Microsoft.Office.Interop.Outlook;
using System;
using System.Collections.Generic;
using System.Linq;

Some files were not shown because too many files have changed in this diff Show More