Container size scan, improvements

This commit is contained in:
2023-08-03 00:00:45 +02:00
parent 1713973c3a
commit f4d361f767
57 changed files with 814 additions and 532 deletions

View File

@@ -0,0 +1,15 @@
using DeclarativeProperty;
namespace FileTime.App.ContainerSizeScanner;
public class ChildContainerPreview : ISizePreviewItem
{
public ChildContainerPreview(ISizeScanContainer container)
{
Name = container.Name;
Size = container.Size;
}
public string Name { get; }
public IDeclarativeProperty<long> Size { get; }
}

View File

@@ -0,0 +1,15 @@
using DeclarativeProperty;
namespace FileTime.App.ContainerSizeScanner;
public class ChildElementPreview : ISizePreviewItem
{
public ChildElementPreview(ISizeScanElement element)
{
Name = element.Name;
Size = element.Size;
}
public string Name { get; }
public IDeclarativeProperty<long> Size { get; }
}

View File

@@ -0,0 +1,36 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using FileTime.App.Core.ViewModels.ItemPreview;
using ObservableComputations;
namespace FileTime.App.ContainerSizeScanner;
public class ContainerPreview : IItemPreviewViewModel, IDisposable
{
private readonly OcConsumer _topItemsConsumer = new();
public const string PreviewName = "SizePreviewContainer";
public string Name => PreviewName;
public ObservableCollection<ISizePreviewItem> TopItems { get; }
public ContainerPreview(ISizeScanContainer sizeScanContainer)
{
TopItems = sizeScanContainer
.SizeItems
.Ordering(c => c.Size.Value, ListSortDirection.Descending)
.Taking(0, 10)
.Selecting(i => CreatePreviewItem(i))
.For(_topItemsConsumer);
}
private ISizePreviewItem CreatePreviewItem(ISizeItem sizeItem)
=> sizeItem switch
{
ISizeScanContainer container => new ChildContainerPreview(container),
ISizeScanElement element => new ChildElementPreview(element),
_ => throw new ArgumentOutOfRangeException(nameof(sizeItem))
};
public void Dispose()
=> _topItemsConsumer.Dispose();
}

View File

@@ -7,14 +7,14 @@ using InitableService;
namespace FileTime.App.ContainerSizeScanner;
public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScanSnapshotProvider
public class ContainerScanProvider : ContentProviderBase, IContainerScanSnapshotProvider
{
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IServiceProvider _serviceProvider;
private readonly List<ISizeScanTask> _sizeScanTasks = new();
internal const string ContentProviderName = "container-size-scan";
public ContainerScanSnapshotProvider(
public ContainerScanProvider(
ITimelessContentProvider timelessContentProvider,
IServiceProvider serviceProvider)
: base(ContentProviderName, timelessContentProvider)
@@ -36,7 +36,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
var pathParts = fullName.Path.Split(Constants.SeparatorChar);
var item = _sizeScanTasks.FirstOrDefault(t => t.SizeContainer.Name == pathParts[1])?.SizeContainer;
var item = _sizeScanTasks.FirstOrDefault(t => t.SizeSizeScanContainer.Name == pathParts[1])?.SizeSizeScanContainer;
if (pathParts.Length == 2)
return item ?? throw new ItemNotFoundException(fullName);
@@ -49,10 +49,10 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
if (item is not null)
{
var container = item.ChildContainers.FirstOrDefault(c => c.Name == pathParts[^1]);
if (container is not null) return container;
var childItem = item.SizeItems.FirstOrDefault(c => c.Name == pathParts[^1]);
if (childItem is not null) return childItem;
var childName = item.RealContainer.FullName?.GetChild(pathParts[^1]);
/*var childName = item.RealContainer.FullName?.GetChild(pathParts[^1]);
if (childName is null) throw new ItemNotFoundException(fullName);
return await _timelessContentProvider.GetItemByFullNameAsync(
@@ -61,7 +61,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
forceResolve,
forceResolvePathType,
itemInitializationSettings
);
);*/
}
throw new ItemNotFoundException(fullName);
@@ -106,7 +106,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
_sizeScanTasks.Add(searchTask);
searchTask.Start();
Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer));
Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer));
return searchTask;
}

View File

@@ -1,22 +0,0 @@
using System.Collections.ObjectModel;
using FileTime.App.Core.ViewModels.ItemPreview;
using ObservableComputations;
namespace FileTime.App.ContainerSizeScanner;
public class ContainerSizeContainerPreview : ISizePreviewItem
{
public const string PreviewName = "SizePreviewContainer";
public string Name => PreviewName;
public ObservableCollection<ISizePreviewItem> Items { get; }
public ContainerSizeContainerPreview(IContainerSizeScanContainer container)
{
Items = container
.ChildContainers
.Ordering(c => c.Size)
.Taking(0, 10)
.Selecting();
}
}

View File

@@ -1,17 +0,0 @@
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner;
public class ContainerSizePreviewProvider : IItemPreviewProvider
{
public bool CanHandle(IItem item) => item is ContainerSizeScanContainer;
public Task<IItemPreviewViewModel> CreatePreviewAsync(IItem item)
{
if(item is not ContainerSizeScanContainer container) throw new NotSupportedException();
return Task.FromResult((IItemPreviewViewModel)new ContainerSizeContainerPreview(container));
}
}

View File

@@ -1,29 +0,0 @@
using System.Collections.ObjectModel;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.ContainerSizeScanner;
public record ContainerSizeScanElement(
string Name,
string DisplayName,
FullName FullName,
NativePath NativePath,
AbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
PointInTime PointInTime,
ObservableCollection<Exception> Exceptions,
ReadOnlyExtensionCollection Extensions) : IElement
{
public AbsolutePathType Type => AbsolutePathType.Element;
public IItem WithParent(AbsolutePath parent) => this with { Parent = parent };
}

View File

@@ -0,0 +1,17 @@
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner;
public class PreviewProvider : IItemPreviewProvider
{
public bool CanHandle(IItem item) => item is SizeScanContainer;
public Task<IItemPreviewViewModel> CreatePreviewAsync(IItem item)
{
if (item is not SizeScanContainer container) throw new NotSupportedException();
return Task.FromResult((IItemPreviewViewModel) new ContainerPreview(container));
}
}

View File

@@ -5,12 +5,13 @@ using DeclarativeProperty;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits;
using FileTime.Core.Timeline;
namespace FileTime.App.ContainerSizeScanner;
//TODO: create readonly version
public class ContainerSizeScanContainer : IContainerSizeScanContainer
public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer
{
private readonly ReadOnlyExtensionCollection _readOnlyExtensions;
private readonly BehaviorSubject<bool> _isLoading = new(false);
@@ -19,20 +20,20 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
public required string DisplayName { get; init; }
public required FullName? FullName { get; init; }
public required NativePath? NativePath { get; init; }
public AbsolutePath? Parent { get; init; }
public required AbsolutePath? Parent { get; init; }
public required IContentProvider Provider { get; init; }
public bool IsHidden => false;
public bool IsExists => true;
public DateTime? CreatedAt { get; }
public DateTime? CreatedAt { get; } = DateTime.Now;
public SupportsDelete CanDelete => SupportsDelete.True;
public bool CanRename => false;
public IContentProvider Provider { get; }
public string? Attributes => null;
public AbsolutePathType Type => AbsolutePathType.Container;
public PointInTime PointInTime => PointInTime.Present;
public ObservableCollection<Exception> Exceptions { get; } = new();
public ExtensionCollection Extensions { get; } = new();
ReadOnlyExtensionCollection IItem.Extensions => _readOnlyExtensions;
public IItem WithParent(AbsolutePath parent) => throw new NotImplementedException();
public IItem WithParent(AbsolutePath parent) => this with {Parent = parent};
public ObservableCollection<AbsolutePath> Items { get; } = new();
public IObservable<bool> IsLoading { get; }
@@ -41,18 +42,21 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
public bool AllowRecursiveDeletion => false;
public IDeclarativeProperty<long> Size => _size;
public ObservableCollection<IContainerSizeScanContainer> ChildContainers { get; } = new();
public IDeclarativeProperty<long> Size { get; }
public ObservableCollection<ISizeScanContainer> ChildContainers { get; } = new();
public ObservableCollection<ISizeScanElement> ChildElements { get; } = new();
public ObservableCollection<ISizeItem> SizeItems { get; } = new();
public required IContainer RealContainer { get; init; }
public ContainerSizeScanContainer(IContainerScanSnapshotProvider provider)
internal SizeScanContainer()
{
_readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions);
IsLoading = _isLoading.AsObservable();
_size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum()));
Size = _size.Debounce(TimeSpan.FromSeconds(1));
CreatedAt = DateTime.Now;
Provider = provider;
}
public async Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement)
@@ -71,4 +75,19 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
IsLoaded = true;
return Task.CompletedTask;
}
public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>();
public required SizeScanTask SizeScanTask { get; init; }
public Task<ContainerEscapeResult> HandleEsc()
{
if (!SizeScanTask.IsRunning)
{
return Task.FromResult(new ContainerEscapeResult(false));
}
SizeScanTask.Stop();
return Task.FromResult(new ContainerEscapeResult(true));
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.ObjectModel;
using DeclarativeProperty;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.ContainerSizeScanner;
public record SizeScanElement : ISizeScanElement
{
public required string Name { get; init; }
public required string DisplayName { get; init; }
public required FullName FullName { get; init; }
public required NativePath NativePath { get; init; }
public AbsolutePath? Parent { get; init; }
public required IDeclarativeProperty<long> Size { get; init; }
public bool IsHidden => false;
public bool IsExists => true;
public DateTime? CreatedAt { get; } = DateTime.Now;
public SupportsDelete CanDelete => SupportsDelete.False;
public bool CanRename => false;
public string? Attributes => "";
public required IContentProvider Provider { get; init; }
public PointInTime PointInTime { get; } = PointInTime.Present;
public ObservableCollection<Exception> Exceptions { get; } = new();
public ReadOnlyExtensionCollection Extensions { get; } = new();
public AbsolutePathType Type => AbsolutePathType.Element;
public IItem WithParent(AbsolutePath parent) => this with {Parent = parent};
}

View File

@@ -9,13 +9,19 @@ namespace FileTime.App.ContainerSizeScanner;
public class SizeScanTask : ISizeScanTask
{
private bool _cancelled;
private int _processedItems;
private ulong _processedItemsTotal;
private IContainer _scanSizeOf = null!;
private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ILogger<SizeScanTask> _logger;
private Thread? _sizeScanThread;
private static int _searchId = 1;
public IContainerSizeScanContainer SizeContainer { get; private set; } = null!;
private readonly DeclarativeProperty<string> _containerStatus = new();
private readonly IDeclarativeProperty<string> _containerStatusDebounced;
public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!;
public bool IsRunning { get; private set; }
public SizeScanTask(
IContainerScanSnapshotProvider containerScanSnapshotProvider,
@@ -25,21 +31,25 @@ public class SizeScanTask : ISizeScanTask
_containerScanSnapshotProvider = containerScanSnapshotProvider;
_timelessContentProvider = timelessContentProvider;
_logger = logger;
_containerStatusDebounced = _containerStatus.Debounce(TimeSpan.FromMilliseconds(250));
}
public void Init(IContainer scanSizeOf)
{
_scanSizeOf = scanSizeOf;
var name = $"{_searchId++}_{scanSizeOf.Name}";
var randomId = ContainerScanSnapshotProvider.ContentProviderName + Constants.SeparatorChar + name;
SizeContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider)
var randomId = ContainerScanProvider.ContentProviderName + Constants.SeparatorChar + name;
SizeSizeScanContainer = new SizeScanContainer
{
Name = name,
DisplayName = scanSizeOf.DisplayName,
FullName = new FullName(randomId),
NativePath = new NativePath(randomId),
Parent = new AbsolutePath(_timelessContentProvider, _containerScanSnapshotProvider),
RealContainer = scanSizeOf
RealContainer = scanSizeOf,
Provider = _containerScanSnapshotProvider,
Status = _containerStatusDebounced,
SizeScanTask = this
};
}
@@ -52,23 +62,34 @@ public class SizeScanTask : ISizeScanTask
_sizeScanThread = sizeScanThread;
}
public void Stop() => _cancelled = true;
private async void Run()
{
try
{
await TraverseTree(_scanSizeOf, SizeContainer);
IsRunning = true;
await SizeSizeScanContainer.StartLoadingAsync();
await TraverseTree(_scanSizeOf, SizeSizeScanContainer);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while scanning container {ContainerName}", _scanSizeOf.Name);
SizeSizeScanContainer.Exceptions.Add(ex);
}
IsRunning = false;
await SizeSizeScanContainer.StopLoadingAsync();
}
//TODO: make static
private async Task TraverseTree(
IContainer realContainer,
IContainerSizeScanContainer container)
ISizeScanContainer sizeScanContainer)
{
if(_cancelled) return;
await realContainer.WaitForLoaded();
var resolvedItems = new List<IItem>(realContainer.Items.Count);
foreach (var item in realContainer.Items)
{
@@ -78,34 +99,59 @@ public class SizeScanTask : ISizeScanTask
foreach (var element in resolvedItems.OfType<IElement>())
{
if(_cancelled) return;
var fileExtension = element.GetExtension<FileExtension>();
if (fileExtension?.Size is not { } size) continue;
var childName = container.FullName!.GetChild(element.Name).Path;
await container.AddSizeSourceAsync(new DeclarativeProperty<long>(size));
container.Items.Add(new AbsolutePath(
var sizeProperty = new DeclarativeProperty<long>(size);
var childName = sizeScanContainer.FullName!.GetChild(element.Name).Path;
await sizeScanContainer.AddSizeSourceAsync(sizeProperty);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider,
PointInTime.Present,
new FullName(childName),
AbsolutePathType.Element));
var childSearchContainer = new SizeScanElement
{
Name = element.Name,
DisplayName = element.DisplayName,
FullName = new FullName(childName),
NativePath = new NativePath(childName),
Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer),
Provider = _containerScanSnapshotProvider,
Size = sizeProperty
};
sizeScanContainer.SizeItems.Add(childSearchContainer);
_processedItems++;
_processedItemsTotal++;
}
foreach (var childContainer in resolvedItems.OfType<IContainer>())
{
var childName = container.FullName!.GetChild(childContainer.Name).Path;
var childSearchContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider)
if(_cancelled) return;
var childName = sizeScanContainer.FullName!.GetChild(childContainer.Name).Path;
var childSearchContainer = new SizeScanContainer
{
Name = childContainer.Name,
DisplayName = childContainer.DisplayName,
FullName = new FullName(childName),
NativePath = new NativePath(childName),
Parent = new AbsolutePath(_timelessContentProvider, container),
RealContainer = childContainer
Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer),
RealContainer = childContainer,
Provider = _containerScanSnapshotProvider,
Status = _containerStatusDebounced,
SizeScanTask = this
};
container.ChildContainers.Add(childSearchContainer);
await container.AddSizeSourceAsync(childSearchContainer.Size);
container.Items.Add(new AbsolutePath(
sizeScanContainer.ChildContainers.Add(childSearchContainer);
sizeScanContainer.SizeItems.Add(childSearchContainer);
await sizeScanContainer.AddSizeSourceAsync(childSearchContainer.Size);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider,
PointInTime.Present,
new FullName(childName),
@@ -113,5 +159,16 @@ public class SizeScanTask : ISizeScanTask
await TraverseTree(childContainer, childSearchContainer);
}
_processedItems++;
_processedItemsTotal++;
await _containerStatus.SetValue("Items processed: " + _processedItemsTotal);
if (_processedItems > 1000)
{
_processedItems = 0;
//Let some time for the UI to refresh
await Task.Delay(1000);
}
}
}

View File

@@ -9,10 +9,10 @@ public static class Startup
{
public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services)
{
services.TryAddSingleton<IContainerScanSnapshotProvider, ContainerScanSnapshotProvider>();
services.TryAddSingleton<IContainerScanSnapshotProvider, ContainerScanProvider>();
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<IContainerScanSnapshotProvider>());
services.AddTransient<ISizeScanTask, SizeScanTask>();
services.AddTransient<IItemPreviewProvider, ContainerSizePreviewProvider>();
services.AddTransient<IItemPreviewProvider, PreviewProvider>();
return services;
}
}