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 ;
using Wino.Core.Domain.Entities ;
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 ;
using Wino.Core.Domain.Models.Requests ;
using Wino.Core.Requests ;
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 : BaseDatabaseService , IWinoRequestProcessor
{
private readonly IFolderService _folderService ;
private readonly IKeyPressService _keyPressService ;
private readonly IPreferencesService _preferencesService ;
private readonly IAccountService _accountService ;
private readonly IDialogService _dialogService ;
2024-06-21 02:11:18 +02:00
private readonly IMailService _mailService ;
2024-04-18 01:44:37 +02:00
/// <summary>
/// Set of rules that defines which action should be executed if user wants to toggle an action.
/// </summary>
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 ( IDatabaseService databaseService ,
IFolderService folderService ,
IKeyPressService keyPressService ,
IPreferencesService preferencesService ,
IAccountService accountService ,
2024-06-21 02:11:18 +02:00
IDialogService dialogService ,
IMailService mailService ) : base ( databaseService )
2024-04-18 01:44:37 +02:00
{
_folderService = folderService ;
_keyPressService = keyPressService ;
_preferencesService = preferencesService ;
_accountService = accountService ;
_dialogService = dialogService ;
2024-06-21 02:11:18 +02:00
_mailService = mailService ;
2024-04-18 01:44:37 +02:00
}
public async Task < List < IRequest > > PrepareRequestsAsync ( MailOperationPreperationRequest preperationRequest )
{
var action = preperationRequest . Action ;
var moveTargetStructure = preperationRequest . MoveTargetFolder ;
// Ask confirmation for permanent delete operation.
// Drafts are always hard deleted without any protection.
if ( ! preperationRequest . IgnoreHardDeleteProtection & & ( ( action = = MailOperation . SoftDelete & & _keyPressService . IsShiftKeyPressed ( ) ) | | action = = MailOperation . HardDelete ) )
{
if ( _preferencesService . IsHardDeleteProtectionEnabled )
{
var shouldDelete = await _dialogService . ShowHardDeleteConfirmationAsync ( ) ;
if ( ! shouldDelete ) return default ;
}
action = MailOperation . HardDelete ;
}
// Make sure there is a move target folder if action is move.
// Let user pick a folder to move from the dialog.
if ( action = = MailOperation . Move & & moveTargetStructure = = null )
{
// TODO: Handle multiple accounts for move operation.
// What happens if we move 2 different mails from 2 different accounts?
var accountId = preperationRequest . MailItems . FirstOrDefault ( ) . AssignedAccount . Id ;
moveTargetStructure = await _dialogService . PickFolderAsync ( accountId , PickFolderReason . Move , _folderService ) ;
if ( moveTargetStructure = = null )
return default ;
}
var requests = new List < IRequest > ( ) ;
foreach ( var item in preperationRequest . MailItems )
{
2024-06-21 02:11:18 +02:00
var singleRequest = await GetSingleRequestAsync ( item , action , moveTargetStructure , preperationRequest . ToggleExecution ) ;
if ( singleRequest = = null ) continue ;
requests . Add ( singleRequest ) ;
2024-04-18 01:44:37 +02:00
}
return requests ;
}
private async Task < IRequest > 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 ) ;
// Rule: Soft deletes from Trash folder must perform Hard Delete.
if ( action = = MailOperation . SoftDelete & & mailItem . AssignedFolder . SpecialFolderType = = SpecialFolderType . Deleted )
action = MailOperation . HardDelete ;
// 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
// 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
// Rule: Toggle actions must be reverted if ToggleExecution is passed true.
if ( shouldToggleActions )
{
var toggleRule = _toggleRequestRules . Find ( a = > a . SourceAction = = action ) ;
if ( toggleRule ! = null & & toggleRule . Condition ( mailItem ) )
{
action = toggleRule . TargetAction ;
}
}
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 )
throw new InvalidMoveTargetException ( ) ;
// TODO
// Rule: You can't move items to non-move target folders;
// Rule: You can't move items from a folder to itself.
//if (!moveTargetStructure.IsMoveTarget || moveTargetStructure.FolderId == mailItem.AssignedFolder.Id)
// throw new InvalidMoveTargetException();
var pickedFolderItem = await _folderService . GetFolderAsync ( moveTargetStructure . Id ) ;
return new MoveRequest ( mailItem , mailItem . AssignedFolder , pickedFolderItem ) ;
}
else if ( action = = MailOperation . Archive )
{
2024-06-21 23:48:03 +02:00
// For IMAP and Outlook: Validate archive folder exists.
// Gmail doesn't need archive folder existence.
2024-04-18 01:44:37 +02:00
2024-06-21 23:48:03 +02:00
MailItemFolder archiveFolder = null ;
bool shouldRequireArchiveFolder = mailItem . AssignedAccount . ProviderType = = MailProviderType . Outlook
| | mailItem . AssignedAccount . ProviderType = = MailProviderType . IMAP4
| | mailItem . AssignedAccount . ProviderType = = MailProviderType . Office365 ;
if ( shouldRequireArchiveFolder )
{
archiveFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Archive )
2024-04-18 01:44:37 +02:00
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Archive , mailItem . AssignedAccount . Id ) ;
2024-06-21 23:48:03 +02:00
}
2024-04-18 01:44:37 +02:00
2024-06-21 23:48:03 +02:00
return new ArchiveRequest ( true , mailItem , mailItem . AssignedFolder , archiveFolder ) ;
2024-04-18 01:44:37 +02:00
}
2024-06-21 23:48:03 +02:00
else if ( action = = MailOperation . MarkAsNotJunk )
2024-04-18 01:44:37 +02:00
{
var inboxFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Inbox )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Inbox , mailItem . AssignedAccount . Id ) ;
return new MoveRequest ( mailItem , mailItem . AssignedFolder , inboxFolder ) ;
}
2024-06-21 23:48:03 +02:00
else if ( action = = MailOperation . UnArchive )
{
var inboxFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Inbox )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Inbox , mailItem . AssignedAccount . Id ) ;
return new ArchiveRequest ( false , mailItem , mailItem . AssignedFolder , inboxFolder ) ;
}
2024-04-18 01:44:37 +02:00
else if ( action = = MailOperation . SoftDelete )
{
var trashFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Deleted )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Deleted , mailItem . AssignedAccount . Id ) ;
return new MoveRequest ( mailItem , mailItem . AssignedFolder , trashFolder ) ;
}
else if ( action = = MailOperation . MoveToJunk )
{
var junkFolder = await _folderService . GetSpecialFolderByAccountIdAsync ( mailItem . AssignedAccount . Id , SpecialFolderType . Junk )
? ? throw new UnavailableSpecialFolderException ( SpecialFolderType . Junk , mailItem . AssignedAccount . Id ) ;
return new MoveRequest ( mailItem , mailItem . AssignedFolder , junkFolder ) ;
}
else if ( action = = MailOperation . AlwaysMoveToFocused | | action = = MailOperation . AlwaysMoveToOther )
return new AlwaysMoveToRequest ( mailItem , action = = MailOperation . AlwaysMoveToFocused ) ;
2024-06-21 02:11:18 +02:00
else if ( action = = MailOperation . DiscardLocalDraft )
await _mailService . DeleteMailAsync ( mailItem . AssignedAccount . Id , mailItem . Id ) ;
2024-04-18 01:44:37 +02:00
else
throw new NotSupportedException ( string . Format ( Translator . Exception_UnsupportedAction , action ) ) ;
2024-06-21 02:11:18 +02:00
return null ;
2024-04-18 01:44:37 +02:00
}
2024-07-09 01:05:16 +02:00
public async Task < IRequestBase > PrepareFolderRequestAsync ( FolderOperationPreperationRequest request )
2024-04-18 01:44:37 +02:00
{
2024-07-09 01:05:16 +02:00
if ( request = = null | | request . Folder = = null ) return default ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
IRequestBase change = null ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
var folder = request . Folder ;
var operation = request . Action ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
switch ( request . Action )
2024-04-18 01:44:37 +02:00
{
case FolderOperation . Pin :
case FolderOperation . Unpin :
2024-07-09 01:05:16 +02:00
await _folderService . ChangeStickyStatusAsync ( folder . Id , operation = = FolderOperation . Pin ) ;
2024-04-18 01:44:37 +02:00
break ;
2024-07-09 01:05:16 +02: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
2024-07-09 01:05:16 +02:00
if ( ! string . IsNullOrEmpty ( newFolderName ) )
{
change = new RenameFolderRequest ( folder , folder . FolderName , newFolderName ) ;
}
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
break ;
case FolderOperation . Empty :
var mailsToDelete = await _mailService . GetMailsByFolderIdAsync ( folder . Id ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
change = new EmptyFolderRequest ( folder , mailsToDelete ) ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
break ;
case FolderOperation . MarkAllAsRead :
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
var unreadItems = await _mailService . GetUnreadMailsByFolderIdAsync ( folder . Id ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
if ( unreadItems . Any ( ) )
change = new MarkFolderAsReadRequest ( folder , unreadItems ) ;
2024-04-18 01:44:37 +02:00
2024-07-09 01:05:16 +02:00
break ;
2024-04-18 01:44:37 +02:00
//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);
// break;
//default:
// throw new NotImplementedException();
}
return change ;
}
}
}