Parent's children, optimalizations

This commit is contained in:
2022-04-18 21:27:04 +02:00
parent 8511ebac58
commit 570f695e60
14 changed files with 174 additions and 48 deletions

View File

@@ -0,0 +1,10 @@
using System.Reactive.Concurrency;
namespace FileTime.App.Core.Services
{
public interface IRxSchedulerService
{
IScheduler GetWorkerScheduler();
IScheduler GetUIScheduler();
}
}

View File

@@ -15,5 +15,6 @@ namespace FileTime.App.Core.ViewModels
IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; } IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; }
IObservable<IEnumerable<FullName>> MarkedItems { get; } IObservable<IEnumerable<FullName>> MarkedItems { get; }
IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; } IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; }
IObservable<IReadOnlyList<IItemViewModel>?> ParentsChildren { get; }
} }
} }

View File

@@ -1,9 +1,8 @@
using System.Reactive.Concurrency;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using FileTime.App.Core.Extensions; using FileTime.App.Core.Extensions;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -15,6 +14,7 @@ namespace FileTime.App.Core.ViewModels
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IItemNameConverterService _itemNameConverterService; private readonly IItemNameConverterService _itemNameConverterService;
private readonly IAppState _appState; private readonly IAppState _appState;
private readonly IRxSchedulerService _rxSchedulerService;
private readonly BehaviorSubject<IEnumerable<FullName>> _markedItems = new(Enumerable.Empty<FullName>()); private readonly BehaviorSubject<IEnumerable<FullName>> _markedItems = new(Enumerable.Empty<FullName>());
private readonly List<IDisposable> _disposables = new(); private readonly List<IDisposable> _disposables = new();
private bool disposed; private bool disposed;
@@ -29,11 +29,13 @@ namespace FileTime.App.Core.ViewModels
public IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; private set; } = null!; public IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; private set; } = null!;
public IObservable<IEnumerable<FullName>> MarkedItems { get; } public IObservable<IEnumerable<FullName>> MarkedItems { get; }
public IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; private set; } = null!; public IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; private set; } = null!;
public IObservable<IReadOnlyList<IItemViewModel>?> ParentsChildren { get; private set; } = null!;
public TabViewModel( public TabViewModel(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IItemNameConverterService itemNameConverterService, IItemNameConverterService itemNameConverterService,
IAppState appState) IAppState appState,
IRxSchedulerService rxSchedulerService)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_itemNameConverterService = itemNameConverterService; _itemNameConverterService = itemNameConverterService;
@@ -41,6 +43,7 @@ namespace FileTime.App.Core.ViewModels
MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable(); MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable();
IsSelected = _appState.SelectedTab.Select(s => s == this); IsSelected = _appState.SelectedTab.Select(s => s == this);
_rxSchedulerService = rxSchedulerService;
} }
public void Init(ITab tab, int tabNumber) public void Init(ITab tab, int tabNumber)
@@ -49,7 +52,16 @@ namespace FileTime.App.Core.ViewModels
TabNumber = tabNumber; TabNumber = tabNumber;
CurrentLocation = tab.CurrentLocation.AsObservable(); CurrentLocation = tab.CurrentLocation.AsObservable();
CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()).Publish(new List<IItemViewModel>()).RefCount(); CurrentItems = tab.CurrentItems
.Select(items =>
items == null
? new List<IItemViewModel>()
: items.Select(MapItemToViewModel).ToList())
.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(new List<IItemViewModel>())
.RefCount();
CurrentSelectedItem = CurrentSelectedItem =
Observable.CombineLatest( Observable.CombineLatest(
CurrentItems, CurrentItems,
@@ -73,7 +85,36 @@ namespace FileTime.App.Core.ViewModels
currentSelectedItemThrottled currentSelectedItemThrottled
.Where(c => c is null || c is not IContainerViewModel) .Where(c => c is null || c is not IContainerViewModel)
.Select(_ => (IReadOnlyList<IItemViewModel>?)null) .Select(_ => (IReadOnlyList<IItemViewModel>?)null)
); )
.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(null)
.RefCount();
var parentThrottled = CurrentLocation
.Select(l => l?.Parent)
.DistinctUntilChanged()
.Publish(null)
.RefCount();
ParentsChildren = Observable.Merge(
parentThrottled
.Where(p => p is not null)
.Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync()))
.Switch()
.Select(p => p.Items)
.Switch()
.Select(items => Observable.FromAsync(async () => await Map(items)))
.Switch()
.Select(items => items?.Select(MapItemToViewModel).ToList()),
parentThrottled
.Where(p => p is null)
.Select(_ => (IReadOnlyList<IItemViewModel>?)null)
)
.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(null)
.RefCount();
tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>())); tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>()));
@@ -83,7 +124,7 @@ namespace FileTime.App.Core.ViewModels
return await items return await items
.ToAsyncEnumerable() .ToAsyncEnumerable()
.SelectAwait(async i => await i.ResolveAsync(true)) .SelectAwait(async i => await i.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true)))
.ToListAsync(); .ToListAsync();
} }
} }

View File

@@ -10,7 +10,7 @@ namespace FileTime.Core.Models
FullName Path { get; } FullName Path { get; }
AbsolutePathType Type { get; } AbsolutePathType Type { get; }
Task<IItem> ResolveAsync(bool forceResolve = false); Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false); Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
} }
} }

View File

@@ -0,0 +1,12 @@
namespace FileTime.Core.Models
{
public readonly struct ItemInitializationSettings
{
public readonly bool SkipChildInitialization;
public ItemInitializationSettings(bool skipChildInitialization)
{
SkipChildInitialization = skipChildInitialization;
}
}
}

View File

@@ -6,8 +6,18 @@ namespace FileTime.Core.Services
{ {
public interface IContentProvider : IContainer, IOnContainerEnter public interface IContentProvider : IContainer, IOnContainerEnter
{ {
Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown); Task<IItem> GetItemByFullNameAsync(
Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown); FullName fullName,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
} }
} }

View File

@@ -19,17 +19,17 @@ namespace FileTime.Core.Models
Type = type; Type = type;
} }
public async Task<IItem> ResolveAsync(bool forceResolve = false) public async Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{ {
var provider = VirtualContentProvider ?? ContentProvider; var provider = VirtualContentProvider ?? ContentProvider;
return await provider.GetItemByFullNameAsync(Path, forceResolve, Type); return await provider.GetItemByFullNameAsync(Path, forceResolve, Type, itemInitializationSettings);
} }
public async Task<IItem?> ResolveAsyncSafe(bool forceResolve = false) public async Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{ {
try try
{ {
return await ResolveAsync(forceResolve); return await ResolveAsync(forceResolve, itemInitializationSettings);
} }
catch { return null; } catch { return null; }
} }

View File

@@ -49,9 +49,17 @@ namespace FileTime.Core.Services
} }
public virtual Task OnEnter() => Task.CompletedTask; public virtual Task OnEnter() => Task.CompletedTask;
public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown) public virtual async Task<IItem> GetItemByFullNameAsync(
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType); FullName fullName,
public abstract Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown); bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings);
public abstract Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
public abstract NativePath GetNativePath(FullName fullName); public abstract NativePath GetNativePath(FullName fullName);
} }

View File

@@ -16,7 +16,7 @@ namespace FileTime.Core.Services
public Tab() public Tab()
{ {
CurrentLocation = _currentLocation.DistinctUntilChanged().Do(_ => {; }).Publish(null).RefCount(); CurrentLocation = _currentLocation.DistinctUntilChanged().Publish(null).RefCount();
CurrentItems = CurrentItems =
Observable.Merge( Observable.Merge(
CurrentLocation CurrentLocation
@@ -69,7 +69,7 @@ namespace FileTime.Core.Services
private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation) private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation)
{ {
//TODO: //TODO:
return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Return((IAbsolutePath?)null); return currentLocation?.Items?.Select(i => i?.FirstOrDefault()) ?? Observable.Return((IAbsolutePath?)null);
} }
public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation); public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation);

View File

@@ -26,6 +26,7 @@ namespace FileTime.GuiApp
.AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>()) .AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>())
.AddSingleton<DefaultModeKeyInputHandler>() .AddSingleton<DefaultModeKeyInputHandler>()
.AddSingleton<RapidTravelModeKeyInputHandler>() .AddSingleton<RapidTravelModeKeyInputHandler>()
.AddSingleton<IRxSchedulerService, AvaloniaRxSchedulerService>()
//TODO: move?? //TODO: move??
.AddTransient<ITab, Tab>() .AddTransient<ITab, Tab>()
.AddTransient<ITabViewModel, TabViewModel>() .AddTransient<ITabViewModel, TabViewModel>()

View File

@@ -0,0 +1,13 @@
using System.Reactive.Concurrency;
using FileTime.App.Core.Services;
using ReactiveUI;
namespace FileTime.GuiApp.Services
{
public class AvaloniaRxSchedulerService : IRxSchedulerService
{
public IScheduler GetUIScheduler() => RxApp.MainThreadScheduler;
public IScheduler GetWorkerScheduler() => RxApp.TaskpoolScheduler;
}
}

View File

@@ -103,6 +103,23 @@
<ColumnDefinition Width="45*" /> <ColumnDefinition Width="45*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ListBox
x:CompileBindings="False"
AutoScrollToSelectedItem="True"
Classes="ContentListView"
IsTabStop="True"
Items="{Binding AppState.SelectedTab^.ParentsChildren^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView
ShowAttributes="False"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Rectangle <Rectangle
Grid.Column="1" Grid.Column="1"
@@ -112,7 +129,6 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" /> Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid <Grid
Grid.Column="2" Grid.Column="2"
RowDefinitions="Auto,*"> RowDefinitions="Auto,*">

View File

@@ -19,17 +19,5 @@ namespace FileTime.Providers.Local
+ ((directoryInfo.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-"); + ((directoryInfo.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
} }
} }
private static IEnumerable<FileInfo> GetFilesSafe(DirectoryInfo directoryInfo)
{
try
{
return directoryInfo.GetFiles();
}
catch
{
return Enumerable.Empty<FileInfo>();
}
}
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -31,9 +32,14 @@ namespace FileTime.Providers.Local
Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList()); Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList());
} }
public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown) public override Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
{ {
var path = nativePath.Path; var path = nativePath.Path;
Exception? innerException;
try try
{ {
if ((path?.Length ?? 0) == 0) if ((path?.Length ?? 0) == 0)
@@ -42,7 +48,7 @@ namespace FileTime.Providers.Local
} }
else if (Directory.Exists(path)) else if (Directory.Exists(path))
{ {
return Task.FromResult((IItem)DirectoryToContainer(new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar))); return Task.FromResult((IItem)DirectoryToContainer(new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar), !itemInitializationSettings.SkipChildInitialization));
} }
else if (File.Exists(path)) else if (File.Exists(path))
{ {
@@ -56,21 +62,25 @@ namespace FileTime.Providers.Local
_ => "Directory or file" _ => "Directory or file"
}; };
if (forceResolvePathType == AbsolutePathType.Container) throw new DirectoryNotFoundException($"{type} not found: '{path}'"); innerException = forceResolvePathType switch
throw new FileNotFoundException(type + " not found", path); {
AbsolutePathType.Container => new DirectoryNotFoundException($"{type} not found: '{path}'"),
_ => new FileNotFoundException(type + " not found", path)
};
} }
catch (Exception e) catch (Exception e)
{ {
if (!forceResolve) throw new Exception($"Could not resolve path '{nativePath.Path}' and {nameof(forceResolve)} is false.", e); if (!forceResolve) throw new Exception($"Could not resolve path '{nativePath.Path}' and {nameof(forceResolve)} is false.", e);
innerException = e;
}
return forceResolvePathType switch return forceResolvePathType switch
{ {
AbsolutePathType.Container => Task.FromResult((IItem)CreateEmptyContainer(nativePath, Observable.Return(new List<Exception>() { e }))), AbsolutePathType.Container => Task.FromResult((IItem)CreateEmptyContainer(nativePath, Observable.Return(new List<Exception>() { innerException }))),
AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)), 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)}.", e) _ => 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<IEnumerable<Exception>>? exceptions = null) private Container CreateEmptyContainer(NativePath nativePath, IObservable<IEnumerable<Exception>>? exceptions = null)
{ {
@@ -108,8 +118,8 @@ namespace FileTime.Providers.Local
} }
public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName)); public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName));
public List<IAbsolutePath> GetItemsByContainer(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path)); private List<IAbsolutePath> GetItemsByContainer(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path));
public List<IAbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(GetFilesSafe(directoryInfo).Select(FileToAbsolutePath)).ToList(); private List<IAbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(directoryInfo.GetFiles().Select(FileToAbsolutePath)).ToList();
private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo) private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo)
{ {
@@ -123,7 +133,7 @@ namespace FileTime.Providers.Local
return new AbsolutePath(this, fullName, AbsolutePathType.Element); return new AbsolutePath(this, fullName, AbsolutePathType.Element);
} }
private Container DirectoryToContainer(DirectoryInfo directoryInfo) private Container DirectoryToContainer(DirectoryInfo directoryInfo, bool initializeChildren = true)
{ {
var fullName = GetFullName(directoryInfo.FullName); var fullName = GetFullName(directoryInfo.FullName);
var parentFullName = fullName.GetParent(); var parentFullName = fullName.GetParent();
@@ -131,6 +141,7 @@ namespace FileTime.Providers.Local
this, this,
parentFullName ?? new FullName(""), parentFullName ?? new FullName(""),
AbsolutePathType.Container); AbsolutePathType.Container);
var exceptions = new BehaviorSubject<IEnumerable<Exception>>(Enumerable.Empty<Exception>());
return new( return new(
directoryInfo.Name, directoryInfo.Name,
@@ -145,9 +156,24 @@ namespace FileTime.Providers.Local
true, true,
GetDirectoryAttributes(directoryInfo), GetDirectoryAttributes(directoryInfo),
this, this,
Observable.Return(Enumerable.Empty<Exception>()), exceptions,
Observable.Return(GetItemsByContainer(directoryInfo)) Observable.FromAsync(async () => await Task.Run(InitChildren))
); );
Task<List<IAbsolutePath>?> InitChildren()
{
List<IAbsolutePath>? result = null;
try
{
result = initializeChildren ? (List<IAbsolutePath>?)GetItemsByContainer(directoryInfo) : null;
}
catch (Exception e)
{
exceptions.OnNext(new List<Exception>() { e });
}
return Task.FromResult(result);
}
} }
private Element FileToElement(FileInfo fileInfo) private Element FileToElement(FileInfo fileInfo)