diff --git a/Wallet.Avalonia/App.axaml.cs b/Wallet.Avalonia/App.axaml.cs index e4dd979..72836af 100644 --- a/Wallet.Avalonia/App.axaml.cs +++ b/Wallet.Avalonia/App.axaml.cs @@ -84,7 +84,8 @@ public partial class App : Application services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); @@ -94,22 +95,19 @@ public partial class App : Application provider.GetServices>().OrderBy(x => x.Name) ?? Enumerable.Empty>(); - return new ItemConfigurationCollection(items.ToDictionary(x => x.Name, x => (Func)(() => x.Value))); + return new ItemConfigurationCollection(items.ToDictionary(x => x.Name, + x => (Func)(() => x.Value))); }); - services.TryAddSingleton>, DecoratorService>>(); + services.TryAddSingleton>, + DecoratorService>>(); services.TryAddSingleton, DecoratorService>(); services.TryAddSingleton, DecoratorService>(); - services.AddDbContextFactory((provider, args) => - { - if (provider.GetRequiredService>() - is IDecoratorService connection) - { - args.UseSqlite($"{connection.Service}"); - } - }); + services.AddTransient(provider => provider.GetRequiredService>().Value!); + + services.AddDbContextFactory(); services.AddHandler(); @@ -125,6 +123,7 @@ public partial class App : Application services.AddHandler(); services.AddHandler(); + services.AddHandler(); services.AddTemplate(); diff --git a/Wallet.Avalonia/WalletNavigationView.axaml b/Wallet.Avalonia/WalletNavigationView.axaml index 2fa8f89..0bc755c 100644 --- a/Wallet.Avalonia/WalletNavigationView.axaml +++ b/Wallet.Avalonia/WalletNavigationView.axaml @@ -7,6 +7,11 @@ x:DataType="vm:WalletNavigationViewModel" ListBoxExtension.IsItemInvokedEnabled="True" ToolTip.Tip="{Binding Name}"> + + 40 + 40 + + @@ -35,8 +40,33 @@ - + + + + \ No newline at end of file diff --git a/Wallet.Data/LockerContext.cs b/Wallet.Data/LockerContext.cs index 34f594b..32611af 100644 --- a/Wallet.Data/LockerContext.cs +++ b/Wallet.Data/LockerContext.cs @@ -2,8 +2,9 @@ namespace Wallet.Data; -public class WalletContext(DbContextOptions options) : - DbContext(options) +public interface IConnection; + +public class WalletContext(IConnection connection) : DbContext { public DbSet Blobs { get; set; } @@ -11,6 +12,11 @@ public class WalletContext(DbContextOptions options) : public DbSet Tags { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"{connection}"); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() diff --git a/Wallet/ArchiveItemHandler.cs b/Wallet/ArchiveItemHandler.cs index c94906b..d45c746 100644 --- a/Wallet/ArchiveItemHandler.cs +++ b/Wallet/ArchiveItemHandler.cs @@ -12,7 +12,7 @@ public class ArchiveItemHandler(IDecoratorService> decorato { try { - if (decoratorService.Service is Item<(Guid, string)> item) + if (decoratorService.Value is Item<(Guid, string)> item) { if (cache.Contains(item)) { diff --git a/Wallet/CloseWalletHandler.cs b/Wallet/CloseWalletHandler.cs new file mode 100644 index 0000000..ecd89d1 --- /dev/null +++ b/Wallet/CloseWalletHandler.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Toolkit.Foundation; +using Wallet.Data; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; + +namespace Wallet; + +public class CloseWalletHandler(IDecoratorService walletConnectionDecorator, + IDbContextFactory dbContextFactory) : + IHandler, bool> +{ + public async Task Handle(CloseEventArgs args, + CancellationToken cancellationToken) + { + walletConnectionDecorator.Set(null); + + return true; + } +} diff --git a/Wallet/ConfirmCreateItemHandler.cs b/Wallet/ConfirmCreateItemHandler.cs index f17d0d2..5a4ecd9 100644 --- a/Wallet/ConfirmCreateItemHandler.cs +++ b/Wallet/ConfirmCreateItemHandler.cs @@ -10,8 +10,8 @@ public class ConfirmCreateItemHandler(IMediator mediator, { public async Task Handle(ConfirmEventArgs args) { - if (itemHeaderConfiguration.Service is ItemHeaderConfiguration headerConfiguration && - itemConfigurationDecorator.Service is ItemConfiguration itemConfiguration) + if (itemHeaderConfiguration.Value is ItemHeaderConfiguration headerConfiguration && + itemConfigurationDecorator.Value is ItemConfiguration itemConfiguration) { if (headerConfiguration.Name is { Length: > 0 } name && headerConfiguration.Category is { Length: > 0 } category) diff --git a/Wallet/ConfirmDeleteItemHandler.cs b/Wallet/ConfirmDeleteItemHandler.cs index 9581191..136dc82 100644 --- a/Wallet/ConfirmDeleteItemHandler.cs +++ b/Wallet/ConfirmDeleteItemHandler.cs @@ -12,7 +12,7 @@ public class ConfirmDeleteItemHandler(IDecoratorService> de { try { - if (decoratorService.Service is Item<(Guid, string)> item) + if (decoratorService.Value is Item<(Guid, string)> item) { (Guid id, string name) = item.Value; diff --git a/Wallet/ConfirmUpdateItemHandler.cs b/Wallet/ConfirmUpdateItemHandler.cs index 0d005d2..43bd8d3 100644 --- a/Wallet/ConfirmUpdateItemHandler.cs +++ b/Wallet/ConfirmUpdateItemHandler.cs @@ -11,9 +11,9 @@ public class ConfirmUpdateItemHandler(IDecoratorService> it { public async Task Handle(ConfirmEventArgs args) { - if (itemDecorator?.Service is Item<(Guid, string)> item && - itemHeaderConfiguration.Service is ItemHeaderConfiguration headerConfiguration && - itemConfigurationDecorator.Service is ItemConfiguration itemConfiguration) + if (itemDecorator?.Value is Item<(Guid, string)> item && + itemHeaderConfiguration.Value is ItemHeaderConfiguration headerConfiguration && + itemConfigurationDecorator.Value is ItemConfiguration itemConfiguration) { if (headerConfiguration?.Name is { Length: > 0 } name && headerConfiguration.Category is { Length: > 0 } category) diff --git a/Wallet/CreateWalletHandler.cs b/Wallet/CreateWalletHandler.cs index 20a3d5a..29aafc0 100644 --- a/Wallet/CreateWalletHandler.cs +++ b/Wallet/CreateWalletHandler.cs @@ -20,8 +20,8 @@ public class CreateWalletHandler(IWalletHostFactory componentFactory, { if (componentFactory.Create(name) is IComponentHost host) { - IWalletFactory factory = host.Services.GetRequiredService(); - if (await factory.Create(name, password, imageDescriptor)) + IWalletFactory walletFactory = host.Services.GetRequiredService(); + if (await walletFactory.Create(name, password, imageDescriptor)) { host.Start(); publisher.Publish(Activated.As(new Wallet(host))); diff --git a/Wallet/FavouriteItemHandler.cs b/Wallet/FavouriteItemHandler.cs index 6f2fce1..765ff60 100644 --- a/Wallet/FavouriteItemHandler.cs +++ b/Wallet/FavouriteItemHandler.cs @@ -11,7 +11,7 @@ public class FavouriteItemHandler(IDecoratorService> decora { try { - if (decoratorService.Service is Item<(Guid, string)> item) + if (decoratorService.Value is Item<(Guid, string)> item) { (Guid id, string name) = item.Value; await mediator.Handle, bool>(new UpdateEventArgs<(Guid, int)>((id, 1))); diff --git a/Wallet/IWalletConnectionFactory.cs b/Wallet/IWalletConnectionFactory.cs new file mode 100644 index 0000000..ffcfb54 --- /dev/null +++ b/Wallet/IWalletConnectionFactory.cs @@ -0,0 +1,8 @@ + +namespace Wallet +{ + public interface IWalletConnectionFactory + { + Task Create(string name, string key); + } +} \ No newline at end of file diff --git a/Wallet/IWalletDatabaseFactory.cs b/Wallet/IWalletDatabaseFactory.cs new file mode 100644 index 0000000..15f9c52 --- /dev/null +++ b/Wallet/IWalletDatabaseFactory.cs @@ -0,0 +1,6 @@ +namespace Wallet; + +public interface IWalletDatabaseFactory +{ + Task Create(string name, string key); +} \ No newline at end of file diff --git a/Wallet/IWalletStoreFactory.cs b/Wallet/IWalletStoreFactory.cs deleted file mode 100644 index a5915ef..0000000 --- a/Wallet/IWalletStoreFactory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Wallet; - -public interface IWalletStoreFactory -{ - Task Create(string name, SecurityKey key); -} \ No newline at end of file diff --git a/Wallet/ItemContentViewModelActivationHandler.cs b/Wallet/ItemContentViewModelActivationHandler.cs index 41b67fb..1952330 100644 --- a/Wallet/ItemContentViewModelActivationHandler.cs +++ b/Wallet/ItemContentViewModelActivationHandler.cs @@ -12,7 +12,7 @@ public class ItemContentViewModelActivationHandler(IDecoratorService args) { - if (itemDecorator.Service is Item<(Guid, string)> item) + if (itemDecorator.Value is Item<(Guid, string)> item) { if (item.Value is (Guid Id, _)) { diff --git a/Wallet/MainViewModelActivationHandler.cs b/Wallet/MainViewModelActivationHandler.cs index 865d17e..cb2fab2 100644 --- a/Wallet/MainViewModelActivationHandler.cs +++ b/Wallet/MainViewModelActivationHandler.cs @@ -21,7 +21,7 @@ public class MainViewModelActivationHandler(IPublisher publisher, { IDecoratorService> profileImageDecorator = Wallet.Services.GetRequiredService>>(); - ProfileImage? profileImage = profileImageDecorator.Service; + ProfileImage? profileImage = profileImageDecorator.Value; if (factory.Create(args => args.Initialize(), configuration.Name, profileImage?.Value ?? null, selected) is WalletNavigationViewModel viewModel) diff --git a/Wallet/OpenWalletHandler.cs b/Wallet/OpenWalletHandler.cs index 68aa50f..a25052d 100644 --- a/Wallet/OpenWalletHandler.cs +++ b/Wallet/OpenWalletHandler.cs @@ -5,10 +5,11 @@ namespace Wallet; public class OpenWalletHandler(IConfigurationDescriptor descriptor, ISecurityKeyFactory securityKeyFactory, - IWalletStoreFactory WalletStorageFactory) : - IHandler>, bool> + IWalletConnectionFactory walletConnectionFactory, + IDecoratorService walletConnectionDecorator) : + IHandler>, bool> { - public async Task Handle(ActivateEventArgs> args, + public async Task Handle(OpenEventArgs> args, CancellationToken cancellationToken) { if (args.Sender is Wallet Wallet && @@ -21,10 +22,13 @@ public class OpenWalletHandler(IConfigurationDescriptor des byte[]? salt = Convert.FromBase64String(keyPart[0]); byte[]? encryptedKey = Convert.FromBase64String(keyPart[1]); - if (securityKeyFactory.Create(Encoding.UTF8.GetBytes(password), encryptedKey, salt) is SecurityKey key) + if (securityKeyFactory.Create(Encoding.UTF8.GetBytes(password), + encryptedKey, salt) is SecurityKey securityKey) { - if (await WalletStorageFactory.Create(name, key)) + if (await walletConnectionFactory.Create(name, Convert.ToBase64String(securityKey.DecryptedKey)) + is WalletConnection connection) { + walletConnectionDecorator.Set(connection); return true; } } diff --git a/Wallet/OpenWalletViewModel.cs b/Wallet/OpenWalletViewModel.cs index d7483af..ec78db2 100644 --- a/Wallet/OpenWalletViewModel.cs +++ b/Wallet/OpenWalletViewModel.cs @@ -43,23 +43,11 @@ public partial class OpenWalletViewModel : using (await new ActivityLock(this)) { if (await Validation.Validate(() => Password, [new ValidationRule(async () => - await Mediator.Handle>, bool>(Activate.As(new Wallet(Password))), + await Mediator.Handle>, bool>(Open.As(new Wallet(Password))), "The password is incorrect, please try again.")])) { Publisher.Publish(Opened.As()); } } } - - public override async Task OnActivated() - { - Publisher.Publish(Activated.As()); - await base.OnActivated(); - } - - public override async Task OnDeactivated() - { - Publisher.Publish(Deactivated.As()); - await base.OnDeactivated(); - } } \ No newline at end of file diff --git a/Wallet/UnarchiveItemHandler.cs b/Wallet/UnarchiveItemHandler.cs index 9b20248..b8f64ac 100644 --- a/Wallet/UnarchiveItemHandler.cs +++ b/Wallet/UnarchiveItemHandler.cs @@ -12,7 +12,7 @@ public class UnarchiveItemHandler(IDecoratorService> decora { try { - if (decoratorService.Service is Item<(Guid, string)> item) + if (decoratorService.Value is Item<(Guid, string)> item) { (Guid id, string name) = item.Value; diff --git a/Wallet/UnfavouriteItemHandler.cs b/Wallet/UnfavouriteItemHandler.cs index b14b297..3ffc922 100644 --- a/Wallet/UnfavouriteItemHandler.cs +++ b/Wallet/UnfavouriteItemHandler.cs @@ -11,7 +11,7 @@ public class UnfavouriteItemHandler(IDecoratorService> deco { try { - if (decoratorService.Service is Item<(Guid, string)> item) + if (decoratorService.Value is Item<(Guid, string)> item) { (Guid id, string name) = item.Value; await mediator.Handle, bool>(new UpdateEventArgs<(Guid, int)>((id, 0))); diff --git a/Wallet/Wallet.csproj b/Wallet/Wallet.csproj index 5b60063..302e079 100644 --- a/Wallet/Wallet.csproj +++ b/Wallet/Wallet.csproj @@ -8,6 +8,16 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Wallet/WalletActivatedHandler.cs b/Wallet/WalletActivatedHandler.cs index 533aee8..22b78e4 100644 --- a/Wallet/WalletActivatedHandler.cs +++ b/Wallet/WalletActivatedHandler.cs @@ -25,7 +25,7 @@ public class WalletActivatedHandler(IWalletHostCollection Wallets, IDecoratorService> profileImageDecorator = host.Services.GetRequiredService>>(); - ProfileImage? profileImage = profileImageDecorator.Service; + ProfileImage? profileImage = profileImageDecorator.Value; if (serviceFactory.Create(args => args.Initialize(), descriptor.Name, profileImage?.Value, false) is WalletNavigationViewModel viewModel) diff --git a/Wallet/WalletComponent.cs b/Wallet/WalletComponent.cs index 9acbd93..5f99655 100644 --- a/Wallet/WalletComponent.cs +++ b/Wallet/WalletComponent.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.Hosting; +using System.Linq; +using System.Xml.Linq; using Toolkit.Foundation; namespace Wallet; diff --git a/Wallet/WalletConnection.cs b/Wallet/WalletConnection.cs index 0490e93..3ba3098 100644 --- a/Wallet/WalletConnection.cs +++ b/Wallet/WalletConnection.cs @@ -1,8 +1,9 @@ -namespace Wallet; +using Wallet.Data; -public record WalletConnection(string connection) +namespace Wallet; + +public record WalletConnection(string Value) : + IConnection { - private readonly string connection = connection; - - public override string ToString() => connection; + public override string ToString() => Value; } \ No newline at end of file diff --git a/Wallet/WalletConnectionFactory.cs b/Wallet/WalletConnectionFactory.cs new file mode 100644 index 0000000..d8a419c --- /dev/null +++ b/Wallet/WalletConnectionFactory.cs @@ -0,0 +1,35 @@ +using Wallet.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; + +namespace Wallet; + +public class WalletConnectionFactory(IHostEnvironment environment) : + IWalletConnectionFactory +{ + public async Task Create(string name, string key) + { + string databaseFile = $"{Path.Combine(environment.ContentRootPath, name)}.wallet"; + if (File.Exists(databaseFile)) + { + try + { + return await Task.Run(async () => + { + WalletConnection connection = new($"Data Source={databaseFile};Mode=ReadWriteCreate;Pooling=true;Password={key}"); + + using WalletContext context = new(connection); + await context.Database.OpenConnectionAsync().ConfigureAwait(false); + + return connection; + }); + } + catch + { + return null; + } + } + + return null; + } +} diff --git a/Wallet/WalletDatabaseFactory.cs b/Wallet/WalletDatabaseFactory.cs new file mode 100644 index 0000000..8a51571 --- /dev/null +++ b/Wallet/WalletDatabaseFactory.cs @@ -0,0 +1,33 @@ +using Wallet.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; + +namespace Wallet; + +public class WalletDatabaseFactory(IHostEnvironment environment) : + IWalletDatabaseFactory +{ + public async Task Create(string name, string key) + { + string databaseFile = $"{Path.Combine(environment.ContentRootPath, name)}.wallet"; + try + { + WalletConnection connection = new($"Data Source={databaseFile};Mode=ReadWriteCreate;Pooling=true;Password={key}"); + + await Task.Run(async () => + { + using WalletContext context = new(connection); + await context.Database.EnsureCreatedAsync(); + + context.Database.GetDbConnection().Close(); + context.Database.SetConnectionString(null); + }); + } + catch + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Wallet/WalletFactory.cs b/Wallet/WalletFactory.cs index 766a64e..a9f92a9 100644 --- a/Wallet/WalletFactory.cs +++ b/Wallet/WalletFactory.cs @@ -5,8 +5,7 @@ using Toolkit.Foundation; namespace Wallet; public class WalletFactory(ISecurityKeyFactory securityKeyFactory, - IDecoratorService secureKeyStore, - IWalletStoreFactory walletStoreFactory, + IWalletDatabaseFactory walletDatabaseFactory, IWritableConfiguration configuration, IHostEnvironment environment, IImageWriter imageWriter) : @@ -18,9 +17,7 @@ public class WalletFactory(ISecurityKeyFactory securityKeyFactory, { if (securityKeyFactory.Create(Encoding.UTF8.GetBytes(password)) is SecurityKey key) { - secureKeyStore.Set(key); - - if (await walletStoreFactory.Create(name, key)) + if (await walletDatabaseFactory.Create(name, Convert.ToBase64String(key.DecryptedKey))) { configuration.Write(args => args.Key = $"{Convert.ToBase64String(key.Salt)}:" + $"{Convert.ToBase64String(key.EncryptedKey)}:{Convert.ToBase64String(key.DecryptedKey)}"); diff --git a/Wallet/WalletNavigationViewModel.cs b/Wallet/WalletNavigationViewModel.cs index 78d752a..cb125ec 100644 --- a/Wallet/WalletNavigationViewModel.cs +++ b/Wallet/WalletNavigationViewModel.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using Toolkit.Foundation; namespace Wallet; @@ -77,6 +78,15 @@ public partial class WalletNavigationViewModel : return Task.CompletedTask; } + [RelayCommand] + private async Task Lock() + { + if (await Mediator.Handle, bool>(Close.As())) + { + IsOpened = false; + } + } + public Task Handle(ActivatedEventArgs args) { IsActivated = true; diff --git a/Wallet/WalletStoreFactory.cs b/Wallet/WalletStoreFactory.cs deleted file mode 100644 index 14852d3..0000000 --- a/Wallet/WalletStoreFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Wallet.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Toolkit.Foundation; - -namespace Wallet; - -public class WalletStoreFactory(IDecoratorService connection, - IHostEnvironment environment, - IServiceProvider provider) : - IWalletStoreFactory -{ - public async Task Create(string name, - SecurityKey key) - { - connection.Set(new WalletConnection($"Data Source={Path.Combine(environment.ContentRootPath, name)}" + - $".wallet;Mode=ReadWriteCreate;Pooling=true;Password={Convert.ToBase64String(key.DecryptedKey)}")); - - IDbContextFactory dbContextFactory = provider.GetRequiredService>(); - using WalletContext context = await dbContextFactory.CreateDbContextAsync(); - - try - { - await Task.Run(async () => - { - await context.Database.EnsureCreatedAsync().ConfigureAwait(false); - await context.Database.CloseConnectionAsync().ConfigureAwait(false); - - context.Database.SetConnectionString(null); - }).ConfigureAwait(false); - } - catch - { - return false; - } - - return true; - } -} \ No newline at end of file