diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj index 4ac4ae89..94d25b84 100644 --- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj +++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj @@ -11,25 +11,26 @@ win-$(Platform).pubxml true true - 10.0.19041.35-preview - True - False - True - False - SHA256 - False - C:\Users\bkaan\Desktop\Packages\WinUI\ - True - True - Always - x86|x64|arm64 - 0 + 10.0.19041.35-preview + True + False + True + False + SHA256 + False + C:\Users\bkaan\Desktop\Packages\WinUI\ + True + True + Always + x86|x64|arm64 + 0 + diff --git a/Wino.Packaging/Wino.Packaging.wapproj b/Wino.Packaging/Wino.Packaging.wapproj index 3779c414..0d5d5472 100644 --- a/Wino.Packaging/Wino.Packaging.wapproj +++ b/Wino.Packaging/Wino.Packaging.wapproj @@ -56,7 +56,6 @@ en-US false $(NoWarn);NU1702 - ..\Wino.Mail\Wino.Mail.csproj False SHA256 False @@ -65,6 +64,7 @@ x64 True 0 + ..\Wino.Mail.WinUI\Wino.Mail.WinUI.csproj Always @@ -157,10 +157,10 @@ - + - - + + \ No newline at end of file diff --git a/Wino.Server.NET8/App.xaml b/Wino.Server.NET8/App.xaml index 69096344..5058d6b7 100644 --- a/Wino.Server.NET8/App.xaml +++ b/Wino.Server.NET8/App.xaml @@ -1,16 +1,16 @@ - + + xmlns:local="using:Wino.Server.NET8" + xmlns:styles="using:Wino.Server.Styles"> - + - diff --git a/Wino.Server.NET8/App.xaml.cs b/Wino.Server.NET8/App.xaml.cs index 0c435100..1b6b410d 100644 --- a/Wino.Server.NET8/App.xaml.cs +++ b/Wino.Server.NET8/App.xaml.cs @@ -1,32 +1,87 @@ -using H.NotifyIcon; +using System; +using System.Threading.Tasks; +using H.NotifyIcon; +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; +using Windows.Storage; +using Wino.Core; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Services; +using Wino.Core.UWP.Services; +using Wino.Services; namespace Wino.Server.NET8 { public partial class App : Application { - public TaskbarIcon? TrayIcon { get; private set; } - public Window? Window { get; set; } + public new static App Current => (App)Application.Current; + private const string WinoServerAppName = "Wino.Server"; + + public TaskbarIcon TrayIcon { get; private set; } public bool HandleClosedEvents { get; set; } = true; + public IServiceProvider Services { get; private set; } public App() { InitializeComponent(); } - protected override void OnLaunched(LaunchActivatedEventArgs args) + private IServiceProvider ConfigureServices() { + var services = new ServiceCollection(); + services.AddTransient(); + services.AddTransient(); + + services.RegisterCoreServices(); + + // Below services belongs to UWP.Core package and some APIs are not available for WPF. + // We register them here to avoid compilation errors. + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services.BuildServiceProvider(); + } + + protected override async void OnLaunched(LaunchActivatedEventArgs args) + { + Services = ConfigureServices(); + + await InitializeNewServerAsync(); + InitializeTrayIcon(); + } + + private async Task InitializeNewServerAsync() + { + // TODO: Error handling. + + var databaseService = Services.GetService(); + var applicationFolderConfiguration = Services.GetService(); + + applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; + applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; + + await databaseService.InitializeAsync(); + + var serverViewModel = Services.GetRequiredService(); + + await serverViewModel.InitializeAsync(); + + return serverViewModel; } private void InitializeTrayIcon() { - var showHideWindowCommand = (XamlUICommand)Resources["ShowHideWindowCommand"]; - // showHideWindowCommand.ExecuteRequested ; + var viewModel = Services.GetService(); - var exitApplicationCommand = (XamlUICommand)Resources["ExitApplicationCommand"]; - //exitApplicationCommand.ExecuteRequested += ExitApplicationCommand_ExecuteRequested; + var launchCommand = (XamlUICommand)Resources["LaunchCommand"]; + launchCommand.Command = viewModel.LaunchWinoCommand; + + var exitApplicationCommand = (XamlUICommand)Resources["TerminateCommand"]; + exitApplicationCommand.Command = viewModel.ExitApplicationCommand; TrayIcon = (TaskbarIcon)Resources["TrayIcon"]; TrayIcon.ForceCreate(); diff --git a/Wino.Server.NET8/ServerContext.cs b/Wino.Server.NET8/ServerContext.cs new file mode 100644 index 00000000..5471c501 --- /dev/null +++ b/Wino.Server.NET8/ServerContext.cs @@ -0,0 +1,217 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Extensions.DependencyInjection; +using Windows.ApplicationModel; +using Windows.ApplicationModel.AppService; +using Windows.Foundation.Collections; +using Wino.Core.Authenticators; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Synchronization; +using Wino.Core.Integration.Processors; +using Wino.Core.Services; +using Wino.Core.Synchronizers; +using Wino.Messaging; +using Wino.Messaging.Enums; +using Wino.Messaging.Server; +using Wino.Server.NET8; + +namespace Wino.Server +{ + public class ServerContext : + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient, + IRecipient + { + private static object connectionLock = new object(); + + private AppServiceConnection connection = null; + + private readonly IDatabaseService _databaseService; + private readonly IApplicationConfiguration _applicationFolderConfiguration; + + public ServerContext(IDatabaseService databaseService, IApplicationConfiguration applicationFolderConfiguration) + { + _databaseService = databaseService; + _applicationFolderConfiguration = applicationFolderConfiguration; + + WeakReferenceMessenger.Default.RegisterAll(this); + } + + #region Message Handlers + + public async void Receive(MailAddedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(AccountCreatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(AccountUpdatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(AccountRemovedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(DraftCreated message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(DraftFailed message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(DraftMapped message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(FolderRenamed message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(FolderSynchronizationEnabled message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(MailDownloadedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(MailRemovedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(MailUpdatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message); + + public async void Receive(MergedInboxRenamed message) => await SendMessageAsync(MessageType.UIMessage, message); + + #endregion + /// + /// Open connection to UWP app service + /// + public async Task InitializeAppServiceConnectionAsync() + { + if (connection != null) DisposeConnection(); + + connection = new AppServiceConnection + { + AppServiceName = "WinoInteropService", + PackageFamilyName = GetAppPackagFamilyName() + }; + + connection.RequestReceived += OnWinRTMessageReceived; + connection.ServiceClosed += OnConnectionClosed; + + AppServiceConnectionStatus status = await connection.OpenAsync(); + + if (status != AppServiceConnectionStatus.Success) + { + // TODO: Handle connection error + + DisposeConnection(); + } + } + + public async Task TestOutlookSynchronizer() + { + var accountService = App.Current.Services.GetService(); + + var accs = await accountService.GetAccountsAsync(); + var acc = accs.ElementAt(0); + + var authenticator = App.Current.Services.GetService(); + var processor = App.Current.Services.GetService(); + + var sync = new OutlookSynchronizer(acc, authenticator, processor); + + var options = new SynchronizationOptions() + { + AccountId = acc.Id, + Type = Core.Domain.Enums.SynchronizationType.Full + }; + + var result = await sync.SynchronizeAsync(options); + } + + /// + /// Disposes current connection to UWP app service. + /// + private void DisposeConnection() + { + lock (connectionLock) + { + if (connection == null) return; + + connection.RequestReceived -= OnWinRTMessageReceived; + connection.ServiceClosed -= OnConnectionClosed; + + connection.Dispose(); + connection = null; + } + } + + /// + /// Sends a serialized object to UWP application if connection exists with given type. + /// + /// Type of the message. + /// IServerMessage object that will be serialized. + /// + /// When the message is not IServerMessage. + private async Task SendMessageAsync(MessageType messageType, object message) + { + if (connection == null) return; + + if (message is not IServerMessage serverMessage) + throw new ArgumentException("Server message must be a type of IServerMessage"); + + string json = JsonSerializer.Serialize(message); + + var set = new ValueSet + { + { MessageConstants.MessageTypeKey, (int)messageType }, + { MessageConstants.MessageDataKey, json }, + { MessageConstants.MessageDataTypeKey, message.GetType().Name } + }; + + Debug.WriteLine($"S: {messageType} ({message.GetType().Name})"); + await connection.SendMessageAsync(set); + } + + private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) + { + // TODO: Handle connection closed. + + // UWP app might've been terminated or suspended. + // At this point, we must keep active synchronizations going, but connection is lost. + // As long as this process is alive, database will be kept updated, but no messages will be sent. + + DisposeConnection(); + } + + private void OnWinRTMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) + { + // TODO: Handle incoming messages from UWP/WINUI Application. + + } + + #region Init + + private string GetAppPackagFamilyName() + { + // If running as a standalone app, Package will throw exception. + // Return hardcoded value for debugging purposes. + // Connection will not be available in this case. + + try + { + return Package.Current.Id.FamilyName; + } + catch (Exception) + { + return "Debug.Wino.Server.FamilyName"; + } + } + + public async Task InitializeAsync() + { + + await InitializeAppServiceConnectionAsync(); + } + + #endregion + } +} diff --git a/Wino.Server.NET8/ServerViewModel.cs b/Wino.Server.NET8/ServerViewModel.cs new file mode 100644 index 00000000..c6bbacb0 --- /dev/null +++ b/Wino.Server.NET8/ServerViewModel.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Wino.Core.Domain.Interfaces; + +namespace Wino.Server +{ + public partial class ServerViewModel : ObservableObject, IInitializeAsync + { + public ServerContext Context { get; } + + public ServerViewModel(ServerContext serverContext) + { + Context = serverContext; + } + + [RelayCommand] + public async Task LaunchWinoAsync() + { + await Context.TestOutlookSynchronizer(); + // ServerContext.SendTestMessageAsync(); + } + + /// + /// Shuts down the application. + /// + [RelayCommand] + public void ExitApplication() + { + // TODO: App service send message to UWP app to terminate itself. + } + + public async Task ReconnectAsync() => await Context.InitializeAppServiceConnectionAsync(); + + public Task InitializeAsync() => Context.InitializeAppServiceConnectionAsync(); + } +} diff --git a/Wino.Server.NET8/TrayIconResources.xaml b/Wino.Server.NET8/Styles/TrayIconResources.xaml similarity index 86% rename from Wino.Server.NET8/TrayIconResources.xaml rename to Wino.Server.NET8/Styles/TrayIconResources.xaml index b42183ca..f3adbab7 100644 --- a/Wino.Server.NET8/TrayIconResources.xaml +++ b/Wino.Server.NET8/Styles/TrayIconResources.xaml @@ -1,13 +1,12 @@  + xmlns:local="using:Wino.Server" + xmlns:tb="using:H.NotifyIcon"> - + IconSource="\Assets\Wino_Icon.ico"> diff --git a/Wino.Server.NET8/Styles/TrayIconResources.xaml.cs b/Wino.Server.NET8/Styles/TrayIconResources.xaml.cs new file mode 100644 index 00000000..07d5ffca --- /dev/null +++ b/Wino.Server.NET8/Styles/TrayIconResources.xaml.cs @@ -0,0 +1,12 @@ +using Microsoft.UI.Xaml; + +namespace Wino.Server.Styles +{ + partial class TrayIconResources : ResourceDictionary + { + public TrayIconResources() + { + InitializeComponent(); + } + } +} diff --git a/Wino.Server.NET8/TrayIconResources.xaml.cs b/Wino.Server.NET8/TrayIconResources.xaml.cs deleted file mode 100644 index 59014a5c..00000000 --- a/Wino.Server.NET8/TrayIconResources.xaml.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.UI.Xaml; - -namespace Wino.Server -{ - partial class TrayIconResources : ResourceDictionary - { - public TrayIconResources() - { - this.InitializeComponent(); - } - } -} diff --git a/Wino.Server.NET8/Wino.Server.NET8.csproj b/Wino.Server.NET8/Wino.Server.NET8.csproj index b1ec355e..7a31aadc 100644 --- a/Wino.Server.NET8/Wino.Server.NET8.csproj +++ b/Wino.Server.NET8/Wino.Server.NET8.csproj @@ -6,11 +6,13 @@ Wino.Server app.manifest x86;x64;ARM64 + true win-x86;win-x64;win-arm64 win10-x86;win10-x64;win10-arm64 win-$(Platform).pubxml true true + false @@ -34,12 +36,13 @@ - + + + - - + @@ -86,6 +89,10 @@ MSBuild:Compile + + + +