Refactored impa synchronization.

This commit is contained in:
Burak Kaan Köse
2026-02-14 12:52:17 +01:00
parent 4a0dcd2899
commit 744145be06
26 changed files with 1492 additions and 1243 deletions
+22 -1
View File
@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SQLite;
using Wino.Core.Domain.Entities.Calendar;
@@ -63,6 +64,26 @@ public class DatabaseService : IDatabaseService
Connection.CreateTableAsync<Reminder>(),
Connection.CreateTableAsync<MailInvitationCalendarMapping>()
);
await EnsureSchemaUpgradesAsync().ConfigureAwait(false);
}
private async Task EnsureSchemaUpgradesAsync()
{
var folderColumns = await Connection.GetTableInfoAsync(nameof(MailItemFolder)).ConfigureAwait(false);
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.HighestKnownUid)))
{
await Connection
.ExecuteAsync($"ALTER TABLE {nameof(MailItemFolder)} ADD COLUMN {nameof(MailItemFolder.HighestKnownUid)} INTEGER NOT NULL DEFAULT 0")
.ConfigureAwait(false);
}
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.LastUidReconcileUtc)))
{
await Connection
.ExecuteAsync($"ALTER TABLE {nameof(MailItemFolder)} ADD COLUMN {nameof(MailItemFolder.LastUidReconcileUtc)} TEXT NULL")
.ConfigureAwait(false);
}
}
}
@@ -93,44 +93,47 @@ public static class MailkitClientExtensions
return HtmlAgilityPackExtensions.GetPreviewText(message.HtmlBody);
}
public static MailCopy GetMailDetails(this IMessageSummary messageSummary, MailItemFolder folder, MimeMessage mime)
public static MailCopy GetMailDetails(this IMessageSummary messageSummary, MailItemFolder folder, MimeMessage mime = null)
{
// MessageSummary will only have UniqueId, Flags, ThreadId.
// Other properties are extracted directly from the MimeMessage.
// IMAP UIDs are unique only within a folder.
// MailCopy.Id maps to {FolderId}_{UID} for deterministic folder-local identity.
// IMAP doesn't have unique id for mails.
// All mails are mapped to specific folders with incremental Id.
// Uid 1 may belong to different messages in different folders, but can never be
// same for different messages in same folders.
// Here we create arbitrary Id that maps the Id of the message with Folder UniqueId.
// When folder becomes invalid, we'll clear out these MailCopies as well.
var envelope = messageSummary.Envelope;
var messageUid = CreateUid(folder.Id, messageSummary.UniqueId.Id);
var previewText = mime.GetPreviewText();
var subject = mime?.Subject ?? envelope?.Subject ?? string.Empty;
var previewText = mime != null ? mime.GetPreviewText() : GetPreviewText(messageSummary, subject);
// Use InternalDate (server received date) if available, otherwise fall back to Date header (sent date)
var creationDate = messageSummary.InternalDate?.UtcDateTime ?? mime.Date.UtcDateTime;
// Prefer InternalDate (server received time). Fall back to envelope date and finally UTC now.
var creationDate = messageSummary.InternalDate?.UtcDateTime
?? envelope?.Date?.UtcDateTime
?? DateTime.UtcNow;
// Detect calendar invitation based on MIME content type
var itemType = GetMailItemTypeFromMime(mime);
var messageId = mime?.GetMessageId() ?? envelope?.MessageId ?? string.Empty;
var fromName = mime != null ? GetActualSenderName(mime) : GetEnvelopeSenderName(envelope);
var fromAddress = mime != null ? GetActualSenderAddress(mime) : GetEnvelopeSenderAddress(envelope);
var references = mime?.References?.GetReferences() ?? messageSummary.References?.GetReferences();
var inReplyTo = mime != null ? mime.GetInReplyTo() : envelope?.InReplyTo ?? string.Empty;
var hasAttachments = mime != null ? mime.Attachments.Any() : false;
var itemType = mime != null ? GetMailItemTypeFromMime(mime) : MailItemType.Mail;
var copy = new MailCopy()
{
Id = messageUid,
CreationDate = creationDate,
ThreadId = messageSummary.GetThreadId(),
MessageId = mime.GetMessageId(),
Subject = mime.Subject,
MessageId = messageId,
Subject = subject,
IsRead = messageSummary.Flags.GetIsRead(),
IsFlagged = messageSummary.Flags.GetIsFlagged(),
PreviewText = previewText,
FromAddress = GetActualSenderAddress(mime),
FromName = GetActualSenderName(mime),
FromAddress = fromAddress,
FromName = fromName,
IsFocused = false,
Importance = mime.GetImportance(),
References = mime.References?.GetReferences(),
InReplyTo = mime.GetInReplyTo(),
HasAttachments = mime.Attachments.Any(),
Importance = mime != null ? mime.GetImportance() : MailImportance.Normal,
References = references,
InReplyTo = inReplyTo,
HasAttachments = hasAttachments,
FileId = Guid.NewGuid(),
ItemType = itemType
};
@@ -138,6 +141,29 @@ public static class MailkitClientExtensions
return copy;
}
private static string GetPreviewText(IMessageSummary messageSummary, string subjectFallback)
{
if (!string.IsNullOrWhiteSpace(messageSummary.PreviewText))
return messageSummary.PreviewText;
return subjectFallback ?? string.Empty;
}
private static string GetEnvelopeSenderName(Envelope envelope)
{
var mailbox = envelope?.From?.Mailboxes?.FirstOrDefault() ?? envelope?.Sender?.Mailboxes?.FirstOrDefault();
if (mailbox == null)
return Translator.UnknownSender;
return string.IsNullOrWhiteSpace(mailbox.Name) ? mailbox.Address : mailbox.Name;
}
private static string GetEnvelopeSenderAddress(Envelope envelope)
{
var mailbox = envelope?.From?.Mailboxes?.FirstOrDefault() ?? envelope?.Sender?.Mailboxes?.FirstOrDefault();
return mailbox?.Address ?? Translator.UnknownSender;
}
/// <summary>
/// Determines MailItemType based on MIME message content type.
/// Calendar invitations have text/calendar content type with METHOD parameter.
+3
View File
@@ -490,6 +490,9 @@ public class FolderService : BaseDatabaseService, IFolderService
await Connection.UpdateAsync(folder, typeof(MailItemFolder)).ConfigureAwait(false);
}
public Task UpdateFolderHighestModeSeqAsync(Guid folderId, long highestModeSeq)
=> Connection.ExecuteAsync("UPDATE MailItemFolder SET HighestModeSeq = ? WHERE Id = ?", highestModeSeq, folderId);
private async Task DeleteFolderAsync(MailItemFolder folder)
{
if (folder == null)