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);