Calendar attachments.

This commit is contained in:
Burak Kaan Köse
2026-01-03 23:59:37 +01:00
parent c8ef031e7d
commit 4603b1fb14
20 changed files with 758 additions and 21 deletions
@@ -1242,6 +1242,39 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
await _gmailChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
}
public override async Task DownloadCalendarAttachmentAsync(
Wino.Core.Domain.Entities.Calendar.CalendarItem calendarItem,
Wino.Core.Domain.Entities.Calendar.CalendarAttachment attachment,
string localFilePath,
CancellationToken cancellationToken = default)
{
try
{
// Gmail calendar attachments are stored in Google Drive
// RemoteAttachmentId contains either FileId or FileUrl
// For simplicity, we'll try to download from the FileId/FileUrl
if (string.IsNullOrEmpty(attachment.RemoteAttachmentId))
{
_logger.Error("RemoteAttachmentId is empty for attachment {AttachmentId}", attachment.Id);
throw new InvalidOperationException("RemoteAttachmentId is required to download Gmail calendar attachment.");
}
// Gmail calendar attachments are links to Google Drive files
// The attachment.RemoteAttachmentId is either a FileId or FileUrl
// Since we can't directly download from Calendar API, this would require Drive API access
// For now, throw NotSupportedException as Gmail attachments require additional Drive API setup
_logger.Warning("Gmail calendar attachment download requires Google Drive API access. FileId/URL: {RemoteId}", attachment.RemoteAttachmentId);
throw new NotSupportedException("Gmail calendar attachments are stored in Google Drive and require additional API configuration to download.");
}
catch (Exception ex)
{
_logger.Error(ex, "Error downloading Gmail calendar attachment {AttachmentId}", attachment.Id);
throw;
}
}
public override List<IRequestBundle<IClientServiceRequest>> RenameFolder(RenameFolderRequest request)
{
var label = new Label()
@@ -243,6 +243,18 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
_clientPool.Release(client);
}
public override Task DownloadCalendarAttachmentAsync(
Wino.Core.Domain.Entities.Calendar.CalendarItem calendarItem,
Wino.Core.Domain.Entities.Calendar.CalendarAttachment attachment,
string localFilePath,
CancellationToken cancellationToken = default)
{
// IMAP protocol doesn't support calendar operations natively
// Calendar functionality would require CalDAV protocol
_logger.Warning("IMAP protocol does not support calendar attachments. CalDAV would be required.");
throw new NotSupportedException("IMAP does not support calendar attachments. Use Outlook or Gmail for calendar functionality.");
}
public override List<IRequestBundle<ImapRequest>> RenameFolder(RenameFolderRequest request)
{
return CreateSingleTaskBundle(async (client, item) =>
+61 -3
View File
@@ -1337,6 +1337,61 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
await _outlookChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
}
public override async Task DownloadCalendarAttachmentAsync(
Wino.Core.Domain.Entities.Calendar.CalendarItem calendarItem,
Wino.Core.Domain.Entities.Calendar.CalendarAttachment attachment,
string localFilePath,
CancellationToken cancellationToken = default)
{
try
{
var calendar = calendarItem.AssignedCalendar;
// First, get the attachment metadata to retrieve contentBytes for FileAttachment
var attachmentItem = await _graphClient.Me
.Calendars[calendar.RemoteCalendarId]
.Events[calendarItem.RemoteEventId]
.Attachments[attachment.RemoteAttachmentId]
.GetAsync(cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (attachmentItem == null)
{
_logger.Error("Failed to retrieve attachment {AttachmentId} for event {EventId}", attachment.RemoteAttachmentId, calendarItem.RemoteEventId);
throw new InvalidOperationException("Failed to retrieve attachment.");
}
byte[] contentBytes = null;
// Handle FileAttachment (has ContentBytes property)
if (attachmentItem is FileAttachment fileAttachment && fileAttachment.ContentBytes != null)
{
contentBytes = fileAttachment.ContentBytes;
}
// Handle ItemAttachment (embedded items like emails)
else if (attachmentItem is ItemAttachment)
{
_logger.Warning("ItemAttachment type not supported for download. AttachmentId: {AttachmentId}", attachment.RemoteAttachmentId);
throw new NotSupportedException("ItemAttachment downloads are not currently supported.");
}
else
{
_logger.Error("Unknown attachment type or missing content for {AttachmentId}", attachment.RemoteAttachmentId);
throw new InvalidOperationException("Attachment content is not available.");
}
// Save to local file
await System.IO.File.WriteAllBytesAsync(localFilePath, contentBytes, cancellationToken).ConfigureAwait(false);
_logger.Information("Downloaded calendar attachment {FileName} to {LocalPath}", attachment.FileName, localFilePath);
}
catch (Exception ex)
{
_logger.Error(ex, "Error downloading calendar attachment {AttachmentId}", attachment.Id);
throw;
}
}
public override List<IRequestBundle<RequestInformation>> RenameFolder(RenameFolderRequest request)
{
var requestBody = new MailFolder
@@ -1693,9 +1748,12 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
{
await _handleCalendarEventRetrievalSemaphore.WaitAsync();
// Check if the event has complete information
// Sometimes delta sync returns events with only Id available
Event fullEvent = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events[item.Id].GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false); ;
Event fullEvent = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events[item.Id]
.GetAsync(requestConfiguration =>
{
// Expand attachments but only get metadata, not the full content
requestConfiguration.QueryParameters.Expand = new[] { "attachments($select=id,name,contentType,size,isInline)" };
}, cancellationToken: cancellationToken).ConfigureAwait(false);
await _outlookChangeProcessor.ManageCalendarEventAsync(fullEvent, calendar, Account).ConfigureAwait(false);
}
catch (Exception ex)
@@ -526,6 +526,19 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
/// <param name="cancellationToken">Cancellation token.</param>
public virtual Task DownloadMissingMimeMessageAsync(MailCopy mailItem, ITransferProgress transferProgress = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
/// <summary>
/// Downloads a calendar attachment from the provider.
/// </summary>
/// <param name="calendarItem">Calendar item the attachment belongs to.</param>
/// <param name="attachment">Attachment metadata to download.</param>
/// <param name="localFilePath">Local file path to save the attachment to.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual Task DownloadCalendarAttachmentAsync(
Wino.Core.Domain.Entities.Calendar.CalendarItem calendarItem,
Wino.Core.Domain.Entities.Calendar.CalendarAttachment attachment,
string localFilePath,
CancellationToken cancellationToken = default) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
/// <summary>
/// Performs an online search for the given query text in the given folders.
/// Downloads the missing messages from the server.