ItemsCollection on Container

This commit is contained in:
2023-02-27 09:00:30 +01:00
parent a01c3a69b4
commit ffbd7b154c
11 changed files with 26 additions and 103 deletions

View File

@@ -195,8 +195,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
var resolvedOnlyItem = await _timelessContentProvider.GetItemByFullNameAsync(itemsToDelete[0], PointInTime.Present); var resolvedOnlyItem = await _timelessContentProvider.GetItemByFullNameAsync(itemsToDelete[0], PointInTime.Present);
if (resolvedOnlyItem is IContainer {AllowRecursiveDeletion: true} onlyContainer if (resolvedOnlyItem is IContainer {AllowRecursiveDeletion: true} onlyContainer
&& await onlyContainer.Items.GetItemsAsync() is { } children && onlyContainer.ItemsCollection.Any())
&& children.Any())
{ {
questionText = $"The container '{onlyContainer.DisplayName}' is not empty. Proceed with delete?"; 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 { }) if (questionText is { })
{ {

View File

@@ -80,8 +80,7 @@ public class SearchTask : ISearchTask
private async Task TraverseTree(IContainer container) private async Task TraverseTree(IContainer container)
{ {
var items = (await container.Items.GetItemsAsync())?.ToList(); var items = container.ItemsCollection.ToList();
if (items is null) return;
var childContainers = new List<IContainer>(); var childContainers = new List<IContainer>();

View File

@@ -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<TParam, TTaskResult>
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly Func<TParam, TTaskResult> _transformResult;
private readonly TaskCompletionSource<TTaskResult?> _taskCompletionSource;
private bool _isFinished;
public IDisposable? Disposable { get; set; }
public DisposableContext(Func<TParam, TTaskResult> transformResult,
TaskCompletionSource<TTaskResult?> 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<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> stream)
=> await GetItemsAsync(stream
.Select(s =>
s is null
? new SourceList<AbsolutePath>().Connect().StartWithEmpty().ToCollection()
: s.StartWithEmpty().ToCollection())
.Switch());
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IChangeSet<AbsolutePath, string>> stream)
=> await GetItemsAsync(stream.StartWithEmpty().ToCollection());
public static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IReadOnlyCollection<AbsolutePath>> stream)
{
var taskCompletionSource = new TaskCompletionSource<IEnumerable<AbsolutePath>?>();
var context = new DisposableContext<IReadOnlyCollection<AbsolutePath>, IEnumerable<AbsolutePath>?>(r => r,
taskCompletionSource);
context.Disposable = stream
.Subscribe(
context.OnNext,
context.OnError,
context.OnCompleted
);
return taskCompletionSource.Task;
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using DynamicData; using DynamicData;
namespace FileTime.Core.Models; namespace FileTime.Core.Models;
@@ -5,6 +6,7 @@ namespace FileTime.Core.Models;
public interface IContainer : IItem public interface IContainer : IItem
{ {
IObservable<IChangeSet<AbsolutePath, string>> Items { get; } IObservable<IChangeSet<AbsolutePath, string>> Items { get; }
ReadOnlyObservableCollection<AbsolutePath> ItemsCollection { get; }
IObservable<bool> IsLoading { get; } IObservable<bool> IsLoading { get; }
bool AllowRecursiveDeletion { get; } bool AllowRecursiveDeletion { get; }
} }

View File

@@ -111,13 +111,12 @@ public class CopyCommand : CommandBase, ITransportationCommand
if (item is IContainer container) 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); await copyOperation.CreateContainerAsync(resolvedTarget, container.Name, container.PointInTime);
} }
var children = await container.Items.GetItemsAsync(); var children = container.ItemsCollection;
if (children is null) continue;
await TraverseTree(currentTime, children.Select(c => c.Path).ToList(), target.GetChild(item.Name, AbsolutePathType.Container), transportMode, copyOperation); await TraverseTree(currentTime, children.Select(c => c.Path).ToList(), target.GetChild(item.Name, AbsolutePathType.Container), transportMode, copyOperation);
await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(_timelessContentProvider, container)); await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(_timelessContentProvider, container));

View File

@@ -42,9 +42,7 @@ public abstract class CreateItemBase : CommandBase, IExecutableCommand, IInitabl
var parent = await ResolveParentAsync(); var parent = await ResolveParentAsync();
if (parent is not IContainer parentContainer) return CanCommandRun.False; if (parent is not IContainer parentContainer) return CanCommandRun.False;
var items = await parentContainer.Items.GetItemsAsync().AwaitWithTimeout(10, Enumerable.Empty<AbsolutePath>()); var items = parentContainer.ItemsCollection;
if (items is null) return CanCommandRun.Forcable;
var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewItemName); var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewItemName);
return existingItem switch return existingItem switch

View File

@@ -71,7 +71,7 @@ public class DeleteCommand : CommandBase, IExecutableCommand
{ {
await TraverseTree( await TraverseTree(
currentTime, currentTime,
(await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty<FullName>(), container.ItemsCollection.Select(i => i.Path),
itemDeleters, itemDeleters,
deleteStrategy deleteStrategy
); );

View File

@@ -7,7 +7,7 @@ public static class Helper
{ {
public static async Task<string?> GetNewNameAsync(IContainer resolvedTarget, string name, TransportMode transportMode) public static async Task<string?> 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 newName = name;
var targetNameExists = items.Any(i => i.Path.GetName() == newName); var targetNameExists = items.Any(i => i.Path.GetName() == newName);
if (transportMode == TransportMode.Merge) if (transportMode == TransportMode.Merge)

View File

@@ -53,8 +53,8 @@ public class StreamCopyCommandHandler : ICommandHandler
{ {
var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!; var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!;
var elementName = targetPath.Path; var elementName = targetPath.Path;
var parentChildren = await parent.Items.GetItemsAsync(); var parentChildren = parent.ItemsCollection.ToList();
if (parentChildren!.All(e => e.Path.GetName() != elementName.GetName())) if (parentChildren.All(e => e.Path.GetName() != elementName.GetName()))
{ {
var itemCreator = _contentAccessorFactory.GetItemCreator(parent.Provider); var itemCreator = _contentAccessorFactory.GetItemCreator(parent.Provider);
await itemCreator.CreateElementAsync(parent.Provider, elementName); await itemCreator.CreateElementAsync(parent.Provider, elementName);

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData; using DynamicData;
@@ -13,6 +14,7 @@ public abstract class ContentProviderBase : IContentProvider
private readonly IObservable<IChangeSet<AbsolutePath, string>> _items; private readonly IObservable<IChangeSet<AbsolutePath, string>> _items;
protected SourceCache<AbsolutePath, string> Items { get; } = new(p => p.Path.Path); protected SourceCache<AbsolutePath, string> Items { get; } = new(p => p.Path.Path);
public ReadOnlyObservableCollection<AbsolutePath> ItemsCollection { get; }
protected ExtensionCollection Extensions { get; } protected ExtensionCollection Extensions { get; }
IObservable<IChangeSet<AbsolutePath, string>> IContainer.Items => _items; IObservable<IChangeSet<AbsolutePath, string>> IContainer.Items => _items;
@@ -61,6 +63,8 @@ public abstract class ContentProviderBase : IContentProvider
Extensions = new ExtensionCollection(); Extensions = new ExtensionCollection();
_extensions = Extensions.AsReadOnly(); _extensions = Extensions.AsReadOnly();
_items = Items.Connect().StartWithEmpty(); _items = Items.Connect().StartWithEmpty();
_items.Bind(out var items).Subscribe();
ItemsCollection = items;
} }
public virtual Task OnEnter() => Task.CompletedTask; public virtual Task OnEnter() => Task.CompletedTask;

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData; using DynamicData;
@@ -26,11 +27,19 @@ public record Container(
ReadOnlyExtensionCollection Extensions, ReadOnlyExtensionCollection Extensions,
IObservable<IChangeSet<AbsolutePath, string>> Items) : IContainer IObservable<IChangeSet<AbsolutePath, string>> Items) : IContainer
{ {
private readonly Lazy<ReadOnlyObservableCollection<AbsolutePath>> _itemsCollectionLazy =
new (() =>
{
Items.Bind(out var items).Subscribe();
return items;
});
private readonly CancellationTokenSource _loadingCancellationTokenSource = new(); private readonly CancellationTokenSource _loadingCancellationTokenSource = new();
public CancellationToken LoadingCancellationToken => _loadingCancellationTokenSource.Token; public CancellationToken LoadingCancellationToken => _loadingCancellationTokenSource.Token;
public BehaviorSubject<bool> IsLoading { get; } = new(false); public BehaviorSubject<bool> IsLoading { get; } = new(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable(); IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
public AbsolutePathType Type => AbsolutePathType.Container; public AbsolutePathType Type => AbsolutePathType.Container;
public ReadOnlyObservableCollection<AbsolutePath> ItemsCollection => _itemsCollectionLazy.Value;
public void CancelLoading() => _loadingCancellationTokenSource.Cancel(); public void CancelLoading() => _loadingCancellationTokenSource.Cancel();
} }