diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/FileTime.App.ContainerSizeScanner.Abstractions.csproj b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/FileTime.App.ContainerSizeScanner.Abstractions.csproj
new file mode 100644
index 0000000..0cc7611
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/FileTime.App.ContainerSizeScanner.Abstractions.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net7.0
+ enable
+ enable
+ FileTime.App.ContainerSizeScanner
+
+
+
+
+
+
+
+
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerScanSnapshotProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerScanSnapshotProvider.cs
new file mode 100644
index 0000000..f5cb708
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerScanSnapshotProvider.cs
@@ -0,0 +1,9 @@
+using FileTime.Core.ContentAccess;
+using FileTime.Core.Models;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public interface IContainerScanSnapshotProvider : IContentProvider
+{
+ ISizeScanTask StartSizeScan(IContainer scanSizeOf);
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs
new file mode 100644
index 0000000..1b972cc
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs
@@ -0,0 +1,13 @@
+using System.Collections.ObjectModel;
+using DeclarativeProperty;
+using FileTime.Core.Models;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public interface IContainerSizeScanContainer : ISizeItem, IContainer
+{
+ public Task AddSizeSourceAsync(IDeclarativeProperty sizeElement);
+ ObservableCollection ChildContainers { get; }
+ IContainer RealContainer { get; init; }
+ IDeclarativeProperty Size { get; }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs
new file mode 100644
index 0000000..cd06cb9
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs
@@ -0,0 +1,8 @@
+using FileTime.Core.Models;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public interface ISizeItem : IItem
+{
+
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs
new file mode 100644
index 0000000..9941462
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs
@@ -0,0 +1,8 @@
+using FileTime.App.Core.ViewModels.ItemPreview;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public interface ISizePreviewItem : IItemPreviewViewModel
+{
+
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs
new file mode 100644
index 0000000..56e3693
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs
@@ -0,0 +1,10 @@
+using FileTime.Core.Models;
+using InitableService;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public interface ISizeScanTask : IInitable
+{
+ IContainerSizeScanContainer SizeContainer { get; }
+ void Start();
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs
new file mode 100644
index 0000000..0b5ebf0
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs
@@ -0,0 +1,113 @@
+using FileTime.App.Core.Exceptions;
+using FileTime.Core.ContentAccess;
+using FileTime.Core.Enums;
+using FileTime.Core.Models;
+using FileTime.Core.Timeline;
+using InitableService;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScanSnapshotProvider
+{
+ private readonly ITimelessContentProvider _timelessContentProvider;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly List _sizeScanTasks = new();
+ internal const string ContentProviderName = "container-size-scan";
+
+ public ContainerScanSnapshotProvider(
+ ITimelessContentProvider timelessContentProvider,
+ IServiceProvider serviceProvider)
+ : base(ContentProviderName, timelessContentProvider)
+ {
+ _timelessContentProvider = timelessContentProvider;
+ _serviceProvider = serviceProvider;
+ }
+
+ public override async Task GetItemByFullNameAsync(
+ FullName fullName,
+ PointInTime pointInTime,
+ bool forceResolve = false,
+ AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
+ ItemInitializationSettings itemInitializationSettings = null
+ )
+ {
+ if (fullName.Path == ContentProviderName)
+ return this;
+
+ var pathParts = fullName.Path.Split(Constants.SeparatorChar);
+
+ var item = _sizeScanTasks.FirstOrDefault(t => t.SizeContainer.Name == pathParts[1])?.SizeContainer;
+
+ if (pathParts.Length == 2)
+ return item ?? throw new ItemNotFoundException(fullName);
+
+ for (var i = 2; i < pathParts.Length - 1 && item != null; i++)
+ {
+ var childName = pathParts[i];
+ item = item.ChildContainers.FirstOrDefault(c => c.Name == childName);
+ }
+
+ if (item is not null)
+ {
+ var container = item.ChildContainers.FirstOrDefault(c => c.Name == pathParts[^1]);
+ if (container is not null) return container;
+
+ var childName = item.RealContainer.FullName?.GetChild(pathParts[^1]);
+ if (childName is null) throw new ItemNotFoundException(fullName);
+
+ return await _timelessContentProvider.GetItemByFullNameAsync(
+ childName,
+ pointInTime,
+ forceResolve,
+ forceResolvePathType,
+ itemInitializationSettings
+ );
+ }
+
+ throw new ItemNotFoundException(fullName);
+ }
+
+ public override async Task GetItemByNativePathAsync(
+ NativePath nativePath,
+ PointInTime pointInTime,
+ bool forceResolve = false,
+ AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
+ ItemInitializationSettings itemInitializationSettings = default
+ ) =>
+ await GetItemByFullNameAsync(
+ new FullName(nativePath.Path),
+ pointInTime,
+ forceResolve,
+ forceResolvePathType,
+ itemInitializationSettings
+ );
+
+ public override NativePath GetNativePath(FullName fullName)
+ => new(fullName.Path);
+
+ public override FullName GetFullName(NativePath nativePath)
+ => new(nativePath.Path);
+
+ public override Task GetContentAsync(
+ IElement element,
+ int? maxLength = null,
+ CancellationToken cancellationToken = default)
+ //TODO read from original source
+ => Task.FromResult((byte[]?) null);
+
+ public override bool CanHandlePath(NativePath path)
+ => path.Path.StartsWith(ContentProviderName);
+
+ public ISizeScanTask StartSizeScan(IContainer scanSizeOf)
+ {
+ var searchTask = _serviceProvider
+ .GetInitableResolver(scanSizeOf)
+ .GetRequiredService();
+
+ _sizeScanTasks.Add(searchTask);
+ searchTask.Start();
+ Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer));
+
+ return searchTask;
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs
new file mode 100644
index 0000000..5a22f63
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs
@@ -0,0 +1,22 @@
+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 Items { get; }
+
+ public ContainerSizeContainerPreview(IContainerSizeScanContainer container)
+ {
+ Items = container
+ .ChildContainers
+ .Ordering(c => c.Size)
+ .Taking(0, 10)
+ .Selecting();
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs
new file mode 100644
index 0000000..9a9feab
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs
@@ -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 ContainerSizePreviewProvider : IItemPreviewProvider
+{
+ public bool CanHandle(IItem item) => item is ContainerSizeScanContainer;
+
+ public Task CreatePreviewAsync(IItem item)
+ {
+ if(item is not ContainerSizeScanContainer container) throw new NotSupportedException();
+
+ return Task.FromResult((IItemPreviewViewModel)new ContainerSizeContainerPreview(container));
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs
new file mode 100644
index 0000000..6140a29
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs
@@ -0,0 +1,74 @@
+using System.Collections.ObjectModel;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using DeclarativeProperty;
+using FileTime.Core.ContentAccess;
+using FileTime.Core.Enums;
+using FileTime.Core.Models;
+using FileTime.Core.Timeline;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+//TODO: create readonly version
+public class ContainerSizeScanContainer : IContainerSizeScanContainer
+{
+ private readonly ReadOnlyExtensionCollection _readOnlyExtensions;
+ private readonly BehaviorSubject _isLoading = new(false);
+ private readonly CombineProperty _size;
+ 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 bool IsHidden => false;
+ public bool IsExists => true;
+ public DateTime? CreatedAt { get; }
+ 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 Exceptions { get; } = new();
+ public ExtensionCollection Extensions { get; } = new();
+ ReadOnlyExtensionCollection IItem.Extensions => _readOnlyExtensions;
+ public IItem WithParent(AbsolutePath parent) => throw new NotImplementedException();
+
+ public ObservableCollection Items { get; } = new();
+ public IObservable IsLoading { get; }
+ public bool? IsLoaded { get; private set; }
+ public Task WaitForLoaded(CancellationToken token = default) => throw new NotImplementedException();
+
+ public bool AllowRecursiveDeletion => false;
+
+ public IDeclarativeProperty Size => _size;
+ public ObservableCollection ChildContainers { get; } = new();
+ public required IContainer RealContainer { get; init; }
+
+ public ContainerSizeScanContainer(IContainerScanSnapshotProvider provider)
+ {
+ _readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions);
+ IsLoading = _isLoading.AsObservable();
+
+ _size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum()));
+ CreatedAt = DateTime.Now;
+ Provider = provider;
+ }
+
+ public async Task AddSizeSourceAsync(IDeclarativeProperty sizeElement)
+ => await _size.AddSourceAsync(sizeElement);
+
+ public Task StartLoadingAsync()
+ {
+ _isLoading.OnNext(true);
+ IsLoaded = false;
+ return Task.CompletedTask;
+ }
+
+ public Task StopLoadingAsync()
+ {
+ _isLoading.OnNext(false);
+ IsLoaded = true;
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs
new file mode 100644
index 0000000..1fb0597
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs
@@ -0,0 +1,29 @@
+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 Exceptions,
+ ReadOnlyExtensionCollection Extensions) : IElement
+{
+ public AbsolutePathType Type => AbsolutePathType.Element;
+
+ public IItem WithParent(AbsolutePath parent) => this with { Parent = parent };
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/FileTime.App.ContainerSizeScanner.csproj b/src/AppCommon/FileTime.App.ContainerSizeScanner/FileTime.App.ContainerSizeScanner.csproj
new file mode 100644
index 0000000..5c2b188
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/FileTime.App.ContainerSizeScanner.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs
new file mode 100644
index 0000000..df7a78b
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs
@@ -0,0 +1,117 @@
+using DeclarativeProperty;
+using FileTime.Core.Enums;
+using FileTime.Core.Models;
+using FileTime.Core.Models.Extensions;
+using FileTime.Core.Timeline;
+using Microsoft.Extensions.Logging;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public class SizeScanTask : ISizeScanTask
+{
+ private IContainer _scanSizeOf = null!;
+ private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider;
+ private readonly ITimelessContentProvider _timelessContentProvider;
+ private readonly ILogger _logger;
+ private Thread? _sizeScanThread;
+ private static int _searchId = 1;
+ public IContainerSizeScanContainer SizeContainer { get; private set; } = null!;
+
+ public SizeScanTask(
+ IContainerScanSnapshotProvider containerScanSnapshotProvider,
+ ITimelessContentProvider timelessContentProvider,
+ ILogger logger)
+ {
+ _containerScanSnapshotProvider = containerScanSnapshotProvider;
+ _timelessContentProvider = timelessContentProvider;
+ _logger = logger;
+ }
+
+ public void Init(IContainer scanSizeOf)
+ {
+ _scanSizeOf = scanSizeOf;
+ var name = $"{_searchId++}_{scanSizeOf.Name}";
+ var randomId = ContainerScanSnapshotProvider.ContentProviderName + Constants.SeparatorChar + name;
+ SizeContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider)
+ {
+ Name = name,
+ DisplayName = scanSizeOf.DisplayName,
+ FullName = new FullName(randomId),
+ NativePath = new NativePath(randomId),
+ Parent = new AbsolutePath(_timelessContentProvider, _containerScanSnapshotProvider),
+ RealContainer = scanSizeOf
+ };
+ }
+
+ public void Start()
+ {
+ if (_sizeScanThread != null) return;
+
+ var sizeScanThread = new Thread(Run);
+ sizeScanThread.Start();
+ _sizeScanThread = sizeScanThread;
+ }
+
+ private async void Run()
+ {
+ try
+ {
+ await TraverseTree(_scanSizeOf, SizeContainer);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error while scanning container {ContainerName}", _scanSizeOf.Name);
+ }
+ }
+
+ //TODO: make static
+ private async Task TraverseTree(
+ IContainer realContainer,
+ IContainerSizeScanContainer container)
+ {
+ var resolvedItems = new List(realContainer.Items.Count);
+ foreach (var item in realContainer.Items)
+ {
+ var resolvedItem = await item.ResolveAsync();
+ resolvedItems.Add(resolvedItem);
+ }
+
+ foreach (var element in resolvedItems.OfType())
+ {
+ var fileExtension = element.GetExtension();
+ if (fileExtension?.Size is not { } size) continue;
+
+ var childName = container.FullName!.GetChild(element.Name).Path;
+ await container.AddSizeSourceAsync(new DeclarativeProperty(size));
+ container.Items.Add(new AbsolutePath(
+ _timelessContentProvider,
+ PointInTime.Present,
+ new FullName(childName),
+ AbsolutePathType.Element));
+ }
+
+ foreach (var childContainer in resolvedItems.OfType())
+ {
+ var childName = container.FullName!.GetChild(childContainer.Name).Path;
+ var childSearchContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider)
+ {
+ Name = childContainer.Name,
+ DisplayName = childContainer.DisplayName,
+ FullName = new FullName(childName),
+ NativePath = new NativePath(childName),
+ Parent = new AbsolutePath(_timelessContentProvider, container),
+ RealContainer = childContainer
+ };
+
+ container.ChildContainers.Add(childSearchContainer);
+ await container.AddSizeSourceAsync(childSearchContainer.Size);
+ container.Items.Add(new AbsolutePath(
+ _timelessContentProvider,
+ PointInTime.Present,
+ new FullName(childName),
+ AbsolutePathType.Container));
+
+ await TraverseTree(childContainer, childSearchContainer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs
new file mode 100644
index 0000000..c967b08
--- /dev/null
+++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs
@@ -0,0 +1,18 @@
+using FileTime.App.Core.Services;
+using FileTime.Core.ContentAccess;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace FileTime.App.ContainerSizeScanner;
+
+public static class Startup
+{
+ public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ services.AddSingleton(sp => sp.GetRequiredService());
+ services.AddTransient();
+ services.AddTransient();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewProvider.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewProvider.cs
new file mode 100644
index 0000000..6afbd30
--- /dev/null
+++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewProvider.cs
@@ -0,0 +1,10 @@
+using FileTime.App.Core.ViewModels.ItemPreview;
+using FileTime.Core.Models;
+
+namespace FileTime.App.Core.Services;
+
+public interface IItemPreviewProvider
+{
+ bool CanHandle(IItem item);
+ Task CreatePreviewAsync(IItem item);
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/ScanSizeCommand.cs b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/ScanSizeCommand.cs
new file mode 100644
index 0000000..aa76b1a
--- /dev/null
+++ b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/ScanSizeCommand.cs
@@ -0,0 +1,14 @@
+namespace FileTime.App.Core.UserCommand;
+
+public sealed class ScanSizeCommand : IIdentifiableUserCommand
+{
+ public const string ScanSizeCommandName = "scan_size";
+ public static readonly ScanSizeCommand Instance = new();
+
+ private ScanSizeCommand()
+ {
+ }
+
+ public string UserCommandID => ScanSizeCommandName;
+ public string Title => "Scan size";
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs
new file mode 100644
index 0000000..7089203
--- /dev/null
+++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs
@@ -0,0 +1,8 @@
+using FileTime.App.Core.Models;
+
+namespace FileTime.App.Core.ViewModels.ItemPreview;
+
+public interface IElementPreviewViewModel : IItemPreviewViewModel
+{
+ ItemPreviewMode Mode { get; }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs
index 863fd0c..71a0a92 100644
--- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs
+++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs
@@ -1,8 +1,6 @@
-using FileTime.App.Core.Models;
-
namespace FileTime.App.Core.ViewModels.ItemPreview;
public interface IItemPreviewViewModel
{
- ItemPreviewMode Mode { get; }
+ string Name { get; }
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj
index 12f9db2..60814e7 100644
--- a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj
+++ b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/AppCommon/FileTime.App.Core/Services/ElementPreviewProvider.cs b/src/AppCommon/FileTime.App.Core/Services/ElementPreviewProvider.cs
new file mode 100644
index 0000000..aef75de
--- /dev/null
+++ b/src/AppCommon/FileTime.App.Core/Services/ElementPreviewProvider.cs
@@ -0,0 +1,26 @@
+using FileTime.App.Core.ViewModels.ItemPreview;
+using FileTime.Core.Models;
+using InitableService;
+
+namespace FileTime.App.Core.Services;
+
+public class ElementPreviewProvider : IItemPreviewProvider
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ public ElementPreviewProvider(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public bool CanHandle(IItem item) => item is IElement;
+
+ public async Task CreatePreviewAsync(IItem item)
+ {
+ if (item is not IElement element) throw new NotSupportedException();
+
+ return await _serviceProvider
+ .GetAsyncInitableResolver(element)
+ .GetRequiredServiceAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs
index 7b9fa0a..c4a208f 100644
--- a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs
+++ b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs
@@ -9,14 +9,21 @@ namespace FileTime.App.Core.Services;
public class ItemPreviewService : IItemPreviewService
{
private readonly IServiceProvider _serviceProvider;
+ private readonly IEnumerable _itemPreviewProviders;
public IObservable ItemPreview { get; }
- public ItemPreviewService(IAppState appState, IServiceProvider serviceProvider)
+ public ItemPreviewService(
+ IAppState appState,
+ IServiceProvider serviceProvider,
+ IEnumerable itemPreviewProviders)
{
_serviceProvider = serviceProvider;
+ _itemPreviewProviders = itemPreviewProviders;
ItemPreview = appState
.SelectedTab
- .Select(t => t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)) ?? Observable.Return(null))
+ .Select(t =>
+ t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250))
+ ?? Observable.Return(null))
.Switch()
.Select(item =>
item == null
@@ -30,11 +37,12 @@ public class ItemPreviewService : IItemPreviewService
private async Task Map(IItemViewModel itemViewModel)
{
- return itemViewModel.BaseItem switch
- {
- IElement element => await _serviceProvider.GetAsyncInitableResolver(element)
- .GetRequiredServiceAsync(),
- _ => null
- };
+ ArgumentNullException.ThrowIfNull(itemViewModel.BaseItem);
+
+ var itemPreviewProvider = _itemPreviewProviders.FirstOrDefault(p => p.CanHandle(itemViewModel.BaseItem));
+
+ return itemPreviewProvider is null
+ ? null
+ : await itemPreviewProvider.CreatePreviewAsync(itemViewModel.BaseItem);
}
}
\ No newline at end of file
diff --git a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs
index 65277d2..55c7b55 100644
--- a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs
+++ b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs
@@ -18,7 +18,8 @@ public class TabPersistenceService : ITabPersistenceService
//TODO: make this a configuration maybe?
private readonly List _contentProvidersNotToRestore = new()
{
- "search"
+ "search",
+ "container-size-scan"
};
private record PersistenceRoot(TabStates? TabStates);
diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs
index 3df9ca0..3535626 100644
--- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs
+++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs
@@ -1,8 +1,10 @@
using System.Diagnostics;
using DeclarativeProperty;
+using FileTime.App.ContainerSizeScanner;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;
using FileTime.App.Search;
+using FileTime.Core.Command;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Interactions;
@@ -20,6 +22,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IUserCommandHandlerService _userCommandHandlerService;
private readonly IContentAccessorFactory _contentAccessorFactory;
+ private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider;
private IDeclarativeProperty? _currentLocation;
private IDeclarativeProperty? _currentSelectedItem;
private ITabViewModel? _currentSelectedTab;
@@ -32,7 +35,8 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
IItemNameConverterService itemNameConverterService,
ITimelessContentProvider timelessContentProvider,
IUserCommandHandlerService userCommandHandlerService,
- IContentAccessorFactory contentAccessorFactory) : base(appState)
+ IContentAccessorFactory contentAccessorFactory,
+ IContainerScanSnapshotProvider containerScanSnapshotProvider) : base(appState)
{
_systemClipboardService = systemClipboardService;
_userCommunicationService = userCommunicationService;
@@ -41,6 +45,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
_timelessContentProvider = timelessContentProvider;
_userCommandHandlerService = userCommandHandlerService;
_contentAccessorFactory = contentAccessorFactory;
+ _containerScanSnapshotProvider = containerScanSnapshotProvider;
SaveCurrentLocation(l => _currentLocation = l);
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
SaveSelectedTab(t => _currentSelectedTab = t);
@@ -51,10 +56,20 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
new TypeUserCommandHandler(CopyNativePath),
new TypeUserCommandHandler(CopyBase64),
new TypeUserCommandHandler(Search),
+ new TypeUserCommandHandler(ScanSize),
new TypeUserCommandHandler(SortItems),
});
}
+ private async Task ScanSize()
+ {
+ if (_currentLocation?.Value is null) return;
+
+ var searchTask = _containerScanSnapshotProvider.StartSizeScan(_currentLocation.Value);
+ var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer));
+ await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
+ }
+
private async Task SortItems(SortItemsCommand sortItemsCommand)
{
if (_currentSelectedTab is null) return;
diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs
index 095d4c5..95dee30 100644
--- a/src/AppCommon/FileTime.App.Core/Startup.cs
+++ b/src/AppCommon/FileTime.App.Core/Startup.cs
@@ -27,6 +27,7 @@ public static class Startup
serviceCollection.TryAddSingleton();
serviceCollection.TryAddSingleton();
serviceCollection.TryAddSingleton();
+ serviceCollection.TryAddSingleton();
return serviceCollection
.AddCommandHandlers()
diff --git a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs
index e996894..f1f9742 100644
--- a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs
+++ b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs
@@ -48,6 +48,7 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
AddUserCommand(RefreshCommand.Instance);
AddUserCommand(RenameCommand.Instance);
AddUserCommand(RunOrOpenCommand.Instance);
+ AddUserCommand(ScanSizeCommand.Instance);
AddUserCommand(StartCommandSchedulerCommand.Instance);
AddUserCommand(SortItemsCommand.OrderByNameCommand);
AddUserCommand(SortItemsCommand.OrderByNameDescCommand);
diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs
index 77a4a23..e2d97c2 100644
--- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs
+++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs
@@ -7,13 +7,14 @@ using MvvmGen;
namespace FileTime.App.Core.ViewModels.ItemPreview;
[ViewModel]
-public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable
+public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncInitable
{
+ public const string PreviewName = "ElementPreview";
private record EncodingResult(char BinaryChar, string PartialResult);
private const int MaxTextPreviewSize = 1024 * 1024;
- private static readonly List _encodings = new()
+ private static readonly List Encodings = new()
{
Encoding.UTF8,
Encoding.Unicode,
@@ -27,6 +28,8 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit
[Property] private string? _textContent;
[Property] private string? _textEncoding;
+ public string Name => PreviewName;
+
public async Task InitAsync(IElement element)
{
try
@@ -59,7 +62,7 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit
(string, Encoding?) GetNormalizedText(byte[] data)
{
var binaryCharacter = new Dictionary();
- foreach (var encoding in _encodings)
+ foreach (var encoding in Encodings)
{
var text = encoding.GetString(data);
var binary = false;
diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs
index b1b457c..85bb57c 100644
--- a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs
+++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs
@@ -1,7 +1,6 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
-using DynamicData;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
diff --git a/src/Core/FileTime.Core.Models/Element.cs b/src/Core/FileTime.Core.Models/Element.cs
index f2854eb..f25a8a9 100644
--- a/src/Core/FileTime.Core.Models/Element.cs
+++ b/src/Core/FileTime.Core.Models/Element.cs
@@ -1,5 +1,4 @@
using System.Collections.ObjectModel;
-using DynamicData;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Timeline;
diff --git a/src/FileTime.sln b/src/FileTime.sln
index ddb2064..ab0553d 100644
--- a/src/FileTime.sln
+++ b/src/FileTime.sln
@@ -111,6 +111,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.Compression"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.Compression.Core", "Tools\FileTime.Tools.Compression.Core\FileTime.Tools.Compression.Core.csproj", "{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.ContainerSizeScanner", "AppCommon\FileTime.App.ContainerSizeScanner\FileTime.App.ContainerSizeScanner.csproj", "{E5FD38ED-6E4B-42AA-850B-470B939B836B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.ContainerSizeScanner.Abstractions", "AppCommon\FileTime.App.ContainerSizeScanner.Abstractions\FileTime.App.ContainerSizeScanner.Abstractions.csproj", "{826AFD32-E36B-48BA-BC1E-1476B393CF24}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -297,6 +301,14 @@ Global
{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E5FD38ED-6E4B-42AA-850B-470B939B836B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E5FD38ED-6E4B-42AA-850B-470B939B836B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E5FD38ED-6E4B-42AA-850B-470B939B836B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E5FD38ED-6E4B-42AA-850B-470B939B836B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {826AFD32-E36B-48BA-BC1E-1476B393CF24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {826AFD32-E36B-48BA-BC1E-1476B393CF24}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {826AFD32-E36B-48BA-BC1E-1476B393CF24}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {826AFD32-E36B-48BA-BC1E-1476B393CF24}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -348,6 +360,8 @@ Global
{9062F7D2-34DE-44B7-A2D6-8B3AFDEBB606} = {778AAF38-20FF-438C-A9C3-60850C8B5A27}
{58243F14-15A6-4601-A071-F99BE896D027} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
{EE1721A0-D15A-4E40-BEA5-8AB6BAB8FD44} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
+ {E5FD38ED-6E4B-42AA-850B-470B939B836B} = {A5291117-3001-498B-AC8B-E14F71F72570}
+ {826AFD32-E36B-48BA-BC1E-1476B393CF24} = {A5291117-3001-498B-AC8B-E14F71F72570}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs
index d47b0bf..65a7d68 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs
@@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using FileTime.App.CommandPalette;
+using FileTime.App.ContainerSizeScanner;
using FileTime.App.DependencyInjection;
using FileTime.App.FrequencyNavigation;
using FileTime.App.Search;
@@ -25,6 +26,7 @@ public class App : Application
.AddServerCoreServices()
.AddFrequencyNavigation()
.AddCommandPalette()
+ .AddContainerSizeScanner()
.AddSearch()
.AddCompression()
.AddConfiguration(configuration)
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj
index 75465ef..1410edf 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj
@@ -41,6 +41,7 @@
+
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj
index a7224bb..5289f68 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj
@@ -39,6 +39,7 @@
+
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml
index d4053eb..d17ba6c 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml
@@ -8,40 +8,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
index 4bb721e..65d3163 100644
--- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
+++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml
@@ -27,8 +27,10 @@
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
xmlns:interactions="using:FileTime.Core.Interactions"
+ xmlns:itemPreview="clr-namespace:FileTime.App.Core.ViewModels.ItemPreview;assembly=FileTime.App.Core"
xmlns:local="using:FileTime.GuiApp.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:sizePreview="clr-namespace:FileTime.App.ContainerSizeScanner;assembly=FileTime.App.ContainerSizeScanner"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -45,8 +47,7 @@
-
+
@@ -60,10 +61,8 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
@@ -511,14 +504,11 @@
Width="1" />
-
-
+
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Items="{Binding Items^}"
+ x:CompileBindings="False">
+
+
+
+
+
+
+
+
+
@@ -654,9 +719,7 @@
-
+
-
+
@@ -702,8 +764,7 @@
-
+
@@ -772,10 +833,8 @@
-
-
+
+
@@ -787,13 +846,11 @@
-
+
-
+
@@ -801,8 +858,7 @@
-
+
@@ -908,7 +964,7 @@
HorizontalAlignment="Stretch"
IsVisible="{Binding ShowWindow^, FallbackValue=False}"
VerticalAlignment="Stretch">
-
+
@@ -919,7 +975,7 @@
HorizontalAlignment="Stretch"
IsVisible="{Binding ShowWindow^, FallbackValue=False}"
VerticalAlignment="Stretch">
-
+
diff --git a/src/Library/DeclarativeProperty/CombineProperty.cs b/src/Library/DeclarativeProperty/CombineProperty.cs
index 6083395..857e01e 100644
--- a/src/Library/DeclarativeProperty/CombineProperty.cs
+++ b/src/Library/DeclarativeProperty/CombineProperty.cs
@@ -10,7 +10,7 @@ public sealed class CombineProperty : DeclarativePropertyBase
_combiner = combiner;
}
- public async Task AddSource(IDeclarativeProperty source)
+ public async Task AddSourceAsync(IDeclarativeProperty source)
{
if (_sourceProperties.Contains(source)) return;
_sourceProperties.Add(source);