diff --git a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs new file mode 100644 index 00000000..c1ee65c6 --- /dev/null +++ b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs @@ -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; + +/// +/// Interface for the singleton synchronization manager that handles synchronizer instances and operations. +/// +public interface ISynchronizationManager +{ + /// + /// Initializes the SynchronizationManager with required dependencies. + /// + Task InitializeAsync(ISynchronizerFactory synchronizerFactory, + IImapTestService imapTestService, + IAccountService accountService, + IAuthenticationProvider authenticationProvider); + + /// + /// Tests IMAP server connectivity for the given server information. + /// + Task TestImapConnectivityAsync(CustomServerInformation serverInformation, bool allowSSLHandshake); + + /// + /// Starts a new mail synchronization for the given account. + /// + Task SynchronizeMailAsync(MailSynchronizationOptions options, + CancellationToken cancellationToken = default); + + /// + /// Checks if there is an ongoing synchronization for the given account. + /// + bool IsAccountSynchronizing(Guid accountId); + + /// + /// Queues a mail action request to the corresponding account's synchronizer. + /// + Task QueueRequestAsync(IRequestBase request, Guid accountId); + + /// + /// Handles folder synchronization for the given account. + /// + Task SynchronizeFoldersAsync(Guid accountId, + CancellationToken cancellationToken = default); + + /// + /// Handles alias synchronization for the given account. + /// + Task SynchronizeAliasesAsync(Guid accountId, + CancellationToken cancellationToken = default); + + /// + /// Handles profile synchronization for the given account. + /// + Task SynchronizeProfileAsync(Guid accountId, + CancellationToken cancellationToken = default); + + /// + /// Downloads a MIME message for the given mail item. + /// + Task DownloadMimeMessageAsync(MailCopy mailItem, Guid accountId, + CancellationToken cancellationToken = default); + + /// + /// Creates a new synchronizer for a newly added account. + /// + Task CreateSynchronizerForAccountAsync(MailAccount account); + + /// + /// Destroys the synchronizer for the given account. + /// + Task DestroySynchronizerAsync(Guid accountId); + + /// + /// Gets all cached synchronizers. + /// + IEnumerable GetAllSynchronizers(); + + /// + /// Gets a synchronizer for the given account ID. + /// + Task GetSynchronizerAsync(Guid accountId); + + /// + /// Handles OAuth authentication for the specified provider. + /// + Task HandleAuthorizationAsync(MailProviderType providerType, + MailAccount account = null, + bool proposeCopyAuthorizationURL = false); +} \ No newline at end of file diff --git a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs b/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs deleted file mode 100644 index 7fc5c4fa..00000000 --- a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Wino.Core.Domain.Interfaces; - -/// -/// Simple wrapper class to maintain compatibility with the original WinoServerResponse structure. -/// -/// Type of the expected response. -public class WinoServerResponse -{ - public bool IsSuccess { get; set; } - public string Message { get; set; } - public T Data { get; set; } - - public static WinoServerResponse CreateSuccessResponse(T data) - { - return new WinoServerResponse - { - IsSuccess = true, - Data = data - }; - } - - public static WinoServerResponse CreateErrorResponse(string message) - { - return new WinoServerResponse - { - IsSuccess = false, - Message = message - }; - } - - public void ThrowIfFailed() - { - if (!IsSuccess) - throw new InvalidOperationException(Message); - } -} - -/// -/// Connection status enum to maintain compatibility. -/// -public enum WinoServerConnectionStatus -{ - None, - Connecting, - Connected, - Disconnected, - Failed -} - -public interface IWinoServerConnectionManager -{ - /// - /// When the connection status changes, this event will be triggered. - /// - event EventHandler StatusChanged; - - /// - /// Gets the connection status. - /// - WinoServerConnectionStatus Status { get; } - - /// - /// 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. - /// - /// Whether connection is established or not. - Task ConnectAsync(); - - /// - /// Queues a new user request to be processed by Wino Server. - /// Healthy connection must present before calling this method. - /// - /// Request to queue for synchronizer in the server. - /// Account id to queueu request for. - Task QueueRequestAsync(IRequestBase request, Guid accountId); - - /// - /// Returns response from server for the given request. - /// - /// Response type. - /// Request type. - /// Request type. - /// Response received from the server for the given TResponse type. - Task> GetResponseAsync(TRequestType clientMessage, CancellationToken cancellationToken = default) where TRequestType : IClientMessage; - - /// - /// 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. - /// - TaskCompletionSource ConnectingHandle { get; } -} - -public interface IWinoServerConnectionManager : IWinoServerConnectionManager, IInitializeAsync -{ - /// - /// Existing connection handle to the server of TAppServiceConnection type. - /// - TAppServiceConnection Connection { get; set; } -} diff --git a/Wino.Core.ViewModels/AccountManagementPageViewModelBase.cs b/Wino.Core.ViewModels/AccountManagementPageViewModelBase.cs index cb282865..03184d43 100644 --- a/Wino.Core.ViewModels/AccountManagementPageViewModelBase.cs +++ b/Wino.Core.ViewModels/AccountManagementPageViewModelBase.cs @@ -40,7 +40,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM public int FREE_ACCOUNT_COUNT { get; } = 3; protected IDialogServiceBase DialogService { get; } - protected IWinoServerConnectionManager WinoServerConnectionManager { get; } protected INavigationService NavigationService { get; } protected IAccountService AccountService { get; } protected IProviderService ProviderService { get; } @@ -49,7 +48,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM protected IPreferencesService PreferencesService { get; } public AccountManagementPageViewModelBase(IDialogServiceBase dialogService, - IWinoServerConnectionManager winoServerConnectionManager, INavigationService navigationService, IAccountService accountService, IProviderService providerService, @@ -58,7 +56,6 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM IPreferencesService preferencesService) { DialogService = dialogService; - WinoServerConnectionManager = winoServerConnectionManager; NavigationService = navigationService; AccountService = accountService; ProviderService = providerService; diff --git a/Wino.Core.WinUI/CoreUWPContainerSetup.cs b/Wino.Core.WinUI/CoreUWPContainerSetup.cs index 95d74bc0..02f4aeac 100644 --- a/Wino.Core.WinUI/CoreUWPContainerSetup.cs +++ b/Wino.Core.WinUI/CoreUWPContainerSetup.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; -using Windows.ApplicationModel.AppService; using Wino.Core.Domain.Interfaces; using Wino.Core.ViewModels; using Wino.Core.WinUI.Services; @@ -12,10 +11,6 @@ public static class CoreUWPContainerSetup { public static void RegisterCoreUWPServices(this IServiceCollection services) { - var serverConnectionManager = new EmptyWinoServerConnectionManager(); - - services.AddSingleton(serverConnectionManager); - services.AddSingleton>(serverConnectionManager); services.AddSingleton, ApplicationResourceManager>(); services.AddSingleton(); diff --git a/Wino.Core.WinUI/Services/EmptyWinoServerConnectionManager.cs b/Wino.Core.WinUI/Services/EmptyWinoServerConnectionManager.cs deleted file mode 100644 index a6b0da50..00000000 --- a/Wino.Core.WinUI/Services/EmptyWinoServerConnectionManager.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Wino.Core.Domain.Interfaces; - -namespace Wino.Core.WinUI.Services; - -/// -/// Empty implementation of IWinoServerConnectionManager that returns default values. -/// This replaces the old AppServiceConnection-based implementation. -/// -public class EmptyWinoServerConnectionManager : IWinoServerConnectionManager -{ - public event EventHandler StatusChanged { add { } remove { } } - - public WinoServerConnectionStatus Status => WinoServerConnectionStatus.Connected; - - public TaskCompletionSource ConnectingHandle { get; } = new TaskCompletionSource(); - - public EmptyWinoServerConnectionManager() - { - ConnectingHandle.SetResult(true); - } - - public Task ConnectAsync() - { - return Task.FromResult(true); - } - - public Task QueueRequestAsync(IRequestBase request, Guid accountId) - { - return Task.CompletedTask; - } - - public Task> GetResponseAsync(TRequestType clientMessage, CancellationToken cancellationToken = default) - where TRequestType : IClientMessage - { - var response = WinoServerResponse.CreateSuccessResponse(default(TResponse)); - return Task.FromResult(response); - } -} - -/// -/// Generic empty implementation for typed connection managers. -/// -/// The connection type (not used in this implementation) -public class EmptyWinoServerConnectionManager : EmptyWinoServerConnectionManager, IWinoServerConnectionManager -{ - public TAppServiceConnection Connection { get; set; } - - public Task InitializeAsync() - { - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Wino.Core.WinUI/WinoApplication.cs b/Wino.Core.WinUI/WinoApplication.cs index d1dd2f2c..efbb297d 100644 --- a/Wino.Core.WinUI/WinoApplication.cs +++ b/Wino.Core.WinUI/WinoApplication.cs @@ -18,6 +18,7 @@ using Windows.Storage; using Wino.Core.Domain; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Translations; +using Wino.Core.Services; using Wino.Messaging.Client.Shell; using Wino.Services; using WinUIEx; @@ -84,6 +85,7 @@ public abstract class WinoApplication : Application, IRecipient yield return DatabaseService; yield return TranslationService; yield return NewThemeService; // Initialize NewThemeService instead of old ThemeService + yield return Services.GetService(); // yield return ThemeService; // Keep old service for backward compatibility but don't initialize } diff --git a/Wino.Core/CoreContainerSetup.cs b/Wino.Core/CoreContainerSetup.cs index 1853c4c9..2645d3de 100644 --- a/Wino.Core/CoreContainerSetup.cs +++ b/Wino.Core/CoreContainerSetup.cs @@ -17,6 +17,8 @@ public static class CoreContainerSetup services.AddSingleton(loggerLevelSwitcher); services.AddSingleton(); + services.AddSingleton(provider => SynchronizationManager.Instance); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Wino.Core/Services/SynchronizationManager.cs b/Wino.Core/Services/SynchronizationManager.cs new file mode 100644 index 00000000..2f8f453e --- /dev/null +++ b/Wino.Core/Services/SynchronizationManager.cs @@ -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; + +/// +/// Singleton manager that handles synchronizer instances and operations for all accounts. +/// Replaces the old WinoServerConnectionManager functionality. +/// +public class SynchronizationManager : ISynchronizationManager +{ + private static readonly Lazy _instance = new(() => new SynchronizationManager()); + public static SynchronizationManager Instance => _instance.Value; + + private readonly ConcurrentDictionary _synchronizerCache = new(); + private readonly SemaphoreSlim _initializationSemaphore = new(1, 1); + private readonly ILogger _logger = Log.ForContext(); + + private ISynchronizerFactory _synchronizerFactory; + private SynchronizerFactory _concreteSynchronizerFactory; + private IImapTestService _imapTestService; + private IAccountService _accountService; + private IAuthenticationProvider _authenticationProvider; + private bool _isInitialized = false; + + private SynchronizationManager() { } + + /// + /// Initializes the SynchronizationManager with required dependencies. + /// This must be called before using any other methods. + /// + /// Factory for creating synchronizers + /// Service for testing IMAP connectivity + /// Service for account operations + /// Provider for OAuth authentication + 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(); + } + } + + /// + /// Tests IMAP server connectivity for the given server information. + /// + /// Server information to test + /// Whether to allow SSL handshake + /// Test results indicating success or failure with details + public async Task 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); + } + } + + /// + /// Starts a new mail synchronization for the given account. + /// + /// Mail synchronization options + /// Cancellation token + /// Synchronization result + public async Task 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; + } + } + + /// + /// Checks if there is an ongoing synchronization for the given account. + /// + /// Account ID to check + /// True if synchronization is ongoing, false otherwise + public bool IsAccountSynchronizing(Guid accountId) + { + EnsureInitialized(); + + if (_synchronizerCache.TryGetValue(accountId, out var synchronizer)) + { + return synchronizer.State == AccountSynchronizerState.Synchronizing || + synchronizer.State == AccountSynchronizerState.ExecutingRequests; + } + + return false; + } + + /// + /// Queues a mail action request to the corresponding account's synchronizer. + /// + /// Request to queue + /// Account ID to queue the request for + 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); + } + + /// + /// Handles folder synchronization for the given account. + /// + /// Account ID to synchronize folders for + /// Cancellation token + /// Synchronization result + public async Task SynchronizeFoldersAsync(Guid accountId, + CancellationToken cancellationToken = default) + { + EnsureInitialized(); + + var options = new MailSynchronizationOptions + { + AccountId = accountId, + Type = MailSynchronizationType.FoldersOnly + }; + + return await SynchronizeMailAsync(options, cancellationToken); + } + + /// + /// Handles alias synchronization for the given account. + /// + /// Account ID to synchronize aliases for + /// Cancellation token + /// Synchronization result + public async Task SynchronizeAliasesAsync(Guid accountId, + CancellationToken cancellationToken = default) + { + EnsureInitialized(); + + var options = new MailSynchronizationOptions + { + AccountId = accountId, + Type = MailSynchronizationType.Alias + }; + + return await SynchronizeMailAsync(options, cancellationToken); + } + + /// + /// Handles profile synchronization for the given account. + /// + /// Account ID to synchronize profile for + /// Cancellation token + /// Synchronization result + public async Task SynchronizeProfileAsync(Guid accountId, + CancellationToken cancellationToken = default) + { + EnsureInitialized(); + + var options = new MailSynchronizationOptions + { + AccountId = accountId, + Type = MailSynchronizationType.UpdateProfile + }; + + return await SynchronizeMailAsync(options, cancellationToken); + } + + /// + /// Downloads a MIME message for the given mail item. + /// + /// Mail item to download + /// Account ID that owns the mail item + /// Cancellation token + /// Downloaded MIME content path + public async Task 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; + } + } + + /// + /// Creates a new synchronizer for a newly added account. + /// + /// Account to create synchronizer for + /// Created synchronizer + public Task 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(null); + } + } + + /// + /// Destroys the synchronizer for the given account. + /// + /// Account ID to destroy synchronizer for + 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); + } + } + } + + /// + /// Gets all cached synchronizers. + /// + /// Collection of all cached synchronizers + public IEnumerable GetAllSynchronizers() + { + EnsureInitialized(); + return _synchronizerCache.Values.ToList(); + } + + /// + /// Gets a synchronizer for the given account ID. + /// + /// Account ID + /// Synchronizer if found, null otherwise + public async Task GetSynchronizerAsync(Guid accountId) + { + EnsureInitialized(); + return await GetOrCreateSynchronizerAsync(accountId); + } + + private async Task 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; + } + + /// + /// Handles OAuth authentication for the specified provider. + /// + /// The mail provider type to authenticate + /// Optional account to authenticate (null for initial authentication) + /// Whether to propose copying auth URL for Gmail + /// Token information containing access token and username + public async Task 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."); + } + } +} \ No newline at end of file diff --git a/Wino.Core/Services/SynchronizationManagerInitializer.cs b/Wino.Core/Services/SynchronizationManagerInitializer.cs new file mode 100644 index 00000000..ddae076f --- /dev/null +++ b/Wino.Core/Services/SynchronizationManagerInitializer.cs @@ -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; + +/// +/// Service responsible for initializing the SynchronizationManager during app startup. +/// +public class SynchronizationManagerInitializer : IInitializeAsync +{ + private readonly IServiceProvider _serviceProvider; + + public SynchronizationManagerInitializer(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task InitializeAsync() + { + var synchronizerFactory = _serviceProvider.GetRequiredService(); + var imapTestService = _serviceProvider.GetRequiredService(); + var accountService = _serviceProvider.GetRequiredService(); + var authenticationProvider = _serviceProvider.GetRequiredService(); + + // Cast to concrete type to access CreateNewSynchronizer method + var concreteSynchronizerFactory = synchronizerFactory as SynchronizerFactory; + + await SynchronizationManager.Instance.InitializeAsync(concreteSynchronizerFactory, imapTestService, accountService, authenticationProvider); + } +} \ No newline at end of file diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index e57c2b0f..7686a9c9 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -19,17 +19,14 @@ namespace Wino.Core.Services; public class WinoRequestDelegator : IWinoRequestDelegator { private readonly IWinoRequestProcessor _winoRequestProcessor; - private readonly IWinoServerConnectionManager _winoServerConnectionManager; private readonly IFolderService _folderService; private readonly IMailDialogService _dialogService; public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor, - IWinoServerConnectionManager winoServerConnectionManager, IFolderService folderService, IMailDialogService dialogService) { _winoRequestProcessor = winoRequestProcessor; - _winoServerConnectionManager = winoServerConnectionManager; _folderService = folderService; _dialogService = dialogService; } @@ -138,14 +135,11 @@ public class WinoRequestDelegator : IWinoRequestDelegator private async Task QueueRequestAsync(IRequestBase request, Guid accountId) { - await EnsureServerConnectedAsync(); - await _winoServerConnectionManager.QueueRequestAsync(request, accountId); + await SynchronizationManager.Instance.QueueRequestAsync(request, accountId); } - private async Task QueueSynchronizationAsync(Guid accountId) + private Task QueueSynchronizationAsync(Guid accountId) { - await EnsureServerConnectedAsync(); - var options = new MailSynchronizationOptions() { AccountId = accountId, @@ -153,12 +147,6 @@ public class WinoRequestDelegator : IWinoRequestDelegator }; WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client)); - } - - private async Task EnsureServerConnectedAsync() - { - if (_winoServerConnectionManager.Status == WinoServerConnectionStatus.Connected) return; - - await _winoServerConnectionManager.ConnectAsync(); + return Task.CompletedTask; } } diff --git a/Wino.Mail.ViewModels/AccountDetailsPageViewModel.cs b/Wino.Mail.ViewModels/AccountDetailsPageViewModel.cs index 494d8813..073b9204 100644 --- a/Wino.Mail.ViewModels/AccountDetailsPageViewModel.cs +++ b/Wino.Mail.ViewModels/AccountDetailsPageViewModel.cs @@ -11,8 +11,8 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.Navigation; +using Wino.Core.Services; using Wino.Messaging.Client.Navigation; -using Wino.Messaging.Server; namespace Wino.Mail.ViewModels; @@ -20,7 +20,6 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel { private readonly IMailDialogService _dialogService; private readonly IAccountService _accountService; - private readonly IWinoServerConnectionManager _serverConnectionManager; private readonly IFolderService _folderService; private bool isLoaded = false; @@ -50,12 +49,10 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel public AccountDetailsPageViewModel(IMailDialogService dialogService, IAccountService accountService, - IWinoServerConnectionManager serverConnectionManager, IFolderService folderService) { _dialogService = dialogService; _accountService = accountService; - _serverConnectionManager = serverConnectionManager; _folderService = folderService; } @@ -95,21 +92,16 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel return; - var isSynchronizerKilledResponse = await _serverConnectionManager.GetResponseAsync(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) { base.OnNavigatedTo(mode, parameters); diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs index a225d76d..f15ff5ef 100644 --- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs +++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs @@ -12,15 +12,13 @@ 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.Navigation; using Wino.Core.Domain.Models.Synchronization; +using Wino.Core.Services; using Wino.Core.ViewModels; using Wino.Core.ViewModels.Data; using Wino.Mail.ViewModels.Data; using Wino.Messaging.Client.Navigation; -using Wino.Messaging.Server; using Wino.Messaging.UI; namespace Wino.Mail.ViewModels; @@ -34,7 +32,6 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel public IMailDialogService MailDialogService { get; } public AccountManagementViewModel(IMailDialogService dialogService, - IWinoServerConnectionManager winoServerConnectionManager, INavigationService navigationService, IAccountService accountService, ISpecialImapProviderConfigResolver specialImapProviderConfigResolver, @@ -43,7 +40,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel IStoreManagementService storeManagementService, IWinoLogger winoLogger, 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; _specialImapProviderConfigResolver = specialImapProviderConfigResolver; @@ -154,37 +151,36 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel createdAccount.Address = customServerInformation.Address; // Let server validate the imap/smtp connection. - var testResultResponse = await WinoServerConnectionManager.GetResponseAsync(new ImapConnectivityTestRequested(customServerInformation, true)); + // TODO: Protocol log with detailed failure. - if (!testResultResponse.IsSuccess) - { - throw new Exception($"{Translator.IMAPSetupDialog_ConnectionFailedTitle}\n{testResultResponse.Message}"); - } - else if (!testResultResponse.Data.IsSuccess) - { - // Server connectivity might succeed, but result might be failed. - throw new ImapClientPoolException(testResultResponse.Data.FailedReason, customServerInformation, testResultResponse.Data.FailureProtocolLog); - } + await _imapTestService.TestImapConnectionAsync(customServerInformation, true); + //var testResultResponse = await WinoServerConnectionManager.GetResponseAsync(new ImapConnectivityTestRequested(customServerInformation, true)); + + //if (!testResultResponse.IsSuccess) + //{ + // throw new Exception($"{Translator.IMAPSetupDialog_ConnectionFailedTitle}\n{testResultResponse.Message}"); + //} + //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 { // OAuth authentication is handled here. - // Server authenticates, returns the token info here. + // Use SynchronizationManager to handle OAuth authentication. - var tokenInformationResponse = await WinoServerConnectionManager - .GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType, - createdAccount, - createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token); + var authTokenInfo = await SynchronizationManager.Instance.HandleAuthorizationAsync( + accountCreationDialogResult.ProviderType, + createdAccount, + createdAccount.ProviderType == MailProviderType.Gmail); if (creationDialog.State == AccountCreationDialogState.Canceled) throw new AccountSetupCanceledException(); - if (!tokenInformationResponse.IsSuccess) - throw new Exception(tokenInformationResponse.Message); - - createdAccount.Address = tokenInformationResponse.Data.AccountAddress; - - tokenInformationResponse.ThrowIfFailed(); + // Update account address with authenticated user information + createdAccount.Address = authTokenInfo.AccountAddress; } } @@ -207,22 +203,23 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel Type = MailSynchronizationType.UpdateProfile }; - var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client)); - - var profileSynchronizationResult = profileSynchronizationResponse.Data; + var profileSynchronizationResult = await SynchronizationManager.Instance.SynchronizeProfileAsync(createdAccount.Id); if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success) throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation); - createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName; - createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData; - - if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress)) + if (profileSynchronizationResult.ProfileInformation != null) { - 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) @@ -237,26 +234,16 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel Type = MailSynchronizationType.FoldersOnly }; - var folderSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync(new NewMailSynchronizationRequested(folderSyncOptions, SynchronizationSource.Client)); - - var folderSynchronizationResult = folderSynchronizationResponse.Data; + var folderSynchronizationResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(createdAccount.Id); 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. if (createdAccount.IsAliasSyncSupported) { // Try to synchronize aliases for the account. - - var aliasSyncOptions = new MailSynchronizationOptions() - { - AccountId = createdAccount.Id, - Type = MailSynchronizationType.Alias - }; - - var aliasSyncResponse = await WinoServerConnectionManager.GetResponseAsync(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client)); - var aliasSynchronizationResult = folderSynchronizationResponse.Data; + var aliasSynchronizationResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(createdAccount.Id); if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success) throw new Exception(Translator.Exception_FailedToSynchronizeAliases); diff --git a/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs b/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs index 71b1f1bc..00453f05 100644 --- a/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs +++ b/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs @@ -12,7 +12,7 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Synchronization; -using Wino.Messaging.Server; +using Wino.Core.Services; namespace Wino.Mail.ViewModels; @@ -20,7 +20,6 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel { private readonly IMailDialogService _dialogService; private readonly IAccountService _accountService; - private readonly IWinoServerConnectionManager _winoServerConnectionManager; [ObservableProperty] [NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))] @@ -32,12 +31,10 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false; public AliasManagementPageViewModel(IMailDialogService dialogService, - IAccountService accountService, - IWinoServerConnectionManager winoServerConnectionManager) + IAccountService accountService) { _dialogService = dialogService; _accountService = accountService; - _winoServerConnectionManager = winoServerConnectionManager; } public override async void OnNavigatedTo(NavigationMode mode, object parameters) @@ -82,12 +79,12 @@ public partial class AliasManagementPageViewModel : MailBaseViewModel Type = MailSynchronizationType.Alias }; - var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client)); + var aliasSyncResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(Account.Id); - if (aliasSyncResponse.IsSuccess) + if (aliasSyncResult.CompletedState == SynchronizationCompletedState.Success) await LoadAliasesAsync(); else - _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error); + _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, "Failed to synchronize aliases", InfoBarMessageType.Error); } [RelayCommand] diff --git a/Wino.Mail.ViewModels/ComposePageViewModel.cs b/Wino.Mail.ViewModels/ComposePageViewModel.cs index 979fcf0a..9363d087 100644 --- a/Wino.Mail.ViewModels/ComposePageViewModel.cs +++ b/Wino.Mail.ViewModels/ComposePageViewModel.cs @@ -13,11 +13,11 @@ 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.Services; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Navigation; using Wino.Core.Extensions; -using Wino.Core.Services; using Wino.Mail.ViewModels.Data; using Wino.Messaging.Client.Mails; using Wino.Messaging.Server; @@ -102,7 +102,6 @@ public partial class ComposePageViewModel : MailBaseViewModel private readonly IWinoRequestDelegator _worker; public readonly IFontService FontService; public readonly IPreferencesService PreferencesService; - private readonly IWinoServerConnectionManager _winoServerConnectionManager; public readonly IContactService ContactService; public ComposePageViewModel(IMailDialogService dialogService, @@ -115,8 +114,7 @@ public partial class ComposePageViewModel : MailBaseViewModel IWinoRequestDelegator worker, IContactService contactService, IFontService fontService, - IPreferencesService preferencesService, - IWinoServerConnectionManager winoServerConnectionManager) + IPreferencesService preferencesService) { NativeAppService = nativeAppService; ContactService = contactService; @@ -130,7 +128,6 @@ public partial class ComposePageViewModel : MailBaseViewModel _fileService = fileService; _accountService = accountService; _worker = worker; - _winoServerConnectionManager = winoServerConnectionManager; } [RelayCommand] @@ -412,13 +409,12 @@ public partial class ComposePageViewModel : MailBaseViewModel { downloadIfNeeded = false; - var package = new DownloadMissingMessageRequested(CurrentMailDraftItem.AssignedAccount.Id, CurrentMailDraftItem.MailCopy); - var downloadResponse = await _winoServerConnectionManager.GetResponseAsync(package); + // Download missing MIME message using SynchronizationManager + await SynchronizationManager.Instance.DownloadMimeMessageAsync( + CurrentMailDraftItem.MailCopy, + CurrentMailDraftItem.AssignedAccount.Id); - if (downloadResponse.IsSuccess) - { - goto retry; - } + goto retry; } else _dialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error); diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index 78e0c54b..58c573a7 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -23,6 +23,7 @@ using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Menus; using Wino.Core.Domain.Models.Reader; using Wino.Core.Domain.Models.Synchronization; +using Wino.Core.Services; using Wino.Mail.ViewModels.Collections; using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Messages; @@ -81,7 +82,6 @@ public partial class MailListPageViewModel : MailBaseViewModel, private readonly IWinoRequestDelegator _winoRequestDelegator; private readonly IKeyPressService _keyPressService; private readonly IWinoLogger _winoLogger; - private readonly IWinoServerConnectionManager _winoServerConnectionManager; private MailItemViewModel _activeMailItem; public List SortingOptions { get; } = @@ -160,14 +160,12 @@ public partial class MailListPageViewModel : MailBaseViewModel, IKeyPressService keyPressService, IPreferencesService preferencesService, INewThemeService themeService, - IWinoLogger winoLogger, - IWinoServerConnectionManager winoServerConnectionManager) + IWinoLogger winoLogger) { MailCollection = new WinoMailCollection(threadingStrategyProvider); PreferencesService = preferencesService; ThemeService = themeService; _winoLogger = winoLogger; - _winoServerConnectionManager = winoServerConnectionManager; StatePersistenceService = statePersistenceService; NavigationService = navigationService; _accountService = accountService; @@ -841,51 +839,52 @@ public partial class MailListPageViewModel : MailBaseViewModel, // Perform online search. if (isDoingOnlineSearch) { - WinoServerResponse onlineSearchResult = null; - string onlineSearchFailedMessage = null; + // TODO: Burak: Handle online search. + //WinoServerResponse onlineSearchResult = null; + //string onlineSearchFailedMessage = null; - try - { - var accountIds = ActiveFolder.HandlingFolders.Select(a => a.MailAccountId).ToList(); - var folders = ActiveFolder.HandlingFolders.ToList(); - var searchRequest = new OnlineSearchRequested(accountIds, SearchQuery, folders); + //try + //{ + // var accountIds = ActiveFolder.HandlingFolders.Select(a => a.MailAccountId).ToList(); + // var folders = ActiveFolder.HandlingFolders.ToList(); + // var searchRequest = new OnlineSearchRequested(accountIds, SearchQuery, folders); - onlineSearchResult = await _winoServerConnectionManager.GetResponseAsync(searchRequest, cancellationToken); + // onlineSearchResult = await _winoServerConnectionManager.GetResponseAsync(searchRequest, cancellationToken); - if (onlineSearchResult.IsSuccess) - { - await ExecuteUIThread(() => { AreSearchResultsOnline = true; }); + // if (onlineSearchResult.IsSuccess) + // { + // await ExecuteUIThread(() => { AreSearchResultsOnline = true; }); - onlineSearchItems = onlineSearchResult.Data.SearchResult; - } - else - { - onlineSearchFailedMessage = onlineSearchResult.Message; - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - Log.Warning(ex, "Failed to perform online search."); - onlineSearchFailedMessage = ex.Message; - } + // onlineSearchItems = onlineSearchResult.Data.SearchResult; + // } + // else + // { + // onlineSearchFailedMessage = onlineSearchResult.Message; + // } + //} + //catch (OperationCanceledException) + //{ + // throw; + //} + //catch (Exception ex) + //{ + // Log.Warning(ex, "Failed to perform online search."); + // onlineSearchFailedMessage = ex.Message; + //} - if (onlineSearchResult != null && !onlineSearchResult.IsSuccess) - { - // Query or server error. - var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchResult.Message); - _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning); + //if (onlineSearchResult != null && !onlineSearchResult.IsSuccess) + //{ + // // Query or server error. + // var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchResult.Message); + // _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning); - } - else if (!string.IsNullOrEmpty(onlineSearchFailedMessage)) - { - // Fatal error. - var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchFailedMessage); - _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning); - } + //} + //else if (!string.IsNullOrEmpty(onlineSearchFailedMessage)) + //{ + // // Fatal error. + // var serverErrorMessage = string.Format(Translator.OnlineSearchFailed_Message, onlineSearchFailedMessage); + // _mailDialogService.InfoBarMessage(Translator.GeneralTitle_Error, serverErrorMessage, InfoBarMessageType.Warning); + //} } } @@ -1110,9 +1109,7 @@ public partial class MailListPageViewModel : MailBaseViewModel, foreach (var accountId in accountIds) { - var serverResponse = await _winoServerConnectionManager.GetResponseAsync(new SynchronizationExistenceCheckRequest(accountId)); - - if (serverResponse.IsSuccess && serverResponse.Data == true) + if (SynchronizationManager.Instance.IsAccountSynchronizing(accountId)) { isAnyAccountSynchronizing = true; break; diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 3a2818c6..6be1fe8b 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -21,6 +21,7 @@ using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Menus; using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Reader; +using Wino.Core.Services; using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Messages; using Wino.Messaging.Client.Mails; @@ -46,7 +47,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, private readonly IClipboardService _clipboardService; private readonly IUnsubscriptionService _unsubscriptionService; private readonly IApplicationConfiguration _applicationConfiguration; - private readonly IWinoServerConnectionManager _winoServerConnectionManager; private bool forceImageLoading = false; private MailItemViewModel initializedMailItemViewModel = null; @@ -142,8 +142,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, IUnsubscriptionService unsubscriptionService, IPreferencesService preferencesService, IPrintService printService, - IApplicationConfiguration applicationConfiguration, - IWinoServerConnectionManager winoServerConnectionManager) + IApplicationConfiguration applicationConfiguration) { _dialogService = dialogService; NativeAppService = nativeAppService; @@ -152,7 +151,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, PreferencesService = preferencesService; PrintService = printService; _applicationConfiguration = applicationConfiguration; - _winoServerConnectionManager = winoServerConnectionManager; _clipboardService = clipboardService; _unsubscriptionService = unsubscriptionService; _underlyingThemeService = underlyingThemeService; @@ -355,8 +353,10 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, // To show the progress on the UI. CurrentDownloadPercentage = 1; - var package = new DownloadMissingMessageRequested(mailItemViewModel.AssignedAccount.Id, mailItemViewModel.MailCopy); - await _winoServerConnectionManager.GetResponseAsync(package); + // Download missing MIME message using SynchronizationManager + await SynchronizationManager.Instance.DownloadMimeMessageAsync( + mailItemViewModel.MailCopy, + mailItemViewModel.AssignedAccount.Id); } catch (OperationCanceledException) { diff --git a/Wino.Mail.WinUI/Views/ImapSetup/TestingImapConnectionPage.xaml.cs b/Wino.Mail.WinUI/Views/ImapSetup/TestingImapConnectionPage.xaml.cs index 2bd04975..4d99cbdf 100644 --- a/Wino.Mail.WinUI/Views/ImapSetup/TestingImapConnectionPage.xaml.cs +++ b/Wino.Mail.WinUI/Views/ImapSetup/TestingImapConnectionPage.xaml.cs @@ -1,25 +1,20 @@ using System; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; -using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Exceptions; -using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.AutoDiscovery; -using Wino.Core.Domain.Models.Connectivity; -using Wino.Mail.WinUI; +using Wino.Core.Services; using Wino.Messaging.Client.Mails; -using Wino.Messaging.Server; namespace Wino.Views.ImapSetup; public sealed partial class TestingImapConnectionPage : Page { - private IWinoServerConnectionManager _winoServerConnectionManager = App.Current.Services.GetService(); private AutoDiscoverySettings autoDiscoverySettings; private CustomServerInformation serverInformationToTest; @@ -67,46 +62,34 @@ public sealed partial class TestingImapConnectionPage : Page await Task.Delay(1000); - var testResultResponse = await _winoServerConnectionManager - .GetResponseAsync(new ImapConnectivityTestRequested(serverInformationToTest, allowSSLHandshake)); + var testResultData = await SynchronizationManager.Instance.TestImapConnectivityAsync(serverInformationToTest, allowSSLHandshake); - if (!testResultResponse.IsSuccess) + if (testResultData.IsSuccess) { - // Wino Server is connection is failed. - ReturnWithError(testResultResponse.Message); + // All success. Finish setup with validated server information. + ReturnWithSuccess(); } else { - var testResultData = testResultResponse.Data; - - if (testResultData.IsSuccess) + // Check if certificate UI is required. + if (testResultData.IsCertificateUIRequired) { - // All success. Finish setup with validated server information. - ReturnWithSuccess(); + // Certificate UI is required. Show certificate dialog. + + 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 { - // Check if certificate UI is required. + // Connection test failed. Show error dialog. - if (testResultData.IsCertificateUIRequired) - { - // Certificate UI is required. Show certificate dialog. + var protocolLog = testResultData.FailureProtocolLog; - 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 - { - // Connection test failed. Show error dialog. - - var protocolLog = testResultData.FailureProtocolLog; - - ReturnWithError(testResultData.FailedReason, protocolLog); - } + ReturnWithError(testResultData.FailedReason, protocolLog); } } }