diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index ab8606b..f1fb4da 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -195,8 +195,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi var resolvedOnlyItem = await _timelessContentProvider.GetItemByFullNameAsync(itemsToDelete[0], PointInTime.Present); if (resolvedOnlyItem is IContainer {AllowRecursiveDeletion: true} onlyContainer - && await onlyContainer.Items.GetItemsAsync() is { } children - && children.Any()) + && onlyContainer.ItemsCollection.Any()) { questionText = $"The container '{onlyContainer.DisplayName}' is not empty. Proceed with delete?"; } @@ -206,7 +205,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi } } - if (itemsToDelete?.Count == 0) return; + if (itemsToDelete.Count == 0) return; if (questionText is { }) { @@ -219,7 +218,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi return; } - + var deleteCommand = _serviceProvider.GetRequiredService(); deleteCommand.HardDelete = command.IsHardDelete; deleteCommand.ItemsToDelete.AddRange(itemsToDelete!); diff --git a/src/AppCommon/FileTime.App.Search/SearchTask.cs b/src/AppCommon/FileTime.App.Search/SearchTask.cs index 5885c3d..48255c8 100644 --- a/src/AppCommon/FileTime.App.Search/SearchTask.cs +++ b/src/AppCommon/FileTime.App.Search/SearchTask.cs @@ -80,8 +80,7 @@ public class SearchTask : ISearchTask private async Task TraverseTree(IContainer container) { - var items = (await container.Items.GetItemsAsync())?.ToList(); - if (items is null) return; + var items = container.ItemsCollection.ToList(); var childContainers = new List(); diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs deleted file mode 100644 index 1225349..0000000 --- a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Reactive.Linq; -using DynamicData; -using FileTime.Core.Models; - -namespace FileTime.Core.Extensions; - -public static class DynamicDataExtensions -{ - private class DisposableContext - { - private readonly SemaphoreSlim _semaphore = new(1, 1); - private readonly Func _transformResult; - private readonly TaskCompletionSource _taskCompletionSource; - private bool _isFinished; - public IDisposable? Disposable { get; set; } - - public DisposableContext(Func transformResult, - TaskCompletionSource taskCompletionSource, IDisposable? disposable = null) - { - _transformResult = transformResult; - _taskCompletionSource = taskCompletionSource; - Disposable = disposable; - } - - public void OnNext(TParam param) - { - if (IsFinished()) return; - Disposable?.Dispose(); - var result = _transformResult(param); - _taskCompletionSource.SetResult(result); - } - - public void OnError(Exception ex) - { - if (IsFinished()) return; - Disposable?.Dispose(); - _taskCompletionSource.SetException(ex); - } - - public void OnCompleted() - { - if (IsFinished()) return; - Disposable?.Dispose(); - _taskCompletionSource.SetResult(default); - } - - private bool IsFinished() - { - _semaphore.Wait(); - var finished = _isFinished; - _isFinished = true; - _semaphore.Release(); - - return finished; - } - } - - public static async Task?> GetItemsAsync( - this IObservable>?> stream) - => await GetItemsAsync(stream - .Select(s => - s is null - ? new SourceList().Connect().StartWithEmpty().ToCollection() - : s.StartWithEmpty().ToCollection()) - .Switch()); - - public static async Task?> GetItemsAsync( - this IObservable> stream) - => await GetItemsAsync(stream.StartWithEmpty().ToCollection()); - - public static Task?> GetItemsAsync( - this IObservable> stream) - { - var taskCompletionSource = new TaskCompletionSource?>(); - var context = new DisposableContext, IEnumerable?>(r => r, - taskCompletionSource); - - context.Disposable = stream - .Subscribe( - context.OnNext, - context.OnError, - context.OnCompleted - ); - - return taskCompletionSource.Task; - } -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs index a295f3a..4487085 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using DynamicData; namespace FileTime.Core.Models; @@ -5,6 +6,7 @@ namespace FileTime.Core.Models; public interface IContainer : IItem { IObservable> Items { get; } + ReadOnlyObservableCollection ItemsCollection { get; } IObservable IsLoading { get; } bool AllowRecursiveDeletion { get; } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index 719221b..fca053b 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -111,13 +111,12 @@ public class CopyCommand : CommandBase, ITransportationCommand if (item is IContainer container) { - if (!((await resolvedTarget.Items.GetItemsAsync())?.Any(i => i.Path.GetName() == item.Name) ?? false)) + if (resolvedTarget.ItemsCollection.All(i => i.Path.GetName() != item.Name)) { await copyOperation.CreateContainerAsync(resolvedTarget, container.Name, container.PointInTime); } - var children = await container.Items.GetItemsAsync(); - if (children is null) continue; + var children = container.ItemsCollection; await TraverseTree(currentTime, children.Select(c => c.Path).ToList(), target.GetChild(item.Name, AbsolutePathType.Container), transportMode, copyOperation); await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(_timelessContentProvider, container)); diff --git a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs index c8abeaa..086550b 100644 --- a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs +++ b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs @@ -42,9 +42,7 @@ public abstract class CreateItemBase : CommandBase, IExecutableCommand, IInitabl var parent = await ResolveParentAsync(); if (parent is not IContainer parentContainer) return CanCommandRun.False; - var items = await parentContainer.Items.GetItemsAsync().AwaitWithTimeout(10, Enumerable.Empty()); - if (items is null) return CanCommandRun.Forcable; - + var items = parentContainer.ItemsCollection; var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewItemName); return existingItem switch diff --git a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs index d78b609..9fe550d 100644 --- a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs @@ -71,7 +71,7 @@ public class DeleteCommand : CommandBase, IExecutableCommand { await TraverseTree( currentTime, - (await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty(), + container.ItemsCollection.Select(i => i.Path), itemDeleters, deleteStrategy ); diff --git a/src/Core/FileTime.Core.Command/Helper.cs b/src/Core/FileTime.Core.Command/Helper.cs index 158f127..01ffc7f 100644 --- a/src/Core/FileTime.Core.Command/Helper.cs +++ b/src/Core/FileTime.Core.Command/Helper.cs @@ -7,7 +7,7 @@ public static class Helper { public static async Task GetNewNameAsync(IContainer resolvedTarget, string name, TransportMode transportMode) { - var items = (await resolvedTarget.Items.GetItemsAsync() ?? throw new NullReferenceException()).ToList(); + var items = resolvedTarget.ItemsCollection.ToList(); var newName = name; var targetNameExists = items.Any(i => i.Path.GetName() == newName); if (transportMode == TransportMode.Merge) diff --git a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs index 10c2105..a07fddd 100644 --- a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs +++ b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs @@ -53,8 +53,8 @@ public class StreamCopyCommandHandler : ICommandHandler { var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!; var elementName = targetPath.Path; - var parentChildren = await parent.Items.GetItemsAsync(); - if (parentChildren!.All(e => e.Path.GetName() != elementName.GetName())) + var parentChildren = parent.ItemsCollection.ToList(); + if (parentChildren.All(e => e.Path.GetName() != elementName.GetName())) { var itemCreator = _contentAccessorFactory.GetItemCreator(parent.Provider); await itemCreator.CreateElementAsync(parent.Provider, elementName); diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs index a2ed227..3762304 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; @@ -13,6 +14,7 @@ public abstract class ContentProviderBase : IContentProvider private readonly IObservable> _items; protected SourceCache Items { get; } = new(p => p.Path.Path); + public ReadOnlyObservableCollection ItemsCollection { get; } protected ExtensionCollection Extensions { get; } IObservable> IContainer.Items => _items; @@ -61,6 +63,8 @@ public abstract class ContentProviderBase : IContentProvider Extensions = new ExtensionCollection(); _extensions = Extensions.AsReadOnly(); _items = Items.Connect().StartWithEmpty(); + _items.Bind(out var items).Subscribe(); + ItemsCollection = items; } public virtual Task OnEnter() => Task.CompletedTask; diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 82853b5..8751d15 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; @@ -26,11 +27,19 @@ public record Container( ReadOnlyExtensionCollection Extensions, IObservable> Items) : IContainer { + private readonly Lazy> _itemsCollectionLazy = + new (() => + { + Items.Bind(out var items).Subscribe(); + return items; + }); 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 ReadOnlyObservableCollection ItemsCollection => _itemsCollectionLazy.Value; + public void CancelLoading() => _loadingCancellationTokenSource.Cancel(); } \ No newline at end of file