Outlook delta synchronization.

This commit is contained in:
Burak Kaan Köse
2025-01-07 13:42:10 +01:00
parent 1ef83a3089
commit 2e9d1d83a4
6 changed files with 117 additions and 63 deletions

View File

@@ -221,7 +221,7 @@ namespace Wino.Calendar.ViewModels
{ {
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions() var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
{ {
AccountId = Guid.Parse("bd0fc1ab-168a-436d-86ce-0661c0eabaf9"), AccountId = Guid.Parse("5b2e28bb-3179-4a7f-a62b-373878ee2b53"),
Type = CalendarSynchronizationType.CalendarMetadata Type = CalendarSynchronizationType.CalendarMetadata
}, SynchronizationSource.Client); }, SynchronizationSource.Client);

View File

@@ -295,7 +295,7 @@
Closed="EventDetailsPopupClosed" Closed="EventDetailsPopupClosed"
DesiredPlacement="{x:Bind calendarHelpers:CalendarXamlHelpers.GetDesiredPlacementModeForEventsDetailsPopup(ViewModel.DisplayDetailsCalendarItemViewModel, ViewModel.StatePersistanceService.CalendarDisplayType), Mode=OneWay}" DesiredPlacement="{x:Bind calendarHelpers:CalendarXamlHelpers.GetDesiredPlacementModeForEventsDetailsPopup(ViewModel.DisplayDetailsCalendarItemViewModel, ViewModel.StatePersistanceService.CalendarDisplayType), Mode=OneWay}"
HorizontalOffset="16" HorizontalOffset="16"
IsLightDismissEnabled="False" IsLightDismissEnabled="True"
IsOpen="{x:Bind ViewModel.IsEventDetailsVisible, Mode=OneWay}" IsOpen="{x:Bind ViewModel.IsEventDetailsVisible, Mode=OneWay}"
PlacementTarget="{x:Bind TeachingTipPositionerGrid}" PlacementTarget="{x:Bind TeachingTipPositionerGrid}"
VerticalOffset="16"> VerticalOffset="16">
@@ -319,7 +319,6 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
FontWeight="Normal" FontWeight="Normal"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(ViewModel.DisplayDetailsCalendarItemViewModel.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
Style="{StaticResource SubtitleTextBlockStyle}" Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.Title, Mode=OneWay}" /> Text="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.Title, Mode=OneWay}" />

View File

@@ -18,5 +18,6 @@ namespace Wino.Core.Domain.Interfaces
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees); Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel); Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId); Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId);
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
} }
} }

View File

@@ -49,6 +49,8 @@ namespace Wino.Core.Integration.Processors
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar); Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar); Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar); Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
} }
public interface IGmailChangeProcessor : IDefaultChangeProcessor public interface IGmailChangeProcessor : IDefaultChangeProcessor
@@ -103,7 +105,11 @@ namespace Wino.Core.Integration.Processors
/// <param name="accountId">Account identifier to reset delta token for.</param> /// <param name="accountId">Account identifier to reset delta token for.</param>
/// <returns>Empty string to assign account delta sync for.</returns> /// <returns>Empty string to assign account delta sync for.</returns>
Task<string> ResetAccountDeltaTokenAsync(Guid accountId); Task<string> ResetAccountDeltaTokenAsync(Guid accountId);
Task ManageCalendarEventAsync(Microsoft.Graph.Models.Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount); Task ManageCalendarEventAsync(Microsoft.Graph.Models.Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount);
} }
public interface IImapChangeProcessor : IDefaultChangeProcessor public interface IImapChangeProcessor : IDefaultChangeProcessor
@@ -199,5 +205,8 @@ namespace Wino.Core.Integration.Processors
public Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar) public Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar)
=> CalendarService.UpdateAccountCalendarAsync(accountCalendar); => CalendarService.UpdateAccountCalendarAsync(accountCalendar);
public Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken)
=> CalendarService.UpdateCalendarDeltaSynchronizationToken(calendarId, deltaToken);
} }
} }

View File

@@ -966,44 +966,66 @@ namespace Wino.Core.Synchronizers.Mail
// await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false); // await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
bool isInitialSync = string.IsNullOrEmpty(Account.CalendarSynchronizationDeltaIdentifier);
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false); var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
Microsoft.Graph.Me.CalendarView.Delta.DeltaGetResponse eventsDeltaResponse = null; Microsoft.Graph.Me.Calendars.Item.CalendarView.Delta.DeltaGetResponse eventsDeltaResponse = null;
// TODO: Maybe we can batch each calendar?
foreach (var calendar in localCalendars)
{
bool isInitialSync = string.IsNullOrEmpty(calendar.SynchronizationDeltaToken);
if (isInitialSync) if (isInitialSync)
{ {
_logger.Debug("No calendar sync identifier for account {Name}. Performing initial sync.", Account.Name); _logger.Information("No calendar sync identifier for calendar {Name}. Performing initial sync.", calendar.Name);
var startDate = DateTime.UtcNow.AddYears(-2).ToString("u"); var startDate = DateTime.UtcNow.AddYears(-2).ToString("u");
var endDate = DateTime.UtcNow.ToString("u"); var endDate = DateTime.UtcNow.ToString("u");
// No delta link. Performing initial sync. eventsDeltaResponse = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
eventsDeltaResponse = await _graphClient.Me.CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
{ {
requestConfiguration.QueryParameters.StartDateTime = startDate; requestConfiguration.QueryParameters.StartDateTime = startDate;
requestConfiguration.QueryParameters.EndDateTime = endDate; requestConfiguration.QueryParameters.EndDateTime = endDate;
// TODO: Expand does not work.
// https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2358
requestConfiguration.QueryParameters.Expand = new string[] { "calendar($select=name,id)" }; // Expand the calendar and select name and id. Customize as needed.
}, cancellationToken: cancellationToken); }, cancellationToken: cancellationToken);
// No delta link. Performing initial sync.
//eventsDeltaResponse = await _graphClient.Me.CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
//{
// requestConfiguration.QueryParameters.StartDateTime = startDate;
// requestConfiguration.QueryParameters.EndDateTime = endDate;
// // TODO: Expand does not work.
// // https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2358
// requestConfiguration.QueryParameters.Expand = new string[] { "calendar($select=name,id)" }; // Expand the calendar and select name and id. Customize as needed.
//}, cancellationToken: cancellationToken);
} }
else else
{ {
//var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events.Delta.ToGetRequestInformation((config) => var currentDeltaToken = calendar.SynchronizationDeltaToken;
_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.Top = (int)InitialMessageDownloadCountPerFolder;
// config.QueryParameters.Select = outlookMessageSelectParameters; // config.QueryParameters.Select = outlookMessageSelectParameters;
// config.QueryParameters.Orderby = ["receivedDateTime desc"]; // config.QueryParameters.Orderby = ["receivedDateTime desc"];
//}); //});
//requestInformation.UrlTemplate = requestInformation.UrlTemplate.Insert(requestInformation.UrlTemplate.Length - 1, ",%24deltatoken");
//requestInformation.QueryParameters.Add("%24deltatoken", currentDeltaToken);
// eventsDeltaResponse = await _graphClient.RequestAdapter.SendAsync(requestInformation, Microsoft.Graph.Me.Calendars.Item.Events.Delta.DeltaGetResponse.CreateFromDiscriminatorValue); requestInformation.UrlTemplate = requestInformation.UrlTemplate.Insert(requestInformation.UrlTemplate.Length - 1, ",%24deltatoken");
requestInformation.QueryParameters.Add("%24deltatoken", currentDeltaToken);
eventsDeltaResponse = await _graphClient.RequestAdapter.SendAsync(requestInformation, Microsoft.Graph.Me.Calendars.Item.CalendarView.Delta.DeltaGetResponse.CreateFromDiscriminatorValue);
} }
List<Event> events = new(); List<Event> events = new();
@@ -1011,7 +1033,7 @@ namespace Wino.Core.Synchronizers.Mail
// We must first save the parent recurring events to not lose exceptions. // We must first save the parent recurring events to not lose exceptions.
// Therefore, order the existing items by their type and save the parent recurring events first. // Therefore, order the existing items by their type and save the parent recurring events first.
var messageIteratorAsync = PageIterator<Event, Microsoft.Graph.Me.CalendarView.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, eventsDeltaResponse, (item) => var messageIteratorAsync = PageIterator<Event, Microsoft.Graph.Me.Calendars.Item.CalendarView.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, eventsDeltaResponse, (item) =>
{ {
events.Add(item); events.Add(item);
@@ -1025,12 +1047,14 @@ namespace Wino.Core.Synchronizers.Mail
// Desc-order will move parent recurring events to the top. // Desc-order will move parent recurring events to the top.
events = events.OrderByDescending(a => a.Type).ToList(); events = events.OrderByDescending(a => a.Type).ToList();
_logger.Information("Found {Count} events in total.", events.Count);
foreach (var item in events) foreach (var item in events)
{ {
try try
{ {
await _handleItemRetrievalSemaphore.WaitAsync(); await _handleItemRetrievalSemaphore.WaitAsync();
//await _outlookChangeProcessor.ManageCalendarEventAsync(item, calendar, Account).ConfigureAwait(false); await _outlookChangeProcessor.ManageCalendarEventAsync(item, calendar, Account).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1042,7 +1066,18 @@ namespace Wino.Core.Synchronizers.Mail
} }
} }
// latestDeltaLink = messageIteratorAsync.Deltalink; var latestDeltaLink = messageIteratorAsync.Deltalink;
//Store delta link for tracking new changes.
if (!string.IsNullOrEmpty(latestDeltaLink))
{
// Parse Delta Token from Delta Link since v5 of Graph SDK works based on the token, not the link.
var deltaToken = GetDeltaTokenFromDeltaLink(latestDeltaLink);
await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
}
}
return default; return default;
} }

View File

@@ -198,5 +198,15 @@ namespace Wino.Services
return calendarItem; return calendarItem;
} }
public Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken)
{
var query = new Query()
.From(nameof(AccountCalendar))
.Where(nameof(AccountCalendar.Id), calendarId)
.AsUpdate(new { SynchronizationDeltaToken = deltaToken });
return Connection.ExecuteAsync(query.GetRawQuery());
}
} }
} }