mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 13:37:40 +02:00
Merge branch 'master' of https://stash.kopano.io/scm/koe/kopano_ol_extension_source
This commit is contained in:
commit
effdc32f9e
@ -241,6 +241,9 @@
|
||||
<Compile Include="Controls\KUITask.cs" />
|
||||
<Compile Include="Controls\KUIUtil.cs" />
|
||||
<Compile Include="DebugOptions.cs" />
|
||||
<Compile Include="Features\BCC\FeatureBCC.cs" />
|
||||
<Compile Include="Features\FreeBusy\FreeBusyServlet.cs" />
|
||||
<Compile Include="Features\FreeBusy\Servlet.cs" />
|
||||
<Compile Include="Features\SecondaryContacts\FeatureSecondaryContacts.cs" />
|
||||
<Compile Include="Features\DebugSupport\AboutDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
@ -309,6 +312,7 @@
|
||||
<Compile Include="Stubs\IOutlookWindow.cs" />
|
||||
<Compile Include="Stubs\IPicture.cs" />
|
||||
<Compile Include="Stubs\IRecipient.cs" />
|
||||
<Compile Include="Stubs\IRecipients.cs" />
|
||||
<Compile Include="Stubs\ISignature.cs" />
|
||||
<Compile Include="Stubs\ISignatures.cs" />
|
||||
<Compile Include="Stubs\IStores.cs" />
|
||||
@ -323,6 +327,7 @@
|
||||
<Compile Include="Stubs\OutlookWrappers\ItemsWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\OutlookItemWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\PictureWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\RecipientsWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\RecipientWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\SignaturesWrapper.cs" />
|
||||
<Compile Include="Stubs\OutlookWrappers\SignatureWrapper.cs" />
|
||||
|
@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Acacia.Stubs;
|
||||
using static Acacia.DebugOptions;
|
||||
using Acacia.ZPush;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net.Mail;
|
||||
using Acacia.Utils;
|
||||
|
||||
namespace Acacia.Features.BCC
|
||||
{
|
||||
[AcaciaOption("Displays the BCC field on sent items.")]
|
||||
public class FeatureBCC : Feature
|
||||
{
|
||||
private readonly FolderRegistration _folderRegistration;
|
||||
|
||||
public FeatureBCC()
|
||||
{
|
||||
_folderRegistration = new FolderRegistrationDefault(this, DefaultFolder.SentMail);
|
||||
}
|
||||
|
||||
public override void Startup()
|
||||
{
|
||||
// TODO: this is very similar to ReplyFlags
|
||||
|
||||
if (UpdateEvents)
|
||||
{
|
||||
// Watch the sent mail folder
|
||||
Watcher.WatchFolder(_folderRegistration,
|
||||
(folder) => Watcher.WatchItems<IMailItem>(folder, CheckBCC, false)
|
||||
);
|
||||
}
|
||||
|
||||
if (ReadEvent)
|
||||
{
|
||||
// As a fallback, add an event handler to update the message when displaying it
|
||||
if (MailEvents != null)
|
||||
{
|
||||
MailEvents.Read += (mail) =>
|
||||
{
|
||||
// Check we're in the SentMail folder
|
||||
using (IFolder folder = mail.Parent)
|
||||
{
|
||||
if (_folderRegistration.IsApplicable(folder))
|
||||
CheckBCC(mail);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex RE_BCC = new Regex("(?m)^Bcc:[ \t]*(([^\r\n]|\r\n[ \t]+)*)\r\n");
|
||||
private static readonly Regex RE_BCC_NAME_EMAIL = new Regex("([^<>]*)[ \t]*<(.*)>");
|
||||
|
||||
private void CheckBCC(IMailItem mail)
|
||||
{
|
||||
// If the item already has a BCC, assume it's correct
|
||||
if (!string.IsNullOrEmpty(mail.BCC))
|
||||
return;
|
||||
|
||||
// Grab the transport headers
|
||||
string headers = (string)mail.GetProperty(OutlookConstants.PR_TRANSPORT_MESSAGE_HEADERS);
|
||||
if (string.IsNullOrEmpty(headers))
|
||||
return;
|
||||
|
||||
// Check if there's a bcc header
|
||||
Match match = RE_BCC.Match(headers);
|
||||
if (match.Groups.Count < 2)
|
||||
return;
|
||||
string bcc = match.Groups[1].Value;
|
||||
if (string.IsNullOrEmpty(bcc))
|
||||
return;
|
||||
|
||||
// Add the recipient
|
||||
string decoded = bcc.DecodeQuotedPrintable();
|
||||
try
|
||||
{
|
||||
using (IRecipients recipients = mail.Recipients)
|
||||
{
|
||||
using (IRecipient recip = CreateRecipient(recipients, decoded))
|
||||
{
|
||||
recip.Type = MailRecipientType.BCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mail.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private IRecipient CreateRecipient(IRecipients recipients, string decoded)
|
||||
{
|
||||
// First try to resolve directly
|
||||
IRecipient recipient = recipients.Add(decoded);
|
||||
if (recipient.Resolve())
|
||||
return recipient;
|
||||
|
||||
// Nope, remove and create with email
|
||||
recipient.Dispose();
|
||||
recipient = null;
|
||||
recipients.Remove(recipients.Count - 1);
|
||||
|
||||
string displayName;
|
||||
string email = ParseBCCHeader(decoded, out displayName);
|
||||
|
||||
// TODO: is it possible to use the display name?
|
||||
recipient = recipients.Add(email);
|
||||
recipient.Resolve();
|
||||
return recipient;
|
||||
}
|
||||
|
||||
// TODO: this is probably generally useful
|
||||
private string ParseBCCHeader(string bcc, out string displayName)
|
||||
{
|
||||
Match match = RE_BCC_NAME_EMAIL.Match(bcc);
|
||||
if (match.Groups.Count > 1)
|
||||
{
|
||||
displayName = match.Groups[1].Value;
|
||||
return match.Groups[2].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayName = null;
|
||||
return bcc;
|
||||
}
|
||||
}
|
||||
|
||||
#region Debug options
|
||||
|
||||
[AcaciaOption("Enables or disables the handling of read events on mail items. If this is enabled, " +
|
||||
"the BCC field is checked. This is almost guaranteed to work, but has the downside " +
|
||||
"of only setting the BCC field when an email is opened.")]
|
||||
public bool ReadEvent
|
||||
{
|
||||
get { return GetOption(OPTION_READ_EVENT); }
|
||||
set { SetOption(OPTION_READ_EVENT, value); }
|
||||
}
|
||||
private static readonly BoolOption OPTION_READ_EVENT = new BoolOption("ReadEvents", true);
|
||||
|
||||
[AcaciaOption("Enables or disables the handling of update events to mail items. When a mail item is " +
|
||||
"updated, it is checked to see if the BCC field needs to be set. This is the main " +
|
||||
"mechanism for setting the BCC field.")]
|
||||
public bool UpdateEvents
|
||||
{
|
||||
get { return GetOption(OPTION_UPDATE_EVENTS); }
|
||||
set { SetOption(OPTION_UPDATE_EVENTS, value); }
|
||||
}
|
||||
private static readonly BoolOption OPTION_UPDATE_EVENTS = new BoolOption("FolderEvents", true);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ namespace Acacia.Features
|
||||
public static readonly Type[] FEATURES =
|
||||
{
|
||||
typeof(ReplyFlags.FeatureReplyFlags),
|
||||
typeof(BCC.FeatureBCC),
|
||||
typeof(OutOfOffice.FeatureOutOfOffice),
|
||||
typeof(SharedFolders.FeatureSharedFolders),
|
||||
typeof(WebApp.FeatureWebApp),
|
||||
|
@ -111,9 +111,9 @@ namespace Acacia.Features.FreeBusy
|
||||
|
||||
private const string REG_KEY = @"Options\Calendar\Internet Free/Busy";
|
||||
private const string REG_VALUE = @"Read URL";
|
||||
internal const string URL_IDENTIFIER = "/zpush/";
|
||||
internal const string URL_IDENTIFIER = "zpush";
|
||||
private const int DEFAULT_PORT = 18632;
|
||||
private const string URL_PREFIX = @"http://127.0.0.1:{0}" + URL_IDENTIFIER;
|
||||
private const string URL_PREFIX = @"http://127.0.0.1:{0}/" + URL_IDENTIFIER + "/";
|
||||
private const string URL = URL_PREFIX + "%NAME%@%SERVER%";
|
||||
|
||||
private void Worker()
|
||||
@ -125,8 +125,9 @@ namespace Acacia.Features.FreeBusy
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
// Set only if empty or already our URL
|
||||
string oldURL = key.GetValueString(REG_VALUE);
|
||||
if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains(URL_IDENTIFIER))
|
||||
if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains("/" + URL_IDENTIFIER + "/"))
|
||||
key.SetValue(REG_VALUE, string.Format(URL, Port));
|
||||
}
|
||||
}
|
||||
|
@ -32,15 +32,15 @@ namespace Acacia.Features.FreeBusy
|
||||
{
|
||||
public class FreeBusyServer
|
||||
{
|
||||
private readonly FeatureFreeBusy _freeBusy;
|
||||
private readonly int _port;
|
||||
private readonly Regex _httpRequest;
|
||||
|
||||
public delegate Servlet ServletFactory();
|
||||
private readonly Dictionary<string, ServletFactory> _servlets = new Dictionary<string, ServletFactory>();
|
||||
|
||||
public FreeBusyServer(FeatureFreeBusy freeBusy)
|
||||
{
|
||||
this._freeBusy = freeBusy;
|
||||
this._port = freeBusy.Port;
|
||||
this._httpRequest = new Regex(@"^GET " + FeatureFreeBusy.URL_IDENTIFIER + @"([^ ]+) HTTP/(\d.\d)$");
|
||||
this._httpRequest = new Regex(@"^GET /([^/]+)/([^ ]*) HTTP/(\d.\d)$");
|
||||
_servlets.Add(FeatureFreeBusy.URL_IDENTIFIER, () => new FreeBusyServlet(freeBusy));
|
||||
}
|
||||
|
||||
public void HandleRequest(TcpClient client)
|
||||
@ -61,19 +61,17 @@ namespace Acacia.Features.FreeBusy
|
||||
Logger.Instance.Trace(this, "Invalid request: {0}", s);
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
string username = m.Groups[1].Value;
|
||||
Logger.Instance.Trace(this, "REQUEST: {0} -> {1}, {2}", s, m.Groups[1], m.Groups[2]);
|
||||
|
||||
// Headers
|
||||
for (;;)
|
||||
string app = m.Groups[1].Value;
|
||||
ServletFactory factory;
|
||||
if (!_servlets.TryGetValue(app, out factory))
|
||||
{
|
||||
s = reader.ReadLine();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
break;
|
||||
Logger.Instance.Trace(this, "Unknown servlet: {0} -> {1}", s, app);
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Write response
|
||||
FetchData(username, writer);
|
||||
Servlet servlet = factory();
|
||||
servlet.Init(s, m.Groups[2].Value, reader, writer);
|
||||
servlet.Process();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
@ -92,67 +90,5 @@ namespace Acacia.Features.FreeBusy
|
||||
Logger.Instance.Error(this, "Error in FreeBusy worker: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void FetchData(string username, StreamWriter output)
|
||||
{
|
||||
// Request the data from the ZPush server
|
||||
ZPushConnection connection = new ZPushConnection(_freeBusy.FindZPushAccount(username), new System.Threading.CancellationToken(false));
|
||||
|
||||
// Include yesterday in the request, outlook shows it by default
|
||||
var request = new ActiveSync.ResolveRecipientsRequest(username,
|
||||
DateTime.Today.AddDays(-1),
|
||||
DateTime.Today.AddMonths(6));
|
||||
var response = connection.Execute(request);
|
||||
|
||||
// If there is no FreeBusy data, return 404
|
||||
if (response?.FreeBusy == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
Logger.Instance.Trace(this, "Writing response");
|
||||
// Encode the response in vcard format
|
||||
output.WriteLine("HTTP/1.0 200 OK");
|
||||
output.WriteLine("Content-Type: text/vcard");
|
||||
output.WriteLine("Connection: close");
|
||||
output.WriteLine("");
|
||||
|
||||
|
||||
output.WriteLine("BEGIN:VCALENDAR");
|
||||
output.WriteLine("PRODID:-//ZPush//EN");
|
||||
output.WriteLine("VERSION:2.0");
|
||||
output.WriteLine("BEGIN:VFREEBUSY");
|
||||
output.WriteLine("ORGANIZER:" + username);
|
||||
output.WriteLine(string.Format("URL:http://127.0.0.1:{0}{1}{2}", _port, FeatureFreeBusy.URL_IDENTIFIER, username));
|
||||
output.WriteLine(string.Format("DTSTAMP:{0:" + Constants.DATE_ISO_8601 + "}", DateTime.Now));
|
||||
output.WriteLine(string.Format("DTSTART:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.StartTime));
|
||||
output.WriteLine(string.Format("DTEND:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.EndTime));
|
||||
|
||||
foreach(ActiveSync.FreeBusyData data in response.FreeBusy)
|
||||
{
|
||||
if (data.Type != ActiveSync.FreeBusyType.Free)
|
||||
{
|
||||
string freeBusy = string.Format("FREEBUSY;FBTYPE={2}:{0:" + Constants.DATE_ISO_8601 + "}/{1:" + Constants.DATE_ISO_8601 + "}",
|
||||
data.Start, data.End, MapType(data.Type));
|
||||
output.WriteLine(freeBusy);
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteLine("END:VFREEBUSY");
|
||||
output.WriteLine("END:VCALENDAR");
|
||||
}
|
||||
|
||||
private object MapType(ActiveSync.FreeBusyType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case ActiveSync.FreeBusyType.Free: return "FREE";
|
||||
case ActiveSync.FreeBusyType.Busy: return "BUSY";
|
||||
case ActiveSync.FreeBusyType.Tentative: return "BUSY-TENTATIVE";
|
||||
case ActiveSync.FreeBusyType.OutOfOffice: return "BUSY-UNAVAILABLE";
|
||||
default:
|
||||
return "BUSY";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
using Acacia.Utils;
|
||||
using Acacia.ZPush.Connect;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Acacia.Features.FreeBusy
|
||||
{
|
||||
public class FreeBusyServlet : Servlet
|
||||
{
|
||||
private readonly FeatureFreeBusy _freeBusy;
|
||||
|
||||
public FreeBusyServlet(FeatureFreeBusy freeBusy)
|
||||
{
|
||||
this._freeBusy = freeBusy;
|
||||
}
|
||||
|
||||
protected override void ProcessRequest()
|
||||
{
|
||||
string username = Url;
|
||||
// Request the data from the ZPush server
|
||||
ZPushConnection connection = new ZPushConnection(_freeBusy.FindZPushAccount(username), new System.Threading.CancellationToken(false));
|
||||
|
||||
// Include yesterday in the request, outlook shows it by default
|
||||
var request = new ActiveSync.ResolveRecipientsRequest(username,
|
||||
DateTime.Today.AddDays(-1),
|
||||
DateTime.Today.AddMonths(6));
|
||||
var response = connection.Execute(request);
|
||||
|
||||
// If there is no FreeBusy data, return 404
|
||||
if (response?.FreeBusy == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
Logger.Instance.Trace(this, "Writing response");
|
||||
// Encode the response in vcard format
|
||||
Out.WriteLine("HTTP/1.0 200 OK");
|
||||
Out.WriteLine("Content-Type: text/vcard");
|
||||
Out.WriteLine("Connection: close");
|
||||
Out.WriteLine("");
|
||||
|
||||
|
||||
Out.WriteLine("BEGIN:VCALENDAR");
|
||||
Out.WriteLine("PRODID:-//ZPush//EN");
|
||||
Out.WriteLine("VERSION:2.0");
|
||||
Out.WriteLine("BEGIN:VFREEBUSY");
|
||||
Out.WriteLine("ORGANIZER:" + username);
|
||||
Out.WriteLine(string.Format("URL:http://127.0.0.1:{0}/{1}/{2}", _freeBusy.Port, FeatureFreeBusy.URL_IDENTIFIER, username));
|
||||
Out.WriteLine(string.Format("DTSTAMP:{0:" + Constants.DATE_ISO_8601 + "}", DateTime.Now));
|
||||
Out.WriteLine(string.Format("DTSTART:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.StartTime));
|
||||
Out.WriteLine(string.Format("DTEND:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.EndTime));
|
||||
|
||||
foreach (ActiveSync.FreeBusyData data in response.FreeBusy)
|
||||
{
|
||||
if (data.Type != ActiveSync.FreeBusyType.Free)
|
||||
{
|
||||
string freeBusy = string.Format("FREEBUSY;FBTYPE={2}:{0:" + Constants.DATE_ISO_8601 + "}/{1:" + Constants.DATE_ISO_8601 + "}",
|
||||
data.Start, data.End, MapType(data.Type));
|
||||
Out.WriteLine(freeBusy);
|
||||
}
|
||||
}
|
||||
|
||||
Out.WriteLine("END:VFREEBUSY");
|
||||
Out.WriteLine("END:VCALENDAR");
|
||||
}
|
||||
|
||||
private object MapType(ActiveSync.FreeBusyType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ActiveSync.FreeBusyType.Free: return "FREE";
|
||||
case ActiveSync.FreeBusyType.Busy: return "BUSY";
|
||||
case ActiveSync.FreeBusyType.Tentative: return "BUSY-TENTATIVE";
|
||||
case ActiveSync.FreeBusyType.OutOfOffice: return "BUSY-UNAVAILABLE";
|
||||
default:
|
||||
return "BUSY";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Acacia.Features.FreeBusy
|
||||
{
|
||||
abstract public class Servlet
|
||||
{
|
||||
private string _request;
|
||||
protected string Url { get; private set; }
|
||||
protected StreamReader In { get; private set; }
|
||||
protected StreamWriter Out { get; private set; }
|
||||
|
||||
public void Init(string request, string url, StreamReader reader, StreamWriter writer)
|
||||
{
|
||||
this._request = request;
|
||||
this.Url = url;
|
||||
this.In = reader;
|
||||
this.Out = writer;
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
ProcessHeaders();
|
||||
ProcessRequest();
|
||||
}
|
||||
|
||||
virtual protected void ProcessHeaders()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
string s = In.ReadLine();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
break;
|
||||
ProcessHeader(s);
|
||||
}
|
||||
}
|
||||
|
||||
virtual protected void ProcessHeader(string s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
abstract protected void ProcessRequest();
|
||||
}
|
||||
}
|
@ -580,7 +580,6 @@ namespace Acacia.Features.GAB
|
||||
// Update the groups
|
||||
AddItemToGroups(contact, id, value, index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CreateGroup(string id, Dictionary<string, object> value, ChunkIndex index)
|
||||
|
@ -75,4 +75,13 @@ namespace Acacia.Stubs
|
||||
EAS,
|
||||
Other
|
||||
}
|
||||
|
||||
// Replacement for OlMailRecipientType
|
||||
public enum MailRecipientType
|
||||
{
|
||||
Originator = 0,
|
||||
To = 1,
|
||||
CC = 2,
|
||||
BCC = 3
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,15 @@ namespace Acacia.Stubs
|
||||
/// </summary>
|
||||
public interface IMailItem : IItem
|
||||
{
|
||||
#region Reply verbs
|
||||
|
||||
DateTime? AttrLastVerbExecutionTime { get; set; }
|
||||
int AttrLastVerbExecuted { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sender
|
||||
|
||||
string SenderEmailAddress { get; }
|
||||
string SenderName { get; }
|
||||
|
||||
@ -38,5 +44,16 @@ namespace Acacia.Stubs
|
||||
/// </summary>
|
||||
/// <param name="addressEntry">The address. The caller is responsible for disposing.</param>
|
||||
void SetSender(IAddressEntry addressEntry);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Recipients
|
||||
|
||||
string To { get; set; }
|
||||
string CC { get; set; }
|
||||
string BCC { get; set; }
|
||||
IRecipients Recipients { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,14 @@ namespace Acacia.Stubs
|
||||
{
|
||||
public interface IRecipient : IComWrapper
|
||||
{
|
||||
bool Resolve();
|
||||
bool IsResolved { get; }
|
||||
|
||||
string Name { get; }
|
||||
string Address { get; }
|
||||
|
||||
MailRecipientType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address entry. The caller is responsible for disposing it.
|
||||
/// </summary>
|
||||
|
15
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs
Normal file
15
src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Acacia.Stubs
|
||||
{
|
||||
public interface IRecipients : IComWrapper, IEnumerable<IRecipient>
|
||||
{
|
||||
int Count { get; }
|
||||
void Remove(int index);
|
||||
IRecipient Add(string name);
|
||||
}
|
||||
}
|
@ -347,8 +347,9 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
NSOutlook.Recipient recipient = com.Add(session.CreateRecipient(name));
|
||||
if (recipient == null)
|
||||
return null;
|
||||
recipient.Resolve();
|
||||
return Mapping.Wrap(com.Remove(recipient));
|
||||
IRecipient wrapped = Mapping.Wrap(com.Remove(recipient));
|
||||
wrapped.Resolve();
|
||||
return wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,8 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
|
||||
#region IMailItem implementation
|
||||
|
||||
#region Reply verbs
|
||||
|
||||
public DateTime? AttrLastVerbExecutionTime
|
||||
{
|
||||
get
|
||||
@ -58,6 +60,10 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sender
|
||||
|
||||
public string SenderEmailAddress
|
||||
{
|
||||
get
|
||||
@ -88,6 +94,35 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Recipients
|
||||
|
||||
public string To
|
||||
{
|
||||
get { return _item.To; }
|
||||
set { _item.To = value; }
|
||||
}
|
||||
|
||||
public string CC
|
||||
{
|
||||
get { return _item.CC; }
|
||||
set { _item.CC = value; }
|
||||
}
|
||||
|
||||
public string BCC
|
||||
{
|
||||
get { return _item.BCC; }
|
||||
set { _item.BCC = value; }
|
||||
}
|
||||
|
||||
public IRecipients Recipients
|
||||
{
|
||||
get { return new RecipientsWrapper(_item.Recipients); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wrapper methods
|
||||
|
||||
protected override NSOutlook.UserProperties GetUserProperties()
|
||||
|
@ -32,6 +32,17 @@ namespace Acacia.Stubs.OutlookWrappers
|
||||
|
||||
internal NSOutlook.Recipient RawItem { get { return _item; } }
|
||||
|
||||
public MailRecipientType Type
|
||||
{
|
||||
get { return (MailRecipientType)_item.Type; }
|
||||
set { _item.Type = (int)value; }
|
||||
}
|
||||
|
||||
public bool Resolve()
|
||||
{
|
||||
return _item.Resolve();
|
||||
}
|
||||
|
||||
public bool IsResolved
|
||||
{
|
||||
get
|
||||
|
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NSOutlook = Microsoft.Office.Interop.Outlook;
|
||||
|
||||
namespace Acacia.Stubs.OutlookWrappers
|
||||
{
|
||||
class RecipientsWrapper : ComWrapper<NSOutlook.Recipients>, IRecipients
|
||||
{
|
||||
public RecipientsWrapper(NSOutlook.Recipients item) : base(item)
|
||||
{
|
||||
}
|
||||
|
||||
public int Count { get { return _item.Count; } }
|
||||
|
||||
public void Remove(int index)
|
||||
{
|
||||
_item.Remove(index + 1);
|
||||
}
|
||||
|
||||
public IRecipient Add(string name)
|
||||
{
|
||||
return new RecipientWrapper(_item.Add(name));
|
||||
}
|
||||
|
||||
public IEnumerator<IRecipient> GetEnumerator()
|
||||
{
|
||||
foreach (NSOutlook.Recipient recipient in _item)
|
||||
yield return new RecipientWrapper(recipient);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
foreach (NSOutlook.Recipient recipient in _item)
|
||||
yield return new RecipientWrapper(recipient);
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ using System.Globalization;
|
||||
using Acacia.UI.Outlook;
|
||||
using Acacia.Stubs;
|
||||
using Acacia.Stubs.OutlookWrappers;
|
||||
using Microsoft.Office.Tools.Ribbon;
|
||||
|
||||
namespace Acacia
|
||||
{
|
||||
@ -77,7 +78,16 @@ namespace Acacia
|
||||
|
||||
#region Startup / Shutdown
|
||||
|
||||
private void ThisAddIn_Startup(object sender, System.EventArgs args)
|
||||
private void InternalStartup()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [KOE-87] Use this event rather than Startup, as that is invoked after the ribbon UI is queried, which leads
|
||||
/// to an empty - and therefore invisible - ribbon.
|
||||
/// </summary>
|
||||
public override void BeginInit()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -145,7 +155,6 @@ namespace Acacia
|
||||
Acacia.Features.DebugSupport.Statistics.StartupTime.Stop();
|
||||
foreach (Feature feature in Features)
|
||||
feature.AfterStartup();
|
||||
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
@ -171,12 +180,6 @@ namespace Acacia
|
||||
}
|
||||
}
|
||||
|
||||
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
|
||||
{
|
||||
// Note: Outlook no longer raises this event. If you have code that
|
||||
// must run when Outlook shuts down, see http://go.microsoft.com/fwlink/?LinkId=506785
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ribbons
|
||||
@ -209,19 +212,6 @@ namespace Acacia
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region VSTO generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InternalStartup()
|
||||
{
|
||||
this.Startup += new System.EventHandler(ThisAddIn_Startup);
|
||||
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ namespace Acacia.Utils
|
||||
return _this;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hex strings
|
||||
@ -112,19 +113,43 @@ namespace Acacia.Utils
|
||||
|
||||
public static string ReplaceStringTokens(this string s, string open, string close, TokenReplacer replacer)
|
||||
{
|
||||
return Regex.Replace(s, Regex.Escape(open) + @"(\w+)" + Regex.Escape(close), (m) =>
|
||||
StringBuilder replaced = new StringBuilder();
|
||||
|
||||
int start = 0;
|
||||
for(;;)
|
||||
{
|
||||
var key = m.Groups[1].Value;
|
||||
// Find open token
|
||||
int newStart = s.IndexOf(open, start);
|
||||
|
||||
// Not found, append rest and done
|
||||
if (newStart < 0)
|
||||
{
|
||||
replaced.Append(s.Substring(start));
|
||||
break;
|
||||
}
|
||||
|
||||
// Append current text
|
||||
replaced.Append(s.Substring(start, newStart - start));
|
||||
|
||||
// Find the close token
|
||||
int keyStart = newStart + open.Length;
|
||||
int newClose = s.IndexOf(close, keyStart);
|
||||
if (newClose < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the replacement
|
||||
string key = s.Substring(keyStart, newClose - keyStart);
|
||||
string replacement = replacer(key);
|
||||
if (replacement == null)
|
||||
{
|
||||
return m.Groups[0].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return replacement;
|
||||
}
|
||||
});
|
||||
if (replacement != null)
|
||||
replaced.Append(replacement);
|
||||
|
||||
// Next
|
||||
start = newClose + close.Length;
|
||||
}
|
||||
|
||||
return replaced.ToString();
|
||||
}
|
||||
|
||||
public static string ReplaceStringTokens(this string s, string open, string close, Dictionary<string, string> replacements)
|
||||
@ -138,5 +163,14 @@ namespace Acacia.Utils
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public static string DecodeQuotedPrintable(this string _this)
|
||||
{
|
||||
return ReplaceStringTokens(_this, "=?", "?=", (token) =>
|
||||
System.Net.Mail.Attachment.CreateAttachmentFromString("", "=?" + token + "?=").Name
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -57,4 +57,30 @@ namespace Acacia.ZPush
|
||||
return Feature.Name + ":" + _itemType.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class FolderRegistrationDefault : FolderRegistration
|
||||
{
|
||||
private readonly DefaultFolder _folder;
|
||||
|
||||
public FolderRegistrationDefault(Feature feature, DefaultFolder folder)
|
||||
:
|
||||
base(feature)
|
||||
{
|
||||
this._folder = folder;
|
||||
}
|
||||
|
||||
public override bool IsApplicable(IFolder folder)
|
||||
{
|
||||
// TODO: cache folder id per store
|
||||
using (IStore store = folder.GetStore())
|
||||
{
|
||||
return folder.EntryID == store.GetDefaultFolderId(_folder);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Feature.Name + ":" + _folder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user