Generic 404 handler for synchronizers.
This commit is contained in:
@@ -188,6 +188,35 @@ public class MenuItemCollection : ObservableRangeCollection<IMenuItem>
|
|||||||
Insert(insertIndex, accountMenuItem);
|
Insert(insertIndex, accountMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool RemoveFolderMenuItem(Guid folderId)
|
||||||
|
{
|
||||||
|
// Check root-level items.
|
||||||
|
var rootItem = this.OfType<IBaseFolderMenuItem>()
|
||||||
|
.FirstOrDefault(a => a.HandlingFolders.Any(b => b.Id == folderId));
|
||||||
|
|
||||||
|
if (rootItem != null)
|
||||||
|
{
|
||||||
|
Remove(rootItem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check sub-items of root folders.
|
||||||
|
foreach (var rootFolder in this.OfType<IBaseFolderMenuItem>())
|
||||||
|
{
|
||||||
|
var subItem = rootFolder.SubMenuItems
|
||||||
|
.OfType<IBaseFolderMenuItem>()
|
||||||
|
.FirstOrDefault(a => a.HandlingFolders.Any(b => b.Id == folderId));
|
||||||
|
|
||||||
|
if (subItem != null)
|
||||||
|
{
|
||||||
|
rootFolder.SubMenuItems.Remove(subItem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearFolderAreaMenuItems()
|
private void ClearFolderAreaMenuItems()
|
||||||
{
|
{
|
||||||
var itemsToRemove = this.Where(a => !_preservingTypesForFolderArea.Contains(a.GetType())).ToList();
|
var itemsToRemove = this.Where(a => !_preservingTypesForFolderArea.Contains(a.GetType())).ToList();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Wino.Authentication;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
|
using Wino.Core.Synchronizers.Errors;
|
||||||
using Wino.Core.Synchronizers.Errors.Gmail;
|
using Wino.Core.Synchronizers.Errors.Gmail;
|
||||||
using Wino.Core.Synchronizers.Errors.Imap;
|
using Wino.Core.Synchronizers.Errors.Imap;
|
||||||
using Wino.Core.Synchronizers.Errors.Outlook;
|
using Wino.Core.Synchronizers.Errors.Outlook;
|
||||||
@@ -49,6 +50,8 @@ public static class CoreContainerSetup
|
|||||||
services.AddTransient<GmailQuotaExceededHandler>();
|
services.AddTransient<GmailQuotaExceededHandler>();
|
||||||
services.AddTransient<GmailRateLimitHandler>();
|
services.AddTransient<GmailRateLimitHandler>();
|
||||||
services.AddTransient<GmailHistoryExpiredHandler>();
|
services.AddTransient<GmailHistoryExpiredHandler>();
|
||||||
|
// Register shared error handlers
|
||||||
|
services.AddTransient<EntityNotFoundHandler>();
|
||||||
|
|
||||||
// Register IMAP error handlers
|
// Register IMAP error handlers
|
||||||
services.AddTransient<ImapConnectionLostHandler>();
|
services.AddTransient<ImapConnectionLostHandler>();
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.Requests;
|
using Wino.Core.Domain.Models.Requests;
|
||||||
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests.Folder;
|
namespace Wino.Core.Requests.Folder;
|
||||||
|
|
||||||
public record DeleteFolderRequest(MailItemFolder Folder) : FolderRequestBase(Folder, FolderSynchronizerOperation.DeleteFolder)
|
public record DeleteFolderRequest(MailItemFolder Folder) : FolderRequestBase(Folder, FolderSynchronizerOperation.DeleteFolder)
|
||||||
{
|
{
|
||||||
public override void ApplyUIChanges() { }
|
public override void ApplyUIChanges()
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new FolderDeleted(Folder));
|
||||||
|
}
|
||||||
|
|
||||||
public override void RevertUIChanges() { }
|
public override void RevertUIChanges() { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Synchronizers.Errors;
|
||||||
using Wino.Core.Synchronizers.Errors.Gmail;
|
using Wino.Core.Synchronizers.Errors.Gmail;
|
||||||
|
|
||||||
namespace Wino.Core.Services;
|
namespace Wino.Core.Services;
|
||||||
@@ -12,11 +13,13 @@ public class GmailSynchronizerErrorHandlingFactory : SynchronizerErrorHandlingFa
|
|||||||
public GmailSynchronizerErrorHandlingFactory(
|
public GmailSynchronizerErrorHandlingFactory(
|
||||||
GmailQuotaExceededHandler quotaExceededHandler,
|
GmailQuotaExceededHandler quotaExceededHandler,
|
||||||
GmailRateLimitHandler rateLimitHandler,
|
GmailRateLimitHandler rateLimitHandler,
|
||||||
GmailHistoryExpiredHandler historyExpiredHandler)
|
GmailHistoryExpiredHandler historyExpiredHandler,
|
||||||
|
EntityNotFoundHandler entityNotFoundHandler)
|
||||||
{
|
{
|
||||||
// Order matters - more specific handlers should be registered first
|
// Order matters - more specific handlers should be registered first
|
||||||
RegisterHandler(quotaExceededHandler);
|
RegisterHandler(quotaExceededHandler);
|
||||||
RegisterHandler(historyExpiredHandler);
|
RegisterHandler(historyExpiredHandler);
|
||||||
|
RegisterHandler(entityNotFoundHandler);
|
||||||
RegisterHandler(rateLimitHandler); // Most generic rate limit handler last
|
RegisterHandler(rateLimitHandler); // Most generic rate limit handler last
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Synchronizers.Errors;
|
||||||
using Wino.Core.Synchronizers.Errors.Imap;
|
using Wino.Core.Synchronizers.Errors.Imap;
|
||||||
|
|
||||||
namespace Wino.Core.Services;
|
namespace Wino.Core.Services;
|
||||||
@@ -12,11 +13,13 @@ public class ImapSynchronizerErrorHandlingFactory : SynchronizerErrorHandlingFac
|
|||||||
public ImapSynchronizerErrorHandlingFactory(
|
public ImapSynchronizerErrorHandlingFactory(
|
||||||
ImapConnectionLostHandler connectionLostHandler,
|
ImapConnectionLostHandler connectionLostHandler,
|
||||||
ImapAuthenticationFailedHandler authFailedHandler,
|
ImapAuthenticationFailedHandler authFailedHandler,
|
||||||
|
EntityNotFoundHandler entityNotFoundHandler,
|
||||||
ImapFolderNotFoundHandler folderNotFoundHandler,
|
ImapFolderNotFoundHandler folderNotFoundHandler,
|
||||||
ImapProtocolErrorHandler protocolErrorHandler)
|
ImapProtocolErrorHandler protocolErrorHandler)
|
||||||
{
|
{
|
||||||
// Order matters - more specific handlers should be registered first
|
// Order matters - more specific handlers should be registered first
|
||||||
RegisterHandler(authFailedHandler);
|
RegisterHandler(authFailedHandler);
|
||||||
|
RegisterHandler(entityNotFoundHandler);
|
||||||
RegisterHandler(folderNotFoundHandler);
|
RegisterHandler(folderNotFoundHandler);
|
||||||
RegisterHandler(connectionLostHandler);
|
RegisterHandler(connectionLostHandler);
|
||||||
RegisterHandler(protocolErrorHandler); // Most generic, registered last
|
RegisterHandler(protocolErrorHandler); // Most generic, registered last
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Synchronizers.Errors;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
|
||||||
using Wino.Core.Synchronizers.Errors.Outlook;
|
using Wino.Core.Synchronizers.Errors.Outlook;
|
||||||
|
|
||||||
namespace Wino.Core.Services;
|
namespace Wino.Core.Services;
|
||||||
@@ -8,13 +7,11 @@ namespace Wino.Core.Services;
|
|||||||
public class OutlookSynchronizerErrorHandlingFactory : SynchronizerErrorHandlingFactory, IOutlookSynchronizerErrorHandlerFactory
|
public class OutlookSynchronizerErrorHandlingFactory : SynchronizerErrorHandlingFactory, IOutlookSynchronizerErrorHandlerFactory
|
||||||
{
|
{
|
||||||
public OutlookSynchronizerErrorHandlingFactory(ObjectCannotBeDeletedHandler objectCannotBeDeleted,
|
public OutlookSynchronizerErrorHandlingFactory(ObjectCannotBeDeletedHandler objectCannotBeDeleted,
|
||||||
|
EntityNotFoundHandler entityNotFoundHandler,
|
||||||
DeltaTokenExpiredHandler deltaTokenExpiredHandler)
|
DeltaTokenExpiredHandler deltaTokenExpiredHandler)
|
||||||
{
|
{
|
||||||
RegisterHandler(objectCannotBeDeleted);
|
RegisterHandler(objectCannotBeDeleted);
|
||||||
|
RegisterHandler(entityNotFoundHandler);
|
||||||
RegisterHandler(deltaTokenExpiredHandler);
|
RegisterHandler(deltaTokenExpiredHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanHandle(SynchronizerErrorContext error) => CanHandle(error);
|
|
||||||
|
|
||||||
public Task HandleAsync(SynchronizerErrorContext error) => HandleErrorAsync(error);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,6 +405,11 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
await synchronizer.DownloadMissingMimeMessageAsync(mailItem, null, cancellationToken);
|
await synchronizer.DownloadMissingMimeMessageAsync(mailItem, null, cancellationToken);
|
||||||
return mailItem.Id.ToString(); // Return some identifier, actual implementation might be different
|
return mailItem.Id.ToString(); // Return some identifier, actual implementation might be different
|
||||||
}
|
}
|
||||||
|
catch (SynchronizerEntityNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.Warning("MIME message for mail item {MailItemId} no longer exists on server. Removed locally.", mailItem.Id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to download MIME message for mail item {MailItemId}", mailItem.Id);
|
_logger.Error(ex, "Failed to download MIME message for mail item {MailItemId}", mailItem.Id);
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Serilog;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
|
namespace Wino.Core.Synchronizers.Errors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic handler for 404 (Not Found) errors across all synchronizers.
|
||||||
|
/// When a resource is already gone on the server, this handler applies
|
||||||
|
/// the intended change locally instead of throwing.
|
||||||
|
/// Works for all mail actions, folder actions, and batch operations.
|
||||||
|
/// </summary>
|
||||||
|
public class EntityNotFoundHandler : ISynchronizerErrorHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = Log.ForContext<EntityNotFoundHandler>();
|
||||||
|
private readonly IMailService _mailService;
|
||||||
|
private readonly IFolderService _folderService;
|
||||||
|
|
||||||
|
public EntityNotFoundHandler(IMailService mailService, IFolderService folderService)
|
||||||
|
{
|
||||||
|
_mailService = mailService;
|
||||||
|
_folderService = folderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHandle(SynchronizerErrorContext error)
|
||||||
|
{
|
||||||
|
if (error.ErrorCode != 404) return false;
|
||||||
|
if (error.RequestBundle == null) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HandleAsync(SynchronizerErrorContext error)
|
||||||
|
{
|
||||||
|
error.Severity = SynchronizerErrorSeverity.Recoverable;
|
||||||
|
error.Category = SynchronizerErrorCategory.ResourceNotFound;
|
||||||
|
|
||||||
|
var uiRequest = error.RequestBundle.UIChangeRequest;
|
||||||
|
|
||||||
|
// --- Folder actions ---
|
||||||
|
if (uiRequest is IFolderActionRequest folderAction)
|
||||||
|
{
|
||||||
|
_logger.Warning("Entity not found (404) for folder operation {Op} on {RemoteFolderId}. Deleting locally.",
|
||||||
|
folderAction.Operation, folderAction.Folder.RemoteFolderId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _folderService.DeleteFolderAsync(
|
||||||
|
folderAction.Folder.MailAccountId,
|
||||||
|
folderAction.Folder.RemoteFolderId).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to delete folder locally after 404.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Individual mail actions ---
|
||||||
|
if (uiRequest is IMailActionRequest mailAction && error.Account != null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Entity not found (404) for mail operation {Op} on {MailId}. Deleting locally.",
|
||||||
|
mailAction.Operation, mailAction.Item.Id);
|
||||||
|
|
||||||
|
// Revert optimistic UI change (e.g. mark-read/flag toggle) before deleting
|
||||||
|
error.RequestBundle.UIChangeRequest?.RevertUIChanges();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _mailService.DeleteMailAsync(
|
||||||
|
error.Account.Id, mailAction.Item.Id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to delete mail locally after 404.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Batch requests (can't identify specific item) ---
|
||||||
|
// Mark as recoverable. Next sync will clean up stale items.
|
||||||
|
_logger.Warning("Entity not found (404) for batch operation. Marking as recoverable.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1318,19 +1318,28 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
ITransferProgress transferProgress = null,
|
ITransferProgress transferProgress = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var request = _gmailService.Users.Messages.Get("me", mailItem.Id);
|
try
|
||||||
request.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
|
|
||||||
|
|
||||||
var gmailMessage = await request.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
var mimeMessage = gmailMessage.GetGmailMimeMessage();
|
|
||||||
|
|
||||||
if (mimeMessage == null)
|
|
||||||
{
|
{
|
||||||
_logger.Warning("Tried to download Gmail Raw Mime with {Id} id and server responded without a data.", mailItem.Id);
|
var request = _gmailService.Users.Messages.Get("me", mailItem.Id);
|
||||||
return;
|
request.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
|
||||||
}
|
|
||||||
|
|
||||||
await _gmailChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
|
var gmailMessage = await request.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
var mimeMessage = gmailMessage.GetGmailMimeMessage();
|
||||||
|
|
||||||
|
if (mimeMessage == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Tried to download Gmail Raw Mime with {Id} id and server responded without a data.", mailItem.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _gmailChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (GoogleApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
_logger.Warning("Gmail message {MailId} not found (404) during MIME download. Deleting locally.", mailItem.Id);
|
||||||
|
await _gmailChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
|
||||||
|
throw new SynchronizerEntityNotFoundException(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task DownloadCalendarAttachmentAsync(
|
public override async Task DownloadCalendarAttachmentAsync(
|
||||||
@@ -1472,12 +1481,12 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
// Create error context
|
// Create error context
|
||||||
var errorContext = new SynchronizerErrorContext
|
var errorContext = new SynchronizerErrorContext
|
||||||
{
|
{
|
||||||
|
Account = Account,
|
||||||
ErrorCode = error.Code,
|
ErrorCode = error.Code,
|
||||||
ErrorMessage = error.Message,
|
ErrorMessage = error.Message,
|
||||||
RequestBundle = bundle,
|
RequestBundle = bundle,
|
||||||
AdditionalData = new Dictionary<string, object>
|
AdditionalData = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ "Account", Account },
|
|
||||||
{ "Error", error }
|
{ "Error", error }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -235,18 +235,36 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
var remoteFolderId = folder.RemoteFolderId;
|
var remoteFolderId = folder.RemoteFolderId;
|
||||||
|
|
||||||
var client = await _clientPool.GetClientAsync().ConfigureAwait(false);
|
var client = await _clientPool.GetClientAsync().ConfigureAwait(false);
|
||||||
var remoteFolder = await client.GetFolderAsync(remoteFolderId, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var uniqueId = new UniqueId(MailkitClientExtensions.ResolveUid(mailItem.Id));
|
try
|
||||||
|
{
|
||||||
|
var remoteFolder = await client.GetFolderAsync(remoteFolderId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);
|
var uniqueId = new UniqueId(MailkitClientExtensions.ResolveUid(mailItem.Id));
|
||||||
|
|
||||||
var message = await remoteFolder.GetMessageAsync(uniqueId, cancellationToken, transferProgress).ConfigureAwait(false);
|
await remoteFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await _imapChangeProcessor.SaveMimeFileAsync(mailItem.FileId, message, Account.Id).ConfigureAwait(false);
|
var message = await remoteFolder.GetMessageAsync(uniqueId, cancellationToken, transferProgress).ConfigureAwait(false);
|
||||||
await remoteFolder.CloseAsync(false, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_clientPool.Release(client);
|
await _imapChangeProcessor.SaveMimeFileAsync(mailItem.FileId, message, Account.Id).ConfigureAwait(false);
|
||||||
|
await remoteFolder.CloseAsync(false, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (FolderNotFoundException ex)
|
||||||
|
{
|
||||||
|
_logger.Warning("IMAP folder {FolderId} not found during MIME download for {MailId}. Deleting locally.", remoteFolderId, mailItem.Id);
|
||||||
|
await _imapChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
|
||||||
|
throw new SynchronizerEntityNotFoundException(ex.Message);
|
||||||
|
}
|
||||||
|
catch (ImapCommandException ex) when (ex.Response == ImapCommandResponse.No)
|
||||||
|
{
|
||||||
|
_logger.Warning("IMAP message {MailId} not found during MIME download (NO response). Deleting locally.", mailItem.Id);
|
||||||
|
await _imapChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
|
||||||
|
throw new SynchronizerEntityNotFoundException(ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_clientPool.Release(client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task DownloadCalendarAttachmentAsync(
|
public override Task DownloadCalendarAttachmentAsync(
|
||||||
@@ -500,16 +518,29 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Retry pattern.
|
|
||||||
// TODO: Error handling.
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await item.NativeRequest.IntegratorTask(executorClient, item.Request).ConfigureAwait(false);
|
await item.NativeRequest.IntegratorTask(executorClient, item.Request).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
item.Request.RevertUIChanges();
|
var errorContext = new SynchronizerErrorContext
|
||||||
throw;
|
{
|
||||||
|
Account = Account,
|
||||||
|
ErrorCode = ex is FolderNotFoundException ? 404 : null,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
Exception = ex,
|
||||||
|
RequestBundle = item,
|
||||||
|
OperationType = "RequestExecution"
|
||||||
|
};
|
||||||
|
|
||||||
|
var handled = await _errorHandlerFactory.HandleErrorAsync(errorContext).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!handled)
|
||||||
|
{
|
||||||
|
item.Request.RevertUIChanges();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1430,8 +1430,17 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
MailKit.ITransferProgress transferProgress = null,
|
MailKit.ITransferProgress transferProgress = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var mimeMessage = await DownloadMimeMessageAsync(mailItem.Id, cancellationToken).ConfigureAwait(false);
|
try
|
||||||
await _outlookChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
|
{
|
||||||
|
var mimeMessage = await DownloadMimeMessageAsync(mailItem.Id, cancellationToken).ConfigureAwait(false);
|
||||||
|
await _outlookChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (ODataError ex) when (ex.ResponseStatusCode == 404)
|
||||||
|
{
|
||||||
|
_logger.Warning("Outlook message {MailId} not found (404) during MIME download. Deleting locally.", mailItem.Id);
|
||||||
|
await _outlookChangeProcessor.DeleteMailAsync(Account.Id, mailItem.Id).ConfigureAwait(false);
|
||||||
|
throw new SynchronizerEntityNotFoundException(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task DownloadCalendarAttachmentAsync(
|
public override async Task DownloadCalendarAttachmentAsync(
|
||||||
|
|||||||
@@ -988,6 +988,21 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
UpdateFolderCollection(mailItemFolder);
|
UpdateFolderCollection(mailItemFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnFolderDeleted(MailItemFolder folder)
|
||||||
|
{
|
||||||
|
base.OnFolderDeleted(folder);
|
||||||
|
|
||||||
|
bool wasSelected = SelectedMenuItem is IBaseFolderMenuItem selectedFolder &&
|
||||||
|
selectedFolder.HandlingFolders.Any(a => a.Id == folder.Id);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => MenuItems.RemoveFolderMenuItem(folder.Id));
|
||||||
|
|
||||||
|
if (wasSelected && latestSelectedAccountMenuItem != null)
|
||||||
|
{
|
||||||
|
await NavigateInboxAsync(latestSelectedAccountMenuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder)
|
protected override void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder)
|
||||||
{
|
{
|
||||||
base.OnFolderSynchronizationEnabled(mailItemFolder);
|
base.OnFolderSynchronizationEnabled(mailItemFolder);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
IRecipient<DraftFailed>,
|
IRecipient<DraftFailed>,
|
||||||
IRecipient<DraftMapped>,
|
IRecipient<DraftMapped>,
|
||||||
IRecipient<FolderRenamed>,
|
IRecipient<FolderRenamed>,
|
||||||
|
IRecipient<FolderDeleted>,
|
||||||
IRecipient<FolderSynchronizationEnabled>
|
IRecipient<FolderSynchronizationEnabled>
|
||||||
{
|
{
|
||||||
protected virtual void OnMailAdded(MailCopy addedMail) { }
|
protected virtual void OnMailAdded(MailCopy addedMail) { }
|
||||||
@@ -27,6 +28,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
|
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
|
||||||
protected virtual void OnDraftMapped(string localDraftCopyId, string remoteDraftCopyId) { }
|
protected virtual void OnDraftMapped(string localDraftCopyId, string remoteDraftCopyId) { }
|
||||||
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
|
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
|
||||||
|
protected virtual void OnFolderDeleted(MailItemFolder folder) { }
|
||||||
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
|
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
|
||||||
|
|
||||||
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
|
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
|
||||||
@@ -39,6 +41,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
void IRecipient<DraftCreated>.Receive(DraftCreated message) => OnDraftCreated(message.DraftMail, message.Account);
|
void IRecipient<DraftCreated>.Receive(DraftCreated message) => OnDraftCreated(message.DraftMail, message.Account);
|
||||||
|
|
||||||
void IRecipient<FolderRenamed>.Receive(FolderRenamed message) => OnFolderRenamed(message.MailItemFolder);
|
void IRecipient<FolderRenamed>.Receive(FolderRenamed message) => OnFolderRenamed(message.MailItemFolder);
|
||||||
|
void IRecipient<FolderDeleted>.Receive(FolderDeleted message) => OnFolderDeleted(message.MailItemFolder);
|
||||||
void IRecipient<FolderSynchronizationEnabled>.Receive(FolderSynchronizationEnabled message) => OnFolderSynchronizationEnabled(message.MailItemFolder);
|
void IRecipient<FolderSynchronizationEnabled>.Receive(FolderSynchronizationEnabled message) => OnFolderSynchronizationEnabled(message.MailItemFolder);
|
||||||
|
|
||||||
protected override void RegisterRecipients()
|
protected override void RegisterRecipients()
|
||||||
@@ -55,6 +58,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
Messenger.Register<DraftFailed>(this);
|
Messenger.Register<DraftFailed>(this);
|
||||||
Messenger.Register<DraftMapped>(this);
|
Messenger.Register<DraftMapped>(this);
|
||||||
Messenger.Register<FolderRenamed>(this);
|
Messenger.Register<FolderRenamed>(this);
|
||||||
|
Messenger.Register<FolderDeleted>(this);
|
||||||
Messenger.Register<FolderSynchronizationEnabled>(this);
|
Messenger.Register<FolderSynchronizationEnabled>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
Messenger.Unregister<DraftFailed>(this);
|
Messenger.Unregister<DraftFailed>(this);
|
||||||
Messenger.Unregister<DraftMapped>(this);
|
Messenger.Unregister<DraftMapped>(this);
|
||||||
Messenger.Unregister<FolderRenamed>(this);
|
Messenger.Unregister<FolderRenamed>(this);
|
||||||
|
Messenger.Unregister<FolderDeleted>(this);
|
||||||
Messenger.Unregister<FolderSynchronizationEnabled>(this);
|
Messenger.Unregister<FolderSynchronizationEnabled>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -780,6 +780,20 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnFolderDeleted(MailItemFolder folder)
|
||||||
|
{
|
||||||
|
base.OnFolderDeleted(folder);
|
||||||
|
|
||||||
|
if (ActiveFolder == null) return;
|
||||||
|
|
||||||
|
bool isActiveFolder = ActiveFolder.HandlingFolders.Any(a => a.Id == folder.Id);
|
||||||
|
|
||||||
|
if (isActiveFolder)
|
||||||
|
{
|
||||||
|
await MailCollection.ClearAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account)
|
protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account)
|
||||||
{
|
{
|
||||||
base.OnDraftCreated(draftMail, account);
|
base.OnDraftCreated(draftMail, account);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
|
||||||
|
namespace Wino.Messaging.UI;
|
||||||
|
|
||||||
|
public record FolderDeleted(MailItemFolder MailItemFolder) : UIMessageBase<FolderDeleted>;
|
||||||
Reference in New Issue
Block a user