Parent's children, optimalizations
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using System.Reactive.Concurrency;
|
||||
|
||||
namespace FileTime.App.Core.Services
|
||||
{
|
||||
public interface IRxSchedulerService
|
||||
{
|
||||
IScheduler GetWorkerScheduler();
|
||||
IScheduler GetUIScheduler();
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,6 @@ namespace FileTime.App.Core.ViewModels
|
||||
IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; }
|
||||
IObservable<IEnumerable<FullName>> MarkedItems { get; }
|
||||
IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; }
|
||||
IObservable<IReadOnlyList<IItemViewModel>?> ParentsChildren { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using FileTime.App.Core.Extensions;
|
||||
using FileTime.App.Core.Models.Enums;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -15,6 +14,7 @@ namespace FileTime.App.Core.ViewModels
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IItemNameConverterService _itemNameConverterService;
|
||||
private readonly IAppState _appState;
|
||||
private readonly IRxSchedulerService _rxSchedulerService;
|
||||
private readonly BehaviorSubject<IEnumerable<FullName>> _markedItems = new(Enumerable.Empty<FullName>());
|
||||
private readonly List<IDisposable> _disposables = new();
|
||||
private bool disposed;
|
||||
@@ -29,11 +29,13 @@ namespace FileTime.App.Core.ViewModels
|
||||
public IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; private set; } = null!;
|
||||
public IObservable<IEnumerable<FullName>> MarkedItems { get; }
|
||||
public IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; private set; } = null!;
|
||||
public IObservable<IReadOnlyList<IItemViewModel>?> ParentsChildren { get; private set; } = null!;
|
||||
|
||||
public TabViewModel(
|
||||
IServiceProvider serviceProvider,
|
||||
IItemNameConverterService itemNameConverterService,
|
||||
IAppState appState)
|
||||
IAppState appState,
|
||||
IRxSchedulerService rxSchedulerService)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_itemNameConverterService = itemNameConverterService;
|
||||
@@ -41,6 +43,7 @@ namespace FileTime.App.Core.ViewModels
|
||||
|
||||
MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable();
|
||||
IsSelected = _appState.SelectedTab.Select(s => s == this);
|
||||
_rxSchedulerService = rxSchedulerService;
|
||||
}
|
||||
|
||||
public void Init(ITab tab, int tabNumber)
|
||||
@@ -49,7 +52,16 @@ namespace FileTime.App.Core.ViewModels
|
||||
TabNumber = tabNumber;
|
||||
|
||||
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 =
|
||||
Observable.CombineLatest(
|
||||
CurrentItems,
|
||||
@@ -73,7 +85,36 @@ namespace FileTime.App.Core.ViewModels
|
||||
currentSelectedItemThrottled
|
||||
.Where(c => c is null || c is not IContainerViewModel)
|
||||
.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>()));
|
||||
|
||||
@@ -83,7 +124,7 @@ namespace FileTime.App.Core.ViewModels
|
||||
|
||||
return await items
|
||||
.ToAsyncEnumerable()
|
||||
.SelectAwait(async i => await i.ResolveAsync(true))
|
||||
.SelectAwait(async i => await i.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true)))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace FileTime.Core.Models
|
||||
FullName Path { get; }
|
||||
AbsolutePathType Type { get; }
|
||||
|
||||
Task<IItem> ResolveAsync(bool forceResolve = false);
|
||||
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false);
|
||||
Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
|
||||
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public readonly struct ItemInitializationSettings
|
||||
{
|
||||
public readonly bool SkipChildInitialization;
|
||||
|
||||
public ItemInitializationSettings(bool skipChildInitialization)
|
||||
{
|
||||
SkipChildInitialization = skipChildInitialization;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,18 @@ namespace FileTime.Core.Services
|
||||
{
|
||||
public interface IContentProvider : IContainer, IOnContainerEnter
|
||||
{
|
||||
Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
|
||||
Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
|
||||
Task<IItem> GetItemByFullNameAsync(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,17 @@ namespace FileTime.Core.Models
|
||||
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;
|
||||
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
|
||||
{
|
||||
return await ResolveAsync(forceResolve);
|
||||
return await ResolveAsync(forceResolve, itemInitializationSettings);
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
@@ -49,9 +49,17 @@ namespace FileTime.Core.Services
|
||||
}
|
||||
|
||||
public virtual Task OnEnter() => Task.CompletedTask;
|
||||
public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown)
|
||||
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType);
|
||||
public abstract Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
|
||||
public virtual async Task<IItem> GetItemByFullNameAsync(
|
||||
FullName fullName,
|
||||
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 NativePath GetNativePath(FullName fullName);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace FileTime.Core.Services
|
||||
|
||||
public Tab()
|
||||
{
|
||||
CurrentLocation = _currentLocation.DistinctUntilChanged().Do(_ => {; }).Publish(null).RefCount();
|
||||
CurrentLocation = _currentLocation.DistinctUntilChanged().Publish(null).RefCount();
|
||||
CurrentItems =
|
||||
Observable.Merge(
|
||||
CurrentLocation
|
||||
@@ -69,7 +69,7 @@ namespace FileTime.Core.Services
|
||||
private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation)
|
||||
{
|
||||
//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);
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FileTime.GuiApp
|
||||
.AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>())
|
||||
.AddSingleton<DefaultModeKeyInputHandler>()
|
||||
.AddSingleton<RapidTravelModeKeyInputHandler>()
|
||||
.AddSingleton<IRxSchedulerService, AvaloniaRxSchedulerService>()
|
||||
//TODO: move??
|
||||
.AddTransient<ITab, Tab>()
|
||||
.AddTransient<ITabViewModel, TabViewModel>()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,23 @@
|
||||
<ColumnDefinition Width="45*" />
|
||||
</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
|
||||
Grid.Column="1"
|
||||
@@ -112,7 +129,6 @@
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{DynamicResource ContentSeparatorBrush}" />
|
||||
|
||||
|
||||
<Grid
|
||||
Grid.Column="2"
|
||||
RowDefinitions="Auto,*">
|
||||
|
||||
@@ -19,17 +19,5 @@ namespace FileTime.Providers.Local
|
||||
+ ((directoryInfo.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<FileInfo> GetFilesSafe(DirectoryInfo directoryInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return directoryInfo.GetFiles();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<FileInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Runtime.InteropServices;
|
||||
using FileTime.Core.Enums;
|
||||
using FileTime.Core.Models;
|
||||
@@ -31,9 +32,14 @@ namespace FileTime.Providers.Local
|
||||
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;
|
||||
Exception? innerException;
|
||||
try
|
||||
{
|
||||
if ((path?.Length ?? 0) == 0)
|
||||
@@ -42,7 +48,7 @@ namespace FileTime.Providers.Local
|
||||
}
|
||||
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))
|
||||
{
|
||||
@@ -56,21 +62,25 @@ namespace FileTime.Providers.Local
|
||||
_ => "Directory or file"
|
||||
};
|
||||
|
||||
if (forceResolvePathType == AbsolutePathType.Container) throw new DirectoryNotFoundException($"{type} not found: '{path}'");
|
||||
throw new FileNotFoundException(type + " not found", path);
|
||||
innerException = forceResolvePathType switch
|
||||
{
|
||||
AbsolutePathType.Container => new DirectoryNotFoundException($"{type} not found: '{path}'"),
|
||||
_ => new FileNotFoundException(type + " not found", path)
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!forceResolve) throw new Exception($"Could not resolve path '{nativePath.Path}' and {nameof(forceResolve)} is false.", e);
|
||||
innerException = e;
|
||||
}
|
||||
|
||||
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)),
|
||||
_ => 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)
|
||||
{
|
||||
@@ -108,8 +118,8 @@ namespace FileTime.Providers.Local
|
||||
}
|
||||
|
||||
public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName));
|
||||
public 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(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path));
|
||||
private List<IAbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(directoryInfo.GetFiles().Select(FileToAbsolutePath)).ToList();
|
||||
|
||||
private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo)
|
||||
{
|
||||
@@ -123,7 +133,7 @@ namespace FileTime.Providers.Local
|
||||
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 parentFullName = fullName.GetParent();
|
||||
@@ -131,6 +141,7 @@ namespace FileTime.Providers.Local
|
||||
this,
|
||||
parentFullName ?? new FullName(""),
|
||||
AbsolutePathType.Container);
|
||||
var exceptions = new BehaviorSubject<IEnumerable<Exception>>(Enumerable.Empty<Exception>());
|
||||
|
||||
return new(
|
||||
directoryInfo.Name,
|
||||
@@ -145,9 +156,24 @@ namespace FileTime.Providers.Local
|
||||
true,
|
||||
GetDirectoryAttributes(directoryInfo),
|
||||
this,
|
||||
Observable.Return(Enumerable.Empty<Exception>()),
|
||||
Observable.Return(GetItemsByContainer(directoryInfo))
|
||||
exceptions,
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user