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

@@ -11,8 +11,8 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Extensions;
using Wino.Core.Messages.Accounts;
using Wino.Core.Requests;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.UI;
namespace Wino.Core.Services
{
@@ -382,7 +382,10 @@ namespace Wino.Core.Services
if (customServerInformation != null)
await Connection.InsertAsync(customServerInformation);
if (tokenInformation != null)
// Outlook token cache is managed by MSAL.
// Don't save it to database.
if (tokenInformation != null && account.ProviderType != MailProviderType.Outlook)
await Connection.InsertAsync(tokenInformation);
}

View File

@@ -0,0 +1,13 @@
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services
{
public class ApplicationConfiguration : IApplicationConfiguration
{
public const string SharedFolderName = "WinoShared";
public string ApplicationDataFolderPath { get; set; }
public string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -11,22 +11,23 @@ namespace Wino.Core.Services
{
private readonly INativeAppService _nativeAppService;
private readonly ITokenService _tokenService;
private readonly IApplicationConfiguration _applicationConfiguration;
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService)
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService, IApplicationConfiguration applicationConfiguration)
{
_nativeAppService = nativeAppService;
_tokenService = tokenService;
_applicationConfiguration = applicationConfiguration;
}
public IAuthenticator GetAuthenticator(MailProviderType providerType)
{
// TODO: Move DI
return providerType switch
{
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService),
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService),
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService, _applicationConfiguration),
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService, _applicationConfiguration),
MailProviderType.Gmail => new GmailAuthenticator(_tokenService, _nativeAppService),
MailProviderType.Yahoo => new YahooAuthenticator(_tokenService),
MailProviderType.IMAP4 => new CustomAuthenticator(_tokenService),
_ => throw new ArgumentException(Translator.Exception_UnsupportedProvider),
};
}

View File

@@ -1,6 +1,6 @@
using CommunityToolkit.Mvvm.Messaging;
using SQLite;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services
{

View File

@@ -14,16 +14,16 @@ namespace Wino.Core.Services
public class DatabaseService : IDatabaseService
{
private string DatabaseName => "Wino172.db";
private const string DatabaseName = "Wino172.db";
private bool _isInitialized = false;
private readonly IAppInitializerService _appInitializerService;
private readonly IApplicationConfiguration _folderConfiguration;
public SQLiteAsyncConnection Connection { get; private set; }
public DatabaseService(IAppInitializerService appInitializerService)
public DatabaseService(IApplicationConfiguration folderConfiguration)
{
_appInitializerService = appInitializerService;
_folderConfiguration = folderConfiguration;
}
public async Task InitializeAsync()
@@ -31,8 +31,8 @@ namespace Wino.Core.Services
if (_isInitialized)
return;
var applicationData = _appInitializerService.GetPublisherSharedFolder();
var databaseFileName = Path.Combine(applicationData, DatabaseName);
var publisherCacheFolder = _folderConfiguration.PublisherSharedFolderPath;
var databaseFileName = Path.Combine(publisherCacheFolder, DatabaseName);
Connection = new SQLiteAsyncConnection(databaseFileName)
{
@@ -45,7 +45,6 @@ namespace Wino.Core.Services
})
};
await CreateTablesAsync();
_isInitialized = true;

View File

@@ -16,7 +16,7 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Extensions;
using Wino.Core.MenuItems;
using Wino.Core.Requests;
using Wino.Messaging.UI;
namespace Wino.Core.Services
{

View File

@@ -11,11 +11,11 @@ namespace Wino.Core.Services
public const string ProtocolLogFileName = "ImapProtocolLog.log";
private readonly IPreferencesService _preferencesService;
private readonly IAppInitializerService _appInitializerService;
private readonly IApplicationConfiguration _appInitializerService;
private Stream _protocolLogStream;
public ImapTestService(IPreferencesService preferencesService, IAppInitializerService appInitializerService)
public ImapTestService(IPreferencesService preferencesService, IApplicationConfiguration appInitializerService)
{
_preferencesService = preferencesService;
_appInitializerService = appInitializerService;
@@ -24,7 +24,7 @@ namespace Wino.Core.Services
private void EnsureProtocolLogFileExists()
{
// Create new file for protocol logger.
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder();
var localAppFolderPath = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);

View File

@@ -1,5 +1,4 @@
using System.IO;
using Serilog;
using Serilog;
using Serilog.Core;
using Serilog.Exceptions;
using Wino.Core.Domain.Interfaces;
@@ -8,8 +7,6 @@ namespace Wino.Core.Services
{
public class LogInitializer : ILogInitializer
{
public const string WinoLogFileName = "WinoDiagnostics.log";
private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
private readonly IPreferencesService _preferencesService;
@@ -25,13 +22,11 @@ namespace Wino.Core.Services
_levelSwitch.MinimumLevel = _preferencesService.IsLoggingEnabled ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Fatal;
}
public void SetupLogger(string logFolderPath)
public void SetupLogger(string fullLogFilePath)
{
string logFilePath = Path.Combine(logFolderPath, WinoLogFileName);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(_levelSwitch)
.WriteTo.File(logFilePath)
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
.WriteTo.Debug()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions.Extensions;
@@ -10,11 +11,12 @@ using SqlKata;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Comparers;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Extensions;
using Wino.Core.Requests;
using Wino.Messaging.UI;
namespace Wino.Core.Services
{
@@ -52,10 +54,12 @@ namespace Wino.Core.Services
}
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
MimeMessage createdDraftMimeMessage,
string generatedReplyMimeMessageBase64,
MimeMessage replyingMimeMessage = null,
IMailItem replyingMailItem = null)
{
var createdDraftMimeMessage = generatedReplyMimeMessageBase64.GetMimeMessageFromBase64();
bool isImapAccount = composerAccount.ServerInformation != null;
string fromName;
@@ -625,7 +629,7 @@ namespace Wino.Core.Services
}
}
public async Task<MimeMessage> CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
public async Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions draftCreationOptions)
{
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
// Same unique id will be used for the local copy as well.
@@ -796,7 +800,14 @@ namespace Wino.Core.Services
// Update TextBody from existing HtmlBody if exists.
}
return message;
using MemoryStream memoryStream = new();
message.WriteTo(FormatOptions.Default, memoryStream);
byte[] buffer = memoryStream.GetBuffer();
int count = (int)memoryStream.Length;
return Convert.ToBase64String(buffer);
// return message;
// Generates html representation of To/Cc/From/Time and so on from referenced message.
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
@@ -919,5 +930,8 @@ namespace Wino.Core.Services
public Task<bool> IsMailExistsAsync(string mailCopyId)
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ?)", mailCopyId);
public Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId)
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ? AND FolderId = ?)", mailCopyId, folderId);
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Integration.Processors;
using Wino.Core.Synchronizers;
namespace Wino.Core.Services
{
public class SynchronizerFactory : ISynchronizerFactory
{
private bool isInitialized = false;
private readonly IAccountService _accountService;
private readonly IOutlookChangeProcessor _outlookChangeProcessor;
private readonly IGmailChangeProcessor _gmailChangeProcessor;
private readonly IImapChangeProcessor _imapChangeProcessor;
private readonly IOutlookAuthenticator _outlookAuthenticator;
private readonly IGmailAuthenticator _gmailAuthenticator;
private readonly List<IBaseSynchronizer> synchronizerCache = new();
public SynchronizerFactory(IOutlookChangeProcessor outlookChangeProcessor,
IGmailChangeProcessor gmailChangeProcessor,
IImapChangeProcessor imapChangeProcessor,
IOutlookAuthenticator outlookAuthenticator,
IGmailAuthenticator gmailAuthenticator,
IAccountService accountService)
{
_outlookChangeProcessor = outlookChangeProcessor;
_gmailChangeProcessor = gmailChangeProcessor;
_imapChangeProcessor = imapChangeProcessor;
_outlookAuthenticator = outlookAuthenticator;
_gmailAuthenticator = gmailAuthenticator;
_accountService = accountService;
}
public async Task<IBaseSynchronizer> GetAccountSynchronizerAsync(Guid accountId)
{
var synchronizer = synchronizerCache.Find(a => a.Account.Id == accountId);
if (synchronizer == null)
{
var account = await _accountService.GetAccountAsync(accountId);
if (account != null)
{
synchronizer = CreateNewSynchronizer(account);
return await GetAccountSynchronizerAsync(accountId);
}
}
return synchronizer;
}
private IBaseSynchronizer CreateIntegratorWithDefaultProcessor(MailAccount mailAccount)
{
var providerType = mailAccount.ProviderType;
switch (providerType)
{
case Domain.Enums.MailProviderType.Outlook:
case Domain.Enums.MailProviderType.Office365:
return new OutlookSynchronizer(mailAccount, _outlookAuthenticator, _outlookChangeProcessor);
case Domain.Enums.MailProviderType.Gmail:
return new GmailSynchronizer(mailAccount, _gmailAuthenticator, _gmailChangeProcessor);
case Domain.Enums.MailProviderType.IMAP4:
return new ImapSynchronizer(mailAccount, _imapChangeProcessor);
default:
break;
}
return null;
}
public IBaseSynchronizer CreateNewSynchronizer(MailAccount account)
{
var synchronizer = CreateIntegratorWithDefaultProcessor(account);
synchronizerCache.Add(synchronizer);
return synchronizer;
}
public async Task InitializeAsync()
{
if (isInitialized) return;
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
{
CreateNewSynchronizer(account);
}
isInitialized = true;
}
}
}

View File

@@ -8,7 +8,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Translations;
using Wino.Core.Messages.Shell;
using Wino.Messaging.Client.Shell;
namespace Wino.Core.Services
{

View File

@@ -11,26 +11,26 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Messages.Synchronization;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Services
{
public class WinoRequestDelegator : IWinoRequestDelegator
{
private readonly IWinoRequestProcessor _winoRequestProcessor;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IFolderService _folderService;
private readonly IDialogService _dialogService;
private readonly ILogger _logger = Log.ForContext<WinoRequestDelegator>();
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
IWinoSynchronizerFactory winoSynchronizerFactory,
IWinoServerConnectionManager winoServerConnectionManager,
IFolderService folderService,
IDialogService dialogService)
{
_winoRequestProcessor = winoRequestProcessor;
_winoSynchronizerFactory = winoSynchronizerFactory;
_winoServerConnectionManager = winoServerConnectionManager;
_folderService = folderService;
_dialogService = dialogService;
}
@@ -77,7 +77,7 @@ namespace Wino.Core.Services
{
foreach (var accountRequest in accountId)
{
QueueRequest(accountRequest, accountId.Key);
await QueueRequestAsync(accountRequest, accountId.Key);
}
QueueSynchronization(accountId.Key);
@@ -107,43 +107,36 @@ namespace Wino.Core.Services
if (request == null) return;
QueueRequest(request, accountId);
await QueueRequestAsync(request, accountId);
QueueSynchronization(accountId);
}
public Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
public async Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
{
var request = new CreateDraftRequest(draftPreperationRequest);
QueueRequest(request, draftPreperationRequest.Account.Id);
await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
QueueSynchronization(draftPreperationRequest.Account.Id);
return Task.CompletedTask;
}
public Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
{
var request = new SendDraftRequest(sendDraftPreperationRequest);
QueueRequest(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
return Task.CompletedTask;
}
private void QueueRequest(IRequestBase request, Guid accountId)
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
{
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
if (synchronizer == null)
try
{
_logger.Warning("Synchronizer not found for account {AccountId}.", accountId);
_logger.Warning("Skipping queueing request {Operation}.", request.Operation);
return;
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
}
catch (WinoServerException serverException)
{
_dialogService.InfoBarMessage("", serverException.Message, InfoBarMessageType.Error);
}
synchronizer.QueueRequest(request);
}
private void QueueSynchronization(Guid accountId)
@@ -154,7 +147,7 @@ namespace Wino.Core.Services
Type = SynchronizationType.ExecuteRequests
};
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options));
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
}
}
}

View File

@@ -9,7 +9,6 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Requests;
namespace Wino.Core.Services
@@ -92,6 +91,7 @@ namespace Wino.Core.Services
var requests = new List<IRequest>();
// TODO: Fix: Collection was modified; enumeration operation may not execute
foreach (var item in preperationRequest.MailItems)
{
var singleRequest = await GetSingleRequestAsync(item, action, moveTargetStructure, preperationRequest.ToggleExecution);