Full trust Wino Server implementation. (#295)

* Separation of messages. Introducing Wino.Messages library.

* Wino.Server and Wino.Packaging projects. Enabling full trust for UWP and app service connection manager basics.

* Remove debug code.

* Enable generating assembly info to deal with unsupported os platform warnings.

* Fix server-client connection.

* UIMessage communication. Single instancing for server and re-connection mechanism on suspension.

* Removed IWinoSynchronizerFactory from UWP project.

* Removal of background task service from core.

* Delegating changes to UI and triggering new background synchronization.

* Fix build error.

* Moved core lib messages to Messaging project.

* Better client-server communication. Handling of requests in the server. New synchronizer factory in the server.

* WAM broker and MSAL token caching for OutlookAuthenticator. Handling account creation for Outlook.

* WinoServerResponse basics.

* Delegating protocol activation for Gmail authenticator.

* Adding margin to searchbox to match action bar width.

* Move libraries into lib folder.

* Storing base64 encoded mime on draft creation instead of MimeMessage object. Fixes serialization/deserialization issue with S.T.Json

* Scrollbar adjustments

* WınoExpander for thread expander layout ıssue.

* Handling synchronizer state changes.

* Double init on background activation.

* FIxing packaging issues and new Wino Mail launcher protocol for activation from full thrust process.

* Remove debug deserialization.

* Remove debug code.

* Making sure the server connection is established when the app is launched.

* Thrust -> Trust string replacement...

* Rename package to Wino Mail

* Enable translated values in the server.

* Fixed an issue where toast activation can't find the clicked mail after the folder is initialized.

* Revert debug code.

* Change server background sync to every 3 minute and Inbox only synchronization.

* Revert google auth changes.

* App preferences page.

* Changing tray icon visibility on preference change.

* Start the server with invisible tray icon if set to invisible.

* Reconnect button on the title bar.

* Handling of toast actions.

* Enable x86 build for server during packaging.

* Get rid of old background tasks and v180 migration.

* Terminate client when Exit clicked in server.

* Introducing SynchronizationSource to prevent notifying UI after server tick synchronization.

* Remove confirmAppClose restricted capability and unused debug code in manifest.

* Closing the reconnect info popup when reconnect is clicked.

* Custom RetryHandler for OutlookSynchronizer and separating client/server logs.

* Running server on Windows startup.

* Fix startup exe.

* Fix for expander list view item paddings.

* Force full sync on app launch instead of Inbox.

* Fix draft creation.

* Fix an issue with custom folder sync logic.

* Reporting back account sync progress from server.

* Fix sending drafts and missing notifications for imap.

* Changing imap folder sync requirements.

* Retain file  count is set to 3.

* Disabled swipe gestures temporarily due to native crash
 with SwipeControl

* Save all attachments implementation.

* Localization for save all attachments button.

* Fix logging dates for logs.

* Fixing ARM64 build.

* Add ARM64 build config to packaging project.

* Comment out OutOfProcPDB for ARM64.

* Hnadling GONE response for Outlook folder synchronization.
This commit is contained in:
Burak Kaan Köse
2024-08-05 00:36:26 +02:00
committed by GitHub
parent 4dc225184d
commit ff77b2b3dc
275 changed files with 4986 additions and 2381 deletions

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
@@ -10,26 +13,36 @@ using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.System.Profile;
using Windows.UI;
using Windows.UI.Notifications;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Activation;
using Wino.Core;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Services;
using Wino.Core.UWP;
using Wino.Core.UWP.Services;
using Wino.Mail.ViewModels;
using Wino.Messaging.Client.Connection;
using Wino.Messaging.Server;
using Wino.Services;
namespace Wino
{
public sealed partial class App : Application
public sealed partial class App : Application, IRecipient<NewSynchronizationRequested>
{
private const string WinoLaunchLogPrefix = "[Wino Launch] ";
private const string AppCenterKey = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72";
@@ -37,20 +50,25 @@ namespace Wino
public new static App Current => (App)Application.Current;
public IServiceProvider Services { get; }
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
private readonly IWinoServerConnectionManager<AppServiceConnection> _appServiceConnectionManager;
private readonly ILogInitializer _logInitializer;
private readonly IThemeService _themeService;
private readonly IDatabaseService _databaseService;
private readonly IAppInitializerService _appInitializerService;
private readonly IWinoSynchronizerFactory _synchronizerFactory;
private readonly IApplicationConfiguration _appInitializerService;
private readonly ITranslationService _translationService;
private readonly IApplicationConfiguration _applicationFolderConfiguration;
private readonly IDialogService _dialogService;
// Order matters.
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
{
_translationService,
_databaseService,
_appServiceConnectionManager,
_translationService,
_themeService,
_synchronizerFactory
};
public App()
@@ -61,6 +79,9 @@ namespace Wino
EnteredBackground += OnEnteredBackground;
LeavingBackground += OnLeavingBackground;
Resuming += OnResuming;
Suspending += OnSuspending;
Services = ConfigureServices();
_logInitializer = Services.GetService<ILogInitializer>();
@@ -70,13 +91,37 @@ namespace Wino
ConfigurePrelaunch();
ConfigureXbox();
_applicationFolderConfiguration = Services.GetService<IApplicationConfiguration>();
// Make sure the paths are setup on app start.
_applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
_applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
_appServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
_themeService = Services.GetService<IThemeService>();
_databaseService = Services.GetService<IDatabaseService>();
_appInitializerService = Services.GetService<IAppInitializerService>();
_synchronizerFactory = Services.GetService<IWinoSynchronizerFactory>();
_appInitializerService = Services.GetService<IApplicationConfiguration>();
_translationService = Services.GetService<ITranslationService>();
_dialogService = Services.GetService<IDialogService>();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
WeakReferenceMessenger.Default.Register(this);
}
private 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.
await _appServiceConnectionManager.ConnectAsync();
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
deferral.Complete();
}
private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
@@ -101,7 +146,7 @@ namespace Wino
private void RegisterActivationHandlers(IServiceCollection services)
{
services.AddTransient<ProtocolActivationHandler>();
services.AddTransient<BackgroundActivationHandler>();
// services.AddTransient<BackgroundActivationHandler>();
services.AddTransient<ToastNotificationActivationHandler>();
services.AddTransient<FileActivationHandler>();
}
@@ -138,13 +183,18 @@ namespace Wino
services.AddTransient(typeof(ReadComposePanePageViewModel));
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
services.AddTransient(typeof(LanguageTimePageViewModel));
services.AddTransient(typeof(AppPreferencesPageViewModel));
}
#endregion
#region Misc Configuration
private void ConfigureLogger() => _logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
private void ConfigureLogger()
{
string logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ClientLogFile);
_logInitializer.SetupLogger(logFilePath);
}
private void ConfigureAppCenter() => AppCenter.Start(AppCenterKey, typeof(Analytics), typeof(Crashes));
@@ -221,9 +271,79 @@ namespace Wino
{
base.OnBackgroundActivated(args);
LogActivation($"OnBackgroundActivated -> {args.GetType().Name}, TaskInstanceIdName -> {args.TaskInstance?.Task?.Name ?? "NA"}");
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
{
// Only accept connections from callers in the same package
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
// Connection established from the fulltrust process
await ActivateWinoAsync(args);
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
_appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstrablished());
}
}
else if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail toastNotificationActionTriggerDetail)
{
await InitializeServicesAsync();
// Notification action is triggered and the app is not running.
toastActionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnToastActionClickedBackgroundTaskCanceled;
var toastArguments = ToastArguments.Parse(toastNotificationActionTriggerDetail.Argument);
// All toast activation mail actions are handled here like mark as read or delete.
// This should not launch the application on the foreground.
// Get the action and mail item id.
// Prepare package and send to delegator.
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
toastArguments.TryGetValue(Constants.ToastMailUniqueIdKey, out string mailUniqueIdString) &&
Guid.TryParse(mailUniqueIdString, out Guid mailUniqueId))
{
// At this point server should've already been connected.
var processor = Services.GetService<IWinoRequestProcessor>();
var delegator = Services.GetService<IWinoRequestDelegator>();
var mailService = Services.GetService<IMailService>();
var mailItem = await mailService.GetSingleMailItemAsync(mailUniqueId);
if (mailItem != null)
{
var package = new MailOperationPreperationRequest(action, mailItem);
await delegator.ExecuteAsync(package);
}
}
toastActionBackgroundTaskDeferral.Complete();
}
else
{
// Other background activations might have handlers.
// AppServiceTrigger is handled here because delegating it to handlers somehow make it not work...
await ActivateWinoAsync(args);
}
}
private void OnToastActionClickedBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnToastActionClickedBackgroundTaskCanceled;
Log.Information($"Toast action background task was canceled. Reason: {reason}");
toastActionBackgroundTaskDeferral?.Complete();
toastActionBackgroundTaskDeferral = null;
}
private void OnAppUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
@@ -244,9 +364,17 @@ namespace Wino
private bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs;
private async Task InitializeServicesAsync()
{
foreach (var service in initializeServices)
{
await service.InitializeAsync();
}
}
private async Task ActivateWinoAsync(object args)
{
await PreInitializationAsync();
await InitializeServicesAsync();
if (IsInteractiveLaunchArgs(args))
{
@@ -270,37 +398,6 @@ namespace Wino
}
}
/// <summary>
/// Tasks that must run before the activation and launch.
/// Regardless of whether it's an interactive launch or not.
/// </summary>
private async Task PreInitializationAsync()
{
// Handle migrations.
// TODO: Automate migration process with more proper way.
if (!ApplicationData.Current.LocalSettings.Values.ContainsKey("Migration_169"))
{
try
{
await _appInitializerService.MigrateAsync();
}
catch (Exception ex)
{
Log.Error(ex, $"{WinoLaunchLogPrefix}Migration_169 failed.");
}
finally
{
ApplicationData.Current.LocalSettings.Values["Migration_169"] = true;
}
}
foreach (var service in initializeServices)
{
await service.InitializeAsync();
}
}
private async Task HandleActivationAsync(object activationArgs)
{
var activationHandler = GetActivationHandlers().FirstOrDefault(h => h.CanHandle(activationArgs));
@@ -323,9 +420,35 @@ namespace Wino
private IEnumerable<ActivationHandler> GetActivationHandlers()
{
yield return Services.GetService<ProtocolActivationHandler>();
yield return Services.GetService<BackgroundActivationHandler>();
yield return Services.GetService<ToastNotificationActivationHandler>();
yield return Services.GetService<FileActivationHandler>();
}
public async void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
Log.Information($"Background task {sender.Task.Name} was canceled. Reason: {reason}");
await _appServiceConnectionManager.DisconnectAsync();
connectionBackgroundTaskDeferral?.Complete();
connectionBackgroundTaskDeferral = null;
_appServiceConnectionManager.Connection = null;
}
public async void Receive(NewSynchronizationRequested message)
{
try
{
var synchronizationResultResponse = await _appServiceConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(message);
synchronizationResultResponse.ThrowIfFailed();
}
catch (WinoServerException serverException)
{
_dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
}
}
}
}