Synchronization manager.
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Models.Authentication;
|
||||||
|
using Wino.Core.Domain.Models.Connectivity;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for the singleton synchronization manager that handles synchronizer instances and operations.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISynchronizationManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the SynchronizationManager with required dependencies.
|
||||||
|
/// </summary>
|
||||||
|
Task InitializeAsync(ISynchronizerFactory synchronizerFactory,
|
||||||
|
IImapTestService imapTestService,
|
||||||
|
IAccountService accountService,
|
||||||
|
IAuthenticationProvider authenticationProvider);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests IMAP server connectivity for the given server information.
|
||||||
|
/// </summary>
|
||||||
|
Task<ImapConnectivityTestResults> TestImapConnectivityAsync(CustomServerInformation serverInformation, bool allowSSLHandshake);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a new mail synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
Task<MailSynchronizationResult> SynchronizeMailAsync(MailSynchronizationOptions options,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if there is an ongoing synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
bool IsAccountSynchronizing(Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a mail action request to the corresponding account's synchronizer.
|
||||||
|
/// </summary>
|
||||||
|
Task QueueRequestAsync(IRequestBase request, Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles folder synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
Task<MailSynchronizationResult> SynchronizeFoldersAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles alias synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
Task<MailSynchronizationResult> SynchronizeAliasesAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles profile synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
Task<MailSynchronizationResult> SynchronizeProfileAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a MIME message for the given mail item.
|
||||||
|
/// </summary>
|
||||||
|
Task<string> DownloadMimeMessageAsync(MailCopy mailItem, Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new synchronizer for a newly added account.
|
||||||
|
/// </summary>
|
||||||
|
Task<IWinoSynchronizerBase> CreateSynchronizerForAccountAsync(MailAccount account);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the synchronizer for the given account.
|
||||||
|
/// </summary>
|
||||||
|
Task DestroySynchronizerAsync(Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all cached synchronizers.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<IWinoSynchronizerBase> GetAllSynchronizers();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a synchronizer for the given account ID.
|
||||||
|
/// </summary>
|
||||||
|
Task<IWinoSynchronizerBase> GetSynchronizerAsync(Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles OAuth authentication for the specified provider.
|
||||||
|
/// </summary>
|
||||||
|
Task<TokenInformationEx> HandleAuthorizationAsync(MailProviderType providerType,
|
||||||
|
MailAccount account = null,
|
||||||
|
bool proposeCopyAuthorizationURL = false);
|
||||||
|
}
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simple wrapper class to maintain compatibility with the original WinoServerResponse structure.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of the expected response.</typeparam>
|
|
||||||
public class WinoServerResponse<T>
|
|
||||||
{
|
|
||||||
public bool IsSuccess { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
public T Data { get; set; }
|
|
||||||
|
|
||||||
public static WinoServerResponse<T> CreateSuccessResponse(T data)
|
|
||||||
{
|
|
||||||
return new WinoServerResponse<T>
|
|
||||||
{
|
|
||||||
IsSuccess = true,
|
|
||||||
Data = data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WinoServerResponse<T> CreateErrorResponse(string message)
|
|
||||||
{
|
|
||||||
return new WinoServerResponse<T>
|
|
||||||
{
|
|
||||||
IsSuccess = false,
|
|
||||||
Message = message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ThrowIfFailed()
|
|
||||||
{
|
|
||||||
if (!IsSuccess)
|
|
||||||
throw new InvalidOperationException(Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connection status enum to maintain compatibility.
|
|
||||||
/// </summary>
|
|
||||||
public enum WinoServerConnectionStatus
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Connecting,
|
|
||||||
Connected,
|
|
||||||
Disconnected,
|
|
||||||
Failed
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IWinoServerConnectionManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// When the connection status changes, this event will be triggered.
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the connection status.
|
|
||||||
/// </summary>
|
|
||||||
WinoServerConnectionStatus Status { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Launches Full Trust process (Wino Server) and awaits connection completion.
|
|
||||||
/// If connection is not established in 10 seconds, it will return false.
|
|
||||||
/// If the server process is already running, it'll connect to existing one.
|
|
||||||
/// If the server process is not running, it'll be launched and connection establishment is awaited.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Whether connection is established or not.</returns>
|
|
||||||
Task<bool> ConnectAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queues a new user request to be processed by Wino Server.
|
|
||||||
/// Healthy connection must present before calling this method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">Request to queue for synchronizer in the server.</param>
|
|
||||||
/// <param name="accountId">Account id to queueu request for.</param>
|
|
||||||
Task QueueRequestAsync(IRequestBase request, Guid accountId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns response from server for the given request.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TResponse">Response type.</typeparam>
|
|
||||||
/// <typeparam name="TRequestType">Request type.</typeparam>
|
|
||||||
/// <param name="clientMessage">Request type.</param>
|
|
||||||
/// <returns>Response received from the server for the given TResponse type.</returns>
|
|
||||||
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage, CancellationToken cancellationToken = default) where TRequestType : IClientMessage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle for connecting to the server.
|
|
||||||
/// If the server is already running, it'll connect to existing one.
|
|
||||||
/// Callers can await this handle to wait for connection establishment.
|
|
||||||
/// </summary>
|
|
||||||
TaskCompletionSource<bool> ConnectingHandle { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Existing connection handle to the server of TAppServiceConnection type.
|
|
||||||
/// </summary>
|
|
||||||
TAppServiceConnection Connection { get; set; }
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM
|
|||||||
|
|
||||||
public int FREE_ACCOUNT_COUNT { get; } = 3;
|
public int FREE_ACCOUNT_COUNT { get; } = 3;
|
||||||
protected IDialogServiceBase DialogService { get; }
|
protected IDialogServiceBase DialogService { get; }
|
||||||
protected IWinoServerConnectionManager WinoServerConnectionManager { get; }
|
|
||||||
protected INavigationService NavigationService { get; }
|
protected INavigationService NavigationService { get; }
|
||||||
protected IAccountService AccountService { get; }
|
protected IAccountService AccountService { get; }
|
||||||
protected IProviderService ProviderService { get; }
|
protected IProviderService ProviderService { get; }
|
||||||
@@ -49,7 +48,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM
|
|||||||
protected IPreferencesService PreferencesService { get; }
|
protected IPreferencesService PreferencesService { get; }
|
||||||
|
|
||||||
public AccountManagementPageViewModelBase(IDialogServiceBase dialogService,
|
public AccountManagementPageViewModelBase(IDialogServiceBase dialogService,
|
||||||
IWinoServerConnectionManager winoServerConnectionManager,
|
|
||||||
INavigationService navigationService,
|
INavigationService navigationService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
@@ -58,7 +56,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM
|
|||||||
IPreferencesService preferencesService)
|
IPreferencesService preferencesService)
|
||||||
{
|
{
|
||||||
DialogService = dialogService;
|
DialogService = dialogService;
|
||||||
WinoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
NavigationService = navigationService;
|
NavigationService = navigationService;
|
||||||
AccountService = accountService;
|
AccountService = accountService;
|
||||||
ProviderService = providerService;
|
ProviderService = providerService;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Windows.ApplicationModel.AppService;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
using Wino.Core.WinUI.Services;
|
using Wino.Core.WinUI.Services;
|
||||||
@@ -12,10 +11,6 @@ public static class CoreUWPContainerSetup
|
|||||||
{
|
{
|
||||||
public static void RegisterCoreUWPServices(this IServiceCollection services)
|
public static void RegisterCoreUWPServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
var serverConnectionManager = new EmptyWinoServerConnectionManager<AppServiceConnection>();
|
|
||||||
|
|
||||||
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
|
|
||||||
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
|
|
||||||
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
|
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
|
||||||
|
|
||||||
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
|
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
namespace Wino.Core.WinUI.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Empty implementation of IWinoServerConnectionManager that returns default values.
|
|
||||||
/// This replaces the old AppServiceConnection-based implementation.
|
|
||||||
/// </summary>
|
|
||||||
public class EmptyWinoServerConnectionManager : IWinoServerConnectionManager
|
|
||||||
{
|
|
||||||
public event EventHandler<WinoServerConnectionStatus> StatusChanged { add { } remove { } }
|
|
||||||
|
|
||||||
public WinoServerConnectionStatus Status => WinoServerConnectionStatus.Connected;
|
|
||||||
|
|
||||||
public TaskCompletionSource<bool> ConnectingHandle { get; } = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
public EmptyWinoServerConnectionManager()
|
|
||||||
{
|
|
||||||
ConnectingHandle.SetResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> ConnectAsync()
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage, CancellationToken cancellationToken = default)
|
|
||||||
where TRequestType : IClientMessage
|
|
||||||
{
|
|
||||||
var response = WinoServerResponse<TResponse>.CreateSuccessResponse(default(TResponse));
|
|
||||||
return Task.FromResult(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generic empty implementation for typed connection managers.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TAppServiceConnection">The connection type (not used in this implementation)</typeparam>
|
|
||||||
public class EmptyWinoServerConnectionManager<TAppServiceConnection> : EmptyWinoServerConnectionManager, IWinoServerConnectionManager<TAppServiceConnection>
|
|
||||||
{
|
|
||||||
public TAppServiceConnection Connection { get; set; }
|
|
||||||
|
|
||||||
public Task InitializeAsync()
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@ using Windows.Storage;
|
|||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Translations;
|
using Wino.Core.Domain.Models.Translations;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Messaging.Client.Shell;
|
using Wino.Messaging.Client.Shell;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
using WinUIEx;
|
using WinUIEx;
|
||||||
@@ -84,6 +85,7 @@ public abstract class WinoApplication : Application, IRecipient<LanguageChanged>
|
|||||||
yield return DatabaseService;
|
yield return DatabaseService;
|
||||||
yield return TranslationService;
|
yield return TranslationService;
|
||||||
yield return NewThemeService; // Initialize NewThemeService instead of old ThemeService
|
yield return NewThemeService; // Initialize NewThemeService instead of old ThemeService
|
||||||
|
yield return Services.GetService<SynchronizationManagerInitializer>();
|
||||||
// yield return ThemeService; // Keep old service for backward compatibility but don't initialize
|
// yield return ThemeService; // Keep old service for backward compatibility but don't initialize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public static class CoreContainerSetup
|
|||||||
|
|
||||||
services.AddSingleton(loggerLevelSwitcher);
|
services.AddSingleton(loggerLevelSwitcher);
|
||||||
services.AddSingleton<ISynchronizerFactory, SynchronizerFactory>();
|
services.AddSingleton<ISynchronizerFactory, SynchronizerFactory>();
|
||||||
|
services.AddSingleton<ISynchronizationManager>(provider => SynchronizationManager.Instance);
|
||||||
|
services.AddTransient<SynchronizationManagerInitializer>();
|
||||||
|
|
||||||
services.AddTransient<IGmailChangeProcessor, GmailChangeProcessor>();
|
services.AddTransient<IGmailChangeProcessor, GmailChangeProcessor>();
|
||||||
services.AddTransient<IImapChangeProcessor, ImapChangeProcessor>();
|
services.AddTransient<IImapChangeProcessor, ImapChangeProcessor>();
|
||||||
|
|||||||
@@ -0,0 +1,451 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Serilog;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Exceptions;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Authentication;
|
||||||
|
using Wino.Core.Domain.Models.Connectivity;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
|
namespace Wino.Core.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton manager that handles synchronizer instances and operations for all accounts.
|
||||||
|
/// Replaces the old WinoServerConnectionManager functionality.
|
||||||
|
/// </summary>
|
||||||
|
public class SynchronizationManager : ISynchronizationManager
|
||||||
|
{
|
||||||
|
private static readonly Lazy<SynchronizationManager> _instance = new(() => new SynchronizationManager());
|
||||||
|
public static SynchronizationManager Instance => _instance.Value;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Guid, IWinoSynchronizerBase> _synchronizerCache = new();
|
||||||
|
private readonly SemaphoreSlim _initializationSemaphore = new(1, 1);
|
||||||
|
private readonly ILogger _logger = Log.ForContext<SynchronizationManager>();
|
||||||
|
|
||||||
|
private ISynchronizerFactory _synchronizerFactory;
|
||||||
|
private SynchronizerFactory _concreteSynchronizerFactory;
|
||||||
|
private IImapTestService _imapTestService;
|
||||||
|
private IAccountService _accountService;
|
||||||
|
private IAuthenticationProvider _authenticationProvider;
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
|
private SynchronizationManager() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the SynchronizationManager with required dependencies.
|
||||||
|
/// This must be called before using any other methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="synchronizerFactory">Factory for creating synchronizers</param>
|
||||||
|
/// <param name="imapTestService">Service for testing IMAP connectivity</param>
|
||||||
|
/// <param name="accountService">Service for account operations</param>
|
||||||
|
/// <param name="authenticationProvider">Provider for OAuth authentication</param>
|
||||||
|
public async Task InitializeAsync(ISynchronizerFactory synchronizerFactory,
|
||||||
|
IImapTestService imapTestService,
|
||||||
|
IAccountService accountService,
|
||||||
|
IAuthenticationProvider authenticationProvider)
|
||||||
|
{
|
||||||
|
await _initializationSemaphore.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
_synchronizerFactory = synchronizerFactory ?? throw new ArgumentNullException(nameof(synchronizerFactory));
|
||||||
|
_concreteSynchronizerFactory = synchronizerFactory as SynchronizerFactory ?? throw new ArgumentException("SynchronizerFactory must be the concrete implementation");
|
||||||
|
_imapTestService = imapTestService ?? throw new ArgumentNullException(nameof(imapTestService));
|
||||||
|
_accountService = accountService ?? throw new ArgumentNullException(nameof(accountService));
|
||||||
|
_authenticationProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));
|
||||||
|
|
||||||
|
// Get all accounts and create synchronizers for them
|
||||||
|
var accounts = await _accountService.GetAccountsAsync();
|
||||||
|
|
||||||
|
foreach (var account in accounts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var synchronizer = _concreteSynchronizerFactory.CreateNewSynchronizer(account);
|
||||||
|
_synchronizerCache.TryAdd(account.Id, synchronizer);
|
||||||
|
|
||||||
|
_logger.Information("Created synchronizer for account {AccountName} ({AccountId})",
|
||||||
|
account.Name, account.Id);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})",
|
||||||
|
account.Name, account.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
_logger.Information("SynchronizationManager initialized with {Count} synchronizers", _synchronizerCache.Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_initializationSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests IMAP server connectivity for the given server information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverInformation">Server information to test</param>
|
||||||
|
/// <param name="allowSSLHandshake">Whether to allow SSL handshake</param>
|
||||||
|
/// <returns>Test results indicating success or failure with details</returns>
|
||||||
|
public async Task<ImapConnectivityTestResults> TestImapConnectivityAsync(CustomServerInformation serverInformation, bool allowSSLHandshake)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Information("Testing IMAP connectivity for {Server}:{Port}",
|
||||||
|
serverInformation.IncomingServer,
|
||||||
|
serverInformation.IncomingServerPort);
|
||||||
|
|
||||||
|
await _imapTestService.TestImapConnectionAsync(serverInformation, allowSSLHandshake);
|
||||||
|
|
||||||
|
_logger.Information("IMAP connectivity test successful");
|
||||||
|
return ImapConnectivityTestResults.Success();
|
||||||
|
}
|
||||||
|
catch (ImapTestSSLCertificateException sslTestException)
|
||||||
|
{
|
||||||
|
_logger.Warning("IMAP connectivity test requires SSL certificate confirmation");
|
||||||
|
return ImapConnectivityTestResults.CertificateUIRequired(
|
||||||
|
sslTestException.Issuer,
|
||||||
|
sslTestException.ExpirationDateString,
|
||||||
|
sslTestException.ValidFromDateString);
|
||||||
|
}
|
||||||
|
catch (ImapClientPoolException clientPoolException)
|
||||||
|
{
|
||||||
|
_logger.Error(clientPoolException, "IMAP connectivity test failed with protocol log");
|
||||||
|
return ImapConnectivityTestResults.Failure(clientPoolException, clientPoolException.ProtocolLog);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error(exception, "IMAP connectivity test failed");
|
||||||
|
return ImapConnectivityTestResults.Failure(exception, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a new mail synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">Mail synchronization options</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Synchronization result</returns>
|
||||||
|
public async Task<MailSynchronizationResult> SynchronizeMailAsync(MailSynchronizationOptions options,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var synchronizer = await GetOrCreateSynchronizerAsync(options.AccountId);
|
||||||
|
if (synchronizer == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Could not find or create synchronizer for account {AccountId}", options.AccountId);
|
||||||
|
return MailSynchronizationResult.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Information("Starting mail synchronization for account {AccountId} with type {SyncType}",
|
||||||
|
options.AccountId, options.Type);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await synchronizer.SynchronizeMailsAsync(options, cancellationToken);
|
||||||
|
|
||||||
|
_logger.Information("Mail synchronization completed for account {AccountId} with state {State}",
|
||||||
|
options.AccountId, result.CompletedState);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Mail synchronization failed for account {AccountId}", options.AccountId);
|
||||||
|
return MailSynchronizationResult.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if there is an ongoing synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID to check</param>
|
||||||
|
/// <returns>True if synchronization is ongoing, false otherwise</returns>
|
||||||
|
public bool IsAccountSynchronizing(Guid accountId)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
if (_synchronizerCache.TryGetValue(accountId, out var synchronizer))
|
||||||
|
{
|
||||||
|
return synchronizer.State == AccountSynchronizerState.Synchronizing ||
|
||||||
|
synchronizer.State == AccountSynchronizerState.ExecutingRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a mail action request to the corresponding account's synchronizer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">Request to queue</param>
|
||||||
|
/// <param name="accountId">Account ID to queue the request for</param>
|
||||||
|
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var synchronizer = await GetOrCreateSynchronizerAsync(accountId);
|
||||||
|
if (synchronizer == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Could not find or create synchronizer for account {AccountId} to queue request", accountId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Queuing request {RequestType} for account {AccountId}",
|
||||||
|
request.GetType().Name, accountId);
|
||||||
|
|
||||||
|
synchronizer.QueueRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles folder synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID to synchronize folders for</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Synchronization result</returns>
|
||||||
|
public async Task<MailSynchronizationResult> SynchronizeFoldersAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var options = new MailSynchronizationOptions
|
||||||
|
{
|
||||||
|
AccountId = accountId,
|
||||||
|
Type = MailSynchronizationType.FoldersOnly
|
||||||
|
};
|
||||||
|
|
||||||
|
return await SynchronizeMailAsync(options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles alias synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID to synchronize aliases for</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Synchronization result</returns>
|
||||||
|
public async Task<MailSynchronizationResult> SynchronizeAliasesAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var options = new MailSynchronizationOptions
|
||||||
|
{
|
||||||
|
AccountId = accountId,
|
||||||
|
Type = MailSynchronizationType.Alias
|
||||||
|
};
|
||||||
|
|
||||||
|
return await SynchronizeMailAsync(options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles profile synchronization for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID to synchronize profile for</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Synchronization result</returns>
|
||||||
|
public async Task<MailSynchronizationResult> SynchronizeProfileAsync(Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var options = new MailSynchronizationOptions
|
||||||
|
{
|
||||||
|
AccountId = accountId,
|
||||||
|
Type = MailSynchronizationType.UpdateProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
return await SynchronizeMailAsync(options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a MIME message for the given mail item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mailItem">Mail item to download</param>
|
||||||
|
/// <param name="accountId">Account ID that owns the mail item</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>Downloaded MIME content path</returns>
|
||||||
|
public async Task<string> DownloadMimeMessageAsync(MailCopy mailItem, Guid accountId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
var synchronizer = await GetOrCreateSynchronizerAsync(accountId);
|
||||||
|
if (synchronizer == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Could not find or create synchronizer for account {AccountId} to download MIME", accountId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Downloading MIME message for mail item {MailItemId}", mailItem.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await synchronizer.DownloadMissingMimeMessageAsync(mailItem, null, cancellationToken);
|
||||||
|
return mailItem.Id.ToString(); // Return some identifier, actual implementation might be different
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to download MIME message for mail item {MailItemId}", mailItem.Id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new synchronizer for a newly added account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="account">Account to create synchronizer for</param>
|
||||||
|
/// <returns>Created synchronizer</returns>
|
||||||
|
public Task<IWinoSynchronizerBase> CreateSynchronizerForAccountAsync(MailAccount account)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var synchronizer = _concreteSynchronizerFactory.CreateNewSynchronizer(account);
|
||||||
|
_synchronizerCache.TryAdd(account.Id, synchronizer);
|
||||||
|
|
||||||
|
_logger.Information("Created new synchronizer for account {AccountName} ({AccountId})",
|
||||||
|
account.Name, account.Id);
|
||||||
|
|
||||||
|
return Task.FromResult(synchronizer);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})",
|
||||||
|
account.Name, account.Id);
|
||||||
|
return Task.FromResult<IWinoSynchronizerBase>(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the synchronizer for the given account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID to destroy synchronizer for</param>
|
||||||
|
public async Task DestroySynchronizerAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
if (_synchronizerCache.TryRemove(accountId, out var synchronizer))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await synchronizer.KillSynchronizerAsync();
|
||||||
|
_logger.Information("Destroyed synchronizer for account {AccountId}", accountId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to destroy synchronizer for account {AccountId}", accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all cached synchronizers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Collection of all cached synchronizers</returns>
|
||||||
|
public IEnumerable<IWinoSynchronizerBase> GetAllSynchronizers()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _synchronizerCache.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a synchronizer for the given account ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account ID</param>
|
||||||
|
/// <returns>Synchronizer if found, null otherwise</returns>
|
||||||
|
public async Task<IWinoSynchronizerBase> GetSynchronizerAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return await GetOrCreateSynchronizerAsync(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IWinoSynchronizerBase> GetOrCreateSynchronizerAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
if (_synchronizerCache.TryGetValue(accountId, out var existingSynchronizer))
|
||||||
|
{
|
||||||
|
return existingSynchronizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create a new synchronizer if not found
|
||||||
|
var account = await _accountService.GetAccountAsync(accountId);
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
return await CreateSynchronizerForAccountAsync(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles OAuth authentication for the specified provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="providerType">The mail provider type to authenticate</param>
|
||||||
|
/// <param name="account">Optional account to authenticate (null for initial authentication)</param>
|
||||||
|
/// <param name="proposeCopyAuthorizationURL">Whether to propose copying auth URL for Gmail</param>
|
||||||
|
/// <returns>Token information containing access token and username</returns>
|
||||||
|
public async Task<TokenInformationEx> HandleAuthorizationAsync(MailProviderType providerType,
|
||||||
|
MailAccount account = null,
|
||||||
|
bool proposeCopyAuthorizationURL = false)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var authenticator = _authenticationProvider.GetAuthenticator(providerType);
|
||||||
|
|
||||||
|
// Some users are having issues with Gmail authentication.
|
||||||
|
// Their browsers may never launch to complete authentication.
|
||||||
|
// Offer to copy auth url for them to complete it manually.
|
||||||
|
// Redirection will occur to the app and the token will be saved.
|
||||||
|
if (proposeCopyAuthorizationURL && authenticator is IGmailAuthenticator gmailAuthenticator)
|
||||||
|
{
|
||||||
|
gmailAuthenticator.ProposeCopyAuthURL = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenInformationEx tokenInfo;
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
// Get token for existing account (may trigger interactive auth if token is expired)
|
||||||
|
tokenInfo = await authenticator.GetTokenInformationAsync(account);
|
||||||
|
_logger.Information("Retrieved token for existing account {AccountAddress}", account.Address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Initial authentication request - there is no account to get token for
|
||||||
|
// This will always trigger interactive authentication
|
||||||
|
tokenInfo = await authenticator.GenerateTokenInformationAsync(null);
|
||||||
|
_logger.Information("Generated new token for {ProviderType} authentication", providerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenInfo;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to handle authorization for {ProviderType}", providerType);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SynchronizationManager must be initialized before use. Call InitializeAsync first.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Services;
|
||||||
|
|
||||||
|
namespace Wino.Core.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service responsible for initializing the SynchronizationManager during app startup.
|
||||||
|
/// </summary>
|
||||||
|
public class SynchronizationManagerInitializer : IInitializeAsync
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public SynchronizationManagerInitializer(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
var synchronizerFactory = _serviceProvider.GetRequiredService<ISynchronizerFactory>();
|
||||||
|
var imapTestService = _serviceProvider.GetRequiredService<IImapTestService>();
|
||||||
|
var accountService = _serviceProvider.GetRequiredService<IAccountService>();
|
||||||
|
var authenticationProvider = _serviceProvider.GetRequiredService<IAuthenticationProvider>();
|
||||||
|
|
||||||
|
// Cast to concrete type to access CreateNewSynchronizer method
|
||||||
|
var concreteSynchronizerFactory = synchronizerFactory as SynchronizerFactory;
|
||||||
|
|
||||||
|
await SynchronizationManager.Instance.InitializeAsync(concreteSynchronizerFactory, imapTestService, accountService, authenticationProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,17 +19,14 @@ namespace Wino.Core.Services;
|
|||||||
public class WinoRequestDelegator : IWinoRequestDelegator
|
public class WinoRequestDelegator : IWinoRequestDelegator
|
||||||
{
|
{
|
||||||
private readonly IWinoRequestProcessor _winoRequestProcessor;
|
private readonly IWinoRequestProcessor _winoRequestProcessor;
|
||||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
|
|
||||||
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
|
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
|
||||||
IWinoServerConnectionManager winoServerConnectionManager,
|
|
||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
IMailDialogService dialogService)
|
IMailDialogService dialogService)
|
||||||
{
|
{
|
||||||
_winoRequestProcessor = winoRequestProcessor;
|
_winoRequestProcessor = winoRequestProcessor;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
_folderService = folderService;
|
_folderService = folderService;
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
}
|
}
|
||||||
@@ -138,14 +135,11 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
|||||||
|
|
||||||
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
{
|
{
|
||||||
await EnsureServerConnectedAsync();
|
await SynchronizationManager.Instance.QueueRequestAsync(request, accountId);
|
||||||
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task QueueSynchronizationAsync(Guid accountId)
|
private Task QueueSynchronizationAsync(Guid accountId)
|
||||||
{
|
{
|
||||||
await EnsureServerConnectedAsync();
|
|
||||||
|
|
||||||
var options = new MailSynchronizationOptions()
|
var options = new MailSynchronizationOptions()
|
||||||
{
|
{
|
||||||
AccountId = accountId,
|
AccountId = accountId,
|
||||||
@@ -153,12 +147,6 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
|||||||
};
|
};
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client));
|
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client));
|
||||||
}
|
return Task.CompletedTask;
|
||||||
|
|
||||||
private async Task EnsureServerConnectedAsync()
|
|
||||||
{
|
|
||||||
if (_winoServerConnectionManager.Status == WinoServerConnectionStatus.Connected) return;
|
|
||||||
|
|
||||||
await _winoServerConnectionManager.ConnectAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
using Wino.Messaging.Server;
|
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IWinoServerConnectionManager _serverConnectionManager;
|
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
private bool isLoaded = false;
|
private bool isLoaded = false;
|
||||||
|
|
||||||
@@ -50,12 +49,10 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
|||||||
|
|
||||||
public AccountDetailsPageViewModel(IMailDialogService dialogService,
|
public AccountDetailsPageViewModel(IMailDialogService dialogService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IWinoServerConnectionManager serverConnectionManager,
|
|
||||||
IFolderService folderService)
|
IFolderService folderService)
|
||||||
{
|
{
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_serverConnectionManager = serverConnectionManager;
|
|
||||||
_folderService = folderService;
|
_folderService = folderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,21 +92,16 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
var isSynchronizerKilledResponse = await _serverConnectionManager.GetResponseAsync<bool, KillAccountSynchronizerRequested>(new KillAccountSynchronizerRequested(Account.Id));
|
await SynchronizationManager.Instance.DestroySynchronizerAsync(Account.Id);
|
||||||
|
|
||||||
if (isSynchronizerKilledResponse.IsSuccess)
|
await _accountService.DeleteAccountAsync(Account);
|
||||||
{
|
|
||||||
await _accountService.DeleteAccountAsync(Account);
|
|
||||||
|
|
||||||
_dialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);
|
_dialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);
|
||||||
|
|
||||||
Messenger.Send(new BackBreadcrumNavigationRequested());
|
Messenger.Send(new BackBreadcrumNavigationRequested());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|||||||
@@ -12,15 +12,13 @@ using Wino.Core.Domain.Entities.Shared;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Authentication;
|
|
||||||
using Wino.Core.Domain.Models.Connectivity;
|
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
using Wino.Core.ViewModels.Data;
|
using Wino.Core.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
using Wino.Messaging.Server;
|
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
@@ -34,7 +32,6 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
public IMailDialogService MailDialogService { get; }
|
public IMailDialogService MailDialogService { get; }
|
||||||
|
|
||||||
public AccountManagementViewModel(IMailDialogService dialogService,
|
public AccountManagementViewModel(IMailDialogService dialogService,
|
||||||
IWinoServerConnectionManager winoServerConnectionManager,
|
|
||||||
INavigationService navigationService,
|
INavigationService navigationService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
||||||
@@ -43,7 +40,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
IStoreManagementService storeManagementService,
|
IStoreManagementService storeManagementService,
|
||||||
IWinoLogger winoLogger,
|
IWinoLogger winoLogger,
|
||||||
IAuthenticationProvider authenticationProvider,
|
IAuthenticationProvider authenticationProvider,
|
||||||
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
||||||
{
|
{
|
||||||
MailDialogService = dialogService;
|
MailDialogService = dialogService;
|
||||||
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
||||||
@@ -154,37 +151,36 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
createdAccount.Address = customServerInformation.Address;
|
createdAccount.Address = customServerInformation.Address;
|
||||||
|
|
||||||
// Let server validate the imap/smtp connection.
|
// Let server validate the imap/smtp connection.
|
||||||
var testResultResponse = await WinoServerConnectionManager.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(customServerInformation, true));
|
// TODO: Protocol log with detailed failure.
|
||||||
|
|
||||||
if (!testResultResponse.IsSuccess)
|
await _imapTestService.TestImapConnectionAsync(customServerInformation, true);
|
||||||
{
|
//var testResultResponse = await WinoServerConnectionManager.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(customServerInformation, true));
|
||||||
throw new Exception($"{Translator.IMAPSetupDialog_ConnectionFailedTitle}\n{testResultResponse.Message}");
|
|
||||||
}
|
//if (!testResultResponse.IsSuccess)
|
||||||
else if (!testResultResponse.Data.IsSuccess)
|
//{
|
||||||
{
|
// throw new Exception($"{Translator.IMAPSetupDialog_ConnectionFailedTitle}\n{testResultResponse.Message}");
|
||||||
// Server connectivity might succeed, but result might be failed.
|
//}
|
||||||
throw new ImapClientPoolException(testResultResponse.Data.FailedReason, customServerInformation, testResultResponse.Data.FailureProtocolLog);
|
//else if (!testResultResponse.Data.IsSuccess)
|
||||||
}
|
//{
|
||||||
|
// // Server connectivity might succeed, but result might be failed.
|
||||||
|
// throw new ImapClientPoolException(testResultResponse.Data.FailedReason, customServerInformation, testResultResponse.Data.FailureProtocolLog);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// OAuth authentication is handled here.
|
// OAuth authentication is handled here.
|
||||||
// Server authenticates, returns the token info here.
|
// Use SynchronizationManager to handle OAuth authentication.
|
||||||
|
|
||||||
var tokenInformationResponse = await WinoServerConnectionManager
|
var authTokenInfo = await SynchronizationManager.Instance.HandleAuthorizationAsync(
|
||||||
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
accountCreationDialogResult.ProviderType,
|
||||||
createdAccount,
|
createdAccount,
|
||||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
createdAccount.ProviderType == MailProviderType.Gmail);
|
||||||
|
|
||||||
if (creationDialog.State == AccountCreationDialogState.Canceled)
|
if (creationDialog.State == AccountCreationDialogState.Canceled)
|
||||||
throw new AccountSetupCanceledException();
|
throw new AccountSetupCanceledException();
|
||||||
|
|
||||||
if (!tokenInformationResponse.IsSuccess)
|
// Update account address with authenticated user information
|
||||||
throw new Exception(tokenInformationResponse.Message);
|
createdAccount.Address = authTokenInfo.AccountAddress;
|
||||||
|
|
||||||
createdAccount.Address = tokenInformationResponse.Data.AccountAddress;
|
|
||||||
|
|
||||||
tokenInformationResponse.ThrowIfFailed();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,22 +203,23 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
Type = MailSynchronizationType.UpdateProfile
|
Type = MailSynchronizationType.UpdateProfile
|
||||||
};
|
};
|
||||||
|
|
||||||
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
var profileSynchronizationResult = await SynchronizationManager.Instance.SynchronizeProfileAsync(createdAccount.Id);
|
||||||
|
|
||||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
|
||||||
|
|
||||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||||
|
|
||||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
if (profileSynchronizationResult.ProfileInformation != null)
|
||||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
|
|
||||||
{
|
{
|
||||||
createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
|
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
||||||
}
|
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
||||||
|
|
||||||
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
|
||||||
|
{
|
||||||
|
createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creationDialog is IImapAccountCreationDialog customServerAccountCreationDialog)
|
if (creationDialog is IImapAccountCreationDialog customServerAccountCreationDialog)
|
||||||
@@ -237,26 +234,16 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
Type = MailSynchronizationType.FoldersOnly
|
Type = MailSynchronizationType.FoldersOnly
|
||||||
};
|
};
|
||||||
|
|
||||||
var folderSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(folderSyncOptions, SynchronizationSource.Client));
|
var folderSynchronizationResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(createdAccount.Id);
|
||||||
|
|
||||||
var folderSynchronizationResult = folderSynchronizationResponse.Data;
|
|
||||||
|
|
||||||
if (folderSynchronizationResult == null || folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
if (folderSynchronizationResult == null || folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||||
throw new Exception($"{Translator.Exception_FailedToSynchronizeFolders}\n{folderSynchronizationResponse.Message}");
|
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||||
|
|
||||||
// Sync aliases if supported.
|
// Sync aliases if supported.
|
||||||
if (createdAccount.IsAliasSyncSupported)
|
if (createdAccount.IsAliasSyncSupported)
|
||||||
{
|
{
|
||||||
// Try to synchronize aliases for the account.
|
// Try to synchronize aliases for the account.
|
||||||
|
var aliasSynchronizationResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(createdAccount.Id);
|
||||||
var aliasSyncOptions = new MailSynchronizationOptions()
|
|
||||||
{
|
|
||||||
AccountId = createdAccount.Id,
|
|
||||||
Type = MailSynchronizationType.Alias
|
|
||||||
};
|
|
||||||
|
|
||||||
var aliasSyncResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
|
|
||||||
var aliasSynchronizationResult = folderSynchronizationResponse.Data;
|
|
||||||
|
|
||||||
if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||||
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
|
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Core.Services;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
|
[NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
|
||||||
@@ -32,12 +31,10 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel
|
|||||||
public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false;
|
public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false;
|
||||||
|
|
||||||
public AliasManagementPageViewModel(IMailDialogService dialogService,
|
public AliasManagementPageViewModel(IMailDialogService dialogService,
|
||||||
IAccountService accountService,
|
IAccountService accountService)
|
||||||
IWinoServerConnectionManager winoServerConnectionManager)
|
|
||||||
{
|
{
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
@@ -82,12 +79,12 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel
|
|||||||
Type = MailSynchronizationType.Alias
|
Type = MailSynchronizationType.Alias
|
||||||
};
|
};
|
||||||
|
|
||||||
var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
|
var aliasSyncResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(Account.Id);
|
||||||
|
|
||||||
if (aliasSyncResponse.IsSuccess)
|
if (aliasSyncResult.CompletedState == SynchronizationCompletedState.Success)
|
||||||
await LoadAliasesAsync();
|
await LoadAliasesAsync();
|
||||||
else
|
else
|
||||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, "Failed to synchronize aliases", InfoBarMessageType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ using Wino.Core.Domain.Entities.Mail;
|
|||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Services;
|
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
@@ -102,7 +102,6 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
|||||||
private readonly IWinoRequestDelegator _worker;
|
private readonly IWinoRequestDelegator _worker;
|
||||||
public readonly IFontService FontService;
|
public readonly IFontService FontService;
|
||||||
public readonly IPreferencesService PreferencesService;
|
public readonly IPreferencesService PreferencesService;
|
||||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
|
||||||
public readonly IContactService ContactService;
|
public readonly IContactService ContactService;
|
||||||
|
|
||||||
public ComposePageViewModel(IMailDialogService dialogService,
|
public ComposePageViewModel(IMailDialogService dialogService,
|
||||||
@@ -115,8 +114,7 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
|||||||
IWinoRequestDelegator worker,
|
IWinoRequestDelegator worker,
|
||||||
IContactService contactService,
|
IContactService contactService,
|
||||||
IFontService fontService,
|
IFontService fontService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService)
|
||||||
IWinoServerConnectionManager winoServerConnectionManager)
|
|
||||||
{
|
{
|
||||||
NativeAppService = nativeAppService;
|
NativeAppService = nativeAppService;
|
||||||
ContactService = contactService;
|
ContactService = contactService;
|
||||||
@@ -130,7 +128,6 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
|||||||
_fileService = fileService;
|
_fileService = fileService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -412,13 +409,12 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
downloadIfNeeded = false;
|
downloadIfNeeded = false;
|
||||||
|
|
||||||
var package = new DownloadMissingMessageRequested(CurrentMailDraftItem.AssignedAccount.Id, CurrentMailDraftItem.MailCopy);
|
// Download missing MIME message using SynchronizationManager
|
||||||
var downloadResponse = await _winoServerConnectionManager.GetResponseAsync<bool, DownloadMissingMessageRequested>(package);
|
await SynchronizationManager.Instance.DownloadMimeMessageAsync(
|
||||||
|
CurrentMailDraftItem.MailCopy,
|
||||||
|
CurrentMailDraftItem.AssignedAccount.Id);
|
||||||
|
|
||||||
if (downloadResponse.IsSuccess)
|
goto retry;
|
||||||
{
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
_dialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error);
|
_dialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using Wino.Core.Domain.Models.MailItem;
|
|||||||
using Wino.Core.Domain.Models.Menus;
|
using Wino.Core.Domain.Models.Menus;
|
||||||
using Wino.Core.Domain.Models.Reader;
|
using Wino.Core.Domain.Models.Reader;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Collections;
|
using Wino.Mail.ViewModels.Collections;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Messages;
|
||||||
@@ -81,7 +82,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||||
private readonly IKeyPressService _keyPressService;
|
private readonly IKeyPressService _keyPressService;
|
||||||
private readonly IWinoLogger _winoLogger;
|
private readonly IWinoLogger _winoLogger;
|
||||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
|
||||||
private MailItemViewModel _activeMailItem;
|
private MailItemViewModel _activeMailItem;
|
||||||
|
|
||||||
public List<SortingOption> SortingOptions { get; } =
|
public List<SortingOption> SortingOptions { get; } =
|
||||||
@@ -160,14 +160,12 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
IKeyPressService keyPressService,
|
IKeyPressService keyPressService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
INewThemeService themeService,
|
INewThemeService themeService,
|
||||||
IWinoLogger winoLogger,
|
IWinoLogger winoLogger)
|
||||||
IWinoServerConnectionManager winoServerConnectionManager)
|
|
||||||
{
|
{
|
||||||
MailCollection = new WinoMailCollection(threadingStrategyProvider);
|
MailCollection = new WinoMailCollection(threadingStrategyProvider);
|
||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
ThemeService = themeService;
|
ThemeService = themeService;
|
||||||
_winoLogger = winoLogger;
|
_winoLogger = winoLogger;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
StatePersistenceService = statePersistenceService;
|
StatePersistenceService = statePersistenceService;
|
||||||
NavigationService = navigationService;
|
NavigationService = navigationService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
@@ -841,51 +839,52 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
// Perform online search.
|
// Perform online search.
|
||||||
if (isDoingOnlineSearch)
|
if (isDoingOnlineSearch)
|
||||||
{
|
{
|
||||||
WinoServerResponse<OnlineSearchResult> onlineSearchResult = null;
|
// TODO: Burak: Handle online search.
|
||||||
string onlineSearchFailedMessage = null;
|
//WinoServerResponse<OnlineSearchResult> onlineSearchResult = null;
|
||||||
|
//string onlineSearchFailedMessage = null;
|
||||||
|
|
||||||
try
|
//try
|
||||||
{
|
//{
|
||||||
var accountIds = ActiveFolder.HandlingFolders.Select(a => a.MailAccountId).ToList();
|
// var accountIds = ActiveFolder.HandlingFolders.Select(a => a.MailAccountId).ToList();
|
||||||
var folders = ActiveFolder.HandlingFolders.ToList();
|
// var folders = ActiveFolder.HandlingFolders.ToList();
|
||||||
var searchRequest = new OnlineSearchRequested(accountIds, SearchQuery, folders);
|
// var searchRequest = new OnlineSearchRequested(accountIds, SearchQuery, folders);
|
||||||
|
|
||||||
onlineSearchResult = await _winoServerConnectionManager.GetResponseAsync<OnlineSearchResult, OnlineSearchRequested>(searchRequest, cancellationToken);
|
// onlineSearchResult = await _winoServerConnectionManager.GetResponseAsync<OnlineSearchResult, OnlineSearchRequested>(searchRequest, cancellationToken);
|
||||||
|
|
||||||
if (onlineSearchResult.IsSuccess)
|
// if (onlineSearchResult.IsSuccess)
|
||||||
{
|
// {
|
||||||
await ExecuteUIThread(() => { AreSearchResultsOnline = true; });
|
// await ExecuteUIThread(() => { AreSearchResultsOnline = true; });
|
||||||
|
|
||||||
onlineSearchItems = onlineSearchResult.Data.SearchResult;
|
// onlineSearchItems = onlineSearchResult.Data.SearchResult;
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
onlineSearchFailedMessage = onlineSearchResult.Message;
|
// onlineSearchFailedMessage = onlineSearchResult.Message;
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
catch (OperationCanceledException)
|
//catch (OperationCanceledException)
|
||||||
{
|
//{
|
||||||
throw;
|
// throw;
|
||||||
}
|
//}
|
||||||
catch (Exception ex)
|
//catch (Exception ex)
|
||||||
{
|
//{
|
||||||
Log.Warning(ex, "Failed to perform online search.");
|
// Log.Warning(ex, "Failed to perform online search.");
|
||||||
onlineSearchFailedMessage = ex.Message;
|
// onlineSearchFailedMessage = ex.Message;
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (onlineSearchResult != null && !onlineSearchResult.IsSuccess)
|
//if (onlineSearchResult != null && !onlineSearchResult.IsSuccess)
|
||||||
{
|
//{
|
||||||
// Query or server error.
|
// // Query or server error.
|
||||||
var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchResult.Message);
|
// var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchResult.Message);
|
||||||
_mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
// _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
||||||
|
|
||||||
}
|
//}
|
||||||
else if (!string.IsNullOrEmpty(onlineSearchFailedMessage))
|
//else if (!string.IsNullOrEmpty(onlineSearchFailedMessage))
|
||||||
{
|
//{
|
||||||
// Fatal error.
|
// // Fatal error.
|
||||||
var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchFailedMessage);
|
// var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchFailedMessage);
|
||||||
_mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
// _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1110,9 +1109,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
foreach (var accountId in accountIds)
|
foreach (var accountId in accountIds)
|
||||||
{
|
{
|
||||||
var serverResponse = await _winoServerConnectionManager.GetResponseAsync<bool, SynchronizationExistenceCheckRequest>(new SynchronizationExistenceCheckRequest(accountId));
|
if (SynchronizationManager.Instance.IsAccountSynchronizing(accountId))
|
||||||
|
|
||||||
if (serverResponse.IsSuccess && serverResponse.Data == true)
|
|
||||||
{
|
{
|
||||||
isAnyAccountSynchronizing = true;
|
isAnyAccountSynchronizing = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using Wino.Core.Domain.Models.MailItem;
|
|||||||
using Wino.Core.Domain.Models.Menus;
|
using Wino.Core.Domain.Models.Menus;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Reader;
|
using Wino.Core.Domain.Models.Reader;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Messages;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
@@ -46,7 +47,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IUnsubscriptionService _unsubscriptionService;
|
private readonly IUnsubscriptionService _unsubscriptionService;
|
||||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
|
||||||
private bool forceImageLoading = false;
|
private bool forceImageLoading = false;
|
||||||
|
|
||||||
private MailItemViewModel initializedMailItemViewModel = null;
|
private MailItemViewModel initializedMailItemViewModel = null;
|
||||||
@@ -142,8 +142,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
IUnsubscriptionService unsubscriptionService,
|
IUnsubscriptionService unsubscriptionService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
IPrintService printService,
|
IPrintService printService,
|
||||||
IApplicationConfiguration applicationConfiguration,
|
IApplicationConfiguration applicationConfiguration)
|
||||||
IWinoServerConnectionManager winoServerConnectionManager)
|
|
||||||
{
|
{
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
NativeAppService = nativeAppService;
|
NativeAppService = nativeAppService;
|
||||||
@@ -152,7 +151,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
PrintService = printService;
|
PrintService = printService;
|
||||||
_applicationConfiguration = applicationConfiguration;
|
_applicationConfiguration = applicationConfiguration;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
_clipboardService = clipboardService;
|
_clipboardService = clipboardService;
|
||||||
_unsubscriptionService = unsubscriptionService;
|
_unsubscriptionService = unsubscriptionService;
|
||||||
_underlyingThemeService = underlyingThemeService;
|
_underlyingThemeService = underlyingThemeService;
|
||||||
@@ -355,8 +353,10 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// To show the progress on the UI.
|
// To show the progress on the UI.
|
||||||
CurrentDownloadPercentage = 1;
|
CurrentDownloadPercentage = 1;
|
||||||
|
|
||||||
var package = new DownloadMissingMessageRequested(mailItemViewModel.AssignedAccount.Id, mailItemViewModel.MailCopy);
|
// Download missing MIME message using SynchronizationManager
|
||||||
await _winoServerConnectionManager.GetResponseAsync<bool, DownloadMissingMessageRequested>(package);
|
await SynchronizationManager.Instance.DownloadMimeMessageAsync(
|
||||||
|
mailItemViewModel.MailCopy,
|
||||||
|
mailItemViewModel.AssignedAccount.Id);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||||
using Wino.Core.Domain.Models.Connectivity;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.WinUI;
|
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.Server;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Wino.Views.ImapSetup;
|
namespace Wino.Views.ImapSetup;
|
||||||
|
|
||||||
public sealed partial class TestingImapConnectionPage : Page
|
public sealed partial class TestingImapConnectionPage : Page
|
||||||
{
|
{
|
||||||
private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService<IWinoServerConnectionManager>();
|
|
||||||
private AutoDiscoverySettings autoDiscoverySettings;
|
private AutoDiscoverySettings autoDiscoverySettings;
|
||||||
private CustomServerInformation serverInformationToTest;
|
private CustomServerInformation serverInformationToTest;
|
||||||
|
|
||||||
@@ -67,46 +62,34 @@ public sealed partial class TestingImapConnectionPage : Page
|
|||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
var testResultResponse = await _winoServerConnectionManager
|
var testResultData = await SynchronizationManager.Instance.TestImapConnectivityAsync(serverInformationToTest, allowSSLHandshake);
|
||||||
.GetResponseAsync<ImapConnectivityTestResults, ImapConnectivityTestRequested>(new ImapConnectivityTestRequested(serverInformationToTest, allowSSLHandshake));
|
|
||||||
|
|
||||||
if (!testResultResponse.IsSuccess)
|
if (testResultData.IsSuccess)
|
||||||
{
|
{
|
||||||
// Wino Server is connection is failed.
|
// All success. Finish setup with validated server information.
|
||||||
ReturnWithError(testResultResponse.Message);
|
ReturnWithSuccess();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var testResultData = testResultResponse.Data;
|
// Check if certificate UI is required.
|
||||||
|
if (testResultData.IsCertificateUIRequired)
|
||||||
if (testResultData.IsSuccess)
|
|
||||||
{
|
{
|
||||||
// All success. Finish setup with validated server information.
|
// Certificate UI is required. Show certificate dialog.
|
||||||
ReturnWithSuccess();
|
|
||||||
|
CertIssuer.Text = testResultData.CertificateIssuer;
|
||||||
|
CertValidFrom.Text = testResultData.CertificateValidFromDateString;
|
||||||
|
CertValidTo.Text = testResultData.CertificateExpirationDateString;
|
||||||
|
|
||||||
|
TestingConnectionPanel.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
||||||
|
CertificateDialog.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check if certificate UI is required.
|
// Connection test failed. Show error dialog.
|
||||||
|
|
||||||
if (testResultData.IsCertificateUIRequired)
|
var protocolLog = testResultData.FailureProtocolLog;
|
||||||
{
|
|
||||||
// Certificate UI is required. Show certificate dialog.
|
|
||||||
|
|
||||||
CertIssuer.Text = testResultData.CertificateIssuer;
|
ReturnWithError(testResultData.FailedReason, protocolLog);
|
||||||
CertValidFrom.Text = testResultData.CertificateValidFromDateString;
|
|
||||||
CertValidTo.Text = testResultData.CertificateExpirationDateString;
|
|
||||||
|
|
||||||
TestingConnectionPanel.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
|
||||||
CertificateDialog.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Connection test failed. Show error dialog.
|
|
||||||
|
|
||||||
var protocolLog = testResultData.FailureProtocolLog;
|
|
||||||
|
|
||||||
ReturnWithError(testResultData.FailedReason, protocolLog);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user