diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index 7552de6e..e391b957 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -11,6 +11,7 @@ using Wino.Calendar.Models.CalendarTypeStrategies; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Calendar; +using Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies; using Wino.Core.Domain.Models.Navigation; using Wino.Core.MenuItems; using Wino.Core.ViewModels; diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index 0d9962cd..b2149b22 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -12,6 +12,10 @@ AlwaysMoveTo, MoveToFocused, Archive, + } + + public enum FolderSynchronizerOperation + { RenameFolder, EmptyFolder, MarkFolderRead, diff --git a/Wino.Core.Domain/Interfaces/IRequestBundle.cs b/Wino.Core.Domain/Interfaces/IRequestBundle.cs index b573d4f3..018bae3f 100644 --- a/Wino.Core.Domain/Interfaces/IRequestBundle.cs +++ b/Wino.Core.Domain/Interfaces/IRequestBundle.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; namespace Wino.Core.Domain.Interfaces @@ -10,7 +9,7 @@ namespace Wino.Core.Domain.Interfaces public interface IRequestBundle { string BundleId { get; set; } - IRequestBase Request { get; } + IUIChangeRequest UIChangeRequest { get; } } /// @@ -20,15 +19,25 @@ namespace Wino.Core.Domain.Interfaces public interface IRequestBundle : IRequestBundle { TRequest NativeRequest { get; } + IRequestBase Request { get; } } - public interface IRequestBase : IClientMessage + public interface IRequestBase : IClientMessage, IUIChangeRequest { /// - /// Synchronizer option to perform. + /// Whether synchronizations should be delayed after executing this request. + /// Specially Outlook sometimes don't report changes back immidiately after sending the API request. + /// This results following synchronization to miss the changes. + /// We add small delay for the following synchronization after executing current requests to overcome this issue. + /// Default is false. /// - MailSynchronizerOperation Operation { get; } + int ResynchronizationDelay { get; } + object GroupingKey(); + } + + public interface IUIChangeRequest + { /// /// UI changes to apply to the item before sending the request to the server. /// Changes here only affect the UI, not the item itself. @@ -40,30 +49,18 @@ namespace Wino.Core.Domain.Interfaces /// Reverts the UI changes applied by if the request fails. /// void RevertUIChanges(); - - /// - /// Whether synchronizations should be delayed after executing this request. - /// Specially Outlook sometimes don't report changes back immidiately after sending the API request. - /// This results following synchronization to miss the changes. - /// We add small delay for the following synchronization after executing current requests to overcome this issue. - /// Default is false. - /// - int ResynchronizationDelay { get; } } - public interface IRequest : IRequestBase + public interface IMailActionRequest : IRequestBase { MailCopy Item { get; } - IBatchChangeRequest CreateBatch(IEnumerable requests); + MailSynchronizerOperation Operation { get; } } - public interface IFolderRequest : IRequestBase + public interface IFolderActionRequest : IRequestBase { MailItemFolder Folder { get; } - } - public interface IBatchChangeRequest : IRequestBase - { - IEnumerable Items { get; } + FolderSynchronizerOperation Operation { get; } } } diff --git a/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs b/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs index 32b1aacf..26cfb574 100644 --- a/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs +++ b/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs @@ -12,7 +12,7 @@ namespace Wino.Core.Domain.Interfaces /// /// /// Base request that synchronizer can execute. - Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request); + Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request); /// /// Prepares proper Wino requests for synchronizers to execute categorized by AccountId and FolderId. @@ -21,6 +21,6 @@ namespace Wino.Core.Domain.Interfaces /// Selected mails. /// When required folder target is not available for account. /// Base request that synchronizer can execute. - Task> PrepareRequestsAsync(MailOperationPreperationRequest request); + Task> PrepareRequestsAsync(MailOperationPreperationRequest request); } } diff --git a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/BaseCalendarTypeDrawingStrategy.cs b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/BaseCalendarTypeDrawingStrategy.cs index f5edf4c6..bfb164ba 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/BaseCalendarTypeDrawingStrategy.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/BaseCalendarTypeDrawingStrategy.cs @@ -2,7 +2,7 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Models.CalendarTypeStrategies +namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies { public abstract class BaseCalendarTypeDrawingStrategy { diff --git a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/DayCalendarDrawingStrategy.cs b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/DayCalendarDrawingStrategy.cs index 931edad6..4b2a3256 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/DayCalendarDrawingStrategy.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/DayCalendarDrawingStrategy.cs @@ -2,7 +2,7 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Models.CalendarTypeStrategies +namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies { public class DayCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy { diff --git a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/WeekCalendarDrawingStrategy.cs b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/WeekCalendarDrawingStrategy.cs index bd83eafd..7a1c108d 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/WeekCalendarDrawingStrategy.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarTypeStrategies/WeekCalendarDrawingStrategy.cs @@ -1,11 +1,11 @@ using System; using Wino.Core.Domain.Models.Calendar; -namespace Wino.Calendar.Models.CalendarTypeStrategies +namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies { public class WeekCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy { - public WeekCalendarDrawingStrategy(CalendarSettings settings) : base(settings, Core.Domain.Enums.CalendarDisplayType.Week) { } + public WeekCalendarDrawingStrategy(CalendarSettings settings) : base(settings, Enums.CalendarDisplayType.Week) { } public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount) => new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(7 * 2)); diff --git a/Wino.Core.Domain/Models/Requests/RequestBase.cs b/Wino.Core.Domain/Models/Requests/RequestBase.cs index 9b354bf4..3f65aca1 100644 --- a/Wino.Core.Domain/Models/Requests/RequestBase.cs +++ b/Wino.Core.Domain/Models/Requests/RequestBase.cs @@ -1,34 +1,40 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; namespace Wino.Core.Domain.Models.Requests { - public abstract record RequestBase(MailCopy Item, MailSynchronizerOperation Operation) : IRequest - where TBatchRequestType : IBatchChangeRequest + public abstract record RequestBase where TOperation : Enum { - public abstract IBatchChangeRequest CreateBatch(IEnumerable requests); - public abstract void ApplyUIChanges(); - public abstract void RevertUIChanges(); - + public virtual void ApplyUIChanges() { } + public virtual void RevertUIChanges() { } public virtual int ResynchronizationDelay => 0; + public abstract TOperation Operation { get; } + public virtual object GroupingKey() { return Operation; } } - public abstract record FolderRequestBase(MailItemFolder Folder, MailSynchronizerOperation Operation) : IFolderRequest + public abstract record MailRequestBase(MailCopy Item) : RequestBase, IMailActionRequest { - public abstract void ApplyUIChanges(); - public abstract void RevertUIChanges(); - - public virtual int ResynchronizationDelay => 0; } - public abstract record BatchRequestBase(IEnumerable Items, MailSynchronizerOperation Operation) : IBatchChangeRequest + public abstract record FolderRequestBase(MailItemFolder Folder, FolderSynchronizerOperation Operation) : IFolderActionRequest { public abstract void ApplyUIChanges(); public abstract void RevertUIChanges(); public virtual int ResynchronizationDelay => 0; - public virtual bool ExecuteSerialBatch => false; + + public virtual object GroupingKey() { return Operation; } + } + + public class BatchCollection : List, IUIChangeRequest where TRequestType : IUIChangeRequest + { + public BatchCollection(IEnumerable collection) : base(collection) + { + } + public void ApplyUIChanges() => ForEach(x => x.ApplyUIChanges()); + public void RevertUIChanges() => ForEach(x => x.RevertUIChanges()); } } diff --git a/Wino.Core/Extensions/ListExtensions.cs b/Wino.Core/Extensions/ListExtensions.cs index 7330c44e..d0f89781 100644 --- a/Wino.Core/Extensions/ListExtensions.cs +++ b/Wino.Core/Extensions/ListExtensions.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; namespace Wino.Core.Extensions { @@ -19,40 +17,5 @@ namespace Wino.Core.Extensions return nodes.Concat(descendants); } - - public static IEnumerable CreateBatch(this IEnumerable> items) - { - IBatchChangeRequest batch = null; - - foreach (var group in items) - { - var key = group.Key; - } - - yield return batch; - } - - public static void AddSorted(this List @this, T item) where T : IComparable - { - if (@this.Count == 0) - { - @this.Add(item); - return; - } - if (@this[@this.Count - 1].CompareTo(item) <= 0) - { - @this.Add(item); - return; - } - if (@this[0].CompareTo(item) >= 0) - { - @this.Insert(0, item); - return; - } - int index = @this.BinarySearch(item); - if (index < 0) - index = ~index; - @this.Insert(index, item); - } } } diff --git a/Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs b/Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs index 3a38f6ae..ff664d0c 100644 --- a/Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs +++ b/Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs @@ -2,7 +2,8 @@ using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Requests; +using Wino.Core.Requests.Folder; +using Wino.Core.Requests.Mail; namespace Wino.Core.Integration.Json { diff --git a/Wino.Core/Misc/RequestComparer.cs b/Wino.Core/Misc/RequestComparer.cs deleted file mode 100644 index e884e5ea..00000000 --- a/Wino.Core/Misc/RequestComparer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Requests; - -namespace Wino.Core.Misc -{ - /// - /// This is incomplete. - /// - internal class RequestComparer : IEqualityComparer - { - public bool Equals(IRequestBase x, IRequestBase y) - { - if (x is MoveRequest sourceMoveRequest && y is MoveRequest targetMoveRequest) - { - return sourceMoveRequest.FromFolder.Id == targetMoveRequest.FromFolder.Id && sourceMoveRequest.ToFolder.Id == targetMoveRequest.ToFolder.Id; - } - else if (x is ChangeFlagRequest sourceFlagRequest && y is ChangeFlagRequest targetFlagRequest) - { - return sourceFlagRequest.IsFlagged == targetFlagRequest.IsFlagged; - } - else if (x is MarkReadRequest sourceMarkReadRequest && y is MarkReadRequest targetMarkReadRequest) - { - return sourceMarkReadRequest.Item.IsRead == targetMarkReadRequest.Item.IsRead; - } - else if (x is DeleteRequest sourceDeleteRequest && y is DeleteRequest targetDeleteRequest) - { - return sourceDeleteRequest.MailItem.AssignedFolder.Id == targetDeleteRequest.MailItem.AssignedFolder.Id; - } - - return true; - } - - public int GetHashCode(IRequestBase obj) => obj.Operation.GetHashCode(); - } -} diff --git a/Wino.Core/Requests/AlwaysMoveToRequest.cs b/Wino.Core/Requests/AlwaysMoveToRequest.cs deleted file mode 100644 index 35f2d995..00000000 --- a/Wino.Core/Requests/AlwaysMoveToRequest.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using Wino.Core.Domain.Entities.Mail; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Requests; - -namespace Wino.Core.Requests -{ - public record AlwaysMoveToRequest(MailCopy Item, bool MoveToFocused) : RequestBase(Item, MailSynchronizerOperation.AlwaysMoveTo) - { - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchAlwaysMoveToRequest(matchingItems, MoveToFocused); - - public override void ApplyUIChanges() - { - - } - - public override void RevertUIChanges() - { - - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchAlwaysMoveToRequest(IEnumerable Items, bool MoveToFocused) : BatchRequestBase(Items, MailSynchronizerOperation.AlwaysMoveTo) - { - public override void ApplyUIChanges() - { - - } - - public override void RevertUIChanges() - { - - } - } -} diff --git a/Wino.Core/Requests/Bundles/RequestBundle.cs b/Wino.Core/Requests/Bundles/RequestBundle.cs index 2f73e2dc..7f8fe6e9 100644 --- a/Wino.Core/Requests/Bundles/RequestBundle.cs +++ b/Wino.Core/Requests/Bundles/RequestBundle.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -8,25 +7,9 @@ using Wino.Core.Domain.Interfaces; namespace Wino.Core.Requests.Bundles { - /// - /// Bundle that encapsulates batch request and native request without a response. - /// - /// Http type for each integrator. eg. ClientServiceRequest for Gmail and RequestInformation for Microsoft Graph. - /// Native type to send via http. - /// Batch request that is generated by base synchronizer. - public record HttpRequestBundle(TRequest NativeRequest, IRequestBase Request) : IRequestBundle + public record HttpRequestBundle(TRequest NativeRequest, IUIChangeRequest UIChangeRequest, IRequestBase Request = null) : IRequestBundle { public string BundleId { get; set; } = string.Empty; - - public override string ToString() - { - if (Request is IRequest singleRequest) - return $"Single {singleRequest.Operation}. No response."; - else if (Request is IBatchChangeRequest batchChangeRequest) - return $"Batch {batchChangeRequest.Operation} for {batchChangeRequest.Items.Count()} items. No response."; - else - return "Unknown http request bundle."; - } } /// @@ -43,15 +26,5 @@ namespace Wino.Core.Requests.Bundles return JsonSerializer.Deserialize(content) ?? throw new InvalidOperationException("Invalid Http Response Deserialization"); } - - public override string ToString() - { - if (Request is IRequest singleRequest) - return $"Single {singleRequest.Operation}. Expecting '{typeof(TResponse).FullName}' type."; - else if (Request is IBatchChangeRequest batchChangeRequest) - return $"Batch {batchChangeRequest.Operation} for {batchChangeRequest.Items.Count()} items. Expecting '{typeof(TResponse).FullName}' type."; - else - return "Unknown http request bundle."; - } } } diff --git a/Wino.Core/Requests/Bundles/TaskRequestBundle.cs b/Wino.Core/Requests/Bundles/TaskRequestBundle.cs index 32b0991e..96e0bb69 100644 --- a/Wino.Core/Requests/Bundles/TaskRequestBundle.cs +++ b/Wino.Core/Requests/Bundles/TaskRequestBundle.cs @@ -5,19 +5,27 @@ using Wino.Core.Domain.Interfaces; namespace Wino.Core.Requests.Bundles { - //public abstract record TaskRequestBundleBase() - //{ - // public abstract Task ExecuteAsync(ImapClient executorImapClient); - //} + public class ImapRequest + { + public Func IntegratorTask { get; } + public IRequestBase Request { get; } - //public record TaskRequestBundle(Func NativeRequest) : TaskRequestBundleBase - //{ - // public override async Task ExecuteAsync(ImapClient executorImapClient) => await NativeRequest(executorImapClient).ConfigureAwait(false); - //} + public ImapRequest(Func integratorTask, IRequestBase request) + { + IntegratorTask = integratorTask; + Request = request; + } + } - public record ImapRequest(Func IntegratorTask, IRequestBase Request) { } + public class ImapRequest : ImapRequest where TRequestBaseType : IRequestBase + { + public ImapRequest(Func integratorTask, TRequestBaseType request) + : base((client, request) => integratorTask(client, (TRequestBaseType)request), request) + { + } + } - public record ImapRequestBundle(ImapRequest NativeRequest, IRequestBase Request) : IRequestBundle + public record ImapRequestBundle(ImapRequest NativeRequest, IRequestBase Request, IUIChangeRequest UIChangeRequest) : IRequestBundle { public string BundleId { get; set; } = Guid.NewGuid().ToString(); } diff --git a/Wino.Core/Requests/ChangeFlagRequest.cs b/Wino.Core/Requests/ChangeFlagRequest.cs deleted file mode 100644 index 47aa0018..00000000 --- a/Wino.Core/Requests/ChangeFlagRequest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; -using Wino.Core.Domain.Entities.Mail; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Requests; -using Wino.Messaging.UI; - -namespace Wino.Core.Requests -{ - public record ChangeFlagRequest(MailCopy Item, bool IsFlagged) : RequestBase(Item, MailSynchronizerOperation.ChangeFlag), - ICustomFolderSynchronizationRequest - { - public List SynchronizationFolderIds => [Item.FolderId]; - - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchChangeFlagRequest(matchingItems, IsFlagged); - - public override void ApplyUIChanges() - { - Item.IsFlagged = IsFlagged; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); - } - - public override void RevertUIChanges() - { - Item.IsFlagged = !IsFlagged; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchChangeFlagRequest(IEnumerable Items, bool IsFlagged) : BatchRequestBase(Items, MailSynchronizerOperation.ChangeFlag) - { - public override void ApplyUIChanges() - { - Items.ForEach(item => - { - item.Item.IsFlagged = IsFlagged; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item.Item)); - }); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => - { - item.Item.IsFlagged = !IsFlagged; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item.Item)); - }); - } - } -} diff --git a/Wino.Core/Requests/CreateDraftRequest.cs b/Wino.Core/Requests/CreateDraftRequest.cs deleted file mode 100644 index 026c5d4e..00000000 --- a/Wino.Core/Requests/CreateDraftRequest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Domain.Models.Requests; -using Wino.Messaging.UI; - -namespace Wino.Core.Requests -{ - public record CreateDraftRequest(DraftPreparationRequest DraftPreperationRequest) - : RequestBase(DraftPreperationRequest.CreatedLocalDraftCopy, MailSynchronizerOperation.CreateDraft), - ICustomFolderSynchronizationRequest - { - public List SynchronizationFolderIds => - [ - DraftPreperationRequest.CreatedLocalDraftCopy.AssignedFolder.Id - ]; - - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchCreateDraftRequest(matchingItems, DraftPreperationRequest); - - public override void ApplyUIChanges() - { - // No need for it since Draft folder is automatically navigated and draft item is added + selected. - // We only need to revert changes in case of network fails to create the draft. - } - - public override void RevertUIChanges() - { - WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record class BatchCreateDraftRequest(IEnumerable Items, DraftPreparationRequest DraftPreperationRequest) - : BatchRequestBase(Items, MailSynchronizerOperation.CreateDraft) - { - public override void ApplyUIChanges() - { - // No need for it since Draft folder is automatically navigated and draft item is added + selected. - // We only need to revert changes in case of network fails to create the draft. - } - - public override void RevertUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); - } - } -} diff --git a/Wino.Core/Requests/EmptyFolderRequest.cs b/Wino.Core/Requests/Folder/EmptyFolderRequest.cs similarity index 83% rename from Wino.Core/Requests/EmptyFolderRequest.cs rename to Wino.Core/Requests/Folder/EmptyFolderRequest.cs index 13fa882e..8ae559c2 100644 --- a/Wino.Core/Requests/EmptyFolderRequest.cs +++ b/Wino.Core/Requests/Folder/EmptyFolderRequest.cs @@ -7,9 +7,9 @@ using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Requests; using Wino.Messaging.UI; -namespace Wino.Core.Requests +namespace Wino.Core.Requests.Folder { - public record EmptyFolderRequest(MailItemFolder Folder, List MailsToDelete) : FolderRequestBase(Folder, MailSynchronizerOperation.EmptyFolder), ICustomFolderSynchronizationRequest + public record EmptyFolderRequest(MailItemFolder Folder, List MailsToDelete) : FolderRequestBase(Folder, FolderSynchronizerOperation.EmptyFolder), ICustomFolderSynchronizationRequest { public override void ApplyUIChanges() { diff --git a/Wino.Core/Requests/MarkFolderAsReadRequest.cs b/Wino.Core/Requests/Folder/MarkFolderAsReadRequest.cs similarity index 84% rename from Wino.Core/Requests/MarkFolderAsReadRequest.cs rename to Wino.Core/Requests/Folder/MarkFolderAsReadRequest.cs index 9703247b..17e989b2 100644 --- a/Wino.Core/Requests/MarkFolderAsReadRequest.cs +++ b/Wino.Core/Requests/Folder/MarkFolderAsReadRequest.cs @@ -7,9 +7,9 @@ using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Requests; using Wino.Messaging.UI; -namespace Wino.Core.Requests +namespace Wino.Core.Requests.Folder { - public record MarkFolderAsReadRequest(MailItemFolder Folder, List MailsToMarkRead) : FolderRequestBase(Folder, MailSynchronizerOperation.MarkFolderRead), ICustomFolderSynchronizationRequest + public record MarkFolderAsReadRequest(MailItemFolder Folder, List MailsToMarkRead) : FolderRequestBase(Folder, FolderSynchronizerOperation.MarkFolderRead), ICustomFolderSynchronizationRequest { public override void ApplyUIChanges() { diff --git a/Wino.Core/Requests/RenameFolderRequest.cs b/Wino.Core/Requests/Folder/RenameFolderRequest.cs similarity index 89% rename from Wino.Core/Requests/RenameFolderRequest.cs rename to Wino.Core/Requests/Folder/RenameFolderRequest.cs index b42f8013..eada5e7d 100644 --- a/Wino.Core/Requests/RenameFolderRequest.cs +++ b/Wino.Core/Requests/Folder/RenameFolderRequest.cs @@ -4,9 +4,9 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Requests; using Wino.Messaging.UI; -namespace Wino.Core.Requests +namespace Wino.Core.Requests.Folder { - public record RenameFolderRequest(MailItemFolder Folder, string CurrentFolderName, string NewFolderName) : FolderRequestBase(Folder, MailSynchronizerOperation.RenameFolder) + public record RenameFolderRequest(MailItemFolder Folder, string CurrentFolderName, string NewFolderName) : FolderRequestBase(Folder, FolderSynchronizerOperation.RenameFolder) { public override void ApplyUIChanges() { diff --git a/Wino.Core/Requests/Mail/AlwaysMoveToRequest.cs b/Wino.Core/Requests/Mail/AlwaysMoveToRequest.cs new file mode 100644 index 00000000..31cc64bd --- /dev/null +++ b/Wino.Core/Requests/Mail/AlwaysMoveToRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests.Mail +{ + public record AlwaysMoveToRequest(MailCopy Item, bool MoveToFocused) : MailRequestBase(Item) + { + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.AlwaysMoveTo; + } + + public class BatchAlwaysMoveToRequest : BatchCollection + { + public BatchAlwaysMoveToRequest(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/Wino.Core/Requests/ArchiveRequest.cs b/Wino.Core/Requests/Mail/ArchiveRequest.cs similarity index 59% rename from Wino.Core/Requests/ArchiveRequest.cs rename to Wino.Core/Requests/Mail/ArchiveRequest.cs index 3e957541..bc37bff3 100644 --- a/Wino.Core/Requests/ArchiveRequest.cs +++ b/Wino.Core/Requests/Mail/ArchiveRequest.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Requests; using Wino.Messaging.UI; -namespace Wino.Core.Requests +namespace Wino.Core.Requests.Mail { /// /// Archive message request. @@ -20,7 +18,8 @@ namespace Wino.Core.Requests /// Mail to archive /// Source folder. /// Optional Target folder. Required for ImapSynchronizer and OutlookSynchronizer. - public record ArchiveRequest(bool IsArchiving, MailCopy Item, MailItemFolder FromFolder, MailItemFolder ToFolder = null) : RequestBase(Item, MailSynchronizerOperation.Archive), ICustomFolderSynchronizationRequest + public record ArchiveRequest(bool IsArchiving, MailCopy Item, MailItemFolder FromFolder, MailItemFolder ToFolder = null) + : MailRequestBase(Item), ICustomFolderSynchronizationRequest { public List SynchronizationFolderIds { @@ -37,8 +36,7 @@ namespace Wino.Core.Requests } } - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchArchiveRequest(IsArchiving, matchingItems, FromFolder, ToFolder); + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Archive; public override void ApplyUIChanges() { @@ -51,17 +49,10 @@ namespace Wino.Core.Requests } } - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchArchiveRequest(bool IsArchiving, IEnumerable Items, MailItemFolder FromFolder, MailItemFolder ToFolder = null) : BatchRequestBase(Items, MailSynchronizerOperation.Archive) + public class BatchArchiveRequest : BatchCollection { - public override void ApplyUIChanges() + public BatchArchiveRequest(IEnumerable collection) : base(collection) { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); } } } diff --git a/Wino.Core/Requests/Mail/ChangeFlagRequest.cs b/Wino.Core/Requests/Mail/ChangeFlagRequest.cs new file mode 100644 index 00000000..56789c31 --- /dev/null +++ b/Wino.Core/Requests/Mail/ChangeFlagRequest.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail +{ + public record ChangeFlagRequest(MailCopy Item, bool IsFlagged) : MailRequestBase(Item), + ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds => [Item.FolderId]; + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.ChangeFlag; + + public override void ApplyUIChanges() + { + Item.IsFlagged = IsFlagged; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); + } + + public override void RevertUIChanges() + { + Item.IsFlagged = !IsFlagged; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); + } + } + + public class BatchChangeFlagRequest : BatchCollection + { + public BatchChangeFlagRequest(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/Wino.Core/Requests/Mail/CreateDraftRequest.cs b/Wino.Core/Requests/Mail/CreateDraftRequest.cs new file mode 100644 index 00000000..beb09fa6 --- /dev/null +++ b/Wino.Core/Requests/Mail/CreateDraftRequest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.MailItem; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail +{ + public record CreateDraftRequest(DraftPreparationRequest DraftPreperationRequest) + : MailRequestBase(DraftPreperationRequest.CreatedLocalDraftCopy), + ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds => + [ + DraftPreperationRequest.CreatedLocalDraftCopy.AssignedFolder.Id + ]; + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.CreateDraft; + + public override void RevertUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); + } + } +} diff --git a/Wino.Core/Requests/DeleteRequest.cs b/Wino.Core/Requests/Mail/DeleteRequest.cs similarity index 52% rename from Wino.Core/Requests/DeleteRequest.cs rename to Wino.Core/Requests/Mail/DeleteRequest.cs index 5d929f53..de6e9ffb 100644 --- a/Wino.Core/Requests/DeleteRequest.cs +++ b/Wino.Core/Requests/Mail/DeleteRequest.cs @@ -1,27 +1,24 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Requests; using Wino.Messaging.UI; -namespace Wino.Core.Requests +namespace Wino.Core.Requests.Mail { /// /// Hard delete request. This request will delete the mail item from the server without moving it to the trash folder. /// /// Item to delete permanently. - public record DeleteRequest(MailCopy MailItem) : RequestBase(MailItem, MailSynchronizerOperation.Delete), + public record DeleteRequest(MailCopy MailItem) : MailRequestBase(MailItem), ICustomFolderSynchronizationRequest { public List SynchronizationFolderIds => [Item.FolderId]; - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchDeleteRequest(matchingItems); + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Delete; public override void ApplyUIChanges() { @@ -34,17 +31,10 @@ namespace Wino.Core.Requests } } - [EditorBrowsable(EditorBrowsableState.Never)] - public record class BatchDeleteRequest(IEnumerable Items) : BatchRequestBase(Items, MailSynchronizerOperation.Delete) + public class BatchDeleteRequest : BatchCollection { - public override void ApplyUIChanges() + public BatchDeleteRequest(IEnumerable collection) : base(collection) { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); } } } diff --git a/Wino.Core/Requests/Mail/MarkReadRequest.cs b/Wino.Core/Requests/Mail/MarkReadRequest.cs new file mode 100644 index 00000000..e5b80e44 --- /dev/null +++ b/Wino.Core/Requests/Mail/MarkReadRequest.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail +{ + public record MarkReadRequest(MailCopy Item, bool IsRead) : MailRequestBase(Item), ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds => [Item.FolderId]; + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.MarkRead; + + public override void ApplyUIChanges() + { + Item.IsRead = IsRead; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); + } + + public override void RevertUIChanges() + { + Item.IsRead = !IsRead; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); + } + } + + public class BatchMarkReadRequest : BatchCollection + { + public BatchMarkReadRequest(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/Wino.Core/Requests/Mail/MoveRequest.cs b/Wino.Core/Requests/Mail/MoveRequest.cs new file mode 100644 index 00000000..8fc198ef --- /dev/null +++ b/Wino.Core/Requests/Mail/MoveRequest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail +{ + public record MoveRequest(MailCopy Item, MailItemFolder FromFolder, MailItemFolder ToFolder) + : MailRequestBase(Item), ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds => new() { FromFolder.Id, ToFolder.Id }; + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Move; + + public override void ApplyUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); + } + + public override void RevertUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item)); + } + } + + public class BatchMoveRequest : BatchCollection, IUIChangeRequest + { + public BatchMoveRequest(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/Wino.Core/Requests/Mail/MoveToFocusedRequest.cs b/Wino.Core/Requests/Mail/MoveToFocusedRequest.cs new file mode 100644 index 00000000..f9b4fea6 --- /dev/null +++ b/Wino.Core/Requests/Mail/MoveToFocusedRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests.Mail +{ + public record MoveToFocusedRequest(MailCopy Item, bool MoveToFocused) : MailRequestBase(Item) + { + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.MoveToFocused; + } + + public class BatchMoveToFocusedRequest : BatchCollection + { + public BatchMoveToFocusedRequest(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/Wino.Core/Requests/Mail/SendDraftRequest.cs b/Wino.Core/Requests/Mail/SendDraftRequest.cs new file mode 100644 index 00000000..0dfc56fe --- /dev/null +++ b/Wino.Core/Requests/Mail/SendDraftRequest.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.MailItem; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail +{ + public record SendDraftRequest(SendDraftPreparationRequest Request) + : MailRequestBase(Request.MailItem), + ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds + { + get + { + var folderIds = new List { Request.DraftFolder.Id }; + + if (Request.SentFolder != null) + { + folderIds.Add(Request.SentFolder.Id); + } + + return folderIds; + } + } + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Send; + + public override void ApplyUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); + } + + public override void RevertUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item)); + } + } +} diff --git a/Wino.Core/Requests/MarkReadRequest.cs b/Wino.Core/Requests/MarkReadRequest.cs deleted file mode 100644 index a3f38e06..00000000 --- a/Wino.Core/Requests/MarkReadRequest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; -using Wino.Core.Domain.Entities.Mail; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Requests; -using Wino.Messaging.UI; - -namespace Wino.Core.Requests -{ - public record MarkReadRequest(MailCopy Item, bool IsRead) : RequestBase(Item, MailSynchronizerOperation.MarkRead), - ICustomFolderSynchronizationRequest - { - public List SynchronizationFolderIds => [Item.FolderId]; - - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchMarkReadRequest(matchingItems, IsRead); - - public override void ApplyUIChanges() - { - Item.IsRead = IsRead; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); - } - - public override void RevertUIChanges() - { - Item.IsRead = !IsRead; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item)); - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchMarkReadRequest(IEnumerable Items, bool IsRead) : BatchRequestBase(Items, MailSynchronizerOperation.MarkRead) - { - public override void ApplyUIChanges() - { - Items.ForEach(item => - { - item.Item.IsRead = IsRead; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item.Item)); - }); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => - { - item.Item.IsRead = !IsRead; - - WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item.Item)); - }); - } - } -} diff --git a/Wino.Core/Requests/MoveRequest.cs b/Wino.Core/Requests/MoveRequest.cs deleted file mode 100644 index d07a78a8..00000000 --- a/Wino.Core/Requests/MoveRequest.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; -using Wino.Core.Domain.Entities.Mail; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Requests; -using Wino.Messaging.UI; - -namespace Wino.Core.Requests -{ - public record MoveRequest(MailCopy Item, MailItemFolder FromFolder, MailItemFolder ToFolder) - : RequestBase(Item, MailSynchronizerOperation.Move), ICustomFolderSynchronizationRequest - { - public List SynchronizationFolderIds => new() { FromFolder.Id, ToFolder.Id }; - - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchMoveRequest(matchingItems, FromFolder, ToFolder); - - public override void ApplyUIChanges() - { - WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); - } - - public override void RevertUIChanges() - { - WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item)); - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchMoveRequest(IEnumerable Items, MailItemFolder FromFolder, MailItemFolder ToFolder) : BatchRequestBase(Items, MailSynchronizerOperation.Move) - { - public override void ApplyUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); - } - - public override int ResynchronizationDelay => 3000; - } -} diff --git a/Wino.Core/Requests/MoveToFocusedRequest.cs b/Wino.Core/Requests/MoveToFocusedRequest.cs deleted file mode 100644 index 20a50859..00000000 --- a/Wino.Core/Requests/MoveToFocusedRequest.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using Wino.Core.Domain.Entities.Mail; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Requests; - -namespace Wino.Core.Requests -{ - public record MoveToFocusedRequest(MailCopy Item, bool MoveToFocused) : RequestBase(Item, MailSynchronizerOperation.Move) - { - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchMoveToFocusedRequest(matchingItems, MoveToFocused); - - public override void ApplyUIChanges() { } - - public override void RevertUIChanges() { } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchMoveToFocusedRequest(IEnumerable Items, bool MoveToFocused) : BatchRequestBase(Items, MailSynchronizerOperation.Move) - { - public override void ApplyUIChanges() { } - - public override void RevertUIChanges() { } - } -} diff --git a/Wino.Core/Requests/SendDraftRequest.cs b/Wino.Core/Requests/SendDraftRequest.cs deleted file mode 100644 index 0effa59f..00000000 --- a/Wino.Core/Requests/SendDraftRequest.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MoreLinq; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Domain.Models.Requests; -using Wino.Messaging.UI; - -namespace Wino.Core.Requests -{ - public record SendDraftRequest(SendDraftPreparationRequest Request) - : RequestBase(Request.MailItem, MailSynchronizerOperation.Send), - ICustomFolderSynchronizationRequest - { - public List SynchronizationFolderIds - { - get - { - var folderIds = new List { Request.DraftFolder.Id }; - - if (Request.SentFolder != null) - { - folderIds.Add(Request.SentFolder.Id); - } - - return folderIds; - } - } - - public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) - => new BatchSendDraftRequestRequest(matchingItems, Request); - - public override void ApplyUIChanges() - { - WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); - } - - public override void RevertUIChanges() - { - WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item)); - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public record BatchSendDraftRequestRequest(IEnumerable Items, - SendDraftPreparationRequest Request) : BatchRequestBase(Items, MailSynchronizerOperation.Send) - { - public override void ApplyUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); - } - - public override void RevertUIChanges() - { - Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); - } - - public override int ResynchronizationDelay => 6000; - public override bool ExecuteSerialBatch => true; - } -} diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index dd3e7941..6223a843 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -11,7 +11,7 @@ using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; -using Wino.Core.Requests; +using Wino.Core.Requests.Mail; using Wino.Messaging.Server; namespace Wino.Core.Services @@ -37,7 +37,7 @@ namespace Wino.Core.Services public async Task ExecuteAsync(MailOperationPreperationRequest request) { - var requests = new List(); + var requests = new List(); try { diff --git a/Wino.Core/Services/WinoRequestProcessor.cs b/Wino.Core/Services/WinoRequestProcessor.cs index c1dff9d9..270d118a 100644 --- a/Wino.Core/Services/WinoRequestProcessor.cs +++ b/Wino.Core/Services/WinoRequestProcessor.cs @@ -9,7 +9,8 @@ using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Requests; +using Wino.Core.Requests.Folder; +using Wino.Core.Requests.Mail; namespace Wino.Core.Services { @@ -17,7 +18,7 @@ namespace Wino.Core.Services /// Intermediary processor for converting a user action to executable Wino requests. /// Primarily responsible for batching requests by AccountId and FolderId. /// - public class WinoRequestProcessor : BaseDatabaseService, IWinoRequestProcessor + public class WinoRequestProcessor : IWinoRequestProcessor { private readonly IFolderService _folderService; private readonly IKeyPressService _keyPressService; @@ -37,13 +38,12 @@ namespace Wino.Core.Services new ToggleRequestRule(MailOperation.ClearFlag, MailOperation.SetFlag, new System.Func((item) => !item.IsFlagged)), ]; - public WinoRequestProcessor(IDatabaseService databaseService, - IFolderService folderService, + public WinoRequestProcessor(IFolderService folderService, IKeyPressService keyPressService, IPreferencesService preferencesService, IAccountService accountService, IMailDialogService dialogService, - IMailService mailService) : base(databaseService) + IMailService mailService) { _folderService = folderService; _keyPressService = keyPressService; @@ -53,7 +53,7 @@ namespace Wino.Core.Services _mailService = mailService; } - public async Task> PrepareRequestsAsync(MailOperationPreperationRequest preperationRequest) + public async Task> PrepareRequestsAsync(MailOperationPreperationRequest preperationRequest) { var action = preperationRequest.Action; var moveTargetStructure = preperationRequest.MoveTargetFolder; @@ -89,7 +89,7 @@ namespace Wino.Core.Services return default; } - var requests = new List(); + var requests = new List(); // TODO: Fix: Collection was modified; enumeration operation may not execute foreach (var item in preperationRequest.MailItems) @@ -104,7 +104,7 @@ namespace Wino.Core.Services return requests; } - private async Task GetSingleRequestAsync(MailCopy mailItem, MailOperation action, IMailItemFolder moveTargetStructure, bool shouldToggleActions) + private async Task GetSingleRequestAsync(MailCopy mailItem, MailOperation action, IMailItemFolder moveTargetStructure, bool shouldToggleActions) { if (mailItem.AssignedAccount == null) throw new ArgumentException(Translator.Exception_NullAssignedAccount); if (mailItem.AssignedFolder == null) throw new ArgumentException(Translator.Exception_NullAssignedFolder); @@ -215,11 +215,11 @@ namespace Wino.Core.Services return null; } - public async Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request) + public async Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request) { if (request == null || request.Folder == null) return default; - IRequestBase change = null; + IFolderActionRequest change = null; var folder = request.Folder; var operation = request.Action; diff --git a/Wino.Core/Synchronizers/BaseMailSynchronizer.cs b/Wino.Core/Synchronizers/BaseMailSynchronizer.cs index 8e6436c7..971e2225 100644 --- a/Wino.Core/Synchronizers/BaseMailSynchronizer.cs +++ b/Wino.Core/Synchronizers/BaseMailSynchronizer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -18,9 +17,9 @@ using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; -using Wino.Core.Misc; -using Wino.Core.Requests; using Wino.Core.Requests.Bundles; +using Wino.Core.Requests.Folder; +using Wino.Core.Requests.Mail; using Wino.Messaging.UI; namespace Wino.Core.Synchronizers @@ -30,7 +29,7 @@ namespace Wino.Core.Synchronizers private SemaphoreSlim synchronizationSemaphore = new(1); private CancellationToken activeSynchronizationCancellationToken; - protected ConcurrentBag changeRequestQueue = []; + protected List changeRequestQueue = []; protected ILogger Logger = Log.ForContext>(); /// @@ -81,7 +80,7 @@ namespace Wino.Core.Synchronizers /// /// Batched requests to execute. Integrator methods will only receive batched requests. /// Cancellation token - public abstract Task ExecuteNativeRequestsAsync(IEnumerable> batchedRequests, CancellationToken cancellationToken = default); + public abstract Task ExecuteNativeRequestsAsync(List> batchedRequests, CancellationToken cancellationToken = default); /// /// Refreshes remote mail account profile if possible. @@ -153,29 +152,69 @@ namespace Wino.Core.Synchronizers { activeSynchronizationCancellationToken = cancellationToken; - var batches = CreateBatchRequests().Distinct(); + State = AccountSynchronizerState.ExecutingRequests; - if (batches.Any()) + List> nativeRequests = new(); + + List requestCopies = new(changeRequestQueue); + + var keys = changeRequestQueue.GroupBy(a => a.GroupingKey()); + + foreach (var group in keys) { - Logger.Information($"{batches?.Count() ?? 0} batched requests"); + var key = group.Key; - State = AccountSynchronizerState.ExecutingRequests; - - var nativeRequests = CreateNativeRequestBundles(batches); - - Console.WriteLine($"Prepared {nativeRequests.Count()} native requests"); - - await ExecuteNativeRequestsAsync(nativeRequests, activeSynchronizationCancellationToken); - - PublishUnreadItemChanges(); - - // Execute request sync options should be re-calculated after execution. - // This is the part we decide which individual folders must be synchronized - // after the batch request execution. - if (options.Type == SynchronizationType.ExecuteRequests) - options = GetSynchronizationOptionsAfterRequestExecution(batches); + if (key is MailSynchronizerOperation mailSynchronizerOperation) + { + switch (mailSynchronizerOperation) + { + case MailSynchronizerOperation.MarkRead: + nativeRequests.AddRange(MarkRead(new BatchMarkReadRequest(group.Cast()))); + break; + case MailSynchronizerOperation.Move: + nativeRequests.AddRange(Move(new BatchMoveRequest(group.Cast()))); + break; + case MailSynchronizerOperation.Delete: + nativeRequests.AddRange(Delete(new BatchDeleteRequest(group.Cast()))); + break; + case MailSynchronizerOperation.CreateDraft: + nativeRequests.AddRange(CreateDraft(group.ElementAt(0) as CreateDraftRequest)); + break; + case MailSynchronizerOperation.Send: + nativeRequests.AddRange(SendDraft(group.ElementAt(0) as SendDraftRequest)); + break; + case MailSynchronizerOperation.ChangeFlag: + nativeRequests.AddRange(ChangeFlag(new BatchChangeFlagRequest(group.Cast()))); + break; + case MailSynchronizerOperation.AlwaysMoveTo: + nativeRequests.AddRange(AlwaysMoveTo(new BatchAlwaysMoveToRequest(group.Cast()))); + break; + case MailSynchronizerOperation.MoveToFocused: + nativeRequests.AddRange(MoveToFocused(new BatchMoveToFocusedRequest(group.Cast()))); + break; + case MailSynchronizerOperation.Archive: + nativeRequests.AddRange(Archive(new BatchArchiveRequest(group.Cast()))); + break; + default: + break; + } + } } + changeRequestQueue.Clear(); + + Console.WriteLine($"Prepared {nativeRequests.Count()} native requests"); + + await ExecuteNativeRequestsAsync(nativeRequests, activeSynchronizationCancellationToken); + + PublishUnreadItemChanges(); + + // Execute request sync options should be re-calculated after execution. + // This is the part we decide which individual folders must be synchronized + // after the batch request execution. + if (options.Type == SynchronizationType.ExecuteRequests) + options = GetSynchronizationOptionsAfterRequestExecution(requestCopies); + State = AccountSynchronizerState.Synchronizing; await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken); @@ -228,11 +267,11 @@ namespace Wino.Core.Synchronizers bool shouldDelayExecution = (Account.ProviderType == MailProviderType.Outlook || Account.ProviderType == MailProviderType.Office365) - && batches.Any(a => a.ResynchronizationDelay > 0); + && requestCopies.Any(a => a.ResynchronizationDelay > 0); if (shouldDelayExecution) { - var maxDelay = batches.Aggregate(0, (max, next) => Math.Max(max, next.ResynchronizationDelay)); + var maxDelay = requestCopies.Aggregate(0, (max, next) => Math.Max(max, next.ResynchronizationDelay)); await Task.Delay(maxDelay); } @@ -290,120 +329,112 @@ namespace Wino.Core.Synchronizers /// since all folders must be asynchronously opened/closed. /// /// Batch request collection for all these single requests. - private List CreateBatchRequests() - { - var batchList = new List(); - var comparer = new RequestComparer(); + //private List CreateBatchRequests() + //{ + // var batchList = new List(); + // var comparer = new RequestComparer(); - while (changeRequestQueue.Count > 0) - { - if (changeRequestQueue.TryPeek(out IRequestBase request)) - { - // Mail request, must be batched. - if (request is IRequest mailRequest) - { - var equalItems = changeRequestQueue - .Where(a => a is IRequest && comparer.Equals(a, request)) - .Cast() - .ToList(); + // while (changeRequestQueue.Count > 0) + // { + // if (changeRequestQueue.TryPeek(out IRequestBase request)) + // { + // // Mail request, must be batched. + // if (request is IMailActionRequest mailRequest) + // { + // var equalItems = changeRequestQueue + // .Where(a => a is IMailActionRequest && comparer.Equals(a, request)) + // .Cast() + // .ToList(); - batchList.Add(mailRequest.CreateBatch(equalItems)); + // batchList.Add(mailRequest.CreateBatch(equalItems)); - // Remove these items from the queue. - foreach (var item in equalItems) - { - changeRequestQueue.TryTake(out _); - } - } - else if (changeRequestQueue.TryTake(out request)) - { - // This is a folder operation. - // There is no need to batch them since Users can't do folder ops in bulk. + // // Remove these items from the queue. + // foreach (var item in equalItems) + // { + // changeRequestQueue.TryTake(out _); + // } + // } + // else if (changeRequestQueue.TryTake(out request)) + // { + // // This is a folder operation. + // // There is no need to batch them since Users can't do folder ops in bulk. - batchList.Add(request); - } - } - } + // batchList.Add(request); + // } + // } + // } - return batchList; - } + // return batchList; + //} /// /// Converts batched requests into HTTP/Task calls that derived synchronizers can execute. /// /// Batch requests to be converted. /// Collection of native requests for individual synchronizer type. - private IEnumerable> CreateNativeRequestBundles(IEnumerable batchChangeRequests) - { - IEnumerable>> GetNativeRequests() - { - foreach (var item in batchChangeRequests) - { - switch (item.Operation) - { - case MailSynchronizerOperation.Send: - yield return SendDraft((BatchSendDraftRequestRequest)item); - break; - case MailSynchronizerOperation.MarkRead: - yield return MarkRead((BatchMarkReadRequest)item); - break; - case MailSynchronizerOperation.Move: - yield return Move((BatchMoveRequest)item); - break; - case MailSynchronizerOperation.Delete: - yield return Delete((BatchDeleteRequest)item); - break; - case MailSynchronizerOperation.ChangeFlag: - yield return ChangeFlag((BatchChangeFlagRequest)item); - break; - case MailSynchronizerOperation.AlwaysMoveTo: - yield return AlwaysMoveTo((BatchAlwaysMoveToRequest)item); - break; - case MailSynchronizerOperation.MoveToFocused: - yield return MoveToFocused((BatchMoveToFocusedRequest)item); - break; - case MailSynchronizerOperation.CreateDraft: - yield return CreateDraft((BatchCreateDraftRequest)item); - break; - case MailSynchronizerOperation.RenameFolder: - yield return RenameFolder((RenameFolderRequest)item); - break; - case MailSynchronizerOperation.EmptyFolder: - yield return EmptyFolder((EmptyFolderRequest)item); - break; - case MailSynchronizerOperation.MarkFolderRead: - yield return MarkFolderAsRead((MarkFolderAsReadRequest)item); - break; - case MailSynchronizerOperation.Archive: - yield return Archive((BatchArchiveRequest)item); - break; - } - } - }; + //private IEnumerable> CreateNativeRequestBundles(IEnumerable batchChangeRequests) + //{ + // IEnumerable>> GetNativeRequests() + // { + // foreach (var item in batchChangeRequests) + // { + // switch (item.Operation) + // { + // case MailSynchronizerOperation.Send: + // yield return SendDraft((BatchSendDraftRequestRequest)item); + // break; + // case MailSynchronizerOperation.MarkRead: + // yield return MarkRead((BatchMarkReadRequest)item); + // break; + // case MailSynchronizerOperation.Move: + // yield return Move((BatchMoveRequest)item); + // break; + // case MailSynchronizerOperation.Delete: + // yield return Delete((BatchDeleteRequest)item); + // break; + // case MailSynchronizerOperation.ChangeFlag: + // yield return ChangeFlag((BatchChangeFlagRequest)item); + // break; + // case MailSynchronizerOperation.AlwaysMoveTo: + // yield return AlwaysMoveTo((BatchAlwaysMoveToRequest)item); + // break; + // case MailSynchronizerOperation.MoveToFocused: + // yield return MoveToFocused((BatchMoveToFocusedRequest)item); + // break; + // case MailSynchronizerOperation.CreateDraft: + // yield return CreateDraft((BatchCreateDraftRequest)item); + // break; + // case MailSynchronizerOperation.RenameFolder: + // yield return RenameFolder((RenameFolderRequest)item); + // break; + // case MailSynchronizerOperation.EmptyFolder: + // yield return EmptyFolder((EmptyFolderRequest)item); + // break; + // case MailSynchronizerOperation.MarkFolderRead: + // yield return MarkFolderAsRead((MarkFolderAsReadRequest)item); + // break; + // case MailSynchronizerOperation.Archive: + // yield return Archive((BatchArchiveRequest)item); + // break; + // } + // } + // }; - return GetNativeRequests().SelectMany(collections => collections); - } + // return GetNativeRequests().SelectMany(collections => collections); + //} /// /// Attempts to find out the best possible synchronization options after the batch request execution. /// /// Batch requests to run in synchronization. /// New synchronization options with minimal HTTP effort. - private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable requests) + private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(List requests) { - List synchronizationFolderIds = new(); - - if (requests.All(a => a is IBatchChangeRequest)) - { - var requestsInsideBatches = requests.Cast().SelectMany(b => b.Items); - - // Gather FolderIds to synchronize. - synchronizationFolderIds = requestsInsideBatches + List synchronizationFolderIds = requests .Where(a => a is ICustomFolderSynchronizationRequest) .Cast() .SelectMany(a => a.SynchronizationFolderIds) .ToList(); - } var options = new SynchronizationOptions() { @@ -427,18 +458,20 @@ namespace Wino.Core.Synchronizers } public virtual bool DelaySendOperationSynchronization() => false; - public virtual IEnumerable> Move(BatchMoveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> ChangeFlag(BatchChangeFlagRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> MarkRead(BatchMarkReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> Delete(BatchDeleteRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> AlwaysMoveTo(BatchAlwaysMoveToRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> MoveToFocused(BatchMoveToFocusedRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> CreateDraft(BatchCreateDraftRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> SendDraft(BatchSendDraftRequestRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> RenameFolder(RenameFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> EmptyFolder(EmptyFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> MarkFolderAsRead(MarkFolderAsReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); - public virtual IEnumerable> Archive(BatchArchiveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> Move(BatchMoveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> ChangeFlag(BatchChangeFlagRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> MarkRead(BatchMarkReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> Delete(BatchDeleteRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> AlwaysMoveTo(BatchAlwaysMoveToRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> MoveToFocused(BatchMoveToFocusedRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> CreateDraft(CreateDraftRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> SendDraft(SendDraftRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> Archive(BatchArchiveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + + public virtual List> RenameFolder(RenameFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> EmptyFolder(EmptyFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> MarkFolderAsRead(MarkFolderAsReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + /// /// Downloads a single missing message from synchronizer and saves it to given FileId from IMailItem. @@ -456,117 +489,35 @@ namespace Wino.Core.Synchronizers #region Bundle Helpers - /// - /// Creates a batched HttpBundle without a response for a collection of MailItem. - /// - /// Generated batch request. - /// An action to get the native request from the MailItem. - /// Collection of http bundle that contains batch and native request. - public IEnumerable> CreateBatchedHttpBundleFromGroup( - IBatchChangeRequest batchChangeRequest, - Func, TBaseRequest> action) + public List> ForEachRequest(IEnumerable requests, + Func action) + where TWinoRequestType : IRequestBase { - if (batchChangeRequest.Items == null) yield break; + List> ret = []; - var groupedItems = batchChangeRequest.Items.Batch((int)BatchModificationSize); + foreach (var request in requests) + ret.Add(new HttpRequestBundle(action(request), request)); - foreach (var group in groupedItems) - yield return new HttpRequestBundle(action(group), batchChangeRequest); + return ret; } - public IEnumerable> CreateBatchedHttpBundle( - IBatchChangeRequest batchChangeRequest, - Func action) + public List> CreateSingleBundle(Func action, IRequestBase request, IUIChangeRequest uIChangeRequest) { - if (batchChangeRequest.Items == null) yield break; - - var groupedItems = batchChangeRequest.Items.Batch((int)BatchModificationSize); - - foreach (var group in groupedItems) - foreach (var item in group) - yield return new HttpRequestBundle(action(item), item); - - yield break; + return [new ImapRequestBundle(new ImapRequest(action, request), request, uIChangeRequest)]; } - /// - /// Creates a single HttpBundle without a response for a collection of MailItem. - /// - /// Batch request - /// An action to get the native request from the MailItem - /// Collection of http bundle that contains batch and native request. - public IEnumerable> CreateHttpBundle( - IBatchChangeRequest batchChangeRequest, - Func action) + public List> CreateTaskBundle(Func value, + List requests) + where TSingeRequestType : IRequestBase, IUIChangeRequest { - if (batchChangeRequest.Items == null) yield break; + List> ret = []; - foreach (var item in batchChangeRequest.Items) - yield return new HttpRequestBundle(action(item), batchChangeRequest); - } + foreach (var request in requests) + { + ret.Add(new ImapRequestBundle(new ImapRequest(value, request), request, request)); + } - public IEnumerable> CreateHttpBundle( - IBatchChangeRequest batchChangeRequest, - Func action) - { - if (batchChangeRequest.Items == null) yield break; - - foreach (var item in batchChangeRequest.Items) - yield return new HttpRequestBundle(action(item), item); - } - - /// - /// Creates HttpBundle with TResponse of expected response type from the http call for each of the items in the batch. - /// - /// Expected http response type after the call. - /// Generated batch request. - /// An action to get the native request from the MailItem. - /// Collection of http bundle that contains batch and native request. - public IEnumerable> CreateHttpBundleWithResponse( - IBatchChangeRequest batchChangeRequest, - Func action) - { - if (batchChangeRequest.Items == null) yield break; - - foreach (var item in batchChangeRequest.Items) - yield return new HttpRequestBundle(action(item), batchChangeRequest); - } - - public IEnumerable> CreateHttpBundleWithResponse( - IRequestBase item, - Func action) - { - yield return new HttpRequestBundle(action(item), item); - } - - /// - /// Creates a batched HttpBundle with TResponse of expected response type from the http call for each of the items in the batch. - /// Func will be executed for each item separately in the batch request. - /// - /// Expected http response type after the call. - /// Generated batch request. - /// An action to get the native request from the MailItem. - /// Collection of http bundle that contains batch and native request. - public IEnumerable> CreateBatchedHttpBundle( - IBatchChangeRequest batchChangeRequest, - Func action) - { - if (batchChangeRequest.Items == null) yield break; - - var groupedItems = batchChangeRequest.Items.Batch((int)BatchModificationSize); - - foreach (var group in groupedItems) - foreach (var item in group) - yield return new HttpRequestBundle(action(item), item); - - yield break; - } - - public IEnumerable> CreateTaskBundle(Func value, IRequestBase request) - { - var imapreq = new ImapRequest(value, request); - - return [new ImapRequestBundle(imapreq, request)]; + return ret; } #endregion diff --git a/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs b/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs index 15a8966f..c2b79aa7 100644 --- a/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs @@ -27,8 +27,9 @@ using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Extensions; using Wino.Core.Http; using Wino.Core.Integration.Processors; -using Wino.Core.Requests; using Wino.Core.Requests.Bundles; +using Wino.Core.Requests.Folder; +using Wino.Core.Requests.Mail; using Wino.Messaging.UI; namespace Wino.Core.Synchronizers.Mail @@ -596,151 +597,148 @@ namespace Wino.Core.Synchronizers.Mail #region Mail Integrations - public override IEnumerable> Move(BatchMoveRequest request) + public override List> Move(BatchMoveRequest request) { - return CreateBatchedHttpBundleFromGroup(request, (items) => + var toFolder = request[0].ToFolder; + var fromFolder = request[0].FromFolder; + + // Sent label can't be removed from mails for Gmail. + // They are automatically assigned by Gmail. + // When you delete sent mail from gmail web portal, it's moved to Trash + // but still has Sent label. It's just hidden from the user. + // Proper assignments will be done later on CreateAssignment call to mimic this behavior. + + var batchModifyRequest = new BatchModifyMessagesRequest { - // Sent label can't be removed from mails for Gmail. - // They are automatically assigned by Gmail. - // When you delete sent mail from gmail web portal, it's moved to Trash - // but still has Sent label. It's just hidden from the user. - // Proper assignments will be done later on CreateAssignment call to mimic this behavior. - var batchModifyRequest = new BatchModifyMessagesRequest - { - Ids = items.Select(a => a.Item.Id.ToString()).ToList(), - AddLabelIds = [request.ToFolder.RemoteFolderId] - }; + Ids = request.Select(a => a.Item.Id.ToString()).ToList(), + AddLabelIds = [toFolder.RemoteFolderId] + }; - // Only add remove label ids if the source folder is not sent folder. - if (request.FromFolder.SpecialFolderType != SpecialFolderType.Sent) - { - batchModifyRequest.RemoveLabelIds = [request.FromFolder.RemoteFolderId]; - } + // Only add remove label ids if the source folder is not sent folder. + if (fromFolder.SpecialFolderType != SpecialFolderType.Sent) + { + batchModifyRequest.RemoveLabelIds = [fromFolder.RemoteFolderId]; + } - return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); - }); + var networkCall = _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; } - public override IEnumerable> ChangeFlag(BatchChangeFlagRequest request) + public override List> ChangeFlag(BatchChangeFlagRequest request) { - return CreateBatchedHttpBundleFromGroup(request, (items) => + bool isFlagged = request[0].IsFlagged; + + var batchModifyRequest = new BatchModifyMessagesRequest { - var batchModifyRequest = new BatchModifyMessagesRequest - { - Ids = items.Select(a => a.Item.Id.ToString()).ToList(), - }; + Ids = request.Select(a => a.Item.Id.ToString()).ToList(), + }; - if (request.IsFlagged) - batchModifyRequest.AddLabelIds = new List() { GoogleIntegratorExtensions.STARRED_LABEL_ID }; - else - batchModifyRequest.RemoveLabelIds = new List() { GoogleIntegratorExtensions.STARRED_LABEL_ID }; + if (isFlagged) + batchModifyRequest.AddLabelIds = new List() { GoogleIntegratorExtensions.STARRED_LABEL_ID }; + else + batchModifyRequest.RemoveLabelIds = new List() { GoogleIntegratorExtensions.STARRED_LABEL_ID }; - return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); - }); + var networkCall = _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; } - public override IEnumerable> MarkRead(BatchMarkReadRequest request) + public override List> MarkRead(BatchMarkReadRequest request) { - return CreateBatchedHttpBundleFromGroup(request, (items) => + bool readStatus = request[0].IsRead; + + var batchModifyRequest = new BatchModifyMessagesRequest { - var batchModifyRequest = new BatchModifyMessagesRequest - { - Ids = items.Select(a => a.Item.Id.ToString()).ToList(), - }; + Ids = request.Select(a => a.Item.Id.ToString()).ToList(), + }; - if (request.IsRead) - batchModifyRequest.RemoveLabelIds = new List() { GoogleIntegratorExtensions.UNREAD_LABEL_ID }; - else - batchModifyRequest.AddLabelIds = new List() { GoogleIntegratorExtensions.UNREAD_LABEL_ID }; + if (readStatus) + batchModifyRequest.RemoveLabelIds = new List() { GoogleIntegratorExtensions.UNREAD_LABEL_ID }; + else + batchModifyRequest.AddLabelIds = new List() { GoogleIntegratorExtensions.UNREAD_LABEL_ID }; - return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); - }); + var networkCall = _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; } - public override IEnumerable> Delete(BatchDeleteRequest request) + public override List> Delete(BatchDeleteRequest request) { - return CreateBatchedHttpBundleFromGroup(request, (items) => + var batchModifyRequest = new BatchDeleteMessagesRequest { - var batchModifyRequest = new BatchDeleteMessagesRequest - { - Ids = items.Select(a => a.Item.Id.ToString()).ToList(), - }; + Ids = request.Select(a => a.Item.Id.ToString()).ToList(), + }; - return _gmailService.Users.Messages.BatchDelete(batchModifyRequest, "me"); - }); + var networkCall = _gmailService.Users.Messages.BatchDelete(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; } - public override IEnumerable> CreateDraft(BatchCreateDraftRequest request) + public override List> CreateDraft(CreateDraftRequest singleRequest) { - return CreateHttpBundle(request, (item) => - { - if (item is not CreateDraftRequest singleRequest) - throw new ArgumentException("BatchCreateDraftRequest collection must be of type CreateDraftRequest."); + Draft draft = null; - Draft draft = null; + // It's new mail. Not a reply + if (singleRequest.DraftPreperationRequest.ReferenceMailCopy == null) + draft = PrepareGmailDraft(singleRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage); + else + draft = PrepareGmailDraft(singleRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage, + singleRequest.DraftPreperationRequest.ReferenceMailCopy.ThreadId, + singleRequest.DraftPreperationRequest.ReferenceMailCopy.DraftId); - // It's new mail. Not a reply - if (singleRequest.DraftPreperationRequest.ReferenceMailCopy == null) - draft = PrepareGmailDraft(singleRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage); - else - draft = PrepareGmailDraft(singleRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage, - singleRequest.DraftPreperationRequest.ReferenceMailCopy.ThreadId, - singleRequest.DraftPreperationRequest.ReferenceMailCopy.DraftId); + var networkCall = _gmailService.Users.Drafts.Create(draft, "me"); - return _gmailService.Users.Drafts.Create(draft, "me"); - }); + return [new HttpRequestBundle(networkCall, singleRequest, singleRequest)]; } - public override IEnumerable> Archive(BatchArchiveRequest request) + public override List> Archive(BatchArchiveRequest request) { - return CreateBatchedHttpBundleFromGroup(request, (items) => + bool isArchiving = request[0].IsArchiving; + var batchModifyRequest = new BatchModifyMessagesRequest { - var batchModifyRequest = new BatchModifyMessagesRequest - { - Ids = items.Select(a => a.Item.Id.ToString()).ToList() - }; + Ids = request.Select(a => a.Item.Id.ToString()).ToList() + }; - if (request.IsArchiving) - { - batchModifyRequest.RemoveLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; - } - else - { - batchModifyRequest.AddLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; - } + if (isArchiving) + { + batchModifyRequest.RemoveLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; + } + else + { + batchModifyRequest.AddLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; + } - return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); - }); + var networkCall = _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; } - public override IEnumerable> SendDraft(BatchSendDraftRequestRequest request) + public override List> SendDraft(SendDraftRequest singleDraftRequest) { - return CreateHttpBundle(request, (item) => + + var message = new Message(); + + if (!string.IsNullOrEmpty(singleDraftRequest.Item.ThreadId)) { - if (item is not SendDraftRequest singleDraftRequest) - throw new ArgumentException("BatchSendDraftRequestRequest collection must be of type SendDraftRequest."); + message.ThreadId = singleDraftRequest.Item.ThreadId; + } - var message = new Message(); + singleDraftRequest.Request.Mime.Prepare(EncodingConstraint.None); - if (!string.IsNullOrEmpty(singleDraftRequest.Item.ThreadId)) - { - message.ThreadId = singleDraftRequest.Item.ThreadId; - } + var mimeString = singleDraftRequest.Request.Mime.ToString(); + var base64UrlEncodedMime = Base64UrlEncoder.Encode(mimeString); + message.Raw = base64UrlEncodedMime; - singleDraftRequest.Request.Mime.Prepare(EncodingConstraint.None); + var draft = new Draft() + { + Id = singleDraftRequest.Request.MailItem.DraftId, + Message = message + }; - var mimeString = singleDraftRequest.Request.Mime.ToString(); - var base64UrlEncodedMime = Base64UrlEncoder.Encode(mimeString); - message.Raw = base64UrlEncodedMime; + var networkCall = _gmailService.Users.Drafts.Send(draft, "me"); - var draft = new Draft() - { - Id = singleDraftRequest.Request.MailItem.DraftId, - Message = message - }; - - return _gmailService.Users.Drafts.Send(draft, "me"); - }); + return [new HttpRequestBundle(networkCall, singleDraftRequest, singleDraftRequest)]; } public override async Task DownloadMissingMimeMessageAsync(IMailItem mailItem, @@ -762,23 +760,19 @@ namespace Wino.Core.Synchronizers.Mail await _gmailChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false); } - public override IEnumerable> RenameFolder(RenameFolderRequest request) + public override List> RenameFolder(RenameFolderRequest request) { - return CreateHttpBundleWithResponse