Sftp, fullname+nativepath refactor
This commit is contained in:
@@ -4,6 +4,7 @@ using FileTime.Core.CommandHandlers;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Providers.Sftp;
|
||||
using FileTime.Providers.Smb;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -18,11 +19,11 @@ namespace FileTime.App.Core
|
||||
return serviceCollection
|
||||
.AddSingleton<IClipboard, Clipboard.Clipboard>()
|
||||
.AddSingleton<TopContainer>()
|
||||
.AddSingleton<LocalContentProvider>()
|
||||
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||
.AddSingleton<IContentProvider, SmbContentProvider>()
|
||||
.AddSingleton<CommandExecutor>()
|
||||
.AddSingleton<TimeRunner>()
|
||||
.AddLocalServices()
|
||||
.AddSmbServices()
|
||||
.AddSftpServices()
|
||||
.RegisterCommandHandlers();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
|
||||
|
||||
@@ -61,7 +61,6 @@ namespace FileTime.Core.Components
|
||||
{
|
||||
if (_currentlySelecting) return false;
|
||||
|
||||
if (_currentSelectedItem == value) return false;
|
||||
IItem? itemToSelect = null;
|
||||
if (value != null)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace FileTime.Core.Models
|
||||
Task RefreshAsync(CancellationToken token = default);
|
||||
async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||
{
|
||||
if (path == null) return this;
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]);
|
||||
|
||||
134
src/Core/FileTime.Core/Providers/AbstractContainer.cs
Normal file
134
src/Core/FileTime.Core/Providers/AbstractContainer.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Providers
|
||||
{
|
||||
public abstract class AbstractContainer<TProvider> : IContainer where TProvider : IContentProvider
|
||||
{
|
||||
private readonly IContainer _parent;
|
||||
private readonly List<Exception> _exceptions = new();
|
||||
private IReadOnlyList<IContainer>? _containers;
|
||||
private IReadOnlyList<IItem>? _items;
|
||||
private IReadOnlyList<IElement>? _elements;
|
||||
|
||||
public IReadOnlyList<Exception> Exceptions { get; }
|
||||
|
||||
public bool IsLoaded { get; protected set; }
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete { get; protected set; }
|
||||
|
||||
public AsyncEventHandler Refreshed { get; protected set; } = new();
|
||||
|
||||
public string Name { get; protected set; }
|
||||
|
||||
public string? FullName { get; protected set; }
|
||||
|
||||
public string? NativePath { get; protected set; }
|
||||
|
||||
public virtual bool IsHidden { get; protected set; }
|
||||
|
||||
public bool IsDestroyed { get; protected set; }
|
||||
|
||||
public virtual SupportsDelete CanDelete { get; protected set; }
|
||||
|
||||
public virtual bool CanRename { get; protected set; }
|
||||
|
||||
public TProvider Provider { get; }
|
||||
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
|
||||
protected AbstractContainer(TProvider provider, IContainer parent, string name)
|
||||
{
|
||||
_parent = parent;
|
||||
Provider = provider;
|
||||
Name = name;
|
||||
FullName = (parent?.FullName ?? Name) + Constants.SeparatorChar + Name;
|
||||
Exceptions = _exceptions.AsReadOnly();
|
||||
}
|
||||
|
||||
public abstract Task<bool> CanOpenAsync();
|
||||
|
||||
public abstract Task<IContainer> CloneAsync();
|
||||
|
||||
public abstract Task<IContainer> CreateContainerAsync(string name);
|
||||
|
||||
public abstract Task<IElement> CreateElementAsync(string name);
|
||||
|
||||
public abstract Task Delete(bool hardDelete = false);
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
_items = null;
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
IsDestroyed = true;
|
||||
Refreshed = new AsyncEventHandler();
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
if (_containers == null) await RefreshAsync(token);
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
if (_elements == null) await RefreshAsync(token);
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
if (_items == null) await RefreshAsync(token);
|
||||
return _items;
|
||||
}
|
||||
|
||||
public virtual IContainer? GetParent() => _parent;
|
||||
|
||||
public virtual async Task<bool> IsExistsAsync(string name)
|
||||
{
|
||||
var items = await GetItems();
|
||||
return items?.Any(i => i.Name == name) ?? false;
|
||||
}
|
||||
|
||||
public virtual async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
var containers = new List<IContainer>();
|
||||
var elements = new List<IElement>();
|
||||
foreach (var item in await RefreshItems(token))
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
containers.Add(container);
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (_items != null)
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_containers = containers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||
_elements = elements.OrderBy(e => e.Name).ToList().AsReadOnly();
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
public abstract Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default);
|
||||
|
||||
public abstract Task Rename(string newName);
|
||||
|
||||
public virtual void Unload()
|
||||
{
|
||||
_items = null;
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace FileTime.Core.Providers
|
||||
public interface IContentProvider : IContainer
|
||||
{
|
||||
bool SupportsContentStreams { get; }
|
||||
string Protocol { get; }
|
||||
|
||||
Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default);
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace FileTime.Core.Timeline
|
||||
public bool IsDestroyed => false;
|
||||
public bool SupportsContentStreams => false;
|
||||
|
||||
public string Protocol => "time2://";
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
|
||||
@@ -39,6 +39,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{0D2B4BAA
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Tools.Compression", "Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj", "{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Sftp", "Providers\FileTime.Providers.Sftp\FileTime.Providers.Sftp.csproj", "{0E650206-801D-4E8D-95BA-4565B32092E1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -293,6 +295,26 @@ Global
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -310,6 +332,7 @@ Global
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7} = {890275FF-943A-4D07-83BA-14E5C52D7846}
|
||||
{B6F6A8F9-9B7B-4E3E-AE99-A90ECFDDC966} = {0D2B4BAA-0399-459C-B022-41DB7F408225}
|
||||
{0E650206-801D-4E8D-95BA-4565B32092E1} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183}
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
|
||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
|
||||
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
|
||||
<converters:StringReplaceConverter x:Key="PathPreformatter" OldValue="://" NewValue="/"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace FileTime.Avalonia
|
||||
.AddServices()
|
||||
.RegisterLogging()
|
||||
.AddViewModels()
|
||||
.RegisterCommandHandlers()
|
||||
.BuildServiceProvider()
|
||||
.InitSerilog();
|
||||
|
||||
|
||||
@@ -51,16 +51,11 @@ namespace FileTime.Avalonia.Application
|
||||
{
|
||||
if (!_updateFromCode && value != null)
|
||||
{
|
||||
/*try
|
||||
{*/
|
||||
/*var task = SetSelectedItemAsync(value, true);
|
||||
Task.WaitAll(new Task[] { task }, 100);*/
|
||||
Task.Run(async () => await SetSelectedItemAsync(value, true)).Wait();
|
||||
/*}
|
||||
catch
|
||||
try
|
||||
{
|
||||
//TODO: Debug, linux start after restore 3 tabs
|
||||
}*/
|
||||
Task.Run(async () => await SetSelectedItemAsync(value, true)).Wait();
|
||||
}
|
||||
catch (AggregateException e) when (e.InnerExceptions.Count == 1 && e.InnerExceptions[0] is IndexOutOfRangeException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class StringReplaceConverter : IValueConverter
|
||||
{
|
||||
public string? OldValue { get; set; }
|
||||
public string? NewValue { get; set; }
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is string s && OldValue != null && NewValue != null ? s.Replace(OldValue, NewValue) : value;
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Persistence;
|
||||
using FileTime.Providers.Smb;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
@@ -39,7 +38,6 @@ namespace FileTime.Avalonia
|
||||
.AddSingleton(new PersistenceSettings(Program.AppDataRoot))
|
||||
.AddSingleton<ProgramsService>()
|
||||
.AddSingleton<ToastMessageSink>()
|
||||
.AddSmbServices()
|
||||
.AddSingleton<IIconProvider, MaterialIconProvider>();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
@@ -53,15 +51,6 @@ namespace FileTime.Avalonia
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
internal static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
foreach (var commandHandler in Providers.Local.Startup.GetCommandHandlers())
|
||||
{
|
||||
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<Rectangle Fill="#01000000"/>
|
||||
|
||||
<StackPanel Margin="20,10" Orientation="Horizontal">
|
||||
<local:PathPresenter DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
||||
<local:PathPresenter DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName,Converter={StaticResource PathPreformatter}}"/>
|
||||
<TextBlock
|
||||
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentBrush}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace FileTime.Providers.Local
|
||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
||||
|
||||
public string Name { get; } = "local";
|
||||
public string Protocol { get; } = "local://";
|
||||
|
||||
public string? FullName { get; }
|
||||
public string? NativePath => null;
|
||||
@@ -59,7 +60,7 @@ namespace FileTime.Providers.Local
|
||||
path = path.Replace(Path.DirectorySeparatorChar, Constants.SeparatorChar).TrimEnd(Constants.SeparatorChar);
|
||||
var pathParts = (IsCaseInsensitive ? path.ToLower() : path).TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && pathParts.Length == 1 && pathParts[0] == "") return this;
|
||||
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);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using FileTime.Core.Providers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Providers.Local
|
||||
{
|
||||
public static class Startup
|
||||
{
|
||||
public static Type[] GetCommandHandlers()
|
||||
public static IServiceCollection AddLocalServices(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return Array.Empty<Type>();
|
||||
return serviceCollection
|
||||
.AddSingleton<LocalContentProvider>()
|
||||
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SSH.NET" Version="2020.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\FileTime.Core\FileTime.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
63
src/Providers/FileTime.Providers.Sftp/SftpClientContext.cs
Normal file
63
src/Providers/FileTime.Providers.Sftp/SftpClientContext.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public class SftpClientContext
|
||||
{
|
||||
private readonly Func<int, Task<SftpClient>> _getSftpClient;
|
||||
private readonly Action _disposeClient;
|
||||
private bool _isRunning;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public SftpClientContext(Func<int, Task<SftpClient>> getSftpClient, Action disposeClient)
|
||||
{
|
||||
_getSftpClient = getSftpClient;
|
||||
_disposeClient = disposeClient;
|
||||
}
|
||||
public async Task RunWithSftpClientAsync(Action<SftpClient> action, int maxRetries = SftpServer.MAXRETRIES)
|
||||
{
|
||||
await RunWithSftpClientAsync<object?>((client) => { action(client); return null; }, maxRetries);
|
||||
}
|
||||
|
||||
public async Task<T> RunWithSftpClientAsync<T>(Func<SftpClient, T> func, int maxRetries = SftpServer.MAXRETRIES)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_isRunning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1);
|
||||
}
|
||||
try
|
||||
{
|
||||
SftpClient client;
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
client = await _getSftpClient(maxRetries);
|
||||
return func(client);
|
||||
}
|
||||
//TODO: dispose client on Sftp exception
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Exception was thrown while executing method with SftpClient.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs
Normal file
153
src/Providers/FileTime.Providers.Sftp/SftpContentProvider.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public class SftpContentProvider : IContentProvider
|
||||
{
|
||||
private IContainer? _parent;
|
||||
private readonly IInputInterface _inputInterface;
|
||||
private readonly List<IContainer> _rootContainers;
|
||||
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
||||
private IReadOnlyList<IItem>? _items;
|
||||
private readonly IReadOnlyList<IElement> _elements = new List<IElement>().AsReadOnly();
|
||||
private readonly ILogger<SftpContentProvider> _logger;
|
||||
|
||||
public bool SupportsContentStreams => false;
|
||||
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().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 SftpContentProvider(IInputInterface inputInterface, ILogger<SftpContentProvider> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_rootContainers = new List<IContainer>();
|
||||
_items = new List<IItem>();
|
||||
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
||||
_inputInterface = inputInterface;
|
||||
}
|
||||
|
||||
public bool CanHandlePath(string path) => path.StartsWith("sftp://");
|
||||
public Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
var container = _rootContainers.Find(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 RefreshAsync();
|
||||
|
||||
//await SaveServers();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElementAsync(string name)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task Delete(bool hardDelete = false)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Destroy() { }
|
||||
|
||||
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
await Init();
|
||||
return _rootContainersReadOnly;
|
||||
}
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IElement>?)_elements);
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
await Init();
|
||||
return _items;
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainersReadOnly);
|
||||
|
||||
public async Task<bool> 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<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||
{
|
||||
if (path == null) return this;
|
||||
|
||||
var pathParts = path.TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||
|
||||
var rootContainer = (await GetContainers())?.FirstOrDefault(c => c.Name == pathParts[0]);
|
||||
|
||||
if (rootContainer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1));
|
||||
try
|
||||
{
|
||||
return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error while getting path {Path}", path);
|
||||
if (acceptDeepestMatch)
|
||||
{
|
||||
return rootContainer ?? this;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/Providers/FileTime.Providers.Sftp/SftpFile.cs
Normal file
71
src/Providers/FileTime.Providers.Sftp/SftpFile.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public class SftpFile : IElement
|
||||
{
|
||||
public bool IsSpecial => throw new NotImplementedException();
|
||||
|
||||
public string Name => throw new NotImplementedException();
|
||||
|
||||
public string? FullName => throw new NotImplementedException();
|
||||
|
||||
public string? NativePath => throw new NotImplementedException();
|
||||
|
||||
public bool IsHidden => throw new NotImplementedException();
|
||||
|
||||
public bool IsDestroyed => throw new NotImplementedException();
|
||||
|
||||
public SupportsDelete CanDelete => throw new NotImplementedException();
|
||||
|
||||
public bool CanRename => throw new NotImplementedException();
|
||||
|
||||
public IContentProvider Provider => throw new NotImplementedException();
|
||||
|
||||
public Task Delete(bool hardDelete = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string> GetContent(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContentReader> GetContentReaderAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContentWriter> GetContentWriterAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<long> GetElementSize(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Providers/FileTime.Providers.Sftp/SftpFolder.cs
Normal file
44
src/Providers/FileTime.Providers.Sftp/SftpFolder.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public class SftpFolder : AbstractContainer<SftpContentProvider>
|
||||
{
|
||||
private readonly SftpServer _server;
|
||||
|
||||
public SftpFolder(SftpContentProvider provider, SftpServer server, IContainer parent, string path) : base(provider, parent, path)
|
||||
{
|
||||
_server = server;
|
||||
NativePath = FullName;
|
||||
}
|
||||
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
public override Task<IContainer> CloneAsync()
|
||||
{
|
||||
return Task.FromResult((IContainer)new SftpFolder(Provider, _server, GetParent()!, Name));
|
||||
}
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task Delete(bool hardDelete = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => await _server.ListDirectory(FullName![(_server.FullName!.Length + 1)..]);
|
||||
|
||||
public override Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/Providers/FileTime.Providers.Sftp/SftpServer.cs
Normal file
175
src/Providers/FileTime.Providers.Sftp/SftpServer.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public class SftpServer : AbstractContainer<SftpContentProvider>
|
||||
{
|
||||
internal const int MAXRETRIES = 5;
|
||||
private bool _reenterCredentials;
|
||||
private SftpClient? _client;
|
||||
private readonly SftpClientContext _sftpClientContext;
|
||||
private bool _refreshingClient;
|
||||
private readonly object _clientGuard = new();
|
||||
private readonly IInputInterface _inputInterface;
|
||||
|
||||
public string? Username { get; private set; }
|
||||
public string? Password { get; private set; }
|
||||
|
||||
public SftpServer(string name, SftpContentProvider sftpContentProvider, IInputInterface inputInterface, string? username = null, string? password = null)
|
||||
: base(sftpContentProvider, sftpContentProvider, name)
|
||||
{
|
||||
_inputInterface = inputInterface;
|
||||
_sftpClientContext = new SftpClientContext(GetClient, () => { });
|
||||
Username = username;
|
||||
Password = password;
|
||||
|
||||
Name = name;
|
||||
NativePath = FullName = sftpContentProvider.Protocol + Constants.SeparatorChar + name;
|
||||
|
||||
CanDelete = SupportsDelete.True;
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<IItem>> RefreshItems(CancellationToken token = default) => await ListDirectory("");
|
||||
|
||||
public override Task<bool> CanOpenAsync() => Task.FromResult(true);
|
||||
|
||||
public override Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
|
||||
|
||||
public override Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<IElement> CreateElementAsync(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task Delete(bool hardDelete = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Unload()
|
||||
{
|
||||
base.Unload();
|
||||
|
||||
lock (_clientGuard)
|
||||
{
|
||||
_client?.Disconnect();
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SftpClient> GetClient(int maxRetries = MAXRETRIES)
|
||||
{
|
||||
bool isClientNull;
|
||||
lock (_clientGuard)
|
||||
{
|
||||
isClientNull = _client == null;
|
||||
}
|
||||
|
||||
int reTries = 0;
|
||||
while (isClientNull)
|
||||
{
|
||||
if (!await RefreshSftpClient())
|
||||
{
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
lock (_clientGuard)
|
||||
{
|
||||
isClientNull = _client == null;
|
||||
}
|
||||
|
||||
if (reTries >= maxRetries)
|
||||
{
|
||||
throw new Exception($"Could not connect to server {Name} after {reTries} retry");
|
||||
}
|
||||
reTries++;
|
||||
}
|
||||
return _client!;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSftpClient()
|
||||
{
|
||||
lock (_clientGuard)
|
||||
{
|
||||
if (_refreshingClient) return false;
|
||||
_refreshingClient = true;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (_reenterCredentials || Username == null || Password == null)
|
||||
{
|
||||
var inputs = await _inputInterface.ReadInputs(
|
||||
new InputElement[]
|
||||
{
|
||||
InputElement.ForText($"Username for '{Name}'", Username ?? ""),
|
||||
InputElement.ForPassword($"Password for '{Name}'", Password ?? "")
|
||||
});
|
||||
|
||||
Username = inputs[0];
|
||||
Password = inputs[1];
|
||||
}
|
||||
|
||||
var client = new SftpClient(Name, Username, Password);
|
||||
try
|
||||
{
|
||||
client.Connect();
|
||||
}
|
||||
catch (SshAuthenticationException)
|
||||
{
|
||||
_reenterCredentials = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
lock (_clientGuard)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_clientGuard)
|
||||
{
|
||||
_refreshingClient = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IItem>> ListDirectory(string path)
|
||||
{
|
||||
return await _sftpClientContext.RunWithSftpClientAsync(client =>
|
||||
{
|
||||
var containers = new List<IContainer>();
|
||||
var elements = new List<IElement>();
|
||||
|
||||
foreach (var file in client.ListDirectory(path))
|
||||
{
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
var container = new SftpFolder(Provider, this, this, file.Name);
|
||||
containers.Add(container);
|
||||
}
|
||||
}
|
||||
|
||||
return containers.Cast<IItem>().Concat(elements);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Providers/FileTime.Providers.Sftp/Startup.cs
Normal file
14
src/Providers/FileTime.Providers.Sftp/Startup.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using FileTime.Core.Providers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Providers.Sftp
|
||||
{
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddSftpServices(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection
|
||||
.AddSingleton<IContentProvider, SftpContentProvider>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
@@ -22,8 +23,9 @@ namespace FileTime.Providers.Smb
|
||||
private readonly ILogger<SmbContentProvider> _logger;
|
||||
|
||||
public string Name { get; } = "smb";
|
||||
public string Protocol { get; } = "smb://";
|
||||
|
||||
public string? FullName { get; }
|
||||
public string? FullName => null;
|
||||
public string? NativePath => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
@@ -53,12 +55,11 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
var fullName = "\\\\" + name;
|
||||
var container = _rootContainers.Find(c => c.Name == name);
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
container = new SmbServer(fullName, this, _inputInterface);
|
||||
container = new SmbServer(name, this, _inputInterface);
|
||||
_rootContainers.Add(container);
|
||||
_items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||
}
|
||||
@@ -84,7 +85,10 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
if (path == null) return this;
|
||||
|
||||
var pathParts = path.TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||
path = path.TrimStart(Constants.SeparatorChar);
|
||||
if (path.StartsWith("\\\\")) path = path[2..];
|
||||
else if (path.StartsWith("smb://")) path = path[6..];
|
||||
var pathParts = path.Split(Constants.SeparatorChar);
|
||||
|
||||
var rootContainer = (await GetContainers())?.FirstOrDefault(c => c.Name == pathParts[0]);
|
||||
|
||||
@@ -195,6 +199,6 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetNativePath(string fullName) => fullName.Replace("/", "\\");
|
||||
public static string GetNativePathSeparator() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/";
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,14 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public SmbFile(string name, SmbContentProvider provider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext)
|
||||
{
|
||||
Name = name;
|
||||
FullName = parent.FullName + Constants.SeparatorChar + Name;
|
||||
NativePath = SmbContentProvider.GetNativePath(FullName);
|
||||
|
||||
Provider = provider;
|
||||
_parent = parent;
|
||||
_smbClientContext = smbClientContext;
|
||||
_smbShare = smbShare;
|
||||
|
||||
Name = name;
|
||||
FullName = parent.FullName + Constants.SeparatorChar + Name;
|
||||
NativePath = parent.NativePath + SmbContentProvider.GetNativePathSeparator() + name;
|
||||
}
|
||||
|
||||
public async Task Delete(bool hardDelete = false)
|
||||
@@ -146,6 +146,6 @@ namespace FileTime.Providers.Smb
|
||||
});
|
||||
}
|
||||
|
||||
private string GetPathFromShare() => SmbContentProvider.GetNativePath(FullName![(_smbShare.FullName!.Length + 1)..]);
|
||||
private string GetPathFromShare() => FullName![(_smbShare.FullName!.Length + 1)..].Replace("/", "\\");
|
||||
}
|
||||
}
|
||||
@@ -37,19 +37,19 @@ namespace FileTime.Providers.Smb
|
||||
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext)
|
||||
{
|
||||
_parent = parent;
|
||||
_smbClientContext = smbClientContext;
|
||||
SmbShare = smbShare;
|
||||
|
||||
Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
NativePath = SmbContentProvider.GetNativePath(FullName);
|
||||
FullName = parent.FullName! + Constants.SeparatorChar + Name;
|
||||
NativePath = parent.NativePath + SmbContentProvider.GetNativePathSeparator() + name;
|
||||
Provider = contentProvider;
|
||||
_smbClientContext = smbClientContext;
|
||||
}
|
||||
|
||||
public async Task<IContainer> CreateContainerAsync(string name)
|
||||
{
|
||||
var path = FullName![(SmbShare.FullName!.Length + 1)..] + Constants.SeparatorChar + name;
|
||||
await SmbShare.CreateContainerWithPathAsync(SmbContentProvider.GetNativePath(path));
|
||||
await SmbShare.CreateContainerWithPathAsync(path.Replace("/", "\\"));
|
||||
await RefreshAsync();
|
||||
|
||||
return _containers!.FirstOrDefault(e => e.Name == name)!;
|
||||
@@ -58,7 +58,7 @@ namespace FileTime.Providers.Smb
|
||||
public async Task<IElement> CreateElementAsync(string name)
|
||||
{
|
||||
var path = FullName![(SmbShare.FullName!.Length + 1)..] + Constants.SeparatorChar + name;
|
||||
await SmbShare.CreateElementWithPathAsync(SmbContentProvider.GetNativePath(path));
|
||||
await SmbShare.CreateElementWithPathAsync(path.Replace("/", "\\"));
|
||||
await RefreshAsync();
|
||||
|
||||
return _elements!.FirstOrDefault(e => e.Name == name)!;
|
||||
@@ -161,6 +161,6 @@ namespace FileTime.Providers.Smb
|
||||
_elements = null;
|
||||
}
|
||||
|
||||
private string GetPathFromShare() => SmbContentProvider.GetNativePath(FullName![(SmbShare.FullName!.Length + 1)..]);
|
||||
private string GetPathFromShare() => FullName![(SmbShare.FullName!.Length + 1)..].Replace("/", "\\");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
@@ -56,7 +57,11 @@ namespace FileTime.Providers.Smb
|
||||
Password = password;
|
||||
|
||||
Provider = contentProvider;
|
||||
NativePath = FullName = Name = path;
|
||||
Name = path;
|
||||
FullName = contentProvider.Protocol + Name;
|
||||
NativePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? "\\\\" + Name
|
||||
: contentProvider.Protocol + Name;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
@@ -182,11 +187,11 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
try
|
||||
{
|
||||
var couldParse = IPAddress.TryParse(Name[2..], out var ipAddress);
|
||||
var couldParse = IPAddress.TryParse(Name, out var ipAddress);
|
||||
var client = new SMB2Client();
|
||||
var connected = couldParse
|
||||
? client.Connect(ipAddress, SMBTransportType.DirectTCPTransport)
|
||||
: client.Connect(Name[2..], SMBTransportType.DirectTCPTransport);
|
||||
: client.Connect(Name, SMBTransportType.DirectTCPTransport);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace FileTime.Providers.Smb
|
||||
_smbClientContext = smbClientContext;
|
||||
|
||||
Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
NativePath = SmbContentProvider.GetNativePath(FullName);
|
||||
FullName = parent.FullName! + Constants.SeparatorChar + Name;
|
||||
NativePath = parent.NativePath + SmbContentProvider.GetNativePathSeparator() + name;
|
||||
Provider = contentProvider;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Providers.Smb.Persistence;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -8,7 +9,8 @@ namespace FileTime.Providers.Smb
|
||||
public static IServiceCollection AddSmbServices(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection
|
||||
.AddSingleton<PersistenceService>();
|
||||
.AddSingleton<PersistenceService>()
|
||||
.AddSingleton<IContentProvider, SmbContentProvider>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user