From b81ab0ca15608a84f9b115790f59373c478b746a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Tue, 30 Dec 2025 11:59:54 +0100 Subject: [PATCH] Creating events. --- .../CalendarPageViewModel.cs | 58 +++++++++---- Wino.Core.Domain/Enums/MailOperation.cs | 7 ++ Wino.Core.Domain/Interfaces/IRequestBundle.cs | 9 +- .../Interfaces/ISynchronizationManager.cs | 2 +- .../Interfaces/IWinoRequestDelegator.cs | 7 ++ .../CalendarOperationPreparationRequest.cs | 19 +++++ .../Models/Requests/RequestBase.cs | 5 ++ .../Calendar/CreateCalendarEventRequest.cs | 35 ++++++++ Wino.Core/Services/SynchronizationManager.cs | 72 +++++++++++----- Wino.Core/Services/WinoRequestDelegator.cs | 29 +++++++ Wino.Core/Synchronizers/GmailSynchronizer.cs | 71 ++++++++++++++++ .../Synchronizers/OutlookSynchronizer.cs | 84 +++++++++++++++++++ Wino.Core/Synchronizers/WinoSynchronizer.cs | 65 +++++++++++++- 13 files changed, 421 insertions(+), 42 deletions(-) create mode 100644 Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs create mode 100644 Wino.Core/Requests/Calendar/CreateCalendarEventRequest.cs diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index a1b6e348..58bcb271 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -128,6 +128,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, private readonly INavigationService _navigationService; private readonly IKeyPressService _keyPressService; private readonly IPreferencesService _preferencesService; + private readonly IWinoRequestDelegator _winoRequestDelegator; // Store latest rendered options. private CalendarDisplayType _currentDisplayType; @@ -147,7 +148,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, INavigationService navigationService, IKeyPressService keyPressService, IAccountCalendarStateService accountCalendarStateService, - IPreferencesService preferencesService) + IPreferencesService preferencesService, + IWinoRequestDelegator winoRequestDelegator) { StatePersistanceService = statePersistanceService; AccountCalendarStateService = accountCalendarStateService; @@ -156,6 +158,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, _navigationService = navigationService; _keyPressService = keyPressService; _preferencesService = preferencesService; + _winoRequestDelegator = winoRequestDelegator; AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; @@ -256,24 +259,47 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))] private async Task SaveQuickEventAsync() { - var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds; - - var testCalendarItem = new CalendarItem + try { - CalendarId = SelectedQuickEventAccountCalendar.Id, - StartDate = QuickEventStartTime, - DurationInSeconds = durationSeconds, - CreatedAt = DateTime.UtcNow, - Description = string.Empty, - Location = Location, - Title = EventName, - Id = Guid.NewGuid() - }; + var startDate = IsAllDay ? SelectedQuickEventDate.Value.Date : QuickEventStartTime; + var endDate = IsAllDay ? SelectedQuickEventDate.Value.Date.AddDays(1) : QuickEventEndTime; + var durationSeconds = (endDate - startDate).TotalSeconds; - IsQuickEventDialogOpen = false; - await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null); + // Get the user's current timezone from the system + var currentTimeZone = TimeZoneInfo.Local.Id; - // TODO: Create the request with the synchronizer. + var calendarItem = new CalendarItem + { + Id = Guid.NewGuid(), + CalendarId = SelectedQuickEventAccountCalendar.Id, + StartDate = startDate, + DurationInSeconds = durationSeconds, + StartTimeZone = currentTimeZone, + EndTimeZone = currentTimeZone, + CreatedAt = DateTime.UtcNow, + Description = string.Empty, + Location = Location ?? string.Empty, + Title = EventName, + IsHidden = false, + AssignedCalendar = SelectedQuickEventAccountCalendar + }; + + // Close dialog first + IsQuickEventDialogOpen = false; + + // Save to local database first + // await _calendarService.CreateNewCalendarItemAsync(calendarItem, null); + + // Queue the request via delegator + var preparationRequest = new CalendarOperationPreparationRequest(CalendarSynchronizerOperation.CreateEvent, calendarItem, null); + await _winoRequestDelegator.ExecuteAsync(preparationRequest); + } + catch (Exception ex) + { + Log.Error(ex, "Error creating quick event"); + // Re-open dialog if there was an error + IsQuickEventDialogOpen = true; + } } [RelayCommand] diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index 973ff62f..e90f22a7 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -21,6 +21,13 @@ public enum FolderSynchronizerOperation MarkFolderRead, } +public enum CalendarSynchronizerOperation +{ + CreateEvent, + UpdateEvent, + DeleteEvent, +} + // UI requests public enum MailOperation { diff --git a/Wino.Core.Domain/Interfaces/IRequestBundle.cs b/Wino.Core.Domain/Interfaces/IRequestBundle.cs index f88a3dc2..59bd9e2d 100644 --- a/Wino.Core.Domain/Interfaces/IRequestBundle.cs +++ b/Wino.Core.Domain/Interfaces/IRequestBundle.cs @@ -1,4 +1,5 @@ -using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; namespace Wino.Core.Domain.Interfaces; @@ -63,3 +64,9 @@ public interface IFolderActionRequest : IRequestBase FolderSynchronizerOperation Operation { get; } } + +public interface ICalendarActionRequest : IRequestBase +{ + CalendarItem Item { get; } + CalendarSynchronizerOperation Operation { get; } +} diff --git a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs index 61017244..86ec5cce 100644 --- a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs +++ b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs @@ -79,7 +79,7 @@ public interface ISynchronizationManager /// /// Creates a new synchronizer for a newly added account. /// - Task CreateSynchronizerForAccountAsync(MailAccount account); + IWinoSynchronizerBase CreateSynchronizerForAccount(MailAccount account); /// /// Destroys the synchronizer for the given account. diff --git a/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs b/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs index de50c517..0cf1cabf 100644 --- a/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs +++ b/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; @@ -29,4 +30,10 @@ public interface IWinoRequestDelegator /// /// Folder prep request. Task ExecuteAsync(FolderOperationPreperationRequest folderOperationPreperationRequest); + + /// + /// Prepares and queues calendar action requests for proper synchronizers. + /// + /// Calendar preparation request. + Task ExecuteAsync(CalendarOperationPreparationRequest calendarOperationPreparationRequest); } diff --git a/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs b/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs new file mode 100644 index 00000000..81a9c342 --- /dev/null +++ b/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.Models.Calendar; + +/// +/// Encapsulates the options for preparing calendar operation requests. +/// +/// Calendar operation to execute (Create, Update, Delete). +/// Calendar item to operate on. +/// List of attendees for the calendar event. +public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List Attendees); +//{ +// public CalendarOperationPreparationRequest(CalendarItem calendarItem) +// : this(calendarItem ?? throw new ArgumentNullException(nameof(calendarItem)), null) +// { +// } +//} diff --git a/Wino.Core.Domain/Models/Requests/RequestBase.cs b/Wino.Core.Domain/Models/Requests/RequestBase.cs index af7b6039..f7e008b7 100644 --- a/Wino.Core.Domain/Models/Requests/RequestBase.cs +++ b/Wino.Core.Domain/Models/Requests/RequestBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; @@ -29,6 +30,10 @@ public abstract record FolderRequestBase(MailItemFolder Folder, FolderSynchroniz public virtual object GroupingKey() { return Operation; } } +public abstract record CalendarRequestBase(CalendarItem Item) : RequestBase, ICalendarActionRequest +{ +} + public class BatchCollection : List, IUIChangeRequest where TRequestType : IUIChangeRequest { public BatchCollection(IEnumerable collection) : base(collection) diff --git a/Wino.Core/Requests/Calendar/CreateCalendarEventRequest.cs b/Wino.Core/Requests/Calendar/CreateCalendarEventRequest.cs new file mode 100644 index 00000000..3135f71e --- /dev/null +++ b/Wino.Core/Requests/Calendar/CreateCalendarEventRequest.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Core.Requests.Calendar; + +/// +/// Request to create a new calendar event on the server. +/// The calendar item should be already saved to the local database before queuing this request. +/// +public record CreateCalendarEventRequest(CalendarItem Item, List Attendees) : CalendarRequestBase(Item) +{ + public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.CreateEvent; + + /// + /// After successful creation, we need to resync to get the remote event ID. + /// + public override int ResynchronizationDelay => 2000; + + public override void ApplyUIChanges() + { + // Notify UI that the event was created locally + WeakReferenceMessenger.Default.Send(new CalendarItemAdded(Item)); + } + + public override void RevertUIChanges() + { + // If creation fails, we should notify the UI to remove it + WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(Item)); + } +} diff --git a/Wino.Core/Services/SynchronizationManager.cs b/Wino.Core/Services/SynchronizationManager.cs index 65c1cf77..4fae1313 100644 --- a/Wino.Core/Services/SynchronizationManager.cs +++ b/Wino.Core/Services/SynchronizationManager.cs @@ -191,7 +191,8 @@ public class SynchronizationManager : ISynchronizationManager } /// - /// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering. + /// Queues a request to the corresponding account's synchronizer with optional synchronization triggering. + /// Automatically determines whether to trigger mail or calendar synchronization based on the request type. /// /// Request to queue /// Account ID to queue the request for @@ -214,28 +215,57 @@ public class SynchronizationManager : ISynchronizationManager if (triggerSynchronization) { - // Trigger synchronization to execute the queued request - _logger.Debug("Triggering synchronization to execute queued request for account {AccountId}", accountId); + // Determine if this is a calendar or mail operation + bool isCalendarOperation = request is ICalendarActionRequest; - var synchronizationOptions = new MailSynchronizationOptions() + if (isCalendarOperation) { - AccountId = accountId, - Type = MailSynchronizationType.ExecuteRequests - }; + // Trigger calendar synchronization + _logger.Debug("Triggering calendar synchronization to execute queued request for account {AccountId}", accountId); - // Trigger synchronization asynchronously without waiting for completion - // This matches the pattern used in WinoRequestDelegator - _ = Task.Run(async () => + var calendarSyncOptions = new CalendarSynchronizationOptions() + { + AccountId = accountId + }; + + // Trigger synchronization asynchronously without waiting for completion + _ = Task.Run(async () => + { + try + { + await SynchronizeCalendarAsync(calendarSyncOptions); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to execute calendar synchronization after queuing request for account {AccountId}", accountId); + } + }); + } + else { - try + // Trigger mail synchronization (includes mail and folder operations) + _logger.Debug("Triggering mail synchronization to execute queued request for account {AccountId}", accountId); + + var mailSyncOptions = new MailSynchronizationOptions() { - await SynchronizeMailAsync(synchronizationOptions); - } - catch (Exception ex) + AccountId = accountId, + Type = MailSynchronizationType.ExecuteRequests + }; + + // Trigger synchronization asynchronously without waiting for completion + // This matches the pattern used in WinoRequestDelegator + _ = Task.Run(async () => { - _logger.Error(ex, "Failed to execute synchronization after queuing request for account {AccountId}", accountId); - } - }); + try + { + await SynchronizeMailAsync(mailSyncOptions); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to execute mail synchronization after queuing request for account {AccountId}", accountId); + } + }); + } } } @@ -387,7 +417,7 @@ public class SynchronizationManager : ISynchronizationManager /// /// Account to create synchronizer for /// Created synchronizer - public Task CreateSynchronizerForAccountAsync(MailAccount account) + public IWinoSynchronizerBase CreateSynchronizerForAccount(MailAccount account) { EnsureInitialized(); @@ -399,13 +429,13 @@ public class SynchronizationManager : ISynchronizationManager _logger.Information("Created new synchronizer for account {AccountName} ({AccountId})", account.Name, account.Id); - return Task.FromResult(synchronizer); + return synchronizer; } catch (Exception ex) { _logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})", account.Name, account.Id); - return Task.FromResult(null); + return null; } } @@ -463,7 +493,7 @@ public class SynchronizationManager : ISynchronizationManager var account = await _accountService.GetAccountAsync(accountId); if (account != null) { - return await CreateSynchronizerForAccountAsync(account); + return CreateSynchronizerForAccount(account); } return null; diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index c58bf34d..1303ec8f 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -8,9 +8,11 @@ using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; +using Wino.Core.Requests.Calendar; using Wino.Core.Requests.Mail; using Wino.Messaging.Server; @@ -133,6 +135,21 @@ public class WinoRequestDelegator : IWinoRequestDelegator await QueueSynchronizationAsync(sendDraftPreperationRequest.MailItem.AssignedAccount.Id); } + public async Task ExecuteAsync(CalendarOperationPreparationRequest calendarPreparationRequest) + { + IRequestBase request = calendarPreparationRequest.Operation switch + { + CalendarSynchronizerOperation.CreateEvent => new CreateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees), + // Future support for update and delete operations + // CalendarSynchronizerOperation.UpdateEvent => new UpdateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees), + // CalendarSynchronizerOperation.DeleteEvent => new DeleteCalendarEventRequest(calendarPreparationRequest.CalendarItem), + _ => throw new NotImplementedException($"Calendar operation {calendarPreparationRequest.Operation} is not implemented yet.") + }; + + await QueueRequestAsync(request, calendarPreparationRequest.CalendarItem.AssignedCalendar.AccountId); + await QueueCalendarSynchronizationAsync(calendarPreparationRequest.CalendarItem.AssignedCalendar.AccountId); + } + private async Task QueueRequestAsync(IRequestBase request, Guid accountId) { // Don't trigger synchronization for individual requests - we'll trigger it once for all requests @@ -150,4 +167,16 @@ public class WinoRequestDelegator : IWinoRequestDelegator WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options)); return Task.CompletedTask; } + + private Task QueueCalendarSynchronizationAsync(Guid accountId) + { + var options = new CalendarSynchronizationOptions() + { + AccountId = accountId, + Type = CalendarSynchronizationType.ExecuteRequests + }; + + WeakReferenceMessenger.Default.Send(new NewCalendarSynchronizationRequested(options)); + return Task.CompletedTask; + } } diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index baefed5e..ab01ffe3 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -35,6 +35,7 @@ using Wino.Core.Extensions; using Wino.Core.Http; using Wino.Core.Integration.Processors; using Wino.Core.Requests.Bundles; +using Wino.Core.Requests.Calendar; using Wino.Core.Requests.Folder; using Wino.Core.Requests.Mail; using Wino.Messaging.UI; @@ -1627,6 +1628,76 @@ public class GmailSynchronizer : WinoSynchronizer> CreateCalendarEvent(CreateCalendarEventRequest request) + { + var calendarItem = request.Item; + var attendees = request.Attendees; + + // Get the calendar for this event + var calendar = calendarItem.AssignedCalendar; + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + // Convert CalendarItem to Google Event + var googleEvent = new Event + { + Summary = calendarItem.Title, + Description = calendarItem.Description, + Location = calendarItem.Location, + Status = calendarItem.Status == CalendarItemStatus.Confirmed ? "confirmed" : "tentative" + }; + + // Set start and end time + if (calendarItem.IsAllDayEvent) + { + // All-day events use Date instead of DateTime + googleEvent.Start = new EventDateTime + { + Date = calendarItem.StartDate.ToString("yyyy-MM-dd") + }; + googleEvent.End = new EventDateTime + { + Date = calendarItem.EndDate.ToString("yyyy-MM-dd") + }; + } + else + { + // Regular events with time + googleEvent.Start = new EventDateTime + { + DateTimeDateTimeOffset = new DateTimeOffset(calendarItem.StartDate, TimeSpan.Zero), + TimeZone = calendarItem.StartTimeZone + }; + googleEvent.End = new EventDateTime + { + DateTimeDateTimeOffset = new DateTimeOffset(calendarItem.EndDate, TimeSpan.Zero), + TimeZone = calendarItem.EndTimeZone + }; + } + + // Add attendees if any + if (attendees != null && attendees.Count > 0) + { + googleEvent.Attendees = attendees.Select(a => new EventAttendee + { + Email = a.Email, + DisplayName = a.Name, + Optional = a.IsOptionalAttendee + }).ToList(); + } + + // Create the insert request + var insertRequest = _calendarService.Events.Insert(googleEvent, calendar.RemoteCalendarId); + + return [new HttpRequestBundle(insertRequest, request)]; + } + + #endregion + public override async Task KillSynchronizerAsync() { await base.KillSynchronizerAsync(); diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 42ba1f75..687b6733 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -39,6 +39,7 @@ using Wino.Core.Http; using Wino.Core.Integration.Processors; using Wino.Core.Misc; using Wino.Core.Requests.Bundles; +using Wino.Core.Requests.Calendar; using Wino.Core.Requests.Folder; using Wino.Core.Requests.Mail; @@ -1804,6 +1805,89 @@ public class OutlookSynchronizer : WinoSynchronizer> CreateCalendarEvent(CreateCalendarEventRequest request) + { + var calendarItem = request.Item; + var attendees = request.Attendees; + + // Get the calendar for this event + var calendar = calendarItem.AssignedCalendar; + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + // Convert CalendarItem to Outlook Event + var outlookEvent = new Microsoft.Graph.Models.Event + { + Subject = calendarItem.Title, + Body = new Microsoft.Graph.Models.ItemBody + { + ContentType = Microsoft.Graph.Models.BodyType.Text, + Content = calendarItem.Description + }, + Location = new Microsoft.Graph.Models.Location + { + DisplayName = calendarItem.Location + } + }; + + // Set start and end time using DateTimeTimeZone + if (calendarItem.IsAllDayEvent) + { + // All-day events + outlookEvent.IsAllDay = true; + outlookEvent.Start = new Microsoft.Graph.Models.DateTimeTimeZone + { + DateTime = calendarItem.StartDate.ToString("yyyy-MM-dd"), + TimeZone = "UTC" + }; + outlookEvent.End = new Microsoft.Graph.Models.DateTimeTimeZone + { + DateTime = calendarItem.EndDate.ToString("yyyy-MM-dd"), + TimeZone = "UTC" + }; + } + else + { + // Regular events with time + outlookEvent.IsAllDay = false; + outlookEvent.Start = new Microsoft.Graph.Models.DateTimeTimeZone + { + DateTime = calendarItem.StartDate.ToString("yyyy-MM-ddTHH:mm:ss"), + TimeZone = calendarItem.StartTimeZone ?? "UTC" + }; + outlookEvent.End = new Microsoft.Graph.Models.DateTimeTimeZone + { + DateTime = calendarItem.EndDate.ToString("yyyy-MM-ddTHH:mm:ss"), + TimeZone = calendarItem.EndTimeZone ?? "UTC" + }; + } + + // Add attendees if any + if (attendees != null && attendees.Count > 0) + { + outlookEvent.Attendees = attendees.Select(a => new Microsoft.Graph.Models.Attendee + { + EmailAddress = new Microsoft.Graph.Models.EmailAddress + { + Address = a.Email, + Name = a.Name + }, + Type = a.IsOptionalAttendee ? Microsoft.Graph.Models.AttendeeType.Optional : Microsoft.Graph.Models.AttendeeType.Required + }).ToList(); + } + + // Create the event using Graph API + var createRequest = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events.ToPostRequestInformation(outlookEvent); + + return [new HttpRequestBundle(createRequest, request)]; + } + + #endregion + public override async Task KillSynchronizerAsync() { await base.KillSynchronizerAsync(); diff --git a/Wino.Core/Synchronizers/WinoSynchronizer.cs b/Wino.Core/Synchronizers/WinoSynchronizer.cs index 468d9c89..eef70a74 100644 --- a/Wino.Core/Synchronizers/WinoSynchronizer.cs +++ b/Wino.Core/Synchronizers/WinoSynchronizer.cs @@ -18,6 +18,7 @@ using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Requests.Bundles; +using Wino.Core.Requests.Calendar; using Wino.Core.Requests.Folder; using Wino.Core.Requests.Mail; using Wino.Messaging.UI; @@ -337,10 +338,67 @@ public abstract class WinoSynchronizerSynchronization options. /// Cancellation token. /// Synchronization result that contains summary of the sync. - public Task SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default) + public async Task SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default) { - // TODO: Execute requests for calendar events. - return SynchronizeCalendarEventsInternalAsync(options, cancellationToken); + bool shouldExecuteRequests = changeRequestQueue.Any(r => r is ICalendarActionRequest); + bool shouldDelayExecution = false; + int maxExecutionDelay = 0; + + if (shouldExecuteRequests) + { + State = AccountSynchronizerState.ExecutingRequests; + + List> nativeRequests = new(); + List requestCopies = new(changeRequestQueue.Where(r => r is ICalendarActionRequest)); + + var keys = requestCopies.GroupBy(a => a.GroupingKey()); + + foreach (var group in keys) + { + var key = group.Key; + + if (key is CalendarSynchronizerOperation calendarSynchronizerOperation) + { + switch (calendarSynchronizerOperation) + { + case CalendarSynchronizerOperation.CreateEvent: + nativeRequests.AddRange(CreateCalendarEvent(group.ElementAt(0) as CreateCalendarEventRequest)); + break; + case CalendarSynchronizerOperation.UpdateEvent: + // TODO: Implement UpdateCalendarEvent + break; + case CalendarSynchronizerOperation.DeleteEvent: + // TODO: Implement DeleteCalendarEvent + break; + default: + break; + } + } + } + + // Remove processed calendar requests from queue + changeRequestQueue.RemoveAll(r => r is ICalendarActionRequest); + + Console.WriteLine($"Prepared {nativeRequests.Count()} native calendar requests"); + + await ExecuteNativeRequestsAsync(nativeRequests, cancellationToken).ConfigureAwait(false); + + // Let servers to finish their job. Sometimes the servers don't respond immediately. + shouldDelayExecution = requestCopies.Any(a => a.ResynchronizationDelay > 0); + + if (shouldDelayExecution) + { + maxExecutionDelay = requestCopies.Aggregate(0, (max, next) => Math.Max(max, next.ResynchronizationDelay)); + } + } + + if (shouldDelayExecution) + { + await Task.Delay(maxExecutionDelay, cancellationToken); + } + + // Execute the actual synchronization + return await SynchronizeCalendarEventsInternalAsync(options, cancellationToken); } /// @@ -435,6 +493,7 @@ public abstract class WinoSynchronizer> CreateCalendarEvent(CreateCalendarEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); #endregion