From f7836eedce576ac8425fffe792877ad24d6adc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sun, 16 Feb 2025 13:23:45 +0100 Subject: [PATCH] Tracking failed imap setup steps for app insights. --- .../Shared/CustomServerInformation.cs | 22 + .../Exceptions/ImapClientPoolException.cs | 17 + .../Interfaces/ILogInitializer.cs | 8 - Wino.Core.Domain/Interfaces/IWinoLogger.cs | 10 + Wino.Core.UWP/WinoApplication.cs | 379 +++++++++--------- Wino.Core.ViewModels/AboutPageViewModel.cs | 205 +++++----- Wino.Core/Services/ImapTestService.cs | 10 + .../AccountManagementViewModel.cs | 31 +- Wino.Server/App.xaml.cs | 2 +- Wino.Services/ServicesContainerSetup.cs | 45 ++- .../{LogInitializer.cs => WinoLogger.cs} | 24 +- 11 files changed, 422 insertions(+), 331 deletions(-) delete mode 100644 Wino.Core.Domain/Interfaces/ILogInitializer.cs create mode 100644 Wino.Core.Domain/Interfaces/IWinoLogger.cs rename Wino.Services/{LogInitializer.cs => WinoLogger.cs} (65%) diff --git a/Wino.Core.Domain/Entities/Shared/CustomServerInformation.cs b/Wino.Core.Domain/Entities/Shared/CustomServerInformation.cs index 164027c7..00aee83b 100644 --- a/Wino.Core.Domain/Entities/Shared/CustomServerInformation.cs +++ b/Wino.Core.Domain/Entities/Shared/CustomServerInformation.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SQLite; using Wino.Core.Domain.Enums; @@ -49,4 +50,25 @@ public class CustomServerInformation /// Default is 5. /// public int MaxConcurrentClients { get; set; } + + public Dictionary GetConnectionProperties() + { + // Printout the public connection properties. + + var connectionProperties = new Dictionary + { + { "IncomingServer", IncomingServer }, + { "IncomingServerPort", IncomingServerPort }, + { "IncomingServerSocketOption", IncomingServerSocketOption.ToString() }, + { "IncomingAuthenticationMethod", IncomingAuthenticationMethod.ToString() }, + { "OutgoingServer", OutgoingServer }, + { "OutgoingServerPort", OutgoingServerPort }, + { "OutgoingServerSocketOption", OutgoingServerSocketOption.ToString() }, + { "OutgoingAuthenticationMethod", OutgoingAuthenticationMethod.ToString() }, + { "ProxyServer", ProxyServer }, + { "ProxyServerPort", ProxyServerPort } + }; + + return connectionProperties; + } } diff --git a/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs b/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs index 9a185743..ffb3bf14 100644 --- a/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs +++ b/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs @@ -1,13 +1,30 @@ using System; +using Wino.Core.Domain.Entities.Shared; namespace Wino.Core.Domain.Exceptions; public class ImapClientPoolException : Exception { + public ImapClientPoolException() + { + } + + public ImapClientPoolException(string message, CustomServerInformation customServerInformation, string protocolLog) : base(message) + { + CustomServerInformation = customServerInformation; + ProtocolLog = protocolLog; + } + + public ImapClientPoolException(string message, string protocolLog) : base(message) + { + ProtocolLog = protocolLog; + } + public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException) { ProtocolLog = protocolLog; } + public CustomServerInformation CustomServerInformation { get; } public string ProtocolLog { get; } } diff --git a/Wino.Core.Domain/Interfaces/ILogInitializer.cs b/Wino.Core.Domain/Interfaces/ILogInitializer.cs deleted file mode 100644 index 35870d53..00000000 --- a/Wino.Core.Domain/Interfaces/ILogInitializer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Wino.Core.Domain.Interfaces; - -public interface ILogInitializer -{ - void SetupLogger(string fullLogFilePath); - - void RefreshLoggingLevel(); -} diff --git a/Wino.Core.Domain/Interfaces/IWinoLogger.cs b/Wino.Core.Domain/Interfaces/IWinoLogger.cs new file mode 100644 index 00000000..c7ea6e60 --- /dev/null +++ b/Wino.Core.Domain/Interfaces/IWinoLogger.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Wino.Core.Domain.Interfaces; + +public interface IWinoLogger +{ + void SetupLogger(string fullLogFilePath); + void RefreshLoggingLevel(); + void TrackEvent(string eventName, Dictionary properties = null); +} diff --git a/Wino.Core.UWP/WinoApplication.cs b/Wino.Core.UWP/WinoApplication.cs index a6c08518..5d47e059 100644 --- a/Wino.Core.UWP/WinoApplication.cs +++ b/Wino.Core.UWP/WinoApplication.cs @@ -22,227 +22,228 @@ using Wino.Core.Domain; using Wino.Core.Domain.Interfaces; using Wino.Services; -namespace Wino.Core.UWP; - -public abstract class WinoApplication : Application +namespace Wino.Core.UWP { - public new static WinoApplication Current => (WinoApplication)Application.Current; - public const string WinoLaunchLogPrefix = "[Wino Launch] "; - - public IServiceProvider Services { get; } - protected ILogInitializer LogInitializer { get; } - protected IApplicationConfiguration AppConfiguration { get; } - protected IWinoServerConnectionManager AppServiceConnectionManager { get; } - protected IThemeService ThemeService { get; } - protected IDatabaseService DatabaseService { get; } - protected ITranslationService TranslationService { get; } - - protected WinoApplication() + public abstract class WinoApplication : Application { - ConfigurePrelaunch(); + public new static WinoApplication Current => (WinoApplication)Application.Current; + public const string WinoLaunchLogPrefix = "[Wino Launch] "; - Services = ConfigureServices(); + public IServiceProvider Services { get; } + protected IWinoLogger LogInitializer { get; } + protected IApplicationConfiguration AppConfiguration { get; } + protected IWinoServerConnectionManager AppServiceConnectionManager { get; } + protected IThemeService ThemeService { get; } + protected IDatabaseService DatabaseService { get; } + protected ITranslationService TranslationService { get; } - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; - UnhandledException += OnAppUnhandledException; - - Resuming += OnResuming; - Suspending += OnSuspending; - - LogInitializer = Services.GetService(); - AppConfiguration = Services.GetService(); - - AppServiceConnectionManager = Services.GetService>(); - ThemeService = Services.GetService(); - DatabaseService = Services.GetService(); - TranslationService = Services.GetService(); - - // Make sure the paths are setup on app start. - AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; - AppConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; - AppConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; - - ConfigureLogging(); - } - - private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) - => Log.Fatal(e.ExceptionObject as Exception, "AppDomain Unhandled Exception"); - - private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) - => Log.Error(e.Exception, "Unobserved Task Exception"); - - private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) - { - Log.Fatal(e.Exception, "Unhandled Exception"); - e.Handled = true; - } - - protected abstract void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e); - protected abstract IEnumerable GetActivationHandlers(); - protected abstract ActivationHandler GetDefaultActivationHandler(); - protected override void OnWindowCreated(WindowCreatedEventArgs args) - { - base.OnWindowCreated(args); - - ConfigureTitleBar(); - - LogActivation($"OnWindowCreated -> IsWindowNull: {args.Window == null}"); - - TryRegisterAppCloseChange(); - } - - public IEnumerable GetActivationServices() - { - yield return DatabaseService; - yield return TranslationService; - yield return ThemeService; - } - - public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll(); - - public bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs; - - public void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); - - private void ConfigureTitleBar() - { - var coreTitleBar = CoreApplication.GetCurrentView().TitleBar; - var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar; - - // Extend shell content into core window to meet design requirements. - coreTitleBar.ExtendViewIntoTitleBar = true; - - // Change system buttons and background colors to meet design requirements. - applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent; - applicationViewTitleBar.BackgroundColor = Colors.Transparent; - applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; - applicationViewTitleBar.ButtonForegroundColor = Colors.White; - } - - public async Task ActivateWinoAsync(object args) - { - await InitializeServicesAsync(); - - if (IsInteractiveLaunchArgs(args)) + protected WinoApplication() { - if (Window.Current.Content == null) + ConfigurePrelaunch(); + + Services = ConfigureServices(); + + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + UnhandledException += OnAppUnhandledException; + + Resuming += OnResuming; + Suspending += OnSuspending; + + LogInitializer = Services.GetService(); + AppConfiguration = Services.GetService(); + + AppServiceConnectionManager = Services.GetService>(); + ThemeService = Services.GetService(); + DatabaseService = Services.GetService(); + TranslationService = Services.GetService(); + + // Make sure the paths are setup on app start. + AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; + AppConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; + AppConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; + + ConfigureLogging(); + } + + private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) + => Log.Fatal(e.ExceptionObject as Exception, "AppDomain Unhandled Exception"); + + private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + => Log.Error(e.Exception, "Unobserved Task Exception"); + + private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) + { + Log.Fatal(e.Exception, "Unhandled Exception"); + e.Handled = true; + } + + protected abstract void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e); + protected abstract IEnumerable GetActivationHandlers(); + protected abstract ActivationHandler GetDefaultActivationHandler(); + protected override void OnWindowCreated(WindowCreatedEventArgs args) + { + base.OnWindowCreated(args); + + ConfigureTitleBar(); + + LogActivation($"OnWindowCreated -> IsWindowNull: {args.Window == null}"); + + TryRegisterAppCloseChange(); + } + + public IEnumerable GetActivationServices() + { + yield return DatabaseService; + yield return TranslationService; + yield return ThemeService; + } + + public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll(); + + public bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs; + + public void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); + + private void ConfigureTitleBar() + { + var coreTitleBar = CoreApplication.GetCurrentView().TitleBar; + var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar; + + // Extend shell content into core window to meet design requirements. + coreTitleBar.ExtendViewIntoTitleBar = true; + + // Change system buttons and background colors to meet design requirements. + applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent; + applicationViewTitleBar.BackgroundColor = Colors.Transparent; + applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + applicationViewTitleBar.ButtonForegroundColor = Colors.White; + } + + public async Task ActivateWinoAsync(object args) + { + await InitializeServicesAsync(); + + if (IsInteractiveLaunchArgs(args)) { - var mainFrame = new Frame(); + if (Window.Current.Content == null) + { + var mainFrame = new Frame(); - Window.Current.Content = mainFrame; + Window.Current.Content = mainFrame; - await ThemeService.InitializeAsync(); + await ThemeService.InitializeAsync(); + } + } + + await HandleActivationAsync(args); + + if (IsInteractiveLaunchArgs(args)) + { + Window.Current.Activate(); + + LogActivation("Window activated"); } } - await HandleActivationAsync(args); - - if (IsInteractiveLaunchArgs(args)) + public async Task HandleActivationAsync(object activationArgs) { - Window.Current.Activate(); - - LogActivation("Window activated"); - } - } - - public async Task HandleActivationAsync(object activationArgs) - { - if (GetActivationHandlers() != null) - { - var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)) ?? null; - - if (activationHandler != null) + if (GetActivationHandlers() != null) { - await activationHandler.HandleAsync(activationArgs); + var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs)) ?? null; + + if (activationHandler != null) + { + await activationHandler.HandleAsync(activationArgs); + } + } + + if (IsInteractiveLaunchArgs(activationArgs)) + { + var defaultHandler = GetDefaultActivationHandler(); + + if (defaultHandler.CanHandle(activationArgs)) + { + await defaultHandler.HandleAsync(activationArgs); + } } } - if (IsInteractiveLaunchArgs(activationArgs)) + protected override async void OnLaunched(LaunchActivatedEventArgs args) { - var defaultHandler = GetDefaultActivationHandler(); + LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}"); - if (defaultHandler.CanHandle(activationArgs)) + if (!args.PrelaunchActivated) { - await defaultHandler.HandleAsync(activationArgs); + await ActivateWinoAsync(args); } } - } - protected override async void OnLaunched(LaunchActivatedEventArgs args) - { - LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}"); - - if (!args.PrelaunchActivated) + protected override async void OnFileActivated(FileActivatedEventArgs args) { + base.OnFileActivated(args); + + LogActivation($"OnFileActivated -> ItemCount: {args.Files.Count}, Kind: {args.Kind}, PreviousExecutionState: {args.PreviousExecutionState}"); + await ActivateWinoAsync(args); } - } - protected override async void OnFileActivated(FileActivatedEventArgs args) - { - base.OnFileActivated(args); - - LogActivation($"OnFileActivated -> ItemCount: {args.Files.Count}, Kind: {args.Kind}, PreviousExecutionState: {args.PreviousExecutionState}"); - - await ActivateWinoAsync(args); - } - - protected override async void OnActivated(IActivatedEventArgs args) - { - base.OnActivated(args); - - Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}"); - - await ActivateWinoAsync(args); - } - - private void TryRegisterAppCloseChange() - { - try + protected override async void OnActivated(IActivatedEventArgs args) { - var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView(); + base.OnActivated(args); - systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested; - systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested; + Log.Information($"OnActivated -> {args.GetType().Name}, Kind -> {args.Kind}, Prev Execution State -> {args.PreviousExecutionState}"); + + await ActivateWinoAsync(args); } - catch { } - } - private void ConfigurePrelaunch() - { - if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch")) - CoreApplication.EnablePrelaunch(true); - } - - - - public virtual async void OnResuming(object sender, object e) - { - // App Service connection was lost on suspension. - // We must restore it. - // Server might be running already, but re-launching it will trigger a new connection attempt. - - try + private void TryRegisterAppCloseChange() { - await AppServiceConnectionManager.ConnectAsync(); - } - catch (OperationCanceledException) - { - // Ignore - } - catch (Exception ex) - { - Log.Error(ex, "Failed to connect to server after resuming the app."); - } - } - public virtual void OnSuspending(object sender, SuspendingEventArgs e) { } + try + { + var systemNavigationManagerPreview = SystemNavigationManagerPreview.GetForCurrentView(); - public abstract IServiceProvider ConfigureServices(); + systemNavigationManagerPreview.CloseRequested -= OnApplicationCloseRequested; + systemNavigationManagerPreview.CloseRequested += OnApplicationCloseRequested; + } + catch { } + } - public void ConfigureLogging() - { - string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile); - LogInitializer.SetupLogger(logFilePath); + private void ConfigurePrelaunch() + { + if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch")) + CoreApplication.EnablePrelaunch(true); + } + + + + public virtual async void OnResuming(object sender, object e) + { + // App Service connection was lost on suspension. + // We must restore it. + // Server might be running already, but re-launching it will trigger a new connection attempt. + + try + { + await AppServiceConnectionManager.ConnectAsync(); + } + catch (OperationCanceledException) + { + // Ignore + } + catch (Exception ex) + { + Log.Error(ex, "Failed to connect to server after resuming the app."); + } + } + public virtual void OnSuspending(object sender, SuspendingEventArgs e) { } + + public abstract IServiceProvider ConfigureServices(); + + public void ConfigureLogging() + { + string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile); + LogInitializer.SetupLogger(logFilePath); + } } } diff --git a/Wino.Core.ViewModels/AboutPageViewModel.cs b/Wino.Core.ViewModels/AboutPageViewModel.cs index fad0d7fa..123025cd 100644 --- a/Wino.Core.ViewModels/AboutPageViewModel.cs +++ b/Wino.Core.ViewModels/AboutPageViewModel.cs @@ -6,124 +6,125 @@ using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; -namespace Wino.Core.ViewModels; - -public partial class AboutPageViewModel : CoreBaseViewModel +namespace Wino.Core.ViewModels { - private readonly IStoreRatingService _storeRatingService; - private readonly IMailDialogService _dialogService; - private readonly INativeAppService _nativeAppService; - private readonly IApplicationConfiguration _appInitializerService; - private readonly IClipboardService _clipboardService; - private readonly IFileService _fileService; - private readonly ILogInitializer _logInitializer; - - public string VersionName => _nativeAppService.GetFullAppVersion(); - public string DiscordChannelUrl => "https://discord.gg/windows-apps-hub-714581497222398064"; - public string GitHubUrl => "https://github.com/bkaankose/Wino-Mail/"; - public string PrivacyPolicyUrl => "https://www.winomail.app/support/privacy"; - public string PaypalUrl => "https://paypal.me/bkaankose?country.x=PL&locale.x=en_US"; - - public IPreferencesService PreferencesService { get; } - - public AboutPageViewModel(IStoreRatingService storeRatingService, - IMailDialogService dialogService, - INativeAppService nativeAppService, - IPreferencesService preferencesService, - IApplicationConfiguration appInitializerService, - IClipboardService clipboardService, - IFileService fileService, - ILogInitializer logInitializer) + public partial class AboutPageViewModel : CoreBaseViewModel { - _storeRatingService = storeRatingService; - _dialogService = dialogService; - _nativeAppService = nativeAppService; - _logInitializer = logInitializer; - _appInitializerService = appInitializerService; - _clipboardService = clipboardService; - _fileService = fileService; + private readonly IStoreRatingService _storeRatingService; + private readonly IMailDialogService _dialogService; + private readonly INativeAppService _nativeAppService; + private readonly IApplicationConfiguration _appInitializerService; + private readonly IClipboardService _clipboardService; + private readonly IFileService _fileService; + private readonly IWinoLogger _logInitializer; - PreferencesService = preferencesService; - } + public string VersionName => _nativeAppService.GetFullAppVersion(); + public string DiscordChannelUrl => "https://discord.gg/windows-apps-hub-714581497222398064"; + public string GitHubUrl => "https://github.com/bkaankose/Wino-Mail/"; + public string PrivacyPolicyUrl => "https://www.winomail.app/support/privacy"; + public string PaypalUrl => "https://paypal.me/bkaankose?country.x=PL&locale.x=en_US"; - protected override void OnActivated() - { - base.OnActivated(); + public IPreferencesService PreferencesService { get; } - PreferencesService.PreferenceChanged -= PreferencesChanged; - PreferencesService.PreferenceChanged += PreferencesChanged; - } - - protected override void OnDeactivated() - { - base.OnDeactivated(); - - PreferencesService.PreferenceChanged -= PreferencesChanged; - } - - private void PreferencesChanged(object sender, string e) - { - if (e == nameof(PreferencesService.IsLoggingEnabled)) + public AboutPageViewModel(IStoreRatingService storeRatingService, + IMailDialogService dialogService, + INativeAppService nativeAppService, + IPreferencesService preferencesService, + IApplicationConfiguration appInitializerService, + IClipboardService clipboardService, + IFileService fileService, + IWinoLogger logInitializer) { - _logInitializer.RefreshLoggingLevel(); + _storeRatingService = storeRatingService; + _dialogService = dialogService; + _nativeAppService = nativeAppService; + _logInitializer = logInitializer; + _appInitializerService = appInitializerService; + _clipboardService = clipboardService; + _fileService = fileService; + + PreferencesService = preferencesService; } - } - [RelayCommand] - private async Task CopyDiagnosticId() - { - try + protected override void OnActivated() { - await _clipboardService.CopyClipboardAsync(PreferencesService.DiagnosticId); - _dialogService.InfoBarMessage(Translator.Buttons_Copy, string.Format(Translator.ClipboardTextCopied_Message, "Id"), InfoBarMessageType.Success); + base.OnActivated(); + + PreferencesService.PreferenceChanged -= PreferencesChanged; + PreferencesService.PreferenceChanged += PreferencesChanged; } - catch (Exception ex) + + protected override void OnDeactivated() { - _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, string.Format(Translator.ClipboardTextCopyFailed_Message, "Id"), InfoBarMessageType.Error); - Log.Error(ex, "Failed to copy diagnostic id to clipboard."); + base.OnDeactivated(); + + PreferencesService.PreferenceChanged -= PreferencesChanged; } - } - [RelayCommand] - private async Task ShareWinoLogAsync() - { - var appDataFolder = _appInitializerService.ApplicationDataFolderPath; - - var selectedFolderPath = await _dialogService.PickWindowsFolderAsync(); - - if (string.IsNullOrEmpty(selectedFolderPath)) return; - - var areLogsSaved = await _fileService.SaveLogsToFolderAsync(appDataFolder, selectedFolderPath).ConfigureAwait(false); - - if (areLogsSaved) + private void PreferencesChanged(object sender, string e) { - _dialogService.InfoBarMessage(Translator.Info_LogsSavedTitle, string.Format(Translator.Info_LogsSavedMessage, Constants.LogArchiveFileName), InfoBarMessageType.Success); - } - else - { - _dialogService.InfoBarMessage(Translator.Info_LogsNotFoundTitle, Translator.Info_LogsNotFoundMessage, InfoBarMessageType.Error); - } - } - - [RelayCommand] - private async Task Navigate(object url) - { - if (url is string stringUrl) - { - if (stringUrl == "Store") - await ShowRateDialogAsync(); - else + if (e == nameof(PreferencesService.IsLoggingEnabled)) { - // Discord disclaimer message about server. - if (stringUrl == DiscordChannelUrl) - await _dialogService.ShowMessageAsync(Translator.DiscordChannelDisclaimerMessage, - Translator.DiscordChannelDisclaimerTitle, - WinoCustomMessageDialogIcon.Warning); - - await _nativeAppService.LaunchUriAsync(new Uri(stringUrl)); + _logInitializer.RefreshLoggingLevel(); } } - } - private Task ShowRateDialogAsync() => _storeRatingService.LaunchStorePageForReviewAsync(); + [RelayCommand] + private async Task CopyDiagnosticId() + { + try + { + await _clipboardService.CopyClipboardAsync(PreferencesService.DiagnosticId); + _dialogService.InfoBarMessage(Translator.Buttons_Copy, string.Format(Translator.ClipboardTextCopied_Message, "Id"), InfoBarMessageType.Success); + } + catch (Exception ex) + { + _dialogService.InfoBarMessage(Translator.GeneralTitle_Error, string.Format(Translator.ClipboardTextCopyFailed_Message, "Id"), InfoBarMessageType.Error); + Log.Error(ex, "Failed to copy diagnostic id to clipboard."); + } + } + + [RelayCommand] + private async Task ShareWinoLogAsync() + { + var appDataFolder = _appInitializerService.ApplicationDataFolderPath; + + var selectedFolderPath = await _dialogService.PickWindowsFolderAsync(); + + if (string.IsNullOrEmpty(selectedFolderPath)) return; + + var areLogsSaved = await _fileService.SaveLogsToFolderAsync(appDataFolder, selectedFolderPath).ConfigureAwait(false); + + if (areLogsSaved) + { + _dialogService.InfoBarMessage(Translator.Info_LogsSavedTitle, string.Format(Translator.Info_LogsSavedMessage, Constants.LogArchiveFileName), InfoBarMessageType.Success); + } + else + { + _dialogService.InfoBarMessage(Translator.Info_LogsNotFoundTitle, Translator.Info_LogsNotFoundMessage, InfoBarMessageType.Error); + } + } + + [RelayCommand] + private async Task Navigate(object url) + { + if (url is string stringUrl) + { + if (stringUrl == "Store") + await ShowRateDialogAsync(); + else + { + // Discord disclaimer message about server. + if (stringUrl == DiscordChannelUrl) + await _dialogService.ShowMessageAsync(Translator.DiscordChannelDisclaimerMessage, + Translator.DiscordChannelDisclaimerTitle, + WinoCustomMessageDialogIcon.Warning); + + await _nativeAppService.LaunchUriAsync(new Uri(stringUrl)); + } + } + } + + private Task ShowRateDialogAsync() => _storeRatingService.LaunchStorePageForReviewAsync(); + } } diff --git a/Wino.Core/Services/ImapTestService.cs b/Wino.Core/Services/ImapTestService.cs index e2d97ec8..ce20f6c9 100644 --- a/Wino.Core/Services/ImapTestService.cs +++ b/Wino.Core/Services/ImapTestService.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading.Tasks; +using MailKit.Net.Smtp; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Connectivity; @@ -53,5 +54,14 @@ public class ImapTestService : IImapTestService clientPool.Release(client); } + + // Test SMTP connectivity. + using var smtpClient = new SmtpClient(); + + if (!smtpClient.IsConnected) + await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto); + + if (!smtpClient.IsAuthenticated) + await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword); } } diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs index fcac4a4d..9be11d0c 100644 --- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs +++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs @@ -13,6 +13,7 @@ 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.ViewModels; @@ -28,6 +29,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel { private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver; private readonly IImapTestService _imapTestService; + private readonly IWinoLogger _winoLogger; public IMailDialogService MailDialogService { get; } @@ -39,12 +41,14 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel IProviderService providerService, IImapTestService imapTestService, IStoreManagementService storeManagementService, + IWinoLogger winoLogger, IAuthenticationProvider authenticationProvider, IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService) { MailDialogService = dialogService; _specialImapProviderConfigResolver = specialImapProviderConfigResolver; _imapTestService = imapTestService; + _winoLogger = winoLogger; } [RelayCommand] @@ -111,6 +115,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel }; await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource); + creationDialog.State = AccountCreationDialogState.SigningIn; string tokenInformation = string.Empty; @@ -146,7 +151,18 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName; createdAccount.Address = customServerInformation.Address; - await _imapTestService.TestImapConnectionAsync(customServerInformation, true); + // Let server validate the imap/smtp connection. + 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 { @@ -266,7 +282,18 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel { // Ignore } - catch (ImapClientPoolException clientPoolException) + catch (ImapClientPoolException testClientPoolException) when (testClientPoolException.CustomServerInformation != null) + { + var properties = testClientPoolException.CustomServerInformation.GetConnectionProperties(); + + properties.Add("ProtocolLog", testClientPoolException.ProtocolLog); + properties.Add("DiagnosticId", PreferencesService.DiagnosticId); + + _winoLogger.TrackEvent("IMAP Test Failed", properties); + + DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, testClientPoolException.Message, InfoBarMessageType.Error); + } + catch (ImapClientPoolException clientPoolException) when (clientPoolException.InnerException != null) { DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error); } diff --git a/Wino.Server/App.xaml.cs b/Wino.Server/App.xaml.cs index a3b5c0ef..2731fab3 100644 --- a/Wino.Server/App.xaml.cs +++ b/Wino.Server/App.xaml.cs @@ -111,7 +111,7 @@ public partial class App : Application applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; // Setup logger - var logInitializer = Services.GetService(); + var logInitializer = Services.GetService(); var logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ServerLogFile); logInitializer.SetupLogger(logFilePath); diff --git a/Wino.Services/ServicesContainerSetup.cs b/Wino.Services/ServicesContainerSetup.cs index 4a16ab63..749c3b66 100644 --- a/Wino.Services/ServicesContainerSetup.cs +++ b/Wino.Services/ServicesContainerSetup.cs @@ -2,34 +2,35 @@ using Wino.Core.Domain.Interfaces; using Wino.Services.Threading; -namespace Wino.Services; - -public static class ServicesContainerSetup +namespace Wino.Services { - public static void RegisterSharedServices(this IServiceCollection services) + public static class ServicesContainerSetup { - services.AddSingleton(); - services.AddSingleton(); + public static void RegisterSharedServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + } } } diff --git a/Wino.Services/LogInitializer.cs b/Wino.Services/WinoLogger.cs similarity index 65% rename from Wino.Services/LogInitializer.cs rename to Wino.Services/WinoLogger.cs index a1bcfe4f..61241728 100644 --- a/Wino.Services/LogInitializer.cs +++ b/Wino.Services/WinoLogger.cs @@ -1,4 +1,6 @@ -using Microsoft.ApplicationInsights.Extensibility; +using System.Collections.Generic; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; using Serilog; using Serilog.Core; using Serilog.Exceptions; @@ -7,20 +9,21 @@ using Wino.Services.Misc; namespace Wino.Services; -public class LogInitializer : ILogInitializer +public class WinoLogger : IWinoLogger { private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch(); private readonly IPreferencesService _preferencesService; - private readonly IApplicationConfiguration _applicationConfiguration; private readonly TelemetryConfiguration _telemetryConfiguration; - public LogInitializer(IPreferencesService preferencesService, IApplicationConfiguration applicationConfiguration) + public TelemetryClient TelemetryClient { get; private set; } + + public WinoLogger(IPreferencesService preferencesService, IApplicationConfiguration applicationConfiguration) { _preferencesService = preferencesService; - _applicationConfiguration = applicationConfiguration; - _telemetryConfiguration = new TelemetryConfiguration(applicationConfiguration.ApplicationInsightsInstrumentationKey); + TelemetryClient = new TelemetryClient(_telemetryConfiguration); + RefreshLoggingLevel(); } @@ -41,9 +44,16 @@ public class LogInitializer : ILogInitializer .MinimumLevel.ControlledBy(_levelSwitch) .WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day) .WriteTo.Debug() - .WriteTo.ApplicationInsights(_telemetryConfiguration, insightsTelemetryConverter, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error) + .WriteTo.ApplicationInsights(TelemetryClient, insightsTelemetryConverter, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error) .Enrich.FromLogContext() .Enrich.WithExceptionDetails() .CreateLogger(); } + + public void TrackEvent(string eventName, Dictionary properties = null) + { + if (TelemetryClient == null) return; + + TelemetryClient.TrackEvent(eventName, properties); + } }