2024-04-18 01:44:37 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
using Wino.Core.Domain ;
2024-11-10 23:28:25 +01:00
using Wino.Core.Domain.Entities.Mail ;
2024-04-18 01:44:37 +02:00
using Wino.Core.Domain.Enums ;
using Wino.Core.Domain.Exceptions ;
using Wino.Core.Domain.Interfaces ;
using Wino.Core.Domain.Models.Folders ;
using Wino.Core.Domain.Models.MailItem ;
2024-11-26 20:03:10 +01:00
using Wino.Core.Requests.Folder ;
using Wino.Core.Requests.Mail ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
namespace Wino.Core.Services ;
/// <summary>
/// Intermediary processor for converting a user action to executable Wino requests.
/// Primarily responsible for batching requests by AccountId and FolderId.
/// </summary>
public class WinoRequestProcessor : IWinoRequestProcessor
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
private readonly IFolderService _folderService ;
private readonly IKeyPressService _keyPressService ;
private readonly IPreferencesService _preferencesService ;
private readonly IMailDialogService _dialogService ;
private readonly IMailService _mailService ;
2024-04-18 01:44:37 +02:00
/// <summary>
2025-02-16 11:54:23 +01:00
/// Set of rules that defines which action should be executed if user wants to toggle an action.
2024-04-18 01:44:37 +02:00
/// </summary>
2025-02-16 11:54:23 +01:00
private readonly List < ToggleRequestRule > _toggleRequestRules =
[
new ToggleRequestRule ( MailOperation . MarkAsRead , MailOperation . MarkAsUnread , new System . Func < IMailItem , bool > ( ( item ) = > item . IsRead ) ) ,
new ToggleRequestRule ( MailOperation . MarkAsUnread , MailOperation . MarkAsRead , new System . Func < IMailItem , bool > ( ( item ) = > ! item . IsRead ) ) ,
new ToggleRequestRule ( MailOperation . SetFlag , MailOperation . ClearFlag , new System . Func < IMailItem , bool > ( ( item ) = > item . IsFlagged ) ) ,
new ToggleRequestRule ( MailOperation . ClearFlag , MailOperation . SetFlag , new System . Func < IMailItem , bool > ( ( item ) = > ! item . IsFlagged ) ) ,
] ;
public WinoRequestProcessor ( IFolderService folderService ,
IKeyPressService keyPressService ,
IPreferencesService preferencesService ,
IMailDialogService dialogService ,
IMailService mailService )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
_folderService = folderService ;
_keyPressService = keyPressService ;
_preferencesService = preferencesService ;
_dialogService = dialogService ;
_mailService = mailService ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < List < IMailActionRequest > > PrepareRequestsAsync ( MailOperationPreperationRequest preperationRequest )
{
var action = preperationRequest . Action ;
var moveTargetStructure = preperationRequest . MoveTargetFolder ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Ask confirmation for permanent delete operation.
// Drafts are always hard deleted without any protection.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( ! preperationRequest . IgnoreHardDeleteProtection & & ( ( action = = MailOperation . SoftDelete & & _keyPressService . IsShiftKeyPressed ( ) ) | | action = = MailOperation . HardDelete ) )
{
if ( _preferencesService . IsHardDeleteProtectionEnabled )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
var shouldDelete = await _dialogService . ShowHardDeleteConfirmationAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( ! shouldDelete ) return default ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
action = MailOperation . HardDelete ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Make sure there is a move target folder if action is move.
// Let user pick a folder to move from the dialog.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( action = = MailOperation . Move & & moveTargetStructure = = null )
{
2025-02-23 17:05:46 +01:00
// Handle the case when user is trying to move multiple mails that belong to different accounts.
// We can't handle this with only 1 picker dialog.
bool isInvalidMoveTarget = preperationRequest . MailItems . Select ( a = > a . AssignedAccount . Id ) . Distinct ( ) . Count ( ) > 1 ;
if ( isInvalidMoveTarget )
throw new InvalidMoveTargetException ( InvalidMoveTargetReason . MultipleAccounts ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var accountId = preperationRequest . MailItems . FirstOrDefault ( ) . AssignedAccount . Id ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
moveTargetStructure = await _dialogService . PickFolderAsync ( accountId , PickFolderReason . Move , _folderService ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( moveTargetStructure = = null )
return default ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var requests = new List < IMailActionRequest > ( ) ;
2024-06-21 02:11:18 +02:00
2025-02-16 11:54:23 +01:00
// TODO: Fix: Collection was modified; enumeration operation may not execute
foreach ( var item in preperationRequest . MailItems )
{
var singleRequest = await GetSingleRequestAsync ( item , action , moveTargetStructure , preperationRequest . ToggleExecution ) ;
2024-06-21 02:11:18 +02:00
2025-02-16 11:54:23 +01:00
if ( singleRequest = = null ) continue ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
requests . Add ( singleRequest ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
return requests ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private async Task < IMailActionRequest > 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 ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Rule: Soft deletes from Trash folder must perform Hard Delete.
if ( action = = MailOperation . SoftDelete & & mailItem . AssignedFolder . SpecialFolderType = = SpecialFolderType . Deleted )
action = MailOperation . HardDelete ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Rule: SoftDelete draft items must be performed as hard delete.
if ( action = = MailOperation . SoftDelete & & mailItem . IsDraft )
action = MailOperation . HardDelete ;
2024-06-21 02:11:18 +02:00
2025-02-16 11:54:23 +01:00
// Rule: Soft/Hard deletes on local drafts are always discard local draft.
if ( ( action = = MailOperation . SoftDelete | | action = = MailOperation . HardDelete ) & & mailItem . IsLocalDraft )
action = MailOperation . DiscardLocalDraft ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Rule: Toggle actions must be reverted if ToggleExecution is passed true.
if ( shouldToggleActions )
{
var toggleRule = _toggleRequestRules . Find ( a = > a . SourceAction = = action ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( toggleRule ! = null & & toggleRule . Condition ( mailItem ) )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
action = toggleRule . TargetAction ;
}
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( action = = MailOperation . MarkAsRead )
return new MarkReadRequest ( mailItem , true ) ;
else if ( action = = MailOperation . MarkAsUnread )
return new MarkReadRequest ( mailItem , false ) ;
else if ( action = = MailOperation . SetFlag )
return new ChangeFlagRequest ( mailItem , true ) ;
else if ( action = = MailOperation . ClearFlag )
return new ChangeFlagRequest ( mailItem , false ) ;
else if ( action = = MailOperation . HardDelete )
return new DeleteRequest ( mailItem ) ;
else if ( action = = MailOperation . Move )
{
if ( moveTargetStructure = = null )
2025-02-23 17:05:46 +01:00
throw new InvalidMoveTargetException ( InvalidMoveTargetReason . NonMoveTarget ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// TODO
// Rule: You can't move items to non-move target folders;
// Rule: You can't move items from a folder to itself.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
//if (!moveTargetStructure.IsMoveTarget || moveTargetStructure.FolderId == mailItem.AssignedFolder.Id)
// throw new InvalidMoveTargetException();
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var pickedFolderItem = await _folderService . GetFolderAsync ( moveTargetStructure . Id ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new MoveRequest ( mailItem , mailItem . AssignedFolder , pickedFolderItem ) ;
}
else if ( action = = MailOperation . Archive )
{
// For IMAP and Outlook: Validate archive folder exists.
// Gmail doesn't need archive folder existence.
2024-06-21 23:48:03 +02:00
2025-02-16 11:54:23 +01:00
MailItemFolder archiveFolder = null ;
2024-06-21 23:48:03 +02:00
2025-02-16 11:54:23 +01:00
bool shouldRequireArchiveFolder = mailItem . AssignedAccount . ProviderType = = MailProviderType . Outlook
| | mailItem . AssignedAccount . ProviderType = = MailProviderType . IMAP4 ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( shouldRequireArchiveFolder )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
archiveFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Archive )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Archive , mailItem . AssignedAccount . Id ) ;
2024-04-18 01:44:37 +02:00
}
2024-06-21 23:48:03 +02:00
2025-02-16 11:54:23 +01:00
return new ArchiveRequest ( true , mailItem , mailItem . AssignedFolder , archiveFolder ) ;
}
else if ( action = = MailOperation . MarkAsNotJunk )
{
var inboxFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Inbox )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Inbox , mailItem . AssignedAccount . Id ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new MoveRequest ( mailItem , mailItem . AssignedFolder , inboxFolder ) ;
}
else if ( action = = MailOperation . UnArchive )
{
var inboxFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Inbox )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Inbox , mailItem . AssignedAccount . Id ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new ArchiveRequest ( false , mailItem , mailItem . AssignedFolder , inboxFolder ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
else if ( action = = MailOperation . SoftDelete )
{
var trashFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Deleted )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Deleted , mailItem . AssignedAccount . Id ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new MoveRequest ( mailItem , mailItem . AssignedFolder , trashFolder ) ;
}
else if ( action = = MailOperation . MoveToJunk )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
var junkFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Junk )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Junk , mailItem . AssignedAccount . Id ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new MoveRequest ( mailItem , mailItem . AssignedFolder , junkFolder ) ;
}
else if ( action = = MailOperation . AlwaysMoveToFocused | | action = = MailOperation . AlwaysMoveToOther )
return new AlwaysMoveToRequest ( mailItem , action = = MailOperation . AlwaysMoveToFocused ) ;
else if ( action = = MailOperation . DiscardLocalDraft )
await _mailService . DeleteMailAsync ( mailItem . AssignedAccount . Id , mailItem . Id ) ;
else
throw new NotSupportedException ( string . Format ( Translator . Exception_UnsupportedAction , action ) ) ;
return null ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < IFolderActionRequest > PrepareFolderRequestAsync ( FolderOperationPreperationRequest request )
{
if ( request = = null | | request . Folder = = null ) return default ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
IFolderActionRequest change = null ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var folder = request . Folder ;
var operation = request . Action ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
switch ( request . Action )
{
case FolderOperation . Pin :
case FolderOperation . Unpin :
await _folderService . ChangeStickyStatusAsync ( folder . Id , operation = = FolderOperation . Pin ) ;
break ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
case FolderOperation . Rename :
var newFolderName = await _dialogService . ShowTextInputDialogAsync ( folder . FolderName , Translator . DialogMessage_RenameFolderTitle , Translator . DialogMessage_RenameFolderMessage , Translator . FolderOperation_Rename ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( ! string . IsNullOrEmpty ( newFolderName ) )
{
change = new RenameFolderRequest ( folder , folder . FolderName , newFolderName ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
break ;
case FolderOperation . Empty :
var mailsToDelete = await _mailService . GetMailsByFolderIdAsync ( folder . Id ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
change = new EmptyFolderRequest ( folder , mailsToDelete ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
break ;
case FolderOperation . MarkAllAsRead :
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var unreadItems = await _mailService . GetUnreadMailsByFolderIdAsync ( folder . Id ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( unreadItems . Any ( ) )
change = new MarkFolderAsReadRequest ( folder , unreadItems ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
break ;
//case FolderOperation.Delete:
// var isConfirmed = await _dialogService.ShowConfirmationDialogAsync($"'{folderStructure.FolderName}' is going to be deleted. Do you want to continue?", "Are you sure?", "Yes delete.");
// if (isConfirmed)
// change = new DeleteFolderRequest(accountId, folderStructure.RemoteFolderId, folderStructure.FolderId);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// break;
//default:
// throw new NotImplementedException();
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
return change ;
2024-04-18 01:44:37 +02:00
}
}