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
}
public async Task < IRequest > PrepareFolderRequestAsync ( FolderOperation operation , IMailItemFolder mailItemFolder )
{
if ( mailItemFolder = = null ) return default ;
var accountId = mailItemFolder . MailAccountId ;
IRequest change = null ;
switch ( operation )
{
case FolderOperation . Pin :
case FolderOperation . Unpin :
await _folderService . ChangeStickyStatusAsync ( mailItemFolder . Id , operation = = FolderOperation . Pin ) ;
break ;
//case FolderOperation.MarkAllAsRead:
// // Get all mails in the folder.
// var mailItems = await _folderService.GetAllUnreadItemsByFolderIdAsync(accountId, folderStructure.RemoteFolderId).ConfigureAwait(false);
// if (mailItems.Any())
// change = new FolderMarkAsReadRequest(accountId, mailItems.Select(a => a.Id).Distinct(), folderStructure.RemoteFolderId, folderStructure.FolderId);
// break;
//case FolderOperation.Empty:
// // Get all mails in the folder.
// var mailsToDelete = await _folderService.GetMailByFolderIdAsync(folderStructure.FolderId).ConfigureAwait(false);
// if (mailsToDelete.Any())
// change = new FolderEmptyRequest(accountId, mailsToDelete.Select(a => a.Id).Distinct(), folderStructure.RemoteFolderId, folderStructure.FolderId);
// break;
//case FolderOperation.Rename:
// var newFolderName = await _dialogService.ShowRenameFolderDialogAsync(folderStructure.FolderName);
// if (!string.IsNullOrEmpty(newFolderName))
// change = new RenameFolderRequest(accountId, folderStructure.RemoteFolderId, folderStructure.FolderId, newFolderName, folderStructure.FolderName);
// 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);
// break;
//default:
// throw new NotImplementedException();
}
return change ;
}
}
}