IconProvider, container exceptions, refactor

This commit is contained in:
2022-02-02 11:02:37 +01:00
parent c4bf417ba3
commit 3e553dd448
31 changed files with 597 additions and 132 deletions

View File

@@ -1,12 +1,11 @@
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Providers;
namespace FileTime.App.Core.Clipboard namespace FileTime.App.Core.Clipboard
{ {
public class Clipboard : IClipboard public class Clipboard : IClipboard
{ {
private readonly List<AbsolutePath> _content; private List<AbsolutePath> _content;
public IReadOnlyList<AbsolutePath> Content { get; } public IReadOnlyList<AbsolutePath> Content { get; }
public Type? CommandType { get; private set; } public Type? CommandType { get; private set; }
@@ -39,7 +38,7 @@ namespace FileTime.App.Core.Clipboard
public void Clear() public void Clear()
{ {
_content.Clear(); _content = new List<AbsolutePath>();
CommandType = null; CommandType = null;
} }

View File

@@ -4,6 +4,7 @@ namespace FileTime.Core.Models
{ {
public interface IContainer : IItem public interface IContainer : IItem
{ {
IReadOnlyList<Exception> Exceptions { get; }
Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default); Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default);
Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default); Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default);
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default); Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
@@ -16,6 +17,7 @@ namespace FileTime.Core.Models
Task<bool> IsExists(string name); Task<bool> IsExists(string name);
Task<IContainer> Clone(); Task<IContainer> Clone();
Task<bool> CanOpen();
bool IsLoaded { get; } bool IsLoaded { get; }

View File

@@ -29,6 +29,7 @@ namespace FileTime.Core.Models
public bool CanRename => BaseContainer.CanRename; public bool CanRename => BaseContainer.CanRename;
public IContentProvider Provider => BaseContainer.Provider; public IContentProvider Provider => BaseContainer.Provider;
public IReadOnlyList<Exception> Exceptions => BaseContainer.Exceptions;
public AsyncEventHandler Refreshed { get; } public AsyncEventHandler Refreshed { get; }
@@ -164,5 +165,6 @@ namespace FileTime.Core.Models
} }
public async Task Rename(string newName) => await BaseContainer.Rename(newName); public async Task Rename(string newName) => await BaseContainer.Rename(newName);
public async Task<bool> CanOpen() => await BaseContainer.CanOpen();
} }
} }

View File

@@ -29,6 +29,8 @@ namespace FileTime.Core.Providers
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public TopContainer(IEnumerable<IContentProvider> contentProviders) public TopContainer(IEnumerable<IContentProvider> contentProviders)
{ {
_contentProviders = new List<IContentProvider>(contentProviders); _contentProviders = new List<IContentProvider>(contentProviders);
@@ -62,5 +64,7 @@ namespace FileTime.Core.Providers
public Task<IContainer> Clone() => Task.FromResult((IContainer)this); public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -25,6 +25,7 @@ namespace FileTime.Core.Timeline
public IContentProvider Provider { get; } public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { get; } public IContentProvider VirtualProvider { get; }
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime) public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{ {
@@ -117,5 +118,6 @@ namespace FileTime.Core.Timeline
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}."); if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider); return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
} }
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -6,6 +6,8 @@ namespace FileTime.Core.Timeline
{ {
public class TimeProvider : IContentProvider public class TimeProvider : IContentProvider
{ {
private readonly PointInTime _pointInTime;
public bool IsLoaded => true; public bool IsLoaded => true;
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
@@ -22,7 +24,7 @@ namespace FileTime.Core.Timeline
public IContentProvider Provider => this; public IContentProvider Provider => this;
private readonly PointInTime _pointInTime; public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public TimeProvider(PointInTime pointInTime) public TimeProvider(PointInTime pointInTime)
{ {
@@ -85,5 +87,6 @@ namespace FileTime.Core.Timeline
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public void SetParent(IContainer container) { } public void SetParent(IContainer container) { }
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -120,6 +120,8 @@
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/> <converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
<converters:IsNullConverter x:Key="IsNullConverter"/> <converters:IsNullConverter x:Key="IsNullConverter"/>
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/> <converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
<converters:IsEmptyConverter x:Key="IsEmptyConverter"/>
<converters:IsEmptyConverter x:Key="IsNotEmptyConverter" Inverse="true"/>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
@@ -132,7 +134,7 @@
<Setter Property="FontSize" Value="16"/> <Setter Property="FontSize" Value="16"/>
</Style> </Style>
<Style Selector="TextBlock.SmallText"> <Style Selector="TextBlock.SmallText">
<Setter Property="FontSize" Value="16"/> <Setter Property="FontSize" Value="12"/>
</Style> </Style>
<Style Selector="ListBox.ContentListView"> <Style Selector="ListBox.ContentListView">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
@@ -145,5 +147,13 @@
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/> <ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Grid.PlacesItem">
<Setter Property="Background" Value="#01000000"/>
</Style>
<Style Selector="Grid.PlacesItem:pointerover">
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
</Style>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -0,0 +1,23 @@
using Avalonia.Data.Converters;
using System;
using System.Globalization;
namespace FileTime.Avalonia.Converters
{
public class IsEmptyConverter : IValueConverter
{
public bool Inverse { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
var result = value is string s && string.IsNullOrWhiteSpace(s);
if (Inverse) result = !result;
return result;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -5,26 +5,45 @@ using Avalonia.Svg.Skia;
using FileTime.Avalonia.IconProviders; using FileTime.Avalonia.IconProviders;
using FileTime.Avalonia.ViewModels; using FileTime.Avalonia.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Avalonia.Converters namespace FileTime.Avalonia.Converters
{ {
public class ItemToImageConverter : IValueConverter public class ItemToImageConverter : IValueConverter
{ {
private readonly IIconProvider _iconProvider;
public ItemToImageConverter()
{
_iconProvider = App.ServiceProvider.GetService<IIconProvider>()!;
}
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value == null) return null; if (value == null) return null;
IIconProvider converter = new MaterialIconProvider();
IItem item = value switch IItem item = value switch
{ {
ContainerViewModel container => container.Container, ContainerViewModel container => container.Container,
ElementViewModel element => element.Element, ElementViewModel element => element.Element,
IItem i => i,
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
}; };
var path = converter.GetImage(item)!; SvgSource? source;
var source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path, null); var path = _iconProvider.GetImage(item)!;
if (path.Type == Models.ImagePathType.Absolute)
{
source = SvgSource.Load<SvgSource>(path.Path!, null);
}
else if(path.Type == Models.ImagePathType.Raw)
{
return path.Image;
}
else
{
source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path.Path, null);
}
return new SvgImage { Source = source }; return new SvgImage { Source = source };
} }

View File

@@ -11,16 +11,17 @@
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.11" /> <PackageReference Include="Avalonia" Version="0.10.12" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.11" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.12" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.11" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.12" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.11" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.12" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.11.1" /> <PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.11.5" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MvvmGen" Version="1.1.2" /> <PackageReference Include="MvvmGen" Version="1.1.2" />
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.2.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" /> <ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />

View File

@@ -1,9 +1,11 @@
using FileTime.Avalonia.Models;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Avalonia.IconProviders namespace FileTime.Avalonia.IconProviders
{ {
public interface IIconProvider public interface IIconProvider
{ {
string GetImage(IItem item); ImagePath GetImage(IItem item);
bool EnableAdvancedIcons { get; set; }
} }
} }

View File

@@ -1,23 +1,30 @@
using Avalonia.Media.Imaging;
using FileTime.Avalonia.Misc;
using FileTime.Avalonia.Models;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
namespace FileTime.Avalonia.IconProviders namespace FileTime.Avalonia.IconProviders
{ {
public class MaterialIconProvider : IIconProvider public class MaterialIconProvider : IIconProvider
{ {
public string GetImage(IItem item) public bool EnableAdvancedIcons { get; set; } = true;
public ImagePath GetImage(IItem item)
{ {
var icon = "file.svg"; var icon = item is IContainer ? "folder.svg" : "file.svg";
if (item is IContainer)
if (EnableAdvancedIcons)
{ {
icon = "folder.svg"; if (item is IElement element)
}
else if (item is IElement element)
{ {
if(element is LocalFile localFile && element.FullName.EndsWith(".svg")) if (element is LocalFile localFile && (element.FullName?.EndsWith(".svg") ?? false))
{ {
return localFile.File.FullName; return new ImagePath(ImagePathType.Absolute, localFile.File.FullName);
} }
icon = !element.Name.Contains('.') icon = !element.Name.Contains('.')
? icon ? icon
@@ -27,7 +34,42 @@ namespace FileTime.Avalonia.IconProviders
_ => icon _ => icon
}; };
} }
return "/Assets/material/" + icon; /*else if (item is LocalFolder folder && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var file = new FileInfo(Path.Combine(folder.FullName, "desktop.ini"));
if (file.Exists)
{
var lines = File.ReadAllLines(file.FullName);
if (Array.Find(lines, l => l.StartsWith("iconresource", StringComparison.OrdinalIgnoreCase)) is string iconLine)
{
var nameLineValue = string.Join('=', iconLine.Split('=')[1..]);
var environemntVariables = Environment.GetEnvironmentVariables();
foreach (var keyo in environemntVariables.Keys)
{
if (keyo is string key && environemntVariables[key] is string value)
{
nameLineValue = nameLineValue.Replace($"%{key}%", value);
}
}
var parts = nameLineValue.Split(',');
if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId))
{
if (parsedResourceId < 0) parsedResourceId *= -1;
var extractedIcon = NativeMethodHelpers.GetIconResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
var extractedIconAsStream = new MemoryStream();
extractedIcon.Save(extractedIconAsStream);
extractedIconAsStream.Position = 0;
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
}
}
}
}*/
}
return new ImagePath(ImagePathType.Asset, "/Assets/material/" + icon);
} }
} }
} }

View File

@@ -0,0 +1,29 @@
using System.IO;
using Avalonia.Media.Imaging;
using FileTime.Avalonia.Misc;
using FileTime.Avalonia.Models;
using FileTime.Core.Models;
using FileTime.Providers.Local;
namespace FileTime.Avalonia.IconProviders
{
public class SystemIconProvider : IIconProvider
{
public bool EnableAdvancedIcons { get; set; }
public ImagePath GetImage(IItem item)
{
if (item is LocalFile file)
{
var extractedIconAsStream = new MemoryStream();
var extractedIcon = System.Drawing.Icon.ExtractAssociatedIcon(file.File.FullName);
extractedIcon.Save(extractedIconAsStream);
extractedIconAsStream.Position = 0;
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
}
var icon = item is IContainer ? "folder.svg" : "file.svg";
return new ImagePath(ImagePathType.Asset, "/Assets/material/" + icon);
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace FileTime.Avalonia.Misc
{
public static class WindowsNativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
[DllImport("user32.dll")]
public static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
[DllImport("kernel32.dll")]
public static extern int FreeLibrary(IntPtr hLibModule);
[DllImport("shell32.dll")]
public static extern IntPtr ExtractAssociatedIcon(IntPtr hInst, StringBuilder lpIconPath, out ushort lpiIcon);
}
public static class NativeMethodHelpers
{
public static string GetStringResource(string fileName, uint resourceId)
{
IntPtr? handle = null;
try
{
handle = WindowsNativeMethods.LoadLibrary(fileName);
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
int length = WindowsNativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
return buffer.ToString(0, length); //Return the part of the buffer that was used.
}
finally
{
if (handle is IntPtr validHandle)
{
WindowsNativeMethods.FreeLibrary(validHandle);
}
}
}
public static Icon GetIconResource(string fileName, uint resourceId)
{
IntPtr? handle = null;
try
{
handle = WindowsNativeMethods.LoadLibrary(fileName);
IntPtr handle2 = WindowsNativeMethods.LoadIcon(handle.Value, new IntPtr(resourceId));
return Icon.FromHandle(handle2);
}
finally
{
if (handle is IntPtr validHandle)
{
WindowsNativeMethods.FreeLibrary(validHandle);
}
}
}
/*public static Icon GetAssociatedIcon()
{
ushort uicon;
StringBuilder strB = new StringBuilder(fileName);
IntPtr handle = WindowsNativeMethods.ExtractAssociatedIcon(this.Handle, strB, out uicon);
return Icon.FromHandle(handle);
}*/
}
}

View File

@@ -0,0 +1,21 @@
namespace FileTime.Avalonia.Models
{
public class ImagePath
{
public string? Path { get; }
public ImagePathType Type { get; }
public object? Image { get; }
public ImagePath(ImagePathType type, string path)
{
Path = path;
Type = type;
}
public ImagePath(ImagePathType type, object image)
{
Image = image;
Type = type;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace FileTime.Avalonia.Models
{
public enum ImagePathType
{
Asset,
Absolute,
Raw
}
}

View File

@@ -0,0 +1,16 @@
using FileTime.Core.Models;
namespace FileTime.Avalonia.Models
{
public class PlaceInfo
{
public string Name { get; }
public IContainer Container { get; }
public PlaceInfo(string name, IContainer container)
{
Name = name;
Container = container;
}
}
}

View File

@@ -19,6 +19,9 @@ namespace FileTime.Avalonia.Models
[Property] [Property]
private string _fullName; private string _fullName;
[Property]
private string _label;
[Property] [Property]
private long _size; private long _size;
@@ -37,11 +40,17 @@ namespace FileTime.Avalonia.Models
_driveInfo = driveInfo; _driveInfo = driveInfo;
_container = container; _container = container;
Name = container.Name; Refresh();
FullName = container.FullName; }
Size = driveInfo.TotalSize;
Free = driveInfo.AvailableFreeSpace; private void Refresh()
Used = driveInfo.TotalSize - driveInfo.AvailableFreeSpace; {
Name = _container.Name;
FullName = _container.FullName;
Label = _driveInfo.VolumeLabel;
Size = _driveInfo.TotalSize;
Free = _driveInfo.AvailableFreeSpace;
Used = _driveInfo.TotalSize - _driveInfo.AvailableFreeSpace;
} }
} }
} }

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using FileTime.Avalonia.Misc;
#pragma warning disable CA1416 #pragma warning disable CA1416
namespace FileTime.Avalonia.Services namespace FileTime.Avalonia.Services
@@ -50,7 +51,7 @@ namespace FileTime.Avalonia.Services
{ {
if (parsedResourceId < 0) parsedResourceId *= -1; if (parsedResourceId < 0) parsedResourceId *= -1;
text = GetStringResource(parts[0], (uint)parsedResourceId); text = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
} }
} }
else else
@@ -231,37 +232,6 @@ namespace FileTime.Avalonia.Services
return (resultX, resultY); return (resultX, resultY);
} }
static string GetStringResource(string fileName, uint resourceId)
{
IntPtr? handle = null;
try
{
handle = NativeMethods.LoadLibrary(fileName);
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
int length = NativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
return buffer.ToString(0, length); //Return the part of the buffer that was used.
}
finally
{
if (handle is IntPtr validHandle)
{
NativeMethods.FreeLibrary(validHandle);
}
}
}
static class NativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
[DllImport("kernel32.dll")]
public static extern int FreeLibrary(IntPtr hLibModule);
}
} }
} }
#pragma warning restore CA1416 #pragma warning restore CA1416

View File

@@ -1,5 +1,6 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FileTime.Avalonia.Application; using FileTime.Avalonia.Application;
using FileTime.Avalonia.IconProviders;
using FileTime.Avalonia.Services; using FileTime.Avalonia.Services;
using FileTime.Avalonia.ViewModels; using FileTime.Avalonia.ViewModels;
using FileTime.Core.Command; using FileTime.Core.Command;
@@ -21,7 +22,8 @@ namespace FileTime.Avalonia
{ {
serviceCollection = serviceCollection serviceCollection = serviceCollection
.AddLogging() .AddLogging()
.AddSingleton<ItemNameConverterService>(); .AddSingleton<ItemNameConverterService>()
.AddSingleton<IIconProvider, MaterialIconProvider>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {

View File

@@ -20,7 +20,7 @@ namespace FileTime.Avalonia.ViewModels
private bool _disposed; private bool _disposed;
private bool _isRefreshing; private bool _isRefreshing;
private bool _isInitialized; private bool _isInitialized;
private INewItemProcessor _newItemProcessor; private readonly INewItemProcessor _newItemProcessor;
[Property] [Property]
private IContainer _container; private IContainer _container;
@@ -37,17 +37,19 @@ namespace FileTime.Avalonia.ViewModels
[Property] [Property]
private ContainerViewModel? _parent; private ContainerViewModel? _parent;
[Property]
private List<Exception> _exceptions;
public IItem Item => _container; public IItem Item => _container;
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>(); private ObservableCollection<ContainerViewModel> _containers = new();
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>(); private ObservableCollection<ElementViewModel> _elements = new();
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>(); private ObservableCollection<IItemViewModel> _items = new();
public List<IItemViewModel> ChildrenToAdopt { get; } = new List<IItemViewModel>(); public List<IItemViewModel> ChildrenToAdopt { get; } = new List<IItemViewModel>();
[PropertyInvalidate(nameof(IsSelected))] [PropertyInvalidate(nameof(IsSelected))]
[PropertyInvalidate(nameof(IsAlternative))] [PropertyInvalidate(nameof(IsAlternative))]
[PropertyInvalidate(nameof(IsMarked))] [PropertyInvalidate(nameof(IsMarked))]
@@ -64,7 +66,7 @@ namespace FileTime.Avalonia.ViewModels
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this); public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
[Obsolete] [Obsolete($"This property is for databinding only, use {nameof(GetContainers)} method instead.")]
public ObservableCollection<ContainerViewModel> Containers public ObservableCollection<ContainerViewModel> Containers
{ {
get get
@@ -72,9 +74,17 @@ namespace FileTime.Avalonia.ViewModels
if (!_isInitialized) Task.Run(Refresh); if (!_isInitialized) Task.Run(Refresh);
return _containers; return _containers;
} }
set
{
if (value != _containers)
{
_containers = value;
OnPropertyChanged(nameof(Containers));
}
}
} }
[Obsolete] [Obsolete($"This property is for databinding only, use {nameof(GetElements)} method instead.")]
public ObservableCollection<ElementViewModel> Elements public ObservableCollection<ElementViewModel> Elements
{ {
get get
@@ -82,9 +92,16 @@ namespace FileTime.Avalonia.ViewModels
if (!_isInitialized) Task.Run(Refresh); if (!_isInitialized) Task.Run(Refresh);
return _elements; return _elements;
} }
set
{
if (value != _elements)
{
_elements = value;
OnPropertyChanged(nameof(Elements));
} }
}
[Obsolete] }
[Obsolete($"This property is for databinding only, use {nameof(GetItems)} method instead.")]
public ObservableCollection<IItemViewModel> Items public ObservableCollection<IItemViewModel> Items
{ {
get get
@@ -92,6 +109,14 @@ namespace FileTime.Avalonia.ViewModels
if (!_isInitialized) Task.Run(Refresh); if (!_isInitialized) Task.Run(Refresh);
return _items; return _items;
} }
set
{
if (value != _items)
{
_items = value;
OnPropertyChanged(nameof(Items));
}
}
} }
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService) public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
@@ -125,44 +150,41 @@ namespace FileTime.Avalonia.ViewModels
_isInitialized = true; _isInitialized = true;
Exceptions = new List<Exception>();
try try
{ {
_isRefreshing = true; _isRefreshing = true;
var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList(); var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList(); var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
Exceptions = new List<Exception>(_container.Exceptions);
var containersToRemove = _containers.Except(containers); foreach (var containerToRemove in _containers.Except(containers))
_containers.Clear();
_elements.Clear();
_items.Clear();
foreach (var containerToRemove in containersToRemove)
{ {
containerToRemove?.Dispose(); containerToRemove?.Dispose();
} }
if (initializeChildren)
{
foreach (var container in containers) foreach (var container in containers)
{ {
if (initializeChildren) await container.Init(false); await container.Init(false);
_containers.Add(container);
_items.Add(container);
} }
foreach (var element in elements)
{
_elements.Add(element);
_items.Add(element);
} }
for (var i = 0; i < _items.Count; i++) for (var i = 0; i < _items.Count; i++)
{ {
_items[i].IsAlternative = i % 2 == 1; _items[i].IsAlternative = i % 2 == 1;
} }
Containers = new ObservableCollection<ContainerViewModel>(containers);
Elements = new ObservableCollection<ElementViewModel>(elements);
Items = new ObservableCollection<IItemViewModel>(containers.Cast<IItemViewModel>().Concat(elements));
}
catch (Exception e)
{
_exceptions.Add(e);
} }
catch { }
await _newItemProcessor.UpdateMarkedItems(this); await _newItemProcessor.UpdateMarkedItems(this);
@@ -193,9 +215,9 @@ namespace FileTime.Avalonia.ViewModels
} }
} }
_containers.Clear(); _containers = new ObservableCollection<ContainerViewModel>();
_elements.Clear(); _elements = new ObservableCollection<ElementViewModel>();
_items.Clear(); _items = new ObservableCollection<IItemViewModel>();
} }
public async Task<ObservableCollection<ContainerViewModel>> GetContainers() public async Task<ObservableCollection<ContainerViewModel>> GetContainers()

View File

@@ -23,6 +23,9 @@ using Microsoft.Extensions.DependencyInjection;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.Core.Providers; using FileTime.Core.Providers;
using Syroot.Windows.IO;
using FileTime.Avalonia.IconProviders;
using Avalonia.Threading;
namespace FileTime.Avalonia.ViewModels namespace FileTime.Avalonia.ViewModels
{ {
@@ -42,6 +45,8 @@ namespace FileTime.Avalonia.ViewModels
private IClipboard _clipboard; private IClipboard _clipboard;
private TimeRunner _timeRunner; private TimeRunner _timeRunner;
private IEnumerable<IContentProvider> _contentProviders; private IEnumerable<IContentProvider> _contentProviders;
private IIconProvider _iconProvider;
private Func<Task>? _inputHandler; private Func<Task>? _inputHandler;
[Property] [Property]
@@ -59,9 +64,15 @@ namespace FileTime.Avalonia.ViewModels
[Property] [Property]
private List<RootDriveInfo> _rootDriveInfos; private List<RootDriveInfo> _rootDriveInfos;
[Property]
private List<PlaceInfo> _places;
[Property] [Property]
private string _messageBoxText; private string _messageBoxText;
[Property]
private ObservableCollection<string> _popupTexts = new ObservableCollection<string>();
public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands; public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands;
async partial void OnInitialize() async partial void OnInitialize()
@@ -69,8 +80,9 @@ namespace FileTime.Avalonia.ViewModels
_clipboard = App.ServiceProvider.GetService<IClipboard>()!; _clipboard = App.ServiceProvider.GetService<IClipboard>()!;
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!; _timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
_contentProviders = App.ServiceProvider.GetService<IEnumerable<IContentProvider>>()!; _contentProviders = App.ServiceProvider.GetService<IEnumerable<IContentProvider>>()!;
_iconProvider = App.ServiceProvider.GetService<IIconProvider>()!;
var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!; var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!;
inputInterface.InputHandler = ReadInputs2; inputInterface.InputHandler = ReadInputs;
App.ServiceProvider.GetService<TopContainer>(); App.ServiceProvider.GetService<TopContainer>();
_timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands)); _timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands));
@@ -103,8 +115,68 @@ namespace FileTime.Avalonia.ViewModels
driveInfos.Add(driveInfo); driveInfos.Add(driveInfo);
} }
} }
RootDriveInfos = driveInfos.OrderBy(d => d.Name).ToList(); RootDriveInfos = driveInfos.OrderBy(d => d.Name).ToList();
var places = new List<PlaceInfo>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var placesFolders = new List<KnownFolder>()
{
KnownFolders.Profile,
KnownFolders.Desktop,
KnownFolders.DocumentsLocalized,
KnownFolders.DownloadsLocalized,
KnownFolders.Music,
KnownFolders.Pictures,
KnownFolders.Videos,
};
foreach (var placesFolder in placesFolders)
{
var possibleContainer = await LocalContentProvider.GetByPath(placesFolder.Path);
if (possibleContainer is IContainer container)
{
var name = container.Name;
if (await container.GetByPath("desktop.ini") is LocalFile element)
{
var lines = File.ReadAllLines(element.File.FullName);
if (Array.Find(lines, l => l.StartsWith("localizedresourcename", StringComparison.OrdinalIgnoreCase)) is string nameLine)
{
var nameLineValue = string.Join('=', nameLine.Split('=')[1..]);
var environemntVariables = Environment.GetEnvironmentVariables();
foreach (var keyo in environemntVariables.Keys)
{
if (keyo is string key && environemntVariables[key] is string value)
{
nameLineValue = nameLineValue.Replace($"%{key}%", value);
}
}
if (nameLineValue.StartsWith("@"))
{
var parts = nameLineValue[1..].Split(',');
if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId))
{
if (parsedResourceId < 0) parsedResourceId *= -1;
name = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
}
}
else
{
name = nameLineValue;
}
}
}
places.Add(new PlaceInfo(name, container));
}
}
}
else
{
throw new Exception("TODO linux places");
}
Places = places;
} }
private async Task<IContainer?> GetContainerForWindowsDrive(DriveInfo drive) private async Task<IContainer?> GetContainerForWindowsDrive(DriveInfo drive)
@@ -123,6 +195,12 @@ namespace FileTime.Avalonia.ViewModels
await AppState.SelectedTab.Open(); await AppState.SelectedTab.Open();
} }
public async Task OpenContainer(IContainer container)
{
AppState.RapidTravelText = "";
await AppState.SelectedTab.OpenContainer(container);
}
public async Task OpenOrRun() public async Task OpenOrRun()
{ {
if (AppState.SelectedTab.SelectedItem is ContainerViewModel) if (AppState.SelectedTab.SelectedItem is ContainerViewModel)
@@ -530,6 +608,20 @@ namespace FileTime.Avalonia.ViewModels
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task ToggleAdvancedIcons()
{
_iconProvider.EnableAdvancedIcons = !_iconProvider.EnableAdvancedIcons;
var text = "Advanced icons are: " + (_iconProvider.EnableAdvancedIcons ? "ON" : "OFF");
_popupTexts.Add(text);
Task.Run(async () =>
{
await Task.Delay(5000);
await Dispatcher.UIThread.InvokeAsync(() => _popupTexts.Remove(text));
});
return Task.CompletedTask;
}
[Command] [Command]
public async void ProcessInputs() public async void ProcessInputs()
{ {
@@ -714,7 +806,7 @@ namespace FileTime.Avalonia.ViewModels
_inputHandler = inputHandler; _inputHandler = inputHandler;
} }
public async Task<string?[]> ReadInputs2(IEnumerable<Core.Interactions.InputElement> fields) public async Task<string?[]> ReadInputs(IEnumerable<Core.Interactions.InputElement> fields)
{ {
var waiting = true; var waiting = true;
var result = new string[0]; var result = new string[0];
@@ -912,9 +1004,14 @@ namespace FileTime.Avalonia.ViewModels
RefreshCurrentLocation), RefreshCurrentLocation),
new CommandBinding( new CommandBinding(
"go to", "go to",
FileTime.App.Core.Command.Commands.Refresh, FileTime.App.Core.Command.Commands.Dummy,
new KeyWithModifiers[]{new KeyWithModifiers(Key.L, ctrl: true)}, new KeyWithModifiers[]{new KeyWithModifiers(Key.L, ctrl: true)},
GoToContainer), GoToContainer),
new CommandBinding(
"toggle advanced icons",
FileTime.App.Core.Command.Commands.Dummy,
new KeyWithModifiers[]{new KeyWithModifiers(Key.Z),new KeyWithModifiers(Key.I)},
ToggleAdvancedIcons),
}; };
var universalCommandBindings = new List<CommandBinding>() var universalCommandBindings = new List<CommandBinding>()
{ {

View File

@@ -40,7 +40,7 @@
</ItemsControl> </ItemsControl>
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}"> <Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
<Grid ColumnDefinitions="180,40"> <Grid ColumnDefinitions="180,45">
<TextBlock Text="{Binding Item.CreatedAt}"/> <TextBlock Text="{Binding Item.CreatedAt}"/>
<TextBlock Grid.Column="1" Text="{Binding Item.Attributes}"/> <TextBlock Grid.Column="1" Text="{Binding Item.Attributes}"/>
</Grid> </Grid>

View File

@@ -27,7 +27,6 @@
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@@ -36,7 +35,6 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
Classes="NormalText"
Text="Drives" /> Text="Drives" />
<ItemsRepeater <ItemsRepeater
@@ -53,25 +51,35 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Source="{SvgImage /Assets/material/folder.svg}" /> Source="{SvgImage /Assets/material/folder.svg}" />
<TextBlock <StackPanel
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Classes="NormalText" Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="{Binding FullName}" /> Text="{Binding FullName}" />
<TextBlock
Margin="5,0,0,0"
VerticalAlignment="Center"
Classes="SmallText"
Text="{Binding Label}" IsVisible="{Binding Label,Converter={StaticResource IsNotEmptyConverter}}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" <StackPanel HorizontalAlignment="Right"
Grid.Column="2" Grid.Column="2"
Orientation="Horizontal" Orientation="Horizontal"
VerticalAlignment="Center"> VerticalAlignment="Center">
<TextBlock Classes="SmallText" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> <TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
</TextBlock> </TextBlock>
<TextBlock Classes="SmallText" Text=" / "> <TextBlock Classes="SmallText" VerticalAlignment="Center" Text=" / ">
</TextBlock> </TextBlock>
<TextBlock Classes="SmallText" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> <TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
@@ -79,8 +87,40 @@
Grid.Column="1" Grid.Column="1"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Grid.Row="2" Grid.Row="2"
Maximum="{Binding Size}" Maximum="100"
Value="{Binding Used}" /> Value="{Binding UsedPercentage}" />
</Grid>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid>
</Border>
<Border Grid.Row="1" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="0,10" Margin="10">
<Grid RowDefinitions="Auto,Auto">
<TextBlock
Margin="10,0,10,10"
Text="Places" />
<ItemsRepeater
Grid.Row="1"
Items="{Binding Places}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Grid Classes="PlacesItem" PointerPressed="OnPlacePointerPressed" Cursor="Hand">
<StackPanel Orientation="Horizontal" Margin="10,5" HorizontalAlignment="Stretch">
<Image
Width="20"
Height="20"
VerticalAlignment="Center"
Source="{Binding Container,Converter={StaticResource ItemToImageConverter}}" />
<TextBlock
Margin="5,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}" />
</StackPanel>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
@@ -234,15 +274,27 @@
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<Grid
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
<TextBlock <TextBlock
x:Name="ChildEmpty" x:Name="ChildEmpty"
Margin="10" Margin="10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
FontWeight="Bold" FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"> IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
Empty Empty
</TextBlock> </TextBlock>
<ItemsRepeater Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
@@ -314,6 +366,28 @@
Content="No" /> Content="No" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<ItemsRepeater Items="{Binding PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<ItemsRepeater.Styles>
<Style Selector="TextBlock">
<Style.Animations>
<Animation Duration="0:0:1">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0.0"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1.0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</ItemsRepeater.Styles>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid> </Grid>
<Grid Grid.Row="3"> <Grid Grid.Row="3">

View File

@@ -4,6 +4,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using FileTime.Avalonia.Misc; using FileTime.Avalonia.Misc;
using FileTime.Avalonia.Models;
using FileTime.Avalonia.ViewModels; using FileTime.Avalonia.ViewModels;
using System.Linq; using System.Linq;
@@ -86,12 +87,25 @@ namespace FileTime.Avalonia.Views
} }
} }
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args) private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{ {
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First()) if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First())
{ {
inputText.Focus(); inputText.Focus();
} }
} }
private void OnPlacePointerPressed(object sender, PointerPressedEventArgs e)
{
if(!e.Handled
&& ViewModel != null
&& e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
&& sender is StyledElement control
&& control.DataContext is PlaceInfo placeInfo)
{
ViewModel.OpenContainer(placeInfo.Container).Wait();
e.Handled = true;
}
}
} }
} }

View File

@@ -29,6 +29,7 @@ namespace FileTime.Providers.Local
public bool IsCaseInsensitive { get; } public bool IsCaseInsensitive { get; }
public bool CanDelete => false; public bool CanDelete => false;
public bool CanRename => false; public bool CanRename => false;
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public LocalContentProvider(ILogger<LocalContentProvider> logger) public LocalContentProvider(ILogger<LocalContentProvider> logger)
{ {
@@ -93,5 +94,6 @@ namespace FileTime.Providers.Local
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -10,6 +10,7 @@ namespace FileTime.Providers.Local
private IReadOnlyList<IItem>? _items; private IReadOnlyList<IItem>? _items;
private IReadOnlyList<IContainer>? _containers; private IReadOnlyList<IContainer>? _containers;
private IReadOnlyList<IElement>? _elements; private IReadOnlyList<IElement>? _elements;
private List<Exception> _exceptions;
private readonly IContainer? _parent; private readonly IContainer? _parent;
public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
@@ -30,12 +31,16 @@ namespace FileTime.Providers.Local
public string Attributes => GetAttributes(); public string Attributes => GetAttributes();
public DateTime CreatedAt => Directory.CreationTime; public DateTime CreatedAt => Directory.CreationTime;
public IReadOnlyList<Exception> Exceptions { get; }
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent) public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent)
{ {
Directory = directory; Directory = directory;
_parent = parent; _parent = parent;
_exceptions = new List<Exception>();
Exceptions = _exceptions.AsReadOnly();
Name = directory.Name.TrimEnd(Path.DirectorySeparatorChar); Name = directory.Name.TrimEnd(Path.DirectorySeparatorChar);
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
Provider = contentProvider; Provider = contentProvider;
@@ -49,13 +54,17 @@ namespace FileTime.Providers.Local
{ {
_containers = new List<IContainer>(); _containers = new List<IContainer>();
_elements = new List<IElement>(); _elements = new List<IElement>();
_exceptions.Clear();
try try
{ {
_containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly(); _containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
_elements = Directory.GetFiles().Select(f => new LocalFile(f, this, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly(); _elements = Directory.GetFiles().Select(f => new LocalFile(f, this, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly();
} }
catch { } catch (Exception e)
{
_exceptions.Add(e);
}
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly(); _items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty); Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty);
@@ -144,5 +153,6 @@ namespace FileTime.Providers.Local
+ ((Directory.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-"); + ((Directory.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
} }
} }
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -25,6 +25,7 @@ namespace FileTime.Providers.Smb
public IContentProvider Provider => this; public IContentProvider Provider => this;
public bool CanDelete => false; public bool CanDelete => false;
public bool CanRename => false; public bool CanRename => false;
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
@@ -98,5 +99,6 @@ namespace FileTime.Providers.Smb
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -27,6 +27,7 @@ namespace FileTime.Providers.Smb
public bool CanRename => true; public bool CanRename => true;
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent) public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent)
{ {
@@ -119,5 +120,6 @@ namespace FileTime.Providers.Smb
if (_elements == null) await Refresh(); if (_elements == null) await Refresh();
return _elements; return _elements;
} }
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -32,8 +32,9 @@ namespace FileTime.Providers.Smb
public SmbContentProvider Provider { get; } public SmbContentProvider Provider { get; }
IContentProvider IItem.Provider => Provider; IContentProvider IItem.Provider => Provider;
public bool CanDelete => false; public bool CanDelete => true;
public bool CanRename => false; public bool CanRename => false;
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
@@ -186,5 +187,6 @@ namespace FileTime.Providers.Smb
} }
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }

View File

@@ -27,6 +27,7 @@ namespace FileTime.Providers.Smb
public bool CanRename => false; public bool CanRename => false;
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext) public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext)
{ {
@@ -156,5 +157,6 @@ namespace FileTime.Providers.Smb
} }
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true);
} }
} }