From 12a39064dcabc6a11b53ab02526b058fa21479c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sat, 15 Nov 2025 13:29:02 +0100 Subject: [PATCH] Some item templates and removal of sqlkata. --- Directory.Packages.props | 1 - Wino.Core/Wino.Core.csproj | 1 - .../Dialogs/KeyboardShortcutDialog.xaml | 10 +- Wino.Mail.WinUI/Package.appxmanifest | 2 +- .../Views/Account/AccountManagementPage.xaml | 10 +- Wino.Mail.WinUI/Views/ComposePage.xaml | 10 +- .../ImapSetup/AdvancedImapSetupPage.xaml | 38 ++- .../Settings/EditAccountDetailsPage.xaml | 37 ++- .../Views/Settings/LanguageTimePage.xaml | 12 +- .../Views/Settings/PersonalizationPage.xaml | 9 +- Wino.Mail.WinUI/Wino.Mail.WinUI.csproj | 20 +- Wino.Services/AccountService.cs | 73 ++---- Wino.Services/CalendarService.cs | 50 ++-- Wino.Services/ContactService.cs | 21 +- Wino.Services/Extensions/SqlKataExtensions.cs | 14 -- Wino.Services/FolderService.cs | 98 ++++---- Wino.Services/KeyboardShortcutService.cs | 44 ++-- Wino.Services/MailService.cs | 218 ++++++++---------- Wino.Services/Wino.Services.csproj | 1 - 19 files changed, 313 insertions(+), 356 deletions(-) delete mode 100644 Wino.Services/Extensions/SqlKataExtensions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 9f6d84b1..ebc19048 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,6 @@ - diff --git a/Wino.Core/Wino.Core.csproj b/Wino.Core/Wino.Core.csproj index 59f2ce00..2ef44ca1 100644 --- a/Wino.Core/Wino.Core.csproj +++ b/Wino.Core/Wino.Core.csproj @@ -31,7 +31,6 @@ - diff --git a/Wino.Mail.WinUI/Dialogs/KeyboardShortcutDialog.xaml b/Wino.Mail.WinUI/Dialogs/KeyboardShortcutDialog.xaml index fefb648f..231b65d2 100644 --- a/Wino.Mail.WinUI/Dialogs/KeyboardShortcutDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/KeyboardShortcutDialog.xaml @@ -32,9 +32,15 @@ + SelectedItem="{x:Bind SelectedMailOperation, Mode=TwoWay}"> + + + + + + + diff --git a/Wino.Mail.WinUI/Package.appxmanifest b/Wino.Mail.WinUI/Package.appxmanifest index d186e688..7ad4f3c9 100644 --- a/Wino.Mail.WinUI/Package.appxmanifest +++ b/Wino.Mail.WinUI/Package.appxmanifest @@ -22,7 +22,7 @@ + Version="2.0.13.0" /> diff --git a/Wino.Mail.WinUI/Views/Account/AccountManagementPage.xaml b/Wino.Mail.WinUI/Views/Account/AccountManagementPage.xaml index 55a8b33c..8a44c482 100644 --- a/Wino.Mail.WinUI/Views/Account/AccountManagementPage.xaml +++ b/Wino.Mail.WinUI/Views/Account/AccountManagementPage.xaml @@ -9,6 +9,7 @@ xmlns:data="using:Wino.Core.ViewModels.Data" xmlns:domain="using:Wino.Core.Domain" xmlns:helpers="using:Wino.Helpers" + xmlns:interfaces="using:Wino.Core.Domain.Interfaces" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:selectors="using:Wino.Selectors" @@ -194,9 +195,14 @@ + SelectedItem="{x:Bind ViewModel.StartupAccount, Mode=TwoWay}"> + + + + + + diff --git a/Wino.Mail.WinUI/Views/ComposePage.xaml b/Wino.Mail.WinUI/Views/ComposePage.xaml index 2c463370..c56d2e53 100644 --- a/Wino.Mail.WinUI/Views/ComposePage.xaml +++ b/Wino.Mail.WinUI/Views/ComposePage.xaml @@ -12,6 +12,7 @@ xmlns:domain="using:Wino.Core.Domain" xmlns:entities="using:Wino.Core.Domain.Entities.Shared" xmlns:helpers="using:Wino.Helpers" + xmlns:mail="using:Wino.Core.Domain.Entities.Mail" xmlns:mailkit="using:MimeKit" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" @@ -470,10 +471,15 @@ + SelectedItem="{x:Bind ViewModel.SelectedAlias, Mode=TwoWay}"> + + + + + + + SelectedIndex="0"> + + + + + + @@ -115,9 +121,14 @@ + SelectedIndex="0"> + + + + + + @@ -174,9 +185,15 @@ + SelectedIndex="0"> + + + + + + + @@ -185,9 +202,14 @@ + SelectedIndex="0"> + + + + + + diff --git a/Wino.Mail.WinUI/Views/Settings/EditAccountDetailsPage.xaml b/Wino.Mail.WinUI/Views/Settings/EditAccountDetailsPage.xaml index 3d4ab072..ac6b7535 100644 --- a/Wino.Mail.WinUI/Views/Settings/EditAccountDetailsPage.xaml +++ b/Wino.Mail.WinUI/Views/Settings/EditAccountDetailsPage.xaml @@ -3,6 +3,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:abstract="using:Wino.Views.Abstract" + xmlns:accounts="using:Wino.Core.Domain.Models.Accounts" xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:converters="using:Wino.Core.WinUI.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" @@ -138,18 +139,28 @@ + SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerConnectionSecurityIndex, Mode=TwoWay}"> + + + + + + + SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerAuthenticationMethodIndex, Mode=TwoWay}"> + + + + + + + SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerConnectionSecurityIndex, Mode=TwoWay}"> + + + + + + + SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerAuthenticationMethodIndex, Mode=TwoWay}"> + + + + + + @@ -15,10 +16,13 @@ CommandParameter="Language" Description="{x:Bind domain:Translator.SettingsLanguage_Description}" Header="{x:Bind domain:Translator.SettingsLanguage_Title}"> - + + + + + + + diff --git a/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml b/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml index 66c440e5..93008c61 100644 --- a/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml +++ b/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml @@ -105,10 +105,15 @@ + SelectedItem="{x:Bind ViewModel.SelectedElementTheme, Mode=TwoWay}"> + + + + + + diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj index d74bef08..0eb35e4b 100644 --- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj +++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj @@ -15,13 +15,20 @@ False True - True + False true + False + True + + + False + True + $(DefineConstants);DISABLE_XAML_GENERATED_MAIN - + @@ -269,13 +276,6 @@ - False - True - - - False - True - True False True @@ -287,4 +287,4 @@ 0 2661584A2F31D3419FFF8AA09A2C16B1991B8290 - \ No newline at end of file + diff --git a/Wino.Services/AccountService.cs b/Wino.Services/AccountService.cs index 2a3c7696..c7ccac9f 100644 --- a/Wino.Services/AccountService.cs +++ b/Wino.Services/AccountService.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using CommunityToolkit.Mvvm.Messaging; using Serilog; -using SqlKata; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; @@ -55,14 +54,13 @@ public class AccountService : BaseDatabaseService, IAccountService await Connection.ExecuteAsync("UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?", mergedInboxId); // Then, add new accounts to merged inbox. - var query = new Query("MailAccount") - .WhereIn("Id", linkedAccountIds) - .AsUpdate(new - { - MergedInboxId = mergedInboxId - }); - - await Connection.ExecuteAsync(query.GetRawQuery()); + var accountIdList = linkedAccountIds.ToList(); + var placeholders = string.Join(",", accountIdList.Select(_ => "?")); + var sql = $"UPDATE MailAccount SET MergedInboxId = ? WHERE Id IN ({placeholders})"; + var parameters = new List { mergedInboxId }; + parameters.AddRange(accountIdList.Cast()); + + await Connection.ExecuteAsync(sql, parameters.ToArray()); WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested()); } @@ -84,14 +82,7 @@ public class AccountService : BaseDatabaseService, IAccountService return; } - var query = new Query("MailAccount") - .Where("MergedInboxId", mergedInboxId) - .AsUpdate(new - { - MergedInboxId = (Guid?)null - }); - - await Connection.ExecuteAsync(query.GetRawQuery()).ConfigureAwait(false); + await Connection.ExecuteAsync("UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?", mergedInboxId).ConfigureAwait(false); await Connection.DeleteAsync(mergedInbox).ConfigureAwait(false); // Change the startup entity id if it was the merged inbox. @@ -191,14 +182,7 @@ public class AccountService : BaseDatabaseService, IAccountService public async Task RenameMergedAccountAsync(Guid mergedInboxId, string newName) { - var query = new Query("MergedInbox") - .Where("Id", mergedInboxId) - .AsUpdate(new - { - Name = newName - }); - - await Connection.ExecuteAsync(query.GetRawQuery()); + await Connection.ExecuteAsync("UPDATE MergedInbox SET Name = ? WHERE Id = ?", newName, mergedInboxId); ReportUIChange(new MergedInboxRenamed(mergedInboxId, newName)); } @@ -261,11 +245,9 @@ public class AccountService : BaseDatabaseService, IAccountService public async Task> GetAccountAliasesAsync(Guid accountId) { - var query = new Query(nameof(MailAccountAlias)) - .Where(nameof(MailAccountAlias.AccountId), accountId) - .OrderByDesc(nameof(MailAccountAlias.IsRootAlias)); - - return await Connection.QueryAsync(query.GetRawQuery()).ConfigureAwait(false); + return await Connection.QueryAsync( + "SELECT * FROM MailAccountAlias WHERE AccountId = ? ORDER BY IsRootAlias DESC", + accountId).ConfigureAwait(false); } private Task GetMergedInboxInformationAsync(Guid mergedInboxId) @@ -273,17 +255,9 @@ public class AccountService : BaseDatabaseService, IAccountService public async Task DeleteAccountMailCacheAsync(Guid accountId, AccountCacheResetReason accountCacheResetReason) { - var deleteQuery = new Query("MailCopy") - .WhereIn("Id", q => q - .From("MailCopy") - .Select("Id") - .WhereIn("FolderId", q2 => q2 - .From("MailItemFolder") - .Select("Id") - .Where("MailAccountId", accountId) - )).AsDelete(); - - await Connection.ExecuteAsync(deleteQuery.GetRawQuery()); + await Connection.ExecuteAsync( + "DELETE FROM MailCopy WHERE Id IN (SELECT Id FROM MailCopy WHERE FolderId IN (SELECT Id FROM MailItemFolder WHERE MailAccountId = ?))", + accountId); WeakReferenceMessenger.Default.Send(new AccountCacheResetMessage(accountId, accountCacheResetReason)); } @@ -306,14 +280,9 @@ public class AccountService : BaseDatabaseService, IAccountService // There will be only one account in the merged inbox. Remove the link for the other account as well. if (mergedInboxAccountCount == 2) { - var query = new Query("MailAccount") - .Where("MergedInboxId", account.MergedInboxId.Value) - .AsUpdate(new - { - MergedInboxId = (Guid?)null - }); - - await Connection.ExecuteAsync(query.GetRawQuery()).ConfigureAwait(false); + await Connection.ExecuteAsync( + "UPDATE MailAccount SET MergedInboxId = NULL WHERE MergedInboxId = ?", + account.MergedInboxId.Value).ConfigureAwait(false); } } @@ -494,11 +463,7 @@ public class AccountService : BaseDatabaseService, IAccountService { // Create query to delete alias. - var query = new Query("MailAccountAlias") - .Where("Id", aliasId) - .AsDelete(); - - await Connection.ExecuteAsync(query.GetRawQuery()).ConfigureAwait(false); + await Connection.ExecuteAsync("DELETE FROM MailAccountAlias WHERE Id = ?", aliasId).ConfigureAwait(false); } public async Task CreateAccountAsync(MailAccount account, CustomServerInformation customServerInformation) diff --git a/Wino.Services/CalendarService.cs b/Wino.Services/CalendarService.cs index b48f7280..bc8d51a8 100644 --- a/Wino.Services/CalendarService.cs +++ b/Wino.Services/CalendarService.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; -using SqlKata; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Enums; @@ -43,14 +42,9 @@ public class CalendarService : BaseDatabaseService, ICalendarService public async Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar) { - var deleteCalendarItemsQuery = new Query() - .From(nameof(CalendarItem)) - .Where(nameof(CalendarItem.CalendarId), accountCalendar.Id) - .Where(nameof(AccountCalendar.AccountId), accountCalendar.AccountId); - - var rawQuery = deleteCalendarItemsQuery.GetRawQuery(); - - await Connection.ExecuteAsync(rawQuery); + await Connection.ExecuteAsync( + "DELETE FROM CalendarItem WHERE CalendarId = ? AND AccountId = ?", + accountCalendar.Id, accountCalendar.AccountId); await Connection.DeleteAsync(accountCalendar); WeakReferenceMessenger.Default.Send(new CalendarListDeleted(accountCalendar)); @@ -182,24 +176,16 @@ public class CalendarService : BaseDatabaseService, ICalendarService public Task GetCalendarItemAsync(Guid id) { - var query = new Query() - .From(nameof(CalendarItem)) - .Where(nameof(CalendarItem.Id), id); - - var rawQuery = query.GetRawQuery(); - return Connection.FindWithQueryAsync(rawQuery); + return Connection.FindWithQueryAsync( + "SELECT * FROM CalendarItem WHERE Id = ?", + id); } public async Task GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId) { - var query = new Query() - .From(nameof(CalendarItem)) - .Where(nameof(CalendarItem.CalendarId), accountCalendarId) - .Where(nameof(CalendarItem.RemoteEventId), remoteEventId); - - var rawQuery = query.GetRawQuery(); - - var calendarItem = await Connection.FindWithQueryAsync(rawQuery); + var calendarItem = await Connection.FindWithQueryAsync( + "SELECT * FROM CalendarItem WHERE CalendarId = ? AND RemoteEventId = ?", + accountCalendarId, remoteEventId); // Load assigned calendar. if (calendarItem != null) @@ -212,12 +198,9 @@ public class CalendarService : BaseDatabaseService, ICalendarService public Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken) { - var query = new Query() - .From(nameof(AccountCalendar)) - .Where(nameof(AccountCalendar.Id), calendarId) - .AsUpdate(new { SynchronizationDeltaToken = deltaToken }); - - return Connection.ExecuteAsync(query.GetRawQuery()); + return Connection.ExecuteAsync( + "UPDATE AccountCalendar SET SynchronizationDeltaToken = ? WHERE Id = ?", + deltaToken, calendarId); } public Task> GetAttendeesAsync(Guid calendarEventTrackingId) @@ -228,12 +211,9 @@ public class CalendarService : BaseDatabaseService, ICalendarService await Connection.RunInTransactionAsync((connection) => { // Clear all attendees. - var query = new Query() - .From(nameof(CalendarEventAttendee)) - .Where(nameof(CalendarEventAttendee.CalendarItemId), calendarItemId) - .AsDelete(); - - connection.Execute(query.GetRawQuery()); + connection.Execute( + "DELETE FROM CalendarEventAttendee WHERE CalendarItemId = ?", + calendarItemId); // Insert new attendees. connection.InsertAll(allAttendees, typeof(CalendarEventAttendee)); diff --git a/Wino.Services/ContactService.cs b/Wino.Services/ContactService.cs index 9a83ae73..cd765165 100644 --- a/Wino.Services/ContactService.cs +++ b/Wino.Services/ContactService.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using MimeKit; using Serilog; -using SqlKata; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; using Wino.Services.Extensions; @@ -29,13 +28,9 @@ public class ContactService : BaseDatabaseService, IContactService if (queryText == null || queryText.Length < 2) return Task.FromResult>(null); - var query = new Query(nameof(AccountContact)); - query.WhereContains("Address", queryText); - query.OrWhereContains("Name", queryText); - - var rawLikeQuery = query.GetRawQuery(); - - return Connection.QueryAsync(rawLikeQuery); + const string query = "SELECT * FROM AccountContact WHERE Address LIKE ? OR Name LIKE ?"; + var pattern = $"%{queryText}%"; + return Connection.QueryAsync(query, pattern, pattern); } public Task GetAddressInformationByAddressAsync(string address) @@ -81,13 +76,9 @@ public class ContactService : BaseDatabaseService, IContactService if (string.IsNullOrWhiteSpace(searchQuery)) return GetAllContactsAsync(); - var query = new Query(nameof(AccountContact)); - query.WhereContains("Address", searchQuery.Trim()); - query.OrWhereContains("Name", searchQuery.Trim()); - - var rawLikeQuery = query.GetRawQuery(); - - return Connection.QueryAsync(rawLikeQuery); + const string query = "SELECT * FROM AccountContact WHERE Address LIKE ? OR Name LIKE ?"; + var pattern = $"%{searchQuery.Trim()}%"; + return Connection.QueryAsync(query, pattern, pattern); } public async Task UpdateContactAsync(AccountContact contact) diff --git a/Wino.Services/Extensions/SqlKataExtensions.cs b/Wino.Services/Extensions/SqlKataExtensions.cs deleted file mode 100644 index b7d75e5f..00000000 --- a/Wino.Services/Extensions/SqlKataExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using SqlKata; -using SqlKata.Compilers; - -namespace Wino.Services.Extensions; - -public static class SqlKataExtensions -{ - private static SqliteCompiler Compiler = new SqliteCompiler(); - - public static string GetRawQuery(this Query query) - { - return Compiler.Compile(query).ToString(); - } -} diff --git a/Wino.Services/FolderService.cs b/Wino.Services/FolderService.cs index 217c4d40..f7266712 100644 --- a/Wino.Services/FolderService.cs +++ b/Wino.Services/FolderService.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Serilog; -using SqlKata; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Entities.Shared; @@ -53,23 +52,38 @@ public class FolderService : BaseDatabaseService, IFolderService if (account == null) return default; - var query = new Query("MailCopy") - .Where("FolderId", folderId) - .SelectRaw("count (DISTINCT Id)"); - - // If focused inbox is enabled, we need to check if this is the inbox folder. + // Convert to raw SQL + string sqlQuery; + object[] parameters; + if (account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault() && folder.SpecialFolderType == SpecialFolderType.Inbox) { - query.Where("IsFocused", 1); + 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 }; + } } - - // Draft and Junk folders are not counted as unread. They must return the item count instead. - if (folder.SpecialFolderType != SpecialFolderType.Draft && folder.SpecialFolderType != SpecialFolderType.Junk) + else { - query.Where("IsRead", 0); + 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 }; + } } - return await Connection.ExecuteScalarAsync(query.GetRawQuery()); + return await Connection.ExecuteScalarAsync(sqlQuery, parameters); } public async Task GetFolderStructureForAccountAsync(Guid accountId, bool includeHiddenFolders) @@ -186,13 +200,10 @@ public class FolderService : BaseDatabaseService, IFolderService // Localize category folder name. if (parentFolder.SpecialFolderType == SpecialFolderType.Category) parentFolder.FolderName = Translator.CategoriesFolderNameOverride; - var query = new Query(nameof(MailItemFolder)) - .Where(nameof(MailItemFolder.ParentRemoteFolderId), parentFolder.RemoteFolderId) - .Where(nameof(MailItemFolder.MailAccountId), parentFolder.MailAccountId); - + const string query = "SELECT * FROM MailItemFolder WHERE ParentRemoteFolderId = ? AND MailAccountId = ?"; var preparedFolder = new FolderMenuItem(parentFolder, account, parentMenuItem); - var childFolders = await Connection.QueryAsync(query.GetRawQuery()).ConfigureAwait(false); + var childFolders = await Connection.QueryAsync(query, parentFolder.RemoteFolderId, parentFolder.MailAccountId).ConfigureAwait(false); if (childFolders.Any()) { @@ -348,21 +359,14 @@ public class FolderService : BaseDatabaseService, IFolderService public Task> GetFoldersAsync(Guid accountId) { - var query = new Query(nameof(MailItemFolder)) - .Where(nameof(MailItemFolder.MailAccountId), accountId) - .OrderBy(nameof(MailItemFolder.SpecialFolderType)); - - return Connection.QueryAsync(query.GetRawQuery()); + const string query = "SELECT * FROM MailItemFolder WHERE MailAccountId = ? ORDER BY SpecialFolderType"; + return Connection.QueryAsync(query, accountId); } public Task> GetVisibleFoldersAsync(Guid accountId) { - var query = new Query(nameof(MailItemFolder)) - .Where(nameof(MailItemFolder.MailAccountId), accountId) - .Where(nameof(MailItemFolder.IsHidden), false) - .OrderBy(nameof(MailItemFolder.SpecialFolderType)); - - return Connection.QueryAsync(query.GetRawQuery()); + const string query = "SELECT * FROM MailItemFolder WHERE MailAccountId = ? AND IsHidden = ? ORDER BY SpecialFolderType"; + return Connection.QueryAsync(query, accountId, 0); } public async Task> GetKnownUidsForFolderAsync(Guid folderId) @@ -516,25 +520,18 @@ public class FolderService : BaseDatabaseService, IFolderService private Task> GetMailCopyIdsByFolderIdAsync(Guid folderId) { - var query = new Query("MailCopy") - .Where("FolderId", folderId) - .Select("Id"); - - return Connection.QueryScalarsAsync(query.GetRawQuery()); + const string query = "SELECT Id FROM MailCopy WHERE FolderId = ?"; + return Connection.QueryScalarsAsync(query, folderId); } public async Task> GetMailFolderPairMetadatasAsync(IEnumerable mailCopyIds) { - // Get all assignments for all items. - var query = new Query(nameof(MailCopy)) - .Join(nameof(MailItemFolder), $"{nameof(MailCopy)}.FolderId", $"{nameof(MailItemFolder)}.Id") - .WhereIn($"{nameof(MailCopy)}.Id", mailCopyIds) - .SelectRaw($"{nameof(MailCopy)}.Id as MailCopyId, {nameof(MailItemFolder)}.Id as FolderId, {nameof(MailItemFolder)}.RemoteFolderId as RemoteFolderId") - .Distinct(); - - var rowQuery = query.GetRawQuery(); - - return await Connection.QueryAsync(rowQuery); + 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().ToArray(); + + return await Connection.QueryAsync(query, parameters); } public Task> GetMailFolderPairMetadatasAsync(string mailCopyId) @@ -687,14 +684,11 @@ public class FolderService : BaseDatabaseService, IFolderService public Task> GetUnreadItemCountResultsAsync(IEnumerable accountIds) { - var query = new Query(nameof(MailCopy)) - .Join(nameof(MailItemFolder), $"{nameof(MailCopy)}.FolderId", $"{nameof(MailItemFolder)}.Id") - .WhereIn($"{nameof(MailItemFolder)}.MailAccountId", accountIds) - .Where($"{nameof(MailCopy)}.IsRead", 0) - .Where($"{nameof(MailItemFolder)}.ShowUnreadCount", 1) - .SelectRaw($"{nameof(MailItemFolder)}.Id as FolderId, {nameof(MailItemFolder)}.SpecialFolderType as SpecialFolderType, count (DISTINCT {nameof(MailCopy)}.Id) as UnreadItemCount, {nameof(MailItemFolder)}.MailAccountId as AccountId") - .GroupBy($"{nameof(MailItemFolder)}.Id"); - - return Connection.QueryAsync(query.GetRawQuery()); + 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().Concat(new object[] { 0, 1 }).ToArray(); + + return Connection.QueryAsync(query, parameters); } } diff --git a/Wino.Services/KeyboardShortcutService.cs b/Wino.Services/KeyboardShortcutService.cs index 1344bdbe..c3896dff 100644 --- a/Wino.Services/KeyboardShortcutService.cs +++ b/Wino.Services/KeyboardShortcutService.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using SqlKata; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; @@ -24,10 +23,8 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer /// public async Task> GetKeyboardShortcutsAsync() { - var query = new Query(nameof(KeyboardShortcut)) - .OrderBy(nameof(KeyboardShortcut.MailOperation)); - - return await Connection.QueryAsync(query.GetRawQuery()); + return await Connection.QueryAsync( + "SELECT * FROM KeyboardShortcut ORDER BY MailOperation"); } /// @@ -35,11 +32,9 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer /// public async Task> GetEnabledKeyboardShortcutsAsync() { - var query = new Query(nameof(KeyboardShortcut)) - .Where(nameof(KeyboardShortcut.IsEnabled), true) - .OrderBy(nameof(KeyboardShortcut.MailOperation)); - - return await Connection.QueryAsync(query.GetRawQuery()); + return await Connection.QueryAsync( + "SELECT * FROM KeyboardShortcut WHERE IsEnabled = ? ORDER BY MailOperation", + true); } /// @@ -66,9 +61,6 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer /// public async Task DeleteKeyboardShortcutAsync(Guid shortcutId) { - var query = new Query(nameof(KeyboardShortcut)) - .Where(nameof(KeyboardShortcut.Id), shortcutId); - await Connection.ExecuteAsync($"DELETE FROM {nameof(KeyboardShortcut)} WHERE {nameof(KeyboardShortcut.Id)} = ?", shortcutId); } @@ -77,12 +69,8 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer /// public async Task GetMailOperationForKeyAsync(string key, ModifierKeys modifierKeys) { - var query = new Query(nameof(KeyboardShortcut)) - .Where(nameof(KeyboardShortcut.Key), key) - .Where(nameof(KeyboardShortcut.ModifierKeys), (int)modifierKeys) - .Where(nameof(KeyboardShortcut.IsEnabled), true); - - var shortcut = await Connection.FindWithQueryAsync(query.GetRawQuery()); + const string query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? AND IsEnabled = ? LIMIT 1"; + var shortcut = await Connection.FindWithQueryAsync(query, key, (int)modifierKeys, 1); return shortcut?.MailOperation; } @@ -91,16 +79,20 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer /// public async Task IsKeyCombinationInUseAsync(string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null) { - var query = new Query(nameof(KeyboardShortcut)) - .Where(nameof(KeyboardShortcut.Key), key) - .Where(nameof(KeyboardShortcut.ModifierKeys), (int)modifierKeys); - + string query; + KeyboardShortcut shortcut; + if (excludeShortcutId.HasValue) { - query = query.WhereNot(nameof(KeyboardShortcut.Id), excludeShortcutId.Value); + query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? AND Id != ? LIMIT 1"; + shortcut = await Connection.FindWithQueryAsync(query, key, (int)modifierKeys, excludeShortcutId.Value); } - - var shortcut = await Connection.FindWithQueryAsync(query.GetRawQuery()); + else + { + query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? LIMIT 1"; + shortcut = await Connection.FindWithQueryAsync(query, key, (int)modifierKeys); + } + return shortcut != null; } diff --git a/Wino.Services/MailService.cs b/Wino.Services/MailService.cs index 67403037..76f28562 100644 --- a/Wino.Services/MailService.cs +++ b/Wino.Services/MailService.cs @@ -2,12 +2,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using MimeKit; using Serilog; -using SqlKata; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Entities.Shared; @@ -144,74 +144,80 @@ public class MailService : BaseDatabaseService, IMailService return unreadMails; } - private static string BuildMailFetchQuery(MailListInitializationOptions options) + private static (string Query, object[] Parameters) BuildMailFetchQuery(MailListInitializationOptions options) { - // If the search query is there, we should ignore some properties and trim it. - //if (!string.IsNullOrEmpty(options.SearchQuery)) - //{ - // options.IsFocusedOnly = null; - // filterType = FilterOptionType.All; - - // searchQuery = searchQuery.Trim(); - //} - - // SQLite PCL doesn't support joins. - // We make the query using SqlKata and execute it directly on SQLite-PCL. - - var query = new Query("MailCopy") - .Join("MailItemFolder", "MailCopy.FolderId", "MailItemFolder.Id") - .WhereIn("MailCopy.FolderId", options.Folders.Select(a => a.Id)) - .Take(ItemLoadCount) - .SelectRaw("MailCopy.*"); - - if (options.SortingOptionType == SortingOptionType.ReceiveDate) - query.OrderByDesc("CreationDate"); - else if (options.SortingOptionType == SortingOptionType.Sender) - query.OrderBy("FromName"); - - // Conditional where. + var sql = new StringBuilder(); + sql.Append("SELECT MailCopy.* FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id"); + + var whereClauses = new List(); + var parameters = new List(); + + // Folder filter + var folderPlaceholders = string.Join(",", options.Folders.Select(_ => "?")); + whereClauses.Add($"MailCopy.FolderId IN ({folderPlaceholders})"); + parameters.AddRange(options.Folders.Select(f => (object)f.Id)); + + // Filter type switch (options.FilterType) { case FilterOptionType.Unread: - query.Where("MailCopy.IsRead", false); + whereClauses.Add("MailCopy.IsRead = 0"); break; case FilterOptionType.Flagged: - query.Where("MailCopy.IsFlagged", true); + whereClauses.Add("MailCopy.IsFlagged = 1"); break; case FilterOptionType.Files: - query.Where("MailCopy.HasAttachments", true); + whereClauses.Add("MailCopy.HasAttachments = 1"); break; } - + + // Focused filter if (options.IsFocusedOnly != null) - query.Where("MailCopy.IsFocused", options.IsFocusedOnly.Value); - + { + whereClauses.Add($"MailCopy.IsFocused = {(options.IsFocusedOnly.Value ? "1" : "0")}"); + } + + // Search query if (!string.IsNullOrEmpty(options.SearchQuery)) - query.Where(a => - a.OrWhereContains("MailCopy.PreviewText", options.SearchQuery) - .OrWhereContains("MailCopy.Subject", options.SearchQuery) - .OrWhereContains("MailCopy.FromName", options.SearchQuery) - .OrWhereContains("MailCopy.FromAddress", options.SearchQuery)); - - // Support pagination by excluding already fetched items + { + whereClauses.Add("(MailCopy.PreviewText LIKE ? OR MailCopy.Subject LIKE ? OR MailCopy.FromName LIKE ? OR MailCopy.FromAddress LIKE ?)"); + var searchPattern = $"%{options.SearchQuery}%"; + parameters.Add(searchPattern); + parameters.Add(searchPattern); + parameters.Add(searchPattern); + parameters.Add(searchPattern); + } + + // Exclude existing items if (options.ExistingUniqueIds?.Any() ?? false) { - query.WhereNotIn("MailCopy.UniqueId", options.ExistingUniqueIds); + var excludePlaceholders = string.Join(",", options.ExistingUniqueIds.Select(_ => "?")); + whereClauses.Add($"MailCopy.UniqueId NOT IN ({excludePlaceholders})"); + parameters.AddRange(options.ExistingUniqueIds.Select(id => (object)id)); } - - // Support skip for pagination + + if (whereClauses.Any()) + { + sql.Append(" WHERE "); + sql.Append(string.Join(" AND ", whereClauses)); + } + + // Sorting + if (options.SortingOptionType == SortingOptionType.ReceiveDate) + sql.Append(" ORDER BY CreationDate DESC"); + else if (options.SortingOptionType == SortingOptionType.Sender) + sql.Append(" ORDER BY FromName ASC"); + + // Pagination + var limit = options.Take > 0 ? options.Take : ItemLoadCount; + sql.Append($" LIMIT {limit}"); + if (options.Skip > 0) { - query.Skip(options.Skip); + sql.Append($" OFFSET {options.Skip}"); } - - // Support custom take count for pagination - if (options.Take > 0) - { - query.Take(options.Take); - } - - return query.GetRawQuery(); + + return (sql.ToString(), parameters.ToArray()); } public async Task> FetchMailsAsync(MailListInitializationOptions options, CancellationToken cancellationToken = default) @@ -226,8 +232,8 @@ public class MailService : BaseDatabaseService, IMailService else { // If not just do the query. - var query = BuildMailFetchQuery(options); - mails = await Connection.QueryAsync(query); + var (query, parameters) = BuildMailFetchQuery(options); + mails = await Connection.QueryAsync(query, parameters); } ConcurrentDictionary folderCache = new(); @@ -295,13 +301,12 @@ public class MailService : BaseDatabaseService, IMailService if (string.IsNullOrEmpty(threadId)) return []; - var query = new Query("MailCopy") - .Where("ThreadId", threadId) - .WhereNotIn("Id", excludeMailIds) - .SelectRaw("MailCopy.*") - .GetRawQuery(); + var placeholders = string.Join(",", excludeMailIds.Select(_ => "?")); + var sql = $"SELECT MailCopy.* FROM MailCopy WHERE ThreadId = ? AND Id NOT IN ({placeholders})"; + var parameters = new List { threadId }; + parameters.AddRange(excludeMailIds.Cast()); - return await Connection.QueryAsync(query); + return await Connection.QueryAsync(sql, parameters.ToArray()); } private async Task> GetMailsByThreadIdsAsync(List threadIds, HashSet excludeMailIds) @@ -309,13 +314,14 @@ public class MailService : BaseDatabaseService, IMailService if (threadIds?.Count == 0) return []; - var query = new Query("MailCopy") - .WhereIn("ThreadId", threadIds) - .WhereNotIn("Id", excludeMailIds) - .SelectRaw("MailCopy.*") - .GetRawQuery(); + var threadPlaceholders = string.Join(",", threadIds.Select(_ => "?")); + var excludePlaceholders = string.Join(",", excludeMailIds.Select(_ => "?")); + var sql = $"SELECT MailCopy.* FROM MailCopy WHERE ThreadId IN ({threadPlaceholders}) AND Id NOT IN ({excludePlaceholders})"; + var parameters = new List(); + parameters.AddRange(threadIds.Cast()); + parameters.AddRange(excludeMailIds.Cast()); - return await Connection.QueryAsync(query).ConfigureAwait(false); + return await Connection.QueryAsync(sql, parameters.ToArray()).ConfigureAwait(false); } /// @@ -451,12 +457,9 @@ public class MailService : BaseDatabaseService, IMailService /// Mail copy id. public async Task GetSingleMailItemAsync(string mailCopyId) { - var query = new Query("MailCopy") - .Where("MailCopy.Id", mailCopyId) - .SelectRaw("MailCopy.*") - .GetRawQuery(); - - var mailCopy = await Connection.FindWithQueryAsync(query); + var mailCopy = await Connection.FindWithQueryAsync( + "SELECT MailCopy.* FROM MailCopy WHERE MailCopy.Id = ?", + mailCopyId); if (mailCopy == null) return null; await LoadAssignedPropertiesAsync(mailCopy).ConfigureAwait(false); @@ -466,14 +469,9 @@ public class MailService : BaseDatabaseService, IMailService public async Task GetSingleMailItemAsync(string mailCopyId, string remoteFolderId) { - var query = new Query("MailCopy") - .Join("MailItemFolder", "MailCopy.FolderId", "MailItemFolder.Id") - .Where("MailCopy.Id", mailCopyId) - .Where("MailItemFolder.RemoteFolderId", remoteFolderId) - .SelectRaw("MailCopy.*") - .GetRawQuery(); - - var mailItem = await Connection.FindWithQueryAsync(query); + var mailItem = await Connection.FindWithQueryAsync( + "SELECT MailCopy.* FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id WHERE MailCopy.Id = ? AND MailItemFolder.RemoteFolderId = ?", + mailCopyId, remoteFolderId); if (mailItem == null) return null; @@ -1030,14 +1028,9 @@ public class MailService : BaseDatabaseService, IMailService public async Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId) { - var query = new Query("MailCopy") - .Join("MailItemFolder", "MailCopy.FolderId", "MailItemFolder.Id") - .Where("MailCopy.UniqueId", localDraftCopyUniqueId) - .Where("MailItemFolder.MailAccountId", accountId) - .SelectRaw("MailCopy.*") - .GetRawQuery(); - - var localDraftCopy = await Connection.FindWithQueryAsync(query); + var localDraftCopy = await Connection.FindWithQueryAsync( + "SELECT MailCopy.* FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id WHERE MailCopy.UniqueId = ? AND MailItemFolder.MailAccountId = ?", + localDraftCopyUniqueId, accountId); if (localDraftCopy == null) { @@ -1085,28 +1078,22 @@ public class MailService : BaseDatabaseService, IMailService public Task> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable downloadedMailCopyIds) { - var rawQuery = new Query("MailCopy") - .Join("MailItemFolder", "MailCopy.FolderId", "MailItemFolder.Id") - .WhereIn("MailCopy.Id", downloadedMailCopyIds) - .Where("MailCopy.IsRead", false) - .Where("MailItemFolder.MailAccountId", accountId) - .Where("MailItemFolder.SpecialFolderType", SpecialFolderType.Inbox) - .SelectRaw("MailCopy.*") - .GetRawQuery(); + var placeholders = string.Join(",", downloadedMailCopyIds.Select(_ => "?")); + var sql = $"SELECT MailCopy.* FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id WHERE MailCopy.Id IN ({placeholders}) AND MailCopy.IsRead = ? AND MailItemFolder.MailAccountId = ? AND MailItemFolder.SpecialFolderType = ?"; + var parameters = new List(); + parameters.AddRange(downloadedMailCopyIds.Cast()); + parameters.Add(false); + parameters.Add(accountId); + parameters.Add((int)SpecialFolderType.Inbox); - return Connection.QueryAsync(rawQuery); + return Connection.QueryAsync(sql, parameters.ToArray()); } public Task GetMailAccountByUniqueIdAsync(Guid uniqueMailId) { - var query = new Query("MailCopy") - .Join("MailItemFolder", "MailCopy.FolderId", "MailItemFolder.Id") - .Join("MailAccount", "MailItemFolder.MailAccountId", "MailAccount.Id") - .Where("MailCopy.UniqueId", uniqueMailId) - .SelectRaw("MailAccount.*") - .GetRawQuery(); - - return Connection.FindWithQueryAsync(query); + return Connection.FindWithQueryAsync( + "SELECT MailAccount.* FROM MailCopy INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id INNER JOIN MailAccount ON MailItemFolder.MailAccountId = MailAccount.Id WHERE MailCopy.UniqueId = ?", + uniqueMailId); } public Task IsMailExistsAsync(string mailCopyId) @@ -1116,11 +1103,10 @@ public class MailService : BaseDatabaseService, IMailService { var localMailIds = uniqueIds.Select(a => MailkitClientExtensions.CreateUid(folderId, a.Id)).ToArray(); - var query = new Query(nameof(MailCopy)) - .WhereIn("Id", localMailIds) - .GetRawQuery(); + var placeholders = string.Join(",", localMailIds.Select(_ => "?")); + var sql = $"SELECT * FROM MailCopy WHERE Id IN ({placeholders})"; - return await Connection.QueryAsync(query); + return await Connection.QueryAsync(sql, localMailIds.Cast().ToArray()); } public Task IsMailExistsAsync(string mailCopyId, Guid folderId) @@ -1142,12 +1128,10 @@ public class MailService : BaseDatabaseService, IMailService { if (!mailCopyIds.Any()) return []; - var query = new Query("MailCopy") - .WhereIn("MailCopy.Id", mailCopyIds) - .SelectRaw("MailCopy.*") - .GetRawQuery(); + var placeholders = string.Join(",", mailCopyIds.Select(_ => "?")); + var sql = $"SELECT MailCopy.* FROM MailCopy WHERE MailCopy.Id IN ({placeholders})"; - var mailCopies = await Connection.QueryAsync(query); + var mailCopies = await Connection.QueryAsync(sql, mailCopyIds.Cast().ToArray()); if (mailCopies?.Count == 0) return []; ConcurrentDictionary folderCache = new(); @@ -1164,11 +1148,9 @@ public class MailService : BaseDatabaseService, IMailService public async Task> AreMailsExistsAsync(IEnumerable mailCopyIds) { - var query = new Query(nameof(MailCopy)) - .WhereIn("Id", mailCopyIds) - .Select("Id") - .GetRawQuery(); + var placeholders = string.Join(",", mailCopyIds.Select(_ => "?")); + var sql = $"SELECT Id FROM MailCopy WHERE Id IN ({placeholders})"; - return await Connection.QueryScalarsAsync(query); + return await Connection.QueryScalarsAsync(sql, mailCopyIds.Cast().ToArray()); } } diff --git a/Wino.Services/Wino.Services.csproj b/Wino.Services/Wino.Services.csproj index 81b677a8..bf159dcc 100644 --- a/Wino.Services/Wino.Services.csproj +++ b/Wino.Services/Wino.Services.csproj @@ -22,7 +22,6 @@ -