Container traits to extensions

This commit is contained in:
2023-08-03 10:48:39 +02:00
parent 86cffa6aa4
commit 558a0a08bb
32 changed files with 258 additions and 158 deletions

View File

@@ -1,9 +0,0 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner;
public interface IContainerScanSnapshotProvider : IContentProvider
{
ISizeScanTask StartSizeScan(IContainer scanSizeOf);
}

View File

@@ -0,0 +1,10 @@
using FileTime.App.Core.Services;
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner;
public interface IContainerSizeScanProvider : IContentProvider, IExitHandler
{
ISizeScanTask StartSizeScan(IContainer scanSizeOf);
}

View File

@@ -1,17 +1,16 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using DeclarativeProperty; using DeclarativeProperty;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
public interface ISizeScanContainer : ISizeItem, IContainer, IStatusProviderContainer public interface ISizeScanContainer : ISizeItem, IContainer
{ {
public Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement); public Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement);
ObservableCollection<ISizeScanContainer> ChildContainers { get; } ObservableCollection<ISizeScanContainer> ChildContainers { get; }
ObservableCollection<ISizeScanElement> ChildElements { get; }
ObservableCollection<ISizeItem> SizeItems { get; } ObservableCollection<ISizeItem> SizeItems { get; }
IContainer RealContainer { get; init; } IContainer RealContainer { get; init; }
Task StartLoadingAsync(); Task StartLoadingAsync();
Task StopLoadingAsync(); Task StopLoadingAsync();
Task AddSizeChildAsync(ISizeItem newChild);
} }

View File

@@ -1,4 +1,5 @@
using FileTime.App.Core.Exceptions; using FileTime.App.Core.Exceptions;
using FileTime.App.Core.Services;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -7,14 +8,14 @@ using InitableService;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
public class ContainerScanProvider : ContentProviderBase, IContainerScanSnapshotProvider public class ContainerSizeSizeScanProvider : ContentProviderBase, IContainerSizeScanProvider
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly List<ISizeScanTask> _sizeScanTasks = new(); private readonly List<ISizeScanTask> _sizeScanTasks = new();
internal const string ContentProviderName = "container-size-scan"; internal const string ContentProviderName = "container-size-scan";
public ContainerScanProvider( public ContainerSizeSizeScanProvider(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: base(ContentProviderName, timelessContentProvider) : base(ContentProviderName, timelessContentProvider)
@@ -110,4 +111,20 @@ public class ContainerScanProvider : ContentProviderBase, IContainerScanSnapshot
return searchTask; return searchTask;
} }
public Task ExitAsync(CancellationToken token = default)
{
foreach (var sizeScanTask in _sizeScanTasks)
{
try
{
sizeScanTask.Stop();
}
catch
{
}
}
return Task.CompletedTask;
}
} }

View File

@@ -5,14 +5,15 @@ using DeclarativeProperty;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits; using FileTime.Core.Models.Extensions;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
//TODO: create readonly version //TODO: create readonly version, or not...
public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer public record SizeScanContainer : ISizeScanContainer
{ {
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ReadOnlyExtensionCollection _readOnlyExtensions; private readonly ReadOnlyExtensionCollection _readOnlyExtensions;
private readonly BehaviorSubject<bool> _isLoading = new(false); private readonly BehaviorSubject<bool> _isLoading = new(false);
private readonly CombineProperty<long, long> _size; private readonly CombineProperty<long, long> _size;
@@ -38,25 +39,35 @@ public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer
public ObservableCollection<AbsolutePath> Items { get; } = new(); public ObservableCollection<AbsolutePath> Items { get; } = new();
public IObservable<bool> IsLoading { get; } public IObservable<bool> IsLoading { get; }
public bool? IsLoaded { get; private set; } public bool? IsLoaded { get; private set; }
public Task WaitForLoaded(CancellationToken token = default) => throw new NotImplementedException();
public async Task WaitForLoaded(CancellationToken token = default)
{
while (IsLoaded != true) await Task.Delay(1, token);
}
public bool AllowRecursiveDeletion => false; public bool AllowRecursiveDeletion => false;
public IDeclarativeProperty<long> Size { get; } public IDeclarativeProperty<long> Size { get; }
public ObservableCollection<ISizeScanContainer> ChildContainers { get; } = new(); public ObservableCollection<ISizeScanContainer> ChildContainers { get; } = new();
public ObservableCollection<ISizeScanElement> ChildElements { get; } = new();
public ObservableCollection<ISizeItem> SizeItems { get; } = new(); public ObservableCollection<ISizeItem> SizeItems { get; } = new();
public required IContainer RealContainer { get; init; } public required IContainer RealContainer { get; init; }
internal SizeScanContainer() internal SizeScanContainer(ITimelessContentProvider timelessContentProvider)
{ {
_timelessContentProvider = timelessContentProvider;
_readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions); _readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions);
IsLoading = _isLoading.AsObservable(); IsLoading = _isLoading.AsObservable();
CreatedAt = DateTime.Now;
_size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum())); _size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum()));
Size = _size.Debounce(TimeSpan.FromSeconds(1)); Size = _size.Debounce(TimeSpan.FromSeconds(1));
CreatedAt = DateTime.Now;
Extensions.Add(new EscHandlerContainerExtension(HandleEsc));
Extensions.Add(new NonRestorableContainerExtension());
Extensions.Add(new RealContainerProviderExtension(() => new(_timelessContentProvider, RealContainer!)));
Extensions.Add(new StatusProviderContainerExtension(() => Status));
} }
public async Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement) public async Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement)
@@ -90,4 +101,19 @@ public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer
SizeScanTask.Stop(); SizeScanTask.Stop();
return Task.FromResult(new ContainerEscapeResult(true)); return Task.FromResult(new ContainerEscapeResult(true));
} }
public async Task AddSizeChildAsync(ISizeItem newChild)
{
SizeItems.Add(newChild);
if (newChild is ISizeScanContainer newContainer)
{
ChildContainers.Add(newContainer);
}
await AddSizeSourceAsync(newChild.Size);
Items.Add(new AbsolutePath(
_timelessContentProvider,
newChild));
}
} }

View File

@@ -1,5 +1,4 @@
using DeclarativeProperty; using DeclarativeProperty;
using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.Extensions; using FileTime.Core.Models.Extensions;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
@@ -13,7 +12,7 @@ public class SizeScanTask : ISizeScanTask
private int _processedItems; private int _processedItems;
private ulong _processedItemsTotal; private ulong _processedItemsTotal;
private IContainer _scanSizeOf = null!; private IContainer _scanSizeOf = null!;
private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider; private readonly IContainerSizeScanProvider _containerSizeScanProvider;
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ILogger<SizeScanTask> _logger; private readonly ILogger<SizeScanTask> _logger;
private Thread? _sizeScanThread; private Thread? _sizeScanThread;
@@ -24,11 +23,11 @@ public class SizeScanTask : ISizeScanTask
public bool IsRunning { get; private set; } public bool IsRunning { get; private set; }
public SizeScanTask( public SizeScanTask(
IContainerScanSnapshotProvider containerScanSnapshotProvider, IContainerSizeScanProvider containerSizeScanProvider,
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
ILogger<SizeScanTask> logger) ILogger<SizeScanTask> logger)
{ {
_containerScanSnapshotProvider = containerScanSnapshotProvider; _containerSizeScanProvider = containerSizeScanProvider;
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_logger = logger; _logger = logger;
_containerStatusDebounced = _containerStatus.Debounce(TimeSpan.FromMilliseconds(250)); _containerStatusDebounced = _containerStatus.Debounce(TimeSpan.FromMilliseconds(250));
@@ -38,16 +37,16 @@ public class SizeScanTask : ISizeScanTask
{ {
_scanSizeOf = scanSizeOf; _scanSizeOf = scanSizeOf;
var name = $"{_searchId++}_{scanSizeOf.Name}"; var name = $"{_searchId++}_{scanSizeOf.Name}";
var randomId = ContainerScanProvider.ContentProviderName + Constants.SeparatorChar + name; var randomId = ContainerSizeSizeScanProvider.ContentProviderName + Constants.SeparatorChar + name;
SizeSizeScanContainer = new SizeScanContainer SizeSizeScanContainer = new SizeScanContainer(_timelessContentProvider)
{ {
Name = name, Name = name,
DisplayName = scanSizeOf.DisplayName, DisplayName = scanSizeOf.DisplayName,
FullName = new FullName(randomId), FullName = new FullName(randomId),
NativePath = new NativePath(randomId), NativePath = new NativePath(randomId),
Parent = new AbsolutePath(_timelessContentProvider, _containerScanSnapshotProvider), Parent = new AbsolutePath(_timelessContentProvider, _containerSizeScanProvider),
RealContainer = scanSizeOf, RealContainer = scanSizeOf,
Provider = _containerScanSnapshotProvider, Provider = _containerSizeScanProvider,
Status = _containerStatusDebounced, Status = _containerStatusDebounced,
SizeScanTask = this SizeScanTask = this
}; };
@@ -87,8 +86,8 @@ public class SizeScanTask : ISizeScanTask
IContainer realContainer, IContainer realContainer,
ISizeScanContainer sizeScanContainer) ISizeScanContainer sizeScanContainer)
{ {
if(_cancelled) return; if (_cancelled) return;
await realContainer.WaitForLoaded(); await realContainer.WaitForLoaded();
var resolvedItems = new List<IItem>(realContainer.Items.Count); var resolvedItems = new List<IItem>(realContainer.Items.Count);
foreach (var item in realContainer.Items) foreach (var item in realContainer.Items)
@@ -99,43 +98,36 @@ public class SizeScanTask : ISizeScanTask
foreach (var element in resolvedItems.OfType<IElement>()) foreach (var element in resolvedItems.OfType<IElement>())
{ {
if(_cancelled) return; if (_cancelled) return;
var fileExtension = element.GetExtension<FileExtension>(); var fileExtension = element.GetExtension<FileExtension>();
if (fileExtension?.Size is not { } size) continue; var size = fileExtension?.Size ?? 0;
var sizeProperty = new DeclarativeProperty<long>(size); var sizeProperty = new DeclarativeProperty<long>(size);
var childName = sizeScanContainer.FullName!.GetChild(element.Name).Path; var childName = sizeScanContainer.FullName!.GetChild(element.Name).Path;
await sizeScanContainer.AddSizeSourceAsync(sizeProperty);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider,
PointInTime.Present,
new FullName(childName),
AbsolutePathType.Element));
var childElement = new SizeScanElement
var childSearchContainer = new SizeScanElement
{ {
Name = element.Name, Name = element.Name,
DisplayName = element.DisplayName, DisplayName = element.DisplayName,
FullName = new FullName(childName), FullName = new FullName(childName),
NativePath = new NativePath(childName), NativePath = new NativePath(childName),
Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer), Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer),
Provider = _containerScanSnapshotProvider, Provider = _containerSizeScanProvider,
Size = sizeProperty Size = sizeProperty
}; };
sizeScanContainer.SizeItems.Add(childSearchContainer); await sizeScanContainer.AddSizeChildAsync(childElement);
_processedItems++; _processedItems++;
_processedItemsTotal++; _processedItemsTotal++;
} }
foreach (var childContainer in resolvedItems.OfType<IContainer>()) foreach (var childContainer in resolvedItems.OfType<IContainer>())
{ {
if(_cancelled) return; if (_cancelled) return;
var childName = sizeScanContainer.FullName!.GetChild(childContainer.Name).Path; var childName = sizeScanContainer.FullName!.GetChild(childContainer.Name).Path;
var childSearchContainer = new SizeScanContainer var childSearchContainer = new SizeScanContainer(_timelessContentProvider)
{ {
Name = childContainer.Name, Name = childContainer.Name,
DisplayName = childContainer.DisplayName, DisplayName = childContainer.DisplayName,
@@ -143,19 +135,12 @@ public class SizeScanTask : ISizeScanTask
NativePath = new NativePath(childName), NativePath = new NativePath(childName),
Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer), Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer),
RealContainer = childContainer, RealContainer = childContainer,
Provider = _containerScanSnapshotProvider, Provider = _containerSizeScanProvider,
Status = _containerStatusDebounced, Status = _containerStatusDebounced,
SizeScanTask = this SizeScanTask = this
}; };
sizeScanContainer.ChildContainers.Add(childSearchContainer); await sizeScanContainer.AddSizeChildAsync(childSearchContainer);
sizeScanContainer.SizeItems.Add(childSearchContainer);
await sizeScanContainer.AddSizeSourceAsync(childSearchContainer.Size);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider,
PointInTime.Present,
new FullName(childName),
AbsolutePathType.Container));
await TraverseTree(childContainer, childSearchContainer); await TraverseTree(childContainer, childSearchContainer);
} }

View File

@@ -9,8 +9,9 @@ public static class Startup
{ {
public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services) public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services)
{ {
services.TryAddSingleton<IContainerScanSnapshotProvider, ContainerScanProvider>(); services.TryAddSingleton<IContainerSizeScanProvider, ContainerSizeSizeScanProvider>();
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<IContainerScanSnapshotProvider>()); services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<IContainerSizeScanProvider>());
services.AddSingleton<IExitHandler>(sp => sp.GetRequiredService<IContainerSizeScanProvider>());
services.AddTransient<ISizeScanTask, SizeScanTask>(); services.AddTransient<ISizeScanTask, SizeScanTask>();
services.AddTransient<IItemPreviewProvider, PreviewProvider>(); services.AddTransient<IItemPreviewProvider, PreviewProvider>();
return services; return services;

View File

@@ -8,25 +8,24 @@ public enum ItemNotFoundExceptionType
FullName, FullName,
NativePath NativePath
} }
public class ItemNotFoundException : Exception public class ItemNotFoundException : Exception
{ {
public string Path { get; } public string Path { get; }
public ItemNotFoundExceptionType Type { get; } = ItemNotFoundExceptionType.Raw; public ItemNotFoundExceptionType Type { get; } = ItemNotFoundExceptionType.Raw;
public ItemNotFoundException(string path) public ItemNotFoundException(string path) : base("Item not found " + path)
{ {
Path = path; Path = path;
} }
public ItemNotFoundException(FullName path) public ItemNotFoundException(FullName path) : this(path.Path)
{ {
Path = path.Path;
Type = ItemNotFoundExceptionType.FullName; Type = ItemNotFoundExceptionType.FullName;
} }
public ItemNotFoundException(NativePath path) public ItemNotFoundException(NativePath path) : this(path.Path)
{ {
Path = path.Path;
Type = ItemNotFoundExceptionType.NativePath; Type = ItemNotFoundExceptionType.NativePath;
} }
} }

View File

@@ -10,4 +10,5 @@ public interface IModalService
void OpenModal(IModalViewModel modalToOpen); void OpenModal(IModalViewModel modalToOpen);
void CloseModal(IModalViewModel modalToClose); void CloseModal(IModalViewModel modalToClose);
T OpenModal<T>() where T : IModalViewModel; T OpenModal<T>() where T : IModalViewModel;
event EventHandler? AllModalClosed;
} }

View File

@@ -2,6 +2,7 @@ using System.Text.Json;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.Extensions;
using FileTime.Core.Services; using FileTime.Core.Services;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Providers.Local; using FileTime.Providers.Local;
@@ -202,7 +203,14 @@ public class TabPersistenceService : ITabPersistenceService
{ {
var currentLocation = tab.CurrentLocation.Value; var currentLocation = tab.CurrentLocation.Value;
if (currentLocation is null) continue; if (currentLocation is null) continue;
tabStates.Add(new TabState(currentLocation.FullName!.Path, tab.TabNumber)); var path = currentLocation.FullName!.Path;
if (currentLocation.GetExtension<RealContainerProviderExtension>()?.RealContainer() is { } realPath)
{
path = realPath.Path.Path;
}
tabStates.Add(new TabState(path, tab.TabNumber));
} }
return new TabStates( return new TabStates(

View File

@@ -13,6 +13,7 @@ using FileTime.Core.Services;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using InitableService; using InitableService;
using Microsoft.Extensions.Logging;
namespace FileTime.App.Core.Services.UserCommandHandler; namespace FileTime.App.Core.Services.UserCommandHandler;
@@ -27,6 +28,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
private readonly IUserCommunicationService _userCommunicationService; private readonly IUserCommunicationService _userCommunicationService;
private readonly IFrequencyNavigationService _frequencyNavigationService; private readonly IFrequencyNavigationService _frequencyNavigationService;
private readonly ICommandPaletteService _commandPaletteService; private readonly ICommandPaletteService _commandPaletteService;
private readonly ILogger<NavigationUserCommandHandlerService> _logger;
private ITabViewModel? _selectedTab; private ITabViewModel? _selectedTab;
private IDeclarativeProperty<IContainer?>? _currentLocation; private IDeclarativeProperty<IContainer?>? _currentLocation;
private IDeclarativeProperty<IItemViewModel?>? _currentSelectedItem; private IDeclarativeProperty<IItemViewModel?>? _currentSelectedItem;
@@ -41,7 +43,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IUserCommunicationService userCommunicationService, IUserCommunicationService userCommunicationService,
IFrequencyNavigationService frequencyNavigationService, IFrequencyNavigationService frequencyNavigationService,
ICommandPaletteService commandPaletteService) : base(appState) ICommandPaletteService commandPaletteService,
ILogger<NavigationUserCommandHandlerService> logger) : base(appState)
{ {
_appState = appState; _appState = appState;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
@@ -51,6 +54,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
_userCommunicationService = userCommunicationService; _userCommunicationService = userCommunicationService;
_frequencyNavigationService = frequencyNavigationService; _frequencyNavigationService = frequencyNavigationService;
_commandPaletteService = commandPaletteService; _commandPaletteService = commandPaletteService;
_logger = logger;
SaveSelectedTab(t => _selectedTab = t); SaveSelectedTab(t => _selectedTab = t);
SaveCurrentSelectedItem(i => _currentSelectedItem = i); SaveCurrentSelectedItem(i => _currentSelectedItem = i);
@@ -332,11 +336,23 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
} }
else if (tabViewModel == null) else if (tabViewModel == null)
{ {
var newLocation = _currentLocation?.Value?.FullName is { } fullName IContainer? newLocation = null;
? (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(fullName, PointInTime.Present)
: _localContentProvider;
var tab = await _serviceProvider.GetAsyncInitableResolver<IContainer>(newLocation) try
{
newLocation = _currentLocation?.Value?.FullName is { } fullName
? (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(fullName, PointInTime.Present)
: _localContentProvider;
}
catch(Exception ex)
{
var fullName = _currentLocation?.Value?.FullName?.Path ?? "unknown";
_logger.LogError(ex, "Could not resolve container while switching to tab {TabNumber} to path {FullName}", number, fullName);
}
newLocation ??= _localContentProvider;
var tab = await _serviceProvider.GetAsyncInitableResolver(newLocation)
.GetRequiredServiceAsync<ITab>(); .GetRequiredServiceAsync<ITab>();
var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>(); var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>();

View File

@@ -22,7 +22,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IUserCommandHandlerService _userCommandHandlerService;
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider; private readonly IContainerSizeScanProvider _containerSizeScanProvider;
private IDeclarativeProperty<IContainer?>? _currentLocation; private IDeclarativeProperty<IContainer?>? _currentLocation;
private IDeclarativeProperty<IItemViewModel?>? _currentSelectedItem; private IDeclarativeProperty<IItemViewModel?>? _currentSelectedItem;
private ITabViewModel? _currentSelectedTab; private ITabViewModel? _currentSelectedTab;
@@ -36,7 +36,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IUserCommandHandlerService userCommandHandlerService, IUserCommandHandlerService userCommandHandlerService,
IContentAccessorFactory contentAccessorFactory, IContentAccessorFactory contentAccessorFactory,
IContainerScanSnapshotProvider containerScanSnapshotProvider) : base(appState) IContainerSizeScanProvider containerSizeScanProvider) : base(appState)
{ {
_systemClipboardService = systemClipboardService; _systemClipboardService = systemClipboardService;
_userCommunicationService = userCommunicationService; _userCommunicationService = userCommunicationService;
@@ -45,7 +45,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_userCommandHandlerService = userCommandHandlerService; _userCommandHandlerService = userCommandHandlerService;
_contentAccessorFactory = contentAccessorFactory; _contentAccessorFactory = contentAccessorFactory;
_containerScanSnapshotProvider = containerScanSnapshotProvider; _containerSizeScanProvider = containerSizeScanProvider;
SaveCurrentLocation(l => _currentLocation = l); SaveCurrentLocation(l => _currentLocation = l);
SaveCurrentSelectedItem(i => _currentSelectedItem = i); SaveCurrentSelectedItem(i => _currentSelectedItem = i);
SaveSelectedTab(t => _currentSelectedTab = t); SaveSelectedTab(t => _currentSelectedTab = t);
@@ -65,7 +65,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
{ {
if (_currentLocation?.Value is null) return; if (_currentLocation?.Value is null) return;
var searchTask = _containerScanSnapshotProvider.StartSizeScan(_currentLocation.Value); var searchTask = _containerSizeScanProvider.StartSizeScan(_currentLocation.Value);
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer)); var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer));
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand); await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
} }

View File

@@ -2,11 +2,9 @@ using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DeclarativeProperty; using DeclarativeProperty;
using DynamicData;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.Core.ViewModels.Timeline;
using FileTime.Core.Models; using FileTime.Core.Models.Extensions;
using FileTime.Core.Models.ContainerTraits;
using MvvmGen; using MvvmGen;
using MoreLinq; using MoreLinq;
@@ -48,7 +46,7 @@ public abstract partial class AppStateBase : IAppState
ContainerStatus = SelectedTab ContainerStatus = SelectedTab
.Map(t => t?.CurrentLocation) .Map(t => t?.CurrentLocation)
.Switch() .Switch()
.Map(c => c is IStatusProviderContainer statusProvider ? statusProvider.Status : null) .Map(c => c?.GetExtension<StatusProviderContainerExtension>()?.GetStatusProperty())
.Switch(); .Switch();
} }

View File

@@ -6,6 +6,7 @@ using FileTime.App.Core.Services;
using FileTime.App.FrequencyNavigation.Models; using FileTime.App.FrequencyNavigation.Models;
using FileTime.App.FrequencyNavigation.ViewModels; using FileTime.App.FrequencyNavigation.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.Extensions;
using FileTime.Core.Services; using FileTime.Core.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
@@ -38,9 +39,16 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
tabEvents.LocationChanged += OnTabLocationChanged; tabEvents.LocationChanged += OnTabLocationChanged;
} }
void OnTabLocationChanged(object? sender, TabLocationChanged e) async void OnTabLocationChanged(object? sender, TabLocationChanged e)
{ {
IncreaseContainerScore(e.Location); try
{
await IncreaseContainerScore(e.Location);
}
catch (Exception ex)
{
_logger.LogError(ex, "Fatal error while increasing container score");
}
} }
public void OpenNavigationWindow() public void OpenNavigationWindow()
@@ -59,12 +67,16 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
} }
} }
private async void IncreaseContainerScore(FullName containerName) private async Task IncreaseContainerScore(IContainer container)
{ {
await _saveLock.WaitAsync(); await _saveLock.WaitAsync();
try try
{ {
var containerNameString = containerName.Path; if (container.GetExtension<NonRestorableContainerExtension>() is not null) return;
var containerNameString = container.FullName?.Path;
if (containerNameString is null) return;
if (_containerScores.ContainsKey(containerNameString)) if (_containerScores.ContainsKey(containerNameString))
{ {
_containerScores[containerNameString].Score++; _containerScores[containerNameString].Score++;
@@ -89,6 +101,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5)) if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5))
{ {
} }
//TODO: move to if above //TODO: move to if above
await SaveStateAsync(); await SaveStateAsync();
} }
@@ -113,7 +126,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
var itemsToRemove = new List<string>(); var itemsToRemove = new List<string>();
foreach (var container in _containerScores) foreach (var container in _containerScores)
{ {
var newScore = (int)Math.Floor(container.Value.Score * 0.9); var newScore = (int) Math.Floor(container.Value.Score * 0.9);
if (newScore > 0) if (newScore > 0)
{ {
container.Value.Score = newScore; container.Value.Score = newScore;

View File

@@ -1,6 +1,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.Extensions;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.App.Search; namespace FileTime.App.Search;
@@ -38,7 +39,8 @@ public class SearchTask : ISearchTask
var extensions = new ExtensionCollection var extensions = new ExtensionCollection
{ {
new SearchExtension(this) new SearchExtension(this),
new RealContainerProviderExtension(() => new AbsolutePath(_timelessContentProvider, baseContainer))
}; };
_container = new Container( _container = new Container(
baseContainer.Name, baseContainer.Name,
@@ -102,9 +104,9 @@ public class SearchTask : ISearchTask
var childName = _container.FullName.GetChild(itemPath.Path.GetName()); var childName = _container.FullName.GetChild(itemPath.Path.GetName());
_realFullNames.Add(childName, itemPath.Path); _realFullNames.Add(childName, itemPath.Path);
_items.Add(new AbsolutePath( _items.Add(new AbsolutePath(
_timelessContentProvider, _timelessContentProvider,
PointInTime.Present, PointInTime.Present,
childName, childName,
AbsolutePathType.Container AbsolutePathType.Container
)); ));
} }

View File

@@ -1,6 +0,0 @@
namespace FileTime.Core.Models.ContainerTraits;
public interface IEscHandlerContainer
{
Task<ContainerEscapeResult> HandleEsc();
}

View File

@@ -1,8 +0,0 @@
using DeclarativeProperty;
namespace FileTime.Core.Models.ContainerTraits;
public interface IStatusProviderContainer
{
IDeclarativeProperty<string> Status { get; }
}

View File

@@ -6,20 +6,12 @@ namespace FileTime.Core.Models;
public class ExtensionCollection : IEnumerable<object> public class ExtensionCollection : IEnumerable<object>
{ {
private readonly List<object> _extensions = new(); private readonly HashSet<object> _extensions = new();
public ExtensionCollection() public ExtensionCollection()
{ {
} }
public ExtensionCollection(IEnumerable<object> objects)
{
foreach (var obj in objects)
{
AddSafe(obj);
}
}
private void AddSafe(object obj) private void AddSafe(object obj)
{ {
var objType = obj.GetType(); var objType = obj.GetType();
@@ -35,12 +27,7 @@ public class ExtensionCollection : IEnumerable<object>
AddSafe(obj); AddSafe(obj);
} }
public void Remove<T>() public ReadOnlyExtensionCollection AsReadOnly() => new(this);
{
_extensions.RemoveAll(i => i is T);
}
public ReadOnlyExtensionCollection AsReadOnly() => new ReadOnlyExtensionCollection(this);
public IEnumerator<object> GetEnumerator() => _extensions.GetEnumerator(); public IEnumerator<object> GetEnumerator() => _extensions.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _extensions.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _extensions.GetEnumerator();

View File

@@ -0,0 +1,12 @@
namespace FileTime.Core.Models.Extensions;
public class EscHandlerContainerExtension
{
private readonly Func<Task<ContainerEscapeResult>> _handleEsc;
public EscHandlerContainerExtension(Func<Task<ContainerEscapeResult>> handleEsc)
{
_handleEsc = handleEsc;
}
public async Task<ContainerEscapeResult> HandleEsc() => await _handleEsc();
}

View File

@@ -0,0 +1,6 @@
namespace FileTime.Core.Models.Extensions;
public class NonRestorableContainerExtension
{
}

View File

@@ -0,0 +1,11 @@
namespace FileTime.Core.Models.Extensions;
public class RealContainerProviderExtension
{
public RealContainerProviderExtension(Func<AbsolutePath> realContainer)
{
RealContainer = realContainer;
}
public Func<AbsolutePath> RealContainer { get; }
}

View File

@@ -0,0 +1,13 @@
using DeclarativeProperty;
namespace FileTime.Core.Models.Extensions;
public class StatusProviderContainerExtension
{
public StatusProviderContainerExtension(Func<IDeclarativeProperty<string>> getStatusProperty)
{
GetStatusProperty = getStatusProperty;
}
public Func<IDeclarativeProperty<string>> GetStatusProperty { get; }
}

View File

@@ -4,10 +4,10 @@ namespace FileTime.Core.Models;
public class TabLocationChanged : EventArgs public class TabLocationChanged : EventArgs
{ {
public FullName Location { get; } public IContainer Location { get; }
public ITab Tab { get; } public ITab Tab { get; }
public TabLocationChanged(FullName location, ITab tab) public TabLocationChanged(IContainer location, ITab tab)
{ {
Location = location; Location = location;
Tab = tab; Tab = tab;

View File

@@ -5,5 +5,5 @@ namespace FileTime.Core.Services;
public interface ITabEvents public interface ITabEvents
{ {
event EventHandler<TabLocationChanged> LocationChanged; event EventHandler<TabLocationChanged> LocationChanged;
void OnLocationChanged(ITab tab, FullName location); void OnLocationChanged(ITab tab, IContainer location);
} }

View File

@@ -69,7 +69,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
{ {
var elapsed = DateTime.Now - start; var elapsed = DateTime.Now - start;
var size = new ByteSize(total / elapsed.TotalSeconds); var size = ByteSize.FromBytes(total / elapsed.TotalSeconds);
return Task.FromResult(size + "/s"); return Task.FromResult(size + "/s");
}); });

View File

@@ -165,7 +165,7 @@ public class Tab : ITab
if (newLocation.FullName != null) if (newLocation.FullName != null)
{ {
_tabEvents.OnLocationChanged(this, newLocation.FullName); _tabEvents.OnLocationChanged(this, newLocation);
} }
} }
@@ -177,7 +177,7 @@ public class Tab : ITab
if (newLocation.FullName != null) if (newLocation.FullName != null)
{ {
_tabEvents.OnLocationChanged(this, newLocation.FullName); _tabEvents.OnLocationChanged(this, newLocation);
} }
} }

View File

@@ -4,10 +4,8 @@ namespace FileTime.Core.Services;
public class TabEvents : ITabEvents public class TabEvents : ITabEvents
{ {
public event EventHandler<TabLocationChanged> LocationChanged; public event EventHandler<TabLocationChanged>? LocationChanged;
public void OnLocationChanged(ITab tab, FullName location) public void OnLocationChanged(ITab tab, IContainer location)
{ => LocationChanged?.Invoke(this, new TabLocationChanged(location, tab));
LocationChanged?.Invoke(this, new TabLocationChanged(location, tab));
}
} }

View File

@@ -22,8 +22,9 @@ public class FormatSizeConverter : IValueConverter
public static string ToSizeString(long fileSize, int? precision = null) public static string ToSizeString(long fileSize, int? precision = null)
{ {
var size = new ByteSize(fileSize); var size = ByteSize.FromBytes(fileSize);
return precision == null? size.ToString() return precision == null
? size.ToString()
: size.ToString("0." + new string('#', precision.Value)); : size.ToString("0." + new string('#', precision.Value));
} }
} }

View File

@@ -1,16 +1,16 @@
using System.Reactive.Linq;
using Avalonia.Input; using Avalonia.Input;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.App.Core.UserCommand; using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Extensions; using FileTime.Core.Extensions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits; using FileTime.Core.Models.Extensions;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Extensions; using FileTime.GuiApp.Extensions;
using FileTime.GuiApp.Models; using FileTime.GuiApp.Models;
using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using DeclarativeProperty;
namespace FileTime.GuiApp.Services; namespace FileTime.GuiApp.Services;
@@ -20,8 +20,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
private readonly IModalService _modalService; private readonly IModalService _modalService;
private readonly IKeyboardConfigurationService _keyboardConfigurationService; private readonly IKeyboardConfigurationService _keyboardConfigurationService;
private readonly List<KeyConfig[]> _keysToSkip = new(); private readonly List<KeyConfig[]> _keysToSkip = new();
private ITabViewModel? _selectedTab; private readonly IDeclarativeProperty<IContainer?> _currentLocation;
private IContainer? _currentLocation;
private readonly ILogger<DefaultModeKeyInputHandler> _logger; private readonly ILogger<DefaultModeKeyInputHandler> _logger;
private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IUserCommandHandlerService _userCommandHandlerService;
private readonly IIdentifiableUserCommandService _identifiableUserCommandService; private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
@@ -42,19 +41,20 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
_modalService = modalService; _modalService = modalService;
_userCommandHandlerService = userCommandHandlerService; _userCommandHandlerService = userCommandHandlerService;
_appState.SelectedTab.Subscribe(t => _selectedTab = t); _currentLocation = _appState.SelectedTab
_appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l); .Map(t => t?.CurrentLocation)
.Switch();
_openModals = modalService.OpenModals.ToBindedCollection(); _openModals = modalService.OpenModals.ToBindedCollection();
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) }); _keysToSkip.Add(new[] {new KeyConfig(Key.Up)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) }); _keysToSkip.Add(new[] {new KeyConfig(Key.Down)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Tab) }); _keysToSkip.Add(new[] {new KeyConfig(Key.Tab)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageDown) }); _keysToSkip.Add(new[] {new KeyConfig(Key.PageDown)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageUp) }); _keysToSkip.Add(new[] {new KeyConfig(Key.PageUp)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.F4, alt: true) }); _keysToSkip.Add(new[] {new KeyConfig(Key.F4, alt: true)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.LWin) }); _keysToSkip.Add(new[] {new KeyConfig(Key.LWin)});
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.RWin) }); _keysToSkip.Add(new[] {new KeyConfig(Key.RWin)});
} }
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled) public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)
@@ -73,7 +73,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
{ {
_modalService.CloseModal(_openModals.Collection!.Last()); _modalService.CloseModal(_openModals.Collection!.Last());
} }
else if (_currentLocation is IEscHandlerContainer escHandler) else if (_currentLocation.Value?.GetExtension<EscHandlerContainerExtension>() is { } escHandler)
{ {
var escapeResult = await escHandler.HandleEsc(); var escapeResult = await escHandler.HandleEsc();
if (escapeResult.NavigateTo != null) if (escapeResult.NavigateTo != null)
@@ -118,7 +118,11 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
setHandled(true); setHandled(true);
_appState.PreviousKeys.Clear(); _appState.PreviousKeys.Clear();
_appState.PossibleCommands = new(); _appState.PossibleCommands = new();
await CallCommandAsync(_identifiableUserCommandService.GetCommand(selectedCommandBinding.Command)); var command = _identifiableUserCommandService.GetCommand(selectedCommandBinding.Command);
if (command is not null)
{
await CallCommandAsync(command);
}
} }
else if (_keysToSkip.Any(k => k.AreKeysEqual(_appState.PreviousKeys))) else if (_keysToSkip.Any(k => k.AreKeysEqual(_appState.PreviousKeys)))
{ {

View File

@@ -10,6 +10,7 @@ public class ModalService : IModalService
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly SourceList<IModalViewModel> _openModals = new(); private readonly SourceList<IModalViewModel> _openModals = new();
public IObservable<IChangeSet<IModalViewModel>> OpenModals { get; } public IObservable<IChangeSet<IModalViewModel>> OpenModals { get; }
public event EventHandler? AllModalClosed;
public ModalService(IServiceProvider serviceProvider) public ModalService(IServiceProvider serviceProvider)
{ {
@@ -19,8 +20,15 @@ public class ModalService : IModalService
public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen); public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen);
public void CloseModal(IModalViewModel modalToClose) => _openModals.Remove(modalToClose); public void CloseModal(IModalViewModel modalToClose)
{
_openModals.Remove(modalToClose);
if (_openModals.Count == 0)
{
AllModalClosed?.Invoke(this, EventArgs.Empty);
}
}
public T OpenModal<T>() where T : IModalViewModel public T OpenModal<T>() where T : IModalViewModel
{ {
var modal = _serviceProvider.GetRequiredService<T>(); var modal = _serviceProvider.GetRequiredService<T>();

View File

@@ -32,12 +32,14 @@ namespace FileTime.GuiApp.ViewModels;
[Inject(typeof(IRefreshSmoothnessCalculator), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IRefreshSmoothnessCalculator), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(IAdminElevationManager), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IAdminElevationManager), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(IClipboardService), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IClipboardService), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(IModalService), PropertyName = "_modalService")]
public partial class MainWindowViewModel : IMainWindowViewModel public partial class MainWindowViewModel : IMainWindowViewModel
{ {
public bool Loading => false; public bool Loading => false;
public IObservable<string?> MainFont => _fontService.MainFont.Select(x => x ?? ""); public IObservable<string?> MainFont => _fontService.MainFont.Select(x => x ?? "");
public IGuiAppState AppState => _appState; public IGuiAppState AppState => _appState;
public string Title { get; private set; } public string Title { get; private set; }
public Action? FocusDefaultElement { get; set; }
partial void OnInitialize() partial void OnInitialize()
{ {
@@ -59,13 +61,13 @@ public partial class MainWindowViewModel : IMainWindowViewModel
Title += " (Debug)"; Title += " (Debug)";
#endif #endif
_modalService.AllModalClosed += (_, _) => FocusDefaultElement?.Invoke();
Task.Run(async () => await _lifecycleService.InitStartupHandlersAsync()).Wait(); Task.Run(async () => await _lifecycleService.InitStartupHandlersAsync()).Wait();
} }
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled) public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{ => _keyInputHandlerService.ProcessKeyDown(key, keyModifiers, setHandled);
_keyInputHandlerService.ProcessKeyDown(key, keyModifiers, setHandled);
}
public async Task OpenContainerByFullName(FullName fullName) public async Task OpenContainerByFullName(FullName fullName)
{ {
@@ -75,8 +77,6 @@ public partial class MainWindowViewModel : IMainWindowViewModel
new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, resolvedContainer))); new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, resolvedContainer)));
} }
public async Task OnExit() public async Task OnExit()
{ => await _lifecycleService.ExitAsync();
await _lifecycleService.ExitAsync();
}
} }

View File

@@ -74,7 +74,10 @@ public partial class MainWindow : Window, IUiAccessor
_logger?.LogInformation( _logger?.LogInformation(
$"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization..."); $"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization...");
ViewModel = DI.ServiceProvider.GetRequiredService<MainWindowViewModel>();
var viewModel = DI.ServiceProvider.GetRequiredService<MainWindowViewModel>();
viewModel.FocusDefaultElement = () => Focus();
ViewModel = viewModel;
} }
} }
@@ -111,7 +114,7 @@ public partial class MainWindow : Window, IUiAccessor
&& sender is StyledElement control) && sender is StyledElement control)
{ {
FullName? path = null; FullName? path = null;
if (control.DataContext is IHaveFullPath { Path: { } } hasFullPath) if (control.DataContext is IHaveFullPath {Path: { }} hasFullPath)
{ {
path = hasFullPath.Path; path = hasFullPath.Path;
} }
@@ -154,7 +157,12 @@ public partial class MainWindow : Window, IUiAccessor
private void OnWindowClosed(object? sender, EventArgs e) private void OnWindowClosed(object? sender, EventArgs e)
{ {
var vm = ViewModel; var vm = ViewModel;
Task.Run(() => vm?.OnExit()).Wait(); Task.Run(async () =>
{
if (vm is null) return;
await vm.OnExit();
})
.Wait();
} }
private void InputList_OnKeyUp(object? sender, KeyEventArgs e) private void InputList_OnKeyUp(object? sender, KeyEventArgs e)