diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs index 6ab596a..9b2140c 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs @@ -47,6 +47,56 @@ public partial class BindedCollection : IDisposable, INotifyPropertyChanged }); } + public void Dispose() + { + _disposable?.Dispose(); + _innerDisposable?.Dispose(); + GC.SuppressFinalize(this); + } +} + +public partial class BindedCollection : IDisposable, INotifyPropertyChanged where TKey : notnull +{ + private readonly IDisposable? _disposable; + private IDisposable? _innerDisposable; + + [Notify] private ReadOnlyObservableCollection? _collection; + + public BindedCollection() + { + } + + public BindedCollection(IObservable> dynamicList) + { + _disposable = dynamicList + .Bind(out var collection) + .DisposeMany() + .Subscribe(); + + _collection = collection; + } + + public BindedCollection(IObservable>?> dynamicListSource) + { + _disposable = dynamicListSource.Subscribe(dynamicList => + { + _innerDisposable?.Dispose(); + if (dynamicList is not null) + { + _innerDisposable = dynamicList + .Bind(out var collection) + .DisposeMany() + .Subscribe(); + + Collection = collection; + } + else + { + Collection = null; + } + }); + } + public void Dispose() { _disposable?.Dispose(); diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs index 2ed1b2d..a9e2612 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs @@ -14,13 +14,13 @@ public interface ITabViewModel : IInitable, IDisposable IObservable IsSelected { get; } IObservable CurrentLocation { get; } IObservable CurrentSelectedItem { get; } - IObservable>?> CurrentItems { get; } + IObservable>?> CurrentItems { get; } IObservable> MarkedItems { get; } - IObservable>?> SelectedsChildren { get; } - IObservable>?> ParentsChildren { get; } - BindedCollection? CurrentItemsCollection { get; } - BindedCollection? SelectedsChildrenCollection { get; } - BindedCollection? ParentsChildrenCollection { get; } + IObservable>?> SelectedsChildren { get; } + IObservable>?> ParentsChildren { get; } + BindedCollection? CurrentItemsCollection { get; } + BindedCollection? SelectedsChildrenCollection { get; } + BindedCollection? ParentsChildrenCollection { get; } IObservable?> CurrentItemsCollectionObservable { get; } IObservable?> ParentsChildrenCollectionObservable { get; } IObservable?> SelectedsChildrenCollectionObservable { get; } diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index 1931107..6ec7ee6 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -31,10 +31,10 @@ public partial class TabViewModel : ITabViewModel public IObservable CurrentLocation { get; private set; } = null!; public IObservable CurrentSelectedItem { get; private set; } = null!; - public IObservable>?> CurrentItems { get; private set; } = null!; + public IObservable>?> CurrentItems { get; private set; } = null!; public IObservable> MarkedItems { get; } - public IObservable>?> SelectedsChildren { get; private set; } = null!; - public IObservable>?> ParentsChildren { get; private set; } = null!; + public IObservable>?> SelectedsChildren { get; private set; } = null!; + public IObservable>?> ParentsChildren { get; private set; } = null!; public IObservable?> CurrentItemsCollectionObservable { get; private set; } = null!; @@ -46,11 +46,11 @@ public partial class TabViewModel : ITabViewModel SelectedsChildrenCollectionObservable { get; private set; } = null!; - [Property] private BindedCollection? _currentItemsCollection; + [Property] private BindedCollection? _currentItemsCollection; - [Property] private BindedCollection? _parentsChildrenCollection; + [Property] private BindedCollection? _parentsChildrenCollection; - [Property] private BindedCollection? _selectedsChildrenCollection; + [Property] private BindedCollection? _selectedsChildrenCollection; public IContainer? CachedCurrentLocation { get; private set; } @@ -81,12 +81,10 @@ public partial class TabViewModel : ITabViewModel CurrentItems = tab.CurrentItems .Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main))) - /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) - .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ .Publish(null) .RefCount(); - CurrentSelectedItem = + /*CurrentSelectedItem = Observable.CombineLatest( CurrentItems, tab.CurrentSelectedItem, @@ -96,18 +94,28 @@ public partial class TabViewModel : ITabViewModel : currentItems .ToCollection() .Select(items => - items.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)) + items.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItemPath?.Path.Path)) ) .Switch() + .Publish(null) + .RefCount();*/ + + CurrentSelectedItem = + Observable.CombineLatest( + CurrentItems, + tab.CurrentSelectedItem, + (currentItems, currentSelectedItemPath) => + CurrentItemsCollection?.Collection?.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItemPath?.Path.Path) + ) .Publish(null) .RefCount(); SelectedsChildren = InitSelectedsChildren(); ParentsChildren = InitParentsChildren(); - CurrentItemsCollectionObservable = InitAsd(CurrentItems); - SelectedsChildrenCollectionObservable = InitAsd(SelectedsChildren); - ParentsChildrenCollectionObservable = InitAsd(ParentsChildren); + CurrentItemsCollectionObservable = InitCollection(CurrentItems); + SelectedsChildrenCollectionObservable = InitCollection(SelectedsChildren); + ParentsChildrenCollectionObservable = InitCollection(ParentsChildren); CurrentItemsCollection = new(CurrentItems); ParentsChildrenCollection = new(ParentsChildren); @@ -115,7 +123,7 @@ public partial class TabViewModel : ITabViewModel tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()).AddToDisposables(_disposables); - IObservable>?> InitSelectedsChildren() + IObservable>?> InitSelectedsChildren() { var currentSelectedItemThrottled = CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount(); @@ -131,7 +139,7 @@ public partial class TabViewModel : ITabViewModel .Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild))), currentSelectedItemThrottled .Where(c => c is null or not IContainerViewModel) - .Select(_ => (IObservable>?)null) + .Select(_ => (IObservable>?)null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ @@ -139,7 +147,7 @@ public partial class TabViewModel : ITabViewModel .RefCount(); } - IObservable>?> InitParentsChildren() + IObservable>?> InitParentsChildren() { var parentThrottled = CurrentLocation .Select(l => l?.Parent) @@ -159,7 +167,7 @@ public partial class TabViewModel : ITabViewModel .Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent))), parentThrottled .Where(p => p is null) - .Select(_ => (IObservable>?)null) + .Select(_ => (IObservable>?)null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ @@ -167,8 +175,8 @@ public partial class TabViewModel : ITabViewModel .RefCount(); } - IObservable?> InitAsd( - IObservable>?> source) + IObservable?> InitCollection( + IObservable>?> source) { return source .Select(c => diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs index e613725..7b4d1aa 100644 --- a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs +++ b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs @@ -41,7 +41,7 @@ public static class DynamicDataExtensions } public static async Task?> GetItemsAsync( - this IObservable>?> stream) + this IObservable>?> stream) => await GetItemsAsync(stream .Select(s => s is null @@ -50,7 +50,7 @@ public static class DynamicDataExtensions .Switch()); public static async Task?> GetItemsAsync( - this IObservable> stream) + this IObservable> stream) => await GetItemsAsync(stream.ToCollection()); private static Task?> GetItemsAsync( diff --git a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs index 6a6b905..5390a31 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs @@ -4,6 +4,6 @@ namespace FileTime.Core.Models; public interface IContainer : IItem { - IObservable>?> Items { get; } + IObservable>?> Items { get; } IObservable IsLoading { get; } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs index 11bdb8d..8b44f16 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs @@ -8,7 +8,7 @@ public interface ITab : IInitable, IDisposable { IObservable CurrentLocation { get; } IObservable CurrentSelectedItem { get; } - IObservable>?> CurrentItems { get; } + IObservable>?> CurrentItems { get; } FullName? LastDeepestSelectedPath { get; } void SetCurrentLocation(IContainer newLocation); diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs index 031b8c0..5354ace 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs @@ -11,10 +11,10 @@ public abstract class ContentProviderBase : IContentProvider { private readonly ReadOnlyExtensionCollection _extensions; - protected BehaviorSubject>?> Items { get; } = new(null); + protected BehaviorSubject>?> Items { get; } = new(null); protected ExtensionCollection Extensions { get; } - IObservable>?> IContainer.Items => Items; + IObservable>?> IContainer.Items => Items; public string Name { get; } diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index b168b95..e8968e8 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -25,7 +25,7 @@ public record Container( PointInTime PointInTime, IObservable> Exceptions, ReadOnlyExtensionCollection Extensions, - IObservable>?> Items) : IContainer + IObservable>?> Items) : IContainer { BehaviorSubject IsLoading { get; } = new BehaviorSubject(false); IObservable IContainer.IsLoading => IsLoading.AsObservable(); diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 6bf9259..26a0592 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -19,7 +19,7 @@ public class Tab : ITab private PointInTime _currentPointInTime; public IObservable CurrentLocation { get; } - public IObservable>?> CurrentItems { get; } + public IObservable>?> CurrentItems { get; } public IObservable CurrentSelectedItem { get; } public FullName? LastDeepestSelectedPath { get; private set; } @@ -55,9 +55,9 @@ public class Tab : ITab (items, filters) => items?.Where(i => filters.All(f => f.Filter(i)))), CurrentLocation .Where(c => c is null) - .Select(_ => (IObservable>?)null) + .Select(_ => (IObservable>?)null) ) - .Publish((IObservable>?)null) + .Publish((IObservable>?)null) .RefCount(); CurrentSelectedItem = diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs index e12bb61..3eea27e 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs @@ -11,5 +11,5 @@ public interface IGuiAppState : IAppState bool NoCommandFound { get; set; } string? MessageBoxText { get; set; } List PossibleCommands { get; set; } - BindedCollection RootDriveInfos { get; set; } + BindedCollection RootDriveInfos { get; set; } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs index 0c17a08..175a048 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs @@ -17,7 +17,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState [Property] private List _possibleCommands = new(); - [Property] private BindedCollection _rootDriveInfos = new(); + [Property] private BindedCollection _rootDriveInfos = new(); public List PreviousKeys { get; } = new(); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs index 8953d31..7debc60 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs @@ -15,14 +15,15 @@ namespace FileTime.GuiApp.Services; public class RootDriveInfoService : IStartupHandler { private readonly SourceList _rootDrives = new(); - private readonly IObservable> _localContentProviderStream; + private readonly IObservable> _localContentProviderStream; - public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider, ITimelessContentProvider timelessContentProvider) + public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider, + ITimelessContentProvider timelessContentProvider) { InitRootDrives(); - var localContentProviderAsList = new SourceList(); - localContentProviderAsList.Add(new AbsolutePath(timelessContentProvider, localContentProvider)); + var localContentProviderAsList = new SourceCache(i => i.Path.Path); + localContentProviderAsList.AddOrUpdate(new AbsolutePath(timelessContentProvider, localContentProvider)); _localContentProviderStream = localContentProviderAsList.Connect(); var rootDriveInfos = Observable.CombineLatest( @@ -31,7 +32,7 @@ public class RootDriveInfoService : IStartupHandler (items, drives) => { return items is null - ? Observable.Empty>() + ? Observable.Empty>() : items! .Or(new[] { _localContentProviderStream }) .Transform(i => (Path: i, Drive: drives.FirstOrDefault(d => @@ -39,7 +40,8 @@ public class RootDriveInfoService : IStartupHandler var containerPath = localContentProvider.GetNativePath(i.Path).Path; var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar); return containerPath == drivePath - || (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && d.Name == "/"); + || (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && + d.Name == "/"); }))) .Filter(t => t.Drive is not null); } @@ -51,7 +53,7 @@ public class RootDriveInfoService : IStartupHandler .Transform(t => new RootDriveInfo(t.Drive, t.Container)) .Sort(SortExpressionComparer.Ascending(d => d.Name)); - guiAppState.RootDriveInfos = new BindedCollection(rootDriveInfos); + guiAppState.RootDriveInfos = new BindedCollection(rootDriveInfos); void InitRootDrives() { diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index b2230a4..3cd976d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -327,11 +327,11 @@ diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index af175cc..c5bdbf5 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -6,7 +6,6 @@ using FileTime.App.Core.Models; using FileTime.Core.ContentAccess; using FileTime.Core.Enums; using FileTime.Core.Models; -using FileTime.Core.Services; using FileTime.Core.Timeline; namespace FileTime.Providers.Local; @@ -14,7 +13,7 @@ namespace FileTime.Providers.Local; public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider { private readonly ITimelessContentProvider _timelessContentProvider; - private readonly SourceList _rootDirectories = new(); + private readonly SourceCache _rootDirectories = new(i => i.Path.Path); private readonly bool _isCaseInsensitive; public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base("local") @@ -43,7 +42,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo _rootDirectories.Edit(actions => { actions.Clear(); - actions.AddRange(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present))); + actions.AddOrUpdate(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present))); }); } @@ -147,7 +146,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo pointInTime, nonNullExceptions, new ExtensionCollection().AsReadOnly(), - Observable.Return>?>(null) + Observable.Return>?>(null) ); } @@ -216,16 +215,16 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo Observable.FromAsync(async () => await Task.Run(InitChildren)) ); - Task>?> InitChildren() + Task>?> InitChildren() { - SourceList? result = null; + SourceCache? result = null; try { var items = initializeChildren ? (List?)GetItemsByContainer(directoryInfo, pointInTime) : null; if (items != null) { - result = new SourceList(); - result.AddRange(items); + result = new SourceCache(i => i.Path.Path); + result.AddOrUpdate(items); } } catch (Exception e)