Ensure containers show up in the menu when they are created

This commit is contained in:
TheXamlGuy
2024-05-12 17:09:59 +01:00
parent 0ae3f60334
commit 975d5e6894
32 changed files with 211 additions and 174 deletions
-40
View File
@@ -1,40 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Toolkit.Foundation;
namespace Bitvault;
public partial class AddItemViewModel :
ObservableCollectionViewModel<IItemViewModel>,
INotificationHandler<Confirm<Item>>
{
[ObservableProperty]
private string named;
public AddItemViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
IContentTemplate template,
NamedComponent named) : base(provider, factory, mediator, publisher, subscriber, disposer)
{
Template = template;
Named = $"{named}";
Add<ItemHeaderViewModel>();
}
public IContentTemplate Template { get; set; }
public async Task Handle(Confirm<Item> args, CancellationToken cancellationToken = default)
{
ItemConfiguration configuration = new();
foreach (IItemViewModel item in this)
{
item.Invoke(configuration);
}
await Mediator.Handle<Create<ItemConfiguration>, bool>(Create.As(configuration), cancellationToken);
}
}
+1
View File
@@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="LinqKit.Microsoft.EntityFrameworkCore" Version="8.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0-preview.3.24172.4" />
</ItemGroup>
<ItemGroup>
+6 -6
View File
@@ -2,25 +2,25 @@
namespace Bitvault;
public record SecureStorage<TValue>(TValue? Value = default);
public record Container<TValue>(TValue? Value = default);
public record Vault
public record Container
{
public Vault(string name, string password)
public Container(string name, string password)
{
Name = name;
Password = password;
}
public Vault(string password)
public Container(string password)
{
Password = password;
}
public static SecureStorage<TValue> As<TValue>(TValue value) => new(value);
public static Container<TValue> As<TValue>(TValue value) => new(value);
public static SecureStorage<TValue> As<TValue>() where TValue : new() => new(new TValue());
public static Container<TValue> As<TValue>() where TValue : new() => new(new TValue());
[MaybeNull]
+26
View File
@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public record ContainerActivatedHandler(IPublisher publisher) :
INotificationHandler<Activated<IComponentHost>>
{
public async Task Handle(Activated<IComponentHost> args, CancellationToken cancellationToken = default)
{
if (args.Value is IComponentHost container)
{
if (container.Services.GetRequiredService<ContainerConfiguration>() is ContainerConfiguration configuration)
{
if (container.Services.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<ContainerNavigationViewModel>(configuration.Name) is ContainerNavigationViewModel viewModel)
{
await publisher.Publish(new Create<IMainNavigationViewModel>(viewModel),
nameof(MainViewModel), cancellationToken);
}
}
}
}
}
}
+1 -1
View File
@@ -7,4 +7,4 @@ public record ContainerConfiguration : ComponentConfiguration
public string? Name { get; set; }
public string? Key { get; set; }
}
}
+2 -2
View File
@@ -6,10 +6,10 @@ using Toolkit.Foundation;
namespace Bitvault;
public class ContainerFactory(IContainer<ContainerConnection> connection,
public class ContainerFactory(IValueStore<ContainerConnection> connection,
IHostEnvironment environment,
IServiceProvider provider) :
IContainer
IContainerFactory
{
public async Task<bool> Create(string name,
SecurityKey key)
+2 -2
View File
@@ -3,7 +3,7 @@
namespace Bitvault;
public partial class ContainerHeaderViewModel : ObservableCollectionViewModel<string, IDisposable>,
INotificationHandler<SecureStorage<Filter<string>>>
INotificationHandler<Container<Filter<string>>>
{
public ContainerHeaderViewModel(IServiceProvider provider,
IServiceFactory factory,
@@ -20,7 +20,7 @@ public partial class ContainerHeaderViewModel : ObservableCollectionViewModel<st
public IContentTemplate Template { get; set; }
public Task Handle(SecureStorage<Filter<string>> args,
public Task Handle(Container<Filter<string>> args,
CancellationToken cancellationToken = default)
{
if (args.Value is Filter<string> filter)
+8 -8
View File
@@ -6,10 +6,10 @@ namespace Bitvault;
public partial class ContainerNavigationViewModel :
ObservableCollectionViewModel<IContainerNavigationViewModel>,
IMainNavigationViewModel,
INotificationHandler<SecureStorage<Opened>>,
INotificationHandler<SecureStorage<Closed>>,
INotificationHandler<SecureStorage<Activated>>,
INotificationHandler<SecureStorage<Deactivated>>
INotificationHandler<Container<Opened>>,
INotificationHandler<Container<Closed>>,
INotificationHandler<Container<Activated>>,
INotificationHandler<Container<Deactivated>>
{
[ObservableProperty]
private bool activated;
@@ -41,7 +41,7 @@ public partial class ContainerNavigationViewModel :
public IContentTemplate Template { get; set; }
public Task Handle(SecureStorage<Opened> args,
public Task Handle(Container<Opened> args,
CancellationToken cancellationToken = default)
{
Add<AllNavigationViewModel>("All");
@@ -53,7 +53,7 @@ public partial class ContainerNavigationViewModel :
return Task.CompletedTask;
}
public Task Handle(SecureStorage<Closed> args,
public Task Handle(Container<Closed> args,
CancellationToken cancellationToken = default)
{
Opened = true;
@@ -62,11 +62,11 @@ public partial class ContainerNavigationViewModel :
return Task.CompletedTask;
}
public Task Handle(SecureStorage<Deactivated> args,
public Task Handle(Container<Deactivated> args,
CancellationToken cancellationToken = default) =>
Task.FromResult(Activated = false);
public Task Handle(SecureStorage<Activated> args,
public Task Handle(Container<Activated> args,
CancellationToken cancellationToken = default) =>
Task.FromResult(Activated = true);
}
+4 -4
View File
@@ -14,7 +14,7 @@ public partial class ContainerViewModel(IServiceProvider provider,
IContentTemplate template,
NamedComponent named,
string? filter = null) : ObservableCollectionViewModel<ItemNavigationViewModel>(provider, factory, mediator, publisher, subscriber, disposer),
INotificationHandler<SecureStorage<Filter<string>>>
INotificationHandler<Container<Filter<string>>>
{
[ObservableProperty]
private string? filter = filter;
@@ -26,17 +26,17 @@ public partial class ContainerViewModel(IServiceProvider provider,
public override async Task Activated()
{
await Publisher.Publish(Vault.As<Activated>());
await Publisher.Publish(Container.As<Activated>());
await base.Activated();
}
public override async Task Deactivated()
{
await Publisher.Publish(Vault.As<Deactivated>());
await Publisher.Publish(Container.As<Deactivated>());
await base.Deactivated();
}
public async Task Handle(SecureStorage<Filter<string>> args,
public async Task Handle(Container<Filter<string>> args,
CancellationToken cancellationToken = default)
{
if (args.Value is Filter<string> filter)
+40 -15
View File
@@ -1,34 +1,59 @@
using Bitvault.Data;
using LinqKit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public class ContainerViewModelHandler(IDbContextFactory<ContainerDbContext> dbContextFactory,
IServiceFactory factory,
IServiceProvider serviceProvider,
IPublisher publisher) :
INotificationHandler<Enumerate<ItemNavigationViewModel, ContainerViewModelConfiguration>>
{
public async Task Handle(Enumerate<ItemNavigationViewModel, ContainerViewModelConfiguration> args,
CancellationToken cancellationToken = default)
{
var items = await Task.Run(async () =>
if (args.Options is ContainerViewModelConfiguration configuration)
{
using ContainerDbContext context = dbContextFactory.CreateDbContext();
return await context.Set<Data.Item>().Select(x => new
{
x.Name,
x.State
}).Where(x => x.State != 3).ToListAsync();
ExpressionStarter<Data.Item> predicate = PredicateBuilder.New<Data.Item>(true);
}, cancellationToken);
foreach (var item in items)
{
if (factory.Create<ItemNavigationViewModel>(item.Name, "Description " + 1) is ItemNavigationViewModel viewModel)
if (configuration.Filter == "All")
{
await publisher.Publish(new Create<ItemNavigationViewModel>(viewModel),
nameof(ContainerViewModel), cancellationToken);
predicate = predicate.And(x => x.State != 3);
}
if (configuration.Filter == "Starred")
{
predicate = predicate.And(x => x.State != 3 && x.State == 2);
}
if (configuration.Filter == "Archive")
{
predicate = predicate.And(x => x.State == 3);
}
var items = await Task.Run(async () =>
{
using ContainerDbContext context = dbContextFactory.CreateDbContext();
return await context.Set<Data.Item>().Where(predicate).Select(x => new
{
x.Id,
x.Name
}).ToListAsync();
}, cancellationToken);
foreach (var item in items)
{
IServiceScope serviceScope = serviceProvider.CreateScope();
IServiceFactory serviceFactory = serviceScope.ServiceProvider.GetRequiredService<IServiceFactory>();
if (serviceFactory.Create<ItemNavigationViewModel>(item.Id, item.Name, "Description " + 1) is ItemNavigationViewModel viewModel)
{
await publisher.Publish(new Create<ItemNavigationViewModel>(viewModel),
nameof(ContainerViewModel), cancellationToken);
}
}
}
}
+11 -9
View File
@@ -5,26 +5,27 @@ using Toolkit.Foundation;
namespace Bitvault;
public class CreateContainerHandler(IContainerComponentFactory componentFactory) :
IHandler<Create<Vault>, bool>
public class CreateContainerHandler(IContainerComponentFactory componentFactory,
IPublisher publisher) :
IHandler<Create<Container>, bool>
{
public async Task<bool> Handle(Create<Vault> args,
public async Task<bool> Handle(Create<Container> args,
CancellationToken cancellationToken)
{
if (args.Value is Vault vault && vault.Name is { Length: > 0 } name &&
vault.Password is { Length: > 0 } password)
if (args.Value is Container container && container.Name is { Length: > 0 } name &&
container.Password is { Length: > 0 } password)
{
if (componentFactory.Create(name) is IComponentHost host)
{
ISecurityKeyFactory keyVaultFactory = host.Services.GetRequiredService<ISecurityKeyFactory>();
IContainer<SecurityKey> vaultKeyContainer = host.Services.GetRequiredService<IContainer<SecurityKey>>();
IContainer vaultStorage = host.Services.GetRequiredService<IContainer>();
IValueStore<SecurityKey> secureKeyStore = host.Services.GetRequiredService<IValueStore<SecurityKey>>();
IContainerFactory containerFactory = host.Services.GetRequiredService<IContainerFactory>();
if (keyVaultFactory.Create(Encoding.UTF8.GetBytes(password)) is SecurityKey key)
{
vaultKeyContainer.Set(key);
secureKeyStore.Set(key);
if (await vaultStorage.Create(name, key))
if (await containerFactory.Create(name, key))
{
IWritableConfiguration<ContainerConfiguration> configuration =
host.Services.GetRequiredService<IWritableConfiguration<ContainerConfiguration>>();
@@ -32,6 +33,7 @@ public class CreateContainerHandler(IContainerComponentFactory componentFactory)
configuration.Write(args => args.Key = $"{Convert.ToBase64String(key.Salt)}:{Convert.ToBase64String(key.EncryptedKey)}:{Convert.ToBase64String(key.DecryptedKey)}");
host.Start();
await publisher.Publish(Activated.As(host), cancellationToken);
return true;
}
}
+1 -1
View File
@@ -22,5 +22,5 @@ public partial class CreateContainerViewModel(IServiceProvider provider,
private string password;
public async Task<bool> Confirm() =>
await Mediator.Handle<Create<Vault>, bool>(Create.As(new Vault(Name, Password)));
await Mediator.Handle<Create<Container>, bool>(Create.As(new Container(Name, Password)));
}
@@ -6,8 +6,8 @@ namespace Bitvault;
public partial class FilterContainerNavigationViewModel : ObservableViewModel,
IContainerNavigationViewModel,
INotificationHandler<SecureStorage<Activated>>,
INotificationHandler<SecureStorage<Deactivated>>
INotificationHandler<Container<Activated>>,
INotificationHandler<Container<Deactivated>>
{
[ObservableProperty]
private bool activated;
@@ -29,14 +29,14 @@ public partial class FilterContainerNavigationViewModel : ObservableViewModel,
Filter = filter;
}
public Task Handle(SecureStorage<Deactivated> args,
public Task Handle(Container<Deactivated> args,
CancellationToken cancellationToken = default) =>
Task.FromResult(Activated = false);
public Task Handle(SecureStorage<Activated> args,
public Task Handle(Container<Activated> args,
CancellationToken cancellationToken = default) =>
Task.FromResult(Activated = true);
[RelayCommand]
public async Task Invoke() => await Publisher.Publish(Vault.As(new Filter<string>(Filter)));
public async Task Invoke() => await Publisher.Publish(Container.As(new Filter<string>(Filter)));
}
@@ -1,6 +1,6 @@
namespace Bitvault;
public interface IContainer
public interface IContainerFactory
{
Task<bool> Create(string name, SecurityKey key);
}
@@ -2,9 +2,9 @@
namespace Bitvault;
public partial class AddItemCommandHeaderViewModel : ObservableCollectionViewModel
public partial class ItemCommandHeaderViewModel : ObservableCollectionViewModel
{
public AddItemCommandHeaderViewModel(IServiceProvider provider,
public ItemCommandHeaderViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
-1
View File
@@ -17,7 +17,6 @@ public class ItemConfigurationHandler(IDbContextFactory<ContainerDbContext> dbCo
await Task.Run(async () =>
{
using ContainerDbContext context = dbContextFactory.CreateDbContext();
await context.AddAsync(new Data.Item { Name = configuration.Name }, cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}, cancellationToken);
+3 -1
View File
@@ -7,9 +7,11 @@ public partial class ItemHeaderViewModel(IServiceProvider provider,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer) : ObservableViewModel<string, string>(provider, factory, mediator, publisher, subscriber, disposer),
IDisposer disposer,
string? value = null) : ObservableViewModel<string, string>(provider, factory, mediator, publisher, subscriber, disposer, value),
IItemViewModel
{
public void Invoke(ItemConfiguration args) =>
args.Name = Value;
}
+4
View File
@@ -11,6 +11,7 @@ public partial class ItemNavigationViewModel(IServiceProvider provider,
IDisposer disposer,
IContentTemplate template,
NamedComponent named,
int id,
string name,
string description) :
ObservableViewModel(provider, factory, mediator, publisher, subscriber, disposer)
@@ -18,6 +19,9 @@ public partial class ItemNavigationViewModel(IServiceProvider provider,
[ObservableProperty]
private string? description = description;
[ObservableProperty]
private int id = id;
[ObservableProperty]
private string? name = name;
+42 -11
View File
@@ -1,15 +1,46 @@
using Toolkit.Foundation;
using CommunityToolkit.Mvvm.ComponentModel;
using Toolkit.Foundation;
namespace Bitvault;
public class ItemViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
IContentTemplate template) :
ObservableViewModel(provider, factory, mediator, publisher, subscriber, disposer)
public partial class ItemViewModel :
ObservableCollectionViewModel<IItemViewModel>,
INotificationHandler<Confirm<Item>>
{
public IContentTemplate Template { get; set; } = template;
}
[ObservableProperty]
private int? id;
[ObservableProperty]
private string named;
public ItemViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer,
IContentTemplate template,
NamedComponent named,
int? id = null,
string? name = null) : base(provider, factory, mediator, publisher, subscriber, disposer)
{
Template = template;
Named = $"{named}";
Id = id;
Add<ItemHeaderViewModel>(name ?? "");
}
public IContentTemplate Template { get; set; }
public async Task Handle(Confirm<Item> args, CancellationToken cancellationToken = default)
{
ItemConfiguration configuration = new();
foreach (IItemViewModel item in this)
{
item.Invoke(configuration);
}
await Mediator.Handle<Create<ItemConfiguration>, bool>(Create.As(configuration), cancellationToken);
}
}
+4 -4
View File
@@ -4,17 +4,17 @@ using Toolkit.Foundation;
namespace Bitvault;
public class MainViewModelHandler(IPublisher publisher,
IContainerHostCollection vaults) :
IContainerHostCollection containers) :
INotificationHandler<Enumerate<IMainNavigationViewModel>>
{
public async Task Handle(Enumerate<IMainNavigationViewModel> args,
CancellationToken cancellationToken = default)
{
foreach (IComponentHost vault in vaults)
foreach (IComponentHost container in containers)
{
if (vault.Services.GetRequiredService<ContainerConfiguration>() is ContainerConfiguration configuration)
if (container.Services.GetRequiredService<ContainerConfiguration>() is ContainerConfiguration configuration)
{
if (vault.Services.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
if (container.Services.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<ContainerNavigationViewModel>(configuration.Name) is ContainerNavigationViewModel viewModel)
{
+4 -4
View File
@@ -5,13 +5,13 @@ namespace Bitvault;
public class OpenContainerHandler(ContainerConfiguration configuration,
ISecurityKeyFactory keyVaultFactory,
IContainer vaultStorage) :
IHandler<Open<Vault>, bool>
IContainerFactory vaultStorage) :
IHandler<Open<Container>, bool>
{
public async Task<bool> Handle(Open<Vault> args,
public async Task<bool> Handle(Open<Container> args,
CancellationToken cancellationToken)
{
if (args.Value is Vault vault && configuration.Name is { Length: > 0 } name && vault.Password is { Length: > 0 } password)
if (args.Value is Container container && configuration.Name is { Length: > 0 } name && container.Password is { Length: > 0 } password)
{
if (configuration.Key?.Split(':') is { Length: >= 2 } keyPart)
{
+2 -2
View File
@@ -20,9 +20,9 @@ public partial class OpenContainerViewModel(IServiceProvider provider,
{
if (Password is { Length: > 0 })
{
if (await Mediator.Handle<Open<Vault>, bool>(Open.As(new Vault(Password))))
if (await Mediator.Handle<Open<Container>, bool>(Open.As(new Container(Password))))
{
await Publisher.Publish(Vault.As<Opened>());
await Publisher.Publish(Container.As<Opened>());
}
}
}