Initial integration.

This commit is contained in:
Burak Kaan Köse
2025-12-26 20:46:48 +01:00
parent 10b85ea135
commit 014b5aa671
79 changed files with 4694 additions and 432 deletions
@@ -177,6 +177,15 @@ public static class GoogleIntegratorExtensions
return null;
}
/// <summary>
/// Extracts the timezone string from EventDateTime.
/// Returns null for all-day events or if timezone is not specified.
/// </summary>
public static string GetEventTimeZone(EventDateTime eventDateTime)
{
return eventDateTime?.TimeZone;
}
/// <summary>
/// RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.
/// </summary>
@@ -229,23 +229,37 @@ public static class OutlookIntegratorExtensions
public static DateTimeOffset GetDateTimeOffsetFromDateTimeTimeZone(DateTimeTimeZone dateTimeTimeZone)
{
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime) || string.IsNullOrEmpty(dateTimeTimeZone.TimeZone))
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime))
{
throw new ArgumentException("DateTimeTimeZone is null or empty.");
throw new ArgumentException("DateTimeTimeZone or DateTime is null or empty.");
}
try
{
// Parse the DateTime string
if (DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
if (!DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
{
throw new ArgumentException("DateTime string is not in a valid format.");
}
// If no timezone is provided, assume UTC
if (string.IsNullOrEmpty(dateTimeTimeZone.TimeZone))
{
return new DateTimeOffset(parsedDateTime, TimeSpan.Zero);
}
try
{
// Get TimeZoneInfo to get the offset
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(dateTimeTimeZone.TimeZone);
TimeSpan offset = timeZoneInfo.GetUtcOffset(parsedDateTime);
return new DateTimeOffset(parsedDateTime, offset);
}
else
throw new ArgumentException("DateTime string is not in a valid format.");
catch (TimeZoneNotFoundException)
{
// If timezone is not found, assume UTC as fallback
return new DateTimeOffset(parsedDateTime, TimeSpan.Zero);
}
}
catch (Exception)
{
@@ -102,6 +102,10 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
DurationInSeconds = totalDurationInSeconds,
Location = string.IsNullOrEmpty(calendarEvent.Location) ? parentRecurringEvent.Location : calendarEvent.Location,
// Store timezone information
StartTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.Start) ?? parentRecurringEvent.StartTimeZone,
EndTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.End) ?? parentRecurringEvent.EndTimeZone,
// Leave it empty if it's not populated.
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent) == null ? string.Empty : GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
Status = GetStatus(calendarEvent.Status),
@@ -137,6 +141,11 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
EndDateOffset = eventEndDateTimeOffset.Value.Offset,
DurationInSeconds = totalDurationInSeconds,
Location = calendarEvent.Location,
// Store timezone information from Google Calendar event
StartTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.Start),
EndTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.End),
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
Status = GetStatus(calendarEvent.Status),
Title = calendarEvent.Summary,
@@ -69,6 +69,11 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
savingItem.EndDateOffset = eventEndDateTimeOffset.Offset;
savingItem.DurationInSeconds = durationInSeconds;
// Store the timezone information from the event
// This preserves the original timezone from Outlook, allowing proper reconstruction later
savingItem.StartTimeZone = calendarEvent.Start?.TimeZone;
savingItem.EndTimeZone = calendarEvent.End?.TimeZone;
savingItem.Title = calendarEvent.Subject;
savingItem.Description = calendarEvent.Body?.Content;
savingItem.Location = calendarEvent.Location?.DisplayName;
@@ -103,6 +108,34 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
savingItem.OrganizerDisplayName = calendarEvent.Organizer?.EmailAddress?.Name;
savingItem.IsHidden = false;
// Set timestamps
if (calendarEvent.CreatedDateTime.HasValue)
savingItem.CreatedAt = calendarEvent.CreatedDateTime.Value;
if (calendarEvent.LastModifiedDateTime.HasValue)
savingItem.UpdatedAt = calendarEvent.LastModifiedDateTime.Value;
// Set visibility
if (calendarEvent.Sensitivity != null)
{
savingItem.Visibility = calendarEvent.Sensitivity.Value switch
{
Sensitivity.Normal => CalendarItemVisibility.Public,
Sensitivity.Personal => CalendarItemVisibility.Private,
Sensitivity.Private => CalendarItemVisibility.Private,
Sensitivity.Confidential => CalendarItemVisibility.Confidential,
_ => CalendarItemVisibility.Public
};
}
else
{
savingItem.Visibility = CalendarItemVisibility.Public;
}
// Set IsLocked based on whether the user is the organizer
// Read-only events are those where the current user is not the organizer
savingItem.IsLocked = calendarEvent.IsOrganizer.HasValue && !calendarEvent.IsOrganizer.Value;
if (calendarEvent.ResponseStatus?.Response != null)
{
switch (calendarEvent.ResponseStatus.Response.Value)
@@ -299,6 +299,56 @@ public class SynchronizationManager : ISynchronizationManager
return await SynchronizeMailAsync(options, cancellationToken);
}
/// <summary>
/// Handles calendar synchronization for the given account.
/// </summary>
/// <param name="options">Calendar synchronization options</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Synchronization result</returns>
public async Task<CalendarSynchronizationResult> SynchronizeCalendarAsync(CalendarSynchronizationOptions options,
CancellationToken cancellationToken = default)
{
EnsureInitialized();
var synchronizer = await GetOrCreateSynchronizerAsync(options.AccountId);
if (synchronizer == null)
{
_logger.Error("Could not find or create synchronizer for account {AccountId}", options.AccountId);
return CalendarSynchronizationResult.Failed;
}
_logger.Information("Starting calendar synchronization for account {AccountId} with type {SyncType}",
options.AccountId, options.Type);
try
{
var result = await synchronizer.SynchronizeCalendarEventsAsync(options, cancellationToken);
_logger.Information("Calendar synchronization completed for account {AccountId} with state {State}",
options.AccountId, result.CompletedState);
// TODO: Create notifications for new calendar events when INotificationBuilder supports it
// if (result.DownloadedEvents?.Any() ?? false)
// await _notificationBuilder.CreateCalendarNotificationsAsync(result.DownloadedEvents);
return result;
}
catch (AuthenticationAttentionException authEx)
{
_logger.Warning("Account {AccountId} requires attention due to authentication issues", options.AccountId);
// Create app notification for authentication attention
_notificationBuilder.CreateAttentionRequiredNotification(authEx.Account);
return CalendarSynchronizationResult.Failed;
}
catch (Exception ex)
{
_logger.Error(ex, "Calendar synchronization failed for account {AccountId}", options.AccountId);
return CalendarSynchronizationResult.Failed;
}
}
/// <summary>
/// Downloads a MIME message for the given mail item.
/// </summary>
+6 -18
View File
@@ -1631,8 +1631,8 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
{
_logger.Information("No calendar sync identifier for calendar {Name}. Performing initial sync.", calendar.Name);
var startDate = DateTime.UtcNow.AddYears(-2).ToString("u");
var endDate = DateTime.UtcNow.ToString("u");
var startDate = DateTimeOffset.UtcNow.AddYears(-2).ToString("o");
var endDate = DateTimeOffset.UtcNow.ToString("o");
eventsDeltaResponse = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
{
@@ -1658,20 +1658,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
_logger.Information("Performing delta sync for calendar {Name}.", calendar.Name);
var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation((requestConfiguration) =>
{
//requestConfiguration.QueryParameters.StartDateTime = startDate;
//requestConfiguration.QueryParameters.EndDateTime = endDate;
});
//var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation((config) =>
//{
// config.QueryParameters.Top = (int)InitialMessageDownloadCountPerFolder;
// config.QueryParameters.Select = outlookMessageSelectParameters;
// config.QueryParameters.Orderby = ["receivedDateTime desc"];
//});
var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation();
requestInformation.UrlTemplate = requestInformation.UrlTemplate.Insert(requestInformation.UrlTemplate.Length - 1, ",%24deltatoken");
requestInformation.QueryParameters.Add("%24deltatoken", currentDeltaToken);
@@ -1726,11 +1713,12 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
var deltaToken = GetDeltaTokenFromDeltaLink(latestDeltaLink);
await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
/// await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
}
}
return default;
// TODO: Return proper results.
return CalendarSynchronizationResult.Empty;
}
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)