using System.Diagnostics; using MeetingAssistant.Calendar; namespace MeetingAssistant.MeetingNotes; public sealed class OutlookClassicMeetingMetadataProvider : IMeetingMetadataProvider, IMeetingMetadataDiagnosticProvider { private readonly ILogger logger; public OutlookClassicMeetingMetadataProvider(ILogger logger) { this.logger = logger; } public Task GetCurrentMeetingAsync( DateTimeOffset startedAt, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { return OutlookClassicCom.RunStaAsync( () => GetCurrentMeeting(startedAt), cancellationToken, "Meeting Assistant Outlook COM Lookup"); } catch (Exception exception) when (exception is not OperationCanceledException) { logger.LogDebug(exception, "Outlook Classic meeting metadata was not available"); return Task.FromResult(null); } } public async Task DiagnoseCurrentMeetingAsync( DateTimeOffset startedAt, TimeSpan timeout, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); try { var lookup = OutlookClassicCom.RunStaAsync( () => GetCurrentMeeting(startedAt), cancellationToken, "Meeting Assistant Outlook COM Lookup"); var metadata = timeout > TimeSpan.Zero ? await lookup.WaitAsync(timeout, cancellationToken) : await lookup.WaitAsync(cancellationToken); stopwatch.Stop(); return new MeetingMetadataDiagnosticResult( nameof(OutlookClassicMeetingMetadataProvider), startedAt, metadata is not null, false, stopwatch.ElapsedMilliseconds, metadata, metadata is null ? "No matching Teams appointment was found." : null); } catch (TimeoutException exception) { stopwatch.Stop(); return new MeetingMetadataDiagnosticResult( nameof(OutlookClassicMeetingMetadataProvider), startedAt, false, true, stopwatch.ElapsedMilliseconds, null, exception.Message); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception exception) { stopwatch.Stop(); return new MeetingMetadataDiagnosticResult( nameof(OutlookClassicMeetingMetadataProvider), startedAt, false, false, stopwatch.ElapsedMilliseconds, null, exception.Message); } } private MeetingMetadata? GetCurrentMeeting(DateTimeOffset startedAt) { using var calendar = OutlookClassicCom.OpenDefaultCalendar(logger, "metadata lookup"); if (calendar is null) { return null; } var startedLocal = startedAt.LocalDateTime; var windowStart = startedLocal.AddHours(-4); var windowEnd = startedLocal.AddMinutes(5); var candidates = new List(); foreach (var item in OutlookClassicCom.EnumerateItems(calendar.Items)) { if (item is null) { continue; } var keepAppointment = false; try { if (!OutlookClassicCom.IsAppointment(item)) { continue; } var start = Convert.ToDateTime(OutlookClassicCom.GetProperty(item, "Start")); if (start > windowEnd) { break; } var end = Convert.ToDateTime(OutlookClassicCom.GetProperty(item, "End")); if (end < windowStart || OutlookClassicCom.IsCanceledAppointment(item) || !OutlookClassicAppointmentMetadata.IsTeamsAppointment(item)) { continue; } candidates.Add(new OutlookAppointmentCandidate(item, start, end)); keepAppointment = true; } finally { if (!keepAppointment) { OutlookClassicCom.ReleaseComObject(item); } } } try { var selected = OutlookMeetingCandidateSelector.Select( candidates, startedLocal, candidate => candidate.Start, candidate => candidate.End); if (selected is null) { return null; } return OutlookClassicAppointmentMetadata.CreateMetadata(selected.Appointment, selected.End); } finally { foreach (var candidate in candidates) { OutlookClassicCom.ReleaseComObject(candidate.Appointment); } } } private sealed record OutlookAppointmentCandidate( object Appointment, DateTime Start, DateTime End); }