From cd04e992f1dab73f23604d4fb7ff7489c6440ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Sat, 19 Feb 2022 23:00:37 +0100 Subject: [PATCH] ContentProvider refactor --- .../Command/Delete/DeleteCommand.cs | 10 +- src/Core/FileTime.Core/Models/IContainer.cs | 1 + .../FileTime.Core/Models/VirtualContainer.cs | 1 + .../Providers/AbstractContainer.cs | 21 ++- .../Providers/ContentProviderBase.cs | 122 ++++++++++++++++ .../Providers/IContentProvider.cs | 4 +- .../FileTime.Core/Providers/TopContainer.cs | 1 + .../FileTime.Core/Timeline/TimeContainer.cs | 1 + .../FileTime.Core/Timeline/TimeProvider.cs | 95 +------------ .../Services/CommandHandlerService.cs | 2 +- .../Services/StatePersistenceService.cs | 2 +- .../ViewModels/MainPageViewModel.cs | 2 +- .../LocalContentProvider.cs | 82 ++--------- .../FileTime.Providers.Local/LocalFolder.cs | 1 + .../SftpContentProvider.cs | 97 ++----------- .../SmbContentProvider.cs | 130 ++---------------- 16 files changed, 189 insertions(+), 383 deletions(-) create mode 100644 src/Core/FileTime.Core/Providers/ContentProviderBase.cs diff --git a/src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs index 0229739..d49bf93 100644 --- a/src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core/Command/Delete/DeleteCommand.cs @@ -1,3 +1,4 @@ +using System.Reflection.Metadata; using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -65,7 +66,7 @@ namespace FileTime.Core.Command.Delete await TraverseTree((await item.ResolveAsync())!); } - foreach(var updatedParent in ItemsToDelete.Select(i => i.GetParent()).Distinct()) + foreach (var updatedParent in ItemsToDelete.Select(i => i.GetParent()).Distinct()) { await timeRunner.RefreshContainer.InvokeAsync(this, updatedParent); } @@ -81,9 +82,12 @@ namespace FileTime.Core.Command.Delete } else { - foreach (var child in (await container.GetItems())!) + if (container.AllowRecursiveDeletion) { - await TraverseTree(child); + foreach (var child in (await container.GetItems())!) + { + await TraverseTree(child); + } } if (_deleteContainer != null) await _deleteContainer.Invoke(container); diff --git a/src/Core/FileTime.Core/Models/IContainer.cs b/src/Core/FileTime.Core/Models/IContainer.cs index 7cd19de..2a4285e 100644 --- a/src/Core/FileTime.Core/Models/IContainer.cs +++ b/src/Core/FileTime.Core/Models/IContainer.cs @@ -8,6 +8,7 @@ namespace FileTime.Core.Models Task?> GetItems(CancellationToken token = default); Task?> GetContainers(CancellationToken token = default); Task?> GetElements(CancellationToken token = default); + bool AllowRecursiveDeletion { get; } Task RefreshAsync(CancellationToken token = default); public async Task GetByPath(string path, bool acceptDeepestMatch = false) diff --git a/src/Core/FileTime.Core/Models/VirtualContainer.cs b/src/Core/FileTime.Core/Models/VirtualContainer.cs index 3403eb5..0b81160 100644 --- a/src/Core/FileTime.Core/Models/VirtualContainer.cs +++ b/src/Core/FileTime.Core/Models/VirtualContainer.cs @@ -38,6 +38,7 @@ namespace FileTime.Core.Models public bool IsDestroyed => BaseContainer.IsDestroyed; public bool IsExists => BaseContainer.IsExists; + public bool AllowRecursiveDeletion => BaseContainer.AllowRecursiveDeletion; private void RefreshAddBase(Func handler) { diff --git a/src/Core/FileTime.Core/Providers/AbstractContainer.cs b/src/Core/FileTime.Core/Providers/AbstractContainer.cs index 4aac1da..fd4637f 100644 --- a/src/Core/FileTime.Core/Providers/AbstractContainer.cs +++ b/src/Core/FileTime.Core/Providers/AbstractContainer.cs @@ -3,9 +3,9 @@ using FileTime.Core.Models; namespace FileTime.Core.Providers { - public abstract class AbstractContainer : IContainer where TProvider : IContentProvider + public abstract class AbstractContainer : IContainer where TProvider : class, IContentProvider { - private readonly IContainer _parent; + private readonly IContainer? _parent; private readonly List _exceptions = new(); private IReadOnlyList? _containers; private IReadOnlyList? _items; @@ -39,13 +39,26 @@ namespace FileTime.Core.Providers public abstract bool IsExists { get; } - protected AbstractContainer(TProvider provider, IContainer parent, string name) + public virtual bool AllowRecursiveDeletion { get; protected set; } + + protected AbstractContainer(TProvider provider, IContainer parent, string name) : this(name) { _parent = parent; Provider = provider; + FullName = parent.FullName == null ? name : parent.FullName + Constants.SeparatorChar + name; + } + + protected AbstractContainer(string name, string? fullName = null) : this(name) + { + Provider = this is TProvider provider ? provider : throw new ArgumentException($"This constructor is for {nameof(IContentProvider)}s only"); + FullName = fullName; + } + + private AbstractContainer(string name) + { Name = name; - FullName = parent.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; Exceptions = _exceptions.AsReadOnly(); + Provider = null!; } public virtual Task CanOpenAsync() => Task.FromResult(_exceptions.Count == 0); diff --git a/src/Core/FileTime.Core/Providers/ContentProviderBase.cs b/src/Core/FileTime.Core/Providers/ContentProviderBase.cs new file mode 100644 index 0000000..fdb5fee --- /dev/null +++ b/src/Core/FileTime.Core/Providers/ContentProviderBase.cs @@ -0,0 +1,122 @@ +using AsyncEvent; +using FileTime.Core.Models; + +namespace FileTime.Core.Providers +{ + public abstract class ContentProviderBase : AbstractContainer, IContentProvider + where T : class, IContentProvider + { + private readonly object _initializationGuard = new(); + private bool _initialized; + private bool _initializing; + private IContainer? _parent; + protected IReadOnlyList? RootContainers { get; private set; } + public override bool IsExists => true; + + protected ContentProviderBase( + string name, + string? fullName, + string protocol, + bool supportsContentStreams) + : base(name, fullName) + { + Protocol = protocol; + SupportsContentStreams = supportsContentStreams; + + CanRename = false; + CanDelete = SupportsDelete.False; + } + + public virtual bool SupportsContentStreams { get; } + + public virtual string Protocol { get; } + + public abstract Task CanHandlePath(string path); + public override IContainer? GetParent() => _parent; + + public void SetParent(IContainer parent) => _parent = parent; + + protected async Task AddRootContainer(IContainer newRootContainer) + { + RootContainers = + (await GetContainers())?.Append(newRootContainer).OrderBy(c => c.Name).ToList().AsReadOnly() + ?? new List() { newRootContainer }.AsReadOnly(); + } + + protected async Task AddRootContainers(IEnumerable newRootContainers) + { + RootContainers = + (await GetContainers())?.Concat(newRootContainers).OrderBy(c => c.Name).ToList().AsReadOnly() + ?? new List(newRootContainers).AsReadOnly(); + } + + protected void SetRootContainers(IEnumerable newRootContainers) + => RootContainers = newRootContainers.OrderBy(c => c.Name).ToList().AsReadOnly(); + + public override async Task?> GetContainers(CancellationToken token = default) + { + await InitIfNeeded(); + return RootContainers; + } + + public override async Task?> GetElements(CancellationToken token = default) + { + await InitIfNeeded(); + return new List(); + } + + public override async Task?> GetItems(CancellationToken token = default) + { + await InitIfNeeded(); + return RootContainers; + } + + private async Task InitIfNeeded() + { + while (true) + { + lock (_initializationGuard) + { + if (!_initializing) + { + _initializing = true; + break; + } + } + await Task.Delay(1); + } + + try + { + if (_initialized) return; + _initialized = true; + await Init(); + IsLoaded = true; + } + finally + { + lock (_initializationGuard) + { + _initializing = false; + } + } + } + + protected virtual Task Init() { return Task.CompletedTask; } + + public override Task> RefreshItems(CancellationToken token = default) { throw new NotImplementedException($"{nameof(RefreshItems)} should not be called in {nameof(ContentProviderBase)}."); } + + public override Task IsExistsAsync(string name) => Task.FromResult(RootContainers?.Any(i => i.Name == name) ?? false); + + public override async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); + public override Task CloneAsync() => Task.FromResult((IContainer)this); + public override Task Delete(bool hardDelete = false) => throw new NotSupportedException(); + public override Task Rename(string newName) => throw new NotSupportedException(); + public override Task CanOpenAsync() => Task.FromResult(true); + + + public override void Unload() { } + + public override void Destroy() { } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core/Providers/IContentProvider.cs b/src/Core/FileTime.Core/Providers/IContentProvider.cs index ec04185..32066f6 100644 --- a/src/Core/FileTime.Core/Providers/IContentProvider.cs +++ b/src/Core/FileTime.Core/Providers/IContentProvider.cs @@ -7,9 +7,7 @@ namespace FileTime.Core.Providers bool SupportsContentStreams { get; } string Protocol { get; } - Task> GetRootContainers(CancellationToken token = default); - - bool CanHandlePath(string path); + Task CanHandlePath(string path); void SetParent(IContainer container); } diff --git a/src/Core/FileTime.Core/Providers/TopContainer.cs b/src/Core/FileTime.Core/Providers/TopContainer.cs index 269ce0a..aee89b4 100644 --- a/src/Core/FileTime.Core/Providers/TopContainer.cs +++ b/src/Core/FileTime.Core/Providers/TopContainer.cs @@ -34,6 +34,7 @@ namespace FileTime.Core.Providers public bool IsDestroyed => false; public bool IsExists => true; + public bool AllowRecursiveDeletion => false; public TopContainer(IEnumerable contentProviders) { diff --git a/src/Core/FileTime.Core/Timeline/TimeContainer.cs b/src/Core/FileTime.Core/Timeline/TimeContainer.cs index a91e664..e5e7db9 100644 --- a/src/Core/FileTime.Core/Timeline/TimeContainer.cs +++ b/src/Core/FileTime.Core/Timeline/TimeContainer.cs @@ -34,6 +34,7 @@ namespace FileTime.Core.Timeline //FIXME: currently this can be different of the real items NativePath, should be fixed public string? NativePath => FullName; public bool IsExists => true; + public bool AllowRecursiveDeletion => true; public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime) { diff --git a/src/Core/FileTime.Core/Timeline/TimeProvider.cs b/src/Core/FileTime.Core/Timeline/TimeProvider.cs index a0e416d..d17d3fb 100644 --- a/src/Core/FileTime.Core/Timeline/TimeProvider.cs +++ b/src/Core/FileTime.Core/Timeline/TimeProvider.cs @@ -1,115 +1,30 @@ -using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Providers; namespace FileTime.Core.Timeline { - public class TimeProvider : IContentProvider + public class TimeProvider : ContentProviderBase { private readonly PointInTime _pointInTime; - public bool IsLoaded => true; - - public AsyncEventHandler Refreshed { get; } = new(); - - public string Name => "time"; - - public string? FullName => null; - public string? NativePath => null; - - public bool IsHidden => false; - - public SupportsDelete CanDelete => SupportsDelete.False; - - public bool CanRename => false; - - public IContentProvider Provider => this; - - public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); - - public bool SupportsDirectoryLevelSoftDelete => false; - - public bool IsDestroyed => false; - public bool SupportsContentStreams => false; - - public string Protocol => "time2://"; - public bool IsExists => true; - - public TimeProvider(PointInTime pointInTime) + public TimeProvider(PointInTime pointInTime) : base("time", null, "time2://", false) { _pointInTime = pointInTime; } - public bool CanHandlePath(string path) + public override Task CanHandlePath(string path) { throw new NotImplementedException(); } - public Task CloneAsync() => Task.FromResult((IContainer)this); - - public Task CreateContainerAsync(string name) + public override Task CreateContainerAsync(string name) { throw new NotImplementedException(); } - public Task CreateElementAsync(string name) + public override Task CreateElementAsync(string name) { throw new NotImplementedException(); } - - public Task Delete(bool hardDelete = false) => throw new NotSupportedException(); - - public Task GetByPath(string path, bool acceptDeepestMatch = false) - { - throw new NotImplementedException(); - } - - public Task?> GetContainers(CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task?> GetElements(CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task?> GetItems(CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public IContainer? GetParent() => null; - - public Task> GetRootContainers(CancellationToken token = default) - { - throw new NotImplementedException(); - } - - public Task IsExistsAsync(string name) - { - throw new NotImplementedException(); - } - - public Task RefreshAsync(CancellationToken token = default) => Task.CompletedTask; - - public Task Rename(string newName) => throw new NotSupportedException(); - - public void SetParent(IContainer container) { } - public Task CanOpenAsync() => Task.FromResult(true); - - public void Destroy() { } - - public void Unload() { } - - public Task GetContentReaderAsync(IElement element) - { - throw new NotSupportedException(); - } - - public Task GetContentWriterAsync(IElement element) - { - throw new NotSupportedException(); - } } } \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs index 7211981..7d87439 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs @@ -564,7 +564,7 @@ namespace FileTime.Avalonia.Services var path = inputs[0].Value; foreach (var contentProvider in _contentProviders) { - if (contentProvider.CanHandlePath(path)) + if (await contentProvider.CanHandlePath(path)) { var possibleContainer = await contentProvider.GetByPath(path); if (possibleContainer is IContainer container) diff --git a/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs b/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs index 66ecd74..6e621ba 100644 --- a/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs +++ b/src/GuiApp/FileTime.Avalonia/Services/StatePersistenceService.cs @@ -121,7 +121,7 @@ namespace FileTime.Avalonia.Services { foreach (var contentProvider in _contentProviders) { - if (contentProvider.CanHandlePath(tab.Path)) + if (await contentProvider.CanHandlePath(tab.Path)) { pathItem = await contentProvider.GetByPath(tab.Path, true); if (pathItem != null) break; diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index 17eef68..4e79ca4 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -235,7 +235,7 @@ namespace FileTime.Avalonia.ViewModels private async Task GetContainerForWindowsDrive(DriveInfo drive) { - return (await LocalContentProvider.GetRootContainers()).FirstOrDefault(d => d.Name == drive.Name.TrimEnd(Path.DirectorySeparatorChar)); + return (await LocalContentProvider.GetContainers())!.FirstOrDefault(d => d.Name == drive.Name.TrimEnd(Path.DirectorySeparatorChar)); } private async Task GetContainerForLinuxDrive(DriveInfo drive) diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 3882108..dab16e7 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -1,46 +1,17 @@ -using System.Threading.Tasks; -using System; using System.Runtime.InteropServices; -using AsyncEvent; using FileTime.Core.Models; using FileTime.Core.Providers; using Microsoft.Extensions.Logging; namespace FileTime.Providers.Local { - public class LocalContentProvider : IContentProvider + public class LocalContentProvider : ContentProviderBase { private readonly ILogger _logger; - private IContainer? _parent; - - private readonly IReadOnlyList _rootContainers; - private readonly IReadOnlyList? _items; - private readonly IReadOnlyList? _elements = new List().AsReadOnly(); - - public string Name { get; } = "local"; - public string Protocol { get; } = "local://"; - - public string? FullName { get; } - public string? NativePath => null; - public bool IsHidden => false; - public bool IsLoaded => true; - - public IContentProvider Provider => this; - - public AsyncEventHandler Refreshed { get; } = new(); - public bool IsCaseInsensitive { get; } - public SupportsDelete CanDelete => SupportsDelete.False; - public bool CanRename => false; - public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); - - public bool SupportsDirectoryLevelSoftDelete => false; - - public bool IsDestroyed => false; - public bool SupportsContentStreams => true; - public bool IsExists => true; public LocalContentProvider(ILogger logger) + : base("local", null, "local://", true) { _logger = logger; @@ -50,8 +21,7 @@ namespace FileTime.Providers.Local ? new DirectoryInfo("/").GetDirectories() : Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d)); - _rootContainers = rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name).ToList().AsReadOnly(); - _items = _rootContainers.Cast().ToList().AsReadOnly(); + SetRootContainers(rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name)); } public async Task GetByPath(string path, bool acceptDeepestMatch = false) @@ -62,11 +32,11 @@ namespace FileTime.Providers.Local if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && pathParts.Length == 1 && pathParts[0]?.Length == 0) return this; var normalizedRootContainerName = NormalizePath(pathParts[0]); - var rootContainer = _rootContainers.FirstOrDefault(c => NormalizePath(c.Name) == normalizedRootContainerName); + var rootContainer = RootContainers?.FirstOrDefault(c => NormalizePath(c.Name) == normalizedRootContainerName); if (rootContainer == null) { - _logger.LogWarning("No root container found with name '{RootContainerName}'", path[0]); + _logger.LogWarning("No root container found with name '{RootContainerName}'.", path[0]); return null; } @@ -74,52 +44,18 @@ namespace FileTime.Providers.Local return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch); } - public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); - - public Task CloneAsync() => Task.FromResult((IContainer)this); - - public IContainer? GetParent() => _parent; - public Task CreateContainerAsync(string name) => throw new NotSupportedException(); - public Task CreateElementAsync(string name) => throw new NotSupportedException(); - public Task IsExistsAsync(string name) => Task.FromResult(_rootContainers.Any(i => i.Name == name)); - - public Task Delete(bool hardDelete = false) => throw new NotSupportedException(); + public override Task CreateContainerAsync(string name) => throw new NotSupportedException(); + public override Task CreateElementAsync(string name) => throw new NotSupportedException(); internal string NormalizePath(string path) => IsCaseInsensitive ? path.ToLower() : path; - public bool CanHandlePath(string path) + public override async Task CanHandlePath(string path) { var normalizedPath = NormalizePath(path); Func match = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? c => normalizedPath.StartsWith(NormalizePath(c.Name)) : c => normalizedPath.StartsWith(NormalizePath("/" + c.Name)); - return _rootContainers.Any(match); - } - - public void SetParent(IContainer container) => _parent = container; - public Task> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainers); - - public Task?> GetItems(CancellationToken token = default) => Task.FromResult(_items); - public Task?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList?)_rootContainers); - public Task?> GetElements(CancellationToken token = default) => Task.FromResult(_elements); - - public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpenAsync() => Task.FromResult(true); - - public void Destroy() - { - foreach (var c in _rootContainers) - { - c.Unload(); - } - } - - public void Unload() - { - foreach (var c in _rootContainers) - { - c.Unload(); - } + return (await GetContainers())?.Any(match) ?? false; } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalFolder.cs b/src/Providers/FileTime.Providers.Local/LocalFolder.cs index d19d667..5bfcc59 100644 --- a/src/Providers/FileTime.Providers.Local/LocalFolder.cs +++ b/src/Providers/FileTime.Providers.Local/LocalFolder.cs @@ -21,6 +21,7 @@ namespace FileTime.Providers.Local NativePath = Directory.FullName; IsHidden = (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; CanRename = true; + AllowRecursiveDeletion = true; //TODO: Linux soft delete SupportsDirectoryLevelSoftDelete = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); diff --git a/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs b/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs index 81e7fe7..05401fc 100644 --- a/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs +++ b/src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs @@ -1,4 +1,3 @@ -using AsyncEvent; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -6,67 +5,28 @@ using Microsoft.Extensions.Logging; namespace FileTime.Providers.Sftp { - public class SftpContentProvider : IContentProvider + public class SftpContentProvider : ContentProviderBase, IContainer { - private IContainer? _parent; private readonly IInputInterface _inputInterface; - private readonly List _rootContainers; - private readonly IReadOnlyList _rootContainersReadOnly; - private IReadOnlyList? _items; - private readonly IReadOnlyList _elements = new List().AsReadOnly(); private readonly ILogger _logger; - public bool SupportsContentStreams => false; - - public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); - - public bool IsLoaded => true; - - public bool SupportsDirectoryLevelSoftDelete => false; - - public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler(); - - public string Name => "sftp"; - - public string? FullName => null; - - public string? NativePath => null; - - public bool IsHidden => false; - - public bool IsDestroyed => false; - - public SupportsDelete CanDelete => SupportsDelete.False; - - public bool CanRename => false; - - public IContentProvider Provider => this; - - public string Protocol => "sftp://"; - public bool IsExists => true; - public SftpContentProvider(IInputInterface inputInterface, ILogger logger) + : base("sftp", null, "sftp://", false) { _logger = logger; - _rootContainers = new List(); - _items = new List(); - _rootContainersReadOnly = _rootContainers.AsReadOnly(); _inputInterface = inputInterface; } - public bool CanHandlePath(string path) => path.StartsWith("sftp://"); - public Task CanOpenAsync() => Task.FromResult(true); - public Task CloneAsync() => Task.FromResult((IContainer)this); + public override Task CanHandlePath(string path) => Task.FromResult(path.StartsWith("sftp://")); - public async Task CreateContainerAsync(string name) + public override async Task CreateContainerAsync(string name) { - var container = _rootContainers.Find(c => c.Name == name); + var container = RootContainers?.FirstOrDefault(c => c.Name == name); if (container == null) { container = new SftpServer(name, this, _inputInterface); - _rootContainers.Add(container); - _items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly(); + await AddRootContainer(container); } await RefreshAsync(); @@ -76,50 +36,9 @@ namespace FileTime.Providers.Sftp return container; } - public Task CreateElementAsync(string name) - { - throw new NotSupportedException(); - } + public override Task CreateElementAsync(string name) => throw new NotSupportedException(); - public Task Delete(bool hardDelete = false) - { - throw new NotSupportedException(); - } - - public void Destroy() { } - - public async Task?> GetContainers(CancellationToken token = default) - { - await Init(); - return _rootContainersReadOnly; - } - public Task?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList?)_elements); - - public async Task?> GetItems(CancellationToken token = default) - { - await Init(); - return _items; - } - - public IContainer? GetParent() => _parent; - public Task> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainersReadOnly); - - public async Task IsExistsAsync(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; - - public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); - - public Task Rename(string newName) => throw new NotSupportedException(); - - public void SetParent(IContainer container) => _parent = container; - - public void Unload() { } - - private Task Init() - { - return Task.CompletedTask; - } - - public async Task GetByPath(string path, bool acceptDeepestMatch = false) + async Task IContainer.GetByPath(string path, bool acceptDeepestMatch) { if (path == null) return this; diff --git a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs index d76ed81..0708b1d 100644 --- a/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs +++ b/src/Providers/FileTime.Providers.Smb/SmbContentProvider.cs @@ -1,6 +1,4 @@ -using System; using System.Runtime.InteropServices; -using AsyncEvent; using FileTime.Core.Interactions; using FileTime.Core.Models; using FileTime.Core.Providers; @@ -8,61 +6,28 @@ using Microsoft.Extensions.Logging; namespace FileTime.Providers.Smb { - public class SmbContentProvider : IContentProvider + public class SmbContentProvider : ContentProviderBase, IContainer { - private readonly object _initializationGuard = new(); - private bool _initialized; - private bool _initializing; - private IContainer? _parent; private readonly IInputInterface _inputInterface; - private readonly List _rootContainers; - private readonly IReadOnlyList _rootContainersReadOnly; - private IReadOnlyList _items; - private readonly IReadOnlyList _elements = new List().AsReadOnly(); private readonly Persistence.PersistenceService _persistenceService; private readonly ILogger _logger; - public string Name { get; } = "smb"; - public string Protocol { get; } = "smb://"; - - public string? FullName => null; - public string? NativePath => null; - - public bool IsHidden => false; - public bool IsLoaded => true; - - public IContentProvider Provider => this; - public SupportsDelete CanDelete => SupportsDelete.False; - public bool CanRename => false; - public IReadOnlyList Exceptions { get; } = new List().AsReadOnly(); - - public AsyncEventHandler Refreshed { get; } = new(); - - public bool SupportsDirectoryLevelSoftDelete => false; - - public bool IsDestroyed => false; - public bool SupportsContentStreams => true; - public bool IsExists => true; - public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger logger) + : base("smb", null, "smb://", true) { - _rootContainers = new List(); - _items = new List(); - _rootContainersReadOnly = _rootContainers.AsReadOnly(); _inputInterface = inputInterface; _persistenceService = persistenceService; _logger = logger; } - public async Task CreateContainerAsync(string name) + public override async Task CreateContainerAsync(string name) { - var container = _rootContainers.Find(c => c.Name == name); + var container = RootContainers?.FirstOrDefault(c => c.Name == name); if (container == null) { container = new SmbServer(name, this, _inputInterface); - _rootContainers.Add(container); - _items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly(); + await AddRootContainer(container); } await RefreshAsync(); @@ -72,17 +37,9 @@ namespace FileTime.Providers.Smb return container; } - public Task CreateElementAsync(string name) - { - throw new NotSupportedException(); - } + public override Task CreateElementAsync(string name) => throw new NotSupportedException(); - public Task Delete(bool hardDelete = false) - { - throw new NotSupportedException(); - } - - public async Task GetByPath(string path, bool acceptDeepestMatch = false) + async Task IContainer.GetByPath(string path, bool acceptDeepestMatch) { if (path == null) return this; @@ -117,45 +74,13 @@ namespace FileTime.Providers.Smb } } - public IContainer? GetParent() => _parent; - - public Task CloneAsync() => Task.FromResult((IContainer)this); - - public async Task IsExistsAsync(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false; - - public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); - - public bool CanHandlePath(string path) => path.StartsWith("smb://") || path.StartsWith(@"\\"); - - public void SetParent(IContainer container) => _parent = container; - public Task> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainersReadOnly); - - public async Task?> GetItems(CancellationToken token = default) - { - await Init(); - return _items; - } - - public async Task?> GetContainers(CancellationToken token = default) - { - await Init(); - return _rootContainersReadOnly; - } - - public Task?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList?)_elements); - - public Task Rename(string newName) => throw new NotSupportedException(); - public Task CanOpenAsync() => Task.FromResult(true); - - public void Destroy() { } - - public void Unload() { } + public override Task CanHandlePath(string path) => Task.FromResult(path.StartsWith("smb://") || path.StartsWith(@"\\")); public async Task SaveServers() { try { - await _persistenceService.SaveServers(_rootContainers.OfType()); + await _persistenceService.SaveServers(RootContainers?.OfType() ?? Enumerable.Empty()); } catch (Exception e) { @@ -163,41 +88,10 @@ namespace FileTime.Providers.Smb } } - private async Task Init() + protected override async Task Init() { - while (true) - { - lock (_initializationGuard) - { - if (!_initializing) - { - _initializing = true; - break; - } - } - await Task.Delay(1); - } - try - { - if (_initialized) return; - if (_items.Count > 0) return; - _initialized = true; - - var servers = await _persistenceService.LoadServers(); - foreach (var server in servers) - { - var smbServer = new SmbServer(server.Path, this, _inputInterface, server.UserName, server.Password); - _rootContainers.Add(smbServer); - } - _items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly(); - } - finally - { - lock (_initializationGuard) - { - _initializing = false; - } - } + var servers = await _persistenceService.LoadServers(); + SetRootContainers(servers.Select(s => new SmbServer(s.Path, this, _inputInterface, s.UserName, s.Password))); } public static string GetNativePathSeparator() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/";