Favorites
This commit is contained in:
@@ -39,6 +39,7 @@ namespace FileTime.App.Core.Command
|
||||
PasteMerge,
|
||||
PasteOverwrite,
|
||||
PasteSkip,
|
||||
PinFavorite,
|
||||
PreviousTimelineBlock,
|
||||
PreviousTimelineCommand,
|
||||
Refresh,
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace FileTime.App.Core.Models
|
||||
{
|
||||
public interface IHaveContainer
|
||||
{
|
||||
IContainer Container { get; }
|
||||
IContainer? Container { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using FileTime.Core.ContainerSizeScanner;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Favorites;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Providers.Sftp;
|
||||
using FileTime.Providers.Smb;
|
||||
@@ -29,6 +30,7 @@ namespace FileTime.App.Core
|
||||
.AddLocalServices()
|
||||
.AddSmbServices()
|
||||
.AddSftpServices()
|
||||
.AddFavoriteServices()
|
||||
.RegisterCommandHandlers();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Favorites\FileTime.Providers.Favorites.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Sftp\FileTime.Providers.Sftp.csproj" />
|
||||
<ProjectReference Include="..\..\Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj" />
|
||||
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
|
||||
@@ -337,6 +337,26 @@ namespace FileTime.Core.Components
|
||||
await SetCurrentLocation(childContainer);
|
||||
}
|
||||
}
|
||||
else if (_currentSelectedItem is ISymlinkElement symlinkElement)
|
||||
{
|
||||
if (symlinkElement.RealItem is IContainer realContainer)
|
||||
{
|
||||
await SetCurrentLocation(realContainer);
|
||||
}
|
||||
else if (symlinkElement.RealItem is IElement realElement)
|
||||
{
|
||||
if (realElement.GetParent() is IContainer parent)
|
||||
{
|
||||
await SetCurrentLocation(parent);
|
||||
if (await _currentLocation.IsExistsAsync(realElement.Name))
|
||||
{
|
||||
var newRealElement = await _currentLocation.GetByPath(realElement.Name);
|
||||
|
||||
if (newRealElement != null) await SetCurrentSelectedItem(newRealElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container) => await SetCurrentLocation(container);
|
||||
|
||||
@@ -7,12 +7,10 @@ namespace FileTime.Core.ContainerSizeScanner
|
||||
public class ContainerScanSnapshotProvider : ContentProviderBase<ContainerScanSnapshotProvider>
|
||||
{
|
||||
|
||||
public ContainerScanSnapshotProvider() : base("size", null, "size://", false)
|
||||
public ContainerScanSnapshotProvider() : base("size", "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();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace FileTime.Core.ContainerSizeScanner
|
||||
|
||||
public override Task<long?> GetElementSize(CancellationToken token = default) => Task.FromResult((long?)Size);
|
||||
|
||||
public override string GetPrimaryAttributeText() => "";
|
||||
public override string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
31
src/Core/FileTime.Core/Models/AbsolutePathDto.cs
Normal file
31
src/Core/FileTime.Core/Models/AbsolutePathDto.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class AbsolutePathDto
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
public AbsolutePathType? Type { get; set; }
|
||||
public string? ContentProviderName { get; set; }
|
||||
public string? VirtualContentProviderName { get; set; }
|
||||
|
||||
public AbsolutePathDto() { }
|
||||
public AbsolutePathDto(AbsolutePath path)
|
||||
{
|
||||
Path = path.Path;
|
||||
Type = path.Type;
|
||||
ContentProviderName = path.ContentProvider.Name;
|
||||
VirtualContentProviderName = path.VirtualContentProvider?.Name;
|
||||
}
|
||||
|
||||
public AbsolutePath Resolve(IEnumerable<IContentProvider> providers)
|
||||
{
|
||||
var contentProvider = providers.FirstOrDefault(p => p.Name == ContentProviderName) ?? throw new Exception($"Could not found content provider with name {ContentProviderName}");
|
||||
var virtualContentProvider = VirtualContentProviderName != null ? providers.FirstOrDefault(p => p.Name == VirtualContentProviderName) : null;
|
||||
|
||||
if (Path is null) throw new Exception(nameof(Path) + " can not be null.");
|
||||
if (Type is not AbsolutePathType type) throw new Exception(nameof(Type) + " can not be null.");
|
||||
return new AbsolutePath(contentProvider, Path, type, virtualContentProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace FileTime.Core.Models
|
||||
public interface IElement : IItem
|
||||
{
|
||||
bool IsSpecial { get; }
|
||||
string GetPrimaryAttributeText();
|
||||
string? GetPrimaryAttributeText();
|
||||
Task<string> GetContent(CancellationToken token = default);
|
||||
Task<long?> GetElementSize(CancellationToken token = default);
|
||||
|
||||
|
||||
7
src/Core/FileTime.Core/Models/ISymlinkElement.cs
Normal file
7
src/Core/FileTime.Core/Models/ISymlinkElement.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface ISymlinkElement
|
||||
{
|
||||
IItem RealItem { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Threading.Tasks;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace FileTime.Core.Providers
|
||||
public abstract Task<long?> GetElementSize(CancellationToken token = default);
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
public abstract string GetPrimaryAttributeText();
|
||||
public abstract string? GetPrimaryAttributeText();
|
||||
|
||||
public abstract Task Rename(string newName);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@ namespace FileTime.Core.Providers
|
||||
protected List<IContainer>? RootContainers { get; private set; }
|
||||
public override bool IsExists => true;
|
||||
|
||||
public virtual bool SupportsContentStreams { get; }
|
||||
|
||||
public virtual string Protocol { get; }
|
||||
|
||||
protected ContentProviderBase(
|
||||
string name,
|
||||
string? fullName,
|
||||
string protocol,
|
||||
bool supportsContentStreams)
|
||||
bool supportsContentStreams,
|
||||
string? fullName = null)
|
||||
: base(name, fullName)
|
||||
{
|
||||
Protocol = protocol;
|
||||
@@ -31,11 +35,7 @@ namespace FileTime.Core.Providers
|
||||
CanDelete = SupportsDelete.False;
|
||||
}
|
||||
|
||||
public virtual bool SupportsContentStreams { get; }
|
||||
|
||||
public virtual string Protocol { get; }
|
||||
|
||||
public abstract Task<bool> CanHandlePath(string path);
|
||||
public virtual Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
public override IContainer? GetParent() => _parent;
|
||||
|
||||
public void SetParent(IContainer parent) => _parent = parent;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace FileTime.Core.Search
|
||||
|
||||
public override async Task<long?> GetElementSize(CancellationToken token = default) => await BaseElement.GetElementSize(token);
|
||||
|
||||
public override string GetPrimaryAttributeText() => BaseElement.GetPrimaryAttributeText();
|
||||
public override string? GetPrimaryAttributeText() => BaseElement.GetPrimaryAttributeText();
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
public string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace FileTime.Core.Timeline
|
||||
{
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime) : base("time", null, "time2://", false)
|
||||
public TimeProvider(PointInTime pointInTime) : base("time", "time2://", false)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Sftp", "
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InitableService", "Core\InitableService\InitableService.csproj", "{B1520189-8646-4DE8-B5C9-46AE04B4D01C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Favorites", "Providers\FileTime.Providers.Favorites\FileTime.Providers.Favorites.csproj", "{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -337,6 +339,26 @@ Global
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -356,6 +378,7 @@ Global
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966} = {0D2B4BAA-0399-459C-B022-41DB7F408225}
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
{B1520189-8646-4DE8-B5C9-46AE04B4D01C} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
|
||||
{27326C9B-FCB2-4A4F-9CCA-598C5244E26A} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183}
|
||||
|
||||
@@ -11,6 +11,7 @@ using FileTime.Avalonia.Configuration;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Providers.Favorites;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -48,6 +49,9 @@ namespace FileTime.Avalonia.Application
|
||||
[Property]
|
||||
private bool _noCommandFound;
|
||||
|
||||
[Property]
|
||||
private List<IItem> _favoriteElements;
|
||||
|
||||
public List<KeyConfig> PreviousKeys { get; } = new();
|
||||
|
||||
public ObservableCollection<ParallelCommandsViewModel> TimelineCommands { get; } = new();
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace FileTime.Avalonia.Configuration
|
||||
new CommandBindingConfiguration(Commands.FindByName, new[] { Key.F, Key.N }),
|
||||
new CommandBindingConfiguration(Commands.FindByNameRegex, new[] { Key.F, Key.R }),
|
||||
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
|
||||
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.OemComma, ctrl: true)),
|
||||
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.L, ctrl: true)),
|
||||
new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }),
|
||||
new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }),
|
||||
new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }),
|
||||
@@ -72,6 +72,7 @@ namespace FileTime.Avalonia.Configuration
|
||||
new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }),
|
||||
new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }),
|
||||
new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }),
|
||||
new CommandBindingConfiguration(Commands.PinFavorite, new[] { Key.F, Key.P }),
|
||||
new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ),
|
||||
new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ),
|
||||
new CommandBindingConfiguration(Commands.Refresh, Key.R),
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\..\Core\InitableService\InitableService.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Favorites\FileTime.Providers.Favorites.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Update="Views\MainWindow.axaml">
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public class MaterialIconProvider : IIconProvider
|
||||
{
|
||||
|
||||
private static readonly Dictionary<string, string> _iconsByExtension = new();
|
||||
private static readonly Dictionary<string, string> _iconsByFileName = new();
|
||||
|
||||
@@ -38,6 +37,7 @@ namespace FileTime.Avalonia.IconProviders
|
||||
|
||||
public ImagePath GetImage(IItem item)
|
||||
{
|
||||
item = item is ISymlinkElement symlinkElement ? symlinkElement.RealItem : item;
|
||||
var icon = item is IContainer ? "folder.svg" : "file.svg";
|
||||
string? localPath = item switch
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace FileTime.Avalonia.Models
|
||||
public class PlaceInfo : IHaveContainer
|
||||
{
|
||||
public string Name { get; }
|
||||
public IContainer Container { get; }
|
||||
public IContainer? Container { get; }
|
||||
|
||||
public PlaceInfo(string name, IContainer container)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace FileTime.Avalonia.Models
|
||||
private readonly DriveInfo _driveInfo;
|
||||
private readonly IContainer _container;
|
||||
|
||||
public IContainer Container => _container;
|
||||
public IContainer? Container => _container;
|
||||
|
||||
[Property]
|
||||
private string _name;
|
||||
|
||||
@@ -26,6 +26,7 @@ using FileTime.Core.Providers;
|
||||
using FileTime.Core.Search;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Favorites;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Tools.Compression.Command;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -49,6 +50,7 @@ namespace FileTime.Avalonia.Services
|
||||
private readonly ILogger<CommandHandlerService> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ContainerScanSnapshotProvider _containerScanSnapshotProvider;
|
||||
private readonly FavoriteContentProvider _favoriteContentProvider;
|
||||
|
||||
public CommandHandlerService(
|
||||
AppState appState,
|
||||
@@ -62,7 +64,8 @@ namespace FileTime.Avalonia.Services
|
||||
ProgramsService programsService,
|
||||
ILogger<CommandHandlerService> logger,
|
||||
IServiceProvider serviceProvider,
|
||||
ContainerScanSnapshotProvider containerScanSnapshotProvider)
|
||||
ContainerScanSnapshotProvider containerScanSnapshotProvider,
|
||||
FavoriteContentProvider favoriteContentProvider)
|
||||
{
|
||||
_appState = appState;
|
||||
_localContentProvider = localContentProvider;
|
||||
@@ -76,6 +79,7 @@ namespace FileTime.Avalonia.Services
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
_containerScanSnapshotProvider = containerScanSnapshotProvider;
|
||||
_favoriteContentProvider = favoriteContentProvider;
|
||||
|
||||
_commandHandlers = new Dictionary<Commands, Func<Task>>
|
||||
{
|
||||
@@ -114,6 +118,7 @@ namespace FileTime.Avalonia.Services
|
||||
{Commands.PasteMerge, PasteMerge},
|
||||
{Commands.PasteOverwrite, PasteOverwrite},
|
||||
{Commands.PasteSkip, PasteSkip},
|
||||
{Commands.PinFavorite, PinFavorite},
|
||||
{Commands.PreviousTimelineBlock, SelectPreviousTimelineBlock},
|
||||
{Commands.PreviousTimelineCommand, SelectPreviousTimelineCommand},
|
||||
{Commands.Refresh, RefreshCurrentLocation},
|
||||
@@ -1011,5 +1016,30 @@ namespace FileTime.Avalonia.Services
|
||||
await OpenContainer(scanTask.Snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PinFavorite()
|
||||
{
|
||||
if (_appState.SelectedTab.SelectedItem is IItemViewModel selectedItemVM)
|
||||
{
|
||||
if (selectedItemVM.BaseItem is FavoriteElement favoriteElement)
|
||||
{
|
||||
favoriteElement.IsPinned = !favoriteElement.IsPinned;
|
||||
|
||||
_appState.FavoriteElements = GetFavoriteElements(_favoriteContentProvider).Select(f => f.BaseItem).ToList();
|
||||
await _favoriteContentProvider.SaveAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_dialogService.ShowToastMessage("Selected item is not a favorite element.");
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<FavoriteElement> GetFavoriteElements(FavoriteContainerBase container)
|
||||
{
|
||||
return container.Elements.Where(e => e.IsPinned).Concat(
|
||||
container.Containers.SelectMany(GetFavoriteElements)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.App.Core.Models;
|
||||
using FileTime.Core.Models;
|
||||
using InitableService;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
public class HistoryItemViewModel : IAsyncInitable<AbsolutePath>
|
||||
public class HistoryItemViewModel : IAsyncInitable<AbsolutePath>, IHaveContainer
|
||||
{
|
||||
public string? Name { get; private set; }
|
||||
public IContainer? Container { get; private set; }
|
||||
|
||||
@@ -22,6 +22,7 @@ using System.Threading;
|
||||
using Avalonia.Input;
|
||||
using System.Reflection;
|
||||
using FileTime.Core.Services;
|
||||
using FileTime.Providers.Favorites;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -72,6 +73,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
Title = "FileTime " + versionString;
|
||||
|
||||
var favoriteContentProvider = App.ServiceProvider.GetRequiredService<FavoriteContentProvider>();
|
||||
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||
var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!;
|
||||
inputInterface.InputHandler = _dialogService.ReadInputs;
|
||||
@@ -174,9 +176,22 @@ namespace FileTime.Avalonia.ViewModels
|
||||
throw new Exception("TODO linux places");
|
||||
}
|
||||
Places = places;
|
||||
|
||||
await favoriteContentProvider.InitIfNeeded();
|
||||
AppState.FavoriteElements = GetFavoriteElements(favoriteContentProvider).Select(f => f.BaseItem).ToList();
|
||||
|
||||
await Task.Delay(100);
|
||||
Loading = false;
|
||||
_logger?.LogInformation($"{nameof(MainPageViewModel)} initialized.");
|
||||
|
||||
|
||||
|
||||
static IEnumerable<FavoriteElement> GetFavoriteElements(FavoriteContainerBase container)
|
||||
{
|
||||
return container.Elements.Where(e => e.IsPinned).Concat(
|
||||
container.Containers.SelectMany(GetFavoriteElements)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Task UpdateParallelCommands(object? sender, IReadOnlyList<ReadOnlyParallelCommands> parallelCommands, CancellationToken token)
|
||||
@@ -269,7 +284,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
|
||||
{
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
KeyInputHandlerService.ProcessKeyDown(key, keyModifiers, setHandled);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto">
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
|
||||
<Border CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="10" Margin="10">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
@@ -149,6 +149,38 @@
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="2" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="0,10" Margin="10">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Margin="10,0,10,10"
|
||||
Text="Favorites" />
|
||||
|
||||
<ItemsRepeater
|
||||
Grid.Row="1"
|
||||
Items="{Binding AppState.FavoriteElements}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Classes="SidebarContainerPresenter" PointerPressed="OnHasContainerPointerPressed" Cursor="Hand">
|
||||
<StackPanel Orientation="Horizontal" Margin="10,5" HorizontalAlignment="Stretch">
|
||||
<Image
|
||||
Width="20"
|
||||
Height="20"
|
||||
VerticalAlignment="Center"
|
||||
Source="{Binding Converter={StaticResource ItemToImageConverter}}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="3" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="0,10" Margin="10">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
|
||||
@@ -3,14 +3,17 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using FileTime.App.Core.Models;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileTime.Avalonia.Views
|
||||
{
|
||||
@@ -94,18 +97,38 @@ namespace FileTime.Avalonia.Views
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
private void OnHasContainerPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.Handled
|
||||
&& ViewModel != null
|
||||
&& e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
|
||||
&& sender is StyledElement control
|
||||
&& control.DataContext is IHaveContainer hasContainer)
|
||||
&& sender is StyledElement control)
|
||||
{
|
||||
if (control.DataContext is IHaveContainer hasContainer
|
||||
&& hasContainer.Container is not null)
|
||||
{
|
||||
ViewModel.CommandHandlerService.OpenContainer(hasContainer.Container);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (control.DataContext is IContainer container)
|
||||
{
|
||||
ViewModel.CommandHandlerService.OpenContainer(container);
|
||||
}
|
||||
else if (control.DataContext is IElement element && element.GetParent() is IContainer parentContainer)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ViewModel.AppState.SelectedTab.OpenContainer(parentContainer);
|
||||
await ViewModel.AppState.SelectedTab.SetCurrentSelectedItem(element);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
private void OnWindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Command.Copy;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Providers.Favorites.CommandHandlers
|
||||
{
|
||||
public class ToFavoriteCopyCommandHandler : ICommandHandler
|
||||
{
|
||||
public bool CanHandle(object command)
|
||||
{
|
||||
return command is CopyCommand copyCommand
|
||||
&& copyCommand.Target?.ContentProvider is FavoriteContentProvider;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(object command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Command must be {typeof(CopyCommand)}.", nameof(command));
|
||||
if (copyCommand.Target is null) throw new NullReferenceException("Command's target can not be null.");
|
||||
|
||||
var resolvedTarget = await copyCommand.Target.ResolveAsync();
|
||||
if (resolvedTarget is not FavoriteContainerBase targetContainer) throw new Exception($"Target is not {nameof(FavoriteContainerBase)}.");
|
||||
|
||||
foreach (var source in copyCommand.Sources)
|
||||
{
|
||||
var resolvedSource = await source.ResolveAsync();
|
||||
if (resolvedSource == null) continue;
|
||||
|
||||
var newElement = new FavoriteElement(targetContainer, resolvedSource.Name, resolvedSource);
|
||||
await targetContainer.AddElementAsync(newElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Favorites.Persistence;
|
||||
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public class FavoriteContainer : FavoriteContainerBase, IFavoriteItem
|
||||
{
|
||||
public FavoriteContainer(PersistenceService persistenceService, FavoriteContentProvider provider, IContainer parent, string name) : base(persistenceService, provider, parent, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Providers.Favorites.Persistence;
|
||||
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public abstract class FavoriteContainerBase : IContainer
|
||||
{
|
||||
private readonly List<FavoriteContainer> _containers;
|
||||
private List<IItem> _items;
|
||||
private readonly List<FavoriteElement> _elements;
|
||||
private readonly List<Exception> _exceptions = new();
|
||||
private readonly PersistenceService _persistenceService;
|
||||
|
||||
public bool IsExists => true;
|
||||
|
||||
public IReadOnlyList<Exception> Exceptions { get; }
|
||||
|
||||
public bool AllowRecursiveDeletion => false;
|
||||
|
||||
public bool Loading => false;
|
||||
|
||||
public bool CanHandleEscape => false;
|
||||
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public AsyncEventHandler<bool> LoadingChanged { get; } = new();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public string? NativePath { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool IsDestroyed => false;
|
||||
|
||||
public SupportsDelete CanDelete => SupportsDelete.True;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public FavoriteContentProvider Provider { get; }
|
||||
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
|
||||
protected IContainer? Parent { get; set; }
|
||||
|
||||
public IReadOnlyList<FavoriteContainer> Containers { get; }
|
||||
public IReadOnlyList<FavoriteElement> Elements { get; }
|
||||
|
||||
protected FavoriteContainerBase(PersistenceService persistenceService, FavoriteContentProvider provider, IContainer parent, string name)
|
||||
: this(persistenceService, name, parent.FullName == null ? name : parent.FullName + Constants.SeparatorChar + name)
|
||||
{
|
||||
Provider = provider;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
protected FavoriteContainerBase(PersistenceService persistenceService, string name)
|
||||
: this(persistenceService, name, null)
|
||||
{
|
||||
Provider = (FavoriteContentProvider)this;
|
||||
}
|
||||
|
||||
private FavoriteContainerBase(PersistenceService persistenceService, string name, string? fullName)
|
||||
{
|
||||
_containers = new List<FavoriteContainer>();
|
||||
_items = new List<IItem>();
|
||||
_elements = new List<FavoriteElement>();
|
||||
_persistenceService = persistenceService;
|
||||
|
||||
Containers = _containers.AsReadOnly();
|
||||
Elements = _elements.AsReadOnly();
|
||||
|
||||
Exceptions = _exceptions.AsReadOnly();
|
||||
DisplayName = Name = name;
|
||||
NativePath = FullName = fullName;
|
||||
Provider = null!;
|
||||
}
|
||||
|
||||
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
var container = new FavoriteContainer(_persistenceService, Provider, this, name);
|
||||
await AddContainerAsync(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElementAsync(string name) => throw new NotSupportedException();
|
||||
|
||||
public Task Delete(bool hardDelete = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async Task SaveFavoritesAsync()
|
||||
{
|
||||
await _persistenceService.SaveFavorites(Provider.Containers.Cast<IFavoriteItem>().Concat(Provider.Elements.Cast<IFavoriteItem>()));
|
||||
}
|
||||
|
||||
public async Task AddContainerAsync(FavoriteContainer container)
|
||||
{
|
||||
_containers.Add(container);
|
||||
UpdateItems();
|
||||
await SaveFavoritesAsync();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public async Task AddContainersAsync(IEnumerable<FavoriteContainer> containers)
|
||||
{
|
||||
_containers.AddRange(containers);
|
||||
UpdateItems();
|
||||
await SaveFavoritesAsync();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteContainerAsync(FavoriteContainer container)
|
||||
{
|
||||
_containers.Remove(container);
|
||||
UpdateItems();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public async Task AddElementAsync(FavoriteElement element)
|
||||
{
|
||||
_elements.Add(element);
|
||||
UpdateItems();
|
||||
await SaveFavoritesAsync();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public async Task AddElementsAsync(IEnumerable<FavoriteElement> elements)
|
||||
{
|
||||
_elements.AddRange(elements);
|
||||
UpdateItems();
|
||||
await SaveFavoritesAsync();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteElementAsync(FavoriteElement element)
|
||||
{
|
||||
_elements.Remove(element);
|
||||
UpdateItems();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
private void UpdateItems()
|
||||
{
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
await InitIfNeeded();
|
||||
return _items;
|
||||
}
|
||||
|
||||
public virtual Task InitIfNeeded() => Task.CompletedTask;
|
||||
|
||||
public async Task<bool> IsExistsAsync(string name)
|
||||
{
|
||||
var items = await GetItems();
|
||||
return items?.Any(i => i.Name == name) ?? false;
|
||||
}
|
||||
|
||||
public virtual Task<bool> CanOpenAsync() => Task.FromResult(_exceptions.Count == 0);
|
||||
|
||||
public void Unload() { }
|
||||
|
||||
public Task<ContainerEscapeResult> HandleEscape() => throw new NotSupportedException();
|
||||
|
||||
public async Task RunWithLoading(Func<CancellationToken, Task> func, CancellationToken token = default)
|
||||
{
|
||||
await func(token);
|
||||
}
|
||||
|
||||
public void Destroy() { }
|
||||
|
||||
public IContainer? GetParent() => Parent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Providers.Favorites.Persistence;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public class FavoriteContentProvider : FavoriteContainerBase, IContentProvider
|
||||
{
|
||||
private bool _initialized;
|
||||
private readonly PersistenceService _persistenceService;
|
||||
private readonly Lazy<IEnumerable<IContentProvider>> _contentProvidersLazy;
|
||||
|
||||
public FavoriteContentProvider(PersistenceService persistenceService, IServiceProvider serviceProvider) : base(persistenceService, "favorite")
|
||||
{
|
||||
Protocol = "favorite://";
|
||||
_persistenceService = persistenceService;
|
||||
_contentProvidersLazy = new Lazy<IEnumerable<IContentProvider>>(() => serviceProvider.GetRequiredService<IEnumerable<IContentProvider>>());
|
||||
}
|
||||
|
||||
public bool SupportsContentStreams => false;
|
||||
|
||||
public string Protocol { get; }
|
||||
|
||||
public Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
|
||||
public void SetParent(IContainer container)
|
||||
{
|
||||
Parent = container;
|
||||
}
|
||||
|
||||
public async Task SaveAsync()
|
||||
{
|
||||
await SaveFavoritesAsync();
|
||||
}
|
||||
|
||||
public override async Task InitIfNeeded()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
var (containerDtos, elementDtos) = await _persistenceService.LoadFavorites();
|
||||
|
||||
await AddItems(this, containerDtos, elementDtos);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddItems(
|
||||
FavoriteContainerBase container,
|
||||
IEnumerable<FavoriteContainerDto> containerDtos,
|
||||
IEnumerable<FavoriteElementDto> elementDtos)
|
||||
{
|
||||
var newContainers = new List<FavoriteContainer>();
|
||||
var newElements = new List<FavoriteElement>();
|
||||
|
||||
foreach (var containerDto in containerDtos)
|
||||
{
|
||||
var newContainer = new FavoriteContainer(_persistenceService, this, container, containerDto.Name);
|
||||
newContainers.Add(newContainer);
|
||||
|
||||
await AddItems(newContainer, containerDto.Containers, containerDto.Elements);
|
||||
}
|
||||
|
||||
foreach (var elementDto in elementDtos)
|
||||
{
|
||||
var item = await elementDto.RealPath.Resolve(_contentProvidersLazy.Value).ResolveAsync();
|
||||
if (item is not null)
|
||||
{
|
||||
var newElement = new FavoriteElement(container, elementDto.Name, item, elementDto.IsPinned);
|
||||
newElements.Add(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
await container.AddContainersAsync(newContainers);
|
||||
await container.AddElementsAsync(newElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public class FavoriteElement : AbstractElement<FavoriteContentProvider>, ISymlinkElement, IFavoriteItem
|
||||
{
|
||||
public IItem BaseItem { get; }
|
||||
public bool IsPinned { get; set; }
|
||||
|
||||
IItem ISymlinkElement.RealItem => BaseItem;
|
||||
|
||||
public FavoriteElement(FavoriteContainerBase parent, string name, IItem baseItem, bool isPinned = false) : base(parent.Provider, parent, name)
|
||||
{
|
||||
BaseItem = baseItem;
|
||||
IsPinned = isPinned;
|
||||
}
|
||||
|
||||
public override Task Delete(bool hardDelete = false) => throw new NotSupportedException();
|
||||
|
||||
public override Task<string> GetContent(CancellationToken token = default) => throw new NotSupportedException();
|
||||
|
||||
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?)null);
|
||||
|
||||
public override string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public override Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public interface IFavoriteItem { }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace FileTime.Providers.Favorites.Persistence
|
||||
{
|
||||
public class FavoriteContainerDto
|
||||
{
|
||||
public List<FavoriteContainerDto> Containers { get; set; } = new();
|
||||
public List<FavoriteElementDto> Elements { get; set; } = new();
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public async Task Init(FavoriteContainer favoriteContainer)
|
||||
{
|
||||
Name = favoriteContainer.Name;
|
||||
|
||||
var newContainers = new List<FavoriteContainerDto>();
|
||||
var newElements = new List<FavoriteElementDto>();
|
||||
|
||||
var containers = await favoriteContainer.GetContainers();
|
||||
var elements = await favoriteContainer.GetElements();
|
||||
|
||||
if (containers != null)
|
||||
{
|
||||
foreach (var container in containers.Cast<FavoriteContainer>())
|
||||
{
|
||||
var childFavorite = new FavoriteContainerDto();
|
||||
await childFavorite.Init(container);
|
||||
newContainers.Add(childFavorite);
|
||||
}
|
||||
}
|
||||
|
||||
if (elements != null)
|
||||
{
|
||||
foreach (var element in elements.Cast<FavoriteElement>())
|
||||
{
|
||||
var childFavorite = new FavoriteElementDto(element);
|
||||
newElements.Add(childFavorite);
|
||||
}
|
||||
}
|
||||
|
||||
Containers = newContainers;
|
||||
Elements = newElements;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Providers.Favorites.Persistence
|
||||
{
|
||||
public class FavoriteElementDto
|
||||
{
|
||||
public string Name { get; set; } = null!;
|
||||
public AbsolutePathDto RealPath { get; set; } = null!;
|
||||
public bool IsPinned { get; set; }
|
||||
|
||||
public FavoriteElementDto() { }
|
||||
|
||||
public FavoriteElementDto(FavoriteElement element)
|
||||
{
|
||||
Name = element.Name;
|
||||
RealPath = new AbsolutePathDto(new AbsolutePath(element.BaseItem));
|
||||
IsPinned = element.IsPinned;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Providers.Favorites.Persistence
|
||||
{
|
||||
public class FavoritePersistenceRoot
|
||||
{
|
||||
public List<FavoriteContainerDto> Containers { get; set; } = new();
|
||||
public List<FavoriteElementDto> Elements { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Text.Json;
|
||||
using FileTime.Core.Persistence;
|
||||
|
||||
namespace FileTime.Providers.Favorites.Persistence
|
||||
{
|
||||
public class PersistenceService
|
||||
{
|
||||
private const string favoriteFolderName = "favorites";
|
||||
private const string favoriteFileName = "favorites.json";
|
||||
private readonly PersistenceSettings _persistenceSettings;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public PersistenceService(PersistenceSettings persistenceSettings)
|
||||
{
|
||||
_persistenceSettings = persistenceSettings;
|
||||
|
||||
_jsonOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task SaveFavorites(IEnumerable<IFavoriteItem> items)
|
||||
{
|
||||
var containers = new List<FavoriteContainerDto>();
|
||||
var elements = new List<FavoriteElementDto>();
|
||||
|
||||
foreach (var favoriteItem in items)
|
||||
{
|
||||
if (favoriteItem is FavoriteContainer favoriteContainer)
|
||||
{
|
||||
var childFavorite = new FavoriteContainerDto();
|
||||
await childFavorite.Init(favoriteContainer);
|
||||
containers.Add(childFavorite);
|
||||
}
|
||||
else if (favoriteItem is FavoriteElement favoriteElement)
|
||||
{
|
||||
var childFavorite = new FavoriteElementDto(favoriteElement);
|
||||
elements.Add(childFavorite);
|
||||
}
|
||||
}
|
||||
|
||||
var root = new FavoritePersistenceRoot()
|
||||
{
|
||||
Containers = containers,
|
||||
Elements = elements
|
||||
};
|
||||
|
||||
var favoriteDirectory = new DirectoryInfo(Path.Combine(_persistenceSettings.RootAppDataPath, favoriteFolderName));
|
||||
if (!favoriteDirectory.Exists) favoriteDirectory.Create();
|
||||
|
||||
var persistencePath = Path.Combine(_persistenceSettings.RootAppDataPath, favoriteFolderName, favoriteFileName);
|
||||
|
||||
using var stream = File.Create(persistencePath);
|
||||
await JsonSerializer.SerializeAsync(stream, root, _jsonOptions);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<FavoriteContainerDto>, IEnumerable<FavoriteElementDto>)> LoadFavorites()
|
||||
{
|
||||
var persistencePath = Path.Combine(_persistenceSettings.RootAppDataPath, favoriteFolderName, favoriteFileName);
|
||||
|
||||
if (!new FileInfo(persistencePath).Exists) return (Enumerable.Empty<FavoriteContainerDto>(), Enumerable.Empty<FavoriteElementDto>());
|
||||
|
||||
using var stream = File.OpenRead(persistencePath);
|
||||
var serversRoot = (await JsonSerializer.DeserializeAsync<FavoritePersistenceRoot>(stream))!;
|
||||
|
||||
return (serversRoot.Containers, serversRoot.Elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Providers/FileTime.Providers.Favorites/Startup.cs
Normal file
35
src/Providers/FileTime.Providers.Favorites/Startup.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Providers.Favorites.CommandHandlers;
|
||||
using FileTime.Providers.Favorites.Persistence;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Providers.Favorites
|
||||
{
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddFavoriteServices(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection
|
||||
.AddSingleton<FavoriteContentProvider>()
|
||||
.AddSingleton<IContentProvider>(serviceProvider => serviceProvider.GetRequiredService<FavoriteContentProvider>())
|
||||
.AddSingleton<PersistenceService>()
|
||||
.RegisterFavoriteCommandHandlers();
|
||||
}
|
||||
|
||||
internal static IServiceCollection RegisterFavoriteCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
var commandHandlers = new List<Type>()
|
||||
{
|
||||
typeof(ToFavoriteCopyCommandHandler)
|
||||
};
|
||||
|
||||
foreach (var commandHandler in commandHandlers)
|
||||
{
|
||||
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace FileTime.Providers.Local
|
||||
public bool IsCaseInsensitive { get; }
|
||||
|
||||
public LocalContentProvider(ILogger<LocalContentProvider> logger)
|
||||
: base("local", null, "local://", true)
|
||||
: base("local", "local://", true)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace FileTime.Providers.Local
|
||||
Provider = contentProvider;
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText() => File.Length.ToSizeString();
|
||||
public string? GetPrimaryAttributeText() => File.Length.ToSizeString();
|
||||
|
||||
public Task Delete(bool hardDelete = false)
|
||||
{
|
||||
|
||||
@@ -11,14 +11,12 @@ namespace FileTime.Providers.Sftp
|
||||
private readonly ILogger<SftpContentProvider> _logger;
|
||||
|
||||
public SftpContentProvider(IInputInterface inputInterface, ILogger<SftpContentProvider> logger)
|
||||
: base("sftp", null, "sftp://", false)
|
||||
: base("sftp", "sftp://", false)
|
||||
{
|
||||
_logger = logger;
|
||||
_inputInterface = inputInterface;
|
||||
}
|
||||
|
||||
public override Task<bool> CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol));
|
||||
|
||||
public override async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
var container = RootContainers?.FirstOrDefault(c => c.Name == name);
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace FileTime.Providers.Sftp
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText()
|
||||
public string? GetPrimaryAttributeText()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace FileTime.Providers.Smb
|
||||
private readonly ILogger<SmbContentProvider> _logger;
|
||||
|
||||
public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger<SmbContentProvider> logger)
|
||||
: base("smb", null, "smb://", true)
|
||||
: base("smb", "smb://", true)
|
||||
{
|
||||
_inputInterface = inputInterface;
|
||||
_persistenceService = persistenceService;
|
||||
|
||||
@@ -74,10 +74,7 @@ namespace FileTime.Providers.Smb
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
public string? GetPrimaryAttributeText() => null;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
public Task<string> GetContent(CancellationToken token = default) => Task.FromResult("NotImplemented");
|
||||
|
||||
Reference in New Issue
Block a user