diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index befc2dc..ba545b4 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -82,7 +82,6 @@ public partial class TabViewModel : ITabViewModel CurrentItems = tab.CurrentItems .Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main))) - .Select(items => items?.Sort(SortItems())) .Publish(null) .RefCount(); diff --git a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentProvider.cs b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentProvider.cs index 5d57fc2..d7dc966 100644 --- a/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentProvider.cs +++ b/src/Core/FileTime.Core.Abstraction/ContentAccess/IContentProvider.cs @@ -22,7 +22,6 @@ public interface IContentProvider : IContainer, IOnContainerEnter AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); - Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime); NativePath GetNativePath(FullName fullName); Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default); diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs index 836a399..6fcdf2f 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs @@ -78,7 +78,6 @@ public abstract class ContentProviderBase : IContentProvider AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default); - public abstract Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime); public abstract NativePath GetNativePath(FullName fullName); public abstract Task GetContentAsync(IElement element, diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 6210e7a..436c518 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -26,7 +26,14 @@ public record Container( ReadOnlyExtensionCollection Extensions, IObservable>?> Items) : IContainer { - BehaviorSubject IsLoading { get; } = new(false); + private readonly CancellationTokenSource _loadingCancellationTokenSource = new(); + public CancellationToken LoadingCancellationToken => _loadingCancellationTokenSource.Token; + public BehaviorSubject IsLoading { get; } = new(false); IObservable IContainer.IsLoading => IsLoading.AsObservable(); public AbsolutePathType Type => AbsolutePathType.Container; + + public void CancelLoading() + { + _loadingCancellationTokenSource.Cancel(); + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 17bcd63..c478b1a 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -2,6 +2,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; using DynamicData.Alias; +using DynamicData.Binding; using FileTime.Core.Helper; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -52,7 +53,11 @@ public class Tab : ITab .Switch() .Select(items => items?.TransformAsync(MapItem)), _itemFilters.Connect().StartWithEmpty().ToCollection(), - (items, filters) => items?.Where(i => filters.All(f => f.Filter(i)))), + (items, filters) => + items + ?.Where(i => filters.All(f => f.Filter(i))) + .Sort(SortItems()) + ), CurrentLocation .Where(c => c is null) .Select(_ => (IObservable>?)null) @@ -89,6 +94,12 @@ public class Tab : ITab }); } + private static SortExpressionComparer SortItems() + //TODO: Order + => SortExpressionComparer + .Ascending(i => i.Type) + .ThenByAscending(i => i.DisplayName?.ToLower() ?? ""); + private async Task MapItem(AbsolutePath item) => await item.ResolveAsync(true); public void Init(IContainer currentLocation) diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 9e7237e..303002e 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -177,23 +177,6 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo throw new NotImplementedException(); } - public override Task> GetItemsByContainerAsync(FullName fullName, PointInTime pointInTime) - => Task.FromResult(GetItemsByContainer(fullName, pointInTime)); - - private List GetItemsByContainer(FullName fullName, PointInTime pointInTime) - => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path), pointInTime); - - private List GetItemsByContainer(DirectoryInfo directoryInfo, PointInTime pointInTime) - => directoryInfo - .GetDirectories() - .Select(d => DirectoryToAbsolutePath(d, pointInTime)) - .Concat( - directoryInfo - .GetFiles() - .Select(f => FileToAbsolutePath(f, pointInTime)) - ) - .ToList(); - private AbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo, PointInTime pointInTime) { var fullName = GetFullName(directoryInfo); @@ -221,7 +204,9 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo AbsolutePathType.Container); var exceptions = new BehaviorSubject>(Enumerable.Empty()); - return new Container( + var children = new SourceCache(i => i.Path.Path); + + var container = new Container( directoryInfo.Name, directoryInfo.Name, fullName, @@ -239,10 +224,13 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo exceptions, new ExtensionCollection().AsReadOnly(), //Observable.FromAsync(async () => await Task.Run(InitChildrenHelper) - Observable.Return(InitChildren()) + //Observable.Return(InitChildren()) + Observable.Return(children.Connect()) ); - Task>?> InitChildrenHelper() => Task.FromResult(InitChildren()); + Task.Run(() => LoadChildren(container, directoryInfo, children, pointInTime)); + + return container; IObservable>? InitChildren() { @@ -267,6 +255,69 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo } } + private void LoadChildren( + Container container, + DirectoryInfo directoryInfo, + SourceCache children, + PointInTime pointInTime) + { + var lockobj = new object(); + var loadingIndicatorCancellation = new CancellationTokenSource(); + + Task.Run(DelayedLoadingIndicator); + LoadChildren(); + + lock (lockobj) + { + loadingIndicatorCancellation.Cancel(); + container.IsLoading.OnNext(false); + } + + void LoadChildren() + { + foreach (var directory in directoryInfo.EnumerateDirectories()) + { + if (container.LoadingCancellationToken.IsCancellationRequested) break; + var absolutePath = DirectoryToAbsolutePath(directory, pointInTime); + children.AddOrUpdate(absolutePath); + } + + foreach (var file in directoryInfo.EnumerateFiles()) + { + if (container.LoadingCancellationToken.IsCancellationRequested) break; + var absolutePath = FileToAbsolutePath(file, pointInTime); + children.AddOrUpdate(absolutePath); + } + } + + async Task DelayedLoadingIndicator() + { + var token = loadingIndicatorCancellation.Token; + try + { + await Task.Delay(500, token); + } + catch { } + + lock (lockobj) + { + if (token.IsCancellationRequested) return; + container.IsLoading.OnNext(true); + } + } + } + + private List GetItemsByContainer(DirectoryInfo directoryInfo, PointInTime pointInTime) + => directoryInfo + .GetDirectories() + .Select(d => DirectoryToAbsolutePath(d, pointInTime)) + .Concat( + directoryInfo + .GetFiles() + .Select(f => FileToAbsolutePath(f, pointInTime)) + ) + .ToList(); + private Element FileToElement(FileInfo fileInfo, PointInTime pointInTime) { var fullName = GetFullName(fileInfo);