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()
{
AccountId = Guid.Parse("bd0fc1ab-168a-436d-86ce-0661c0eabaf9"),
AccountId = Guid.Parse("5b2e28bb-3179-4a7f-a62b-373878ee2b53"),
Type = CalendarSynchronizationType.CalendarMetadata
}, SynchronizationSource.Client);

View File

@@ -295,7 +295,7 @@
Closed="EventDetailsPopupClosed"
DesiredPlacement="{x:Bind calendarHelpers:CalendarXamlHelpers.GetDesiredPlacementModeForEventsDetailsPopup(ViewModel.DisplayDetailsCalendarItemViewModel, ViewModel.StatePersistanceService.CalendarDisplayType), Mode=OneWay}"
HorizontalOffset="16"
IsLightDismissEnabled="False"
IsLightDismissEnabled="True"
IsOpen="{x:Bind ViewModel.IsEventDetailsVisible, Mode=OneWay}"
PlacementTarget="{x:Bind TeachingTipPositionerGrid}"
VerticalOffset="16">
@@ -319,7 +319,6 @@
</Grid.RowDefinitions>
<TextBlock
FontWeight="Normal"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(ViewModel.DisplayDetailsCalendarItemViewModel.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
Style="{StaticResource SubtitleTextBlockStyle}"
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<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
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 InsertAccountCalendarAsync(AccountCalendar accountCalendar);
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
}
public interface IGmailChangeProcessor : IDefaultChangeProcessor
@@ -103,7 +105,11 @@ namespace Wino.Core.Integration.Processors
/// <param name="accountId">Account identifier to reset delta token for.</param>
/// <returns>Empty string to assign account delta sync for.</returns>
Task<string> ResetAccountDeltaTokenAsync(Guid accountId);
Task ManageCalendarEventAsync(Microsoft.Graph.Models.Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount);
}
public interface IImapChangeProcessor : IDefaultChangeProcessor
@@ -199,5 +205,8 @@ namespace Wino.Core.Integration.Processors
public Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar)
=> CalendarService.UpdateAccountCalendarAsync(accountCalendar);
public Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken)
=> CalendarService.UpdateCalendarDeltaSynchronizationToken(calendarId, deltaToken);
}
}

View File

@@ -966,84 +966,119 @@ namespace Wino.Core.Synchronizers.Mail
// await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
bool isInitialSync = string.IsNullOrEmpty(Account.CalendarSynchronizationDeltaIdentifier);
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;
if (isInitialSync)
// TODO: Maybe we can batch each calendar?
foreach (var calendar in localCalendars)
{
_logger.Debug("No calendar sync identifier for account {Name}. Performing initial sync.", Account.Name);
bool isInitialSync = string.IsNullOrEmpty(calendar.SynchronizationDeltaToken);
var startDate = DateTime.UtcNow.AddYears(-2).ToString("u");
var endDate = DateTime.UtcNow.ToString("u");
// No delta link. Performing initial sync.
eventsDeltaResponse = await _graphClient.Me.CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
if (isInitialSync)
{
requestConfiguration.QueryParameters.StartDateTime = startDate;
requestConfiguration.QueryParameters.EndDateTime = endDate;
_logger.Information("No calendar sync identifier for calendar {Name}. Performing initial sync.", calendar.Name);
// TODO: Expand does not work.
// https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2358
var startDate = DateTime.UtcNow.AddYears(-2).ToString("u");
var endDate = DateTime.UtcNow.ToString("u");
requestConfiguration.QueryParameters.Expand = new string[] { "calendar($select=name,id)" }; // Expand the calendar and select name and id. Customize as needed.
}, cancellationToken: cancellationToken);
}
else
{
//var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events.Delta.ToGetRequestInformation((config) =>
//{
// config.QueryParameters.Top = (int)InitialMessageDownloadCountPerFolder;
// config.QueryParameters.Select = outlookMessageSelectParameters;
// config.QueryParameters.Orderby = ["receivedDateTime desc"];
//});
eventsDeltaResponse = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.StartDateTime = startDate;
requestConfiguration.QueryParameters.EndDateTime = endDate;
}, cancellationToken: cancellationToken);
//requestInformation.UrlTemplate = requestInformation.UrlTemplate.Insert(requestInformation.UrlTemplate.Length - 1, ",%24deltatoken");
//requestInformation.QueryParameters.Add("%24deltatoken", currentDeltaToken);
// No delta link. Performing initial sync.
//eventsDeltaResponse = await _graphClient.Me.CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
//{
// requestConfiguration.QueryParameters.StartDateTime = startDate;
// requestConfiguration.QueryParameters.EndDateTime = endDate;
// eventsDeltaResponse = await _graphClient.RequestAdapter.SendAsync(requestInformation, Microsoft.Graph.Me.Calendars.Item.Events.Delta.DeltaGetResponse.CreateFromDiscriminatorValue);
}
// // TODO: Expand does not work.
// // https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2358
List<Event> events = new();
// 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.
var messageIteratorAsync = PageIterator<Event, Microsoft.Graph.Me.CalendarView.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, eventsDeltaResponse, (item) =>
{
events.Add(item);
return true;
});
await messageIteratorAsync
.IterateAsync(cancellationToken)
.ConfigureAwait(false);
// Desc-order will move parent recurring events to the top.
events = events.OrderByDescending(a => a.Type).ToList();
foreach (var item in events)
{
try
{
await _handleItemRetrievalSemaphore.WaitAsync();
//await _outlookChangeProcessor.ManageCalendarEventAsync(item, calendar, Account).ConfigureAwait(false);
// requestConfiguration.QueryParameters.Expand = new string[] { "calendar($select=name,id)" }; // Expand the calendar and select name and id. Customize as needed.
//}, cancellationToken: cancellationToken);
}
catch (Exception ex)
else
{
// _logger.Error(ex, "Error occurred while handling item {Id} for calendar {Name}", item.Id, calendar.Name);
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.Select = outlookMessageSelectParameters;
// 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.CalendarView.Delta.DeltaGetResponse.CreateFromDiscriminatorValue);
}
finally
List<Event> events = new();
// 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.
var messageIteratorAsync = PageIterator<Event, Microsoft.Graph.Me.Calendars.Item.CalendarView.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, eventsDeltaResponse, (item) =>
{
_handleItemRetrievalSemaphore.Release();
events.Add(item);
return true;
});
await messageIteratorAsync
.IterateAsync(cancellationToken)
.ConfigureAwait(false);
// Desc-order will move parent recurring events to the top.
events = events.OrderByDescending(a => a.Type).ToList();
_logger.Information("Found {Count} events in total.", events.Count);
foreach (var item in events)
{
try
{
await _handleItemRetrievalSemaphore.WaitAsync();
await _outlookChangeProcessor.ManageCalendarEventAsync(item, calendar, Account).ConfigureAwait(false);
}
catch (Exception ex)
{
// _logger.Error(ex, "Error occurred while handling item {Id} for calendar {Name}", item.Id, calendar.Name);
}
finally
{
_handleItemRetrievalSemaphore.Release();
}
}
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);
}
}
// latestDeltaLink = messageIteratorAsync.Deltalink;
return default;
}

View File

@@ -198,5 +198,15 @@ namespace Wino.Services
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());
}
}
}