Creating events.
This commit is contained in:
@@ -128,6 +128,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
private readonly INavigationService _navigationService;
|
private readonly INavigationService _navigationService;
|
||||||
private readonly IKeyPressService _keyPressService;
|
private readonly IKeyPressService _keyPressService;
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||||
|
|
||||||
// Store latest rendered options.
|
// Store latest rendered options.
|
||||||
private CalendarDisplayType _currentDisplayType;
|
private CalendarDisplayType _currentDisplayType;
|
||||||
@@ -147,7 +148,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
INavigationService navigationService,
|
INavigationService navigationService,
|
||||||
IKeyPressService keyPressService,
|
IKeyPressService keyPressService,
|
||||||
IAccountCalendarStateService accountCalendarStateService,
|
IAccountCalendarStateService accountCalendarStateService,
|
||||||
IPreferencesService preferencesService)
|
IPreferencesService preferencesService,
|
||||||
|
IWinoRequestDelegator winoRequestDelegator)
|
||||||
{
|
{
|
||||||
StatePersistanceService = statePersistanceService;
|
StatePersistanceService = statePersistanceService;
|
||||||
AccountCalendarStateService = accountCalendarStateService;
|
AccountCalendarStateService = accountCalendarStateService;
|
||||||
@@ -156,6 +158,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
_navigationService = navigationService;
|
_navigationService = navigationService;
|
||||||
_keyPressService = keyPressService;
|
_keyPressService = keyPressService;
|
||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
|
_winoRequestDelegator = winoRequestDelegator;
|
||||||
|
|
||||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||||
@@ -256,24 +259,47 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
|
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
|
||||||
private async Task SaveQuickEventAsync()
|
private async Task SaveQuickEventAsync()
|
||||||
{
|
{
|
||||||
var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds;
|
try
|
||||||
|
|
||||||
var testCalendarItem = new CalendarItem
|
|
||||||
{
|
{
|
||||||
CalendarId = SelectedQuickEventAccountCalendar.Id,
|
var startDate = IsAllDay ? SelectedQuickEventDate.Value.Date : QuickEventStartTime;
|
||||||
StartDate = QuickEventStartTime,
|
var endDate = IsAllDay ? SelectedQuickEventDate.Value.Date.AddDays(1) : QuickEventEndTime;
|
||||||
DurationInSeconds = durationSeconds,
|
var durationSeconds = (endDate - startDate).TotalSeconds;
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
Description = string.Empty,
|
|
||||||
Location = Location,
|
|
||||||
Title = EventName,
|
|
||||||
Id = Guid.NewGuid()
|
|
||||||
};
|
|
||||||
|
|
||||||
IsQuickEventDialogOpen = false;
|
// Get the user's current timezone from the system
|
||||||
await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null);
|
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]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ public enum FolderSynchronizerOperation
|
|||||||
MarkFolderRead,
|
MarkFolderRead,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CalendarSynchronizerOperation
|
||||||
|
{
|
||||||
|
CreateEvent,
|
||||||
|
UpdateEvent,
|
||||||
|
DeleteEvent,
|
||||||
|
}
|
||||||
|
|
||||||
// UI requests
|
// UI requests
|
||||||
public enum MailOperation
|
public enum MailOperation
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
@@ -63,3 +64,9 @@ public interface IFolderActionRequest : IRequestBase
|
|||||||
|
|
||||||
FolderSynchronizerOperation Operation { get; }
|
FolderSynchronizerOperation Operation { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ICalendarActionRequest : IRequestBase
|
||||||
|
{
|
||||||
|
CalendarItem Item { get; }
|
||||||
|
CalendarSynchronizerOperation Operation { get; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public interface ISynchronizationManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new synchronizer for a newly added account.
|
/// Creates a new synchronizer for a newly added account.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IWinoSynchronizerBase> CreateSynchronizerForAccountAsync(MailAccount account);
|
IWinoSynchronizerBase CreateSynchronizerForAccount(MailAccount account);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroys the synchronizer for the given account.
|
/// Destroys the synchronizer for the given account.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
|
|
||||||
@@ -29,4 +30,10 @@ public interface IWinoRequestDelegator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="folderOperationPreperationRequest">Folder prep request.</param>
|
/// <param name="folderOperationPreperationRequest">Folder prep request.</param>
|
||||||
Task ExecuteAsync(FolderOperationPreperationRequest folderOperationPreperationRequest);
|
Task ExecuteAsync(FolderOperationPreperationRequest folderOperationPreperationRequest);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepares and queues calendar action requests for proper synchronizers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendarOperationPreparationRequest">Calendar preparation request.</param>
|
||||||
|
Task ExecuteAsync(CalendarOperationPreparationRequest calendarOperationPreparationRequest);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates the options for preparing calendar operation requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Operation">Calendar operation to execute (Create, Update, Delete).</param>
|
||||||
|
/// <param name="CalendarItem">Calendar item to operate on.</param>
|
||||||
|
/// <param name="Attendees">List of attendees for the calendar event.</param>
|
||||||
|
public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List<CalendarEventAttendee> Attendees);
|
||||||
|
//{
|
||||||
|
// public CalendarOperationPreparationRequest(CalendarItem calendarItem)
|
||||||
|
// : this(calendarItem ?? throw new ArgumentNullException(nameof(calendarItem)), null)
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -29,6 +30,10 @@ public abstract record FolderRequestBase(MailItemFolder Folder, FolderSynchroniz
|
|||||||
public virtual object GroupingKey() { return Operation; }
|
public virtual object GroupingKey() { return Operation; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract record CalendarRequestBase(CalendarItem Item) : RequestBase<CalendarSynchronizerOperation>, ICalendarActionRequest
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public class BatchCollection<TRequestType> : List<TRequestType>, IUIChangeRequest where TRequestType : IUIChangeRequest
|
public class BatchCollection<TRequestType> : List<TRequestType>, IUIChangeRequest where TRequestType : IUIChangeRequest
|
||||||
{
|
{
|
||||||
public BatchCollection(IEnumerable<TRequestType> collection) : base(collection)
|
public BatchCollection(IEnumerable<TRequestType> collection) : base(collection)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public record CreateCalendarEventRequest(CalendarItem Item, List<CalendarEventAttendee> Attendees) : CalendarRequestBase(Item)
|
||||||
|
{
|
||||||
|
public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.CreateEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After successful creation, we need to resync to get the remote event ID.
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -191,7 +191,8 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">Request to queue</param>
|
/// <param name="request">Request to queue</param>
|
||||||
/// <param name="accountId">Account ID to queue the request for</param>
|
/// <param name="accountId">Account ID to queue the request for</param>
|
||||||
@@ -214,28 +215,57 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
|
|
||||||
if (triggerSynchronization)
|
if (triggerSynchronization)
|
||||||
{
|
{
|
||||||
// Trigger synchronization to execute the queued request
|
// Determine if this is a calendar or mail operation
|
||||||
_logger.Debug("Triggering synchronization to execute queued request for account {AccountId}", accountId);
|
bool isCalendarOperation = request is ICalendarActionRequest;
|
||||||
|
|
||||||
var synchronizationOptions = new MailSynchronizationOptions()
|
if (isCalendarOperation)
|
||||||
{
|
{
|
||||||
AccountId = accountId,
|
// Trigger calendar synchronization
|
||||||
Type = MailSynchronizationType.ExecuteRequests
|
_logger.Debug("Triggering calendar synchronization to execute queued request for account {AccountId}", accountId);
|
||||||
};
|
|
||||||
|
|
||||||
// Trigger synchronization asynchronously without waiting for completion
|
var calendarSyncOptions = new CalendarSynchronizationOptions()
|
||||||
// This matches the pattern used in WinoRequestDelegator
|
{
|
||||||
_ = Task.Run(async () =>
|
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);
|
AccountId = accountId,
|
||||||
}
|
Type = MailSynchronizationType.ExecuteRequests
|
||||||
catch (Exception ex)
|
};
|
||||||
|
|
||||||
|
// 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
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="account">Account to create synchronizer for</param>
|
/// <param name="account">Account to create synchronizer for</param>
|
||||||
/// <returns>Created synchronizer</returns>
|
/// <returns>Created synchronizer</returns>
|
||||||
public Task<IWinoSynchronizerBase> CreateSynchronizerForAccountAsync(MailAccount account)
|
public IWinoSynchronizerBase CreateSynchronizerForAccount(MailAccount account)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
|
|
||||||
@@ -399,13 +429,13 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
_logger.Information("Created new synchronizer for account {AccountName} ({AccountId})",
|
_logger.Information("Created new synchronizer for account {AccountName} ({AccountId})",
|
||||||
account.Name, account.Id);
|
account.Name, account.Id);
|
||||||
|
|
||||||
return Task.FromResult(synchronizer);
|
return synchronizer;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})",
|
_logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})",
|
||||||
account.Name, account.Id);
|
account.Name, account.Id);
|
||||||
return Task.FromResult<IWinoSynchronizerBase>(null);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,7 +493,7 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
var account = await _accountService.GetAccountAsync(accountId);
|
var account = await _accountService.GetAccountAsync(accountId);
|
||||||
if (account != null)
|
if (account != null)
|
||||||
{
|
{
|
||||||
return await CreateSynchronizerForAccountAsync(account);
|
return CreateSynchronizerForAccount(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ using Wino.Core.Domain;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
@@ -133,6 +135,21 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
|||||||
await QueueSynchronizationAsync(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
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)
|
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
{
|
{
|
||||||
// Don't trigger synchronization for individual requests - we'll trigger it once for all requests
|
// 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));
|
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options));
|
||||||
return Task.CompletedTask;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ using Wino.Core.Extensions;
|
|||||||
using Wino.Core.Http;
|
using Wino.Core.Http;
|
||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
@@ -1627,6 +1628,76 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Calendar Operations
|
||||||
|
|
||||||
|
public override List<IRequestBundle<IClientServiceRequest>> 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<IClientServiceRequest>(insertRequest, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public override async Task KillSynchronizerAsync()
|
public override async Task KillSynchronizerAsync()
|
||||||
{
|
{
|
||||||
await base.KillSynchronizerAsync();
|
await base.KillSynchronizerAsync();
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ using Wino.Core.Http;
|
|||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
using Wino.Core.Misc;
|
using Wino.Core.Misc;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
|
|
||||||
@@ -1804,6 +1805,89 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
return !localCalendarName.Equals(remoteCalendarName, StringComparison.OrdinalIgnoreCase);
|
return !localCalendarName.Equals(remoteCalendarName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Calendar Operations
|
||||||
|
|
||||||
|
public override List<IRequestBundle<RequestInformation>> 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<RequestInformation>(createRequest, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public override async Task KillSynchronizerAsync()
|
public override async Task KillSynchronizerAsync()
|
||||||
{
|
{
|
||||||
await base.KillSynchronizerAsync();
|
await base.KillSynchronizerAsync();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Wino.Core.Domain.Models.Folders;
|
|||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
@@ -337,10 +338,67 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
/// <param name="options">Synchronization options.</param>
|
/// <param name="options">Synchronization options.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>Synchronization result that contains summary of the sync.</returns>
|
/// <returns>Synchronization result that contains summary of the sync.</returns>
|
||||||
public Task<CalendarSynchronizationResult> SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
|
public async Task<CalendarSynchronizationResult> SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// TODO: Execute requests for calendar events.
|
bool shouldExecuteRequests = changeRequestQueue.Any(r => r is ICalendarActionRequest);
|
||||||
return SynchronizeCalendarEventsInternalAsync(options, cancellationToken);
|
bool shouldDelayExecution = false;
|
||||||
|
int maxExecutionDelay = 0;
|
||||||
|
|
||||||
|
if (shouldExecuteRequests)
|
||||||
|
{
|
||||||
|
State = AccountSynchronizerState.ExecutingRequests;
|
||||||
|
|
||||||
|
List<IRequestBundle<TBaseRequest>> nativeRequests = new();
|
||||||
|
List<IRequestBase> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -435,6 +493,7 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
|
|
||||||
#region Calendar Operations
|
#region Calendar Operations
|
||||||
|
|
||||||
|
public virtual List<IRequestBundle<TBaseRequest>> CreateCalendarEvent(CreateCalendarEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user