From 9a99aad030bd1df2deaf6f294f35edab3279d885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Sat, 14 May 2022 22:14:37 +0200 Subject: [PATCH] RootDrives WIP --- .../Models/BindedCollection.cs | 8 +- .../Services/IExitHandler.cs | 6 + .../Services/IStartupHandler.cs | 5 + .../Services/IContentProvider.cs | 1 + .../ContentProviderBase.cs | 8 +- .../FileTime.GuiApp.Abstractions.csproj | 5 + .../Models/IHaveAbsolutePath.cs | 8 + .../ViewModels/IGuiAppState.cs | 3 + .../ViewModels/RootDriveInfo.cs | 57 ++++++ .../Avalonia/FileTime.GuiApp.App/Startup.cs | 2 + .../ViewModels/GuiAppState.cs | 16 +- .../Services/LifecycleService.cs | 21 ++ .../Services/RootDriveInfoService.cs | 61 ++++++ .../ViewModels/MainWindowViewModel.cs | 3 + .../FileTime.GuiApp/Views/MainWindow.axaml | 180 +++++++++++++++++- .../FileTime.GuiApp/Views/MainWindow.axaml.cs | 40 ++++ 16 files changed, 409 insertions(+), 15 deletions(-) create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/Services/IExitHandler.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/Services/IStartupHandler.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Services/LifecycleService.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs index 8c7292d..6ab596a 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs @@ -7,11 +7,15 @@ namespace FileTime.App.Core.Models; public partial class BindedCollection : IDisposable, INotifyPropertyChanged { - private readonly IDisposable _disposable; + private readonly IDisposable? _disposable; private IDisposable? _innerDisposable; [Notify] private ReadOnlyObservableCollection? _collection; + public BindedCollection() + { + } + public BindedCollection(IObservable> dynamicList) { _disposable = dynamicList @@ -45,7 +49,7 @@ public partial class BindedCollection : IDisposable, INotifyPropertyChanged public void Dispose() { - _disposable.Dispose(); + _disposable?.Dispose(); _innerDisposable?.Dispose(); GC.SuppressFinalize(this); } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IExitHandler.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IExitHandler.cs new file mode 100644 index 0000000..73c1d78 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IExitHandler.cs @@ -0,0 +1,6 @@ +namespace FileTime.App.Core.Services; + +public interface IExitHandler +{ + Task ExitAsync(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IStartupHandler.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IStartupHandler.cs new file mode 100644 index 0000000..d649307 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IStartupHandler.cs @@ -0,0 +1,5 @@ +namespace FileTime.App.Core.Services; + +public interface IStartupHandler +{ +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs index c803d60..f2aae5e 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs @@ -19,4 +19,5 @@ public interface IContentProvider : IContainer, IOnContainerEnter ItemInitializationSettings itemInitializationSettings = default); Task> GetItemsByContainerAsync(FullName fullName); + NativePath GetNativePath(FullName fullName); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/ContentProviderBase.cs b/src/Core/FileTime.Core.Services/ContentProviderBase.cs index 4637447..4505d20 100644 --- a/src/Core/FileTime.Core.Services/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.Services/ContentProviderBase.cs @@ -8,7 +8,7 @@ namespace FileTime.Core.Services; public abstract class ContentProviderBase : IContentProvider { - protected BehaviorSubject>?> Items { get; } = new (null); + protected BehaviorSubject>?> Items { get; } = new(null); IObservable>?> IContainer.Items => Items; @@ -16,7 +16,7 @@ public abstract class ContentProviderBase : IContentProvider public string DisplayName { get; } - public FullName? FullName => null; + public FullName? FullName { get; } public NativePath? NativePath => null; @@ -47,20 +47,24 @@ public abstract class ContentProviderBase : IContentProvider protected ContentProviderBase(string name) { DisplayName = Name = name; + FullName = new FullName(name); } public virtual Task OnEnter() => Task.CompletedTask; + public virtual async Task GetItemByFullNameAsync( FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings); + public abstract Task GetItemByNativePathAsync( NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); + public abstract Task> GetItemsByContainerAsync(FullName fullName); public abstract NativePath GetNativePath(FullName fullName); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/FileTime.GuiApp.Abstractions.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/FileTime.GuiApp.Abstractions.csproj index 3f49aba..2896cee 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/FileTime.GuiApp.Abstractions.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/FileTime.GuiApp.Abstractions.csproj @@ -13,10 +13,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs new file mode 100644 index 0000000..6e3217d --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Models/IHaveAbsolutePath.cs @@ -0,0 +1,8 @@ +using FileTime.Core.Models; + +namespace FileTime.GuiApp.Models; + +public interface IHaveAbsolutePath +{ + IAbsolutePath Path { get; } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs index bfad024..0026e2a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; +using FileTime.App.Core.Models; using FileTime.App.Core.ViewModels; using FileTime.GuiApp.Configuration; @@ -10,4 +12,5 @@ public interface IGuiAppState : IAppState bool NoCommandFound { get; set; } string? MessageBoxText { get; set; } List PossibleCommands { get; set; } + BindedCollection RootDriveInfos { get; set; } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs new file mode 100644 index 0000000..e52f4c3 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/RootDriveInfo.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using FileTime.Core.Models; +using FileTime.GuiApp.Models; +using PropertyChanged.SourceGenerator; +using IContainer = FileTime.Core.Models.IContainer; + +namespace FileTime.GuiApp.ViewModels; + +public partial class RootDriveInfo : IHaveAbsolutePath, INotifyPropertyChanged +{ + private readonly DriveInfo _driveInfo; + + [Notify] private string _name; + + [Notify] private string _fullName; + + [Notify] private string? _label; + + [Notify] private long _size = 0; + + [Notify] private long _free = 0; + + [Notify] private long _used = 0; + + [Notify] public long UsedPercentage => Size == 0 ? 0 : Used * 100 / Size; + + public IAbsolutePath Path { get; } + + public RootDriveInfo(DriveInfo driveInfo, IContainer container) + { + _driveInfo = driveInfo; + + _name = container.Name; + + _fullName = _name; + try + { + _fullName = container.FullName?.Path[(container.Provider.FullName!.Path.Length + 1)..] ?? _fullName; + } + catch + { + } + + Path = new AbsolutePath(container); + + Refresh(); + } + + private void Refresh() + { + Label = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? _driveInfo.VolumeLabel : null; + Size = _driveInfo.TotalSize; + Free = _driveInfo.AvailableFreeSpace; + Used = _driveInfo.TotalSize - _driveInfo.AvailableFreeSpace; + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs index e575363..54e27f1 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs @@ -31,6 +31,8 @@ public static class Startup .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton(); } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs index 5e2c5d8..d6da2d3 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; +using FileTime.App.Core.Models; using FileTime.App.Core.ViewModels; using FileTime.GuiApp.Configuration; using MvvmGen; @@ -7,17 +9,15 @@ namespace FileTime.GuiApp.ViewModels; [ViewModel] public partial class GuiAppState : AppStateBase, IGuiAppState { - [Property] - private bool _isAllShortcutVisible; + [Property] private bool _isAllShortcutVisible; - [Property] - private bool _noCommandFound; + [Property] private bool _noCommandFound; - [Property] - private string? _messageBoxText; + [Property] private string? _messageBoxText; - [Property] - private List _possibleCommands = new(); + [Property] private List _possibleCommands = new(); + + [Property] private BindedCollection _rootDriveInfos = new(); public List PreviousKeys { get; } = new(); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LifecycleService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LifecycleService.cs new file mode 100644 index 0000000..6ece91c --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LifecycleService.cs @@ -0,0 +1,21 @@ +using FileTime.App.Core.Services; + +namespace FileTime.GuiApp.Services; + +public class LifecycleService +{ + private readonly IEnumerable _exitHandlers; + + public LifecycleService(IEnumerable startupHandlers, IEnumerable exitHandlers) + { + _exitHandlers = exitHandlers; + } + + public async Task Exit() + { + foreach (var exitHandler in _exitHandlers) + { + await exitHandler.ExitAsync(); + } + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs new file mode 100644 index 0000000..aa1a9b2 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs @@ -0,0 +1,61 @@ +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using DynamicData; +using FileTime.App.Core.Models; +using FileTime.App.Core.Services; +using FileTime.Core.Models; +using FileTime.GuiApp.ViewModels; +using FileTime.Providers.Local; +using IContainer = FileTime.Core.Models.IContainer; + +namespace FileTime.GuiApp.Services; + +public class RootDriveInfoService : IStartupHandler +{ + private readonly SourceList _rootDrives = new(); + + public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider) + { + InitRootDrives(); + + var rootDriveInfos = Observable.CombineLatest( + localContentProvider.Items, + _rootDrives.Connect().StartWithEmpty().ToCollection(), + (items, drives) => + { + return items is null + ? Observable.Empty>() + : items + .Transform(i => (Path: i, Drive: drives.FirstOrDefault(d => + { + var asd1 = localContentProvider.GetNativePath(i.Path).Path; + var asd2 = d.Name.TrimEnd(Path.DirectorySeparatorChar); + return asd1 == asd2; + }))) + .Filter(t => t.Drive is not null); + } + ) + .Switch() + .TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!)) + .Filter(t => t.Item is IContainer) + .Transform(t => (Container: (IContainer)t.Item!, t.Drive)) + .Transform(t => new RootDriveInfo(t.Drive, t.Container)); + + guiAppState.RootDriveInfos = new BindedCollection(rootDriveInfos); + + void InitRootDrives() + { + var driveInfos = new List(); + IEnumerable drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed) + : DriveInfo.GetDrives().Where(d => + d.DriveType == DriveType.Fixed + && d.DriveFormat != "pstorefs" + && d.DriveFormat != "bpf_fs" + && d.DriveFormat != "tracefs" + && !d.RootDirectory.FullName.StartsWith("/snap/")); + + _rootDrives.AddRange(drives); + } + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs index fbee23b..4dfbf55 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs @@ -2,6 +2,7 @@ using System.Reflection; using Avalonia.Input; using FileTime.App.Core; +using FileTime.App.Core.Services; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; using FileTime.Core.Services; @@ -19,6 +20,8 @@ namespace FileTime.GuiApp.ViewModels; [Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")] [Inject(typeof(ILogger), PropertyName = "_logger")] [Inject(typeof(IKeyInputHandlerService), PropertyName = "_keyInputHandlerService")] +[Inject(typeof(ICommandHandlerService), PropertyAccessModifier = AccessModifier.Public)] +[Inject(typeof(LifecycleService), PropertyName = "_lifecycleService")] public partial class MainWindowViewModel : IMainWindowViewModelBase { public bool Loading => false; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 595b91c..02815c7 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -26,11 +26,12 @@ + x:DataType="vm:MainWindowViewModel"> + RowDefinitions="Auto,*" + IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + await Dispatcher.UIThread.InvokeAsync(async () => + { + await ViewModel.AppState.SelectedTab.OpenContainer(parentContainer); + await ViewModel.AppState.SelectedTab.SetCurrentSelectedItem(element); + }); + }); + }*/ + + if (path is null) return; + + var resolvedItem = await path.ResolveAsync(); + if (resolvedItem is not IContainer resolvedContainer) return; + //ViewModel.CommandHandlerService.HandleCommandAsync() + e.Handled = true; + } + } } \ No newline at end of file