Recursive container size scan
This commit is contained in:
@@ -44,6 +44,7 @@ namespace FileTime.App.Core.Command
|
||||
Refresh,
|
||||
Rename,
|
||||
RunCommand,
|
||||
ScanContainerSize,
|
||||
ShowAllShotcut,
|
||||
SoftDelete,
|
||||
SwitchToLastTab,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FileTime.App.Core.Clipboard;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.CommandHandlers;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.Timeline;
|
||||
@@ -22,6 +23,8 @@ namespace FileTime.App.Core
|
||||
.AddSingleton<TopContainer>()
|
||||
.AddSingleton<CommandExecutor>()
|
||||
.AddSingleton<TimeRunner>()
|
||||
.AddSingleton<ContainerScanSnapshotProvider>()
|
||||
.AddSingleton<IContentProvider, ContainerScanSnapshotProvider>(p => p.GetService<ContainerScanSnapshotProvider>() ?? throw new ArgumentException(nameof(ContainerScanSnapshotProvider) + " is not registered"))
|
||||
.AddSingleton<ItemNameConverterService>()
|
||||
.AddLocalServices()
|
||||
.AddSmbServices()
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
List<Func<TSender, TArg, CancellationToken, Task>>? handlers;
|
||||
lock (_guard)
|
||||
{
|
||||
handlers = _handlers;
|
||||
handlers = new List<Func<TSender, TArg, CancellationToken, Task>>(_handlers);
|
||||
}
|
||||
|
||||
foreach (var handler in handlers)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerScanSnapshotProvider : ContentProviderBase<ContainerScanSnapshotProvider>
|
||||
{
|
||||
|
||||
public ContainerScanSnapshotProvider() : base("size", null, "size://", false)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public async Task AddSnapshotAsync(ContainerSizeContainer snapshot)
|
||||
{
|
||||
if (RootContainers != null)
|
||||
{
|
||||
RootContainers.Add(snapshot);
|
||||
while (RootContainers.Count > 10)
|
||||
{
|
||||
RootContainers.RemoveAt(0);
|
||||
}
|
||||
await RefreshAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Providers.ContainerProperty;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerSizeContainer : LazyLoadingContainer<ContainerScanSnapshotProvider, ContainerSizeContainer, ContainerSizeElement>, IItemWithSize, IHaveCreatedAt, IHaveAttributes
|
||||
{
|
||||
public override bool IsExists => true;
|
||||
|
||||
public long? Size { get; private set; }
|
||||
public AsyncEventHandler<long?> SizeChanged { get; } = new();
|
||||
|
||||
public bool AllowSizeScan { get; private set; } = true;
|
||||
|
||||
private readonly IContainer _baseContainer;
|
||||
|
||||
public string Attributes => _baseContainer is IHaveAttributes haveAttributes ? haveAttributes.Attributes : "";
|
||||
|
||||
public DateTime? CreatedAt => _baseContainer is IHaveCreatedAt haveCreatedAt ? haveCreatedAt.CreatedAt : null;
|
||||
|
||||
public ContainerSizeContainer(ContainerScanSnapshotProvider provider, IContainer parent, IContainer baseContainer, string? displayName = null) : base(provider, parent, baseContainer.Name)
|
||||
{
|
||||
_baseContainer = baseContainer;
|
||||
AllowRecursiveDeletion = false;
|
||||
CanHandleEscape = true;
|
||||
if (displayName != null)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => throw new NotImplementedException();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public override async Task AddContainerAsync(ContainerSizeContainer container)
|
||||
{
|
||||
await base.AddContainerAsync(container);
|
||||
container.SizeChanged.Add(ChildContainerSizeChanged);
|
||||
}
|
||||
|
||||
public override async Task AddElementAsync(ContainerSizeElement element)
|
||||
{
|
||||
await base.AddElementAsync(element);
|
||||
}
|
||||
|
||||
public IEnumerable<IItemWithSize> GetItemsWithSize() => Containers.Cast<IItemWithSize>().Concat(Elements);
|
||||
|
||||
private async Task ChildContainerSizeChanged(object? sender, long? size, CancellationToken token) => await UpdateSize();
|
||||
|
||||
public async Task UpdateSize()
|
||||
{
|
||||
Size = Containers.Aggregate(0L, (sum, c) => sum + c.Size ?? 0)
|
||||
+ Elements.Aggregate(0L, (sum, e) => sum + e.Size);
|
||||
|
||||
await SizeChanged.InvokeAsync(this, Size);
|
||||
}
|
||||
|
||||
public override Task<ContainerEscapeResult> HandleEscape()
|
||||
{
|
||||
if (AllowSizeScan)
|
||||
{
|
||||
AllowSizeScan = false;
|
||||
return Task.FromResult(new ContainerEscapeResult(true));
|
||||
}
|
||||
|
||||
return Task.FromResult(new ContainerEscapeResult(_baseContainer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ContainerSizeElement : AbstractElement<ContainerScanSnapshotProvider>, IItemWithSize, IFile
|
||||
{
|
||||
private readonly IElement _element;
|
||||
|
||||
public long Size { get; }
|
||||
|
||||
long? IItemWithSize.Size => Size;
|
||||
|
||||
public string Attributes => _element is IFile file ? file.Attributes : _element.GetType().Name.Split('.').Last();
|
||||
|
||||
public DateTime CreatedAt => _element is IFile file ? file.CreatedAt : DateTime.MinValue;
|
||||
|
||||
public ContainerSizeElement(ContainerScanSnapshotProvider provider, IContainer parent, IElement element, long size) : base(provider, parent, element.Name)
|
||||
{
|
||||
Size = size;
|
||||
CanDelete = SupportsDelete.False;
|
||||
_element = element;
|
||||
}
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<string> GetContent(CancellationToken token = default) => Task.FromResult("NotImplementedException");
|
||||
|
||||
public override Task<IContentReader> GetContentReaderAsync() => throw new NotSupportedException();
|
||||
|
||||
public override Task<IContentWriter> GetContentWriterAsync() => throw new NotSupportedException();
|
||||
|
||||
public override Task<long?> GetElementSize(CancellationToken token = default) => Task.FromResult((long?)Size);
|
||||
|
||||
public override string GetPrimaryAttributeText() => "";
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
128
src/Core/FileTime.Core/ContainerSizeScanner/ScanSizeTask.cs
Normal file
128
src/Core/FileTime.Core/ContainerSizeScanner/ScanSizeTask.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.ContainerSizeScanner
|
||||
{
|
||||
public class ScanSizeTask
|
||||
{
|
||||
private readonly object _scanGuard = new();
|
||||
private bool _scannig;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public IContainer ContainerToScan { get; }
|
||||
public ContainerSizeContainer Snapshot { get; }
|
||||
|
||||
public bool Scanning
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
return _scannig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ScanSizeTask(ContainerScanSnapshotProvider provider, IContainer containerToScan)
|
||||
{
|
||||
ContainerToScan = containerToScan;
|
||||
Snapshot = new ContainerSizeContainer(provider, provider, containerToScan, "Size scan on " + containerToScan.DisplayName);
|
||||
Task.Run(async () => await provider.AddSnapshotAsync(Snapshot)).Wait();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
if (_scannig) return;
|
||||
_scannig = true;
|
||||
}
|
||||
|
||||
new Thread(BootstrapScan).Start();
|
||||
|
||||
void BootstrapScan()
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(async () => await Snapshot.RunWithLazyLoading(async (token) => await ScanAsync(ContainerToScan, Snapshot, token))).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
_scannig = false;
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanAsync(IContainer container, ContainerSizeContainer targetContainer, CancellationToken token)
|
||||
{
|
||||
if (IsScanCancelled(targetContainer)) return;
|
||||
|
||||
var childElements = await container.GetElements(token);
|
||||
if (childElements != null)
|
||||
{
|
||||
foreach (var childElement in childElements)
|
||||
{
|
||||
if (token.IsCancellationRequested
|
||||
|| IsScanCancelled(targetContainer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var newSizeElement = new ContainerSizeElement(targetContainer.Provider, targetContainer, childElement, await childElement.GetElementSize(token) ?? 0);
|
||||
await targetContainer.AddElementAsync(newSizeElement);
|
||||
}
|
||||
}
|
||||
|
||||
var childContainers = await container.GetContainers(token);
|
||||
if (childContainers != null)
|
||||
{
|
||||
var newSizeContainers = new List<(ContainerSizeContainer, IContainer)>();
|
||||
foreach (var childContainer in childContainers)
|
||||
{
|
||||
var newSizeContainer = new ContainerSizeContainer(targetContainer.Provider, targetContainer, childContainer);
|
||||
await targetContainer.AddContainerAsync(newSizeContainer);
|
||||
newSizeContainers.Add((newSizeContainer, childContainer));
|
||||
}
|
||||
|
||||
foreach (var (newSizeContainer, childContainer) in newSizeContainers)
|
||||
{
|
||||
if (token.IsCancellationRequested
|
||||
|| IsScanCancelled(newSizeContainer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
await newSizeContainer.RunWithLazyLoading(async (token) => await ScanAsync(childContainer, newSizeContainer, token), token);
|
||||
}
|
||||
}
|
||||
|
||||
await targetContainer.UpdateSize();
|
||||
}
|
||||
|
||||
private static bool IsScanCancelled(ContainerSizeContainer container)
|
||||
{
|
||||
IContainer? parent = container;
|
||||
while (parent is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
if (!sizeContainer.AllowSizeScan)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
parent = parent.GetParent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
lock (_scanGuard)
|
||||
{
|
||||
if (_scannig || _cancellationTokenSource == null) return;
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Core/FileTime.Core/Models/IItemWithSize.cs
Normal file
7
src/Core/FileTime.Core/Models/IItemWithSize.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IItemWithSize : IItem
|
||||
{
|
||||
long? Size { get; }
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ namespace FileTime.Core.Providers
|
||||
DisplayName = Name = name;
|
||||
Exceptions = _exceptions.AsReadOnly();
|
||||
Provider = null!;
|
||||
AllowRecursiveDeletion = true;
|
||||
}
|
||||
|
||||
public virtual Task<bool> CanOpenAsync() => Task.FromResult(_exceptions.Count == 0);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Providers.ContainerProperty
|
||||
{
|
||||
public interface IHaveAttributes
|
||||
{
|
||||
string Attributes { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Providers.ContainerProperty
|
||||
{
|
||||
public interface IHaveCreatedAt
|
||||
{
|
||||
DateTime? CreatedAt { get; }
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@ namespace FileTime.Core.Providers
|
||||
where T : class, IContentProvider
|
||||
{
|
||||
private readonly object _initializationGuard = new();
|
||||
protected IReadOnlyList<IContainer> _rootContainers;
|
||||
private bool _initialized;
|
||||
private bool _initializing;
|
||||
private IContainer? _parent;
|
||||
protected IReadOnlyList<IContainer>? RootContainers { get; private set; }
|
||||
protected List<IContainer>? RootContainers { get; private set; }
|
||||
public override bool IsExists => true;
|
||||
|
||||
protected ContentProviderBase(
|
||||
@@ -22,6 +23,9 @@ namespace FileTime.Core.Providers
|
||||
{
|
||||
Protocol = protocol;
|
||||
SupportsContentStreams = supportsContentStreams;
|
||||
RootContainers = new List<IContainer>();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
AllowRecursiveDeletion = false;
|
||||
|
||||
CanRename = false;
|
||||
CanDelete = SupportsDelete.False;
|
||||
@@ -39,21 +43,35 @@ namespace FileTime.Core.Providers
|
||||
protected async Task AddRootContainer(IContainer newRootContainer)
|
||||
{
|
||||
RootContainers =
|
||||
(await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList().AsReadOnly()
|
||||
?? new List<IContainer>() { newRootContainer }.AsReadOnly();
|
||||
(await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList()
|
||||
?? new List<IContainer>() { newRootContainer };
|
||||
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected async Task AddRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||
{
|
||||
RootContainers =
|
||||
(await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList().AsReadOnly()
|
||||
?? new List<IContainer>(newRootContainers).AsReadOnly();
|
||||
(await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList()
|
||||
?? new List<IContainer>(newRootContainers);
|
||||
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected void SetRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||
=> RootContainers = newRootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||
protected async Task SetRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||
{
|
||||
RootContainers = newRootContainers.OrderBy(c => c.Name).ToList();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
protected void ClearRootContainers() => RootContainers = new List<IContainer>().AsReadOnly();
|
||||
protected void ClearRootContainers()
|
||||
{
|
||||
RootContainers = new List<IContainer>();
|
||||
_rootContainers = RootContainers.AsReadOnly();
|
||||
}
|
||||
|
||||
public override async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
@@ -120,7 +138,6 @@ namespace FileTime.Core.Providers
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
|
||||
public override void Unload() { }
|
||||
|
||||
public override void Destroy() { }
|
||||
|
||||
74
src/Core/FileTime.Core/Providers/LazyLoadingContainer.cs
Normal file
74
src/Core/FileTime.Core/Providers/LazyLoadingContainer.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class LazyLoadingContainer<TProvider, TContainer, TElement> : AbstractContainer<TProvider>
|
||||
where TProvider : class, IContentProvider
|
||||
where TContainer : class, IContainer
|
||||
where TElement : class, IElement
|
||||
{
|
||||
protected List<TContainer> Containers { get; }
|
||||
private IReadOnlyList<IItem> _items;
|
||||
protected List<TElement> Elements { get; }
|
||||
|
||||
private readonly IReadOnlyList<IContainer> _containersReadOnly;
|
||||
private readonly IReadOnlyList<IElement> _elementsReadOnly;
|
||||
|
||||
protected LazyLoadingContainer(TProvider provider, IContainer parent, string name) : base(provider, parent, name)
|
||||
{
|
||||
Containers = new List<TContainer>();
|
||||
Elements = new List<TElement>();
|
||||
|
||||
_containersReadOnly = Containers.AsReadOnly();
|
||||
_elementsReadOnly = Elements.AsReadOnly();
|
||||
_items = Containers.Cast<IItem>().Concat(Elements).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task RunWithLazyLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
LazyLoading = true;
|
||||
await LazyLoadingChanged.InvokeAsync(this, LazyLoading, token);
|
||||
await func(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LazyLoading = false;
|
||||
await LazyLoadingChanged.InvokeAsync(this, LazyLoading, token);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task AddContainerAsync(TContainer container)
|
||||
{
|
||||
Containers.Add(container);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
public virtual async Task AddElementAsync(TElement element)
|
||||
{
|
||||
Elements.Add(element);
|
||||
await UpdateChildren();
|
||||
}
|
||||
|
||||
private async Task UpdateChildren()
|
||||
{
|
||||
_items = Containers.Cast<IItem>().Concat(Elements).ToList().AsReadOnly();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public override async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => Task.FromResult(Enumerable.Empty<IItem>());
|
||||
|
||||
public override Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_containersReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IElement>?)_elementsReadOnly);
|
||||
|
||||
public override Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IItem>?)_items);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Threading.Tasks;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
@@ -23,9 +22,9 @@ namespace FileTime.Core.Search
|
||||
public SearchContainer(IContainer searchBaseContainer, SearchTaskBase searchTaskBase) : base(searchBaseContainer.Provider, searchBaseContainer.GetParent()!, searchBaseContainer.Name)
|
||||
{
|
||||
SearchBaseContainer = searchBaseContainer;
|
||||
SearchTaskBase = searchTaskBase;
|
||||
_containers = new List<IContainer>();
|
||||
_elements = new List<IElement>();
|
||||
SearchTaskBase = searchTaskBase;
|
||||
|
||||
_containersReadOnly = _containers.AsReadOnly();
|
||||
_elementsReadOnly = _elements.AsReadOnly();
|
||||
@@ -33,6 +32,7 @@ namespace FileTime.Core.Search
|
||||
|
||||
UseLazyLoad = true;
|
||||
CanHandleEscape = true;
|
||||
CanDelete = SupportsDelete.False;
|
||||
}
|
||||
|
||||
public async Task RunWithLazyLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
|
||||
@@ -148,6 +148,9 @@
|
||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
|
||||
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
|
||||
<converters:StringReplaceConverter x:Key="PathPreformatter" OldValue="://" NewValue="/"/>
|
||||
<converters:ItemSizeToSizeConverter x:Key="ItemSizeToSizeConverter"/>
|
||||
<converters:ItemSizeToBrushConverter x:Key="ItemSizeToBrushConverter"/>
|
||||
<converters:ItemSizeToBrushConverter x:Key="ItemSizeToForegroundBrushConverter" HueDiff="180"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -15,6 +15,7 @@ using FileTime.Avalonia.ViewModels.ItemPreview;
|
||||
using FileTime.Core.Search;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -214,6 +215,12 @@ namespace FileTime.Avalonia.Application
|
||||
if (currentSelectenItem == null)
|
||||
{
|
||||
|
||||
}
|
||||
else if (currentSelectenItem.Item is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
var sizeContainerPreview = _serviceProvider.GetService<SizeContainerPreview>()!;
|
||||
sizeContainerPreview.Init(sizeContainer);
|
||||
preview = sizeContainerPreview;
|
||||
}
|
||||
else if (currentSelectenItem.Item is ChildSearchContainer searchContainer)
|
||||
{
|
||||
@@ -233,6 +240,11 @@ namespace FileTime.Avalonia.Application
|
||||
await elementPreview.Init(elementViewModel.Element);
|
||||
preview = elementPreview;
|
||||
}
|
||||
|
||||
if (ItemPreview != null)
|
||||
{
|
||||
await ItemPreview.Destroy();
|
||||
}
|
||||
ItemPreview = preview;
|
||||
/*}
|
||||
catch
|
||||
|
||||
@@ -78,6 +78,7 @@ namespace FileTime.Avalonia.Configuration
|
||||
new CommandBindingConfiguration(Commands.Rename, Key.F2),
|
||||
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
|
||||
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
|
||||
new CommandBindingConfiguration(Commands.ScanContainerSize, new[] { Key.C, Key.S }),
|
||||
new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1),
|
||||
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
|
||||
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
(long size, true) => ToSizeString(size, prec),
|
||||
(long size, false) => ToSizeString(size),
|
||||
(null, _) => "...",
|
||||
_ => value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.ViewModels.ItemPreview;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemSizeToBrushConverter : IMultiValueConverter
|
||||
{
|
||||
public double HueDiff { get; set; }
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count == 2
|
||||
&& values[0] is ISizeItemViewModel sizeContainerViewModel
|
||||
&& values[1] is IList<ISizeItemViewModel> items)
|
||||
{
|
||||
|
||||
int i = 0;
|
||||
for (; i < items.Count; i++)
|
||||
{
|
||||
if (items[i].Item == sizeContainerViewModel.Item) break;
|
||||
}
|
||||
|
||||
var hue = (360d * i / (items.Count < 1 ? 1 : items.Count)) + HueDiff;
|
||||
if (hue > 360) hue -= 360;
|
||||
if (hue < 0) hue += 360;
|
||||
|
||||
var (r, g, b) = ColorHelper.HlsToRgb(hue, 0.5, 1);
|
||||
var task = Dispatcher.UIThread.InvokeAsync(() => new SolidColorBrush(Color.FromRgb(r, g, b)));
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
using FileTime.Avalonia.ViewModels.ItemPreview;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemSizeToSizeConverter : IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count == 3
|
||||
&& values[0] is ISizeItemViewModel sizeContainerViewModel
|
||||
&& values[1] is IEnumerable<ISizeItemViewModel> items
|
||||
&& values[2] is double width && width > 0)
|
||||
{
|
||||
var commulativeSize = items.Select(i => i.Size).Sum();
|
||||
return width * sizeContainerViewModel.Size / commulativeSize;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
|
||||
@@ -30,8 +31,9 @@ namespace FileTime.Avalonia.Converters
|
||||
}
|
||||
return AttibuteType.Element;
|
||||
}
|
||||
else if (value is ContainerViewModel)
|
||||
else if (value is ContainerViewModel containerVM)
|
||||
{
|
||||
if(containerVM.BaseItem is ContainerSizeContainer) return AttibuteType.SizeContainer;
|
||||
return AttibuteType.Container;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.2.3" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
|
||||
178
src/GuiApp/FileTime.Avalonia/Misc/ColorHelpers.cs
Normal file
178
src/GuiApp/FileTime.Avalonia/Misc/ColorHelpers.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
namespace FileTime.Avalonia.Misc
|
||||
{
|
||||
public static class ColorHelper
|
||||
{
|
||||
/*public struct RGB
|
||||
{
|
||||
public RGB(byte r, byte g, byte b)
|
||||
{
|
||||
R = r;
|
||||
G = g;
|
||||
B = b;
|
||||
}
|
||||
|
||||
public byte R { get; set; }
|
||||
|
||||
public byte G { get; set; }
|
||||
|
||||
public byte B { get; set; }
|
||||
|
||||
public bool Equals(RGB rgb)
|
||||
{
|
||||
return (R == rgb.R) && (G == rgb.G) && (B == rgb.B);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HSL
|
||||
{
|
||||
public HSL(int h, float s, float l)
|
||||
{
|
||||
H = h;
|
||||
S = s;
|
||||
L = l;
|
||||
}
|
||||
|
||||
public int H { get; set; }
|
||||
|
||||
public float S { get; set; }
|
||||
|
||||
public float L { get; set; }
|
||||
|
||||
public bool Equals(HSL hsl)
|
||||
{
|
||||
return (H == hsl.H) && (S == hsl.S) && (L == hsl.L);
|
||||
}
|
||||
}
|
||||
|
||||
public static RGB HSLToRGB(HSL hsl)
|
||||
{
|
||||
byte r = 0;
|
||||
byte g = 0;
|
||||
byte b = 0;
|
||||
|
||||
if (hsl.S == 0)
|
||||
{
|
||||
r = g = b = (byte)(hsl.L * 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
float v1, v2;
|
||||
float hue = (float)hsl.H / 360;
|
||||
|
||||
v2 = (hsl.L < 0.5) ? (hsl.L * (1 + hsl.S)) : ((hsl.L + hsl.S) - (hsl.L * hsl.S));
|
||||
v1 = 2 * hsl.L - v2;
|
||||
|
||||
r = (byte)(255 * HueToRGB(v1, v2, hue + (1.0f / 3)));
|
||||
g = (byte)(255 * HueToRGB(v1, v2, hue));
|
||||
b = (byte)(255 * HueToRGB(v1, v2, hue - (1.0f / 3)));
|
||||
}
|
||||
|
||||
return new RGB(r, g, b);
|
||||
}
|
||||
|
||||
private static float HueToRGB(float v1, float v2, float vH)
|
||||
{
|
||||
if (vH < 0)
|
||||
vH += 1;
|
||||
|
||||
if (vH > 1)
|
||||
vH -= 1;
|
||||
|
||||
if ((6 * vH) < 1)
|
||||
return (v1 + (v2 - v1) * 6 * vH);
|
||||
|
||||
if ((2 * vH) < 1)
|
||||
return v2;
|
||||
|
||||
if ((3 * vH) < 2)
|
||||
return (v1 + (v2 - v1) * ((2.0f / 3) - vH) * 6);
|
||||
|
||||
return v1;
|
||||
}*/
|
||||
|
||||
/*public static (byte r, byte g, byte b) ColorFromHSL(double h, double s, double l)
|
||||
{
|
||||
double r = 0, g = 0, b = 0;
|
||||
if (l != 0)
|
||||
{
|
||||
if (s == 0)
|
||||
{
|
||||
r = g = b = l;
|
||||
}
|
||||
else
|
||||
{
|
||||
double temp2;
|
||||
if (l < 0.5)
|
||||
temp2 = l * (1.0 + s);
|
||||
else
|
||||
temp2 = l + s - (l * s);
|
||||
|
||||
double temp1 = 2.0 * l - temp2;
|
||||
|
||||
r = GetColorComponent(temp1, temp2, h + 1.0 / 3.0);
|
||||
g = GetColorComponent(temp1, temp2, h);
|
||||
b = GetColorComponent(temp1, temp2, h - 1.0 / 3.0);
|
||||
}
|
||||
}
|
||||
return ((byte)(255 * r), (byte)(255 * g), (byte)(255 * b));
|
||||
|
||||
}
|
||||
|
||||
private static double GetColorComponent(double temp1, double temp2, double temp3)
|
||||
{
|
||||
if (temp3 < 0.0)
|
||||
temp3 += 1.0;
|
||||
else if (temp3 > 1.0)
|
||||
temp3 -= 1.0;
|
||||
|
||||
if (temp3 < 1.0 / 6.0)
|
||||
return temp1 + (temp2 - temp1) * 6.0 * temp3;
|
||||
else if (temp3 < 0.5)
|
||||
return temp2;
|
||||
else if (temp3 < 2.0 / 3.0)
|
||||
return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0);
|
||||
else
|
||||
return temp1;
|
||||
}*/
|
||||
|
||||
|
||||
// Convert an HLS value into an RGB value.
|
||||
public static (byte r, byte g, byte b) HlsToRgb(double h, double l, double s)
|
||||
{
|
||||
double p2;
|
||||
if (l <= 0.5) p2 = l * (1 + s);
|
||||
else p2 = l + s - l * s;
|
||||
|
||||
double p1 = 2 * l - p2;
|
||||
double double_r, double_g, double_b;
|
||||
if (s == 0)
|
||||
{
|
||||
double_r = l;
|
||||
double_g = l;
|
||||
double_b = l;
|
||||
}
|
||||
else
|
||||
{
|
||||
double_r = QqhToRgb(p1, p2, h + 120);
|
||||
double_g = QqhToRgb(p1, p2, h);
|
||||
double_b = QqhToRgb(p1, p2, h - 120);
|
||||
}
|
||||
|
||||
// Convert RGB to the 0 to 255 range.
|
||||
return ((byte)(double_r * 255.0),
|
||||
(byte)(double_g * 255.0),
|
||||
(byte)(double_b * 255.0));
|
||||
}
|
||||
|
||||
private static double QqhToRgb(double q1, double q2, double hue)
|
||||
{
|
||||
if (hue > 360) hue -= 360;
|
||||
else if (hue < 0) hue += 360;
|
||||
|
||||
if (hue < 60) return q1 + (q2 - q1) * hue / 60;
|
||||
if (hue < 180) return q2;
|
||||
if (hue < 240) return q1 + (q2 - q1) * (240 - hue) / 60;
|
||||
return q1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace FileTime.Avalonia.Models
|
||||
{
|
||||
File,
|
||||
Element,
|
||||
Container
|
||||
Container,
|
||||
SizeContainer
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace FileTime.Avalonia.Models
|
||||
Text,
|
||||
Empty,
|
||||
SearchContainer,
|
||||
SearchElement
|
||||
SearchElement,
|
||||
SizeContainer,
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ using FileTime.Core.Command.Delete;
|
||||
using FileTime.Core.Command.Move;
|
||||
using FileTime.Core.Command.Rename;
|
||||
using FileTime.Core.Components;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
@@ -47,6 +48,7 @@ namespace FileTime.Avalonia.Services
|
||||
private readonly ProgramsService _programsService;
|
||||
private readonly ILogger<CommandHandlerService> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ContainerScanSnapshotProvider _containerScanSnapshotProvider;
|
||||
|
||||
public CommandHandlerService(
|
||||
AppState appState,
|
||||
@@ -59,7 +61,8 @@ namespace FileTime.Avalonia.Services
|
||||
IEnumerable<IContentProvider> contentProviders,
|
||||
ProgramsService programsService,
|
||||
ILogger<CommandHandlerService> logger,
|
||||
IServiceProvider serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
ContainerScanSnapshotProvider containerScanSnapshotProvider)
|
||||
{
|
||||
_appState = appState;
|
||||
_localContentProvider = localContentProvider;
|
||||
@@ -72,6 +75,7 @@ namespace FileTime.Avalonia.Services
|
||||
_programsService = programsService;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
_containerScanSnapshotProvider = containerScanSnapshotProvider;
|
||||
|
||||
_commandHandlers = new Dictionary<Commands, Func<Task>>
|
||||
{
|
||||
@@ -115,6 +119,7 @@ namespace FileTime.Avalonia.Services
|
||||
{Commands.Refresh, RefreshCurrentLocation},
|
||||
{Commands.Rename, Rename},
|
||||
{Commands.RunCommand, RunCommandInContainer},
|
||||
{Commands.ScanContainerSize, ScanContainerSize},
|
||||
{Commands.ShowAllShotcut, ShowAllShortcut},
|
||||
{Commands.SoftDelete, SoftDelete},
|
||||
{Commands.SwitchToLastTab, async() => await SwitchToTab(-1)},
|
||||
@@ -995,5 +1000,16 @@ namespace FileTime.Avalonia.Services
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ScanContainerSize()
|
||||
{
|
||||
if (_appState.SelectedTab.CurrentLocation != null)
|
||||
{
|
||||
var scanTask = new ScanSizeTask(_containerScanSnapshotProvider, _appState.SelectedTab.CurrentLocation.Container);
|
||||
scanTask.Start();
|
||||
|
||||
await OpenContainer(scanTask.Snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ namespace FileTime.Avalonia
|
||||
.AddTransient<MainPageViewModel>()
|
||||
.AddTransient<SearchElementPreview>()
|
||||
.AddTransient<SearchContainerPreview>()
|
||||
.AddTransient<SizeContainerViewModel>()
|
||||
.AddTransient<SizeElementViewmodel>()
|
||||
.AddTransient<SizeContainerPreview>()
|
||||
.AddSingleton<IInputInterface, BasicInputHandler>();
|
||||
}
|
||||
internal static IServiceCollection AddServices(this IServiceCollection serviceCollection)
|
||||
|
||||
@@ -14,6 +14,7 @@ using FileTime.Avalonia.Application;
|
||||
using System.Threading;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.Search;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -48,6 +49,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private List<Exception> _exceptions;
|
||||
|
||||
//[Property]
|
||||
//private long? _size;
|
||||
|
||||
public IItem Item => _container;
|
||||
public IItem BaseItem => _baseContainer;
|
||||
|
||||
@@ -81,6 +85,8 @@ namespace FileTime.Avalonia.ViewModels
|
||||
public Task Elements => GetElements();
|
||||
public Task Items => GetItems();
|
||||
|
||||
public long? Size => BaseContainer is ContainerSizeContainer sizeContainer ? sizeContainer.Size : null;
|
||||
|
||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||
@@ -133,10 +139,21 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
Container = container;
|
||||
BaseContainer = container is ChildSearchContainer childSearchContainer ? childSearchContainer.BaseContainer : container;
|
||||
if (BaseContainer is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
sizeContainer.SizeChanged.Add(UpdateSize);
|
||||
}
|
||||
Container.Refreshed.Add(Container_Refreshed);
|
||||
Container.LazyLoadingChanged.Add(Container_LazyLoadingChanged);
|
||||
}
|
||||
|
||||
private Task UpdateSize(object? sender, long? size, CancellationToken token)
|
||||
{
|
||||
OnPropertyChanged(nameof(Size));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
||||
|
||||
public async Task Init(bool initializeChildren = true, CancellationToken token = default)
|
||||
|
||||
@@ -53,5 +53,7 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
Mode = ItemPreviewMode.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public Task Destroy() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.Models;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
@@ -5,5 +6,6 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
public interface IItemPreviewViewModel
|
||||
{
|
||||
ItemPreviewMode Mode { get; }
|
||||
Task Destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
{
|
||||
public interface ISizeItemViewModel
|
||||
{
|
||||
string? Name { get; }
|
||||
long? Size { get; }
|
||||
IItem? Item { get; }
|
||||
}
|
||||
}
|
||||
@@ -29,5 +29,6 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
RealtiveParentPath = new AbsolutePath(null!, container.FullName!.Substring(pathCommonPath.Length).Trim(Constants.SeparatorChar), AbsolutePathType.Unknown, null).GetParentPath();
|
||||
Task.Run(async () => ItemNameParts = await Dispatcher.UIThread.InvokeAsync(() => container.SearchDisplayName.ConvertAll(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null))));
|
||||
}
|
||||
public Task Destroy() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,6 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
RealtiveParentPath = new AbsolutePath(null!, element.FullName!.Substring(pathCommonPath.Length).Trim(Constants.SeparatorChar), AbsolutePathType.Unknown, null).GetParentPath();
|
||||
Task.Run(async () => ItemNameParts = await Dispatcher.UIThread.InvokeAsync(() => element.SearchDisplayName.ConvertAll(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null))));
|
||||
}
|
||||
public Task Destroy() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
{
|
||||
[ViewModel]
|
||||
[Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")]
|
||||
public partial class SizeContainerPreview : IItemPreviewViewModel
|
||||
{
|
||||
private readonly BehaviorSubject<object?> _update = new BehaviorSubject<object?>(null);
|
||||
private IEnumerable<ISizeItemViewModel>? _allItems;
|
||||
|
||||
[Property]
|
||||
private IObservable<IEnumerable<ISizeItemViewModel>> _items;
|
||||
[Property]
|
||||
private IObservable<IEnumerable<ISizeItemViewModel>> _topItems;
|
||||
|
||||
public ItemPreviewMode Mode => ItemPreviewMode.SizeContainer;
|
||||
|
||||
public void Init(ContainerSizeContainer container)
|
||||
{
|
||||
container.Refreshed.Add(ContainerRefreshed);
|
||||
container.SizeChanged.Add(ItemsChanged);
|
||||
Items = _update.Throttle(TimeSpan.FromMilliseconds(500)).Select((_) => RefreshItems(this, container));
|
||||
TopItems = Items.Select(items => items.Take(10));
|
||||
_update.OnNext(null);
|
||||
}
|
||||
|
||||
private IEnumerable<ISizeItemViewModel> RefreshItems(SizeContainerPreview parent, ContainerSizeContainer container)
|
||||
{
|
||||
if (_allItems != null)
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
if (item is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
sizeContainer.SizeChanged.Remove(ItemsChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items = GetItems(parent, container).ToList();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
sizeContainer.SizeChanged.Add(ItemsChanged);
|
||||
}
|
||||
}
|
||||
|
||||
_allItems = items;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private Task ItemsChanged(object? sender, long? size, CancellationToken token)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ContainerRefreshed(object? sender, AsyncEventArgs e, CancellationToken token)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IEnumerable<ISizeItemViewModel> GetItems(SizeContainerPreview parent, ContainerSizeContainer container)
|
||||
{
|
||||
var items = new List<ISizeItemViewModel>();
|
||||
var itemsWithSize = container.GetItemsWithSize().OrderByDescending(i => i.Size).ToList();
|
||||
|
||||
foreach (var itemWithSize in itemsWithSize)
|
||||
{
|
||||
if (itemWithSize is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
var containerVm = _serviceProvider.GetService<SizeContainerViewModel>()!;
|
||||
containerVm.Init(parent, sizeContainer);
|
||||
items.Add(containerVm);
|
||||
}
|
||||
else if (itemWithSize is ContainerSizeElement sizeElement)
|
||||
{
|
||||
var elementVm = _serviceProvider.GetService<SizeElementViewmodel>()!;
|
||||
elementVm.Init(sizeElement);
|
||||
items.Add(elementVm);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
public Task Destroy() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
{
|
||||
public class SizeContainerViewModel : ISizeItemViewModel
|
||||
{
|
||||
private bool _initialized;
|
||||
private SizeContainerPreview? _parent;
|
||||
private ContainerSizeContainer? _sizeContainer;
|
||||
|
||||
private readonly BehaviorSubject<object?> _update = new BehaviorSubject<object?>(null);
|
||||
private IEnumerable<ISizeItemViewModel>? _allItems;
|
||||
|
||||
private IObservable<IEnumerable<ISizeItemViewModel>>? _items;
|
||||
private IObservable<IEnumerable<ISizeItemViewModel>>? _topItems;
|
||||
|
||||
public IObservable<IEnumerable<ISizeItemViewModel>>? Items
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!_initialized)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
_initialized = true;
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
public IObservable<IEnumerable<ISizeItemViewModel>>? TopItems
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
return _topItems;
|
||||
}
|
||||
}
|
||||
|
||||
public string? Name { get; private set; }
|
||||
public long? Size { get; private set; }
|
||||
|
||||
public IItem? Item => _sizeContainer;
|
||||
|
||||
public void Init(SizeContainerPreview parent, ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
_sizeContainer = sizeContainer;
|
||||
_parent = parent;
|
||||
|
||||
Name = sizeContainer.DisplayName;
|
||||
Size = sizeContainer.Size;
|
||||
|
||||
sizeContainer.Refreshed.Add(ContainerRefreshed);
|
||||
sizeContainer.SizeChanged.Add(ItemsChanged);
|
||||
_items = _update.Throttle(TimeSpan.FromMilliseconds(500)).Select((_) => RefreshItems(parent, sizeContainer));
|
||||
_topItems = _items.Select(items => items.Take(10));
|
||||
}
|
||||
|
||||
private IEnumerable<ISizeItemViewModel> RefreshItems(SizeContainerPreview parent, ContainerSizeContainer container)
|
||||
{
|
||||
if (_allItems != null)
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
if (item is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
sizeContainer.SizeChanged.Remove(ItemsChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var items = parent.GetItems(parent, container).ToList();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is ContainerSizeContainer sizeContainer)
|
||||
{
|
||||
sizeContainer.SizeChanged.Add(ItemsChanged);
|
||||
}
|
||||
}
|
||||
|
||||
_allItems = items;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private Task ItemsChanged(object? sender, long? size, CancellationToken token)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ContainerRefreshed(object? sender, AsyncEventArgs e, CancellationToken token)
|
||||
{
|
||||
_update.OnNext(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Models;
|
||||
using MvvmGen;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||
{
|
||||
[ViewModel]
|
||||
public partial class SizeElementViewmodel : ISizeItemViewModel
|
||||
{
|
||||
private ContainerSizeElement? _sizeElement;
|
||||
public string? Name { get; private set; }
|
||||
public long? Size { get; private set; }
|
||||
|
||||
public IItem? Item => _sizeElement;
|
||||
|
||||
public void Init(ContainerSizeElement sizeElement)
|
||||
{
|
||||
_sizeElement = sizeElement;
|
||||
|
||||
Name = sizeElement.DisplayName;
|
||||
Size = sizeElement.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,15 @@
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.Attributes}"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="50,90,40,45"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.SizeContainer}}">
|
||||
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
|
||||
<!--TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
|
||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="3" Text="{Binding BaseItem.Attributes}"/-->
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="50,50"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.Element}}">
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
Fill="{DynamicResource ContentSeparatorBrush}" />
|
||||
|
||||
<Grid Grid.Column="4">
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNullConverter}}">
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
@@ -304,8 +305,6 @@
|
||||
<Grid
|
||||
x:CompileBindings="False"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNullConverter}}">
|
||||
<TextBlock
|
||||
x:Name="ChildEmpty"
|
||||
Margin="10"
|
||||
@@ -315,7 +314,6 @@
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
RowDefinitions="Auto, Auto"
|
||||
@@ -338,6 +336,7 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNotNullConverter}}">
|
||||
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this item." IsVisible="{Binding AppState.SelectedTab.ItemPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ItemPreviewMode.Unknown}}"/>
|
||||
@@ -398,6 +397,85 @@
|
||||
<TextBlock Margin="0,0,10,0" Grid.Row="1" Text="Path"/>
|
||||
<local:PathPresenter Grid.Column="1" Grid.Row="1" x:CompileBindings="False" DataContext="{Binding AppState.SelectedTab.ItemPreview.RealtiveParentPath,Converter={StaticResource PathPreformatter}}"/>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
DataContext="{Binding AppState.SelectedTab.ItemPreview}"
|
||||
RowDefinitions="Auto, Auto"
|
||||
x:Name="SizeContainerPreview"
|
||||
IsVisible="{Binding Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ItemPreviewMode.SizeContainer}}">
|
||||
<ItemsControl
|
||||
Margin="0,0,0,30"
|
||||
x:CompileBindings="False"
|
||||
Items="{Binding TopItems^}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Width>
|
||||
<MultiBinding Converter="{StaticResource ItemSizeToSizeConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding/>
|
||||
<Binding ElementName="SizeContainerPreview" Path="DataContext.TopItems^"/>
|
||||
<Binding ElementName="SizeContainerPreview" Path="Bounds.Width"/>
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</Grid.Width>
|
||||
|
||||
<Rectangle HorizontalAlignment="Stretch">
|
||||
<Rectangle.Fill>
|
||||
<MultiBinding Converter="{StaticResource ItemSizeToBrushConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding/>
|
||||
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^"/>
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||
<TextBlock Classes="SmallText" Margin="0,5,0,2" Text="{Binding Name}">
|
||||
<TextBlock.Foreground>
|
||||
<MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding/>
|
||||
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^"/>
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
<TextBlock Classes="SmallText" Margin="0,2,0,5" Text="{Binding Size,Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
<TextBlock.Foreground>
|
||||
<MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}">
|
||||
<MultiBinding.Bindings>
|
||||
<Binding/>
|
||||
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^"/>
|
||||
</MultiBinding.Bindings>
|
||||
</MultiBinding>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<ItemsControl
|
||||
Grid.Row="1"
|
||||
x:CompileBindings="False"
|
||||
Items="{Binding Items^}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*" Margin="0,0,0,20">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<TextBlock Margin="0,0,20,0" HorizontalAlignment="Right" Grid.Column="1" Text="{Binding Size,Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace FileTime.Providers.Local
|
||||
? new DirectoryInfo("/").GetDirectories()
|
||||
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
|
||||
|
||||
SetRootContainers(rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name));
|
||||
SetRootContainers(rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name)).Wait();
|
||||
}
|
||||
|
||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Providers.ContainerProperty;
|
||||
using FileTime.Providers.Local.Interop;
|
||||
|
||||
namespace FileTime.Providers.Local
|
||||
{
|
||||
public class LocalFolder : AbstractContainer<LocalContentProvider>, IContainer
|
||||
public class LocalFolder : AbstractContainer<LocalContentProvider>, IContainer, IHaveCreatedAt, IHaveAttributes
|
||||
{
|
||||
public DirectoryInfo Directory { get; }
|
||||
|
||||
public string Attributes => GetAttributes();
|
||||
|
||||
public DateTime CreatedAt => Directory.CreationTime;
|
||||
public DateTime? CreatedAt => Directory.CreationTime;
|
||||
public override bool IsExists => Directory.Exists;
|
||||
|
||||
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer parent)
|
||||
@@ -21,7 +22,6 @@ namespace FileTime.Providers.Local
|
||||
NativePath = Directory.FullName;
|
||||
IsHidden = (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
CanRename = true;
|
||||
AllowRecursiveDeletion = true;
|
||||
|
||||
//TODO: Linux soft delete
|
||||
SupportsDirectoryLevelSoftDelete = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace FileTime.Providers.Sftp
|
||||
_inputInterface = inputInterface;
|
||||
}
|
||||
|
||||
public override Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith("sftp://"));
|
||||
public override Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
|
||||
public override async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace FileTime.Providers.Sftp
|
||||
NativePath = FullName = sftpContentProvider.Protocol + Constants.SeparatorChar + name;
|
||||
|
||||
CanDelete = SupportsDelete.True;
|
||||
AllowRecursiveDeletion = false;
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => await ListDirectory(this, "");
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace FileTime.Providers.Smb
|
||||
protected override async Task Init()
|
||||
{
|
||||
var servers = await _persistenceService.LoadServers();
|
||||
SetRootContainers(servers.Select(s => new SmbServer(s.Path, this, _inputInterface, s.UserName, s.Password)));
|
||||
await SetRootContainers(servers.Select(s => new SmbServer(s.Path, this, _inputInterface, s.UserName, s.Password)));
|
||||
}
|
||||
|
||||
public static string GetNativePathSeparator() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/";
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace FileTime.Providers.Smb
|
||||
Username = username;
|
||||
Password = password;
|
||||
CanDelete = SupportsDelete.True;
|
||||
AllowRecursiveDeletion = false;
|
||||
|
||||
FullName = contentProvider.Protocol + Name;
|
||||
NativePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
|
||||
Reference in New Issue
Block a user