From 255f3d34c9a0ae8affe3ee9a7ee6c727eefa9cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Wed, 18 May 2022 07:49:39 +0200 Subject: [PATCH] Linux improvements --- .../Services/RootDriveInfoService.cs | 53 +++++++------ .../FileTime.GuiApp/Views/MainWindow.axaml | 3 - .../LocalContentProvider.cs | 76 ++++++++++++++----- 3 files changed, 89 insertions(+), 43 deletions(-) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs index aa1a9b2..8512849 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs @@ -1,6 +1,7 @@ using System.Reactive.Linq; using System.Runtime.InteropServices; using DynamicData; +using DynamicData.Binding; using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.Core.Models; @@ -13,40 +14,48 @@ namespace FileTime.GuiApp.Services; public class RootDriveInfoService : IStartupHandler { private readonly SourceList _rootDrives = new(); + private readonly IObservable> _localContentProviderStream; public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider) { InitRootDrives(); + var localContentProviderAsList = new SourceList(); + localContentProviderAsList.Add(new AbsolutePath(localContentProvider)); + _localContentProviderStream = localContentProviderAsList.Connect(); + 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)); + localContentProvider.Items, + _rootDrives.Connect().StartWithEmpty().ToCollection(), + (items, drives) => + { + return items is null + ? Observable.Empty>() + : items! + .Or(new[] { _localContentProviderStream }) + .Transform(i => (Path: i, Drive: drives.FirstOrDefault(d => + { + var containerPath = localContentProvider.GetNativePath(i.Path).Path; + var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar); + return containerPath == drivePath + || (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && d.Name == "/"); + }))) + .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)) + .Sort(SortExpressionComparer.Ascending(d => d.Name)); guiAppState.RootDriveInfos = new BindedCollection(rootDriveInfos); void InitRootDrives() { var driveInfos = new List(); - IEnumerable drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + var drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed) : DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index d2c9bf3..07041b2 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -20,9 +20,6 @@ Opened="OnWindowOpened" TransparencyLevelHint="Blur" mc:Ignorable="d"> - - - Task.FromResult((IItem) CreateEmptyContainer(nativePath, Observable.Return(new List() {innerException}))), + AbsolutePathType.Container => Task.FromResult( + (IItem)CreateEmptyContainer(nativePath, Observable.Return(new List() { innerException })) + ), AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)), - _ => throw new Exception($"Could not resolve path '{nativePath.Path}' and could not force create, because {nameof(forceResolvePathType)} is {nameof(AbsolutePathType.Unknown)}.", innerException) + _ => throw new Exception( + $"Could not resolve path '{nativePath.Path}' and could not force create, because {nameof(forceResolvePathType)} is {nameof(AbsolutePathType.Unknown)}.", + innerException) }; } - private Container CreateEmptyContainer(NativePath nativePath, IObservable>? exceptions = null) + private Container CreateEmptyContainer(NativePath nativePath, + IObservable>? exceptions = null) { var nonNullExceptions = exceptions ?? Observable.Return(Enumerable.Empty()); var name = nativePath.Path.Split(Path.DirectorySeparatorChar).LastOrDefault() ?? "???"; @@ -127,9 +142,22 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo throw new NotImplementedException(); } - public override Task> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName)); - private List GetItemsByContainer(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path)); - private List GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(directoryInfo.GetFiles().Select(FileToAbsolutePath)).ToList(); + public override Task> GetItemsByContainerAsync(FullName fullName) + => Task.FromResult(GetItemsByContainer(fullName)); + + private List GetItemsByContainer(FullName fullName) + => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path)); + + private List GetItemsByContainer(DirectoryInfo directoryInfo) + => directoryInfo + .GetDirectories() + .Select(DirectoryToAbsolutePath) + .Concat( + directoryInfo + .GetFiles() + .Select(FileToAbsolutePath) + ) + .ToList(); private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo) { @@ -153,11 +181,11 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo AbsolutePathType.Container); var exceptions = new BehaviorSubject>(Enumerable.Empty()); - return new( + return new Container( directoryInfo.Name, directoryInfo.Name, fullName, - new(directoryInfo.FullName), + new NativePath(directoryInfo.FullName), parent, (directoryInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden, directoryInfo.Exists, @@ -175,7 +203,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo SourceList? result = null; try { - var items = initializeChildren ? (List?) GetItemsByContainer(directoryInfo) : null; + var items = initializeChildren ? (List?)GetItemsByContainer(directoryInfo) : null; if (items != null) { result = new SourceList(); @@ -184,7 +212,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo } catch (Exception e) { - exceptions.OnNext(new List() {e}); + exceptions.OnNext(new List() { e }); } return Task.FromResult(result?.Connect()); @@ -194,14 +222,15 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo private Element FileToElement(FileInfo fileInfo) { var fullName = GetFullName(fileInfo); - var parentFullName = fullName.GetParent() ?? throw new Exception($"Path does not have parent: '{fileInfo.FullName}'"); + var parentFullName = fullName.GetParent() ?? + throw new Exception($"Path does not have parent: '{fileInfo.FullName}'"); var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container); return new FileElement( fileInfo.Name, fileInfo.Name, fullName, - new(fileInfo.FullName), + new NativePath(fileInfo.FullName), parent, (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden, fileInfo.Exists, @@ -216,9 +245,20 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo } private FullName GetFullName(DirectoryInfo directoryInfo) => GetFullName(directoryInfo.FullName); + private FullName GetFullName(FileInfo fileInfo) => GetFullName(fileInfo.FullName); private FullName GetFullName(NativePath nativePath) => GetFullName(nativePath.Path); - private FullName GetFullName(string nativePath) => new((Name + Constants.SeparatorChar + string.Join(Constants.SeparatorChar, nativePath.Split(Path.DirectorySeparatorChar))).TrimEnd(Constants.SeparatorChar)); - public override NativePath GetNativePath(FullName fullName) => new(string.Join(Path.DirectorySeparatorChar, fullName.Path.Split(Constants.SeparatorChar).Skip(1))); + private FullName GetFullName(string nativePath) => + new((Name + Constants.SeparatorChar + + string.Join(Constants.SeparatorChar, + nativePath.TrimStart(Constants.SeparatorChar).Split(Path.DirectorySeparatorChar))) + .TrimEnd(Constants.SeparatorChar)); + + public override NativePath GetNativePath(FullName fullName) + { + var path = string.Join(Path.DirectorySeparatorChar, fullName.Path.Split(Constants.SeparatorChar).Skip(1)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !path.StartsWith("/")) path = "/" + path; + return new NativePath(path); + } } \ No newline at end of file