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:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Services;
|
||||
using Wino.Services;
|
||||
@@ -9,19 +10,24 @@ namespace Wino.Core.UWP
|
||||
{
|
||||
public static void RegisterCoreUWPServices(this IServiceCollection services)
|
||||
{
|
||||
var serverConnectionManager = new WinoServerConnectionManager();
|
||||
|
||||
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
|
||||
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
|
||||
|
||||
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
|
||||
services.AddSingleton<INativeAppService, NativeAppService>();
|
||||
services.AddSingleton<IStoreManagementService, StoreManagementService>();
|
||||
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
|
||||
|
||||
services.AddTransient<IAppInitializerService, AppInitializerService>();
|
||||
|
||||
services.AddTransient<IConfigurationService, ConfigurationService>();
|
||||
services.AddTransient<IFileService, FileService>();
|
||||
services.AddTransient<IStoreRatingService, StoreRatingService>();
|
||||
services.AddTransient<IKeyPressService, KeyPressService>();
|
||||
services.AddTransient<IBackgroundSynchronizer, BackgroundSynchronizer>();
|
||||
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
||||
services.AddTransient<IClipboardService, ClipboardService>();
|
||||
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
Wino.Core.UWP/Extensions/StartupTaskStateExtensions.cs
Normal file
25
Wino.Core.UWP/Extensions/StartupTaskStateExtensions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Windows.ApplicationModel;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.UWP.Extensions
|
||||
{
|
||||
public static class StartupTaskStateExtensions
|
||||
{
|
||||
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case StartupTaskState.Disabled:
|
||||
case StartupTaskState.DisabledByPolicy:
|
||||
return StartupBehaviorResult.Disabled;
|
||||
case StartupTaskState.DisabledByUser:
|
||||
return StartupBehaviorResult.DisabledByUser;
|
||||
case StartupTaskState.Enabled:
|
||||
case StartupTaskState.EnabledByPolicy:
|
||||
return StartupBehaviorResult.Enabled;
|
||||
default:
|
||||
return StartupBehaviorResult.Fatal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class AppInitializerService : IAppInitializerService
|
||||
{
|
||||
private readonly IBackgroundTaskService _backgroundTaskService;
|
||||
|
||||
public AppInitializerService(IBackgroundTaskService backgroundTaskService)
|
||||
{
|
||||
_backgroundTaskService = backgroundTaskService;
|
||||
}
|
||||
|
||||
public string GetPublisherSharedFolder() => ApplicationData.Current.GetPublisherCacheFolder("WinoShared").Path;
|
||||
public string GetApplicationDataFolder() => ApplicationData.Current.LocalFolder.Path;
|
||||
|
||||
public Task MigrateAsync()
|
||||
{
|
||||
UnregisterAllBackgroundTasks();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#region 1.6.8 -> 1.6.9
|
||||
|
||||
private void UnregisterAllBackgroundTasks()
|
||||
{
|
||||
_backgroundTaskService.UnregisterAllBackgroundTask();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 1.7.0
|
||||
|
||||
/// <summary>
|
||||
/// We decided to use publisher cache folder as a database going forward.
|
||||
/// This migration will move the file from application local folder and delete it.
|
||||
/// Going forward database will be initialized from publisher cache folder.
|
||||
/// </summary>
|
||||
private async Task MoveExistingDatabaseToSharedCacheFolderAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Windows.Storage;
|
||||
using Wino.Core;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Synchronizers;
|
||||
|
||||
namespace Wino.Services
|
||||
{
|
||||
public interface IBackgroundSynchronizer
|
||||
{
|
||||
Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason);
|
||||
void CreateLock();
|
||||
void ReleaseLock();
|
||||
bool IsBackgroundSynchronizationLocked();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for handling background synchronization on timer and session connected events.
|
||||
/// </summary>
|
||||
public class BackgroundSynchronizer : IBackgroundSynchronizer
|
||||
{
|
||||
private const string BackgroundSynchronizationLock = nameof(BackgroundSynchronizationLock);
|
||||
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
|
||||
|
||||
public BackgroundSynchronizer(IAccountService accountService,
|
||||
IFolderService folderService,
|
||||
IWinoSynchronizerFactory winoSynchronizerFactory)
|
||||
{
|
||||
_accountService = accountService;
|
||||
_folderService = folderService;
|
||||
_winoSynchronizerFactory = winoSynchronizerFactory;
|
||||
}
|
||||
|
||||
public void CreateLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = true;
|
||||
public void ReleaseLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = false;
|
||||
|
||||
public bool IsBackgroundSynchronizationLocked()
|
||||
=> ApplicationData.Current.LocalSettings.Values.ContainsKey(BackgroundSynchronizationLock)
|
||||
&& ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] is bool boolValue && boolValue;
|
||||
|
||||
public async Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason)
|
||||
{
|
||||
Log.Information($"{reason} background synchronization is kicked in.");
|
||||
|
||||
// This should never crash.
|
||||
// We might be in-process or out-of-process.
|
||||
|
||||
//if (IsBackgroundSynchronizationLocked())
|
||||
//{
|
||||
// Log.Warning("Background synchronization is locked. Hence another background synchronization is canceled.");
|
||||
// return;
|
||||
//}
|
||||
|
||||
try
|
||||
{
|
||||
CreateLock();
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
// We can't sync broken account.
|
||||
if (account.AttentionReason != AccountAttentionReason.None)
|
||||
continue;
|
||||
|
||||
// TODO
|
||||
// We can't synchronize without system folder setup is done.
|
||||
//var isSystemFolderSetupDone = await _folderService.CheckSystemFolderSetupDoneAsync(account.Id);
|
||||
|
||||
//// No need to throw here. It's a background process.
|
||||
//if (!isSystemFolderSetupDone)
|
||||
// continue;
|
||||
|
||||
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(account.Id);
|
||||
|
||||
if (synchronizer.State != AccountSynchronizerState.Idle)
|
||||
{
|
||||
Log.Information("Skipping background synchronization for {Name} since current state is {State}", synchronizer.Account.Name, synchronizer.State);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await HandleSynchronizationAsync(synchronizer, reason);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[BackgroundSynchronization] Failed with message {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSynchronizationAsync(IBaseSynchronizer synchronizer, BackgroundSynchronizationReason reason)
|
||||
{
|
||||
if (synchronizer.State != AccountSynchronizerState.Idle) return;
|
||||
|
||||
var account = synchronizer.Account;
|
||||
|
||||
try
|
||||
{
|
||||
// SessionConnected will do Full synchronization for logon, Timer task will do Inbox only.
|
||||
|
||||
var syncType = reason == BackgroundSynchronizationReason.SessionConnected ? SynchronizationType.Full : SynchronizationType.Inbox;
|
||||
|
||||
var options = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = syncType,
|
||||
};
|
||||
|
||||
await synchronizer.SynchronizeAsync(options);
|
||||
}
|
||||
catch (AuthenticationAttentionException authenticationAttentionException)
|
||||
{
|
||||
Log.Error(authenticationAttentionException, $"[BackgroundSync] Invalid credentials for account {account.Address}");
|
||||
|
||||
account.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
||||
await _accountService.UpdateAccountAsync(account);
|
||||
}
|
||||
catch (SystemFolderConfigurationMissingException configMissingException)
|
||||
{
|
||||
Log.Error(configMissingException, $"[BackgroundSync] Missing system folder configuration for account {account.Address}");
|
||||
|
||||
account.AttentionReason = AccountAttentionReason.MissingSystemFolderConfiguration;
|
||||
await _accountService.UpdateAccountAsync(account);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "[BackgroundSync] Synchronization failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class BackgroundTaskService : IBackgroundTaskService
|
||||
{
|
||||
private const string IsBackgroundExecutionDeniedMessageKey = nameof(IsBackgroundExecutionDeniedMessageKey);
|
||||
private const string Is180BackgroundTasksRegisteredKey = nameof(Is180BackgroundTasksRegisteredKey);
|
||||
|
||||
public const string BackgroundSynchronizationTimerTaskNameEx = nameof(BackgroundSynchronizationTimerTaskNameEx);
|
||||
public const string ToastActivationTaskEx = nameof(ToastActivationTaskEx);
|
||||
|
||||
private const string SessionConnectedTaskEntryPoint = "Wino.BackgroundTasks.SessionConnectedTask";
|
||||
private const string SessionConnectedTaskName = "SessionConnectedTask";
|
||||
|
||||
private readonly IConfigurationService _configurationService;
|
||||
private readonly List<string> registeredBackgroundTaskNames = new List<string>();
|
||||
|
||||
public BackgroundTaskService(IConfigurationService configurationService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
|
||||
LoadRegisteredTasks();
|
||||
}
|
||||
|
||||
// Calling WinRT all the time for registered tasks might be slow. Cache them on ctor.
|
||||
private void LoadRegisteredTasks()
|
||||
{
|
||||
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
||||
{
|
||||
registeredBackgroundTaskNames.Add(task.Value.Name);
|
||||
}
|
||||
|
||||
Log.Information($"Found {registeredBackgroundTaskNames.Count} registered background tasks. [{string.Join(',', registeredBackgroundTaskNames)}]");
|
||||
}
|
||||
|
||||
public async Task HandleBackgroundTaskRegistrations()
|
||||
{
|
||||
bool is180BackgroundTaskRegistered = _configurationService.Get<bool>(Is180BackgroundTasksRegisteredKey);
|
||||
|
||||
// Don't re-register tasks.
|
||||
if (is180BackgroundTaskRegistered) return;
|
||||
|
||||
var response = await BackgroundExecutionManager.RequestAccessAsync();
|
||||
|
||||
if (response == BackgroundAccessStatus.DeniedBySystemPolicy ||
|
||||
response == BackgroundAccessStatus.DeniedByUser)
|
||||
if (response != BackgroundAccessStatus.DeniedBySystemPolicy ||
|
||||
response != BackgroundAccessStatus.DeniedByUser)
|
||||
{
|
||||
// Only notify users about disabled background execution once.
|
||||
// Unregister all tasks and register new ones.
|
||||
|
||||
bool isNotifiedBefore = _configurationService.Get(IsBackgroundExecutionDeniedMessageKey, false);
|
||||
|
||||
if (!isNotifiedBefore)
|
||||
{
|
||||
_configurationService.Set(IsBackgroundExecutionDeniedMessageKey, true);
|
||||
|
||||
throw new BackgroundTaskExecutionRequestDeniedException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnregisterAllBackgroundTask();
|
||||
RegisterSessionConnectedTask();
|
||||
RegisterTimerSynchronizationTask();
|
||||
RegisterToastNotificationHandlerBackgroundTask();
|
||||
|
||||
_configurationService.Set(Is180BackgroundTasksRegisteredKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsBackgroundTaskRegistered(string taskName)
|
||||
=> registeredBackgroundTaskNames.Contains(taskName);
|
||||
|
||||
public void UnregisterAllBackgroundTask()
|
||||
{
|
||||
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
||||
{
|
||||
task.Value.Unregister(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogBackgroundTaskRegistration(string taskName)
|
||||
{
|
||||
Log.Information($"Registered new background task -> {taskName}");
|
||||
|
||||
registeredBackgroundTaskNames.Add($"{taskName}");
|
||||
Log.Information("Unregistered all background tasks.");
|
||||
}
|
||||
|
||||
private BackgroundTaskRegistration RegisterSessionConnectedTask()
|
||||
{
|
||||
if (IsBackgroundTaskRegistered(SessionConnectedTaskName)) return null;
|
||||
|
||||
var builder = new BackgroundTaskBuilder
|
||||
{
|
||||
Name = SessionConnectedTaskName,
|
||||
@@ -95,41 +63,6 @@ namespace Wino.Core.UWP.Services
|
||||
|
||||
builder.SetTrigger(new SystemTrigger(SystemTriggerType.SessionConnected, false));
|
||||
|
||||
LogBackgroundTaskRegistration(SessionConnectedTaskName);
|
||||
|
||||
return builder.Register();
|
||||
}
|
||||
|
||||
private BackgroundTaskRegistration RegisterToastNotificationHandlerBackgroundTask()
|
||||
{
|
||||
if (IsBackgroundTaskRegistered(ToastActivationTaskEx)) return null;
|
||||
|
||||
var builder = new BackgroundTaskBuilder
|
||||
{
|
||||
Name = ToastActivationTaskEx
|
||||
};
|
||||
|
||||
builder.SetTrigger(new ToastNotificationActionTrigger());
|
||||
|
||||
LogBackgroundTaskRegistration(ToastActivationTaskEx);
|
||||
|
||||
return builder.Register();
|
||||
}
|
||||
|
||||
private BackgroundTaskRegistration RegisterTimerSynchronizationTask()
|
||||
{
|
||||
if (IsBackgroundTaskRegistered(BackgroundSynchronizationTimerTaskNameEx)) return null;
|
||||
|
||||
var builder = new BackgroundTaskBuilder
|
||||
{
|
||||
Name = BackgroundSynchronizationTimerTaskNameEx
|
||||
};
|
||||
|
||||
builder.SetTrigger(new TimeTrigger(15, false));
|
||||
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
|
||||
|
||||
LogBackgroundTaskRegistration(BackgroundSynchronizationTimerTaskNameEx);
|
||||
|
||||
return builder.Register();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,17 @@ using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.System;
|
||||
using Windows.UI.Shell;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authorization;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain;
|
||||
|
||||
|
||||
|
||||
#if WINDOWS_UWP
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
#endif
|
||||
|
||||
namespace Wino.Services
|
||||
{
|
||||
@@ -20,8 +27,18 @@ namespace Wino.Services
|
||||
{
|
||||
private string _mimeMessagesFolder;
|
||||
private string _editorBundlePath;
|
||||
private TaskCompletionSource<Uri> authorizationCompletedTaskSource;
|
||||
|
||||
public string GetWebAuthenticationBrokerUri() => WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
|
||||
public Func<IntPtr> GetCoreWindowHwnd { get; set; }
|
||||
|
||||
public string GetWebAuthenticationBrokerUri()
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
return WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
|
||||
#endif
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<string> GetMimeMessageStoragePath()
|
||||
{
|
||||
@@ -91,7 +108,16 @@ namespace Wino.Services
|
||||
return _editorBundlePath;
|
||||
}
|
||||
|
||||
public bool IsAppRunning() => (Window.Current?.Content as Frame)?.Content != null;
|
||||
[Obsolete("This should be removed. There should be no functionality.")]
|
||||
public bool IsAppRunning()
|
||||
{
|
||||
#if WINDOWS_UWP
|
||||
return (Window.Current?.Content as Frame)?.Content != null;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public async Task LaunchFileAsync(string filePath)
|
||||
{
|
||||
@@ -100,7 +126,7 @@ namespace Wino.Services
|
||||
await Launcher.LaunchFileAsync(file);
|
||||
}
|
||||
|
||||
public Task LaunchUriAsync(Uri uri) => Xamarin.Essentials.Launcher.OpenAsync(uri);
|
||||
public Task LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask();
|
||||
|
||||
public string GetFullAppVersion()
|
||||
{
|
||||
@@ -127,5 +153,28 @@ namespace Wino.Services
|
||||
|
||||
await taskbarManager.RequestPinCurrentAppAsync();
|
||||
}
|
||||
|
||||
public async Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri)
|
||||
{
|
||||
if (authorizationCompletedTaskSource != null)
|
||||
{
|
||||
authorizationCompletedTaskSource.TrySetException(new AuthenticationException(Translator.Exception_AuthenticationCanceled));
|
||||
authorizationCompletedTaskSource = null;
|
||||
}
|
||||
|
||||
authorizationCompletedTaskSource = new TaskCompletionSource<Uri>();
|
||||
|
||||
await LaunchUriAsync(new Uri(authorizationUri));
|
||||
|
||||
return await authorizationCompletedTaskSource.Task;
|
||||
}
|
||||
|
||||
public void ContinueAuthorization(Uri authorizationResponseUri)
|
||||
{
|
||||
if (authorizationCompletedTaskSource != null)
|
||||
{
|
||||
authorizationCompletedTaskSource.TrySetResult(authorizationResponseUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using CommunityToolkit.WinUI.Notifications;
|
||||
using Windows.Data.Xml.Dom;
|
||||
using Windows.UI.Notifications;
|
||||
using Wino.Core.Domain;
|
||||
@@ -70,8 +70,8 @@ namespace Wino.Core.UWP.Services
|
||||
|
||||
foreach (var mailItem in validItems)
|
||||
{
|
||||
if (mailItem.IsRead)
|
||||
continue;
|
||||
//if (mailItem.IsRead)
|
||||
// continue;
|
||||
|
||||
var builder = new ToastContentBuilder();
|
||||
builder.SetToastScenario(ToastScenario.Default);
|
||||
@@ -104,11 +104,11 @@ namespace Wino.Core.UWP.Services
|
||||
builder.AddText(mailItem.Subject);
|
||||
builder.AddText(mailItem.PreviewText);
|
||||
|
||||
builder.AddArgument(Constants.ToastMailItemIdKey, mailItem.UniqueId.ToString());
|
||||
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
||||
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
|
||||
|
||||
builder.AddButton(GetMarkedAsRead(mailItem.Id, mailItem.AssignedFolder.RemoteFolderId));
|
||||
builder.AddButton(GetDeleteButton(mailItem.Id, mailItem.AssignedFolder.RemoteFolderId));
|
||||
builder.AddButton(GetMarkedAsRead(mailItem.UniqueId));
|
||||
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
||||
builder.AddButton(GetDismissButton());
|
||||
|
||||
builder.Show();
|
||||
@@ -123,21 +123,19 @@ namespace Wino.Core.UWP.Services
|
||||
.SetDismissActivation()
|
||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
|
||||
|
||||
private ToastButton GetDeleteButton(string mailCopyId, string remoteFolderId)
|
||||
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
||||
=> new ToastButton()
|
||||
.SetContent(Translator.MailOperation_Delete)
|
||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/delete.png"))
|
||||
.AddArgument(Constants.ToastMailItemIdKey, mailCopyId)
|
||||
.AddArgument(Constants.ToastMailItemRemoteFolderIdKey, remoteFolderId)
|
||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
||||
.SetBackgroundActivation();
|
||||
|
||||
private ToastButton GetMarkedAsRead(string mailCopyId, string remoteFolderId)
|
||||
private ToastButton GetMarkedAsRead(Guid mailUniqueId)
|
||||
=> new ToastButton()
|
||||
.SetContent(Translator.MailOperation_MarkAsRead)
|
||||
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
|
||||
.AddArgument(Constants.ToastMailItemIdKey, mailCopyId)
|
||||
.AddArgument(Constants.ToastMailItemRemoteFolderIdKey, remoteFolderId)
|
||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
|
||||
.SetBackgroundActivation();
|
||||
|
||||
|
||||
211
Wino.Core.UWP/Services/PreferencesService.cs
Normal file
211
Wino.Core.UWP/Services/PreferencesService.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Core.Services;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class PreferencesService : ObservableObject, IPreferencesService
|
||||
{
|
||||
private readonly IConfigurationService _configurationService;
|
||||
|
||||
public event EventHandler<string> PreferenceChanged;
|
||||
|
||||
public PreferencesService(IConfigurationService configurationService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
PreferenceChanged?.Invoke(this, e.PropertyName);
|
||||
}
|
||||
|
||||
private void SaveProperty(string propertyName, object value) => _configurationService.Set(propertyName, value);
|
||||
|
||||
private void SetPropertyAndSave(string propertyName, object value)
|
||||
{
|
||||
_configurationService.Set(propertyName, value);
|
||||
|
||||
OnPropertyChanged(propertyName);
|
||||
Debug.WriteLine($"PreferencesService -> {propertyName}:{value?.ToString()}");
|
||||
}
|
||||
|
||||
public MailRenderingOptions GetRenderingOptions()
|
||||
=> new MailRenderingOptions() { LoadImages = RenderImages, LoadStyles = RenderStyles };
|
||||
|
||||
public MailListDisplayMode MailItemDisplayMode
|
||||
{
|
||||
get => _configurationService.Get(nameof(MailItemDisplayMode), MailListDisplayMode.Spacious);
|
||||
set => SetPropertyAndSave(nameof(MailItemDisplayMode), value);
|
||||
}
|
||||
|
||||
public bool IsSemanticZoomEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsSemanticZoomEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsSemanticZoomEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsHardDeleteProtectionEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsHardDeleteProtectionEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsHardDeleteProtectionEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsThreadingEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsThreadingEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsThreadingEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsShowSenderPicturesEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsShowSenderPicturesEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsShowPreviewEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsShowPreviewEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsShowPreviewEnabled), value);
|
||||
}
|
||||
|
||||
public bool RenderStyles
|
||||
{
|
||||
get => _configurationService.Get(nameof(RenderStyles), true);
|
||||
set => SetPropertyAndSave(nameof(RenderStyles), value);
|
||||
}
|
||||
|
||||
public bool RenderImages
|
||||
{
|
||||
get => _configurationService.Get(nameof(RenderImages), true);
|
||||
set => SetPropertyAndSave(nameof(RenderImages), value);
|
||||
}
|
||||
|
||||
public bool Prefer24HourTimeFormat
|
||||
{
|
||||
get => _configurationService.Get(nameof(Prefer24HourTimeFormat), false);
|
||||
set => SetPropertyAndSave(nameof(Prefer24HourTimeFormat), value);
|
||||
}
|
||||
|
||||
public MailMarkAsOption MarkAsPreference
|
||||
{
|
||||
get => _configurationService.Get(nameof(MarkAsPreference), MailMarkAsOption.WhenSelected);
|
||||
set => SetPropertyAndSave(nameof(MarkAsPreference), value);
|
||||
}
|
||||
|
||||
public int MarkAsDelay
|
||||
{
|
||||
get => _configurationService.Get(nameof(MarkAsDelay), 5);
|
||||
set => SetPropertyAndSave(nameof(MarkAsDelay), value);
|
||||
}
|
||||
|
||||
public MailOperation RightSwipeOperation
|
||||
{
|
||||
get => _configurationService.Get(nameof(RightSwipeOperation), MailOperation.MarkAsRead);
|
||||
set => SetPropertyAndSave(nameof(RightSwipeOperation), value);
|
||||
}
|
||||
|
||||
public MailOperation LeftSwipeOperation
|
||||
{
|
||||
get => _configurationService.Get(nameof(LeftSwipeOperation), MailOperation.SoftDelete);
|
||||
set => SetPropertyAndSave(nameof(LeftSwipeOperation), value);
|
||||
}
|
||||
|
||||
public bool IsHoverActionsEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsHoverActionsEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsHoverActionsEnabled), value);
|
||||
}
|
||||
|
||||
public MailOperation LeftHoverAction
|
||||
{
|
||||
get => _configurationService.Get(nameof(LeftHoverAction), MailOperation.Archive);
|
||||
set => SetPropertyAndSave(nameof(LeftHoverAction), value);
|
||||
}
|
||||
|
||||
public MailOperation CenterHoverAction
|
||||
{
|
||||
get => _configurationService.Get(nameof(CenterHoverAction), MailOperation.SoftDelete);
|
||||
set => SetPropertyAndSave(nameof(CenterHoverAction), value);
|
||||
}
|
||||
|
||||
public MailOperation RightHoverAction
|
||||
{
|
||||
get => _configurationService.Get(nameof(RightHoverAction), MailOperation.SetFlag);
|
||||
set => SetPropertyAndSave(nameof(RightHoverAction), value);
|
||||
}
|
||||
|
||||
public bool IsLoggingEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsLoggingEnabled), true);
|
||||
set => SetPropertyAndSave(nameof(IsLoggingEnabled), value);
|
||||
}
|
||||
|
||||
public bool IsMailkitProtocolLoggerEnabled
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsMailkitProtocolLoggerEnabled), false);
|
||||
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
|
||||
}
|
||||
|
||||
public Guid? StartupEntityId
|
||||
{
|
||||
get => _configurationService.Get<Guid?>(nameof(StartupEntityId), null);
|
||||
set => SaveProperty(propertyName: nameof(StartupEntityId), value);
|
||||
}
|
||||
|
||||
public AppLanguage CurrentLanguage
|
||||
{
|
||||
get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage);
|
||||
set => SaveProperty(propertyName: nameof(CurrentLanguage), value);
|
||||
}
|
||||
|
||||
public string ReaderFont
|
||||
{
|
||||
get => _configurationService.Get(nameof(ReaderFont), "Calibri");
|
||||
set => SaveProperty(propertyName: nameof(ReaderFont), value);
|
||||
}
|
||||
|
||||
public int ReaderFontSize
|
||||
{
|
||||
get => _configurationService.Get(nameof(ReaderFontSize), 14);
|
||||
set => SaveProperty(propertyName: nameof(ReaderFontSize), value);
|
||||
}
|
||||
|
||||
public string ComposerFont
|
||||
{
|
||||
get => _configurationService.Get(nameof(ComposerFont), "Calibri");
|
||||
set => SaveProperty(propertyName: nameof(ComposerFont), value);
|
||||
}
|
||||
|
||||
public int ComposerFontSize
|
||||
{
|
||||
get => _configurationService.Get(nameof(ComposerFontSize), 14);
|
||||
set => SaveProperty(propertyName: nameof(ComposerFontSize), value);
|
||||
}
|
||||
|
||||
public bool IsNavigationPaneOpened
|
||||
{
|
||||
get => _configurationService.Get(nameof(IsNavigationPaneOpened), true);
|
||||
set => SaveProperty(propertyName: nameof(IsNavigationPaneOpened), value);
|
||||
}
|
||||
|
||||
public bool AutoSelectNextItem
|
||||
{
|
||||
get => _configurationService.Get(nameof(AutoSelectNextItem), true);
|
||||
set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value);
|
||||
}
|
||||
|
||||
public ServerBackgroundMode ServerTerminationBehavior
|
||||
{
|
||||
get => _configurationService.Get(nameof(ServerTerminationBehavior), ServerBackgroundMode.MinimizedTray);
|
||||
set => SaveProperty(propertyName: nameof(ServerTerminationBehavior), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Wino.Core.UWP/Services/StartupBehaviorService.cs
Normal file
37
Wino.Core.UWP/Services/StartupBehaviorService.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class StartupBehaviorService : IStartupBehaviorService
|
||||
{
|
||||
private const string WinoServerTaskId = "WinoServer";
|
||||
|
||||
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
|
||||
{
|
||||
var task = await StartupTask.GetAsync(WinoServerTaskId);
|
||||
|
||||
if (isEnabled)
|
||||
{
|
||||
await task.RequestEnableAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
task.Disable();
|
||||
}
|
||||
|
||||
return await GetCurrentStartupBehaviorAsync();
|
||||
}
|
||||
|
||||
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
|
||||
{
|
||||
var task = await StartupTask.GetAsync(WinoServerTaskId);
|
||||
|
||||
return task.State.AsStartupBehaviorResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Wino.Core.UWP/Services/StatePersistenceService.cs
Normal file
124
Wino.Core.UWP/Services/StatePersistenceService.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
namespace Wino.Services
|
||||
{
|
||||
public class StatePersistenceService : ObservableObject, IStatePersistanceService
|
||||
{
|
||||
public event EventHandler<string> StatePropertyChanged;
|
||||
|
||||
private const string OpenPaneLengthKey = nameof(OpenPaneLengthKey);
|
||||
private const string MailListPaneLengthKey = nameof(MailListPaneLengthKey);
|
||||
|
||||
private readonly IConfigurationService _configurationService;
|
||||
|
||||
public StatePersistenceService(IConfigurationService configurationService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
|
||||
openPaneLength = _configurationService.Get(OpenPaneLengthKey, 320d);
|
||||
_mailListPaneLength = _configurationService.Get(MailListPaneLengthKey, 420d);
|
||||
|
||||
PropertyChanged += ServicePropertyChanged;
|
||||
}
|
||||
|
||||
private void ServicePropertyChanged(object sender, PropertyChangedEventArgs e) => StatePropertyChanged?.Invoke(this, e.PropertyName);
|
||||
|
||||
public bool IsBackButtonVisible => IsReadingMail && IsReaderNarrowed;
|
||||
|
||||
private bool isReadingMail;
|
||||
|
||||
public bool IsReadingMail
|
||||
{
|
||||
get => isReadingMail;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref isReadingMail, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsBackButtonVisible));
|
||||
WeakReferenceMessenger.Default.Send(new ShellStateUpdated());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool shouldShiftMailRenderingDesign;
|
||||
|
||||
public bool ShouldShiftMailRenderingDesign
|
||||
{
|
||||
get { return shouldShiftMailRenderingDesign; }
|
||||
set { shouldShiftMailRenderingDesign = value; }
|
||||
}
|
||||
|
||||
private bool isReaderNarrowed;
|
||||
|
||||
public bool IsReaderNarrowed
|
||||
{
|
||||
get => isReaderNarrowed;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref isReaderNarrowed, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsBackButtonVisible));
|
||||
WeakReferenceMessenger.Default.Send(new ShellStateUpdated());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string coreWindowTitle;
|
||||
|
||||
public string CoreWindowTitle
|
||||
{
|
||||
get => coreWindowTitle;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref coreWindowTitle, value))
|
||||
{
|
||||
UpdateAppCoreWindowTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Settings
|
||||
|
||||
private double openPaneLength;
|
||||
public double OpenPaneLength
|
||||
{
|
||||
get => openPaneLength;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref openPaneLength, value))
|
||||
{
|
||||
_configurationService.Set(OpenPaneLengthKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _mailListPaneLength;
|
||||
public double MailListPaneLength
|
||||
{
|
||||
get => _mailListPaneLength;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _mailListPaneLength, value))
|
||||
{
|
||||
_configurationService.Set(MailListPaneLengthKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateAppCoreWindowTitle()
|
||||
{
|
||||
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
|
||||
|
||||
if (appView != null)
|
||||
appView.Title = CoreWindowTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Personalization;
|
||||
using Wino.Core.Messages.Shell;
|
||||
using Wino.Core.UWP.Extensions;
|
||||
using Wino.Core.UWP.Models.Personalization;
|
||||
using Wino.Core.UWP.Services;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
namespace Wino.Services
|
||||
{
|
||||
@@ -167,6 +167,7 @@ namespace Wino.Services
|
||||
await ApplyCustomThemeAsync(true);
|
||||
|
||||
// Registering to color changes, thus we notice when user changes theme system wide
|
||||
uiSettings.ColorValuesChanged -= UISettingsColorChanged;
|
||||
uiSettings.ColorValuesChanged += UISettingsColorChanged;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
@@ -21,12 +21,12 @@ namespace Wino.Core.UWP.Services
|
||||
|
||||
public bool IsUnderlyingThemeDark()
|
||||
{
|
||||
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ElementTheme.Default);
|
||||
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ApplicationElementTheme.Default);
|
||||
|
||||
if (currentTheme == ElementTheme.Default)
|
||||
if (currentTheme == ApplicationElementTheme.Default)
|
||||
return uiSettings.GetColorValue(UIColorType.Background).ToString() == "#FF000000";
|
||||
else
|
||||
return currentTheme == ElementTheme.Dark;
|
||||
return currentTheme == ApplicationElementTheme.Dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
316
Wino.Core.UWP/Services/WinoServerConnectionManager.cs
Normal file
316
Wino.Core.UWP/Services/WinoServerConnectionManager.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Nito.AsyncEx;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Models.Server;
|
||||
using Wino.Core.Integration.Json;
|
||||
using Wino.Messaging;
|
||||
using Wino.Messaging.Client.Connection;
|
||||
using Wino.Messaging.Enums;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class WinoServerConnectionManager :
|
||||
IWinoServerConnectionManager<AppServiceConnection>,
|
||||
IRecipient<WinoServerConnectionEstrablished>
|
||||
{
|
||||
private const int ServerConnectionTimeoutMs = 5000;
|
||||
|
||||
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
||||
private TaskCompletionSource<bool> _connectionTaskCompletionSource;
|
||||
|
||||
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
|
||||
|
||||
private WinoServerConnectionStatus status;
|
||||
|
||||
public WinoServerConnectionStatus Status
|
||||
{
|
||||
get { return status; }
|
||||
private set
|
||||
{
|
||||
status = value;
|
||||
StatusChanged?.Invoke(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
private AppServiceConnection _connection;
|
||||
public AppServiceConnection Connection
|
||||
{
|
||||
get { return _connection; }
|
||||
set
|
||||
{
|
||||
if (_connection != null)
|
||||
{
|
||||
_connection.RequestReceived -= ServerMessageReceived;
|
||||
_connection.ServiceClosed -= ServerDisconnected;
|
||||
}
|
||||
|
||||
_connection = value;
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
Status = WinoServerConnectionStatus.Disconnected;
|
||||
}
|
||||
else
|
||||
{
|
||||
value.RequestReceived += ServerMessageReceived;
|
||||
value.ServiceClosed += ServerDisconnected;
|
||||
|
||||
Status = WinoServerConnectionStatus.Connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
|
||||
{
|
||||
TypeInfoResolver = new ServerRequestTypeInfoResolver()
|
||||
};
|
||||
|
||||
public WinoServerConnectionManager()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
if (Status == WinoServerConnectionStatus.Connected) return true;
|
||||
|
||||
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
_connectionTaskCompletionSource ??= new TaskCompletionSource<bool>();
|
||||
|
||||
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
||||
|
||||
Status = WinoServerConnectionStatus.Connecting;
|
||||
|
||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||
|
||||
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
||||
// Once the connection is established, the handler will set the Connection property
|
||||
// and WinoServerConnectionEstrablished will be fired by the messenger.
|
||||
|
||||
await _connectionTaskCompletionSource.Task.WaitAsync(connectionCancellationToken.Token);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Status = WinoServerConnectionStatus.Failed;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> DisconnectAsync()
|
||||
{
|
||||
if (Connection == null || Status == WinoServerConnectionStatus.Disconnected) return true;
|
||||
|
||||
// TODO: Send disconnect message to the fulltrust process.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var isConnectionSuccessfull = await ConnectAsync();
|
||||
|
||||
// TODO: Log connection status
|
||||
}
|
||||
|
||||
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
|
||||
{
|
||||
if (args.Request.Message.TryGetValue(MessageConstants.MessageTypeKey, out object messageTypeObject) && messageTypeObject is int messageTypeInt)
|
||||
{
|
||||
var messageType = (MessageType)messageTypeInt;
|
||||
|
||||
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case MessageType.UIMessage:
|
||||
if (!args.Request.Message.TryGetValue(MessageConstants.MessageDataTypeKey, out object dataTypeObject) || dataTypeObject is not string dataTypeName)
|
||||
throw new ArgumentException("Message data type is missing.");
|
||||
|
||||
HandleUIMessage(messageJson, dataTypeName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
|
||||
/// </summary>
|
||||
/// <param name="messageJson">Message data in json format.</param>
|
||||
private void HandleUIMessage(string messageJson, string typeName)
|
||||
{
|
||||
switch (typeName)
|
||||
{
|
||||
case nameof(MailAddedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailAddedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailDownloadedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailDownloadedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailRemovedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailRemovedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailUpdatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailUpdatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountCreatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountCreatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountRemovedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountRemovedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountUpdatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountUpdatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(DraftCreated):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftCreated>(messageJson));
|
||||
break;
|
||||
case nameof(DraftFailed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftFailed>(messageJson));
|
||||
break;
|
||||
case nameof(DraftMapped):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftMapped>(messageJson));
|
||||
break;
|
||||
case nameof(FolderRenamed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderRenamed>(messageJson));
|
||||
break;
|
||||
case nameof(FolderSynchronizationEnabled):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderSynchronizationEnabled>(messageJson));
|
||||
break;
|
||||
case nameof(MergedInboxRenamed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MergedInboxRenamed>(messageJson));
|
||||
break;
|
||||
case nameof(AccountSynchronizationCompleted):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizationCompleted>(messageJson));
|
||||
break;
|
||||
case nameof(RefreshUnreadCountsMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<RefreshUnreadCountsMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountSynchronizerStateChanged):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizerStateChanged>(messageJson));
|
||||
break;
|
||||
case nameof(AccountSynchronizationProgressUpdatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizationProgressUpdatedMessage>(messageJson));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid data type name passed to client.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
||||
{
|
||||
// TODO: Handle server disconnection.
|
||||
}
|
||||
|
||||
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||
{
|
||||
var queuePackage = new ServerRequestPackage(accountId, request);
|
||||
|
||||
var queueResponse = await GetResponseInternalAsync<bool, ServerRequestPackage>(queuePackage, new Dictionary<string, object>()
|
||||
{
|
||||
{ MessageConstants.MessageDataRequestAccountIdKey, accountId }
|
||||
});
|
||||
|
||||
queueResponse.ThrowIfFailed();
|
||||
}
|
||||
|
||||
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message) where TRequestType : IClientMessage
|
||||
=> GetResponseInternalAsync<TResponse, TRequestType>(message);
|
||||
|
||||
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null)
|
||||
{
|
||||
if (Connection == null)
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established.");
|
||||
|
||||
string serializedMessage = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
serializedMessage = JsonSerializer.Serialize(message, _jsonSerializerOptions);
|
||||
}
|
||||
catch (Exception serializationException)
|
||||
{
|
||||
Logger.Error(serializationException, $"Failed to serialize client message for sending.");
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to serialize message.\n{serializationException.Message}");
|
||||
}
|
||||
|
||||
AppServiceResponse response = null;
|
||||
|
||||
try
|
||||
{
|
||||
var valueSet = new ValueSet
|
||||
{
|
||||
{ MessageConstants.MessageTypeKey, (int)MessageType.ServerMessage },
|
||||
{ MessageConstants.MessageDataKey, serializedMessage },
|
||||
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
||||
};
|
||||
|
||||
// Add additional parameters into ValueSet
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var item in parameters)
|
||||
{
|
||||
valueSet.Add(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
response = await Connection.SendMessageAsync(valueSet);
|
||||
}
|
||||
catch (Exception serverSendException)
|
||||
{
|
||||
Logger.Error(serverSendException, $"Failed to send message to server.");
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to send message to server.\n{serverSendException.Message}");
|
||||
}
|
||||
|
||||
// It should be always Success.
|
||||
if (response.Status != AppServiceResponseStatus.Success)
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse($"Wino Server responded with '{response.Status}' status to message delivery.");
|
||||
|
||||
// All responses must contain a message data.
|
||||
if (!(response.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson))
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse("Server response did not contain message data.");
|
||||
|
||||
// Try deserialize the message data.
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<WinoServerResponse<TResponse>>(messageJson);
|
||||
}
|
||||
catch (Exception jsonDeserializationError)
|
||||
{
|
||||
Logger.Error(jsonDeserializationError, $"Failed to deserialize server response message data.");
|
||||
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to deserialize Wino server response message data.\n{jsonDeserializationError.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(WinoServerConnectionEstrablished message)
|
||||
{
|
||||
if (_connectionTaskCompletionSource != null)
|
||||
{
|
||||
_connectionTaskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,25 +17,6 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -118,17 +99,21 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CoreUWPContainerSetup.cs" />
|
||||
<Compile Include="Dispatcher.cs" />
|
||||
<Compile Include="Extensions\ElementThemeExtensions.cs" />
|
||||
<Compile Include="Extensions\StartupTaskStateExtensions.cs" />
|
||||
<Compile Include="Models\Personalization\CustomAppTheme.cs" />
|
||||
<Compile Include="Models\Personalization\PreDefinedAppTheme.cs" />
|
||||
<Compile Include="Models\Personalization\SystemAppTheme.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\AppInitializerService.cs" />
|
||||
<Compile Include="Services\BackgroundSynchronizer.cs" />
|
||||
<Compile Include="Services\PreferencesService.cs" />
|
||||
<Compile Include="Services\StartupBehaviorService.cs" />
|
||||
<Compile Include="Services\StatePersistenceService.cs" />
|
||||
<Compile Include="Services\WinoServerConnectionManager.cs" />
|
||||
<Compile Include="Services\BackgroundTaskService.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Services\ConfigurationService.cs" />
|
||||
@@ -143,9 +128,9 @@
|
||||
<EmbeddedResource Include="Properties\Wino.Core.UWP.rd.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!--<PackageReference Include="CommunityToolkit.Uwp.Helpers">
|
||||
<Version>8.0.230907</Version>
|
||||
</PackageReference>-->
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications">
|
||||
<Version>7.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
||||
<Version>5.0.4</Version>
|
||||
</PackageReference>
|
||||
@@ -155,9 +140,6 @@
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp">
|
||||
<Version>7.1.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
|
||||
<Version>7.1.3</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj">
|
||||
@@ -168,8 +150,16 @@
|
||||
<Project>{e6b1632a-8901-41e8-9ddf-6793c7698b0b}</Project>
|
||||
<Name>Wino.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
|
||||
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
|
||||
<Name>Wino.Messaging</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<SDKReference Include="WindowsDesktop, Version=10.0.22621.0">
|
||||
<Name>Windows Desktop Extensions for the UWP</Name>
|
||||
</SDKReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user