mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 13:37:40 +02:00
[KOE-17] Basic infrastructure is working, still need replacement of placeholders
This commit is contained in:
parent
629bfd371b
commit
e735aafc72
@ -274,16 +274,17 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Features\SendAs\FeatureSendAs.cs" />
|
<Compile Include="Features\SendAs\FeatureSendAs.cs" />
|
||||||
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
|
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
|
||||||
|
<Compile Include="Features\Signatures\FeatureSignatures.cs" />
|
||||||
<Compile Include="GlobalOptions.cs" />
|
<Compile Include="GlobalOptions.cs" />
|
||||||
<Compile Include="Logging.cs" />
|
<Compile Include="Logging.cs" />
|
||||||
<Compile Include="Native\IOleWindow.cs" />
|
<Compile Include="Native\IOleWindow.cs" />
|
||||||
|
<Compile Include="Native\IOlkAccount.cs" />
|
||||||
<Compile Include="Native\MAPI\Binary.cs" />
|
<Compile Include="Native\MAPI\Binary.cs" />
|
||||||
<Compile Include="Native\MAPI\IMAPIContainer.cs" />
|
<Compile Include="Native\MAPI\IMAPIContainer.cs" />
|
||||||
<Compile Include="Native\MAPI\IMAPIFolder.cs" />
|
<Compile Include="Native\MAPI\IMAPIFolder.cs" />
|
||||||
<Compile Include="Native\MAPI\IMAPIProp.cs" />
|
<Compile Include="Native\MAPI\IMAPIProp.cs" />
|
||||||
<Compile Include="Native\MAPI\Property.cs" />
|
<Compile Include="Native\MAPI\Property.cs" />
|
||||||
<Compile Include="Native\MAPI\Restriction.cs" />
|
<Compile Include="Native\MAPI\Restriction.cs" />
|
||||||
<Compile Include="Native\NativeEncoder.cs" />
|
|
||||||
<Compile Include="OutlookConstants.cs" />
|
<Compile Include="OutlookConstants.cs" />
|
||||||
<Compile Include="SearchQuery.cs" />
|
<Compile Include="SearchQuery.cs" />
|
||||||
<Compile Include="Stubs\Enums.cs" />
|
<Compile Include="Stubs\Enums.cs" />
|
||||||
@ -299,6 +300,8 @@
|
|||||||
<Compile Include="Stubs\IOutlookWindow.cs" />
|
<Compile Include="Stubs\IOutlookWindow.cs" />
|
||||||
<Compile Include="Stubs\IPicture.cs" />
|
<Compile Include="Stubs\IPicture.cs" />
|
||||||
<Compile Include="Stubs\IRecipient.cs" />
|
<Compile Include="Stubs\IRecipient.cs" />
|
||||||
|
<Compile Include="Stubs\ISignature.cs" />
|
||||||
|
<Compile Include="Stubs\ISignatures.cs" />
|
||||||
<Compile Include="Stubs\IStores.cs" />
|
<Compile Include="Stubs\IStores.cs" />
|
||||||
<Compile Include="Stubs\ISyncObject.cs" />
|
<Compile Include="Stubs\ISyncObject.cs" />
|
||||||
<Compile Include="Stubs\OutlookWrappers\AccountWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\AccountWrapper.cs" />
|
||||||
@ -312,6 +315,8 @@
|
|||||||
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
|
||||||
<Compile Include="Stubs\OutlookWrappers\PictureWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\PictureWrapper.cs" />
|
||||||
<Compile Include="Stubs\OutlookWrappers\RecipientWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\RecipientWrapper.cs" />
|
||||||
|
<Compile Include="Stubs\OutlookWrappers\SignaturesWrapper.cs" />
|
||||||
|
<Compile Include="Stubs\OutlookWrappers\SignatureWrapper.cs" />
|
||||||
<Compile Include="Stubs\OutlookWrappers\StoresWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\StoresWrapper.cs" />
|
||||||
<Compile Include="Stubs\OutlookWrappers\SyncObjectWrapper.cs" />
|
<Compile Include="Stubs\OutlookWrappers\SyncObjectWrapper.cs" />
|
||||||
<Compile Include="Stubs\Wrappers.cs" />
|
<Compile Include="Stubs\Wrappers.cs" />
|
||||||
|
@ -74,7 +74,8 @@ namespace Acacia
|
|||||||
public const string ZPUSH_HEADER_CLIENT_CAPABILITIES = "X-Push-Plugin-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";
|
||||||
|
public const string ZPUSH_HEADER_SIGNATURES_HASH = "X-Push-Signatures-Hash";
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -142,6 +142,38 @@ namespace Acacia
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class StringOption : Option<string>
|
||||||
|
{
|
||||||
|
private readonly string _defaultValue;
|
||||||
|
|
||||||
|
public StringOption(string token, string defaultValue)
|
||||||
|
:
|
||||||
|
base(token)
|
||||||
|
{
|
||||||
|
this._defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetToken(string value)
|
||||||
|
{
|
||||||
|
if (value.Equals(_defaultValue))
|
||||||
|
return null;
|
||||||
|
return Token + "=" + value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetValue(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return _defaultValue;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value.ToLower().StartsWith(Token.ToLower() + "="))
|
||||||
|
value = value.Substring(Token.Length + 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// General
|
// General
|
||||||
public static readonly BoolOption ENABLED = new BoolOption("", true);
|
public static readonly BoolOption ENABLED = new BoolOption("", true);
|
||||||
public static readonly BoolOption FEATURE_DISABLED_DEFAULT = new BoolOption("", false);
|
public static readonly BoolOption FEATURE_DISABLED_DEFAULT = new BoolOption("", false);
|
||||||
|
@ -87,7 +87,6 @@ namespace Acacia.Features.SecondaryContacts
|
|||||||
// So, when the folder is detected, we make it invisible and perform steps 1 and 2. We issue a warning
|
// 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
|
// 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.
|
// At this point the name is patched and the folder is made visible.
|
||||||
|
|
||||||
if (!folder.AttrHidden)
|
if (!folder.AttrHidden)
|
||||||
{
|
{
|
||||||
// Stage 1
|
// Stage 1
|
||||||
@ -113,12 +112,12 @@ namespace Acacia.Features.SecondaryContacts
|
|||||||
// Stage 2
|
// Stage 2
|
||||||
|
|
||||||
// Patch the name
|
// Patch the name
|
||||||
Logger.Instance.Trace(this, "Patching name");
|
Logger.Instance.Trace(this, "Patching name");
|
||||||
folder.Name = strippedName;
|
folder.Name = strippedName;
|
||||||
|
|
||||||
// Show it
|
// Show it
|
||||||
folder.AttrHidden = false;
|
folder.AttrHidden = false;
|
||||||
Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
|
Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
|
||||||
}
|
}
|
||||||
Logger.Instance.Debug(this, "Patching done: {0}: {1}", strippedName, folder.AttrHidden);
|
Logger.Instance.Debug(this, "Patching done: {0}: {1}", strippedName, folder.AttrHidden);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,186 @@
|
|||||||
|
/// 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 Acacia.ZPush.Connect;
|
||||||
|
using Acacia.ZPush.Connect.Soap;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using static Acacia.DebugOptions;
|
||||||
|
|
||||||
|
namespace Acacia.Features.Signatures
|
||||||
|
{
|
||||||
|
[AcaciaOption("Provides the possibility to synchronise signatures from the server.")]
|
||||||
|
public class FeatureSignatures : Feature
|
||||||
|
{
|
||||||
|
#region Debug options
|
||||||
|
|
||||||
|
[AcaciaOption("The format for local names of synchronised signatures, to prevent overwriting local signatures. May contain %account% and %name%.")]
|
||||||
|
public string SignatureLocalName
|
||||||
|
{
|
||||||
|
get { return GetOption(OPTION_SIGNATURE_LOCAL_NAME); }
|
||||||
|
set { SetOption(OPTION_SIGNATURE_LOCAL_NAME, value); }
|
||||||
|
}
|
||||||
|
private static readonly StringOption OPTION_SIGNATURE_LOCAL_NAME = new StringOption("SignatureLocalName", "%name% (KOE-%account%)");
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override void Startup()
|
||||||
|
{
|
||||||
|
Watcher.AccountDiscovered += Watcher_AccountDiscovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Watcher_AccountDiscovered(ZPushAccount account)
|
||||||
|
{
|
||||||
|
account.ConfirmedChanged += Account_ConfirmedChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Account_ConfirmedChanged(ZPushAccount account)
|
||||||
|
{
|
||||||
|
// TODO: make a helper to register for all zpush accounts with specific capabilities, best even
|
||||||
|
// the feature's capabilities
|
||||||
|
if (account.Confirmed == ZPushAccount.ConfirmationType.IsZPush &&
|
||||||
|
account.Capabilities.Has("signatures"))
|
||||||
|
{
|
||||||
|
Logger.Instance.Trace(this, "Checking signature hash for account {0}: {1}", account, account.ServerSignaturesHash);
|
||||||
|
|
||||||
|
// Fetch signatures if there is a change
|
||||||
|
if (account.ServerSignaturesHash != account.Account.LocalSignaturesHash)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Instance.Debug(this, "Updating signatures: {0}", account);
|
||||||
|
FetchSignatures(account);
|
||||||
|
|
||||||
|
// Store updated hash
|
||||||
|
account.Account.LocalSignaturesHash = account.ServerSignaturesHash;
|
||||||
|
Logger.Instance.Debug(this, "Updated signatures: {0}", account);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Instance.Error(this, "Error fetching signatures: {0}: {1}", account, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Prevent field assignment warnings
|
||||||
|
#pragma warning disable 0649
|
||||||
|
|
||||||
|
private class Signature
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
public string name;
|
||||||
|
public string content;
|
||||||
|
public bool isHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GetSignatures
|
||||||
|
{
|
||||||
|
public Dictionary<string, Signature> all;
|
||||||
|
public string new_message;
|
||||||
|
public string replyforward_message;
|
||||||
|
public string hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning restore 0649
|
||||||
|
|
||||||
|
private class GetSignaturesRequest : SoapRequest<GetSignatures>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FetchSignatures(ZPushAccount account)
|
||||||
|
{
|
||||||
|
Logger.Instance.Debug(this, "Fetching signatures for account {0}", account);
|
||||||
|
using (ZPushConnection connection = account.Connect())
|
||||||
|
using (ZPushWebServiceInfo infoService = connection.InfoService)
|
||||||
|
{
|
||||||
|
GetSignatures result = infoService.Execute(new GetSignaturesRequest());
|
||||||
|
|
||||||
|
// Store the signatures
|
||||||
|
Dictionary<object, string> fullNames = new Dictionary<object, string>();
|
||||||
|
using (ISignatures signatures = ThisAddIn.Instance.GetSignatures())
|
||||||
|
{
|
||||||
|
foreach (Signature signature in result.all.Values)
|
||||||
|
{
|
||||||
|
string name = StoreSignature(signatures, account, signature);
|
||||||
|
fullNames.Add(signature.id, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default signatures if available and none are set
|
||||||
|
if (!string.IsNullOrEmpty(result.new_message) && string.IsNullOrEmpty(account.Account.SignatureNewMessage))
|
||||||
|
{
|
||||||
|
account.Account.SignatureNewMessage = fullNames[result.new_message];
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(result.replyforward_message) && string.IsNullOrEmpty(account.Account.SignatureReplyForwardMessage))
|
||||||
|
{
|
||||||
|
account.Account.SignatureReplyForwardMessage = fullNames[result.replyforward_message];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StoreSignature(ISignatures signatures, ZPushAccount account, Signature signatureInfo)
|
||||||
|
{
|
||||||
|
string name = SignatureLocalName.ReplacePercentStrings(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "account", account.DisplayName },
|
||||||
|
{ "name", signatureInfo.name }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any existing signature
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ISignature signature = signatures.Get(name);
|
||||||
|
if (signature != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
signature.Delete();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
signature.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Logger.Instance.Error(this, "Unable to delete signature {0}: {1}", name, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new signature
|
||||||
|
using (ISignature signature = signatures.Add(name))
|
||||||
|
{
|
||||||
|
signature.SetContent(signatureInfo.content, signatureInfo.isHTML ? ISignatureFormat.HTML : ISignatureFormat.Text);
|
||||||
|
// TODO: generate text version if we get an HTML?
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
using Acacia.Native.MAPI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Acacia.Native
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
unsafe public struct ACCT_VARIANT
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public uint dwType;
|
||||||
|
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public uint dwAlignPad;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public char* lpszW;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("9240a6d2-af41-11d2-8c3b-00104b2a6676")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
unsafe public interface IOlkAccount
|
||||||
|
{
|
||||||
|
// IOlkErrorUnknown
|
||||||
|
void IOlkErrorUnknown_GetLastError();
|
||||||
|
|
||||||
|
void IOlkAccount_Placeholder1();
|
||||||
|
void IOlkAccount_Placeholder2();
|
||||||
|
void IOlkAccount_Placeholder3();
|
||||||
|
void IOlkAccount_Placeholder4();
|
||||||
|
void IOlkAccount_Placeholder5();
|
||||||
|
void IOlkAccount_Placeholder6();
|
||||||
|
|
||||||
|
void GetAccountInfo(Guid* pclsidType, int* pcCategories, Guid** prgclsidCategory);
|
||||||
|
void GetProp(PropTag dwProp, ACCT_VARIANT *pVar);
|
||||||
|
void SetProp(PropTag dwProp, ACCT_VARIANT *pVar);
|
||||||
|
|
||||||
|
void IOlkAccount_Placeholder7();
|
||||||
|
void IOlkAccount_Placeholder8();
|
||||||
|
void IOlkAccount_Placeholder9();
|
||||||
|
|
||||||
|
void FreeMemory(byte* pv);
|
||||||
|
|
||||||
|
void IOlkAccount_Placeholder10();
|
||||||
|
|
||||||
|
void SaveChanges(uint dwFlags);
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,15 @@ namespace Acacia.Native.MAPI
|
|||||||
{
|
{
|
||||||
return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)type);
|
return SearchQuery.PropertyIdentifier.FromTag(prop, (ushort)type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PropTag FromInt(int v)
|
||||||
|
{
|
||||||
|
return new PropTag()
|
||||||
|
{
|
||||||
|
prop = (ushort)((v & 0xFFFF0000) >> 16),
|
||||||
|
type = (PropType)(v & 0xFFFF)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,10 @@ namespace Acacia
|
|||||||
public const string REG_VAL_DELIVERY_STORE = "Delivery Store EntryID";
|
public const string REG_VAL_DELIVERY_STORE = "Delivery Store EntryID";
|
||||||
public const string REG_VAL_DELIVERY_FOLDER = "Delivery Folder EntryID";
|
public const string REG_VAL_DELIVERY_FOLDER = "Delivery Folder EntryID";
|
||||||
|
|
||||||
|
public const string REG_VAL_NEW_SIGNATURE = "New Signature";
|
||||||
|
public const string REG_VAL_REPLY_FORWARD_SIGNATURE = "Reply-Forward Signature";
|
||||||
|
public const string REG_VAL_CURRENT_SIGNATURE = "KOE Signature Digest";
|
||||||
|
|
||||||
public const string REG_VAL_NEXT_ACCOUNT_ID = "NextAccountID";
|
public const string REG_VAL_NEXT_ACCOUNT_ID = "NextAccountID";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -49,5 +49,23 @@ namespace Acacia.Stubs
|
|||||||
|
|
||||||
string DomainName { get; }
|
string DomainName { get; }
|
||||||
|
|
||||||
|
// TODO: this is really a Z-Push thing, but it's here to store it in the registry
|
||||||
|
string LocalSignaturesHash
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
string SignatureNewMessage
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
string SignatureReplyForwardMessage
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,5 +86,9 @@ namespace Acacia.Stubs
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
ISignatures GetSignatures();
|
||||||
|
|
||||||
|
void InUI(Action action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISignature.cs
Normal file
20
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISignature.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Acacia.Stubs
|
||||||
|
{
|
||||||
|
public enum ISignatureFormat
|
||||||
|
{
|
||||||
|
HTML,
|
||||||
|
Text
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISignature : IDisposable
|
||||||
|
{
|
||||||
|
void Delete();
|
||||||
|
void SetContent(string content, ISignatureFormat format);
|
||||||
|
}
|
||||||
|
}
|
14
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISignatures.cs
Normal file
14
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/ISignatures.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Acacia.Stubs
|
||||||
|
{
|
||||||
|
public interface ISignatures : IDisposable
|
||||||
|
{
|
||||||
|
ISignature Get(string name);
|
||||||
|
ISignature Add(string name);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
/// Copyright 2017 Kopano b.v.
|
|
||||||
|
using Acacia.Native;
|
||||||
|
using Acacia.Native.MAPI;
|
||||||
|
/// 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,
|
||||||
@ -13,26 +16,29 @@
|
|||||||
/// 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.Utils;
|
using Acacia.Utils;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
||||||
|
|
||||||
namespace Acacia.Stubs.OutlookWrappers
|
namespace Acacia.Stubs.OutlookWrappers
|
||||||
{
|
{
|
||||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||||
class AccountWrapper : DisposableWrapper, IAccount, LogContext
|
class AccountWrapper : ComWrapper<NSOutlook.Application>, IAccount, LogContext
|
||||||
{
|
{
|
||||||
private readonly string _regPath;
|
private readonly string _regPath;
|
||||||
private readonly IStore _store;
|
private readonly IStore _store;
|
||||||
|
|
||||||
internal AccountWrapper(string regPath, IStore store)
|
internal AccountWrapper(NSOutlook.Application item, string regPath, IStore store) : base(item)
|
||||||
{
|
{
|
||||||
this._regPath = regPath;
|
this._regPath = regPath;
|
||||||
this._store = store;
|
this._store = store;
|
||||||
@ -44,6 +50,7 @@ namespace Acacia.Stubs.OutlookWrappers
|
|||||||
protected override void DoRelease()
|
protected override void DoRelease()
|
||||||
{
|
{
|
||||||
_store.Dispose();
|
_store.Dispose();
|
||||||
|
base.DoRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
@ -163,6 +170,83 @@ namespace Acacia.Stubs.OutlookWrappers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string LocalSignaturesHash
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_CURRENT_SIGNATURE, null);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
RegistryUtil.SetValueString(_regPath, OutlookConstants.REG_VAL_CURRENT_SIGNATURE, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string SignatureNewMessage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_NEW_SIGNATURE, null);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// TODO: constant for account
|
||||||
|
SetAccountProp(PropTag.FromInt(0x0016001F), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe private void SetAccountProp(PropTag propTag, string value)
|
||||||
|
{
|
||||||
|
// Use IOlkAccount to notify while we're running
|
||||||
|
// IOlkAccount can only be accessed on main thread
|
||||||
|
ThisAddIn.Instance.InUI(() =>
|
||||||
|
{
|
||||||
|
using (ComRelease com = new ComRelease())
|
||||||
|
{
|
||||||
|
NSOutlook.Account account = com.Add(FindAccountObject());
|
||||||
|
IOlkAccount olk = com.Add(account.IOlkAccount);
|
||||||
|
|
||||||
|
fixed (char* ptr = value.ToCharArray())
|
||||||
|
{
|
||||||
|
ACCT_VARIANT val = new ACCT_VARIANT()
|
||||||
|
{
|
||||||
|
dwType = (uint)PropType.UNICODE,
|
||||||
|
lpszW = ptr
|
||||||
|
};
|
||||||
|
olk.SetProp(propTag, &val);
|
||||||
|
olk.SaveChanges(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private NSOutlook.Account FindAccountObject()
|
||||||
|
{
|
||||||
|
using (ComRelease com = new ComRelease())
|
||||||
|
{
|
||||||
|
NSOutlook.NameSpace session = com.Add(_item.Session);
|
||||||
|
foreach(NSOutlook.Account account in session.Accounts.ComEnum(false))
|
||||||
|
{
|
||||||
|
if (account.SmtpAddress == this.SmtpAddress)
|
||||||
|
return account;
|
||||||
|
else
|
||||||
|
com.Add(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SignatureReplyForwardMessage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return RegistryUtil.GetValueString(_regPath, OutlookConstants.REG_VAL_REPLY_FORWARD_SIGNATURE, null);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetAccountProp(PropTag.FromInt(0x0017001F), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,17 +25,19 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
||||||
|
|
||||||
namespace Acacia.Stubs.OutlookWrappers
|
namespace Acacia.Stubs.OutlookWrappers
|
||||||
{
|
{
|
||||||
public class AddInWrapper : IAddIn
|
class AddInWrapper : IAddIn
|
||||||
{
|
{
|
||||||
private readonly NSOutlook.Application _app;
|
private readonly NSOutlook.Application _app;
|
||||||
private readonly ThisAddIn _thisAddIn;
|
private readonly ThisAddIn _thisAddIn;
|
||||||
private readonly StoresWrapper _stores;
|
private readonly StoresWrapper _stores;
|
||||||
|
private readonly SynchronizationContext _sync;
|
||||||
|
|
||||||
public AddInWrapper(ThisAddIn thisAddIn)
|
public AddInWrapper(ThisAddIn thisAddIn)
|
||||||
{
|
{
|
||||||
@ -51,6 +53,38 @@ namespace Acacia.Stubs.OutlookWrappers
|
|||||||
{
|
{
|
||||||
ComRelease.Release(session);
|
ComRelease.Release(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The synchronization context is needed to allow background tasks to jump back to the UI thread.
|
||||||
|
// It's null in older versions of .Net, this fixes that
|
||||||
|
if (SynchronizationContext.Current == null)
|
||||||
|
{
|
||||||
|
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
|
||||||
|
}
|
||||||
|
_sync = SynchronizationContext.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISignatures GetSignatures()
|
||||||
|
{
|
||||||
|
return new SignaturesWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InUI(Action action)
|
||||||
|
{
|
||||||
|
Exception x = null;
|
||||||
|
_sync.Send((_) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
x = e;
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
if (x != null)
|
||||||
|
throw x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendReceive(IAccount account)
|
public void SendReceive(IAccount account)
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
using Acacia.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Acacia.Stubs.OutlookWrappers
|
||||||
|
{
|
||||||
|
class SignatureWrapper : DisposableWrapper, ISignature
|
||||||
|
{
|
||||||
|
private static readonly string[] SUFFIXES =
|
||||||
|
{
|
||||||
|
"htm", "html", "rtf", "txt"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string BasePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Microsoft\\Signatures";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
public SignatureWrapper(string name)
|
||||||
|
{
|
||||||
|
this._name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoRelease()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ISignature FindExisting(string name)
|
||||||
|
{
|
||||||
|
foreach(string suffix in SUFFIXES)
|
||||||
|
{
|
||||||
|
string path = GetPath(name, suffix);
|
||||||
|
if (new FileInfo(path).Exists)
|
||||||
|
return new SignatureWrapper(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPath(string name, string suffix)
|
||||||
|
{
|
||||||
|
return Path.ChangeExtension(Path.Combine(BasePath, name), suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
foreach (string suffix in SUFFIXES)
|
||||||
|
{
|
||||||
|
string path = GetPath(_name, suffix);
|
||||||
|
FileInfo file = new FileInfo(path);
|
||||||
|
if (file.Exists)
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
// TODO: additional files folder? We never create it
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContent(string content, ISignatureFormat format)
|
||||||
|
{
|
||||||
|
// Determine suffix
|
||||||
|
string suffix = "txt";
|
||||||
|
switch(format)
|
||||||
|
{
|
||||||
|
case ISignatureFormat.HTML: suffix = "htm"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
string path = GetPath(_name, suffix);
|
||||||
|
File.WriteAllText(path, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
using Acacia.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Acacia.Stubs.OutlookWrappers
|
||||||
|
{
|
||||||
|
class SignaturesWrapper : DisposableWrapper, ISignatures
|
||||||
|
{
|
||||||
|
public SignaturesWrapper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoRelease()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISignature Get(string name)
|
||||||
|
{
|
||||||
|
return SignatureWrapper.FindExisting(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISignature Add(string name)
|
||||||
|
{
|
||||||
|
// Check if exists
|
||||||
|
using (ISignature existing = SignatureWrapper.FindExisting(name))
|
||||||
|
{
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Signature " + name + " already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SignatureWrapper(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -247,7 +247,7 @@ namespace Acacia.Stubs.OutlookWrappers
|
|||||||
{
|
{
|
||||||
if (baseKey == null)
|
if (baseKey == null)
|
||||||
return null;
|
return null;
|
||||||
AccountWrapper account = new AccountWrapper(baseKey.Name, store);
|
AccountWrapper account = new AccountWrapper(_item.Application, baseKey.Name, store);
|
||||||
Register(account);
|
Register(account);
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
@ -101,13 +101,6 @@ namespace Acacia
|
|||||||
int lcid = Application.LanguageSettings.get_LanguageID(Microsoft.Office.Core.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);
|
||||||
|
|
||||||
// The synchronization context is needed to allow background tasks to jump back to the UI thread.
|
|
||||||
// It's null in older versions of .Net, this fixes that
|
|
||||||
if (SynchronizationContext.Current == null)
|
|
||||||
{
|
|
||||||
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the watcher
|
// Create the watcher
|
||||||
Watcher = new ZPushWatcher(Instance);
|
Watcher = new ZPushWatcher(Instance);
|
||||||
OutlookUI.Watcher = Watcher;
|
OutlookUI.Watcher = Watcher;
|
||||||
|
@ -43,8 +43,13 @@ namespace Acacia.Utils
|
|||||||
public static object Convert(this Type type, object value)
|
public static object Convert(this Type type, object value)
|
||||||
{
|
{
|
||||||
// For value types, null becomes a default instance
|
// For value types, null becomes a default instance
|
||||||
if (value == null && type.IsValueType)
|
if (value == null)
|
||||||
return Activator.CreateInstance(type);
|
{
|
||||||
|
if (type.IsValueType)
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we need a conversion
|
// Check if we need a conversion
|
||||||
if (!type.IsAssignableFrom(value.GetType()))
|
if (!type.IsAssignableFrom(value.GetType()))
|
||||||
@ -89,11 +94,27 @@ namespace Acacia.Utils
|
|||||||
|
|
||||||
public static Type[] GetGenericArguments(this Type type, Type _base)
|
public static Type[] GetGenericArguments(this Type type, Type _base)
|
||||||
{
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == _base)
|
||||||
|
return type.GetGenericArguments();
|
||||||
|
|
||||||
IEnumerable<Type> bases = _base.IsInterface ? type.GetInterfaces() : type.AllBaseTypes();
|
IEnumerable<Type> bases = _base.IsInterface ? type.GetInterfaces() : type.AllBaseTypes();
|
||||||
return bases.Select(x =>
|
return bases.Select(x =>
|
||||||
(x.IsGenericType && x.GetGenericTypeDefinition() == _base) ? x.GetGenericArguments() : null
|
(x.IsGenericType && x.GetGenericTypeDefinition() == _base) ? x.GetGenericArguments() : null
|
||||||
).FirstOrDefault();
|
).FirstOrDefault();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> DebugComTypeNames(object o)
|
||||||
|
{
|
||||||
|
foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
foreach (Type type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (type.IsInstanceOfType(o))
|
||||||
|
yield return type.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,17 @@ namespace Acacia.Utils
|
|||||||
return RegToString(o);
|
return RegToString(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove, also above
|
|
||||||
public static string GetValueString(string keyPath, string valueName, string defaultValue)
|
public static string GetValueString(string keyPath, string valueName, string defaultValue)
|
||||||
{
|
{
|
||||||
object o = Registry.GetValue(keyPath, valueName, defaultValue);
|
object o = Registry.GetValue(keyPath, valueName, defaultValue);
|
||||||
return RegToString(o);
|
return RegToString(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetValueString(string keyPath, string valueName, string value)
|
||||||
|
{
|
||||||
|
Registry.SetValue(keyPath, valueName, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static string RegToString(object o)
|
public static string RegToString(object o)
|
||||||
{
|
{
|
||||||
if (o is byte[])
|
if (o is byte[])
|
||||||
|
@ -19,6 +19,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
@ -105,5 +106,25 @@ namespace Acacia.Utils
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Formatting / Replacement
|
||||||
|
|
||||||
|
public static string ReplacePercentStrings(this string s, Dictionary<string, string> replacements)
|
||||||
|
{
|
||||||
|
return Regex.Replace(s, @"%(\w+)%", (m) =>
|
||||||
|
{
|
||||||
|
string replacement;
|
||||||
|
var key = m.Groups[1].Value;
|
||||||
|
if (replacements.TryGetValue(key, out replacement))
|
||||||
|
{
|
||||||
|
return Convert.ToString(replacement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m.Groups[0].Value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Type-specific parsing
|
// Type-specific parsing
|
||||||
TypeHandler type = LookupType(part);
|
TypeHandler type = LookupType(part, expectedType);
|
||||||
return type.Deserialize(part, expectedType);
|
return type.Deserialize(part, expectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,24 +109,43 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
|
|
||||||
public object Deserialize(XmlNode node, Type expectedType)
|
public object Deserialize(XmlNode node, Type expectedType)
|
||||||
{
|
{
|
||||||
if (expectedType != null && _baseType != null)
|
|
||||||
{
|
|
||||||
// Check if the expected type matches the type
|
|
||||||
if (!_baseType.IsAssignableFrom(expectedType))
|
|
||||||
throw new InvalidOperationException("Expected " + expectedType + ", found " + _baseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
object value = DeserializeContents(node, expectedType);
|
object value = DeserializeContents(node, expectedType);
|
||||||
|
|
||||||
if (expectedType != null)
|
if (expectedType != null)
|
||||||
{
|
{
|
||||||
// Check if it's the expected type
|
// Try to convert it to the expected type
|
||||||
return expectedType.Cast(value);
|
return SoapConvert(expectedType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected object SoapConvert(Type type, object value)
|
||||||
|
{
|
||||||
|
// Check if any conversion is needed
|
||||||
|
if (value != null && type.IsAssignableFrom(value.GetType()))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// Try Soap conversion
|
||||||
|
if (typeof(ISoapSerializable<>).IsGenericAssignableFrom(type))
|
||||||
|
{
|
||||||
|
// Get the serialization type
|
||||||
|
Type serializationType = type.GetGenericArguments(typeof(ISoapSerializable<>))[0];
|
||||||
|
if (serializationType.IsAssignableFrom(value.GetType()))
|
||||||
|
{
|
||||||
|
// Create the instance
|
||||||
|
return Activator.CreateInstance(type, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or standard conversions
|
||||||
|
return type.Convert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract protected object DeserializeContents(XmlNode node, Type expectedType);
|
abstract protected object DeserializeContents(XmlNode node, Type expectedType);
|
||||||
|
|
||||||
abstract public void Serialize(string name, object value, StringBuilder s);
|
abstract public void Serialize(string name, object value, StringBuilder s);
|
||||||
@ -150,7 +169,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
}
|
}
|
||||||
private class TypeHandlerInt : TypeHandler
|
private class TypeHandlerInt : TypeHandler
|
||||||
{
|
{
|
||||||
public TypeHandlerInt() : base(SoapConstants.XMLNS_XSD, "int", typeof(int)) { }
|
public TypeHandlerInt() : base(SoapConstants.XMLNS_XSD, "int", typeof(long)) { }
|
||||||
|
|
||||||
public override void Serialize(string name, object value, StringBuilder s)
|
public override void Serialize(string name, object value, StringBuilder s)
|
||||||
{
|
{
|
||||||
@ -159,7 +178,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
|
|
||||||
protected override object DeserializeContents(XmlNode node, Type expectedType)
|
protected override object DeserializeContents(XmlNode node, Type expectedType)
|
||||||
{
|
{
|
||||||
return int.Parse(node.InnerText);
|
return long.Parse(node.InnerText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +249,10 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
{
|
{
|
||||||
// Determine if the expected type is an ISoapSerializable
|
// Determine if the expected type is an ISoapSerializable
|
||||||
if (!typeof(ISoapSerializable<>).IsGenericAssignableFrom(expectedType))
|
if (!typeof(ISoapSerializable<>).IsGenericAssignableFrom(expectedType))
|
||||||
throw new InvalidOperationException("Cannot parse type " + expectedType);
|
{
|
||||||
|
// Nope, try simple assignment
|
||||||
|
return DeserializeContentsRaw(node, expectedType);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the serialization type
|
// Get the serialization type
|
||||||
Type serializationType = expectedType.GetGenericArguments(typeof(ISoapSerializable<>))[0];
|
Type serializationType = expectedType.GetGenericArguments(typeof(ISoapSerializable<>))[0];
|
||||||
@ -243,6 +265,32 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
return CreateCustomInstance(values, serializationType, expectedType);
|
return CreateCustomInstance(values, serializationType, expectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object DeserializeContentsRaw(XmlNode node, Type expectedType)
|
||||||
|
{
|
||||||
|
// TODO: better error on failure
|
||||||
|
// Get the values as a dictionary
|
||||||
|
Dictionary<string, object> values = new Dictionary<string, object>();
|
||||||
|
DeserializeMembers(node, expectedType, values);
|
||||||
|
|
||||||
|
// And assign them to a new instance
|
||||||
|
return DeserializeContentsRaw(values, expectedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DeserializeContentsRaw(Dictionary<string, object> node, Type serializationType)
|
||||||
|
{
|
||||||
|
object instance = Activator.CreateInstance(serializationType);
|
||||||
|
foreach (FieldInfo field in serializationType.GetFields())
|
||||||
|
{
|
||||||
|
object value = null;
|
||||||
|
if (node.TryGetValue(field.Name.ToLower(), out value))
|
||||||
|
{
|
||||||
|
value = SoapConvert(field.FieldType, value);
|
||||||
|
field.SetValue(instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
abstract protected void DeserializeMembers(XmlNode node, Type serializationType, Dictionary<string, object> values);
|
abstract protected void DeserializeMembers(XmlNode node, Type serializationType, Dictionary<string, object> values);
|
||||||
|
|
||||||
private object CreateCustomInstance(Dictionary<string, object> node, Type serializationType, Type finalType)
|
private object CreateCustomInstance(Dictionary<string, object> node, Type serializationType, Type finalType)
|
||||||
@ -255,47 +303,13 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Initialise the serialization type
|
// Initialise the serialization type
|
||||||
instance = Activator.CreateInstance(serializationType);
|
instance = DeserializeContentsRaw(node, serializationType);
|
||||||
foreach (FieldInfo field in serializationType.GetFields())
|
|
||||||
{
|
|
||||||
object value = null;
|
|
||||||
if (node.TryGetValue(field.Name.ToLower(), out value))
|
|
||||||
{
|
|
||||||
value = SoapConvert(field.FieldType, value);
|
|
||||||
field.SetValue(instance, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the final type
|
// Return the final type
|
||||||
return Activator.CreateInstance(finalType, instance);
|
return Activator.CreateInstance(finalType, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private object SoapConvert(Type type, object value)
|
|
||||||
{
|
|
||||||
// Check if any conversion is needed
|
|
||||||
if (value != null && type.IsAssignableFrom(value.GetType()))
|
|
||||||
return value;
|
|
||||||
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
// Try Soap conversion
|
|
||||||
if (typeof(ISoapSerializable<>).IsGenericAssignableFrom(type))
|
|
||||||
{
|
|
||||||
// Get the serialization type
|
|
||||||
Type serializationType = type.GetGenericArguments(typeof(ISoapSerializable<>))[0];
|
|
||||||
if (serializationType.IsAssignableFrom(value.GetType()))
|
|
||||||
{
|
|
||||||
// Create the instance
|
|
||||||
return Activator.CreateInstance(type, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or standard conversions
|
|
||||||
return type.Convert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Serialize(string name, object value, StringBuilder s)
|
public override void Serialize(string name, object value, StringBuilder s)
|
||||||
{
|
{
|
||||||
Dictionary<string, object> dict;
|
Dictionary<string, object> dict;
|
||||||
@ -319,6 +333,18 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void SerializeMembers(string name, Dictionary<string, object> fields, StringBuilder s);
|
protected abstract void SerializeMembers(string name, Dictionary<string, object> fields, StringBuilder s);
|
||||||
|
|
||||||
|
protected virtual Type DetermineChildType(Type type, string field)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
FieldInfo prop = type.GetField(field);
|
||||||
|
if (prop == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return prop.FieldType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TypeHandlerStruct : TypeHandlerObject
|
private class TypeHandlerStruct : TypeHandlerObject
|
||||||
@ -333,7 +359,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
foreach (XmlNode child in node.ChildNodes)
|
foreach (XmlNode child in node.ChildNodes)
|
||||||
{
|
{
|
||||||
string key = child.Name.ToLower();
|
string key = child.Name.ToLower();
|
||||||
object value = DeserializeNode(child, null);
|
object value = DeserializeNode(child, DetermineChildType(expectedType, key));
|
||||||
dict.Add(key, value);
|
dict.Add(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,9 +371,9 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class TypeHandlerMap : TypeHandlerObject
|
private class TypeHandlerObjectMap : TypeHandlerObject
|
||||||
{
|
{
|
||||||
public TypeHandlerMap() : base(SoapConstants.XMLNS_APACHE, "Map")
|
public TypeHandlerObjectMap() : base(SoapConstants.XMLNS_APACHE, "Map")
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -357,7 +383,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
foreach (XmlNode child in node.ChildNodes)
|
foreach (XmlNode child in node.ChildNodes)
|
||||||
{
|
{
|
||||||
string key = (string)DeserializeNode(child.SelectSingleNode("key"), typeof(string));
|
string key = (string)DeserializeNode(child.SelectSingleNode("key"), typeof(string));
|
||||||
object value = DeserializeNode(child.SelectSingleNode("value"), null);
|
object value = DeserializeNode(child.SelectSingleNode("value"), DetermineChildType(expectedType, key));
|
||||||
dict.Add(key, value);
|
dict.Add(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,10 +403,38 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Map
|
||||||
|
|
||||||
|
private class TypeHandlerMap<KeyType,ValueType> : TypeHandler
|
||||||
|
{
|
||||||
|
public TypeHandlerMap() : base(SoapConstants.XMLNS_SOAP_ENC, "Array", typeof(System.Collections.ICollection)) { }
|
||||||
|
|
||||||
|
protected override object DeserializeContents(XmlNode node, Type expectedType)
|
||||||
|
{
|
||||||
|
Dictionary<KeyType, ValueType> map = new Dictionary<KeyType, ValueType>();
|
||||||
|
|
||||||
|
foreach (XmlNode child in node.ChildNodes)
|
||||||
|
{
|
||||||
|
KeyType key = (KeyType)DeserializeNode(child.SelectSingleNode("key"), typeof(KeyType));
|
||||||
|
ValueType value = (ValueType)DeserializeNode(child.SelectSingleNode("value"), typeof(ValueType));
|
||||||
|
map.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Serialize(string name, object value, StringBuilder s)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
private readonly static Dictionary<string, TypeHandler> TYPES_BY_FULL_NAME = new Dictionary<string, TypeHandler>();
|
private readonly static Dictionary<string, TypeHandler> TYPES_BY_FULL_NAME = new Dictionary<string, TypeHandler>();
|
||||||
private readonly static Dictionary<Type, TypeHandler> TYPES_BY_TYPE = new Dictionary<Type, TypeHandler>();
|
private readonly static Dictionary<Type, TypeHandler> TYPES_BY_TYPE = new Dictionary<Type, TypeHandler>();
|
||||||
private readonly static TypeHandler TYPE_HANDLER_OBJECT = new TypeHandlerMap();
|
private readonly static TypeHandler TYPE_HANDLER_OBJECT = new TypeHandlerObjectMap();
|
||||||
|
|
||||||
static SoapSerializer()
|
static SoapSerializer()
|
||||||
{
|
{
|
||||||
@ -415,8 +469,14 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TypeHandler LookupType(XmlNode part)
|
private static TypeHandler LookupType(XmlNode part, Type expectedType)
|
||||||
{
|
{
|
||||||
|
if (expectedType != null && typeof(IDictionary<,>).IsGenericAssignableFrom(expectedType))
|
||||||
|
{
|
||||||
|
Type bound = typeof(TypeHandlerMap<,>).MakeGenericType(expectedType.GetGenericArguments(typeof(IDictionary<,>)));
|
||||||
|
return (TypeHandler)Activator.CreateInstance(bound);
|
||||||
|
}
|
||||||
|
|
||||||
XmlAttribute typeAttr = part.Attributes["type", SoapConstants.XMLNS_XSI];
|
XmlAttribute typeAttr = part.Attributes["type", SoapConstants.XMLNS_XSI];
|
||||||
if (typeAttr == null)
|
if (typeAttr == null)
|
||||||
throw new Exception("Missing type");
|
throw new Exception("Missing type");
|
||||||
@ -431,6 +491,7 @@ namespace Acacia.ZPush.Connect.Soap
|
|||||||
TypeHandler type;
|
TypeHandler type;
|
||||||
if (!TYPES_BY_FULL_NAME.TryGetValue(fullName, out type))
|
if (!TYPES_BY_FULL_NAME.TryGetValue(fullName, out type))
|
||||||
throw new Exception("Unknown type: " + fullName);
|
throw new Exception("Unknown type: " + fullName);
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,6 +204,12 @@ namespace Acacia.ZPush.Connect
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string SignaturesHash
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetStringHeader(HttpResponseMessage response, string name)
|
private string GetStringHeader(HttpResponseMessage response, string name)
|
||||||
{
|
{
|
||||||
IEnumerable<string> values;
|
IEnumerable<string> values;
|
||||||
@ -227,6 +233,7 @@ namespace Acacia.ZPush.Connect
|
|||||||
|
|
||||||
Capabilities = ZPushCapabilities.Parse(GetStringHeader(response, Constants.ZPUSH_HEADER_CAPABILITIES));
|
Capabilities = ZPushCapabilities.Parse(GetStringHeader(response, Constants.ZPUSH_HEADER_CAPABILITIES));
|
||||||
ZPushVersion = GetStringHeader(response, Constants.ZPUSH_HEADER_VERSION);
|
ZPushVersion = GetStringHeader(response, Constants.ZPUSH_HEADER_VERSION);
|
||||||
|
SignaturesHash = GetStringHeader(response, Constants.ZPUSH_HEADER_SIGNATURES_HASH);
|
||||||
|
|
||||||
// Check for success
|
// Check for success
|
||||||
Success = response.IsSuccessStatusCode;
|
Success = response.IsSuccessStatusCode;
|
||||||
|
@ -120,6 +120,13 @@ namespace Acacia.ZPush
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ServerSignaturesHash
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void LinkedGABFolder(IFolder folder)
|
public void LinkedGABFolder(IFolder folder)
|
||||||
{
|
{
|
||||||
GABFolderLinked = folder.EntryID;
|
GABFolderLinked = folder.EntryID;
|
||||||
@ -128,13 +135,14 @@ namespace Acacia.ZPush
|
|||||||
internal void OnConfirmationResponse(ZPushConnection.Response response)
|
internal void OnConfirmationResponse(ZPushConnection.Response response)
|
||||||
{
|
{
|
||||||
Capabilities = response.Capabilities;
|
Capabilities = response.Capabilities;
|
||||||
|
// TODO: move these properties to the features? Though it's nice to have them here for the debug dialog
|
||||||
GABFolder = response.GABName;
|
GABFolder = response.GABName;
|
||||||
ZPushVersion = response.ZPushVersion;
|
ZPushVersion = response.ZPushVersion;
|
||||||
|
ServerSignaturesHash = response.SignaturesHash;
|
||||||
Confirmed = Capabilities == null ? ConfirmationType.IsNotZPush : ConfirmationType.IsZPush;
|
Confirmed = Capabilities == null ? ConfirmationType.IsNotZPush : ConfirmationType.IsZPush;
|
||||||
Logger.Instance.Info(this, "ZPush confirmation: {0} -> {1}, {2}", Confirmed, Capabilities, GABFolder);
|
Logger.Instance.Info(this, "ZPush confirmation: {0} -> {1}, {2}", Confirmed, Capabilities, GABFolder);
|
||||||
|
|
||||||
if (_confirmedChanged != null)
|
_confirmedChanged?.Invoke(this);
|
||||||
_confirmedChanged(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
Loading…
Reference in New Issue
Block a user