From 1ec8d5bbf2b796e82fcfa519cc92a8f74521e28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Fri, 6 Feb 2026 21:46:30 +0100 Subject: [PATCH] Gmail drafting --- Wino.Core/Synchronizers/GmailSynchronizer.cs | 89 +++++++++++++++++--- Wino.Mail.ViewModels/ComposePageViewModel.cs | 4 +- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index 74023de5..43bd7938 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -243,6 +243,11 @@ public class GmailSynchronizer : WinoSynchronizer + /// Enriches a MailCopy with fields extracted from a parsed MimeMessage. + /// This is needed when messages are downloaded with Raw format (delta sync), + /// because the Gmail API does not populate Payload.Headers in Raw format. + /// Fields already populated (non-null/non-empty) are NOT overwritten. + /// + private static void EnrichMailCopyFromMime(MailCopy copy, MimeMessage mime) + { + if (copy == null || mime == null) return; + + if (string.IsNullOrEmpty(copy.Subject)) + copy.Subject = mime.Subject ?? string.Empty; + + if (string.IsNullOrEmpty(copy.FromName)) + { + var from = mime.From.Mailboxes.FirstOrDefault(); + if (from != null) + copy.FromName = from.Name ?? string.Empty; + } + + if (string.IsNullOrEmpty(copy.FromAddress)) + { + var from = mime.From.Mailboxes.FirstOrDefault(); + if (from != null) + copy.FromAddress = from.Address ?? string.Empty; + } + + if (string.IsNullOrEmpty(copy.MessageId)) + copy.MessageId = mime.MessageId; + + if (string.IsNullOrEmpty(copy.InReplyTo)) + copy.InReplyTo = mime.InReplyTo; + + if (string.IsNullOrEmpty(copy.References) && mime.References?.Count > 0) + copy.References = string.Join(";", mime.References); + + if (!copy.HasAttachments && mime.Attachments.Any()) + copy.HasAttachments = true; + + if (copy.Importance == MailImportance.Normal) + { + copy.Importance = mime.Importance switch + { + MessageImportance.High => MailImportance.High, + MessageImportance.Low => MailImportance.Low, + _ => MailImportance.Normal + }; + } + } + /// /// Determines MailItemType based on Gmail message headers. /// Gmail doesn't have EventMessage type like Outlook, but calendar invitations can be detected @@ -1788,21 +1850,20 @@ public class GmailSynchronizer : WinoSynchronizer h.Name.Equals(Domain.Constants.WinoLocalDraftHeader, StringComparison.OrdinalIgnoreCase))?.Value; - if (!string.IsNullOrEmpty(draftIdHeader) && Guid.TryParse(draftIdHeader, out Guid localDraftCopyUniqueId)) + if (!string.IsNullOrEmpty(draftIdHeader) && Guid.TryParse(draftIdHeader, out _)) { - // This message belongs to existing local draft copy. - // We don't need to create a new mail copy for this message, just update the existing one. - - bool isMappingSuccesfull = await _gmailChangeProcessor.MapLocalDraftAsync(Account.Id, localDraftCopyUniqueId, baseMailCopy.Id, baseMailCopy.DraftId, baseMailCopy.ThreadId); - - if (isMappingSuccesfull) return null; - - // Local copy doesn't exists. Continue execution to insert mail copy. + // This message belongs to an existing local draft copy. + // Skip creating a new mail copy - the local copy was already mapped by the response handler. + return null; } } @@ -1825,7 +1886,7 @@ public class GmailSynchronizer : WinoSynchronizer a.AliasAddress == CurrentMailDraftItem.FromAddress); } - primaryAlias ??= await _accountService.GetPrimaryAccountAliasAsync(ComposingAccount.Id).ConfigureAwait(false); + primaryAlias ??= await _accountService.GetPrimaryAccountAliasAsync(composingAccount.Id).ConfigureAwait(false); await ExecuteUIThread(() => { @@ -477,7 +477,7 @@ public partial class ComposePageViewModel : MailBaseViewModel if (!isComposerInitialized) return; - retry: + retry: // Replying existing message. MimeMessageInformation mimeMessageInformation = null;