From 09f1cee3a5d40d2ec9dd5cc82e458a7617dbf38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sat, 7 Mar 2026 11:43:56 +0100 Subject: [PATCH] Remove sqlite base64 contact store from AccountContact. --- .../Entities/Shared/AccountContact.cs | 8 ---- .../Interfaces/IMailItemDisplayInformation.cs | 2 +- .../PersonalizationPageViewModel.cs | 2 +- Wino.Mail.ViewModels/ContactsPageViewModel.cs | 21 ++++++--- .../Data/AccountContactViewModel.cs | 3 -- .../Data/MailItemViewModel.cs | 16 ++++--- .../Data/ThreadMailItemViewModel.cs | 8 ++-- .../MailRenderingPageViewModel.cs | 5 --- .../Controls/ImagePreviewControl.cs | 42 ++++-------------- .../Dialogs/ContactEditDialog.xaml | 3 +- .../Dialogs/ContactEditDialog.xaml.cs | 13 +----- .../Views/Calendar/CalendarPage.xaml | 5 ++- .../Views/Calendar/EventDetailsPage.xaml | 6 ++- Wino.Mail.WinUI/Views/Mail/ComposePage.xaml | 4 +- .../Views/Mail/MailRenderingPage.xaml | 5 ++- Wino.Services/AccountService.cs | 44 ++++++++++++++++++- Wino.Services/ContactPictureFileService.cs | 18 +++++--- Wino.Services/MailService.cs | 34 ++++++++++---- 18 files changed, 136 insertions(+), 103 deletions(-) diff --git a/Wino.Core.Domain/Entities/Shared/AccountContact.cs b/Wino.Core.Domain/Entities/Shared/AccountContact.cs index 103b76e7..d6ecb591 100644 --- a/Wino.Core.Domain/Entities/Shared/AccountContact.cs +++ b/Wino.Core.Domain/Entities/Shared/AccountContact.cs @@ -27,17 +27,9 @@ public class AccountContact : IEquatable /// /// File ID for the contact picture stored on disk. /// The actual file lives at {ApplicationDataFolderPath}/contacts/{ContactPictureFileId}.jpg. - /// Preferred over Base64ContactPicture — allows native BitmapImage file loading and avoids SQLite bloat. /// public Guid? ContactPictureFileId { get; set; } - /// - /// Legacy base64 encoded profile image of the contact. - /// For user-set contact pictures: migrate to file storage via ContactPictureFileId instead. - /// Still used for OAuth account profile pictures (MailAccount.Base64ProfilePictureData). - /// - public string Base64ContactPicture { get; set; } - /// /// All registered accounts have their contacts registered as root. /// Root contacts must not be overridden by any configuration. diff --git a/Wino.Core.Domain/Interfaces/IMailItemDisplayInformation.cs b/Wino.Core.Domain/Interfaces/IMailItemDisplayInformation.cs index 0c2184cc..613f2be2 100644 --- a/Wino.Core.Domain/Interfaces/IMailItemDisplayInformation.cs +++ b/Wino.Core.Domain/Interfaces/IMailItemDisplayInformation.cs @@ -20,7 +20,7 @@ public interface IMailItemDisplayInformation : INotifyPropertyChanged bool IsCalendarEvent { get; } bool IsFlagged { get; } DateTime CreationDate { get; } - string Base64ContactPicture { get; } + Guid? ContactPictureFileId { get; } bool ThumbnailUpdatedEvent { get; } bool IsThreadExpanded { get; } AccountContact SenderContact { get; } diff --git a/Wino.Core.ViewModels/PersonalizationPageViewModel.cs b/Wino.Core.ViewModels/PersonalizationPageViewModel.cs index c9d90391..c247a9b8 100644 --- a/Wino.Core.ViewModels/PersonalizationPageViewModel.cs +++ b/Wino.Core.ViewModels/PersonalizationPageViewModel.cs @@ -341,7 +341,7 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel public bool IsCalendarEvent { get; } = false; public bool IsFlagged { get; } = false; public DateTime CreationDate { get; } = DateTime.Now; - public string Base64ContactPicture { get; } = string.Empty; + public Guid? ContactPictureFileId { get; } = null; public bool ThumbnailUpdatedEvent { get; } = false; public bool IsBusy { get; } = false; public bool IsThreadExpanded { get; } = false; diff --git a/Wino.Mail.ViewModels/ContactsPageViewModel.cs b/Wino.Mail.ViewModels/ContactsPageViewModel.cs index bc8e6170..4e883ef8 100644 --- a/Wino.Mail.ViewModels/ContactsPageViewModel.cs +++ b/Wino.Mail.ViewModels/ContactsPageViewModel.cs @@ -22,6 +22,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel private readonly IContactService _contactService; private readonly IMailDialogService _dialogService; + private readonly IContactPictureFileService _contactPictureFileService; private CancellationTokenSource _searchDebounceCancellationTokenSource; private int _currentOffset = 0; @@ -60,10 +61,11 @@ public partial class ContactsPageViewModel : MailBaseViewModel public ObservableCollection Contacts { get; } = new(); public ObservableCollection SelectedContacts { get; } = new(); - public ContactsPageViewModel(IContactService contactService, IMailDialogService dialogService) + public ContactsPageViewModel(IContactService contactService, IMailDialogService dialogService, IContactPictureFileService contactPictureFileService) { _contactService = contactService; _dialogService = dialogService; + _contactPictureFileService = contactPictureFileService; Contacts.CollectionChanged += ContactsCollectionChanged; } @@ -195,9 +197,9 @@ public partial class ContactsPageViewModel : MailBaseViewModel { var newContact = await _contactService.CreateNewContactAsync(result.Address, result.Name); - if (!string.IsNullOrEmpty(result.Base64ContactPicture)) + if (result.ContactPictureFileId.HasValue) { - newContact.Base64ContactPicture = result.Base64ContactPicture; + newContact.ContactPictureFileId = result.ContactPictureFileId; await _contactService.UpdateContactAsync(newContact); } @@ -230,7 +232,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel try { contact.Name = result.Name; - contact.Base64ContactPicture = result.Base64ContactPicture; + contact.ContactPictureFileId = result.ContactPictureFileId; contact.IsOverridden = result.IsOverridden; await _contactService.UpdateContactAsync(contact); @@ -382,9 +384,14 @@ public partial class ContactsPageViewModel : MailBaseViewModel if (files?.Any() == true) { var file = files.First(); - var base64Image = Convert.ToBase64String(file.Data); - contact.Base64ContactPicture = base64Image; + if (contact.ContactPictureFileId.HasValue) + await _contactPictureFileService.DeleteContactPictureAsync(contact.ContactPictureFileId.Value); + + contact.ContactPictureFileId = await _contactPictureFileService + .SaveContactPictureAsync(file.Data) + .ConfigureAwait(false); + await _contactService.UpdateContactAsync(contact); await RefreshContactInUiAsync(contact); @@ -432,7 +439,7 @@ public partial class ContactsPageViewModel : MailBaseViewModel { Address = contact.Address, Name = contact.Name, - Base64ContactPicture = contact.Base64ContactPicture, + ContactPictureFileId = contact.ContactPictureFileId, IsRootContact = contact.IsRootContact, IsOverridden = contact.IsOverridden }; diff --git a/Wino.Mail.ViewModels/Data/AccountContactViewModel.cs b/Wino.Mail.ViewModels/Data/AccountContactViewModel.cs index 30c1e302..7a9ae7f2 100644 --- a/Wino.Mail.ViewModels/Data/AccountContactViewModel.cs +++ b/Wino.Mail.ViewModels/Data/AccountContactViewModel.cs @@ -12,7 +12,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla public string Address { get; set; } public string Name { get; set; } public Guid? ContactPictureFileId { get; set; } - public string Base64ContactPicture { get; set; } public bool IsRootContact { get; set; } public bool IsOverridden { get; set; } @@ -22,7 +21,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla Address = contact.Address; Name = contact.Name; ContactPictureFileId = contact.ContactPictureFileId; - Base64ContactPicture = contact.Base64ContactPicture; IsRootContact = contact.IsRootContact; IsOverridden = contact.IsOverridden; } @@ -75,7 +73,6 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla Address = Address, Name = Name, ContactPictureFileId = ContactPictureFileId, - Base64ContactPicture = Base64ContactPicture, IsRootContact = IsRootContact, IsOverridden = IsOverridden }; diff --git a/Wino.Mail.ViewModels/Data/MailItemViewModel.cs b/Wino.Mail.ViewModels/Data/MailItemViewModel.cs index 75af1f53..7eebd613 100644 --- a/Wino.Mail.ViewModels/Data/MailItemViewModel.cs +++ b/Wino.Mail.ViewModels/Data/MailItemViewModel.cs @@ -35,7 +35,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient, [NotifyPropertyChangedFor(nameof(FileId))] [NotifyPropertyChangedFor(nameof(FolderId))] [NotifyPropertyChangedFor(nameof(UniqueId))] - [NotifyPropertyChangedFor(nameof(Base64ContactPicture))] + [NotifyPropertyChangedFor(nameof(ContactPictureFileId))] [NotifyPropertyChangedFor(nameof(SenderContact))] public partial MailCopy MailCopy { get; set; } = mailCopy; @@ -191,10 +191,14 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient, set => SetProperty(MailCopy.UniqueId, value, MailCopy, (u, n) => u.UniqueId = n); } - public string Base64ContactPicture + public Guid? ContactPictureFileId { - get => MailCopy.SenderContact?.Base64ContactPicture ?? string.Empty; - set => SetProperty(MailCopy.SenderContact.Base64ContactPicture, value, MailCopy, (u, n) => u.SenderContact.Base64ContactPicture = n); + get => MailCopy.SenderContact?.ContactPictureFileId; + set => SetProperty(MailCopy.SenderContact?.ContactPictureFileId, value, MailCopy, (u, n) => + { + if (u.SenderContact != null) + u.SenderContact.ContactPictureFileId = n; + }); } public DateTime SortingDate => CreationDate; @@ -236,7 +240,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient, nameof(FileId) => MailCopyChangeFlags.FileId, nameof(FolderId) => MailCopyChangeFlags.FolderId, nameof(UniqueId) => MailCopyChangeFlags.UniqueId, - nameof(Base64ContactPicture) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact, + nameof(ContactPictureFileId) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact, _ => MailCopyChangeFlags.None }; } @@ -398,7 +402,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient, if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0) { - Queue(nameof(Base64ContactPicture)); + Queue(nameof(ContactPictureFileId)); Queue(nameof(SenderContact)); } diff --git a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs index c5d40d42..bc82acf5 100644 --- a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs +++ b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs @@ -155,7 +155,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte /// public Guid UniqueId => latestMailViewModel?.UniqueId ?? Guid.Empty; - public string Base64ContactPicture => latestMailViewModel?.MailCopy?.SenderContact?.Base64ContactPicture ?? string.Empty; + public Guid? ContactPictureFileId => latestMailViewModel?.MailCopy?.SenderContact?.ContactPictureFileId; public bool ThumbnailUpdatedEvent => latestMailViewModel?.ThumbnailUpdatedEvent ?? false; @@ -188,7 +188,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte [NotifyPropertyChangedFor(nameof(FileId))] [NotifyPropertyChangedFor(nameof(FolderId))] [NotifyPropertyChangedFor(nameof(UniqueId))] - [NotifyPropertyChangedFor(nameof(Base64ContactPicture))] + [NotifyPropertyChangedFor(nameof(ContactPictureFileId))] [NotifyPropertyChangedFor(nameof(SenderContact))] public partial ObservableCollection ThreadEmails { get; set; } = []; @@ -369,7 +369,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte Queue(nameof(FileId)); Queue(nameof(FolderId)); Queue(nameof(UniqueId)); - Queue(nameof(Base64ContactPicture)); + Queue(nameof(ContactPictureFileId)); Queue(nameof(SenderContact)); Queue(nameof(ThumbnailUpdatedEvent)); Queue(nameof(SortingDate)); @@ -433,7 +433,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte if ((changedFlags & MailCopyChangeFlags.SenderContact) != 0) { - Queue(nameof(Base64ContactPicture)); + Queue(nameof(ContactPictureFileId)); Queue(nameof(SenderContact)); } } diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 3115a52e..11140273 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -124,9 +124,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, [ObservableProperty] public partial string FromName { get; set; } - [ObservableProperty] - public partial string ContactPicture { get; set; } - [ObservableProperty] public partial IMailItemDisplayInformation CurrentMailItemDisplayInformation { get; set; } @@ -497,8 +494,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, // Use the received date from MailCopy if available, otherwise fall back to the sent date from MIME message CreationDate = initializedMailItemViewModel?.MailCopy.CreationDate ?? message.Date.DateTime; - ContactPicture = initializedMailItemViewModel?.MailCopy.SenderContact?.Base64ContactPicture; - // Automatically disable images for Junk folder to prevent pixel tracking. // This can only work for selected mail item rendering, not for EML file rendering. if (initializedMailItemViewModel != null && diff --git a/Wino.Mail.WinUI/Controls/ImagePreviewControl.cs b/Wino.Mail.WinUI/Controls/ImagePreviewControl.cs index 06cc8a00..badbc77f 100644 --- a/Wino.Mail.WinUI/Controls/ImagePreviewControl.cs +++ b/Wino.Mail.WinUI/Controls/ImagePreviewControl.cs @@ -18,13 +18,13 @@ namespace Wino.Controls; /// /// Contact avatar control built on top of PersonPicture. /// Priority: -/// 1) AccountContact/Base64 picture +/// 1) AccountContact file-based picture /// 2) Gravatar thumbnail (if enabled) /// 3) Initials from display name fallback /// public sealed partial class ImagePreviewControl : PersonPicture { - private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId, string Base64Picture); + private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId); private static readonly TimeSpan RefreshDebounceDuration = TimeSpan.FromMilliseconds(40); @@ -109,7 +109,7 @@ public sealed partial class ImagePreviewControl : PersonPicture { // Refresh only for fields that affect avatar image or initials. if (string.IsNullOrEmpty(e.PropertyName) - || e.PropertyName == nameof(IMailItemDisplayInformation.Base64ContactPicture) + || e.PropertyName == nameof(IMailItemDisplayInformation.ContactPictureFileId) || e.PropertyName == nameof(IMailItemDisplayInformation.SenderContact) || e.PropertyName == nameof(IMailItemDisplayInformation.FromName) || e.PropertyName == nameof(IMailItemDisplayInformation.FromAddress) @@ -222,18 +222,7 @@ public sealed partial class ImagePreviewControl : PersonPicture } } - // 2) Legacy base64 contact picture (used until migration completes or for fallback). - if (!string.IsNullOrWhiteSpace(snapshot.Base64Picture)) - { - var localBitmap = await CreateBitmapFromBase64Async(snapshot.Base64Picture, cancellationToken).ConfigureAwait(false); - if (localBitmap != null) - { - await ApplyProfilePictureAsync(localBitmap, refreshVersion, cancellationToken).ConfigureAwait(false); - return; - } - } - - // 3) Gravatar lookup through thumbnail service (if enabled). + // 2) Gravatar lookup through thumbnail service (if enabled). if (_preferencesService?.IsGravatarEnabled == true && _thumbnailService != null && !string.IsNullOrWhiteSpace(snapshot.Address) && @@ -254,7 +243,7 @@ public sealed partial class ImagePreviewControl : PersonPicture } } - // 4) Initials fallback is already in place via DisplayName + ProfilePicture = null. + // 3) Initials fallback is already in place via DisplayName + ProfilePicture = null. } catch (OperationCanceledException) { @@ -276,10 +265,11 @@ public sealed partial class ImagePreviewControl : PersonPicture var address = ResolveAddress(); var displayName = ResolveDisplayName(address); - var base64Picture = ResolveBase64Picture(); - var contactPictureFileId = PreviewContact?.ContactPictureFileId ?? MailItemInformation?.SenderContact?.ContactPictureFileId; + var contactPictureFileId = PreviewContact?.ContactPictureFileId + ?? MailItemInformation?.SenderContact?.ContactPictureFileId + ?? MailItemInformation?.ContactPictureFileId; - return new RefreshSnapshot(displayName, address, contactPictureFileId, base64Picture); + return new RefreshSnapshot(displayName, address, contactPictureFileId); }).ConfigureAwait(false); } @@ -321,20 +311,6 @@ public sealed partial class ImagePreviewControl : PersonPicture return resolvedAddress.Trim(); } - - private string ResolveBase64Picture() - { - if (!string.IsNullOrWhiteSpace(PreviewContact?.Base64ContactPicture)) - return PreviewContact.Base64ContactPicture; - - if (!string.IsNullOrWhiteSpace(MailItemInformation?.SenderContact?.Base64ContactPicture)) - return MailItemInformation.SenderContact.Base64ContactPicture; - - if (!string.IsNullOrWhiteSpace(MailItemInformation?.Base64ContactPicture)) - return MailItemInformation.Base64ContactPicture; - - return string.Empty; - } private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken) { await ExecuteOnUiThreadAsync(() => diff --git a/Wino.Mail.WinUI/Dialogs/ContactEditDialog.xaml b/Wino.Mail.WinUI/Dialogs/ContactEditDialog.xaml index 037aab21..b0a8bba5 100644 --- a/Wino.Mail.WinUI/Dialogs/ContactEditDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/ContactEditDialog.xaml @@ -2,6 +2,7 @@ x:Class="Wino.Dialogs.ContactEditDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="using:Wino.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -52,7 +53,7 @@ - @@ -338,10 +339,10 @@ - + DisplayNameOverride="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.CalendarItem.OrganizerDisplayName, Mode=OneWay}" /> - + Address="{x:Bind Email}" + DisplayNameOverride="{x:Bind Name}" /> diff --git a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml index 611c6794..92b6aa64 100644 --- a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml +++ b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml @@ -43,7 +43,7 @@ --> - + - + diff --git a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml index 4e4b521f..72254258 100644 --- a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml +++ b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml @@ -151,7 +151,10 @@ - + diff --git a/Wino.Services/AccountService.cs b/Wino.Services/AccountService.cs index bce8d32e..55d6f6d9 100644 --- a/Wino.Services/AccountService.cs +++ b/Wino.Services/AccountService.cs @@ -42,6 +42,7 @@ public class AccountService : BaseDatabaseService, IAccountService private readonly IAuthenticationProvider _authenticationProvider; private readonly IMimeFileService _mimeFileService; private readonly IPreferencesService _preferencesService; + private readonly IContactPictureFileService _contactPictureFileService; private readonly ILogger _logger = Log.ForContext(); @@ -49,12 +50,14 @@ public class AccountService : BaseDatabaseService, IAccountService ISignatureService signatureService, IAuthenticationProvider authenticationProvider, IMimeFileService mimeFileService, - IPreferencesService preferencesService) : base(databaseService) + IPreferencesService preferencesService, + IContactPictureFileService contactPictureFileService) : base(databaseService) { _signatureService = signatureService; _authenticationProvider = authenticationProvider; _mimeFileService = mimeFileService; _preferencesService = preferencesService; + _contactPictureFileService = contactPictureFileService; } @@ -399,11 +402,19 @@ public class AccountService : BaseDatabaseService, IAccountService } // Forcefully add or update a contact data with the provided information. + var existingContact = await Connection.Table() + .FirstOrDefaultAsync(a => a.Address == account.Address) + .ConfigureAwait(false); + + var contactPictureFileId = await SaveProfilePictureAsync( + account.Base64ProfilePictureData, + existingContact?.ContactPictureFileId).ConfigureAwait(false); + var accountContact = new AccountContact() { Address = account.Address, Name = account.SenderName, - Base64ContactPicture = account.Base64ProfilePictureData, + ContactPictureFileId = contactPictureFileId, IsRootContact = true }; @@ -413,6 +424,35 @@ public class AccountService : BaseDatabaseService, IAccountService } } + private async Task SaveProfilePictureAsync(string base64ProfilePictureData, Guid? existingFileId) + { + if (string.IsNullOrWhiteSpace(base64ProfilePictureData)) + { + if (existingFileId.HasValue) + await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false); + + return null; + } + + byte[] bytes; + try + { + bytes = Convert.FromBase64String(base64ProfilePictureData); + } + catch (FormatException ex) + { + _logger.Warning(ex, "Failed to decode account profile picture for contact migration."); + return existingFileId; + } + + var newFileId = await _contactPictureFileService.SaveContactPictureAsync(bytes).ConfigureAwait(false); + + if (existingFileId.HasValue) + await _contactPictureFileService.DeleteContactPictureAsync(existingFileId.Value).ConfigureAwait(false); + + return newFileId; + } + public async Task GetAccountAsync(Guid accountId) { var account = await Connection.Table().FirstOrDefaultAsync(a => a.Id == accountId); diff --git a/Wino.Services/ContactPictureFileService.cs b/Wino.Services/ContactPictureFileService.cs index 99e1eb9f..2e6e5703 100644 --- a/Wino.Services/ContactPictureFileService.cs +++ b/Wino.Services/ContactPictureFileService.cs @@ -13,6 +13,12 @@ namespace Wino.Services; /// public class ContactPictureFileService : BaseDatabaseService, IContactPictureFileService { + private sealed class LegacyAccountContactPictureRow + { + public string Address { get; set; } + public string Base64ContactPicture { get; set; } + } + private const string ContactsSubFolder = "contacts"; private readonly string _contactPicturesFolder; @@ -52,8 +58,8 @@ public class ContactPictureFileService : BaseDatabaseService, IContactPictureFil try { var contacts = await Connection - .QueryAsync( - "SELECT * FROM AccountContact WHERE Base64ContactPicture IS NOT NULL AND ContactPictureFileId IS NULL") + .QueryAsync( + "SELECT Address, Base64ContactPicture FROM AccountContact WHERE Base64ContactPicture IS NOT NULL AND ContactPictureFileId IS NULL") .ConfigureAwait(false); foreach (var contact in contacts) @@ -67,10 +73,10 @@ public class ContactPictureFileService : BaseDatabaseService, IContactPictureFil var bytes = Convert.FromBase64String(base64); var fileId = await SaveContactPictureAsync(bytes).ConfigureAwait(false); - contact.ContactPictureFileId = fileId; - contact.Base64ContactPicture = null; - - await Connection.UpdateAsync(contact, typeof(AccountContact)).ConfigureAwait(false); + await Connection.ExecuteAsync( + "UPDATE AccountContact SET ContactPictureFileId = ?, Base64ContactPicture = NULL WHERE Address = ?", + fileId, + contact.Address).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Wino.Services/MailService.cs b/Wino.Services/MailService.cs index 6dd16d77..d35d8fe1 100644 --- a/Wino.Services/MailService.cs +++ b/Wino.Services/MailService.cs @@ -465,14 +465,21 @@ public class MailService : BaseDatabaseService, IMailService // Self-sent mails (e.g. Sent folder): construct contact from account meta // to get the up-to-date profile picture without a DB roundtrip. - if (!string.IsNullOrEmpty(mail.FromAddress) && mail.FromAddress == account.Address) + if (!string.IsNullOrEmpty(mail.FromAddress) && + string.Equals(mail.FromAddress, account.Address, StringComparison.OrdinalIgnoreCase)) { - mail.SenderContact = new AccountContact + if (contactCache.TryGetValue(mail.FromAddress, out var ownContact)) { - Address = account.Address, - Name = account.SenderName, - Base64ContactPicture = account.Base64ProfilePictureData - }; + mail.SenderContact = ownContact; + } + else + { + mail.SenderContact = new AccountContact + { + Address = account.Address, + Name = account.SenderName + }; + } } else { @@ -543,9 +550,9 @@ public class MailService : BaseDatabaseService, IMailService private Task GetSenderContactForAccountAsync(MailAccount account, string fromAddress) { // Make sure to return the latest up to date contact information for the original account. - if (fromAddress == account.Address) + if (string.Equals(fromAddress, account.Address, StringComparison.OrdinalIgnoreCase)) { - return Task.FromResult(new AccountContact() { Address = account.Address, Name = account.SenderName, Base64ContactPicture = account.Base64ProfilePictureData }); + return GetOwnSenderContactAsync(account); } else { @@ -553,6 +560,17 @@ public class MailService : BaseDatabaseService, IMailService } } + private async Task GetOwnSenderContactAsync(MailAccount account) + { + var contact = await _contactService.GetAddressInformationByAddressAsync(account.Address).ConfigureAwait(false); + + return contact ?? new AccountContact + { + Address = account.Address, + Name = account.SenderName + }; + } + private async Task LoadAssignedPropertiesAsync(MailCopy mailCopy) { if (mailCopy == null) return;