Recursive container size scan
This commit is contained in:
@@ -44,6 +44,7 @@ namespace FileTime.App.Core.Command
|
|||||||
Refresh,
|
Refresh,
|
||||||
Rename,
|
Rename,
|
||||||
RunCommand,
|
RunCommand,
|
||||||
|
ScanContainerSize,
|
||||||
ShowAllShotcut,
|
ShowAllShotcut,
|
||||||
SoftDelete,
|
SoftDelete,
|
||||||
SwitchToLastTab,
|
SwitchToLastTab,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using FileTime.App.Core.Clipboard;
|
using FileTime.App.Core.Clipboard;
|
||||||
using FileTime.Core.Command;
|
using FileTime.Core.Command;
|
||||||
using FileTime.Core.CommandHandlers;
|
using FileTime.Core.CommandHandlers;
|
||||||
|
using FileTime.Core.ContainerSizeScanner;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
@@ -22,6 +23,8 @@ namespace FileTime.App.Core
|
|||||||
.AddSingleton<TopContainer>()
|
.AddSingleton<TopContainer>()
|
||||||
.AddSingleton<CommandExecutor>()
|
.AddSingleton<CommandExecutor>()
|
||||||
.AddSingleton<TimeRunner>()
|
.AddSingleton<TimeRunner>()
|
||||||
|
.AddSingleton<ContainerScanSnapshotProvider>()
|
||||||
|
.AddSingleton<IContentProvider, ContainerScanSnapshotProvider>(p => p.GetService<ContainerScanSnapshotProvider>() ?? throw new ArgumentException(nameof(ContainerScanSnapshotProvider) + " is not registered"))
|
||||||
.AddSingleton<ItemNameConverterService>()
|
.AddSingleton<ItemNameConverterService>()
|
||||||
.AddLocalServices()
|
.AddLocalServices()
|
||||||
.AddSmbServices()
|
.AddSmbServices()
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
List<Func<TSender, TArg, CancellationToken, Task>>? handlers;
|
List<Func<TSender, TArg, CancellationToken, Task>>? handlers;
|
||||||
lock (_guard)
|
lock (_guard)
|
||||||
{
|
{
|
||||||
handlers = _handlers;
|
handlers = new List<Func<TSender, TArg, CancellationToken, Task>>(_handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var handler in 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;
|
DisplayName = Name = name;
|
||||||
Exceptions = _exceptions.AsReadOnly();
|
Exceptions = _exceptions.AsReadOnly();
|
||||||
Provider = null!;
|
Provider = null!;
|
||||||
|
AllowRecursiveDeletion = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Task<bool> CanOpenAsync() => Task.FromResult(_exceptions.Count == 0);
|
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
|
where T : class, IContentProvider
|
||||||
{
|
{
|
||||||
private readonly object _initializationGuard = new();
|
private readonly object _initializationGuard = new();
|
||||||
|
protected IReadOnlyList<IContainer> _rootContainers;
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
private bool _initializing;
|
private bool _initializing;
|
||||||
private IContainer? _parent;
|
private IContainer? _parent;
|
||||||
protected IReadOnlyList<IContainer>? RootContainers { get; private set; }
|
protected List<IContainer>? RootContainers { get; private set; }
|
||||||
public override bool IsExists => true;
|
public override bool IsExists => true;
|
||||||
|
|
||||||
protected ContentProviderBase(
|
protected ContentProviderBase(
|
||||||
@@ -22,6 +23,9 @@ namespace FileTime.Core.Providers
|
|||||||
{
|
{
|
||||||
Protocol = protocol;
|
Protocol = protocol;
|
||||||
SupportsContentStreams = supportsContentStreams;
|
SupportsContentStreams = supportsContentStreams;
|
||||||
|
RootContainers = new List<IContainer>();
|
||||||
|
_rootContainers = RootContainers.AsReadOnly();
|
||||||
|
AllowRecursiveDeletion = false;
|
||||||
|
|
||||||
CanRename = false;
|
CanRename = false;
|
||||||
CanDelete = SupportsDelete.False;
|
CanDelete = SupportsDelete.False;
|
||||||
@@ -39,21 +43,35 @@ namespace FileTime.Core.Providers
|
|||||||
protected async Task AddRootContainer(IContainer newRootContainer)
|
protected async Task AddRootContainer(IContainer newRootContainer)
|
||||||
{
|
{
|
||||||
RootContainers =
|
RootContainers =
|
||||||
(await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList().AsReadOnly()
|
(await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList()
|
||||||
?? new List<IContainer>() { newRootContainer }.AsReadOnly();
|
?? new List<IContainer>() { newRootContainer };
|
||||||
|
|
||||||
|
_rootContainers = RootContainers.AsReadOnly();
|
||||||
|
await RefreshAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task AddRootContainers(IEnumerable<IContainer> newRootContainers)
|
protected async Task AddRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||||
{
|
{
|
||||||
RootContainers =
|
RootContainers =
|
||||||
(await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList().AsReadOnly()
|
(await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList()
|
||||||
?? new List<IContainer>(newRootContainers).AsReadOnly();
|
?? new List<IContainer>(newRootContainers);
|
||||||
|
|
||||||
|
_rootContainers = RootContainers.AsReadOnly();
|
||||||
|
await RefreshAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SetRootContainers(IEnumerable<IContainer> newRootContainers)
|
protected async Task SetRootContainers(IEnumerable<IContainer> newRootContainers)
|
||||||
=> RootContainers = newRootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
{
|
||||||
|
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)
|
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 Rename(string newName) => throw new NotSupportedException();
|
||||||
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||||
|
|
||||||
|
|
||||||
public override void Unload() { }
|
public override void Unload() { }
|
||||||
|
|
||||||
public override void Destroy() { }
|
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 AsyncEvent;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
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)
|
public SearchContainer(IContainer searchBaseContainer, SearchTaskBase searchTaskBase) : base(searchBaseContainer.Provider, searchBaseContainer.GetParent()!, searchBaseContainer.Name)
|
||||||
{
|
{
|
||||||
SearchBaseContainer = searchBaseContainer;
|
SearchBaseContainer = searchBaseContainer;
|
||||||
|
SearchTaskBase = searchTaskBase;
|
||||||
_containers = new List<IContainer>();
|
_containers = new List<IContainer>();
|
||||||
_elements = new List<IElement>();
|
_elements = new List<IElement>();
|
||||||
SearchTaskBase = searchTaskBase;
|
|
||||||
|
|
||||||
_containersReadOnly = _containers.AsReadOnly();
|
_containersReadOnly = _containers.AsReadOnly();
|
||||||
_elementsReadOnly = _elements.AsReadOnly();
|
_elementsReadOnly = _elements.AsReadOnly();
|
||||||
@@ -33,6 +32,7 @@ namespace FileTime.Core.Search
|
|||||||
|
|
||||||
UseLazyLoad = true;
|
UseLazyLoad = true;
|
||||||
CanHandleEscape = true;
|
CanHandleEscape = true;
|
||||||
|
CanDelete = SupportsDelete.False;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunWithLazyLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
public async Task RunWithLazyLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||||
|
|||||||
@@ -148,6 +148,9 @@
|
|||||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
|
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
|
||||||
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
|
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
|
||||||
<converters:StringReplaceConverter x:Key="PathPreformatter" OldValue="://" NewValue="/"/>
|
<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>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using FileTime.Avalonia.ViewModels.ItemPreview;
|
|||||||
using FileTime.Core.Search;
|
using FileTime.Core.Search;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
|
using FileTime.Core.ContainerSizeScanner;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Application
|
namespace FileTime.Avalonia.Application
|
||||||
{
|
{
|
||||||
@@ -214,6 +215,12 @@ namespace FileTime.Avalonia.Application
|
|||||||
if (currentSelectenItem == null)
|
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)
|
else if (currentSelectenItem.Item is ChildSearchContainer searchContainer)
|
||||||
{
|
{
|
||||||
@@ -233,6 +240,11 @@ namespace FileTime.Avalonia.Application
|
|||||||
await elementPreview.Init(elementViewModel.Element);
|
await elementPreview.Init(elementViewModel.Element);
|
||||||
preview = elementPreview;
|
preview = elementPreview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ItemPreview != null)
|
||||||
|
{
|
||||||
|
await ItemPreview.Destroy();
|
||||||
|
}
|
||||||
ItemPreview = preview;
|
ItemPreview = preview;
|
||||||
/*}
|
/*}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ namespace FileTime.Avalonia.Configuration
|
|||||||
new CommandBindingConfiguration(Commands.Rename, Key.F2),
|
new CommandBindingConfiguration(Commands.Rename, Key.F2),
|
||||||
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
|
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
|
||||||
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
|
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.ShowAllShotcut, Key.F1),
|
||||||
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
|
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
|
||||||
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
|
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace FileTime.Avalonia.Converters
|
|||||||
{
|
{
|
||||||
(long size, true) => ToSizeString(size, prec),
|
(long size, true) => ToSizeString(size, prec),
|
||||||
(long size, false) => ToSizeString(size),
|
(long size, false) => ToSizeString(size),
|
||||||
|
(null, _) => "...",
|
||||||
_ => value
|
_ => 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 Avalonia.Data.Converters;
|
||||||
using FileTime.Avalonia.Models;
|
using FileTime.Avalonia.Models;
|
||||||
using FileTime.Avalonia.ViewModels;
|
using FileTime.Avalonia.ViewModels;
|
||||||
|
using FileTime.Core.ContainerSizeScanner;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Providers.Local;
|
using FileTime.Providers.Local;
|
||||||
|
|
||||||
@@ -30,8 +31,9 @@ namespace FileTime.Avalonia.Converters
|
|||||||
}
|
}
|
||||||
return AttibuteType.Element;
|
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;
|
return AttibuteType.Container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.2.3" />
|
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.2.3" />
|
||||||
|
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
<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,
|
File,
|
||||||
Element,
|
Element,
|
||||||
Container
|
Container,
|
||||||
|
SizeContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace FileTime.Avalonia.Models
|
|||||||
Text,
|
Text,
|
||||||
Empty,
|
Empty,
|
||||||
SearchContainer,
|
SearchContainer,
|
||||||
SearchElement
|
SearchElement,
|
||||||
|
SizeContainer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ using FileTime.Core.Command.Delete;
|
|||||||
using FileTime.Core.Command.Move;
|
using FileTime.Core.Command.Move;
|
||||||
using FileTime.Core.Command.Rename;
|
using FileTime.Core.Command.Rename;
|
||||||
using FileTime.Core.Components;
|
using FileTime.Core.Components;
|
||||||
|
using FileTime.Core.ContainerSizeScanner;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
@@ -47,6 +48,7 @@ namespace FileTime.Avalonia.Services
|
|||||||
private readonly ProgramsService _programsService;
|
private readonly ProgramsService _programsService;
|
||||||
private readonly ILogger<CommandHandlerService> _logger;
|
private readonly ILogger<CommandHandlerService> _logger;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ContainerScanSnapshotProvider _containerScanSnapshotProvider;
|
||||||
|
|
||||||
public CommandHandlerService(
|
public CommandHandlerService(
|
||||||
AppState appState,
|
AppState appState,
|
||||||
@@ -59,7 +61,8 @@ namespace FileTime.Avalonia.Services
|
|||||||
IEnumerable<IContentProvider> contentProviders,
|
IEnumerable<IContentProvider> contentProviders,
|
||||||
ProgramsService programsService,
|
ProgramsService programsService,
|
||||||
ILogger<CommandHandlerService> logger,
|
ILogger<CommandHandlerService> logger,
|
||||||
IServiceProvider serviceProvider)
|
IServiceProvider serviceProvider,
|
||||||
|
ContainerScanSnapshotProvider containerScanSnapshotProvider)
|
||||||
{
|
{
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
_localContentProvider = localContentProvider;
|
_localContentProvider = localContentProvider;
|
||||||
@@ -72,6 +75,7 @@ namespace FileTime.Avalonia.Services
|
|||||||
_programsService = programsService;
|
_programsService = programsService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
_containerScanSnapshotProvider = containerScanSnapshotProvider;
|
||||||
|
|
||||||
_commandHandlers = new Dictionary<Commands, Func<Task>>
|
_commandHandlers = new Dictionary<Commands, Func<Task>>
|
||||||
{
|
{
|
||||||
@@ -115,6 +119,7 @@ namespace FileTime.Avalonia.Services
|
|||||||
{Commands.Refresh, RefreshCurrentLocation},
|
{Commands.Refresh, RefreshCurrentLocation},
|
||||||
{Commands.Rename, Rename},
|
{Commands.Rename, Rename},
|
||||||
{Commands.RunCommand, RunCommandInContainer},
|
{Commands.RunCommand, RunCommandInContainer},
|
||||||
|
{Commands.ScanContainerSize, ScanContainerSize},
|
||||||
{Commands.ShowAllShotcut, ShowAllShortcut},
|
{Commands.ShowAllShotcut, ShowAllShortcut},
|
||||||
{Commands.SoftDelete, SoftDelete},
|
{Commands.SoftDelete, SoftDelete},
|
||||||
{Commands.SwitchToLastTab, async() => await SwitchToTab(-1)},
|
{Commands.SwitchToLastTab, async() => await SwitchToTab(-1)},
|
||||||
@@ -995,5 +1000,16 @@ namespace FileTime.Avalonia.Services
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
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<MainPageViewModel>()
|
||||||
.AddTransient<SearchElementPreview>()
|
.AddTransient<SearchElementPreview>()
|
||||||
.AddTransient<SearchContainerPreview>()
|
.AddTransient<SearchContainerPreview>()
|
||||||
|
.AddTransient<SizeContainerViewModel>()
|
||||||
|
.AddTransient<SizeElementViewmodel>()
|
||||||
|
.AddTransient<SizeContainerPreview>()
|
||||||
.AddSingleton<IInputInterface, BasicInputHandler>();
|
.AddSingleton<IInputInterface, BasicInputHandler>();
|
||||||
}
|
}
|
||||||
internal static IServiceCollection AddServices(this IServiceCollection serviceCollection)
|
internal static IServiceCollection AddServices(this IServiceCollection serviceCollection)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using FileTime.Avalonia.Application;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
using FileTime.Core.Search;
|
using FileTime.Core.Search;
|
||||||
|
using FileTime.Core.ContainerSizeScanner;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.ViewModels
|
namespace FileTime.Avalonia.ViewModels
|
||||||
{
|
{
|
||||||
@@ -48,6 +49,9 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
[Property]
|
[Property]
|
||||||
private List<Exception> _exceptions;
|
private List<Exception> _exceptions;
|
||||||
|
|
||||||
|
//[Property]
|
||||||
|
//private long? _size;
|
||||||
|
|
||||||
public IItem Item => _container;
|
public IItem Item => _container;
|
||||||
public IItem BaseItem => _baseContainer;
|
public IItem BaseItem => _baseContainer;
|
||||||
|
|
||||||
@@ -81,6 +85,8 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
public Task Elements => GetElements();
|
public Task Elements => GetElements();
|
||||||
public Task Items => GetItems();
|
public Task Items => GetItems();
|
||||||
|
|
||||||
|
public long? Size => BaseContainer is ContainerSizeContainer sizeContainer ? sizeContainer.Size : null;
|
||||||
|
|
||||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||||
@@ -133,10 +139,21 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
|
|
||||||
Container = container;
|
Container = container;
|
||||||
BaseContainer = container is ChildSearchContainer childSearchContainer ? childSearchContainer.BaseContainer : container;
|
BaseContainer = container is ChildSearchContainer childSearchContainer ? childSearchContainer.BaseContainer : container;
|
||||||
|
if (BaseContainer is ContainerSizeContainer sizeContainer)
|
||||||
|
{
|
||||||
|
sizeContainer.SizeChanged.Add(UpdateSize);
|
||||||
|
}
|
||||||
Container.Refreshed.Add(Container_Refreshed);
|
Container.Refreshed.Add(Container_Refreshed);
|
||||||
Container.LazyLoadingChanged.Add(Container_LazyLoadingChanged);
|
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 void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
||||||
|
|
||||||
public async Task Init(bool initializeChildren = true, CancellationToken token = default)
|
public async Task Init(bool initializeChildren = true, CancellationToken token = default)
|
||||||
|
|||||||
@@ -53,5 +53,7 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
|||||||
Mode = ItemPreviewMode.Unknown;
|
Mode = ItemPreviewMode.Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Destroy() => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
using FileTime.Avalonia.Models;
|
using FileTime.Avalonia.Models;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
namespace FileTime.Avalonia.ViewModels.ItemPreview
|
||||||
@@ -5,5 +6,6 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
|
|||||||
public interface IItemPreviewViewModel
|
public interface IItemPreviewViewModel
|
||||||
{
|
{
|
||||||
ItemPreviewMode Mode { get; }
|
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();
|
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))));
|
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();
|
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))));
|
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="1" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
|
||||||
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.Attributes}"/>
|
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.Attributes}"/>
|
||||||
</Grid>
|
</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"
|
<Grid ColumnDefinitions="50,50"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.Element}}">
|
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttibuteTypeConverter},ConverterParameter={x:Static models:AttibuteType.Element}}">
|
||||||
|
|||||||
@@ -286,26 +286,25 @@
|
|||||||
Fill="{DynamicResource ContentSeparatorBrush}" />
|
Fill="{DynamicResource ContentSeparatorBrush}" />
|
||||||
|
|
||||||
<Grid Grid.Column="4">
|
<Grid Grid.Column="4">
|
||||||
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
|
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNullConverter}}">
|
||||||
<ListBox
|
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
|
||||||
Classes="ContentListView"
|
<ListBox
|
||||||
IsEnabled="False"
|
Classes="ContentListView"
|
||||||
x:Name="ChildItems"
|
IsEnabled="False"
|
||||||
x:CompileBindings="False"
|
x:Name="ChildItems"
|
||||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items^}"
|
x:CompileBindings="False"
|
||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
Items="{Binding AppState.SelectedTab.ChildContainer.Items^}"
|
||||||
<ListBox.ItemTemplate>
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||||
<DataTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<local:ItemView/>
|
<DataTemplate>
|
||||||
</DataTemplate>
|
<local:ItemView/>
|
||||||
</ListBox.ItemTemplate>
|
</DataTemplate>
|
||||||
</ListBox>
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
x:CompileBindings="False"
|
x:CompileBindings="False"
|
||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||||
|
|
||||||
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNullConverter}}">
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="ChildEmpty"
|
x:Name="ChildEmpty"
|
||||||
Margin="10"
|
Margin="10"
|
||||||
@@ -315,26 +314,26 @@
|
|||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||||
Empty
|
Empty
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
RowDefinitions="Auto, Auto"
|
RowDefinitions="Auto, Auto"
|
||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
Text="There were some errors while opening container."
|
Text="There were some errors while opening container."
|
||||||
Foreground="{DynamicResource ErrorBrush}" />
|
Foreground="{DynamicResource ErrorBrush}" />
|
||||||
|
|
||||||
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
<TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsRepeater.ItemTemplate>
|
</ItemsRepeater.ItemTemplate>
|
||||||
</ItemsRepeater>
|
</ItemsRepeater>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -398,6 +397,85 @@
|
|||||||
<TextBlock Margin="0,0,10,0" Grid.Row="1" Text="Path"/>
|
<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}}"/>
|
<local:PathPresenter Grid.Column="1" Grid.Row="1" x:CompileBindings="False" DataContext="{Binding AppState.SelectedTab.ItemPreview.RealtiveParentPath,Converter={StaticResource PathPreformatter}}"/>
|
||||||
</Grid>
|
</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>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace FileTime.Providers.Local
|
|||||||
? new DirectoryInfo("/").GetDirectories()
|
? new DirectoryInfo("/").GetDirectories()
|
||||||
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
|
: 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)
|
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
|
using FileTime.Core.Providers.ContainerProperty;
|
||||||
using FileTime.Providers.Local.Interop;
|
using FileTime.Providers.Local.Interop;
|
||||||
|
|
||||||
namespace FileTime.Providers.Local
|
namespace FileTime.Providers.Local
|
||||||
{
|
{
|
||||||
public class LocalFolder : AbstractContainer<LocalContentProvider>, IContainer
|
public class LocalFolder : AbstractContainer<LocalContentProvider>, IContainer, IHaveCreatedAt, IHaveAttributes
|
||||||
{
|
{
|
||||||
public DirectoryInfo Directory { get; }
|
public DirectoryInfo Directory { get; }
|
||||||
|
|
||||||
public string Attributes => GetAttributes();
|
public string Attributes => GetAttributes();
|
||||||
|
|
||||||
public DateTime CreatedAt => Directory.CreationTime;
|
public DateTime? CreatedAt => Directory.CreationTime;
|
||||||
public override bool IsExists => Directory.Exists;
|
public override bool IsExists => Directory.Exists;
|
||||||
|
|
||||||
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer parent)
|
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer parent)
|
||||||
@@ -21,7 +22,6 @@ namespace FileTime.Providers.Local
|
|||||||
NativePath = Directory.FullName;
|
NativePath = Directory.FullName;
|
||||||
IsHidden = (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
IsHidden = (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||||
CanRename = true;
|
CanRename = true;
|
||||||
AllowRecursiveDeletion = true;
|
|
||||||
|
|
||||||
//TODO: Linux soft delete
|
//TODO: Linux soft delete
|
||||||
SupportsDirectoryLevelSoftDelete = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
SupportsDirectoryLevelSoftDelete = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace FileTime.Providers.Sftp
|
|||||||
_inputInterface = inputInterface;
|
_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)
|
public override async Task<IContainer> CreateContainerAsync(string name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace FileTime.Providers.Sftp
|
|||||||
NativePath = FullName = sftpContentProvider.Protocol + Constants.SeparatorChar + name;
|
NativePath = FullName = sftpContentProvider.Protocol + Constants.SeparatorChar + name;
|
||||||
|
|
||||||
CanDelete = SupportsDelete.True;
|
CanDelete = SupportsDelete.True;
|
||||||
|
AllowRecursiveDeletion = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => await ListDirectory(this, "");
|
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()
|
protected override async Task Init()
|
||||||
{
|
{
|
||||||
var servers = await _persistenceService.LoadServers();
|
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) ? "\\" : "/";
|
public static string GetNativePathSeparator() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/";
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace FileTime.Providers.Smb
|
|||||||
Username = username;
|
Username = username;
|
||||||
Password = password;
|
Password = password;
|
||||||
CanDelete = SupportsDelete.True;
|
CanDelete = SupportsDelete.True;
|
||||||
|
AllowRecursiveDeletion = false;
|
||||||
|
|
||||||
FullName = contentProvider.Protocol + Name;
|
FullName = contentProvider.Protocol + Name;
|
||||||
NativePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
NativePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
|
|||||||
Reference in New Issue
Block a user