diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/IGuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/IGuiAppState.cs index ecbef5c..0ecc30b 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/IGuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/IGuiAppState.cs @@ -7,7 +7,6 @@ namespace FileTime.GuiApp.App.ViewModels; public interface IGuiAppState : IAppState { - ObservableCollection RootDriveInfos { get; set; } IReadOnlyList Places { get; set; } ObservableCollection PopupTexts { get; } IObservable ActivePanel { get; } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/IMainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/IMainWindowViewModel.cs index 708c2fa..fa49c66 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/IMainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/IMainWindowViewModel.cs @@ -6,6 +6,7 @@ using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.FrequencyNavigation.Services; using FileTime.GuiApp.App.CloudDrives; using FileTime.GuiApp.App.Services; +using FileTime.Providers.Local; using FileTime.Providers.LocalAdmin; namespace FileTime.GuiApp.App.ViewModels; @@ -23,6 +24,7 @@ public interface IMainWindowViewModel : IMainWindowViewModelBase ITimelineViewModel TimelineViewModel { get; } IPossibleCommandsViewModel PossibleCommands { get; } ICloudDriveService CloudDriveService { get; } + IRootDriveInfoService RootDriveInfoService { get; } Action? ShowWindow { get; set; } Thickness IconStatusPanelMargin { get; } Task RunOrOpenItem(IItemViewModel itemViewModel); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/MainWindowViewModel.cs index 1ec2473..42546b6 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/ViewModels/MainWindowViewModel.cs @@ -45,6 +45,7 @@ namespace FileTime.GuiApp.App.ViewModels; [Inject(typeof(IPossibleCommandsViewModel), PropertyName = "PossibleCommands", PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IInstanceMessageHandler), PropertyName = "_instanceMessageHandler")] [Inject(typeof(ICloudDriveService), PropertyAccessModifier = AccessModifier.Public)] +[Inject(typeof(IRootDriveInfoService), PropertyAccessModifier = AccessModifier.Public)] public partial class MainWindowViewModel : IMainWindowViewModel { public bool Loading => false; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml index f9c0364..0def58f 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml @@ -128,12 +128,13 @@ + ItemsSource="{Binding RootDriveInfoService.RootDriveInfos}"> + PointerPressed="OnHasContainerPointerPressed" + ToolTip.Tip="{Binding FullName}"> + Text="{Binding Name}" /> _activePanel = new(GuiPanel.FileBrowser); - [Notify] private ObservableCollection _rootDriveInfos; - [Notify] private IReadOnlyList _places = new List(); public ObservableCollection PopupTexts { get; } = new(); public IObservable ActivePanel { get; } - public GuiAppState(IRootDriveInfoService rootDriveInfoService) + public GuiAppState() { ActivePanel = _activePanel.AsObservable(); - _rootDriveInfos = rootDriveInfoService.RootDriveInfos; } public void SetActivePanel(GuiPanel newPanel) diff --git a/src/Providers/FileTime.Providers.Local.Abstractions/IRootDriveInfoService.cs b/src/Providers/FileTime.Providers.Local.Abstractions/IRootDriveInfoService.cs index 8cb1593..c3da223 100644 --- a/src/Providers/FileTime.Providers.Local.Abstractions/IRootDriveInfoService.cs +++ b/src/Providers/FileTime.Providers.Local.Abstractions/IRootDriveInfoService.cs @@ -5,5 +5,6 @@ namespace FileTime.Providers.Local; public interface IRootDriveInfoService : IExitHandler { - ObservableCollection RootDriveInfos { get; set; } + ReadOnlyObservableCollection RootDriveInfos { get; set; } + ReadOnlyObservableCollection AllDrives { get; set; } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local.Abstractions/RootDriveInfo.cs b/src/Providers/FileTime.Providers.Local.Abstractions/RootDriveInfo.cs index 450e68b..73581f6 100644 --- a/src/Providers/FileTime.Providers.Local.Abstractions/RootDriveInfo.cs +++ b/src/Providers/FileTime.Providers.Local.Abstractions/RootDriveInfo.cs @@ -31,14 +31,7 @@ public partial class RootDriveInfo _name = container.Name; - _fullName = _name; - try - { - _fullName = container.FullName?.Path[(container.Provider.FullName!.Path.Length + 1)..] ?? _fullName; - } - catch - { - } + _fullName = driveInfo.Name; Path = container.FullName ?? throw new NullReferenceException($"Container does not have a {nameof(FullName)}"); diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 15a2b9a..d8a4b7d 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -14,7 +14,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo private readonly ITimelessContentProvider _timelessContentProvider; private readonly IContentProviderRegistry _contentProviderRegistry; private readonly bool _isCaseInsensitive; - private readonly Lazy> _rootDriveInfos; + private readonly Lazy _rootDriveInfo; public LocalContentProvider( ITimelessContentProvider timelessContentProvider, @@ -26,7 +26,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo _contentProviderRegistry = contentProviderRegistry; _isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - _rootDriveInfos = new Lazy>(() => serviceProvider.GetRequiredService().RootDriveInfos); + _rootDriveInfo = new Lazy(serviceProvider.GetRequiredService); SupportsContentStreams = true; @@ -68,12 +68,26 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo public override ValueTask GetVolumeSizeInfoAsync(FullName path) { - var rootDriveInfos = _rootDriveInfos.Value; - var rootDriveInfo = rootDriveInfos.FirstOrDefault(d => path.Path.StartsWith(d.Path.Path)); + var nativePath = GetNativePath(path); + + var possibleRootDrives = _rootDriveInfo.Value.AllDrives.Where(d => nativePath.Path.StartsWith(d.RootDirectory.FullName)).ToArray(); + var rootDrive = possibleRootDrives.Length == 0 + ? null + : possibleRootDrives.MaxBy(d => d.RootDirectory.FullName.Length); + + return rootDrive is null + ? ValueTask.FromResult(null) + : ValueTask.FromResult(new VolumeSizeInfo(rootDrive.TotalSize, rootDrive.AvailableFreeSpace)); + + /*var rootDriveInfos = _rootDriveInfo.Value; + var possibleRootDriveInfo = rootDriveInfos.RootDriveInfos.Where(d => path.Path.StartsWith(d.Path.Path)).ToArray(); + var rootDriveInfo = possibleRootDriveInfo.Length == 0 + ? null + : possibleRootDriveInfo.MaxBy(d => d.FullName.Length); if (rootDriveInfo is null) return ValueTask.FromResult(null); - return ValueTask.FromResult(new VolumeSizeInfo(rootDriveInfo.Size, rootDriveInfo.Free)); + return ValueTask.FromResult(new VolumeSizeInfo(rootDriveInfo.Size, rootDriveInfo.Free));*/ } public override async Task GetItemByNativePathAsync(NativePath nativePath, @@ -103,7 +117,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo return FileToElement(new FileInfo(path), pointInTime); } - var pathParts = path.Split(Path.DirectorySeparatorChar).SelectMany(p => p.Split(Constants.SeparatorChar)).ToArray(); + var pathParts = path.Split(Path.DirectorySeparatorChar).SelectMany(p => p.Split(Constants.SeparatorChar)) + .ToArray(); for (var i = pathParts.Length - 1; i > 0; i--) { @@ -116,7 +131,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo var subPath = string.Join(Constants.SeparatorChar, pathParts.Skip(i)); - var resolvedItem = await subContentProvider.GetItemByFullNameAsync(element, new FullName(subPath), pointInTime, forceResolvePathType, itemInitializationSettings); + var resolvedItem = await subContentProvider.GetItemByFullNameAsync(element, new FullName(subPath), + pointInTime, forceResolvePathType, itemInitializationSettings); if (resolvedItem is not null) { @@ -155,7 +171,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo AbsolutePathType.Container => CreateEmptyContainer( nativePath, pointInTime, - new List {innerException} + new List { innerException } ), AbsolutePathType.Element => CreateEmptyElement(nativePath), _ => throw new Exception( @@ -167,8 +183,9 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo public override ValueTask GetSupportedPathPart(NativePath nativePath) { var path = nativePath.Path; - var pathParts = path.Split(Path.DirectorySeparatorChar).SelectMany(p => p.Split(Constants.SeparatorChar)).ToArray(); - + var pathParts = path.Split(Path.DirectorySeparatorChar).SelectMany(p => p.Split(Constants.SeparatorChar)) + .ToArray(); + for (var i = pathParts.Length - 1; i > 0; i--) { var possiblePath = string.Join(Path.DirectorySeparatorChar, pathParts.Take(i)); @@ -440,7 +457,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo var finalSize = size switch { > int.MaxValue => int.MaxValue, - _ => (int) size + _ => (int)size }; var buffer = new byte[finalSize]; var realSize = await reader.ReadAsync(buffer.AsMemory(0, finalSize), cancellationToken); diff --git a/src/Providers/FileTime.Providers.Local/RootDriveInfoService.cs b/src/Providers/FileTime.Providers.Local/RootDriveInfoService.cs index a6ccf2c..9d299c6 100644 --- a/src/Providers/FileTime.Providers.Local/RootDriveInfoService.cs +++ b/src/Providers/FileTime.Providers.Local/RootDriveInfoService.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using System.Runtime.InteropServices; using FileTime.Core.Models; +using FileTime.Core.Timeline; using ObservableComputations; namespace FileTime.Providers.Local; @@ -8,66 +9,65 @@ namespace FileTime.Providers.Local; public class RootDriveInfoService : IRootDriveInfoService { private readonly ILocalContentProvider _localContentProvider; - private readonly List _rootDrives = new(); + private readonly ObservableCollection _rootDrives = new(); + private readonly ObservableCollection _allDrives = new(); private readonly OcConsumer _rootDriveInfosConsumer = new(); - public ObservableCollection RootDriveInfos { get; set; } + public ReadOnlyObservableCollection AllDrives { get; set; } + public ReadOnlyObservableCollection RootDriveInfos { get; set; } public RootDriveInfoService(ILocalContentProvider localContentProvider) { _localContentProvider = localContentProvider; - InitRootDrives(); + var (rootDrives, allDrives) = GetRootDrives(); - var rootDriveInfos = localContentProvider.Items.Selecting( - i => MatchRootDrive(i) - ) - .Filtering(t => IsNotNull(t.Drive)) - .Selecting(t => Resolve(t)) - .Filtering(t => t.Item is IContainer) - .Selecting(t => new RootDriveInfo(t.Drive, (IContainer) t.Item!)) - .Ordering(d => d.Name); - - rootDriveInfos.For(_rootDriveInfosConsumer); - - RootDriveInfos = rootDriveInfos; - - void InitRootDrives() + foreach (var driveInfo in rootDrives) { - var drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed) - : DriveInfo.GetDrives().Where(d => - d.DriveType == DriveType.Fixed + _rootDrives.Add(driveInfo); + } + + foreach (var driveInfo in allDrives) + { + _allDrives.Add(driveInfo); + } + + var rootDriveInfos = _rootDrives.Selecting(r => GetContainer(r)) + .Filtering(t => t.Item != null) + .Selecting(t => new RootDriveInfo(t.Drive, t.Item!)) + .Ordering(d => d.Name) + .For(_rootDriveInfosConsumer); + + RootDriveInfos = new ReadOnlyObservableCollection(rootDriveInfos); + AllDrives = new ReadOnlyObservableCollection(_allDrives); + + (DriveInfo[] RootDrives, DriveInfo[] AllDrives) GetRootDrives() + { + var allDrives = DriveInfo.GetDrives(); + var drives = DriveInfo.GetDrives().Where(d => d.DriveType is not DriveType.Unknown and not DriveType.Ram); + drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? drives + : drives.Where(d => + d.TotalSize != 0 && d.DriveFormat != "pstorefs" && d.DriveFormat != "bpf_fs" && d.DriveFormat != "tracefs" + && d.DriveFormat != "rpc_pipefs" && !d.RootDirectory.FullName.StartsWith("/snap/")); - _rootDrives.Clear(); - _rootDrives.AddRange(drives); + return (drives.ToArray(), allDrives); } } - private static bool IsNotNull(object? obj) => obj is not null; - - private static (IItem? Item, DriveInfo Drive) Resolve((AbsolutePath Path, DriveInfo? Drive) tuple) + private (DriveInfo Drive, IContainer? Item) GetContainer(DriveInfo rootDriveInfo) { - var t = Task.Run(async () => await tuple.Path.ResolveAsyncSafe()); - t.Wait(); - return (Item: t.Result, Drive: tuple.Drive!); - } + var task = Task.Run( + async () => await _localContentProvider.GetItemByNativePathAsync( + new NativePath(rootDriveInfo.RootDirectory.FullName), + PointInTime.Present) + ); + task.Wait(); - private (AbsolutePath Path, DriveInfo? Drive) MatchRootDrive(AbsolutePath sourceItem) - { - var rootDrive = _rootDrives.FirstOrDefault(d => - { - var containerPath = _localContentProvider.GetNativePath(sourceItem.Path).Path; - var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar); - return containerPath == drivePath - || (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && - d.Name == "/"); - }); - - return (Path: sourceItem, Drive: rootDrive); + return (rootDriveInfo, task.Result as IContainer); } public Task ExitAsync(CancellationToken token = default)