From 394af3ba0a08866aa4051109154dc84474eb6135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Wed, 29 Oct 2025 18:44:15 +0100 Subject: [PATCH] Gmail synchronizer improvements. --- Wino.Core/Synchronizers/GmailSynchronizer.cs | 93 +++++++++++++------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index aea0dd91..63bb9a80 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -46,13 +46,15 @@ public class GmailSynchronizer : WinoSynchronizer 1000; - /// This now represents actual per-folder download count for initial sync + /// Maximum messages to fetch per folder during initial sync (1500). + /// For each folder: first 50 messages are downloaded with MIME, next 500 with metadata only. + /// Messages beyond 550 per folder are skipped during initial sync. public override uint InitialMessageDownloadCountPerFolder => 1500; // It's actually 100. But Gmail SDK has internal bug for Out of Memory exception. // https://github.com/googleapis/google-api-dotnet-client/issues/2603 private const uint MaximumAllowedBatchRequestSize = 10; - + private readonly ConfigurableHttpClient _googleHttpClient; private readonly GmailService _gmailService; private readonly CalendarService _calendarService; @@ -64,7 +66,7 @@ public class GmailSynchronizer : WinoSynchronizer _folderDownloadCounts = new(); @@ -204,6 +206,8 @@ public class GmailSynchronizer : WinoSynchronizer= InitialMessageDownloadCountPerFolder) + // Stop if we've downloaded enough messages for this folder or reached the metadata limit + if (downloadedCount >= InitialMessageDownloadCountPerFolder || + (folderMimeDownloadCount >= InitialSyncMimeDownloadCount && folderMetadataDownloadCount >= maxMetadataOnlyMessagesPerLabel)) { break; } } while (!string.IsNullOrEmpty(nextPageToken)); - _logger.Information("Downloaded {Count} messages for folder {Folder} (first {MimeCount} with MIME)", - downloadedCount, folder.FolderName, Math.Min(folderMimeDownloadCount, InitialSyncMimeDownloadCount)); - + _logger.Information("Downloaded {Count} messages for folder {Folder} (first {MimeCount} with MIME, {MetadataCount} metadata-only)", + downloadedCount, folder.FolderName, Math.Min(folderMimeDownloadCount, InitialSyncMimeDownloadCount), folderMetadataDownloadCount); + // Track how many messages we've downloaded with MIME for this folder _folderDownloadCounts[folder.RemoteFolderId] = folderMimeDownloadCount; } @@ -306,19 +319,10 @@ public class GmailSynchronizer : WinoSynchronizer a.Messages != null).SelectMany(a => a.Messages).Select(a => a.Id).ToList(); - var totalDownloadedCount = _folderDownloadCounts.Values.Sum(); - - // Skip the messages that were already downloaded with MIME and add the rest for metadata-only download - if (allInitialSyncMessages.Count > totalDownloadedCount) - { - var remainingMessages = allInitialSyncMessages.Skip(totalDownloadedCount).ToList(); - missingMessageIds.AddRange(remainingMessages); - - _logger.Information("Added {Count} remaining messages for metadata-only download", remainingMessages.Count); - } + // For initial sync, messages are now downloaded immediately during folder processing + // (first 50 with MIME, next 500 metadata-only per folder) + // No remaining messages to process here + _logger.Information("Initial sync completed: messages downloaded per folder (50 MIME + up to 500 metadata-only)"); } // Add missing message ids from delta changes. @@ -395,7 +399,7 @@ public class GmailSynchronizer : WinoSynchronizer - /// Returns a single get request to retrieve the raw message with the given id + /// Returns a single get request to retrieve the message with the given id /// /// Message to download. - /// Get request for raw mail. - private UsersResource.MessagesResource.GetRequest CreateSingleMessageGet(string messageId) + /// True to download raw MIME content, false to download metadata only (headers, labels, etc.) + /// Get request for message with appropriate format. + private UsersResource.MessagesResource.GetRequest CreateSingleMessageGet(string messageId, bool downloadMime = true) { var singleRequest = _gmailService.Users.Messages.Get("me", messageId); - singleRequest.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw; + + // Use Raw format when downloading MIME content, Metadata format when downloading headers only + // This is critical: Raw format doesn't populate Payload.Headers, Metadata format does! + // Previously always used Raw format, causing FromAddress/FromName to be empty when downloadMime=false + singleRequest.Format = downloadMime + ? UsersResource.MessagesResource.GetRequest.FormatEnum.Raw + : UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata; return singleRequest; } @@ -1482,9 +1493,27 @@ public class GmailSynchronizer : WinoSynchronizer h.Name.Equals("Date", StringComparison.OrdinalIgnoreCase))?.Value; + if (!string.IsNullOrEmpty(dateHeaderValue) && DateTime.TryParse(dateHeaderValue, out var parsedDate)) + { + creationDate = parsedDate.ToUniversalTime(); + } + } + return new MailCopy() { - CreationDate = DateTime.UtcNow, // We don't have the exact date without MIME, use current time + CreationDate = creationDate, Subject = HttpUtility.HtmlDecode(gmailMessage.Payload?.Headers?.FirstOrDefault(h => h.Name.Equals("Subject", StringComparison.OrdinalIgnoreCase))?.Value ?? ""), FromName = HttpUtility.HtmlDecode(gmailMessage.Payload?.Headers?.FirstOrDefault(h => h.Name.Equals("From", StringComparison.OrdinalIgnoreCase))?.Value ?? ""), FromAddress = ExtractEmailFromHeader(gmailMessage.Payload?.Headers?.FirstOrDefault(h => h.Name.Equals("From", StringComparison.OrdinalIgnoreCase))?.Value ?? ""), @@ -1532,7 +1561,7 @@ public class GmailSynchronizer : WinoSynchronizer