Recursive container size scan

This commit is contained in:
2022-02-23 13:58:12 +01:00
parent 5734126c31
commit 697339768f
44 changed files with 1112 additions and 58 deletions

View File

@@ -44,6 +44,7 @@ namespace FileTime.App.Core.Command
Refresh,
Rename,
RunCommand,
ScanContainerSize,
ShowAllShotcut,
SoftDelete,
SwitchToLastTab,

View File

@@ -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()

View File

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

View File

@@ -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();
}
}
}
}

View File

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

View File

@@ -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();
}
}

View 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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Models
{
public interface IItemWithSize : IItem
{
long? Size { get; }
}
}

View File

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

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Providers.ContainerProperty
{
public interface IHaveAttributes
{
string Attributes { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Providers.ContainerProperty
{
public interface IHaveCreatedAt
{
DateTime? CreatedAt { get; }
}
}

View File

@@ -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() { }

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

View File

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

View File

@@ -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>

View File

@@ -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

View File

@@ -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),

View File

@@ -17,6 +17,7 @@ namespace FileTime.Avalonia.Converters
{
(long size, true) => ToSizeString(size, prec),
(long size, false) => ToSizeString(size),
(null, _) => "...",
_ => value
};
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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" />

View 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;
}
}
}

View File

@@ -4,6 +4,7 @@ namespace FileTime.Avalonia.Models
{
File,
Element,
Container
Container,
SizeContainer
}
}

View File

@@ -6,6 +6,7 @@ namespace FileTime.Avalonia.Models
Text,
Empty,
SearchContainer,
SearchElement
SearchElement,
SizeContainer,
}
}

View File

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

View File

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

View File

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

View File

@@ -53,5 +53,7 @@ namespace FileTime.Avalonia.ViewModels.ItemPreview
Mode = ItemPreviewMode.Unknown;
}
}
public Task Destroy() => Task.CompletedTask;
}
}

View File

@@ -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();
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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}}">

View File

@@ -286,26 +286,25 @@
Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid Grid.Column="4">
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
<ListBox
Classes="ContentListView"
IsEnabled="False"
x:Name="ChildItems"
x:CompileBindings="False"
Items="{Binding AppState.SelectedTab.ChildContainer.Items^}"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid IsVisible="{Binding AppState.SelectedTab.ItemPreview, Converter={StaticResource IsNullConverter}}">
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
<ListBox
Classes="ContentListView"
IsEnabled="False"
x:Name="ChildItems"
x:CompileBindings="False"
Items="{Binding AppState.SelectedTab.ChildContainer.Items^}"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<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}}">
<Grid
x:CompileBindings="False"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
<TextBlock
x:Name="ChildEmpty"
Margin="10"
@@ -315,26 +314,26 @@
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
Empty
</TextBlock>
</Grid>
<Grid
RowDefinitions="Auto, Auto"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<Grid
RowDefinitions="Auto, Auto"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
TextWrapping="Wrap"
Text="There were some errors while opening container."
Foreground="{DynamicResource ErrorBrush}" />
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
TextWrapping="Wrap"
Text="There were some errors while opening container."
Foreground="{DynamicResource ErrorBrush}" />
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid>
</Grid>
</Grid>
</Grid>
@@ -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>

View File

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

View File

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

View File

@@ -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)
{

View File

@@ -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, "");

View File

@@ -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) ? "\\" : "/";

View File

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