Public Access
249 lines
6.8 KiB
C#
249 lines
6.8 KiB
C#
#if WINDOWS
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace MeetingAssistant.MeetingNotes;
|
|
|
|
internal static class OutlookClassicCom
|
|
{
|
|
private const int OutlookCalendarFolder = 9;
|
|
|
|
public static OutlookCalendarSession? OpenDefaultCalendar(
|
|
ILogger logger,
|
|
string logContext)
|
|
{
|
|
EnsureOutlookRunning(logger, logContext);
|
|
|
|
object? application = null;
|
|
object? session = null;
|
|
object? calendar = null;
|
|
object? items = null;
|
|
|
|
try
|
|
{
|
|
var applicationType = Type.GetTypeFromProgID("Outlook.Application");
|
|
if (applicationType is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
application = Activator.CreateInstance(applicationType);
|
|
if (application is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
session = GetProperty(application, "Session");
|
|
calendar = Invoke(session!, "GetDefaultFolder", OutlookCalendarFolder);
|
|
items = GetProperty(calendar!, "Items");
|
|
Invoke(items!, "Sort", "[Start]");
|
|
SetProperty(items!, "IncludeRecurrences", true);
|
|
|
|
return new OutlookCalendarSession(application, session, calendar, items!);
|
|
}
|
|
catch
|
|
{
|
|
ReleaseComObject(items);
|
|
ReleaseComObject(calendar);
|
|
ReleaseComObject(session);
|
|
ReleaseComObject(application);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public static Task<T> RunStaAsync<T>(
|
|
Func<T> action,
|
|
CancellationToken cancellationToken,
|
|
string threadName)
|
|
{
|
|
var completion = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
completion.TrySetResult(action());
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
completion.TrySetException(exception);
|
|
}
|
|
})
|
|
{
|
|
IsBackground = true,
|
|
Name = threadName
|
|
};
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
|
|
var registration = cancellationToken.Register(() => completion.TrySetCanceled(cancellationToken));
|
|
_ = completion.Task.ContinueWith(
|
|
_ => registration.Dispose(),
|
|
CancellationToken.None,
|
|
TaskContinuationOptions.ExecuteSynchronously,
|
|
TaskScheduler.Default);
|
|
thread.Start();
|
|
return completion.Task;
|
|
}
|
|
|
|
public static IEnumerable<object?> EnumerateItems(object items)
|
|
{
|
|
if (items is IEnumerable enumerable)
|
|
{
|
|
foreach (var item in enumerable)
|
|
{
|
|
yield return item;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool IsAppointment(object item)
|
|
{
|
|
try
|
|
{
|
|
return Convert.ToInt32(GetProperty(item, "Class")) == 26;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static bool IsCanceledAppointment(object appointment)
|
|
{
|
|
var meetingStatus = TryGetProperty(appointment, "MeetingStatus");
|
|
if (meetingStatus is not null)
|
|
{
|
|
try
|
|
{
|
|
var status = Convert.ToInt32(meetingStatus);
|
|
if (status is 5 or 7)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception exception) when (exception is FormatException or InvalidCastException or OverflowException)
|
|
{
|
|
// Fall back to subject prefixes when Outlook exposes a non-numeric status value.
|
|
}
|
|
}
|
|
|
|
var subject = Convert.ToString(TryGetProperty(appointment, "Subject")) ?? "";
|
|
return SubjectIndicatesCancellation(subject);
|
|
}
|
|
|
|
internal static bool SubjectIndicatesCancellation(string subject)
|
|
{
|
|
var normalized = subject.TrimStart();
|
|
return normalized.StartsWith("Canceled:", StringComparison.OrdinalIgnoreCase) ||
|
|
normalized.StartsWith("Cancelled:", StringComparison.OrdinalIgnoreCase) ||
|
|
normalized.StartsWith("Abgesagt:", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static DateTimeOffset ToLocalOffset(DateTime value)
|
|
{
|
|
return new DateTimeOffset(DateTime.SpecifyKind(value, DateTimeKind.Local));
|
|
}
|
|
|
|
public static object? TryGetProperty(object target, string name)
|
|
{
|
|
try
|
|
{
|
|
return GetProperty(target, name);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static object? GetProperty(object target, string name)
|
|
{
|
|
return target.GetType().InvokeMember(
|
|
name,
|
|
System.Reflection.BindingFlags.GetProperty,
|
|
null,
|
|
target,
|
|
null);
|
|
}
|
|
|
|
public static void SetProperty(object target, string name, object value)
|
|
{
|
|
target.GetType().InvokeMember(
|
|
name,
|
|
System.Reflection.BindingFlags.SetProperty,
|
|
null,
|
|
target,
|
|
[value]);
|
|
}
|
|
|
|
public static object? Invoke(object target, string name, params object[] arguments)
|
|
{
|
|
return target.GetType().InvokeMember(
|
|
name,
|
|
System.Reflection.BindingFlags.InvokeMethod,
|
|
null,
|
|
target,
|
|
arguments);
|
|
}
|
|
|
|
public static void ReleaseComObject(object? value)
|
|
{
|
|
if (value is not null && Marshal.IsComObject(value))
|
|
{
|
|
Marshal.FinalReleaseComObject(value);
|
|
}
|
|
}
|
|
|
|
private static void EnsureOutlookRunning(ILogger logger, string logContext)
|
|
{
|
|
if (Process.GetProcessesByName("OUTLOOK").Length > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = "outlook.exe",
|
|
UseShellExecute = true
|
|
});
|
|
Thread.Sleep(TimeSpan.FromSeconds(5));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogDebug(exception, "Could not start Outlook Classic before {OutlookContext}", logContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class OutlookCalendarSession : IDisposable
|
|
{
|
|
private readonly object? application;
|
|
private readonly object? session;
|
|
private readonly object? calendar;
|
|
|
|
public OutlookCalendarSession(
|
|
object? application,
|
|
object? session,
|
|
object? calendar,
|
|
object items)
|
|
{
|
|
this.application = application;
|
|
this.session = session;
|
|
this.calendar = calendar;
|
|
Items = items;
|
|
}
|
|
|
|
public object Items { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
OutlookClassicCom.ReleaseComObject(Items);
|
|
OutlookClassicCom.ReleaseComObject(calendar);
|
|
OutlookClassicCom.ReleaseComObject(session);
|
|
OutlookClassicCom.ReleaseComObject(application);
|
|
}
|
|
}
|
|
#endif
|