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 Include="Features\SendAs\FeatureSendAs.cs" />
|
||||
<Compile Include="Features\SharedFolders\FolderTreeNode.cs" />
|
||||
<Compile Include="Features\Signatures\FeatureSignatures.cs" />
|
||||
<Compile Include="GlobalOptions.cs" />
|
||||
<Compile Include="Logging.cs" />
|
||||
<Compile Include="Native\IOleWindow.cs" />
|
||||
<Compile Include="Native\IOlkAccount.cs" />
|
||||
<Compile Include="Native\MAPI\Binary.cs" />
|
||||
<Compile Include="Native\MAPI\IMAPIContainer.cs" />
|
||||
<Compile Include="Native\MAPI\IMAPIFolder.cs" />
|
||||
<Compile Include="Native\MAPI\IMAPIProp.cs" />
|
||||
<Compile Include="Native\MAPI\Property.cs" />
|
||||
<Compile Include="Native\MAPI\Restriction.cs" />
|
||||
<Compile Include="Native\NativeEncoder.cs" />
|
||||
<Compile Include="OutlookConstants.cs" />
|
||||
<Compile Include="SearchQuery.cs" />
|
||||
<Compile Include="Stubs\Enums.cs" />
|
||||
@ -299,6 +300,8 @@
|
||||
<Compile Include="Stubs\IOutlookWindow.cs" />
|
||||
<Compile Include="Stubs\IPicture.cs" />
|
||||
<Compile Include="Stubs\IRecipient.cs" />
|
||||
<Compile Include="Stubs\ISignature.cs" />
|
||||
<Compile Include="Stubs\ISignatures.cs" />
|
||||
<Compile Include="Stubs\IStores.cs" />
|
||||
<Compile Include="Stubs\ISyncObject.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\AccountWrapper.cs" />
|
||||
@ -312,6 +315,8 @@
|
||||
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\PictureWrapper.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\SyncObjectWrapper.cs" />
|
||||
<Compile Include="Stubs\Wrappers.cs" />
|
||||
|
@ -74,6 +74,7 @@ namespace Acacia
|
||||
public const string ZPUSH_HEADER_CLIENT_CAPABILITIES = "X-Push-Plugin-Capabilities";
|
||||
public const string ZPUSH_HEADER_PLUGIN = "X-Push-Plugin";
|
||||
public const string ZPUSH_HEADER_VERSION = "X-Z-Push-Version";
|
||||
public const string ZPUSH_HEADER_SIGNATURES_HASH = "X-Push-Signatures-Hash";
|
||||
|
||||
|
||||
#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
|
||||
public static readonly BoolOption ENABLED = new BoolOption("", true);
|
||||
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
|
||||
// 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
|
||||
@ -113,12 +112,12 @@ namespace Acacia.Features.SecondaryContacts
|
||||
// Stage 2
|
||||
|
||||
// Patch the name
|
||||
Logger.Instance.Trace(this, "Patching name");
|
||||
folder.Name = strippedName;
|
||||
Logger.Instance.Trace(this, "Patching name");
|
||||
folder.Name = strippedName;
|
||||
|
||||
// Show it
|
||||
folder.AttrHidden = false;
|
||||
Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
|
||||
// Show it
|
||||
folder.AttrHidden = false;
|
||||
Logger.Instance.Debug(this, "Shown secondary contacts folder: {0}", strippedName);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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_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";
|
||||
|
||||
#endregion
|
||||
|
@ -49,5 +49,23 @@ namespace Acacia.Stubs
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
/// 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/>.
|
||||
///
|
||||
/// 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.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
||||
|
||||
namespace Acacia.Stubs.OutlookWrappers
|
||||
{
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
class AccountWrapper : DisposableWrapper, IAccount, LogContext
|
||||
class AccountWrapper : ComWrapper<NSOutlook.Application>, IAccount, LogContext
|
||||
{
|
||||
private readonly string _regPath;
|
||||
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._store = store;
|
||||
@ -44,6 +50,7 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
protected override void DoRelease()
|
||||
{
|
||||
_store.Dispose();
|
||||
base.DoRelease();
|
||||
}
|
||||
|
||||
[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
|
||||
}
|
||||
}
|
||||
|
@ -25,17 +25,19 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
||||
|
||||
namespace Acacia.Stubs.OutlookWrappers
|
||||
{
|
||||
public class AddInWrapper : IAddIn
|
||||
class AddInWrapper : IAddIn
|
||||
{
|
||||
private readonly NSOutlook.Application _app;
|
||||
private readonly ThisAddIn _thisAddIn;
|
||||
private readonly StoresWrapper _stores;
|
||||
private readonly SynchronizationContext _sync;
|
||||
|
||||
public AddInWrapper(ThisAddIn thisAddIn)
|
||||
{
|
||||
@ -51,6 +53,38 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
{
|
||||
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)
|
||||
|
@ -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)
|
||||
return null;
|
||||
AccountWrapper account = new AccountWrapper(baseKey.Name, store);
|
||||
AccountWrapper account = new AccountWrapper(_item.Application, baseKey.Name, store);
|
||||
Register(account);
|
||||
return account;
|
||||
}
|
||||
|
@ -101,13 +101,6 @@ namespace Acacia
|
||||
int lcid = Application.LanguageSettings.get_LanguageID(Microsoft.Office.Core.MsoAppLanguageID.msoLanguageIDUI);
|
||||
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
|
||||
Watcher = new ZPushWatcher(Instance);
|
||||
OutlookUI.Watcher = Watcher;
|
||||
|
@ -43,8 +43,13 @@ namespace Acacia.Utils
|
||||
public static object Convert(this Type type, object value)
|
||||
{
|
||||
// For value types, null becomes a default instance
|
||||
if (value == null && type.IsValueType)
|
||||
return Activator.CreateInstance(type);
|
||||
if (value == null)
|
||||
{
|
||||
if (type.IsValueType)
|
||||
return Activator.CreateInstance(type);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we need a conversion
|
||||
if (!type.IsAssignableFrom(value.GetType()))
|
||||
@ -89,11 +94,27 @@ namespace Acacia.Utils
|
||||
|
||||
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();
|
||||
return bases.Select(x =>
|
||||
(x.IsGenericType && x.GetGenericTypeDefinition() == _base) ? x.GetGenericArguments() : null
|
||||
).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);
|
||||
}
|
||||
|
||||
// TODO: remove, also above
|
||||
public static string GetValueString(string keyPath, string valueName, string defaultValue)
|
||||
{
|
||||
object o = Registry.GetValue(keyPath, valueName, defaultValue);
|
||||
return RegToString(o);
|
||||
}
|
||||
|
||||
public static void SetValueString(string keyPath, string valueName, string value)
|
||||
{
|
||||
Registry.SetValue(keyPath, valueName, value);
|
||||
}
|
||||
|
||||
public static string RegToString(object o)
|
||||
{
|
||||
if (o is byte[])
|
||||
|
@ -19,6 +19,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
@ -105,5 +106,25 @@ namespace Acacia.Utils
|
||||
|
||||
#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;
|
||||
|
||||
// Type-specific parsing
|
||||
TypeHandler type = LookupType(part);
|
||||
TypeHandler type = LookupType(part, expectedType);
|
||||
return type.Deserialize(part, expectedType);
|
||||
}
|
||||
|
||||
@ -109,24 +109,43 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
|
||||
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);
|
||||
|
||||
if (expectedType != null)
|
||||
{
|
||||
// Check if it's the expected type
|
||||
return expectedType.Cast(value);
|
||||
// Try to convert it to the expected type
|
||||
return SoapConvert(expectedType, 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 public void Serialize(string name, object value, StringBuilder s);
|
||||
@ -150,7 +169,7 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
}
|
||||
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)
|
||||
{
|
||||
@ -159,7 +178,7 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
|
||||
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
|
||||
if (!typeof(ISoapSerializable<>).IsGenericAssignableFrom(expectedType))
|
||||
throw new InvalidOperationException("Cannot parse type " + expectedType);
|
||||
{
|
||||
// Nope, try simple assignment
|
||||
return DeserializeContentsRaw(node, expectedType);
|
||||
}
|
||||
|
||||
// Get the serialization type
|
||||
Type serializationType = expectedType.GetGenericArguments(typeof(ISoapSerializable<>))[0];
|
||||
@ -243,6 +265,32 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
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);
|
||||
|
||||
private object CreateCustomInstance(Dictionary<string, object> node, Type serializationType, Type finalType)
|
||||
@ -255,47 +303,13 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
else
|
||||
{
|
||||
// Initialise the serialization type
|
||||
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);
|
||||
}
|
||||
}
|
||||
instance = DeserializeContentsRaw(node, serializationType);
|
||||
}
|
||||
|
||||
// Return the final type
|
||||
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)
|
||||
{
|
||||
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 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
|
||||
@ -333,7 +359,7 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
foreach (XmlNode child in node.ChildNodes)
|
||||
{
|
||||
string key = child.Name.ToLower();
|
||||
object value = DeserializeNode(child, null);
|
||||
object value = DeserializeNode(child, DetermineChildType(expectedType, key));
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -377,10 +403,38 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
|
||||
#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<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()
|
||||
{
|
||||
@ -415,8 +469,14 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
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];
|
||||
if (typeAttr == null)
|
||||
throw new Exception("Missing type");
|
||||
@ -431,6 +491,7 @@ namespace Acacia.ZPush.Connect.Soap
|
||||
TypeHandler type;
|
||||
if (!TYPES_BY_FULL_NAME.TryGetValue(fullName, out type))
|
||||
throw new Exception("Unknown type: " + fullName);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
|
@ -204,6 +204,12 @@ namespace Acacia.ZPush.Connect
|
||||
private set;
|
||||
}
|
||||
|
||||
public string SignaturesHash
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private string GetStringHeader(HttpResponseMessage response, string name)
|
||||
{
|
||||
IEnumerable<string> values;
|
||||
@ -227,6 +233,7 @@ namespace Acacia.ZPush.Connect
|
||||
|
||||
Capabilities = ZPushCapabilities.Parse(GetStringHeader(response, Constants.ZPUSH_HEADER_CAPABILITIES));
|
||||
ZPushVersion = GetStringHeader(response, Constants.ZPUSH_HEADER_VERSION);
|
||||
SignaturesHash = GetStringHeader(response, Constants.ZPUSH_HEADER_SIGNATURES_HASH);
|
||||
|
||||
// Check for success
|
||||
Success = response.IsSuccessStatusCode;
|
||||
|
@ -120,6 +120,13 @@ namespace Acacia.ZPush
|
||||
private set;
|
||||
}
|
||||
|
||||
public string ServerSignaturesHash
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
public void LinkedGABFolder(IFolder folder)
|
||||
{
|
||||
GABFolderLinked = folder.EntryID;
|
||||
@ -128,13 +135,14 @@ namespace Acacia.ZPush
|
||||
internal void OnConfirmationResponse(ZPushConnection.Response response)
|
||||
{
|
||||
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;
|
||||
ZPushVersion = response.ZPushVersion;
|
||||
ServerSignaturesHash = response.SignaturesHash;
|
||||
Confirmed = Capabilities == null ? ConfirmationType.IsNotZPush : ConfirmationType.IsZPush;
|
||||
Logger.Instance.Info(this, "ZPush confirmation: {0} -> {1}, {2}", Confirmed, Capabilities, GABFolder);
|
||||
|
||||
if (_confirmedChanged != null)
|
||||
_confirmedChanged(this);
|
||||
_confirmedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
Loading…
Reference in New Issue
Block a user