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

View File

@ -125,7 +125,7 @@
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<DefineConstants>VSTO40</DefineConstants> <DefineConstants>VSTO40</DefineConstants>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<!-- <!--
@ -241,6 +241,7 @@
<Compile Include="Controls\KUITask.cs" /> <Compile Include="Controls\KUITask.cs" />
<Compile Include="Controls\KUIUtil.cs" /> <Compile Include="Controls\KUIUtil.cs" />
<Compile Include="DebugOptions.cs" /> <Compile Include="DebugOptions.cs" />
<Compile Include="Features\SecondaryContacts\FeatureSecondaryContacts.cs" />
<Compile Include="Features\DebugSupport\AboutDialog.cs"> <Compile Include="Features\DebugSupport\AboutDialog.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@ -275,13 +276,44 @@
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" /> <Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
<Compile Include="GlobalOptions.cs" /> <Compile Include="GlobalOptions.cs" />
<Compile Include="Logging.cs" /> <Compile Include="Logging.cs" />
<Compile Include="Native\MAPI.cs" />
<Compile Include="Native\IOleWindow.cs" />
<Compile Include="OutlookConstants.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\OutlookImageList.cs" />
<Compile Include="UI\Outlook\RibbonToggleButton.cs" /> <Compile Include="UI\Outlook\RibbonToggleButton.cs" />
<Compile Include="UI\Outlook\RibbonButton.cs" /> <Compile Include="UI\Outlook\RibbonButton.cs" />
<Compile Include="UI\Outlook\CommandElement.cs" /> <Compile Include="UI\Outlook\CommandElement.cs" />
<Compile Include="UI\Outlook\MenuItem.cs" /> <Compile Include="UI\Outlook\MenuItem.cs" />
<Compile Include="UI\Outlook\Types.cs" /> <Compile Include="UI\Outlook\Types.cs" />
<Compile Include="Utils\DisposableWrapper.cs" />
<Compile Include="Utils\ImageUtils.cs" />
<Compile Include="Utils\RegistryUtil.cs" /> <Compile Include="Utils\RegistryUtil.cs" />
<Compile Include="ZPush\API\SharedFolders\AvailableFolder.cs" /> <Compile Include="ZPush\API\SharedFolders\AvailableFolder.cs" />
<Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" /> <Compile Include="ZPush\API\SharedFolders\SharedFolder.cs" />
@ -306,8 +338,7 @@
<Compile Include="ZPush\Connect\ZPushRequestEncoder.cs" /> <Compile Include="ZPush\Connect\ZPushRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequestEncoder.cs" /> <Compile Include="ZPush\Connect\Soap\SoapRequestEncoder.cs" />
<Compile Include="ZPush\Connect\Soap\SoapRequest.cs" /> <Compile Include="ZPush\Connect\Soap\SoapRequest.cs" />
<Compile Include="Stubs\ItemType.cs" /> <Compile Include="Stubs\OutlookWrappers\ComWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\DisposableWrapper.cs" />
<Compile Include="UI\FeatureSettings.cs"> <Compile Include="UI\FeatureSettings.cs">
<SubType>UserControl</SubType> <SubType>UserControl</SubType>
</Compile> </Compile>
@ -393,7 +424,6 @@
<Compile Include="Stubs\INoteItem.cs" /> <Compile Include="Stubs\INoteItem.cs" />
<Compile Include="Stubs\ISearch.cs" /> <Compile Include="Stubs\ISearch.cs" />
<Compile Include="Stubs\IStorageItem.cs" /> <Compile Include="Stubs\IStorageItem.cs" />
<Compile Include="Stubs\IUserProperty.cs" />
<Compile Include="Stubs\IZPushItem.cs" /> <Compile Include="Stubs\IZPushItem.cs" />
<Compile Include="Stubs\OutlookWrappers\AddressBookWrapper.cs" /> <Compile Include="Stubs\OutlookWrappers\AddressBookWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\DistributionListWrapper.cs" /> <Compile Include="Stubs\OutlookWrappers\DistributionListWrapper.cs" />
@ -408,7 +438,6 @@
<Compile Include="Stubs\OutlookWrappers\SearchWrapper.cs" /> <Compile Include="Stubs\OutlookWrappers\SearchWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StorageItemWrapper.cs" /> <Compile Include="Stubs\OutlookWrappers\StorageItemWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\StoreWrapper.cs" /> <Compile Include="Stubs\OutlookWrappers\StoreWrapper.cs" />
<Compile Include="Stubs\OutlookWrappers\UserPropertyWrapper.cs" />
<Compile Include="Stubs\IStore.cs" /> <Compile Include="Stubs\IStore.cs" />
<Compile Include="UI\ProgressDialog.cs"> <Compile Include="UI\ProgressDialog.cs">
<SubType>Form</SubType> <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_GAB_NAME = "X-Push-GAB-Name";
public const string ZPUSH_HEADER_CAPABILITIES = "X-Push-Capabilities"; 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_PLUGIN = "X-Push-Plugin";
public const string ZPUSH_HEADER_VERSION = "X-Z-Push-Version"; public const string ZPUSH_HEADER_VERSION = "X-Z-Push-Version";

View File

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

View File

@ -47,6 +47,76 @@ namespace Acacia.Features.DebugSupport
Properties.Refresh(); 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 #region Logging
private const string INDENT = "+"; private const string INDENT = "+";
@ -89,11 +159,12 @@ namespace Acacia.Features.DebugSupport
private void buttonGC_Click(object sender, EventArgs e) private void buttonGC_Click(object sender, EventArgs e)
{ {
GC.Collect(); GarbageCollect();
GC.WaitForPendingFinalizers(); }
GC.Collect();
GC.WaitForPendingFinalizers();
private void GarbageCollect()
{
Util.GarbageCollect();
UpdateFields(); UpdateFields();
} }

View File

@ -32,6 +32,7 @@ namespace Acacia.Features.DebugSupport
{ {
Version, Version,
Memory, Memory,
Tasks,
Wrappers, Wrappers,
Misc, Misc,
System, System,
@ -101,9 +102,9 @@ namespace Acacia.Features.DebugSupport
} }
// Add Add-ins // 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); properties.Add(p);
} }
} }
@ -133,6 +134,25 @@ namespace Acacia.Features.DebugSupport
#endregion #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 #region Wrappers
[DebugCategory(DebugCategory.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)] [DebugCategory(DebugCategory.Misc)]
public bool ZPushSync public bool ZPushSync
{ {
@ -227,14 +241,14 @@ namespace Acacia.Features.DebugSupport
#endregion #endregion
#region Outlook #region Outlook
[DebugCategory(DebugCategory.System)] [DebugCategory(DebugCategory.System)]
public string OutlookVersion public string OutlookVersion
{ {
get get
{ {
return ThisAddIn.Instance.Application.Version; return ThisAddIn.Instance.Version;
} }
} }
@ -247,7 +261,7 @@ namespace Acacia.Features.DebugSupport
} }
} }
#endregion #endregion
#region Helpers #region Helpers

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ using Acacia.UI.Outlook;
using Acacia.Utils; using Acacia.Utils;
using Acacia.ZPush; using Acacia.ZPush;
using Acacia.ZPush.Connect; using Acacia.ZPush.Connect;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -48,6 +47,12 @@ namespace Acacia.Features.OutOfOffice
Watcher.ZPushAccountChange += Watcher_ZPushAccountChange; Watcher.ZPushAccountChange += Watcher_ZPushAccountChange;
} }
override public void GetCapabilities(ZPushCapabilities caps)
{
caps.Add("oof");
caps.Add("ooftime");
}
private static bool IsOOFEnabled(ActiveSync.SettingsOOF settings) private static bool IsOOFEnabled(ActiveSync.SettingsOOF settings)
{ {
if (settings == null) if (settings == null)
@ -227,7 +232,7 @@ namespace Acacia.Features.OutOfOffice
if (oof.State != ActiveSync.OOFState.Disabled) if (oof.State != ActiveSync.OOFState.Disabled)
{ {
if (MessageBox.Show( if (MessageBox.Show(
string.Format(Properties.Resources.OOFStartup_Message, account.SmtpAddress), string.Format(Properties.Resources.OOFStartup_Message, account.Account.SmtpAddress),
Properties.Resources.OOFStartup_Title, Properties.Resources.OOFStartup_Title,
MessageBoxButtons.YesNo, MessageBoxButtons.YesNo,
MessageBoxIcon.Question MessageBoxIcon.Question

View File

@ -43,7 +43,7 @@ namespace Acacia.Features.OutOfOffice
InitializeComponent(); InitializeComponent();
// Add the email address to the title // Add the email address to the title
Text = string.Format(Text, account.SmtpAddress); Text = string.Format(Text, account.Account.SmtpAddress);
// Set the time formats // Set the time formats
timeFrom.CustomFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; timeFrom.CustomFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;

View File

@ -22,7 +22,6 @@ using System.Threading.Tasks;
using Acacia.Stubs; using Acacia.Stubs;
using Acacia.Utils; using Acacia.Utils;
using Acacia.ZPush; using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using static Acacia.DebugOptions; using static Acacia.DebugOptions;
namespace Acacia.Features.ReplyFlags namespace Acacia.Features.ReplyFlags
@ -48,18 +47,30 @@ namespace Acacia.Features.ReplyFlags
if (ReadEvent) if (ReadEvent)
{ {
// As a fallback, add an event handler to update the message when displaying it // 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) if (SendEvents)
{ {
// Hook reply and send events to update local state to server // Hook reply and send events to update local state to server
MailEvents.Reply += OnReply; if (MailEvents != null)
MailEvents.ReplyAll += OnReplyAll; {
MailEvents.Forward += OnForwarded; 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 " + [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 " + "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")] "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.Stubs;
using Acacia.Utils; using Acacia.Utils;
using Acacia.ZPush; using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using Acacia.Features.SharedFolders; using Acacia.Features.SharedFolders;
using Acacia.ZPush.API.SharedFolders; using Acacia.ZPush.API.SharedFolders;
using static Acacia.DebugOptions; using static Acacia.DebugOptions;
@ -30,7 +29,7 @@ using static Acacia.DebugOptions;
namespace Acacia.Features.SendAs namespace Acacia.Features.SendAs
{ {
[AcaciaOption("Provides the ability to select different senders for Z-Push accounts.")] [AcaciaOption("Provides the ability to select different senders for Z-Push accounts.")]
public class FeatureSendAs : FeatureDisabled public class FeatureSendAs : Feature
{ {
private FeatureSharedFolders _sharedFolders; private FeatureSharedFolders _sharedFolders;
@ -50,7 +49,10 @@ namespace Acacia.Features.SendAs
public override void Startup() public override void Startup()
{ {
MailEvents.ItemSend += MailEvents_ItemSend; if (MailEvents != null)
{
MailEvents.ItemSend += MailEvents_ItemSend;
}
if (SendAsOwner) if (SendAsOwner)
{ {
@ -58,7 +60,10 @@ namespace Acacia.Features.SendAs
_sharedFolders = ThisAddIn.Instance.GetFeature<FeatureSharedFolders>(); _sharedFolders = ThisAddIn.Instance.GetFeature<FeatureSharedFolders>();
if (_sharedFolders != null) 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) private void MailEvents_Respond(IMailItem mail, IMailItem response)
{ {
Logger.Instance.Trace(this, "Responding to mail, checking"); Logger.Instance.Trace(this, "Responding to mail, checking");
using (IStore store = mail.Store) using (IStore store = mail.GetStore())
{ {
ZPushAccount zpush = Watcher.Accounts.GetAccount(store); ZPushAccount zpush = Watcher.Accounts.GetAccount(store);
Logger.Instance.Trace(this, "Checking ZPush: {0}", zpush); 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); 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 // It's a shared folder, use the owner as the sender if possible
// TODO: make a wrapper for this using (IRecipient recip = ThisAddIn.Instance.ResolveRecipient(shared.Store.UserName))
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())
{ {
Logger.Instance.Trace(this, "Sending as: {0}", recip.AddressEntry.Address); Logger.Instance.Trace(this, "Checking, Shared folder owner recipient: {0}", recip.Name);
response.SetSender(recip.AddressEntry); if (recip != null && recip.IsResolved)
} {
else Logger.Instance.Trace(this, "Sending as: {0}", recip.Address);
{ using (IAddressEntry address = recip.GetAddressEntry())
Logger.Instance.Trace(this, "Unable to resolve sender"); {
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) 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); ZPushAccount zpush = Watcher.Accounts.GetAccount(store);
if (zpush != null) if (zpush != null)
{ {
string address = item.SenderEmailAddress; 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); Logger.Instance.Trace(this, "SendAs: {0}: {1}", address, item.SenderName);
item.SetProperty(Constants.ZPUSH_SEND_AS, address); item.SetProperty(Constants.ZPUSH_SEND_AS, address);

View File

@ -71,7 +71,7 @@ namespace Acacia.Features.SharedFolders
).Images; ).Images;
// Add the email address to the title // Add the email address to the title
Text = string.Format(Text, account.SmtpAddress); Text = string.Format(Text, account.Account.SmtpAddress);
// Set up options // Set up options
ShowOptions(new KTreeNode[0]); ShowOptions(new KTreeNode[0]);
@ -191,7 +191,7 @@ namespace Acacia.Features.SharedFolders
ctx.AddBusy(-count); ctx.AddBusy(-count);
// Sync account // Sync account
_account.SendReceive(); _account.Account.SendReceive();
// Show success // Show success
ShowCompletion(Properties.Resources.SharedFolders_Applying_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 /// 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, /// 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/>. /// along with this program.If not, see<http://www.gnu.org/licenses/>.
/// ///
/// Consult LICENSE file for details /// Consult LICENSE file for details
using Acacia.UI; using Acacia.UI;
using Acacia.UI.Outlook; using Acacia.UI.Outlook;
using Acacia.Utils; 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() private void OpenWebApp()
{ {
ZPushAccount account = Watcher.CurrentZPushAccount(); ZPushAccount account = Watcher.CurrentZPushAccount();
@ -107,15 +100,15 @@ namespace Acacia.Features.WebApp
// Perform a cached auto discover // Perform a cached auto discover
try try
{ {
Logger.Instance.Debug(this, "Starting kdiscover: {0}", account.DomainName); Logger.Instance.Debug(this, "Starting kdiscover: {0}", account.Account.DomainName);
string url = PerformAutoDiscover(account); 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)); account.SetFeatureData(this, TXT_KDISCOVER, new URLCached(url));
return 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); account.SetFeatureData(this, TXT_KDISCOVER, null);
return null; return null;
} }
@ -124,7 +117,7 @@ namespace Acacia.Features.WebApp
private string PerformAutoDiscover(ZPushAccount account) private string PerformAutoDiscover(ZPushAccount account)
{ {
// Fetch the txt record // Fetch the txt record
IList<string> txt = DnsUtil.GetTxtRecord(account.DomainName); IList<string> txt = DnsUtil.GetTxtRecord(account.Account.DomainName);
if (txt == null) if (txt == null)
return null; return null;

View File

@ -51,7 +51,7 @@ namespace Acacia
get { return GetOption(null, THREADING); } get { return GetOption(null, THREADING); }
set { SetOption(null, THREADING, value); } 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 " + [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 " + "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 #region UI Options
[AcaciaOption("Completely enables or disables modifications to the Outlook UI." + [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_SUBJECT = PROP + "0037" + PT_UNICODE;
public const string PR_CONTAINER_CLASS = PROP + "3613" + PT_UNICODE;
#endregion #endregion
#region Email specific #region Email specific
@ -120,6 +122,7 @@ namespace Acacia
public const string PR_EAS_SYNCTYPE = PROP + "6A1A" + PT_LONG; public const string PR_EAS_SYNCTYPE = PROP + "6A1A" + PT_LONG;
public const string PR_EAS_SYNC2 = PROP + "6A1D" + PT_BOOLEAN; 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_NET_FOLDER_FLAGS = PROP + "36DE" + PT_LONG;
public const string PR_EAS_NAME = PROP + "6915" + PT_UNICODE;
public enum SyncType 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> /// <summary>
/// Looks up a localized string similar to Support. /// Looks up a localized string similar to Support.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Unable to open the shared folder. Please ensure you have permission to open the shared folder.. /// Looks up a localized string similar to Unable to open the shared folder. Please ensure you have permission to open the shared folder..
/// </summary> /// </summary>

View File

@ -431,4 +431,18 @@
<data name="Ribbon_About_Supertip" xml:space="preserve"> <data name="Ribbon_About_Supertip" xml:space="preserve">
<value>Shows the about dialog, which contains licensing and version information.</value> <value>Shows the about dialog, which contains licensing and version information.</value>
</data> </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> </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> /// </summary>
void Clear(); 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 /// 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, /// it under the terms of the GNU Affero General Public License, version 3,
@ -22,17 +22,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs 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 namespace Acacia.Stubs
{ {
public interface IBase : IDisposable public interface IBase : IComWrapper
{ {
#region MAPI properties #region Properties
bool AttrHidden { get; set; } bool AttrHidden { get; set; }
@ -34,23 +34,35 @@ namespace Acacia.Stubs
#endregion #endregion
string EntryId { get; } #region Ids and hierarchy
IFolder Parent { get; }
string ParentEntryId { get; } string EntryID { get; }
IFolder Parent { get; }
string ParentEntryID { get; }
IStore Store { get; }
/// <summary> /// <summary>
/// Quick accessor to Store.Id, to prevent allocation a wrapper for it. /// Returns the store. The owner is responsible for disposing.
/// </summary> /// </summary>
string StoreId { get; } IStore GetStore();
/// <summary> /// <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> /// </summary>
string StoreDisplayName { get; } string StoreDisplayName { get; }
#endregion
#region Methods
void Delete(); void Delete();
bool MustRelease { get; set; }
string ToString(); 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 DLName { get; set; }
string SMTPAddress { 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); 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; } bool ShowAsOutlookAB { get; set; }
IEnumerable<IItem> Items { get; } IItems Items { get; }
IEnumerable<IItem> ItemsSorted(string field, bool descending);
IItem GetItemById(string id); IItem GetItemById(string id);
string FullFolderPath { get; }
ItemType DefaultItemType { get; }
#endregion #endregion
#region Searching #region Searching
@ -55,6 +57,11 @@ namespace Acacia.Stubs
IEnumerable<FolderType> GetSubFolders<FolderType>() IEnumerable<FolderType> GetSubFolders<FolderType>()
where FolderType : IFolder; where FolderType : IFolder;
IFolders SubFolders
{
get;
}
FolderType GetSubFolder<FolderType>(string name) FolderType GetSubFolder<FolderType>(string name)
where FolderType : IFolder; where FolderType : IFolder;
@ -101,5 +108,11 @@ namespace Acacia.Stubs
/// function prevents creating lots of wrappers. /// function prevents creating lots of wrappers.
/// </summary> /// </summary>
bool IsAtDepth(int depth); 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 Body { get; set; }
string Subject { get; set; } string Subject { get; set; }
/// <summary>
/// Returns the events for the item. The caller is responsible for disposing.
/// </summary>
IItemEvents GetEvents();
#endregion #endregion
#region User properties #region User properties
/// <summary> /// <summary>
/// Retrieves the user property with the specified name. /// Retrieves the item's user property with the specified name.
/// </summary> /// </summary>
/// <param name="create">If true, the property is created if it does not exist. /// <typeparam name="Type">The property type.</typeparam>
/// If false, null is returned in this case</param> /// <param name="name">The name of the property.</param>
IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false); /// <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 #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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs namespace Acacia.Stubs
{ {
@ -34,6 +33,10 @@ namespace Acacia.Stubs
string SenderEmailAddress { get; } string SenderEmailAddress { get; }
string SenderName { 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 /// 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, /// it under the terms of the GNU Affero General Public License, version 3,
@ -22,17 +22,7 @@ using System.Threading.Tasks;
namespace Acacia.Stubs 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 namespace Acacia.Stubs
{ {
public enum SearchOperation /// <summary>
/// Order matches MAPI RELOP_ constants
/// </summary>
public enum SearchOperation : uint
{ {
Smaller,
SmallerEqual,
Greater,
GreaterEqual,
Equal, Equal,
NotEqual, NotEqual,
SmallerEqual,
Smaller,
GreaterEqual,
Greater,
Like Like
} }
@ -41,7 +44,8 @@ namespace Acacia.Stubs
public enum SearchOperator public enum SearchOperator
{ {
Or, Or,
And And,
Not
} }
public interface ISearchOperator public interface ISearchOperator
@ -49,8 +53,7 @@ namespace Acacia.Stubs
ISearchField AddField(string name, bool isUserField = false); ISearchField AddField(string name, bool isUserField = false);
} }
public interface ISearch<ItemType> public interface ISearch<ItemType> : ISearchOperator, IDisposable
: ISearchOperator
where ItemType : IItem where ItemType : IItem
{ {
ISearchOperator AddOperator(SearchOperator oper); ISearchOperator AddOperator(SearchOperator oper);

View File

@ -24,9 +24,29 @@ namespace Acacia.Stubs
{ {
public interface IStore : IDisposable public interface IStore : IDisposable
{ {
/// <summary>
/// Returns the root folder.
/// </summary>
/// <returns>The root folder. The caller is responsible for disposing.</returns>
IFolder GetRootFolder(); 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); IItem GetItemFromID(string id);
string DisplayName { get; } string DisplayName { get; }
string StoreID { 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 /// Consult LICENSE file for details
using Acacia.Utils; using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers namespace Acacia.Stubs.OutlookWrappers
{ {
class AddressBookWrapper : FolderWrapper, IAddressBook class AddressBookWrapper : FolderWrapper, IAddressBook
{ {
public AddressBookWrapper(Folder folder) public AddressBookWrapper(NSOutlook.MAPIFolder folder)
: :
base(folder) base(folder)
{ {
} }
public override IFolder Clone()
{
return new AddressBookWrapper(CloneComObject());
}
IAddressBook IAddressBook.Clone()
{
return new AddressBookWrapper(CloneComObject());
}
public void Clear() public void Clear()
{ {
foreach(dynamic item in _item.Items) foreach(dynamic item in _item.Items.ComEnum())
{ {
item.Delete(); 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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils; using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) base(item)
{ {
} }
public override string ToString() { return "Appointment: " + Subject; }
protected override PropertyAccessor GetPropertyAccessor() #region IAppointmentItem implementation
{
return _item.PropertyAccessor;
}
#region Properties
public DateTime Start public DateTime Start
{ {
@ -58,6 +52,29 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Location = value; } 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 public string Body
{ {
get { return _item.Body; } get { return _item.Body; }
@ -70,45 +87,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; } 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(); } public void Save() { _item.Save(); }
#endregion #endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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 /// Consult LICENSE file for details
using Acacia.Utils; using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) base(item)
{ {
} }
protected override PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString() { return "Contact: " + Subject; }
#region IContactItem implementation #region IContactItem implementation
public string CustomerID public string CustomerID
@ -179,6 +172,30 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Language = value; } 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 #endregion
#region IItem implementation #region IItem implementation
@ -195,49 +212,71 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; } 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 Save() { _item.Save(); }
public void SetPicture(string path)
{
_item.AddPicture(path);
}
#endregion #endregion
#region IBase implementation #region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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 #endregion

View File

@ -19,95 +19,64 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils; using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) base(item)
{ {
} }
protected override PropertyAccessor GetPropertyAccessor() #region IDistributionList implementation
{
return _item.PropertyAccessor;
}
#region Properties
public string SMTPAddress public string SMTPAddress
{ {
get get
{ {
PropertyAccessor props = _item.PropertyAccessor; return (string)GetProperty(OutlookConstants.PR_EMAIL1EMAILADDRESS);
try
{
return (string)props.GetProperty(OutlookConstants.PR_EMAIL1EMAILADDRESS);
}
finally
{
ComRelease.Release(props);
}
} }
set set
{ {
string displayName = DLName + " (" + value + ")"; string displayName = DLName + " (" + value + ")";
byte[] oneOffId = CreateOneOffMemberId(DLName, "SMTP", value); byte[] oneOffId = CreateOneOffMemberId(DLName, "SMTP", value);
PropertyAccessor props = _item.PropertyAccessor;
try SetProperties(
{ new string[]
props.SetProperties( {
new string[] OutlookConstants.PR_EMAIL1DISPLAYNAME,
{ OutlookConstants.PR_EMAIL1EMAILADDRESS,
OutlookConstants.PR_EMAIL1DISPLAYNAME, OutlookConstants.PR_EMAIL1ADDRESSTYPE,
OutlookConstants.PR_EMAIL1EMAILADDRESS, OutlookConstants.PR_EMAIL1ORIGINALDISPLAYNAME,
OutlookConstants.PR_EMAIL1ADDRESSTYPE, OutlookConstants.PR_EMAIL1ORIGINALENTRYID
OutlookConstants.PR_EMAIL1ORIGINALDISPLAYNAME, },
OutlookConstants.PR_EMAIL1ORIGINALENTRYID new object[]
}, {
new object[] DLName,
{ value,
DLName, "SMTP",
value, value,
"SMTP", oneOffId
value, }
oneOffId );
}
);
}
finally
{
ComRelease.Release(props);
}
} }
} }
#endregion public string DLName
#region Methods
public IUserProperty<Type> GetUserProperty<Type>(string name, bool create = false)
{ {
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) public void AddMember(IItem item)
{ {
if (item is IContactItem) if (item is IContactItem)
{ {
string email = ((IContactItem)item).Email1Address; AddContactMember((IContactItem)item);
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);
} }
else if (item is IDistributionList) else if (item is IDistributionList)
{ {
@ -115,8 +84,22 @@ namespace Acacia.Stubs.OutlookWrappers
} }
else 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) 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 // 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 // groups (e.g. 'Germany' and 'Sales Germany' fails to find Germany). Patch the member
// tables explicitly. // tables explicitly.
PropertyAccessor props = _item.PropertyAccessor; object[] members = (object[])GetProperty(OutlookConstants.PR_DISTLIST_MEMBERS);
object[] members = props.GetProperty(OutlookConstants.PR_DISTLIST_MEMBERS); object[] oneOffMembers = (object[])GetProperty(OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS);
object[] oneOffMembers = props.GetProperty(OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS);
// Create the new member ids // Create the new member ids
byte[] memberId = CreateMemberId(member); byte[] memberId = CreateMemberId(member);
@ -163,7 +145,7 @@ namespace Acacia.Stubs.OutlookWrappers
newOneOffMembers[existingIndex] = oneOffMemberId; newOneOffMembers[existingIndex] = oneOffMemberId;
// Write back // Write back
props.SetProperties( SetProperties(
new string[] { OutlookConstants.PR_DISTLIST_MEMBERS, OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS }, new string[] { OutlookConstants.PR_DISTLIST_MEMBERS, OutlookConstants.PR_DISTLIST_ONEOFFMEMBERS },
new object[] { newMembers, newOneOffMembers } new object[] { newMembers, newOneOffMembers }
); );
@ -178,7 +160,7 @@ namespace Acacia.Stubs.OutlookWrappers
{ {
List<byte> id = new List<byte>(); List<byte> id = new List<byte>();
id.AddRange(PREFIX_MEMBER_ID); id.AddRange(PREFIX_MEMBER_ID);
id.AddRange(StringUtil.HexToBytes(member.EntryId)); id.AddRange(StringUtil.HexToBytes(member.EntryID));
return id.ToArray(); return id.ToArray();
} }
@ -213,14 +195,27 @@ namespace Acacia.Stubs.OutlookWrappers
#endregion #endregion
public override string ToString() { return "DistributionList: " + DLName; } #region Wrapper methods
public string DLName protected override NSOutlook.UserProperties GetUserProperties()
{ {
get { return _item.DLName; } return _item.UserProperties;
set { _item.DLName = value; }
} }
protected override NSOutlook.PropertyAccessor GetPropertyAccessor()
{
return _item.PropertyAccessor;
}
public override string ToString()
{
return "DistributionList: " + DLName;
}
#endregion
#region IItem implementation
public string Body public string Body
{ {
get { return _item.Body; } get { return _item.Body; }
@ -233,40 +228,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; } set { _item.Subject = value; }
} }
public IFolder Parent { get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); } } public void Save() { _item.Save(); }
public string ParentEntryId
#endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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 get
{ {
// TODO: release needed using (ComRelease com = new ComRelease())
return _item.Parent?.Store?.StoreID; {
NSOutlook.Folder parent = com.Add(_item.Parent);
NSOutlook.Store store = com.Add(parent?.Store);
return store.StoreID;
}
} }
} }
public string StoreDisplayName public string StoreDisplayName
{ {
get get
{ {
// TODO: release needed using (ComRelease com = new ComRelease())
return _item.Parent?.Store?.DisplayName; {
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 /// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -23,39 +22,66 @@ using System.Threading.Tasks;
using System.Collections; using System.Collections;
using Acacia.Utils; using Acacia.Utils;
using Acacia.ZPush; using Acacia.ZPush;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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; return _item.PropertyAccessor;
} }
public string FullFolderPath { get { return _item.FullFolderPath; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; return parent?.EntryID;
} }
finally
{
ComRelease.Release(parent);
}
} }
} }
@ -69,17 +95,20 @@ namespace Acacia.Stubs.OutlookWrappers
using (ComRelease com = new ComRelease()) using (ComRelease com = new ComRelease())
{ {
// The parent of the root item is a session, not null. Hence the explicit type checks. // 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) for (int i = 0; i < depth; ++i)
{ {
object parent = current.Parent; object parent = com.Add(current.Parent);
com.Add(parent);
if (!(parent is Folder)) current = parent as NSOutlook.Folder;
if (current == null)
return false; 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 IStore GetStore() { return Mapping.Wrap(_item.Store); }
public string StoreId
public string StoreID
{ {
get get
{ {
using (IStore store = Store) using (IStore store = GetStore())
{ {
return store.StoreID; return store.StoreID;
} }
@ -109,7 +139,7 @@ namespace Acacia.Stubs.OutlookWrappers
{ {
get get
{ {
using (IStore store = Store) using (IStore store = GetStore())
{ {
return store.DisplayName; return store.DisplayName;
} }
@ -118,119 +148,20 @@ namespace Acacia.Stubs.OutlookWrappers
public ItemType ItemType { get { return (ItemType)(int)_item.DefaultItemType; } } 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) public IItems Items
{
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
{ {
get 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) public IItem GetItemById(string entryId)
{ {
try try
{ {
using (IStore store = Store) using (IStore store = GetStore())
{ {
return store.GetItemFromID(entryId); return store.GetItemFromID(entryId);
} }
@ -270,77 +201,76 @@ namespace Acacia.Stubs.OutlookWrappers
return new SearchWrapper<ItemType>(_item.Items); return new SearchWrapper<ItemType>(_item.Items);
} }
#region Subfolders
public IEnumerable<FolderType> GetSubFolders<FolderType>() public IEnumerable<FolderType> GetSubFolders<FolderType>()
where FolderType : IFolder 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) public FolderType GetSubFolder<FolderType>(string name)
where FolderType : IFolder where FolderType : IFolder
{ {
// Fetching the folder by name throws an exception if not found, loop and find // Fetching the folder by name throws an exception if not found, loop and find
// to prevent exceptions in the log // to prevent exceptions in the log.
MAPIFolder sub = null; // Don't release the items in RawEnum, they are release manually or handed to WrapFolders.
foreach(MAPIFolder folder in _item.Folders) NSOutlook.Folder sub = null;
foreach(NSOutlook.Folder folder in _item.Folders.ComEnum(false))
{ {
if (folder.Name == name) if (folder.Name == name)
{ {
sub = folder; sub = folder;
break; break; // TODO: does this prevent the rest of the objects from getting released?
}
else
{
ComRelease.Release(folder);
} }
} }
if (sub == null) if (sub == null)
return default(FolderType); return default(FolderType);
return WrapFolder<FolderType>(sub); return sub.Wrap<FolderType>();
} }
public FolderType CreateFolder<FolderType>(string name) public FolderType CreateFolder<FolderType>(string name)
where FolderType : IFolder where FolderType : IFolder
{ {
Folders folders = _item.Folders; using (ComRelease com = new ComRelease())
try
{ {
NSOutlook.Folders folders = com.Add(_item.Folders);
if (typeof(FolderType) == typeof(IFolder)) if (typeof(FolderType) == typeof(IFolder))
{ {
return WrapFolder<FolderType>(folders.Add(name)); return folders.Add(name).Wrap<FolderType>();
} }
else if (typeof(FolderType) == typeof(IAddressBook)) 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; newFolder.ShowAsOutlookAB = true;
return WrapFolder<FolderType>(newFolder); return newFolder.Wrap<FolderType>();
} }
else else
throw new NotSupportedException(); throw new NotSupportedException();
} }
finally
{
ComRelease.Release(folders);
}
} }
private FolderType WrapFolder<FolderType>(MAPIFolder folder) #endregion
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();
}
public IStorageItem GetStorageItem(string name) public IStorageItem GetStorageItem(string name)
{ {
StorageItem item = _item.GetStorage(name, OlStorageIdentifierType.olIdentifyBySubject); NSOutlook.StorageItem item = _item.GetStorage(name, NSOutlook.OlStorageIdentifierType.olIdentifyBySubject);
if (item == null) if (item == null)
return null; return null;
return new StorageItemWrapper(item); return new StorageItemWrapper(item);
@ -355,16 +285,12 @@ namespace Acacia.Stubs.OutlookWrappers
public ItemType Create<ItemType>() public ItemType Create<ItemType>()
where ItemType : IItem where ItemType : IItem
{ {
Items items = _item.Items; using (ComRelease com = new ComRelease())
try
{ {
NSOutlook.Items items = com.Add(_item.Items);
object item = items.Add(Mapping.OutlookItemType<ItemType>()); object item = items.Add(Mapping.OutlookItemType<ItemType>());
return Mapping.Wrap<ItemType>(item); return Mapping.Wrap<ItemType>(item);
} }
finally
{
ComRelease.Release(items);
}
} }
#endregion #endregion
@ -411,14 +337,14 @@ namespace Acacia.Stubs.OutlookWrappers
_item.BeforeItemMove -= HandleBeforeItemMove; _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 try
{ {
if (_beforeItemMove != null) if (_beforeItemMove != null)
{ {
using (IItem itemWrapped = Mapping.Wrap<IItem>(item)) using (IItem itemWrapped = Mapping.Wrap<IItem>(item, false))
using (IFolder targetWrapped = Mapping.Wrap<IFolder>(target)) using (IFolder targetWrapped = Mapping.Wrap<IFolder>(target, false))
{ {
if (itemWrapped != null && targetWrapped != null) if (itemWrapped != null && targetWrapped != null)
{ {
@ -435,5 +361,15 @@ namespace Acacia.Stubs.OutlookWrappers
#endregion #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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils; using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) 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; 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 public string Body
{ {
@ -53,37 +121,53 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; } 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 get
{ {
Folder parent = (Folder)_item.Parent; // The wrapper manages the returned folder
try return Mapping.Wrap<IFolder>(_item.Parent as NSOutlook.Folder);
}
}
public string ParentEntryID
{
get
{
using (ComRelease com = new ComRelease())
{ {
return StoreWrapper.Wrap(parent?.Store); NSOutlook.Folder parent = com.Add(_item.Parent);
} return parent?.EntryID;
finally
{
ComRelease.Release(parent);
} }
} }
} }
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 get
{ {
Folder parent = (Folder)_item.Parent; using (ComRelease com = new ComRelease())
Store store = null;
try
{ {
store = parent?.Store; NSOutlook.Folder parent = com.Add(_item.Parent);
return store?.StoreID; NSOutlook.Store store = com.Add(parent?.Store);
} return store.StoreID;
finally
{
ComRelease.Release(parent);
ComRelease.Release(store);
} }
} }
} }
@ -92,76 +176,17 @@ namespace Acacia.Stubs.OutlookWrappers
{ {
get get
{ {
Folder parent = (Folder)_item.Parent; using (ComRelease com = new ComRelease())
Store store = null;
try
{ {
store = parent?.Store; NSOutlook.Folder parent = com.Add(_item.Parent);
return store?.DisplayName; NSOutlook.Store store = com.Add(parent?.Store);
} return store.StoreID;
finally
{
ComRelease.Release(parent);
ComRelease.Release(store);
} }
} }
} }
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 Delete() { _item.Delete(); }
public void Save() { _item.Save(); }
#endregion #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 /// Consult LICENSE file for details
using Acacia.Utils; using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers namespace Acacia.Stubs.OutlookWrappers
{ {
// TODO: a clean up is needed, move as much as possible to Wrappers.cs
public static class Mapping public static class Mapping
{ {
@ -32,40 +33,42 @@ namespace Acacia.Stubs.OutlookWrappers
/// </summary> /// </summary>
/// <param name="o">The Outlook object.</param> /// <param name="o">The Outlook object.</param>
/// <returns>The IItem wrapper, or null if the object could not be wrapped</returns> /// <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) if (o == null)
return null; return null;
IBase wrapper = CreateWrapper(o); IBase wrapper = CreateWrapper(o, mustRelease);
if (wrapper != null) if (wrapper != null)
wrapper.MustRelease = mustRelease; wrapper.MustRelease = mustRelease;
ComRelease.LogWrapper(o, wrapper); ComRelease.LogWrapper(o, wrapper);
return wrapper; return wrapper;
} }
private static IBase CreateWrapper(object o) private static IBase CreateWrapper(object o, bool mustRelease)
{ {
// TODO: switch on o.Class // TODO: switch on o.Class
if (o is MailItem) if (o is NSOutlook.MailItem)
return new MailItemWrapper((MailItem)o); return new MailItemWrapper((NSOutlook.MailItem)o);
if (o is AppointmentItem) if (o is NSOutlook.AppointmentItem)
return new AppointmentItemWrapper((AppointmentItem)o); return new AppointmentItemWrapper((NSOutlook.AppointmentItem)o);
if (o is Folder) if (o is NSOutlook.Folder)
return new FolderWrapper((Folder)o); return new FolderWrapper((NSOutlook.Folder)o);
if (o is ContactItem) if (o is NSOutlook.ContactItem)
return new ContactItemWrapper((ContactItem)o); return new ContactItemWrapper((NSOutlook.ContactItem)o);
if (o is DistListItem) if (o is NSOutlook.DistListItem)
return new DistributionListWrapper((DistListItem)o); return new DistributionListWrapper((NSOutlook.DistListItem)o);
if (o is NoteItem) if (o is NSOutlook.NoteItem)
return new NoteItemWrapper((NoteItem)o); return new NoteItemWrapper((NSOutlook.NoteItem)o);
if (o is TaskItem) if (o is NSOutlook.TaskItem)
return new TaskItemWrapper((TaskItem)o); return new TaskItemWrapper((NSOutlook.TaskItem)o);
// TODO: support this?
if (o is ReportItem)
return null;
// 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; return null;
} }
@ -75,59 +78,64 @@ namespace Acacia.Stubs.OutlookWrappers
return (Type)Wrap(o, mustRelease); 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) public static Type WrapOrDefault<Type>(object o, bool mustRelease = true)
where Type : IBase where Type : IBase
{ {
IBase wrapped = Wrap(o, mustRelease); IBase wrapped = Wrap(o, mustRelease);
if (wrapped is Type) if (wrapped is Type)
return (Type)wrapped; return (Type)wrapped;
// Release if required
if (wrapped != null) if (wrapped != null)
wrapped.Dispose(); wrapped.Dispose();
return default(Type); return default(Type);
} }
public static OlItemType OutlookItemType<ItemType>() public static NSOutlook.OlItemType OutlookItemType<ItemType>()
where ItemType: IItem where ItemType: IItem
{ {
Type type = typeof(ItemType); Type type = typeof(ItemType);
if (type == typeof(IContactItem)) if (type == typeof(IContactItem))
return OlItemType.olContactItem; return NSOutlook.OlItemType.olContactItem;
if (type == typeof(IDistributionList)) if (type == typeof(IDistributionList))
return OlItemType.olDistributionListItem; return NSOutlook.OlItemType.olDistributionListItem;
throw new NotImplementedException(); // TODO throw new NotImplementedException(); // TODO
} }
public static OlUserPropertyType OutlookPropertyType<PropType>() public static NSOutlook.OlUserPropertyType OutlookPropertyType<PropType>()
{ {
Type type = typeof(PropType); Type type = typeof(PropType);
if (type == typeof(string)) if (type == typeof(string))
return OlUserPropertyType.olText; return NSOutlook.OlUserPropertyType.olText;
if (type == typeof(DateTime)) if (type == typeof(DateTime))
return OlUserPropertyType.olDateTime; return NSOutlook.OlUserPropertyType.olDateTime;
if (type == typeof(int)) if (type == typeof(int))
return OlUserPropertyType.olInteger; return NSOutlook.OlUserPropertyType.olInteger;
if (type.IsEnum) if (type.IsEnum)
return OlUserPropertyType.olInteger; return NSOutlook.OlUserPropertyType.olInteger;
if (type == typeof(string[])) if (type == typeof(string[]))
return OlUserPropertyType.olKeywords; return NSOutlook.OlUserPropertyType.olKeywords;
throw new NotImplementedException(); // TODO 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 /// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Acacia.Utils; using Acacia.Utils;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) 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; 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 public string Body
{ {
@ -50,48 +62,78 @@ namespace Acacia.Stubs.OutlookWrappers
public string Subject public string Subject
{ {
get { return _item.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(); } public void Save() { _item.Save(); }
#endregion #endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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 /// Consult LICENSE file for details
using Acacia.Features.DebugSupport; using Acacia.Features.DebugSupport;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -23,56 +22,45 @@ using Acacia.Utils;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers namespace Acacia.Stubs.OutlookWrappers
{ {
/// <summary> /// <summary>
/// Helper for Outlook wrapper implementations /// Helper for Outlook wrapper implementations
/// </summary> /// </summary>
abstract public class OutlookWrapper<ItemType> : DisposableWrapper abstract class OutlookWrapper<ItemType> : ComWrapper<ItemType>
{ {
#region Construction / Destruction #region Construction / Destruction
protected ItemType _item;
/// <summary> /// <summary>
/// Creates a wrapper. /// Creates a wrapper.
/// </summary> /// </summary>
internal OutlookWrapper(ItemType item) internal OutlookWrapper(ItemType item) : base(item)
{
this._item = item;
}
~OutlookWrapper()
{ {
} }
protected override void DoRelease() protected override void DoRelease()
{ {
// Always release props, as we allocated that
if (_props != null) if (_props != null)
{ {
ComRelease.Release(_props); ComRelease.Release(_props);
_props = null; _props = null;
} }
if (MustRelease) base.DoRelease();
{
if (_item != null)
{
ComRelease.Release(_item);
_item = default(ItemType);
}
}
} }
#endregion #endregion
#region Properties implementation #region Properties implementation
private PropertyAccessor _props; // Assigned in Props, released in DoRelease
private NSOutlook.PropertyAccessor _props;
private PropertyAccessor Props private NSOutlook.PropertyAccessor Props
{ {
get get
{ {
@ -88,7 +76,7 @@ namespace Acacia.Stubs.OutlookWrappers
/// Returns the wrapped item's property accessor. /// Returns the wrapped item's property accessor.
/// </summary> /// </summary>
/// <returns>The property accessor. The caller is responsible for disposing this.</returns> /// <returns>The property accessor. The caller is responsible for disposing this.</returns>
abstract protected PropertyAccessor GetPropertyAccessor(); abstract protected NSOutlook.PropertyAccessor GetPropertyAccessor();
#endregion #endregion
@ -112,7 +100,14 @@ namespace Acacia.Stubs.OutlookWrappers
{ {
get get
{ {
return Props.GetProperty(OutlookConstants.PR_ATTR_HIDDEN); try
{
return Props.GetProperty(OutlookConstants.PR_ATTR_HIDDEN);
}
catch(System.Exception)
{
return false;
}
} }
set 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) public object GetProperty(string property)
{ {
try try
@ -153,7 +124,7 @@ namespace Acacia.Stubs.OutlookWrappers
return null; return null;
return val; return val;
} }
catch(System.Exception) { return null; } // TODO: is this fine everywhere? catch(System.Exception) { return null; }
} }
public void SetProperty(string property, object value) 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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils; using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers namespace Acacia.Stubs.OutlookWrappers
{ {
class SearchWrapper<ItemType> : ISearch<ItemType> class SearchWrapper<ItemType> : ComWrapper<NSOutlook.Items>, ISearch<ItemType>
where ItemType : IItem where ItemType : IItem
{ {
private interface SearchTerm private interface SearchTerm
@ -151,11 +151,13 @@ namespace Acacia.Stubs.OutlookWrappers
} }
private readonly List<SearchTerm> terms = new List<SearchTerm>(); 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) public ISearchOperator AddOperator(SearchOperator oper)
@ -174,31 +176,42 @@ namespace Acacia.Stubs.OutlookWrappers
public IEnumerable<ItemType> Search(int maxResults) public IEnumerable<ItemType> Search(int maxResults)
{ {
List<ItemType> values = new List<ItemType>();
string filter = MakeFilter(); string filter = MakeFilter();
object value = _items.Find(filter);
int count = 0;
object value = _item.Find(filter);
while(value != null) 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); ItemType wrapped = Mapping.WrapOrDefault<ItemType>(value);
if (wrapped != null) if (wrapped != null)
{ {
values.Add(wrapped); try
{
yield return wrapped;
}
finally
{
wrapped.Dispose();
}
} }
} }
else else
{ {
// Release if not returned. Keep looping to release any others
ComRelease.Release(value); ComRelease.Release(value);
} }
value = _items.FindNext(); value = _item.FindNext();
++count;
} }
return values;
} }
public ItemType SearchOne() public ItemType SearchOne()
{ {
object value = _items.Find(MakeFilter()); // Wrap manages com object in value
object value = _item.Find(MakeFilter());
if (value == null) if (value == null)
return default(ItemType); return default(ItemType);
return Mapping.Wrap<ItemType>(value); return Mapping.Wrap<ItemType>(value);

View File

@ -15,31 +15,44 @@
/// Consult LICENSE file for details /// Consult LICENSE file for details
using Acacia.Utils; using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) 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; return _item.PropertyAccessor;
} }
public override string ToString() { return "StorageItem"; } public override string ToString()
{
return "StorageItem";
}
#region Properties #endregion
#region IItem implementation
public string Body public string Body
{ {
@ -53,45 +66,72 @@ namespace Acacia.Stubs.OutlookWrappers
set { _item.Subject = value; } 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(); } public void Save() { _item.Save(); }
#endregion #endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Office.Interop.Outlook;
using Acacia.Utils; using Acacia.Utils;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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() 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) public IItem GetItemFromID(string id)
{ {
NameSpace nmspace = _store.Session; using (ComRelease com = new ComRelease())
try {
{ NSOutlook.NameSpace nmspace = com.Add(_item.Session);
// Get the item; the wrapper manages it
object o = nmspace.GetItemFromID(id); object o = nmspace.GetItemFromID(id);
return Mapping.Wrap<IItem>(o); return Mapping.Wrap<IItem>(o);
} }
finally
{
ComRelease.Release(nmspace);
}
} }
public string DisplayName { get { return _store.DisplayName; } } public string DisplayName { get { return _item.DisplayName; } }
public string StoreID { get { return _store.StoreID; } } 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 /// Consult LICENSE file for details
using Acacia.Utils; using Acacia.Utils;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.Stubs.OutlookWrappers 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) 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; 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 public string Body
{ {
@ -50,48 +63,75 @@ namespace Acacia.Stubs.OutlookWrappers
public string Subject public string Subject
{ {
get { return _item.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(); } public void Save() { _item.Save(); }
#endregion #endregion
#region IBase implementation
public string EntryID { get { return _item.EntryID; } }
public IFolder Parent public IFolder Parent
{
get { return (IFolder)Mapping.Wrap(_item.Parent as Folder); }
}
public string ParentEntryId
{ {
get get
{ {
Folder parent = _item.Parent; // The wrapper manages the returned folder
try 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; 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.Linq;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using Acacia.Features; using Acacia.Features;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
@ -29,27 +27,23 @@ using Acacia.UI;
using Acacia.ZPush; using Acacia.ZPush;
using System.Globalization; using System.Globalization;
using Acacia.UI.Outlook; 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 namespace Acacia
{ {
public partial class ThisAddIn public partial class ThisAddIn
{ {
public static IAddIn Instance
public static ThisAddIn Instance
{ {
get; get;
private set; 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 #region Features
/// <summary> /// <summary>
@ -69,15 +63,20 @@ namespace Acacia
private set; private set;
} }
public FeatureType GetFeature<FeatureType>() private MailEvents _mailEvents;
where FeatureType : Feature public MailEvents MailEvents
{ {
foreach(Feature feature in Features) get
{ {
if (feature is FeatureType) if (_mailEvents == null)
return (FeatureType)feature; {
if (GlobalOptions.INSTANCE.HookItemEvents)
{
_mailEvents = new MailEvents(Instance);
}
}
return _mailEvents;
} }
return default(FeatureType);
} }
#region Startup / Shutdown #region Startup / Shutdown
@ -100,16 +99,12 @@ namespace Acacia
return; return;
} }
Instance = this; Instance = new AddInWrapper(this);
// Set the culture info from Outlook's language setting rather than the OS setting // 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); 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. // 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 // It's null in older versions of .Net, this fixes that
if (SynchronizationContext.Current == null) if (SynchronizationContext.Current == null)
@ -118,7 +113,7 @@ namespace Acacia
} }
// Create the watcher // Create the watcher
Watcher = new ZPushWatcher(Application); Watcher = new ZPushWatcher(Instance);
OutlookUI.Watcher = Watcher; OutlookUI.Watcher = Watcher;
// Allow to features to register whatever they need // Allow to features to register whatever they need
@ -151,11 +146,17 @@ namespace Acacia
// Start watching events // Start watching events
if (DebugOptions.GetOption(null, DebugOptions.WATCHER_ENABLED)) if (DebugOptions.GetOption(null, DebugOptions.WATCHER_ENABLED))
{
((AddInWrapper)Instance).Start();
Watcher.Start(); Watcher.Start();
}
// Done // Done
Logger.Instance.Debug(this, "Startup done"); Logger.Instance.Debug(this, "Startup done");
Acacia.Features.DebugSupport.Statistics.StartupTime.Stop(); Acacia.Features.DebugSupport.Statistics.StartupTime.Stop();
foreach (Feature feature in Features)
feature.AfterStartup();
} }
catch (System.Exception e) catch (System.Exception e)
{ {
@ -172,6 +173,7 @@ namespace Acacia
{ {
try try
{ {
// TODO: is any management of Pages needed here?
Pages.Add(new SettingsPage(Features.ToArray()), Properties.Resources.ThisAddIn_Title); Pages.Add(new SettingsPage(Features.ToArray()), Properties.Resources.ThisAddIn_Title);
} }
catch(System.Exception e) catch(System.Exception e)

View File

@ -224,23 +224,22 @@ namespace Acacia.UI
public List<GABUser> Lookup(string text, int max) public List<GABUser> Lookup(string text, int max)
{ {
// Begin GAB lookup, search on full name or username // Begin GAB lookup, search on full name or username
ISearch<IContactItem> search = _gab.Contacts.Search<IContactItem>(); using (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 (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)); users.Add(new GABUser(result.FullName, result.CustomerID));
} }
}
return users; return users;
}
} }
public GABUser LookupExact(string username) public GABUser LookupExact(string username)
@ -252,13 +251,11 @@ namespace Acacia.UI
search.AddField("urn:schemas:contacts:customerid").SetOperation(SearchOperation.Equal, username); search.AddField("urn:schemas:contacts:customerid").SetOperation(SearchOperation.Equal, username);
// Fetch the result, if any. // Fetch the result, if any.
// TODO: make a SearchOne method?
List<GABUser> users = new List<GABUser>(); List<GABUser> users = new List<GABUser>();
foreach (IContactItem result in search.Search(1)) 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 /// 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, /// 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/>. /// along with this program.If not, see<http://www.gnu.org/licenses/>.
/// ///
/// Consult LICENSE file for details /// Consult LICENSE file for details
using Microsoft.Office.Core; using Microsoft.Office.Core;
using stdole; using stdole;
using System; using System;
@ -35,105 +37,19 @@ namespace Acacia.UI.Outlook
{ {
public ImageList Images { get; private set; } 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) public OutlookImageList(params string[] icons)
{ {
Images = new ImageList(); Images = new ImageList();
Images.ColorDepth = ColorDepth.Depth32Bit; Images.ColorDepth = ColorDepth.Depth32Bit;
Images.ImageSize = new Size(16, 16); Images.ImageSize = new Size(16, 16);
CommandBars cmdBars = ThisAddIn.Instance.Application.ActiveWindow().CommandBars; using (IExplorer explorer = ThisAddIn.Instance.GetActiveExplorer())
foreach (string id in icons) using (ICommandBars cmdBars = explorer.GetCommandBars())
{ {
IPictureDisp pict = cmdBars.GetImageMso(id, Images.ImageSize.Width, Images.ImageSize.Height); foreach (string id in icons)
var img = GetBitmapFromHBitmap2(new IntPtr(pict.Handle)); {
Images.Images.Add(img); Images.Images.Add(cmdBars.GetMso(id).GetImage(Images.ImageSize));
}
} }
} }
} }

View File

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

View File

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

View File

@ -25,11 +25,12 @@ using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Acacia.Features; using Acacia.Features;
using NSOutlook = Microsoft.Office.Interop.Outlook;
namespace Acacia.UI namespace Acacia.UI
{ {
[ComVisible(true)] [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>(); 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); Dirty = _featuresDirty.Values.Aggregate((a, b) => a | b);
} }
private Microsoft.Office.Interop.Outlook.PropertyPageSite _propertyPageSite; private NSOutlook.PropertyPageSite _propertyPageSite;
public Microsoft.Office.Interop.Outlook.PropertyPageSite PropertyPageSite public NSOutlook.PropertyPageSite PropertyPageSite
{ {
get get
{ {
@ -96,7 +97,8 @@ namespace Acacia.UI
System.Reflection.MethodInfo methodInfo = oleObjectType.GetMethod("GetClientSite"); System.Reflection.MethodInfo methodInfo = oleObjectType.GetMethod("GetClientSite");
Object propertyPageSite = methodInfo.Invoke(this, null); 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; return _propertyPageSite;
} }

View File

@ -34,6 +34,12 @@ namespace Acacia.Utils
return t; return t;
} }
public Type Remove<Type>(Type t)
{
objects.Remove(t);
return t;
}
public void Dispose() public void Dispose()
{ {
foreach (object o in objects) 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) public static void Release(object o)
{ {
if (!Enabled) if (!Enabled)
return; return;
if (o == null) if (o == null || !Marshal.IsComObject(o))
return; return;
if (Logger.Instance.IsLevelEnabled(LogLevel.TraceExtra)) 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 /// 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, /// it under the terms of the GNU Affero General Public License, version 3,
@ -15,7 +15,6 @@
/// Consult LICENSE file for details /// Consult LICENSE file for details
using Acacia.Features.DebugSupport; using Acacia.Features.DebugSupport;
using Acacia.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -23,15 +22,13 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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> protected DisposableWrapper()
/// Creates a wrapper.
/// </summary>
internal DisposableWrapper()
{ {
Interlocked.Increment(ref Statistics.CreatedWrappers); Interlocked.Increment(ref Statistics.CreatedWrappers);
this._createdTrace = new System.Diagnostics.StackTrace(); this._createdTrace = new System.Diagnostics.StackTrace();
@ -43,9 +40,12 @@ namespace Acacia.Stubs.OutlookWrappers
if (!_isDisposed) if (!_isDisposed)
{ {
Logger.Instance.Warning(this, "Undisposed wrapper: {0}", _createdTrace); Logger.Instance.Warning(this, "Undisposed wrapper: {0}", _createdTrace);
Dispose(); // Dispose, but don't count auto disposals, so the stats show it.
// Don't count auto disposals DoRelease();
Interlocked.Decrement(ref Statistics.DisposedWrappers); }
else
{
--typeCounts[GetType()];
} }
} }
@ -56,6 +56,11 @@ namespace Acacia.Stubs.OutlookWrappers
{ {
if (!_isDisposed) if (!_isDisposed)
{ {
if (!typeCounts.ContainsKey(GetType()))
typeCounts.Add(GetType(), 1);
else
++typeCounts[GetType()];
Logger.Instance.TraceExtra(this, "Disposing wrapper: {0}", new System.Diagnostics.StackTrace()); Logger.Instance.TraceExtra(this, "Disposing wrapper: {0}", new System.Diagnostics.StackTrace());
_isDisposed = true; _isDisposed = true;
Interlocked.Increment(ref Statistics.DisposedWrappers); Interlocked.Increment(ref Statistics.DisposedWrappers);
@ -63,12 +68,7 @@ namespace Acacia.Stubs.OutlookWrappers
} }
} }
public bool MustRelease
{
get;
set;
}
abstract protected void DoRelease(); abstract protected void DoRelease();
} }
} }

View File

@ -28,17 +28,19 @@ namespace Acacia.Utils
{ {
public static class FolderUtils 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) if (orig)
{ {
string type = (string)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE_ORIG); string type = (string)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE_ORIG);
if (string.IsNullOrEmpty(type))
return null;
return (OutlookConstants.SyncType)int.Parse(type); return (OutlookConstants.SyncType)int.Parse(type);
} }
else else
{ {
int type = (int)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE); int? type = (int?)folder.GetProperty(OutlookConstants.PR_EAS_SYNCTYPE);
return (OutlookConstants.SyncType)type; 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 /// Consult LICENSE file for details
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -47,20 +46,16 @@ namespace Acacia.Utils
public event MailResponseEventHandler Respond; public event MailResponseEventHandler Respond;
public event MailResponseEventHandler Reply; public event MailResponseEventHandler Reply;
private void OnReply(MailItem mail, MailItem response) private void OnReply(IMailItem mail, IMailItem response)
{ {
try 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), if (Reply != null)
responseWrapped = Mapping.Wrap<IMailItem>(response)) Reply(mail, response);
{ if (Respond != null)
if (Reply != null) Respond(mail, response);
Reply(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
} }
} }
catch (System.Exception e) catch (System.Exception e)
@ -70,20 +65,16 @@ namespace Acacia.Utils
} }
public event MailResponseEventHandler ReplyAll; public event MailResponseEventHandler ReplyAll;
private void OnReplyAll(MailItem mail, MailItem response) private void OnReplyAll(IMailItem mail, IMailItem response)
{ {
try 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), if (ReplyAll != null)
responseWrapped = Mapping.Wrap<IMailItem>(response)) ReplyAll(mail, response);
{ if (Respond != null)
if (ReplyAll != null) Respond(mail, response);
ReplyAll(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
} }
} }
catch (System.Exception e) catch (System.Exception e)
@ -93,20 +84,16 @@ namespace Acacia.Utils
} }
public event MailResponseEventHandler Forward; public event MailResponseEventHandler Forward;
private void OnForward(MailItem mail, MailItem response) private void OnForward(IMailItem mail, IMailItem response)
{ {
try 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), if (Forward != null)
responseWrapped = Mapping.Wrap<IMailItem>(response)) Forward(mail, response);
{ if (Respond != null)
if (Forward != null) Respond(mail, response);
Forward(mailWrapped, responseWrapped);
if (Respond != null)
Respond(mailWrapped, responseWrapped);
}
} }
} }
catch (System.Exception e) catch (System.Exception e)
@ -116,16 +103,13 @@ namespace Acacia.Utils
} }
public event MailEventHandler Read; public event MailEventHandler Read;
private void OnRead(MailItem mail) private void OnRead(IMailItem mail)
{ {
try try
{ {
if (Read != null && mail != null) if (Read != null && mail != null)
{ {
using (IMailItem wrapped = Mapping.Wrap<IMailItem>(mail, false)) Read(mail);
{
Read(wrapped);
}
} }
} }
catch (System.Exception e) catch (System.Exception e)
@ -135,17 +119,13 @@ namespace Acacia.Utils
} }
public event CancellableItemEventHandler BeforeDelete; public event CancellableItemEventHandler BeforeDelete;
private void OnBeforeDelete(object item, ref bool cancel) private void OnBeforeDelete(IItem item, ref bool cancel)
{ {
try try
{ {
if (BeforeDelete != null && item != null) if (BeforeDelete != null && item != null)
{ {
using (IItem wrapped = Mapping.Wrap<IItem>(item, false)) BeforeDelete(item, ref cancel);
{
if (wrapped != null)
BeforeDelete(wrapped, ref cancel);
}
} }
} }
catch(System.Exception e) catch(System.Exception e)
@ -156,17 +136,13 @@ namespace Acacia.Utils
// TODO: should this be CancellableMailItemEventHandler? // TODO: should this be CancellableMailItemEventHandler?
public event CancellableItemEventHandler Write; public event CancellableItemEventHandler Write;
private void OnWrite(object item, ref bool cancel) private void OnWrite(IItem item, ref bool cancel)
{ {
try try
{ {
if (Write != null && item != null) if (Write != null && item != null)
{ {
using (IItem wrapped = Mapping.Wrap<IItem>(item, false)) Write(item, ref cancel);
{
if (wrapped != null)
Write(wrapped, ref cancel);
}
} }
} }
catch (System.Exception e) catch (System.Exception e)
@ -199,93 +175,102 @@ namespace Acacia.Utils
#region Implementation #region Implementation
public MailEvents(Application app) public MailEvents(IAddIn app)
{ {
app.ItemLoad += OnItemLoad; app.ItemLoad += OnItemLoad;
app.ItemSend += OnItemSend; app.ItemSend += OnItemSend;
} }
void OnItemLoad(object item) private void OnItemLoad(object item)
{ {
ItemEvents_10_Event hasEvents = item as ItemEvents_10_Event; IItem wrapped = Wrappers.Wrap<IItem>(item, false);
if (hasEvents != null) // 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 IItem _item;
private readonly MailEvents events; private readonly MailEvents _events;
public MailEventHooker(ItemEvents_10_Event item, MailEvents events) public MailEventHooker(IItem item, MailEvents events)
{ {
this.item = item; this._item = item;
this.events = events; this._events = events;
HookEvents(true); HookEvents(true);
} }
protected override void DoRelease()
{
_item.Dispose();
}
private void HookEvents(bool add) 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.BeforeDelete += HandleBeforeDelete; events.Read += HandleRead;
events.Forward += HandleForward; events.Reply += HandleReply;
events.Read += HandleRead; events.ReplyAll += HandleReplyAll;
events.Reply += HandleReply; events.Unload += HandleUnload;
events.ReplyAll += HandleReplyAll; events.Write += HandleWrite;
events.Unload += HandleUnload; }
events.Write += HandleWrite; else
} {
else events.BeforeDelete -= HandleBeforeDelete;
{ events.Forward -= HandleForward;
events.BeforeDelete -= HandleBeforeDelete; events.Read -= HandleRead;
events.Forward -= HandleForward; events.Reply -= HandleReply;
events.Read -= HandleRead; events.ReplyAll -= HandleReplyAll;
events.Reply -= HandleReply; events.Unload -= HandleUnload;
events.ReplyAll -= HandleReplyAll; events.Write -= HandleWrite;
events.Unload -= HandleUnload; }
events.Write -= HandleWrite;
} }
} }
private void HandleBeforeDelete(object item, ref bool cancel) 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) 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() private void HandleRead()
{ {
events.OnRead(item as MailItem); _events.OnRead(_item as IMailItem);
} }
private void HandleReply(object response, ref bool cancel) 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) 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() private void HandleUnload()
{ {
// All events must be unhooked on unload, otherwise a resource leak is created. // All events must be unhooked on unload, otherwise a resource leak is created.
HookEvents(false); HookEvents(false);
Dispose();
} }
private void HandleWrite(ref bool cancel) 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) public static RegistryKey OpenOutlookKey(string suffix = null, RegistryKeyPermissionCheck permissions = RegistryKeyPermissionCheck.Default)
{ {
// Determine the base path // Determine the base path
string[] versionParts = ThisAddIn.Instance.Application.Version.Split('.'); string[] versionParts = ThisAddIn.Instance.Version.Split('.');
string versionString = versionParts[0] + "." + versionParts[1]; string versionString = versionParts[0] + "." + versionParts[1];
string baseKeyPath = string.Format(OutlookConstants.REG_KEY_BASE, versionString); string baseKeyPath = string.Format(OutlookConstants.REG_KEY_BASE, versionString);
return RegistryUtil.OpenKeyImpl(baseKeyPath, suffix, false, permissions); return RegistryUtil.OpenKeyImpl(baseKeyPath, suffix, false, permissions);

View File

@ -15,9 +15,11 @@
/// Consult LICENSE file for details /// Consult LICENSE file for details
using Acacia.Features; using Acacia.Features;
using Acacia.Features.DebugSupport;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
namespace Acacia.Utils namespace Acacia.Utils
@ -64,10 +66,29 @@ namespace Acacia.Utils
} }
public interface TaskExecutor public abstract class TaskExecutor
{ {
string Name { get; } public abstract string Name { get; }
void ExecuteTask(AcaciaTask task);
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 public static class Tasks
@ -103,12 +124,12 @@ namespace Acacia.Utils
public static void Task(Feature owner, string name, Action action) 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) 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 /// 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, /// 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/>. /// along with this program.If not, see<http://www.gnu.org/licenses/>.
/// ///
/// Consult LICENSE file for details /// Consult LICENSE file for details
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -40,15 +41,15 @@ namespace Acacia.Utils
while (!_tasks.IsCompleted) while (!_tasks.IsCompleted)
{ {
AcaciaTask task = _tasks.Take(); AcaciaTask task = _tasks.Take();
task.Execute(); PerformTask(task);
} }
} }
public void ExecuteTask(AcaciaTask task) protected override void EnqueueTask(AcaciaTask task)
{ {
_tasks.Add(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(); AcaciaTask task = _tasks.Dequeue();
Logger.Instance.Trace(task.Id, "Beginning task"); Logger.Instance.Trace(task.Id, "Beginning task");
task.Execute(); PerformTask(task);
Logger.Instance.Info(task.Id, "Ending task: {0}ms", timer.ElapsedMilliseconds); Logger.Instance.Info(task.Id, "Ending task: {0}ms", timer.ElapsedMilliseconds);
// Execute another task if available and we haven't taken too long. // Execute another task if available and we haven't taken too long.
} while (_tasks.Count > 0 && timer.ElapsedMilliseconds < 50); } while (_tasks.Count > 0 && timer.ElapsedMilliseconds < 50);
@ -99,7 +99,7 @@ namespace Acacia.Utils
/// </summary> /// </summary>
/// <param name="name">The name, for debugging and logging.</param> /// <param name="name">The name, for debugging and logging.</param>
/// <param name="action">The action to execute</param> /// <param name="action">The action to execute</param>
public void ExecuteTask(AcaciaTask task) override protected void EnqueueTask(AcaciaTask task)
{ {
if (_init == InitState.Uninitialised) if (_init == InitState.Uninitialised)
{ {
@ -112,6 +112,6 @@ namespace Acacia.Utils
_tasks.Enqueue(task); _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 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 /// Consult LICENSE file for details
using Acacia.ZPush; using Acacia.ZPush;
using Microsoft.Office.Interop.Outlook;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -44,5 +44,123 @@ namespace Acacia.Utils
return a.Equals(b); 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, // 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 // 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. // 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)); var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
_client.DefaultRequestHeaders.Authorization = header; _client.DefaultRequestHeaders.Authorization = header;
} }
@ -278,10 +278,10 @@ namespace Acacia.ZPush.Connect
public Response Execute(ActiveSync.RequestBase request) public Response Execute(ActiveSync.RequestBase request)
{ {
string url = string.Format(ACTIVESYNC_URL, _account.ServerURL, _account.DeviceId, string url = string.Format(ACTIVESYNC_URL, _account.Account.ServerURL, _account.Account.DeviceId,
request.Command, _account.UserName, "WindowsOutlook"); request.Command, _account.Account.UserName, "WindowsOutlook");
// Parse the body // Construct the body
WBXMLDocument doc = new WBXMLDocument(); WBXMLDocument doc = new WBXMLDocument();
doc.LoadXml(request.Body); doc.LoadXml(request.Body);
doc.VersionNumber = 1.3; doc.VersionNumber = 1.3;
@ -291,8 +291,11 @@ namespace Acacia.ZPush.Connect
using (HttpContent content = new ByteArrayContent(contentBody)) 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"); 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) using (HttpResponseMessage response = _client.PostAsync(url, content, _cancel).Result)
{ {
return new Response(response); return new Response(response);

View File

@ -46,14 +46,14 @@ namespace Acacia.ZPush.Connect
public ResponseType Execute<ResponseType>(SoapRequest<ResponseType> request) public ResponseType Execute<ResponseType>(SoapRequest<ResponseType> request)
{ {
// Create the url // 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, ServiceName,
// TODO: this username is a bit of a quick hack. // TODO: this username is a bit of a quick hack.
request.UserName ?? _connection.Account.UserName, request.UserName ?? _connection.Account.Account.UserName,
"webservice"); "webservice");
// Set up the encoding // 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; encoder.ServiceName = ServiceName;
// Execute the request // Execute the request
@ -85,7 +85,7 @@ namespace Acacia.ZPush.Connect
get get
{ {
SoapParameters parameters = new SoapParameters(); SoapParameters parameters = new SoapParameters();
parameters.Add("devid", _connection.Account.DeviceId.ToLower()); parameters.Add("devid", _connection.Account.Account.DeviceId.ToLower());
return parameters; return parameters;
} }
} }

View File

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

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