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,12 +0,0 @@
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces
{
public interface IAppInitializerService
{
string GetApplicationDataFolder();
string GetPublisherSharedFolder();
Task MigrateAsync();
}
}

View File

@@ -0,0 +1,21 @@
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// Singleton object that holds the application data folder path and the publisher shared folder path.
/// Load the values before calling any service.
/// App data folder is used for storing files.
/// Pubhlisher cache folder is only used for database file so other apps can access it in the same package by same publisher.
/// </summary>
public interface IApplicationConfiguration
{
/// <summary>
/// Application data folder.
/// </summary>
string ApplicationDataFolderPath { get; set; }
/// <summary>
/// Publisher shared folder path.
/// </summary>
string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
@@ -23,28 +22,12 @@ namespace Wino.Core.Domain.Interfaces
/// <summary>
/// Initial creation of token. Requires user interaction.
/// This will save token into database, but still returns for account creation
/// This will cache the token but still returns for account creation
/// since account address is required.
/// </summary>
/// <param name="expectedAccountAddress">Token cache might ask for regeneration of token for specific
/// account address. If one is provided and re-generation native token doesn't belong to this address
/// token saving to database won't happen.</param>
/// <returns>Freshly created TokenInformation..</returns>
Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken);
/// <summary>
/// Required for external authorization on launched browser to continue.
/// Used for Gmail.
/// </summary>
/// <param name="authorizationResponseUri">Response's redirect uri.</param>
void ContinueAuthorization(Uri authorizationResponseUri);
/// <summary>
/// For external browser required authentications.
/// Canceling Gmail authentication dialog etc.
/// </summary>
void CancelAuthorization();
/// <summary>
/// ClientId in case of needed for authorization/authentication.
/// </summary>

View File

@@ -0,0 +1,6 @@
namespace Wino.Core.Domain.Interfaces
{
public interface IOutlookAuthenticator : IAuthenticator { }
public interface IGmailAuthenticator : IAuthenticator { }
public interface IImapAuthenticator : IAuthenticator { }
}

View File

@@ -0,0 +1,54 @@
using System.Threading;
using System.Threading.Tasks;
using MailKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
namespace Wino.Core.Domain.Interfaces
{
public interface IBaseSynchronizer
{
/// <summary>
/// Account that is assigned for this synchronizer.
/// </summary>
MailAccount Account { get; }
/// <summary>
/// Synchronizer state.
/// </summary>
AccountSynchronizerState State { get; }
/// <summary>
/// Queues a single request to be executed in the next synchronization.
/// </summary>
/// <param name="request">Request to queue.</param>
void QueueRequest(IRequestBase request);
/// <summary>
/// TODO
/// </summary>
/// <returns>Whether active synchronization is stopped or not.</returns>
bool CancelActiveSynchronization();
/// <summary>
/// Performs a full synchronization with the server with given options.
/// This will also prepares batch requests for execution.
/// Requests are executed in the order they are queued and happens before the synchronization.
/// Result of the execution queue is processed during the synchronization.
/// </summary>
/// <param name="options">Options for synchronization.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>
/// <param name="mailItem">Mail item to download from server.</param>
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
}
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// All messages that Client sends to Server and awaits a response in return.
/// For example; triggering a new synchronization request.
/// </summary>
public interface IClientMessage;
}

View File

@@ -2,7 +2,7 @@
{
public interface ILogInitializer
{
void SetupLogger(string logFolderPath);
void SetupLogger(string fullLogFilePath);
void RefreshLoggingLevel();
}

View File

@@ -11,7 +11,7 @@ namespace Wino.Core.Domain.Interfaces
{
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, MimeMessage generatedReplyMime, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, string generatedReplyMimeMessageBase64, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
/// <summary>
@@ -51,7 +51,16 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="newThreadId"></param>
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
Task<MimeMessage> CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions options);
/// <summary>
/// Creates a draft message with the given options.
/// </summary>
/// <param name="accountId">Account to create draft for.</param>
/// <param name="options">Draft creation options.</param>
/// <returns>
/// Base64 encoded string of MimeMessage object.
/// This is mainly for serialization purposes.
/// </returns>
Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions options);
Task UpdateMailAsync(MailCopy mailCopy);
/// <summary>
@@ -92,5 +101,14 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
/// <param name="folderId">Folder id to get unread mails for.</param>
Task<List<MailCopy>> GetUnreadMailsByFolderIdAsync(Guid folderId);
/// <summary>
/// Checks whether the mail exists in the folder.
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
/// </summary>
/// <param name="messageId">Message id</param>
/// <param name="folderId">Folder's local id.</param>
/// <returns>Whether mail exists in the folder or not.</returns>
Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId);
}
}

View File

@@ -11,6 +11,20 @@ namespace Wino.Core.Domain.Interfaces
Task<string> GetEditorBundlePathAsync();
Task LaunchFileAsync(string filePath);
Task LaunchUriAsync(Uri uri);
/// <summary>
/// Launches the default browser with the specified uri and waits for protocol activation to finish.
/// </summary>
/// <param name="authenticator"></param>
/// <returns>Response callback from the browser.</returns>
Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri);
/// <summary>
/// Finalizes GetAuthorizationResponseUriAsync for current IAuthenticator.
/// </summary>
/// <param name="authorizationResponseUri"></param>
void ContinueAuthorization(Uri authorizationResponseUri);
bool IsAppRunning();
string GetFullAppVersion();
@@ -21,5 +35,11 @@ namespace Wino.Core.Domain.Interfaces
/// Some cryptographic shit is needed for requesting Google authentication in UWP.
/// </summary>
GoogleAuthorizationRequest GetGoogleAuthorizationRequest();
/// <summary>
/// Gets or sets the function that returns a pointer for main window hwnd for UWP.
/// This is used to display WAM broker dialog on running UWP app called by a windowless server code.
/// </summary>
Func<IntPtr> GetCoreWindowHwnd { get; set; }
}
}

View File

@@ -150,5 +150,10 @@ namespace Wino.Core.Domain.Interfaces
/// Setting: Whether the next item should be automatically selected once the current item is moved or removed.
/// </summary>
bool AutoSelectNextItem { get; set; }
/// <summary>
/// Setting: Gets or sets what should happen to server app when the client is terminated.
/// </summary>
ServerBackgroundMode ServerTerminationBehavior { get; set; }
}
}

View File

@@ -22,7 +22,7 @@ namespace Wino.Core.Domain.Interfaces
TRequest NativeRequest { get; }
}
public interface IRequestBase
public interface IRequestBase : IClientMessage
{
/// <summary>
/// Synchronizer option to perform.

View File

@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Interfaces
{
public interface IStartupBehaviorService
{
/// <summary>
/// Gets whether Wino Server is set to launch on startup or not.
/// </summary>
Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync();
/// <summary>
/// Enables/disables the current startup behavior for Wino Server.
/// </summary>
/// <param name="isEnabled">Whether to launch enabled or disabled.</param>
/// <returns>True if operation success, false if not.</returns>
Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled);
}
}

View File

@@ -1,19 +0,0 @@
using System;
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// An interface for reporting progress of the synchronization.
/// Gmail does not support reporting folder progress.
/// For others, account progress is calculated based on the number of folders.
/// </summary>
public interface ISynchronizationProgress
{
/// <summary>
/// Reports account synchronization progress.
/// </summary>
/// <param name="accountId">Account id for the report.</param>
/// <param name="progress">Value. This is always between 0 - 100</param>
void AccountProgressUpdated(Guid accountId, int progress);
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces
{
public interface ISynchronizerFactory
{
Task<IBaseSynchronizer> GetAccountSynchronizerAsync(Guid accountId);
Task InitializeAsync();
}
}

View File

@@ -0,0 +1,11 @@
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// Interface for all messages to report UI changes from synchronizers to UI.
/// None of these messages can't run a code that manipulates database.
/// They are sent either from processor or view models to signal some other
/// parts of the application.
/// </summary>
public interface IUIMessage;
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Server;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoServerConnectionManager
{
/// <summary>
/// When the connection status changes, this event will be triggered.
/// </summary>
event EventHandler<WinoServerConnectionStatus> StatusChanged;
/// <summary>
/// Gets the connection status.
/// </summary>
WinoServerConnectionStatus Status { get; }
/// <summary>
/// Launches Full Trust process (Wino Server) and awaits connection completion.
/// If connection is not established in 5 seconds, it will return false.
/// If the server process is already running, it'll connect to existing one.
/// If the server process is not running, it'll be launched and connection establishment is awaited.
/// </summary>
/// <returns>Whether connection is established or not.</returns>
Task<bool> ConnectAsync();
/// <summary>
/// Disconnects from existing connection and disposes the connection.
/// </summary>
/// <returns>Whether disconnection is succesfull or not.</returns>
Task<bool> DisconnectAsync();
/// <summary>
/// Queues a new user request to be processed by Wino Server.
/// Healthy connection must present before calling this method.
/// </summary>
/// <param name="request">Request to queue for synchronizer in the server.</param>
/// <param name="accountId">Account id to queueu request for.</param>
Task QueueRequestAsync(IRequestBase request, Guid accountId);
/// <summary>
/// Returns response from server for the given request.
/// </summary>
/// <typeparam name="TResponse">Response type.</typeparam>
/// <typeparam name="TRequestType">Request type.</typeparam>
/// <param name="clientMessage">Request type.</param>
/// <returns>Response received from the server for the given TResponse type.</returns>
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage;
}
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
{
/// <summary>
/// Existing connection handle to the server of TAppServiceConnection type.
/// </summary>
TAppServiceConnection Connection { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoSynchronizerFactory : IInitializeAsync
{
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
}
}