2024-04-18 01:44:37 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
2024-07-09 01:05:16 +02:00
using CommunityToolkit.Mvvm.Messaging ;
2024-04-18 01:44:37 +02:00
using Serilog ;
using Wino.Core.Domain ;
2024-11-10 23:28:25 +01:00
using Wino.Core.Domain.Entities.Mail ;
using Wino.Core.Domain.Entities.Shared ;
2024-04-18 01:44:37 +02:00
using Wino.Core.Domain.Enums ;
using Wino.Core.Domain.Interfaces ;
2024-11-30 23:05:07 +01:00
using Wino.Core.Domain.MenuItems ;
2024-07-09 01:05:16 +02:00
using Wino.Core.Domain.Models.Accounts ;
2024-04-18 01:44:37 +02:00
using Wino.Core.Domain.Models.Folders ;
using Wino.Core.Domain.Models.MailItem ;
using Wino.Core.Domain.Models.Synchronization ;
2024-08-05 00:36:26 +02:00
using Wino.Messaging.UI ;
2024-11-30 23:05:07 +01:00
using Wino.Services.Extensions ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
namespace Wino.Services ;
public class FolderService : BaseDatabaseService , IFolderService
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
private readonly IAccountService _accountService ;
2026-04-15 01:18:07 +02:00
private readonly IMailCategoryService _mailCategoryService ;
2025-02-16 11:54:23 +01:00
private readonly ILogger _logger = Log . ForContext < FolderService > ( ) ;
private readonly SpecialFolderType [ ] gmailCategoryFolderTypes =
[
SpecialFolderType.Promotions,
SpecialFolderType.Social,
SpecialFolderType.Updates,
SpecialFolderType.Forums,
SpecialFolderType.Personal
] ;
public FolderService ( IDatabaseService databaseService ,
2026-04-15 01:18:07 +02:00
IAccountService accountService ,
IMailCategoryService mailCategoryService ) : base ( databaseService )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
_accountService = accountService ;
2026-04-15 01:18:07 +02:00
_mailCategoryService = mailCategoryService ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task ChangeStickyStatusAsync ( Guid folderId , bool isSticky )
= > await Connection . ExecuteAsync ( "UPDATE MailItemFolder SET IsSticky = ? WHERE Id = ?" , isSticky , folderId ) ;
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
public async Task ChangeFolderHiddenStatusAsync ( Guid folderId , bool isHidden )
{
await Connection . ExecuteAsync ( "UPDATE MailItemFolder SET IsHidden = ? WHERE Id = ?" , isHidden , folderId ) ;
var folder = await GetFolderAsync ( folderId ) . ConfigureAwait ( false ) ;
if ( folder ! = null )
{
Messenger . Send ( new AccountFolderConfigurationUpdated ( folder . MailAccountId ) ) ;
}
}
public async Task UpdateFolderOrdersAsync ( Guid accountId , IReadOnlyList < Guid > orderedFolderIds )
{
if ( orderedFolderIds = = null | | orderedFolderIds . Count = = 0 ) return ;
await Connection . RunInTransactionAsync ( conn = >
{
for ( int i = 0 ; i < orderedFolderIds . Count ; i + + )
{
conn . Execute ( "UPDATE MailItemFolder SET \"Order\" = ? WHERE Id = ? AND MailAccountId = ?" ,
i + 1 , orderedFolderIds [ i ] , accountId ) ;
}
} ) . ConfigureAwait ( false ) ;
Messenger . Send ( new AccountFolderConfigurationUpdated ( accountId ) ) ;
}
public async Task ResetFolderCustomizationAsync ( Guid accountId )
{
await Connection . RunInTransactionAsync ( conn = >
{
conn . Execute ( "UPDATE MailItemFolder SET \"Order\" = 0, IsHidden = 0 WHERE MailAccountId = ?" , accountId ) ;
// Restore system folder stickiness. Category-type folders are virtual stickies too.
conn . Execute (
"UPDATE MailItemFolder SET IsSticky = 1 WHERE MailAccountId = ? AND (IsSystemFolder = 1 OR SpecialFolderType = ?)" ,
accountId , ( int ) SpecialFolderType . Category ) ;
} ) . ConfigureAwait ( false ) ;
Messenger . Send ( new AccountFolderConfigurationUpdated ( accountId ) ) ;
}
2026-04-18 00:02:54 +02:00
private static int GetDefaultFolderOrder ( MailItemFolder folder )
= > folder . SpecialFolderType = = SpecialFolderType . Other
? int . MaxValue
: ( int ) folder . SpecialFolderType ;
2026-04-16 14:07:17 +02:00
/// <summary>
/// Orders folders by user-set Order first (customized entries ahead of uncustomized ones),
2026-04-18 00:02:54 +02:00
/// then falls back to SpecialFolderType enum order for known special folders so defaults
/// like Inbox stay at the top, and finally to alphabetic folder name (culture-aware).
2026-04-16 14:07:17 +02:00
/// </summary>
private static IOrderedEnumerable < MailItemFolder > ApplyFolderSort ( IEnumerable < MailItemFolder > folders )
= > folders
. OrderBy ( a = > a . Order = = 0 ? 1 : 0 )
. ThenBy ( a = > a . Order )
2026-04-18 00:02:54 +02:00
. ThenBy ( GetDefaultFolderOrder )
2026-04-16 14:07:17 +02:00
. ThenBy ( a = > a . FolderName , StringComparer . CurrentCultureIgnoreCase )
. ThenBy ( a = > a . SpecialFolderType ) ;
2025-02-16 11:54:23 +01:00
public async Task < int > GetFolderNotificationBadgeAsync ( Guid folderId )
{
var folder = await GetFolderAsync ( folderId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( folder = = null | | ! folder . ShowUnreadCount ) return default ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var account = await _accountService . GetAccountAsync ( folder . MailAccountId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( account = = null ) return default ;
2024-04-18 01:44:37 +02:00
2025-11-15 13:29:02 +01:00
// Convert to raw SQL
string sqlQuery ;
object [ ] parameters ;
2025-02-16 11:54:23 +01:00
if ( account . Preferences . IsFocusedInboxEnabled . GetValueOrDefault ( ) & & folder . SpecialFolderType = = SpecialFolderType . Inbox )
{
2025-11-15 13:29:02 +01:00
if ( folder . SpecialFolderType ! = SpecialFolderType . Draft & & folder . SpecialFolderType ! = SpecialFolderType . Junk )
{
sqlQuery = "SELECT COUNT(*) FROM MailCopy WHERE FolderId = ? AND IsFocused = ? AND IsRead = ?" ;
parameters = new object [ ] { folderId , 1 , 0 } ;
}
else
{
sqlQuery = "SELECT COUNT(*) FROM MailCopy WHERE FolderId = ? AND IsFocused = ?" ;
parameters = new object [ ] { folderId , 1 } ;
}
2025-02-16 11:43:30 +01:00
}
2025-11-15 13:29:02 +01:00
else
2025-02-16 11:43:30 +01:00
{
2025-11-15 13:29:02 +01:00
if ( folder . SpecialFolderType ! = SpecialFolderType . Draft & & folder . SpecialFolderType ! = SpecialFolderType . Junk )
{
sqlQuery = "SELECT COUNT(*) FROM MailCopy WHERE FolderId = ? AND IsRead = ?" ;
parameters = new object [ ] { folderId , 0 } ;
}
else
{
sqlQuery = "SELECT COUNT(*) FROM MailCopy WHERE FolderId = ?" ;
parameters = new object [ ] { folderId } ;
}
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-11-15 13:29:02 +01:00
return await Connection . ExecuteScalarAsync < int > ( sqlQuery , parameters ) ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < AccountFolderTree > GetFolderStructureForAccountAsync ( Guid accountId , bool includeHiddenFolders )
{
var account = await _accountService . GetAccountAsync ( accountId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( account = = null )
throw new ArgumentException ( nameof ( account ) ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var accountTree = new AccountFolderTree ( account ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Account folders.
var folderQuery = Connection . Table < MailItemFolder > ( ) . Where ( a = > a . MailAccountId = = accountId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( ! includeHiddenFolders )
folderQuery = folderQuery . Where ( a = > ! a . IsHidden ) ;
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
// Load child folders for each folder, applying user-defined ordering with
// alphabetic fallback for folders the user hasn't explicitly re-ordered.
var rawFolders = await folderQuery . ToListAsync ( ) ;
var allFolders = ApplyFolderSort ( rawFolders ) . ToList ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( allFolders . Any ( ) )
{
// Get sticky folders. Category type is always sticky.
// Sticky folders don't have tree structure. So they can be added to the main tree.
var stickyFolders = allFolders . Where ( a = > a . IsSticky & & a . SpecialFolderType ! = SpecialFolderType . Category ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
foreach ( var stickyFolder in stickyFolders )
{
var childStructure = await GetChildFolderItemsRecursiveAsync ( stickyFolder . Id , accountId ) ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
accountTree . Folders . Add ( childStructure ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Check whether we need special 'Categories' kind of folder.
var categoryExists = allFolders . Any ( a = > a . SpecialFolderType = = SpecialFolderType . Category ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( categoryExists )
{
var categoryFolder = allFolders . First ( a = > a . SpecialFolderType = = SpecialFolderType . Category ) ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
// Construct category items under pinned items.
var categoryFolders = allFolders . Where ( a = > gmailCategoryFolderTypes . Contains ( a . SpecialFolderType ) ) ;
foreach ( var categoryFolderSubItem in categoryFolders )
{
categoryFolder . ChildFolders . Add ( categoryFolderSubItem ) ;
2025-02-16 11:43:30 +01:00
}
2025-02-16 11:54:23 +01:00
accountTree . Folders . Add ( categoryFolder ) ;
allFolders . Remove ( categoryFolder ) ;
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
// Move rest of the items into virtual More folder if any.
var nonStickyFolders = allFolders . Except ( stickyFolders ) ;
if ( nonStickyFolders . Any ( ) )
{
var virtualMoreFolder = new MailItemFolder ( )
{
FolderName = Translator . More ,
SpecialFolderType = SpecialFolderType . More
} ;
foreach ( var unstickyItem in nonStickyFolders )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
if ( account . ProviderType = = MailProviderType . Gmail )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
// Gmail requires this check to not include child folders as
// separate folder without their parent for More folder...
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( ! string . IsNullOrEmpty ( unstickyItem . ParentRemoteFolderId ) )
continue ;
}
else if ( account . ProviderType = = MailProviderType . Outlook )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
bool belongsToExistingParent = await Connection
. Table < MailItemFolder > ( )
. Where ( a = > unstickyItem . ParentRemoteFolderId = = a . RemoteFolderId )
. CountAsync ( ) > 0 ;
// No need to include this as unsticky.
if ( belongsToExistingParent ) continue ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
var structure = await GetChildFolderItemsRecursiveAsync ( unstickyItem . Id , accountId ) ;
virtualMoreFolder . ChildFolders . Add ( structure ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
// Only add more if there are any.
if ( virtualMoreFolder . ChildFolders . Count > 0 )
accountTree . Folders . Add ( virtualMoreFolder ) ;
}
2025-02-16 11:43:30 +01:00
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
return accountTree ;
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
public Task < IEnumerable < IMenuItem > > GetAccountFoldersForDisplayAsync ( IAccountMenuItem accountMenuItem )
{
if ( accountMenuItem is IMergedAccountMenuItem mergedAccountFolderMenuItem )
2024-07-09 01:05:16 +02:00
{
2025-02-16 11:54:23 +01:00
return GetMergedAccountFolderMenuItemsAsync ( mergedAccountFolderMenuItem ) ;
2025-02-16 11:35:43 +01:00
}
2025-02-16 11:54:23 +01:00
else
2025-02-16 11:43:30 +01:00
{
2025-02-16 11:54:23 +01:00
return GetSingleAccountFolderMenuItemsAsync ( accountMenuItem ) ;
}
}
private async Task < FolderMenuItem > GetPreparedFolderMenuItemRecursiveAsync ( MailAccount account , MailItemFolder parentFolder , IMenuItem parentMenuItem )
{
// Localize category folder name.
if ( parentFolder . SpecialFolderType = = SpecialFolderType . Category ) parentFolder . FolderName = Translator . CategoriesFolderNameOverride ;
2024-07-09 01:05:16 +02:00
2025-11-15 13:29:02 +01:00
const string query = "SELECT * FROM MailItemFolder WHERE ParentRemoteFolderId = ? AND MailAccountId = ?" ;
2025-02-16 11:54:23 +01:00
var preparedFolder = new FolderMenuItem ( parentFolder , account , parentMenuItem ) ;
2024-07-09 01:05:16 +02:00
2025-11-15 13:29:02 +01:00
var childFolders = await Connection . QueryAsync < MailItemFolder > ( query , parentFolder . RemoteFolderId , parentFolder . MailAccountId ) . ConfigureAwait ( false ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( childFolders . Any ( ) )
{
foreach ( var subChildFolder in childFolders )
2024-07-09 01:05:16 +02:00
{
2025-02-16 11:54:23 +01:00
var preparedChild = await GetPreparedFolderMenuItemRecursiveAsync ( account , subChildFolder , preparedFolder ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( preparedChild = = null ) continue ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
preparedFolder . SubMenuItems . Add ( preparedChild ) ;
2024-07-09 01:05:16 +02:00
}
2025-02-16 11:43:30 +01:00
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
return preparedFolder ;
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
private async Task < IEnumerable < IMenuItem > > GetSingleAccountFolderMenuItemsAsync ( IAccountMenuItem accountMenuItem )
{
var accountId = accountMenuItem . EntityId . Value ;
var preparedFolderMenuItems = new List < IMenuItem > ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Get all folders for the account. Excluding hidden folders.
var folders = await GetVisibleFoldersAsync ( accountId ) . ConfigureAwait ( false ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( ! folders . Any ( ) ) return new List < IMenuItem > ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var mailAccount = accountMenuItem . HoldingAccounts . First ( ) ;
2024-07-09 01:05:16 +02:00
2026-04-16 14:07:17 +02:00
var listingFolders = ApplyFolderSort ( folders ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var moreFolder = MailItemFolder . CreateMoreFolder ( ) ;
var categoryFolder = MailItemFolder . CreateCategoriesFolder ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var moreFolderMenuItem = new FolderMenuItem ( moreFolder , mailAccount , accountMenuItem ) ;
var categoryFolderMenuItem = new FolderMenuItem ( categoryFolder , mailAccount , accountMenuItem ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
foreach ( var item in listingFolders )
{
// Category type folders should be skipped. They will be categorized under virtual category folder.
if ( ServiceConstants . SubCategoryFolderLabelIds . Contains ( item . RemoteFolderId ) ) continue ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
bool skipEmptyParentRemoteFolders = mailAccount . ProviderType = = MailProviderType . Gmail ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( skipEmptyParentRemoteFolders & & ! string . IsNullOrEmpty ( item . ParentRemoteFolderId ) ) continue ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Sticky items belong to account menu item directly. Rest goes to More folder.
IMenuItem parentFolderMenuItem = item . IsSticky ? accountMenuItem : ServiceConstants . SubCategoryFolderLabelIds . Contains ( item . FolderName . ToUpper ( ) ) ? categoryFolderMenuItem : moreFolderMenuItem ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var preparedItem = await GetPreparedFolderMenuItemRecursiveAsync ( mailAccount , item , parentFolderMenuItem ) . ConfigureAwait ( false ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Don't add menu items that are prepared for More folder. They've been included in More virtual folder already.
// We'll add More folder later on at the end of the list.
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( preparedItem = = null ) continue ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( item . IsSticky )
{
preparedFolderMenuItems . Add ( preparedItem ) ;
}
else if ( parentFolderMenuItem is FolderMenuItem baseParentFolderMenuItem )
{
baseParentFolderMenuItem . SubMenuItems . Add ( preparedItem ) ;
}
}
2024-07-09 01:05:16 +02:00
2026-04-15 01:18:07 +02:00
var favoriteCategories = await GetFavoriteCategoryMenuItemsAsync ( mailAccount , folders , accountMenuItem ) . ConfigureAwait ( false ) ;
preparedFolderMenuItems . AddRange ( favoriteCategories ) ;
2025-02-16 11:54:23 +01:00
// Only add category folder if it's Gmail.
if ( mailAccount . ProviderType = = MailProviderType . Gmail ) preparedFolderMenuItems . Add ( categoryFolderMenuItem ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Only add More folder if there are any items in it.
if ( moreFolderMenuItem . SubMenuItems . Any ( ) ) preparedFolderMenuItems . Add ( moreFolderMenuItem ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
return preparedFolderMenuItems ;
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
private async Task < IEnumerable < IMenuItem > > GetMergedAccountFolderMenuItemsAsync ( IMergedAccountMenuItem mergedAccountFolderMenuItem )
{
var holdingAccounts = mergedAccountFolderMenuItem . HoldingAccounts ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( holdingAccounts = = null | | ! holdingAccounts . Any ( ) ) return [ ] ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var preparedFolderMenuItems = new List < IMenuItem > ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// First gather all account folders.
// Prepare single menu items for both of them.
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var allAccountFolders = new List < List < MailItemFolder > > ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
foreach ( var account in holdingAccounts )
{
var accountFolders = await GetVisibleFoldersAsync ( account . Id ) . ConfigureAwait ( false ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
allAccountFolders . Add ( accountFolders ) ;
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
var commonFolders = FindCommonFolders ( allAccountFolders ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Prepare menu items for common folders.
foreach ( var commonFolderType in commonFolders )
{
var folderItems = allAccountFolders . SelectMany ( a = > a . Where ( b = > b . SpecialFolderType = = commonFolderType ) ) . Cast < IMailItemFolder > ( ) . ToList ( ) ;
var menuItem = new MergedAccountFolderMenuItem ( folderItems , null , mergedAccountFolderMenuItem . Parameter ) ;
2025-02-16 11:43:30 +01:00
2025-02-16 11:54:23 +01:00
preparedFolderMenuItems . Add ( menuItem ) ;
2024-07-09 01:05:16 +02:00
}
2026-04-15 01:18:07 +02:00
var favoriteCategories = await GetMergedFavoriteCategoryMenuItemsAsync ( holdingAccounts , allAccountFolders , mergedAccountFolderMenuItem . Parameter ) . ConfigureAwait ( false ) ;
preparedFolderMenuItems . AddRange ( favoriteCategories ) ;
2025-02-16 11:54:23 +01:00
return preparedFolderMenuItems ;
}
2024-07-09 01:05:16 +02:00
2026-04-15 01:18:07 +02:00
private async Task < IEnumerable < IMenuItem > > GetFavoriteCategoryMenuItemsAsync ( MailAccount account , IEnumerable < IMailItemFolder > handlingFolders , IMenuItem parentMenuItem )
{
var favoriteCategories = await _mailCategoryService . GetFavoriteCategoriesAsync ( account . Id ) . ConfigureAwait ( false ) ;
if ( ! favoriteCategories . Any ( ) )
return [ ] ;
var availableFolders = handlingFolders
. Where ( a = > a . IsMoveTarget )
. Cast < IMailItemFolder > ( )
. ToList ( ) ;
return favoriteCategories
. Select ( category = > ( IMenuItem ) new MailCategoryMenuItem ( category , account , availableFolders , parentMenuItem ) )
. ToList ( ) ;
}
private async Task < IEnumerable < IMenuItem > > GetMergedFavoriteCategoryMenuItemsAsync ( IEnumerable < MailAccount > holdingAccounts , IEnumerable < IEnumerable < MailItemFolder > > allAccountFolders , MergedInbox mergedInbox )
{
var categoriesByAccount = new List < ( MailAccount Account , List < MailCategory > Categories ) > ( ) ;
foreach ( var account in holdingAccounts )
{
var categories = await _mailCategoryService . GetFavoriteCategoriesAsync ( account . Id ) . ConfigureAwait ( false ) ;
if ( categories . Any ( ) )
{
categoriesByAccount . Add ( ( account , categories ) ) ;
}
}
if ( ! categoriesByAccount . Any ( ) )
return [ ] ;
var handlingFolders = allAccountFolders
. SelectMany ( a = > a )
. Where ( a = > a . IsMoveTarget )
. Cast < IMailItemFolder > ( )
. ToList ( ) ;
return categoriesByAccount
. SelectMany ( a = > a . Categories )
. GroupBy ( a = > NormalizeCategoryName ( a . Name ) , StringComparer . OrdinalIgnoreCase )
. Select ( group = > ( IMenuItem ) new MergedMailCategoryMenuItem ( group . ToList ( ) , handlingFolders , mergedInbox ) )
. OrderBy ( item = > ( ( MergedMailCategoryMenuItem ) item ) . FolderName , StringComparer . CurrentCultureIgnoreCase )
. ToList ( ) ;
}
private static string NormalizeCategoryName ( string name )
= > name ? . Trim ( ) ? ? string . Empty ;
2025-02-16 11:54:23 +01:00
private HashSet < SpecialFolderType > FindCommonFolders ( List < List < MailItemFolder > > lists )
{
var allSpecialTypesExceptOther = Enum . GetValues < SpecialFolderType > ( ) . Cast < SpecialFolderType > ( ) . Where ( a = > a ! = SpecialFolderType . Other ) . ToList ( ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Start with all special folder types from the first list
var commonSpecialFolderTypes = new HashSet < SpecialFolderType > ( allSpecialTypesExceptOther ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
// Intersect with special folder types from all lists
foreach ( var list in lists )
{
commonSpecialFolderTypes . IntersectWith ( list . Select ( f = > f . SpecialFolderType ) ) ;
2024-07-09 01:05:16 +02:00
}
2025-02-16 11:54:23 +01:00
return commonSpecialFolderTypes ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private async Task < MailItemFolder > GetChildFolderItemsRecursiveAsync ( Guid folderId , Guid accountId )
{
var folder = await Connection . Table < MailItemFolder > ( ) . Where ( a = > a . Id = = folderId & & a . MailAccountId = = accountId ) . FirstOrDefaultAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( folder = = null )
return null ;
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
var childFoldersRaw = await Connection . Table < MailItemFolder > ( )
2025-02-16 11:54:23 +01:00
. Where ( a = > a . ParentRemoteFolderId = = folder . RemoteFolderId & & a . MailAccountId = = folder . MailAccountId )
. ToListAsync ( ) ;
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
var childFolders = ApplyFolderSort ( childFoldersRaw ) . ToList ( ) ;
2025-02-16 11:54:23 +01:00
foreach ( var childFolder in childFolders )
{
var subChild = await GetChildFolderItemsRecursiveAsync ( childFolder . Id , accountId ) ;
folder . ChildFolders . Add ( subChild ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
return folder ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < MailItemFolder > GetSpecialFolderByAccountIdAsync ( Guid accountId , SpecialFolderType type )
= > await Connection . Table < MailItemFolder > ( ) . FirstOrDefaultAsync ( a = > a . MailAccountId = = accountId & & a . SpecialFolderType = = type ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < MailItemFolder > GetFolderAsync ( Guid folderId )
= > await Connection . Table < MailItemFolder > ( ) . FirstOrDefaultAsync ( a = > a . Id . Equals ( folderId ) ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public Task < int > GetCurrentItemCountForFolder ( Guid folderId )
= > Connection . Table < MailCopy > ( ) . Where ( a = > a . FolderId = = folderId ) . CountAsync ( ) ;
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
public async Task < List < MailItemFolder > > GetFoldersAsync ( Guid accountId )
2025-02-16 11:54:23 +01:00
{
2026-04-16 14:07:17 +02:00
// Ordering is applied in managed code so that StringComparer.CurrentCultureIgnoreCase
// is honored. SQLite's default ORDER BY is not culture-aware.
const string query = "SELECT * FROM MailItemFolder WHERE MailAccountId = ?" ;
var rows = await Connection . QueryAsync < MailItemFolder > ( query , accountId ) . ConfigureAwait ( false ) ;
return ApplyFolderSort ( rows ) . ToList ( ) ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2026-04-16 14:07:17 +02:00
public async Task < List < MailItemFolder > > GetVisibleFoldersAsync ( Guid accountId )
2025-02-16 11:54:23 +01:00
{
2026-04-16 14:07:17 +02:00
const string query = "SELECT * FROM MailItemFolder WHERE MailAccountId = ? AND IsHidden = ?" ;
var rows = await Connection . QueryAsync < MailItemFolder > ( query , accountId , 0 ) . ConfigureAwait ( false ) ;
return ApplyFolderSort ( rows ) . ToList ( ) ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < IList < uint > > GetKnownUidsForFolderAsync ( Guid folderId )
{
var mailCopyIds = await GetMailCopyIdsByFolderIdAsync ( folderId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Make sure we don't include Ids that doesn't have uid separator.
// Local drafts might not have it for example.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new List < uint > ( mailCopyIds
. Where ( a = > a . Contains ( MailkitClientExtensions . MailCopyUidSeparator ) )
. Select ( a = > MailkitClientExtensions . ResolveUid ( a ) ) ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < MailAccount > UpdateSystemFolderConfigurationAsync ( Guid accountId , SystemFolderConfiguration configuration )
{
if ( configuration = = null )
throw new ArgumentNullException ( nameof ( configuration ) ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Update system folders for this account.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
await Task . WhenAll ( UpdateSystemFolderInternalAsync ( configuration . SentFolder , SpecialFolderType . Sent ) ,
UpdateSystemFolderInternalAsync ( configuration . DraftFolder , SpecialFolderType . Draft ) ,
UpdateSystemFolderInternalAsync ( configuration . JunkFolder , SpecialFolderType . Junk ) ,
UpdateSystemFolderInternalAsync ( configuration . TrashFolder , SpecialFolderType . Deleted ) ,
UpdateSystemFolderInternalAsync ( configuration . ArchiveFolder , SpecialFolderType . Archive ) ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return await _accountService . GetAccountAsync ( accountId ) . ConfigureAwait ( false ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private Task UpdateSystemFolderInternalAsync ( MailItemFolder folder , SpecialFolderType assignedSpecialFolderType )
{
if ( folder = = null ) return Task . CompletedTask ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
folder . IsSticky = true ;
folder . IsSynchronizationEnabled = true ;
folder . IsSystemFolder = true ;
folder . SpecialFolderType = assignedSpecialFolderType ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return UpdateFolderAsync ( folder ) ;
}
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
public async Task ChangeFolderSynchronizationStateAsync ( Guid folderId , bool isSynchronizationEnabled )
{
var localFolder = await Connection . Table < MailItemFolder > ( ) . FirstOrDefaultAsync ( a = > a . Id = = folderId ) ;
2025-02-16 11:43:30 +01:00
2025-02-16 11:54:23 +01:00
if ( localFolder ! = null )
{
localFolder . IsSynchronizationEnabled = isSynchronizationEnabled ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
await UpdateFolderAsync ( localFolder ) . ConfigureAwait ( false ) ;
Messenger . Send ( new FolderSynchronizationEnabled ( localFolder ) ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
#region Repository Calls
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task InsertFolderAsync ( MailItemFolder folder )
{
if ( folder = = null )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
_logger . Warning ( "Folder is null. Cannot insert." ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var account = await _accountService . GetAccountAsync ( folder . MailAccountId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( account = = null )
{
_logger . Warning ( "Account with id {MailAccountId} does not exist. Cannot insert folder." , folder . MailAccountId ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var existingFolder = await GetFolderAsync ( folder . Id ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// IMAP servers don't have unique identifier for folders all the time.
// So we'll try to match them with remote folder id and account id relation.
// If we have a match, we'll update the folder instead of inserting.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
existingFolder ? ? = await GetFolderAsync ( folder . MailAccountId , folder . RemoteFolderId ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( existingFolder = = null )
{
_logger . Debug ( "Inserting folder {Id} - {FolderName}" , folder . Id , folder . FolderName , folder . MailAccountId ) ;
2024-04-18 01:44:37 +02:00
2025-11-14 14:28:10 +01:00
await Connection . InsertAsync ( folder , typeof ( MailItemFolder ) ) . ConfigureAwait ( false ) ;
2025-02-16 11:54:23 +01:00
}
else
{
// TODO: This is not alright. We should've updated the folder instead of inserting.
// Now we need to match the properties that user might've set locally.
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
folder . Id = existingFolder . Id ;
folder . IsSticky = existingFolder . IsSticky ;
folder . SpecialFolderType = existingFolder . SpecialFolderType ;
folder . ShowUnreadCount = existingFolder . ShowUnreadCount ;
folder . TextColorHex = existingFolder . TextColorHex ;
folder . BackgroundColorHex = existingFolder . BackgroundColorHex ;
2026-04-16 14:07:17 +02:00
folder . Order = existingFolder . Order ;
folder . IsHidden = existingFolder . IsHidden ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
_logger . Debug ( "Folder {Id} - {FolderName} already exists. Updating." , folder . Id , folder . FolderName ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
await UpdateFolderAsync ( folder ) . ConfigureAwait ( false ) ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task UpdateFolderAsync ( MailItemFolder folder )
{
if ( folder = = null )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
_logger . Warning ( "Folder is null. Cannot update." ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return ;
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
_logger . Debug ( "Updating folder {FolderName}" , folder . Id , folder . FolderName ) ;
2025-02-16 11:35:43 +01:00
2025-11-14 14:28:10 +01:00
await Connection . UpdateAsync ( folder , typeof ( MailItemFolder ) ) . ConfigureAwait ( false ) ;
2025-02-16 11:54:23 +01:00
}
2026-02-14 12:52:17 +01:00
public Task UpdateFolderHighestModeSeqAsync ( Guid folderId , long highestModeSeq )
= > Connection . ExecuteAsync ( "UPDATE MailItemFolder SET HighestModeSeq = ? WHERE Id = ?" , highestModeSeq , folderId ) ;
2025-02-16 11:54:23 +01:00
private async Task DeleteFolderAsync ( MailItemFolder folder )
{
if ( folder = = null )
{
_logger . Warning ( "Folder is null. Cannot delete." ) ;
return ;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
var account = await _accountService . GetAccountAsync ( folder . MailAccountId ) . ConfigureAwait ( false ) ;
if ( account = = null )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
_logger . Warning ( "Account with id {MailAccountId} does not exist. Cannot delete folder." , folder . MailAccountId ) ;
return ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
_logger . Debug ( "Deleting folder {FolderName}" , folder . FolderName ) ;
2024-04-18 01:44:37 +02:00
2025-12-25 17:21:23 +01:00
await Connection . DeleteAsync < MailItemFolder > ( folder . Id ) . ConfigureAwait ( false ) ;
2024-06-21 04:24:04 +02:00
2025-02-16 11:54:23 +01:00
// Delete all existing mails from this folder.
await Connection . ExecuteAsync ( "DELETE FROM MailCopy WHERE FolderId = ?" , folder . Id ) ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
// TODO: Delete MIME messages from the disk.
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
#endregion
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private Task < List < string > > GetMailCopyIdsByFolderIdAsync ( Guid folderId )
{
2025-11-15 13:29:02 +01:00
const string query = "SELECT Id FROM MailCopy WHERE FolderId = ?" ;
return Connection . QueryScalarsAsync < string > ( query , folderId ) ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < List < MailFolderPairMetadata > > GetMailFolderPairMetadatasAsync ( IEnumerable < string > mailCopyIds )
{
2025-11-15 13:29:02 +01:00
var mailCopyIdList = mailCopyIds . ToList ( ) ;
var placeholders = string . Join ( "," , mailCopyIdList . Select ( _ = > "?" ) ) ;
var query = $"SELECT DISTINCT MailCopy.Id as MailCopyId, MailItemFolder.Id as FolderId, MailItemFolder.RemoteFolderId as RemoteFolderId FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id WHERE MailCopy.Id IN ({placeholders})" ;
var parameters = mailCopyIdList . Cast < object > ( ) . ToArray ( ) ;
return await Connection . QueryAsync < MailFolderPairMetadata > ( query , parameters ) ;
2025-02-16 11:54:23 +01:00
}
public Task < List < MailFolderPairMetadata > > GetMailFolderPairMetadatasAsync ( string mailCopyId )
= > GetMailFolderPairMetadatasAsync ( new List < string > ( ) { mailCopyId } ) ;
public async Task < List < MailItemFolder > > GetSynchronizationFoldersAsync ( MailSynchronizationOptions options )
{
var folders = new List < MailItemFolder > ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( options . Type = = MailSynchronizationType . IMAPIdle )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
// Type Inbox will include Sent, Drafts and Deleted folders as well.
// For IMAP idle sync, we must include only Inbox folder.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var inboxFolder = await GetSpecialFolderByAccountIdAsync ( options . AccountId , SpecialFolderType . Inbox ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( inboxFolder ! = null )
{
folders . Add ( inboxFolder ) ;
}
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
else if ( options . Type = = MailSynchronizationType . FullFolders )
{
// Only get sync enabled folders.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var synchronizationFolders = await Connection . Table < MailItemFolder > ( )
. Where ( a = > a . MailAccountId = = options . AccountId & & a . IsSynchronizationEnabled )
. OrderBy ( a = > a . SpecialFolderType )
. ToListAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
folders . AddRange ( synchronizationFolders ) ;
}
else
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
// Inbox, Sent and Draft folders must always be synchronized regardless of whether they are enabled or not.
// Custom folder sync will add additional folders to the list if not specified.
2025-02-16 11:43:30 +01:00
2025-02-16 11:54:23 +01:00
var mustHaveFolders = await GetInboxSynchronizationFoldersAsync ( options . AccountId ) ;
2025-02-16 11:43:30 +01:00
2025-02-16 11:54:23 +01:00
if ( options . Type = = MailSynchronizationType . InboxOnly )
{
return mustHaveFolders ;
2025-02-15 12:53:32 +01:00
}
2025-02-16 11:54:23 +01:00
else if ( options . Type = = MailSynchronizationType . CustomFolders )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
// Only get the specified folders.
2024-04-18 01:44:37 +02:00
2024-08-21 13:15:50 +02:00
var synchronizationFolders = await Connection . Table < MailItemFolder > ( )
2025-02-16 11:54:23 +01:00
. Where ( a = >
a . MailAccountId = = options . AccountId & &
options . SynchronizationFolderIds . Contains ( a . Id ) )
2024-08-21 13:15:50 +02:00
. ToListAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( options . ExcludeMustHaveFolders )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
return synchronizationFolders ;
2024-04-18 01:44:37 +02:00
}
2025-02-15 12:53:32 +01:00
2025-02-16 11:54:23 +01:00
// Order is important for moving.
// By implementation, removing mail folders must be synchronized first. Requests are made in that order for custom sync.
// eg. Moving item from Folder A to Folder B. If we start syncing Folder B first, we might miss adding assignment for Folder A.
2024-08-21 13:15:50 +02:00
2025-02-16 11:54:23 +01:00
var orderedCustomFolders = synchronizationFolders . OrderBy ( a = > options . SynchronizationFolderIds . IndexOf ( a . Id ) ) ;
2024-08-21 13:15:50 +02:00
2025-02-16 11:54:23 +01:00
foreach ( var item in orderedCustomFolders )
{
if ( ! mustHaveFolders . Any ( a = > a . Id = = item . Id ) )
2024-08-21 13:15:50 +02:00
{
2025-02-16 11:54:23 +01:00
mustHaveFolders . Add ( item ) ;
2024-08-21 13:15:50 +02:00
}
2024-04-18 01:44:37 +02:00
}
}
2025-02-16 11:54:23 +01:00
return mustHaveFolders ;
2024-08-21 13:15:50 +02:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return folders ;
}
2024-08-21 13:15:50 +02:00
2025-02-16 11:54:23 +01:00
private async Task < List < MailItemFolder > > GetInboxSynchronizationFoldersAsync ( Guid accountId )
{
var folders = new List < MailItemFolder > ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var inboxFolder = await GetSpecialFolderByAccountIdAsync ( accountId , SpecialFolderType . Inbox ) ;
var sentFolder = await GetSpecialFolderByAccountIdAsync ( accountId , SpecialFolderType . Sent ) ;
var draftFolder = await GetSpecialFolderByAccountIdAsync ( accountId , SpecialFolderType . Draft ) ;
var deletedFolder = await GetSpecialFolderByAccountIdAsync ( accountId , SpecialFolderType . Deleted ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( deletedFolder ! = null )
{
folders . Add ( deletedFolder ) ;
}
2024-06-21 23:44:59 +02:00
2025-02-16 11:54:23 +01:00
if ( inboxFolder ! = null )
{
folders . Add ( inboxFolder ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// For properly creating threads we need Sent and Draft to be synchronized as well.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( sentFolder ! = null )
{
folders . Add ( sentFolder ) ;
2025-02-16 11:35:43 +01:00
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( draftFolder ! = null )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
folders . Add ( draftFolder ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return folders ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public Task < MailItemFolder > GetFolderAsync ( Guid accountId , string remoteFolderId )
= > Connection . Table < MailItemFolder > ( ) . FirstOrDefaultAsync ( a = > a . MailAccountId = = accountId & & a . RemoteFolderId = = remoteFolderId ) ;
2024-06-02 21:35:03 +02:00
2025-02-16 11:54:23 +01:00
public async Task DeleteFolderAsync ( Guid accountId , string remoteFolderId )
{
var folder = await GetFolderAsync ( accountId , remoteFolderId ) ;
2024-07-09 01:05:16 +02:00
2025-02-16 11:54:23 +01:00
if ( folder = = null )
2024-07-09 01:05:16 +02:00
{
2025-02-16 11:54:23 +01:00
_logger . Warning ( "Folder with id {RemoteFolderId} does not exist. Delete folder canceled." , remoteFolderId ) ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
return ;
2024-07-09 01:05:16 +02:00
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
await DeleteFolderAsync ( folder ) . ConfigureAwait ( false ) ;
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
public async Task ChangeFolderShowUnreadCountStateAsync ( Guid folderId , bool showUnreadCount )
{
var localFolder = await GetFolderAsync ( folderId ) ;
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
if ( localFolder ! = null )
2025-02-16 11:43:30 +01:00
{
2025-02-16 11:54:23 +01:00
localFolder . ShowUnreadCount = showUnreadCount ;
await UpdateFolderAsync ( localFolder ) . ConfigureAwait ( false ) ;
2025-02-16 11:43:30 +01:00
}
2025-02-16 11:35:43 +01:00
}
2025-02-16 11:54:23 +01:00
public async Task < bool > IsInboxAvailableForAccountAsync ( Guid accountId )
= > await Connection . Table < MailItemFolder > ( )
. Where ( a = > a . SpecialFolderType = = SpecialFolderType . Inbox & & a . MailAccountId = = accountId )
. CountAsync ( ) = = 1 ;
public Task UpdateFolderLastSyncDateAsync ( Guid folderId )
= > Connection . ExecuteAsync ( "UPDATE MailItemFolder SET LastSynchronizedDate = ? WHERE Id = ?" , DateTime . UtcNow , folderId ) ;
public Task < List < UnreadItemCountResult > > GetUnreadItemCountResultsAsync ( IEnumerable < Guid > accountIds )
{
2025-11-15 13:29:02 +01:00
var accountIdList = accountIds . ToList ( ) ;
var placeholders = string . Join ( "," , accountIdList . Select ( _ = > "?" ) ) ;
var query = $"SELECT MailItemFolder.Id as FolderId, MailItemFolder.SpecialFolderType as SpecialFolderType, count(DISTINCT MailCopy.Id) as UnreadItemCount, MailItemFolder.MailAccountId as AccountId FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id WHERE MailItemFolder.MailAccountId IN ({placeholders}) AND MailCopy.IsRead = ? AND MailItemFolder.ShowUnreadCount = ? GROUP BY MailItemFolder.Id" ;
var parameters = accountIdList . Cast < object > ( ) . Concat ( new object [ ] { 0 , 1 } ) . ToArray ( ) ;
return Connection . QueryAsync < UnreadItemCountResult > ( query , parameters ) ;
2025-02-16 11:54:23 +01:00
}
2024-04-18 01:44:37 +02:00
}