Calendar invitations for Mail part of the app.

This commit is contained in:
Burak Kaan Köse
2026-01-05 00:21:07 +01:00
parent 0b0f6b8d8e
commit 3d07328f47
24 changed files with 679 additions and 66 deletions
+14 -7
View File
@@ -12,7 +12,6 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.UI;
using Wino.Services.Extensions;
namespace Wino.Services;
@@ -21,6 +20,7 @@ public class AccountService : BaseDatabaseService, IAccountService
public IAuthenticator ExternalAuthenticationAuthenticator { get; set; }
private readonly ISignatureService _signatureService;
private readonly IAuthenticationProvider _authenticationProvider;
private readonly IMimeFileService _mimeFileService;
private readonly IPreferencesService _preferencesService;
@@ -28,10 +28,12 @@ public class AccountService : BaseDatabaseService, IAccountService
public AccountService(IDatabaseService databaseService,
ISignatureService signatureService,
IAuthenticationProvider authenticationProvider,
IMimeFileService mimeFileService,
IPreferencesService preferencesService) : base(databaseService)
{
_signatureService = signatureService;
_authenticationProvider = authenticationProvider;
_mimeFileService = mimeFileService;
_preferencesService = preferencesService;
}
@@ -59,7 +61,7 @@ public class AccountService : BaseDatabaseService, IAccountService
var sql = $"UPDATE MailAccount SET MergedInboxId = ? WHERE Id IN ({placeholders})";
var parameters = new List<object> { mergedInboxId };
parameters.AddRange(accountIdList.Cast<object>());
await Connection.ExecuteAsync(sql, parameters.ToArray());
WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested());
@@ -193,13 +195,18 @@ public class AccountService : BaseDatabaseService, IAccountService
if (account == null) return;
//var authenticator = _authenticationProvider.GetAuthenticator(account.ProviderType);
var authenticator = _authenticationProvider.GetAuthenticator(account.ProviderType);
//// This will re-generate token.
//var token = await authenticator.GenerateTokenInformationAsync(account);
// This will re-generate token with interactive authentication
// New authentication will include calendar scopes
var token = await authenticator.GenerateTokenInformationAsync(account);
// TODO: Rest?
// Guard.IsNotNull(token);
Guard.IsNotNull(token);
// Enable calendar access since new token includes calendar scopes
account.IsCalendarAccessGranted = true;
await UpdateAccountAsync(account);
}
private Task<MailAccountPreferences> GetAccountPreferencesAsync(Guid accountId)
@@ -111,6 +111,9 @@ public static class MailkitClientExtensions
// Use InternalDate (server received date) if available, otherwise fall back to Date header (sent date)
var creationDate = messageSummary.InternalDate?.UtcDateTime ?? mime.Date.UtcDateTime;
// Detect calendar invitation based on MIME content type
var itemType = GetMailItemTypeFromMime(mime);
var copy = new MailCopy()
{
Id = messageUid,
@@ -128,12 +131,49 @@ public static class MailkitClientExtensions
References = mime.References?.GetReferences(),
InReplyTo = mime.GetInReplyTo(),
HasAttachments = mime.Attachments.Any(),
FileId = Guid.NewGuid()
FileId = Guid.NewGuid(),
ItemType = itemType
};
return copy;
}
/// <summary>
/// Determines MailItemType based on MIME message content type.
/// Calendar invitations have text/calendar content type with METHOD parameter.
/// </summary>
private static MailItemType GetMailItemTypeFromMime(MimeMessage mime)
{
if (mime == null) return MailItemType.Mail;
// Check if the message contains text/calendar content
var calendarPart = mime.BodyParts.OfType<MimePart>()
.FirstOrDefault(p => p.ContentType?.MimeType?.Equals("text/calendar", StringComparison.OrdinalIgnoreCase) == true);
if (calendarPart != null)
{
// Check the METHOD parameter to determine invitation type
var method = calendarPart.ContentType.Parameters
.FirstOrDefault(p => p.Name.Equals("method", StringComparison.OrdinalIgnoreCase))?.Value?.ToUpperInvariant();
if (!string.IsNullOrEmpty(method))
{
return method switch
{
"REQUEST" => MailItemType.CalendarInvitation,
"CANCEL" => MailItemType.CalendarCancellation,
"REPLY" => MailItemType.CalendarResponse,
_ => MailItemType.Mail
};
}
// If no method specified, assume it's an invitation
return MailItemType.CalendarInvitation;
}
return MailItemType.Mail;
}
// TODO: Name and Address parsing should be handled better.
// At some point Wino needs better contact management.