diff --git a/src/AppCommon/FileTime.App.Core/Command/Commands.cs b/src/AppCommon/FileTime.App.Core/Command/Commands.cs index 9de49cc..22a7765 100644 --- a/src/AppCommon/FileTime.App.Core/Command/Commands.cs +++ b/src/AppCommon/FileTime.App.Core/Command/Commands.cs @@ -39,6 +39,7 @@ namespace FileTime.App.Core.Command PasteMerge, PasteOverwrite, PasteSkip, + PinFavorite, PreviousTimelineBlock, PreviousTimelineCommand, Refresh, diff --git a/src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs b/src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs index b658577..7f6d0d9 100644 --- a/src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs +++ b/src/AppCommon/FileTime.App.Core/Models/IHaveContainer.cs @@ -4,6 +4,6 @@ namespace FileTime.App.Core.Models { public interface IHaveContainer { - IContainer Container { get; } + IContainer? Container { get; } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs index ad17d44..823903a 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs +++ b/src/AppCommon/FileTime.App.DependencyInjection/DependencyInjection.cs @@ -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(); } diff --git a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj index f3a2791..854748f 100644 --- a/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj +++ b/src/AppCommon/FileTime.App.DependencyInjection/FileTime.App.DependencyInjection.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Core/FileTime.Core/Components/Tab.cs b/src/Core/FileTime.Core/Components/Tab.cs index 6cdaffc..477a1d6 100644 --- a/src/Core/FileTime.Core/Components/Tab.cs +++ b/src/Core/FileTime.Core/Components/Tab.cs @@ -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); diff --git a/src/Core/FileTime.Core/ContainerSizeScanner/ContainerScanSnapshotProvider.cs b/src/Core/FileTime.Core/ContainerSizeScanner/ContainerScanSnapshotProvider.cs index ff8a91e..948b158 100644 --- a/src/Core/FileTime.Core/ContainerSizeScanner/ContainerScanSnapshotProvider.cs +++ b/src/Core/FileTime.Core/ContainerSizeScanner/ContainerScanSnapshotProvider.cs @@ -7,12 +7,10 @@ namespace FileTime.Core.ContainerSizeScanner public class ContainerScanSnapshotProvider : ContentProviderBase { - public ContainerScanSnapshotProvider() : base("size", null, "size://", false) + public ContainerScanSnapshotProvider() : base("size", "size://", false) { } - public override Task CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol)); - public override Task CreateContainerAsync(string name) => throw new NotSupportedException(); public override Task CreateElementAsync(string name) => throw new NotSupportedException(); diff --git a/src/Core/FileTime.Core/ContainerSizeScanner/ContainerSizeElement.cs b/src/Core/FileTime.Core/ContainerSizeScanner/ContainerSizeElement.cs index af04fad..eb94ad0 100644 --- a/src/Core/FileTime.Core/ContainerSizeScanner/ContainerSizeElement.cs +++ b/src/Core/FileTime.Core/ContainerSizeScanner/ContainerSizeElement.cs @@ -32,7 +32,7 @@ namespace FileTime.Core.ContainerSizeScanner public override Task 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(); } diff --git a/src/Core/FileTime.Core/Models/AbsolutePathDto.cs b/src/Core/FileTime.Core/Models/AbsolutePathDto.cs new file mode 100644 index 0000000..477594a --- /dev/null +++ b/src/Core/FileTime.Core/Models/AbsolutePathDto.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Models/IElement.cs b/src/Core/FileTime.Core/Models/IElement.cs index 1a68ddb..2b4596e 100644 --- a/src/Core/FileTime.Core/Models/IElement.cs +++ b/src/Core/FileTime.Core/Models/IElement.cs @@ -5,7 +5,7 @@ namespace FileTime.Core.Models public interface IElement : IItem { bool IsSpecial { get; } - string GetPrimaryAttributeText(); + string? GetPrimaryAttributeText(); Task GetContent(CancellationToken token = default); Task GetElementSize(CancellationToken token = default); diff --git a/src/Core/FileTime.Core/Models/ISymlinkElement.cs b/src/Core/FileTime.Core/Models/ISymlinkElement.cs new file mode 100644 index 0000000..35080e0 --- /dev/null +++ b/src/Core/FileTime.Core/Models/ISymlinkElement.cs @@ -0,0 +1,7 @@ +namespace FileTime.Core.Models +{ + public interface ISymlinkElement + { + IItem RealItem { get; } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/AbstractContainer.cs b/src/Core/FileTime.Core/Providers/AbstractContainer.cs index 55c5093..ac84456 100644 --- a/src/Core/FileTime.Core/Providers/AbstractContainer.cs +++ b/src/Core/FileTime.Core/Providers/AbstractContainer.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using AsyncEvent; using FileTime.Core.Models; diff --git a/src/Core/FileTime.Core/Providers/AbstractElement.cs b/src/Core/FileTime.Core/Providers/AbstractElement.cs index 891bee3..acb9ea5 100644 --- a/src/Core/FileTime.Core/Providers/AbstractElement.cs +++ b/src/Core/FileTime.Core/Providers/AbstractElement.cs @@ -51,7 +51,7 @@ namespace FileTime.Core.Providers public abstract Task GetElementSize(CancellationToken token = default); public IContainer? GetParent() => _parent; - public abstract string GetPrimaryAttributeText(); + public abstract string? GetPrimaryAttributeText(); public abstract Task Rename(string newName); } diff --git a/src/Core/FileTime.Core/Providers/ContentProviderBase.cs b/src/Core/FileTime.Core/Providers/ContentProviderBase.cs index 439bce5..9fd9dec 100644 --- a/src/Core/FileTime.Core/Providers/ContentProviderBase.cs +++ b/src/Core/FileTime.Core/Providers/ContentProviderBase.cs @@ -14,11 +14,15 @@ namespace FileTime.Core.Providers protected List? 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 CanHandlePath(string path); + public virtual Task CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol)); public override IContainer? GetParent() => _parent; public void SetParent(IContainer parent) => _parent = parent; diff --git a/src/Core/FileTime.Core/Search/ChildSearchElement.cs b/src/Core/FileTime.Core/Search/ChildSearchElement.cs index e0ac039..d1bee98 100644 --- a/src/Core/FileTime.Core/Search/ChildSearchElement.cs +++ b/src/Core/FileTime.Core/Search/ChildSearchElement.cs @@ -29,7 +29,7 @@ namespace FileTime.Core.Search public override async Task 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(); } diff --git a/src/Core/FileTime.Core/Timeline/TimeElement.cs b/src/Core/FileTime.Core/Timeline/TimeElement.cs index 61b704a..dc5b145 100644 --- a/src/Core/FileTime.Core/Timeline/TimeElement.cs +++ b/src/Core/FileTime.Core/Timeline/TimeElement.cs @@ -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; diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index d17d3fb..69b1f1a 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -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; } diff --git a/src/FileTime.sln b/src/FileTime.sln index 2c4bf15..929b588 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -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} diff --git a/src/GuiApp/FileTime.Avalonia/Application/AppState.cs b/src/GuiApp/FileTime.Avalonia/Application/AppState.cs index 68accd5..e185eaf 100644 --- a/src/GuiApp/FileTime.Avalonia/Application/AppState.cs +++ b/src/GuiApp/FileTime.Avalonia/Application/AppState.cs @@ -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 _favoriteElements; + public List PreviousKeys { get; } = new(); public ObservableCollection TimelineCommands { get; } = new(); diff --git a/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs b/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs index aa71e19..383673b 100644 --- a/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs +++ b/src/GuiApp/FileTime.Avalonia/Configuration/MainConfiguration.cs @@ -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), diff --git a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj index 9da264b..6e76cd7 100644 --- a/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj +++ b/src/GuiApp/FileTime.Avalonia/FileTime.Avalonia.csproj @@ -53,6 +53,7 @@ + diff --git a/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs b/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs index a950f55..9ca4e97 100644 --- a/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs +++ b/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs @@ -10,7 +10,6 @@ namespace FileTime.Avalonia.IconProviders { public class MaterialIconProvider : IIconProvider { - private static readonly Dictionary _iconsByExtension = new(); private static readonly Dictionary _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 { diff --git a/src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs b/src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs index d8c8ece..13606e9 100644 --- a/src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs +++ b/src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs @@ -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) { diff --git a/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs b/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs index 58a54de..f61cff7 100644 --- a/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs +++ b/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs @@ -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; diff --git a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs index 47a3454..0364635 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs @@ -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 _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 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> { @@ -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 GetFavoriteElements(FavoriteContainerBase container) + { + return container.Elements.Where(e => e.IsPinned).Concat( + container.Containers.SelectMany(GetFavoriteElements) + ); + } + } } } \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/HistoryItemViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/HistoryItemViewModel.cs index 317e701..397101f 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/HistoryItemViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/HistoryItemViewModel.cs @@ -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 + public class HistoryItemViewModel : IAsyncInitable, IHaveContainer { public string? Name { get; private set; } public IContainer? Container { get; private set; } diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index bd718da..d2f7826 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -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(); _timeRunner = App.ServiceProvider.GetService()!; var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService()!; 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 GetFavoriteElements(FavoriteContainerBase container) + { + return container.Elements.Where(e => e.IsPinned).Concat( + container.Containers.SelectMany(GetFavoriteElements) + ); + } } private Task UpdateParallelCommands(object? sender, IReadOnlyList parallelCommands, CancellationToken token) @@ -269,7 +284,9 @@ namespace FileTime.Avalonia.ViewModels public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action 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 } } } diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml index 37620ba..04ac18a 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml @@ -41,7 +41,7 @@ - + @@ -149,6 +149,38 @@ + + + + + + + + + + + + + + + + + + + + + + { + 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) { diff --git a/src/Providers/FileTime.Providers.Favorites/CommandHandlers/ToFavoriteCopyCommandHandler.cs b/src/Providers/FileTime.Providers.Favorites/CommandHandlers/ToFavoriteCopyCommandHandler.cs new file mode 100644 index 0000000..67c64ee --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/CommandHandlers/ToFavoriteCopyCommandHandler.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/FavoriteContainer.cs b/src/Providers/FileTime.Providers.Favorites/FavoriteContainer.cs new file mode 100644 index 0000000..38bec2a --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/FavoriteContainer.cs @@ -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) + { + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/FavoriteContainerBase.cs b/src/Providers/FileTime.Providers.Favorites/FavoriteContainerBase.cs new file mode 100644 index 0000000..caca73b --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/FavoriteContainerBase.cs @@ -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 _containers; + private List _items; + private readonly List _elements; + private readonly List _exceptions = new(); + private readonly PersistenceService _persistenceService; + + public bool IsExists => true; + + public IReadOnlyList 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 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 Containers { get; } + public IReadOnlyList 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(); + _items = new List(); + _elements = new List(); + _persistenceService = persistenceService; + + Containers = _containers.AsReadOnly(); + Elements = _elements.AsReadOnly(); + + Exceptions = _exceptions.AsReadOnly(); + DisplayName = Name = name; + NativePath = FullName = fullName; + Provider = null!; + } + + public Task CloneAsync() => Task.FromResult((IContainer)this); + + public async Task CreateContainerAsync(string name) + { + var container = new FavoriteContainer(_persistenceService, Provider, this, name); + await AddContainerAsync(container); + return container; + } + + public Task 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().Concat(Provider.Elements.Cast())); + } + + public async Task AddContainerAsync(FavoriteContainer container) + { + _containers.Add(container); + UpdateItems(); + await SaveFavoritesAsync(); + await RefreshAsync(); + } + + public async Task AddContainersAsync(IEnumerable 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 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().Concat(_elements).ToList(); + } + + public async Task?> GetContainers(CancellationToken token = default) + { + await InitIfNeeded(); + return _containers; + } + + public async Task?> GetElements(CancellationToken token = default) + { + await InitIfNeeded(); + return _elements; + } + + public async Task?> GetItems(CancellationToken token = default) + { + await InitIfNeeded(); + return _items; + } + + public virtual Task InitIfNeeded() => Task.CompletedTask; + + public async Task IsExistsAsync(string name) + { + var items = await GetItems(); + return items?.Any(i => i.Name == name) ?? false; + } + + public virtual Task CanOpenAsync() => Task.FromResult(_exceptions.Count == 0); + + public void Unload() { } + + public Task HandleEscape() => throw new NotSupportedException(); + + public async Task RunWithLoading(Func func, CancellationToken token = default) + { + await func(token); + } + + public void Destroy() { } + + public IContainer? GetParent() => Parent; + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/FavoriteContentProvider.cs b/src/Providers/FileTime.Providers.Favorites/FavoriteContentProvider.cs new file mode 100644 index 0000000..c49ba9e --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/FavoriteContentProvider.cs @@ -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> _contentProvidersLazy; + + public FavoriteContentProvider(PersistenceService persistenceService, IServiceProvider serviceProvider) : base(persistenceService, "favorite") + { + Protocol = "favorite://"; + _persistenceService = persistenceService; + _contentProvidersLazy = new Lazy>(() => serviceProvider.GetRequiredService>()); + } + + public bool SupportsContentStreams => false; + + public string Protocol { get; } + + public Task 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 containerDtos, + IEnumerable elementDtos) + { + var newContainers = new List(); + var newElements = new List(); + + 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); + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/FavoriteElement.cs b/src/Providers/FileTime.Providers.Favorites/FavoriteElement.cs new file mode 100644 index 0000000..1e0d58a --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/FavoriteElement.cs @@ -0,0 +1,33 @@ +using FileTime.Core.Models; +using FileTime.Core.Providers; + +namespace FileTime.Providers.Favorites +{ + public class FavoriteElement : AbstractElement, 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 GetContent(CancellationToken token = default) => throw new NotSupportedException(); + + public override Task GetContentReaderAsync() => throw new NotSupportedException(); + + public override Task GetContentWriterAsync() => throw new NotSupportedException(); + + public override Task GetElementSize(CancellationToken token = default) => Task.FromResult((long?)null); + + public override string? GetPrimaryAttributeText() => null; + + public override Task Rename(string newName) => throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/FileTime.Providers.Favorites.csproj b/src/Providers/FileTime.Providers.Favorites/FileTime.Providers.Favorites.csproj new file mode 100644 index 0000000..889a469 --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/FileTime.Providers.Favorites.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/src/Providers/FileTime.Providers.Favorites/IFavoriteItem.cs b/src/Providers/FileTime.Providers.Favorites/IFavoriteItem.cs new file mode 100644 index 0000000..c7861f9 --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/IFavoriteItem.cs @@ -0,0 +1,4 @@ +namespace FileTime.Providers.Favorites +{ + public interface IFavoriteItem { } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteContainerDto.cs b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteContainerDto.cs new file mode 100644 index 0000000..451b4a9 --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteContainerDto.cs @@ -0,0 +1,42 @@ +namespace FileTime.Providers.Favorites.Persistence +{ + public class FavoriteContainerDto + { + public List Containers { get; set; } = new(); + public List Elements { get; set; } = new(); + public string Name { get; set; } = null!; + + public async Task Init(FavoriteContainer favoriteContainer) + { + Name = favoriteContainer.Name; + + var newContainers = new List(); + var newElements = new List(); + + var containers = await favoriteContainer.GetContainers(); + var elements = await favoriteContainer.GetElements(); + + if (containers != null) + { + foreach (var container in containers.Cast()) + { + var childFavorite = new FavoriteContainerDto(); + await childFavorite.Init(container); + newContainers.Add(childFavorite); + } + } + + if (elements != null) + { + foreach (var element in elements.Cast()) + { + var childFavorite = new FavoriteElementDto(element); + newElements.Add(childFavorite); + } + } + + Containers = newContainers; + Elements = newElements; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteElementDto.cs b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteElementDto.cs new file mode 100644 index 0000000..937c4d7 --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoriteElementDto.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/Persistence/FavoritePersistenceRoot.cs b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoritePersistenceRoot.cs new file mode 100644 index 0000000..25a5f1e --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/Persistence/FavoritePersistenceRoot.cs @@ -0,0 +1,8 @@ +namespace FileTime.Providers.Favorites.Persistence +{ + public class FavoritePersistenceRoot + { + public List Containers { get; set; } = new(); + public List Elements { get; set; } = new(); + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/Persistence/PersistenceService.cs b/src/Providers/FileTime.Providers.Favorites/Persistence/PersistenceService.cs new file mode 100644 index 0000000..d4624b9 --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/Persistence/PersistenceService.cs @@ -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 items) + { + var containers = new List(); + var elements = new List(); + + 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, IEnumerable)> LoadFavorites() + { + var persistencePath = Path.Combine(_persistenceSettings.RootAppDataPath, favoriteFolderName, favoriteFileName); + + if (!new FileInfo(persistencePath).Exists) return (Enumerable.Empty(), Enumerable.Empty()); + + using var stream = File.OpenRead(persistencePath); + var serversRoot = (await JsonSerializer.DeserializeAsync(stream))!; + + return (serversRoot.Containers, serversRoot.Elements); + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Favorites/Startup.cs b/src/Providers/FileTime.Providers.Favorites/Startup.cs new file mode 100644 index 0000000..037243d --- /dev/null +++ b/src/Providers/FileTime.Providers.Favorites/Startup.cs @@ -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() + .AddSingleton(serviceProvider => serviceProvider.GetRequiredService()) + .AddSingleton() + .RegisterFavoriteCommandHandlers(); + } + + internal static IServiceCollection RegisterFavoriteCommandHandlers(this IServiceCollection serviceCollection) + { + var commandHandlers = new List() + { + typeof(ToFavoriteCopyCommandHandler) + }; + + foreach (var commandHandler in commandHandlers) + { + serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler); + } + + return serviceCollection; + } + } +} \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index ecfe36a..f384e42 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -11,7 +11,7 @@ namespace FileTime.Providers.Local public bool IsCaseInsensitive { get; } public LocalContentProvider(ILogger logger) - : base("local", null, "local://", true) + : base("local", "local://", true) { _logger = logger; diff --git a/src/Providers/FileTime.Providers.Local/LocalFile.cs b/src/Providers/FileTime.Providers.Local/LocalFile.cs index e734e44..ca9bc17 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFile.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFile.cs @@ -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) { diff --git a/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs b/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs index 90915ac..5f1feda 100644 --- a/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs +++ b/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs @@ -11,14 +11,12 @@ namespace FileTime.Providers.Sftp private readonly ILogger _logger; public SftpContentProvider(IInputInterface inputInterface, ILogger logger) - : base("sftp", null, "sftp://", false) + : base("sftp", "sftp://", false) { _logger = logger; _inputInterface = inputInterface; } - public override Task CanHandlePath(string path) => Task.FromResult(path.StartsWith(Protocol)); - public override async Task CreateContainerAsync(string name) { var container = RootContainers?.FirstOrDefault(c => c.Name == name); diff --git a/src/Providers/FileTime.Providers.Sftp/SftpFile.cs b/src/Providers/FileTime.Providers.Sftp/SftpFile.cs index 39048b9..0e4e38d 100644 --- a/src/Providers/FileTime.Providers.Sftp/SftpFile.cs +++ b/src/Providers/FileTime.Providers.Sftp/SftpFile.cs @@ -61,7 +61,7 @@ namespace FileTime.Providers.Sftp throw new NotImplementedException(); } - public string GetPrimaryAttributeText() + public string? GetPrimaryAttributeText() { throw new NotImplementedException(); } diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs index f4956f3..a335a08 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -13,7 +13,7 @@ namespace FileTime.Providers.Smb private readonly ILogger _logger; public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger logger) - : base("smb", null, "smb://", true) + : base("smb", "smb://", true) { _inputInterface = inputInterface; _persistenceService = persistenceService; diff --git a/src/Providers/FileTime.Providers.Smb/SmbFile.cs b/src/Providers/FileTime.Providers.Smb/SmbFile.cs index 4717998..3c56495 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbFile.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbFile.cs @@ -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 GetContent(CancellationToken token = default) => Task.FromResult("NotImplemented");