diff --git a/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs b/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
index 8c18873..4e95be8 100644
--- a/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
+++ b/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
@@ -31,9 +31,10 @@ public override void Dispose()
client.Dispose();
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
index 43d0dfe..6333eba 100644
--- a/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
+++ b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
@@ -28,9 +28,10 @@ public override void Dispose()
{
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
index 24a9876..19378dd 100644
--- a/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
+++ b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
@@ -27,9 +27,10 @@ public override void Dispose()
{
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs b/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
index 6ff9106..7f40622 100644
--- a/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
+++ b/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
@@ -29,9 +29,10 @@ public override void Dispose()
client.Dispose();
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.Ftp/FileSystem.Adapters.Ftp.csproj b/FileSystem.Adapters.Ftp/FileSystem.Adapters.Ftp.csproj
new file mode 100644
index 0000000..6c8c872
--- /dev/null
+++ b/FileSystem.Adapters.Ftp/FileSystem.Adapters.Ftp.csproj
@@ -0,0 +1,31 @@
+
+
+
+ SharpGrip.FileSystem.Adapters.Ftp
+
+
+
+ SharpGrip.FileSystem.Adapters.Ftp
+ SharpGrip.FileSystem.Adapters.Ftp
+ SharpGrip FileSystem FTP adapter
+ The SharpGrip FileSystem FTP adapter.
+ sharpgrip;file-system;ftp
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/FileSystem.Adapters.Ftp/src/FtpAdapter.cs b/FileSystem.Adapters.Ftp/src/FtpAdapter.cs
new file mode 100644
index 0000000..d95e289
--- /dev/null
+++ b/FileSystem.Adapters.Ftp/src/FtpAdapter.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentFTP;
+using FluentFTP.Exceptions;
+using SharpGrip.FileSystem.Constants;
+using SharpGrip.FileSystem.Exceptions;
+using SharpGrip.FileSystem.Extensions;
+using SharpGrip.FileSystem.Models;
+using SharpGrip.FileSystem.Utilities;
+using DirectoryNotFoundException = SharpGrip.FileSystem.Exceptions.DirectoryNotFoundException;
+using FileNotFoundException = SharpGrip.FileSystem.Exceptions.FileNotFoundException;
+
+namespace SharpGrip.FileSystem.Adapters.Ftp
+{
+ public class FtpAdapter : Adapter
+ {
+ private readonly IAsyncFtpClient client;
+
+ public FtpAdapter(string prefix, string rootPath, IAsyncFtpClient client, Action? configuration = null) : base(prefix, rootPath, configuration)
+ {
+ this.client = client;
+ }
+
+ public override void Dispose()
+ {
+ client.Dispose();
+ }
+
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
+ {
+ if (client.IsConnected)
+ {
+ return;
+ }
+
+ try
+ {
+ Logger.LogStartConnectingAdapter(this);
+ await client.Connect(cancellationToken);
+ Logger.LogFinishedConnectingAdapter(this);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ var path = GetPath(virtualPath);
+
+ try
+ {
+ var file = await client.GetObjectInfo(path, token: cancellationToken);
+
+ if (file == null || file.Type != FtpObjectType.File)
+ {
+ throw new FileNotFoundException(path, Prefix);
+ }
+
+ return ModelFactory.CreateFile(file, virtualPath);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ var path = GetPath(virtualPath);
+
+ try
+ {
+ var directory = await client.GetObjectInfo(path, token: cancellationToken);
+
+ if (directory == null || directory.Type != FtpObjectType.Directory)
+ {
+ throw new DirectoryNotFoundException(path, Prefix);
+ }
+
+ return ModelFactory.CreateDirectory(directory, virtualPath);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
+ {
+ await GetDirectoryAsync(virtualPath, cancellationToken);
+ var path = GetPath(virtualPath);
+
+ try
+ {
+ var ftpListItems = await client.GetListing(path, cancellationToken);
+
+ return ftpListItems.Where(file => file.Type == FtpObjectType.File).Select(file => ModelFactory.CreateFile(file, GetVirtualPath(file.FullName)));
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
+ {
+ await GetDirectoryAsync(virtualPath, cancellationToken);
+ var path = GetPath(virtualPath);
+
+ try
+ {
+ var ftpListItems = await client.GetListing(path, cancellationToken);
+
+ return ftpListItems.Where(file => file.Type == FtpObjectType.Directory).Select(file => ModelFactory.CreateDirectory(file, GetVirtualPath(file.FullName)));
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task CreateDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ if (await DirectoryExistsAsync(virtualPath, cancellationToken))
+ {
+ throw new DirectoryExistsException(GetPath(virtualPath), Prefix);
+ }
+
+ try
+ {
+ await client.CreateDirectory(GetPath(virtualPath), cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task DeleteDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetDirectoryAsync(virtualPath, cancellationToken);
+
+ try
+ {
+ await client.DeleteDirectory(GetPath(virtualPath), cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task DeleteFileAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetFileAsync(virtualPath, cancellationToken);
+
+ try
+ {
+ await client.DeleteFile(GetPath(virtualPath), cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task ReadFileStreamAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetFileAsync(virtualPath, cancellationToken);
+
+ try
+ {
+ var fileStream = await client.OpenRead(GetPath(virtualPath), token: cancellationToken);
+
+ return await StreamUtilities.CopyContentsToMemoryStreamAsync(fileStream, true, cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task WriteFileAsync(string virtualPath, Stream contents, bool overwrite = false, CancellationToken cancellationToken = default)
+ {
+ if (!overwrite && await FileExistsAsync(virtualPath, cancellationToken))
+ {
+ throw new FileExistsException(GetPath(virtualPath), Prefix);
+ }
+
+ try
+ {
+ contents.Seek(0, SeekOrigin.Begin);
+
+ using var writeStream = await client.OpenWrite(GetPath(virtualPath), token: cancellationToken);
+
+ await contents.CopyToAsync(writeStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
+ await writeStream.FlushAsync(cancellationToken);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task AppendFileAsync(string virtualPath, Stream contents, CancellationToken cancellationToken = default)
+ {
+ await GetFileAsync(virtualPath, cancellationToken);
+
+ try
+ {
+ using var fileStream = await client.OpenAppend(GetPath(virtualPath), token: cancellationToken);
+
+ await contents.CopyToAsync(fileStream);
+ }
+ catch (Exception exception)
+ {
+ throw new AdapterRuntimeException(exception);
+ }
+ }
+
+ protected override Exception Exception(Exception exception)
+ {
+ if (exception is FileSystemException)
+ {
+ return exception;
+ }
+
+ if (exception is SocketException socketException)
+ {
+ return new ConnectionException(socketException);
+ }
+
+ if (exception is FtpAuthenticationException ftpAuthenticationException)
+ {
+ return new ConnectionException(ftpAuthenticationException);
+ }
+
+ if (exception is FtpSecurityNotAvailableException ftpSecurityNotAvailableException)
+ {
+ return new ConnectionException(ftpSecurityNotAvailableException);
+ }
+
+ return new AdapterRuntimeException(exception);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.Ftp/src/FtpAdapterConfiguration.cs b/FileSystem.Adapters.Ftp/src/FtpAdapterConfiguration.cs
new file mode 100644
index 0000000..6d5764c
--- /dev/null
+++ b/FileSystem.Adapters.Ftp/src/FtpAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.Ftp
+{
+ public class FtpAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.Ftp/src/ModelFactory.cs b/FileSystem.Adapters.Ftp/src/ModelFactory.cs
new file mode 100644
index 0000000..7bced46
--- /dev/null
+++ b/FileSystem.Adapters.Ftp/src/ModelFactory.cs
@@ -0,0 +1,33 @@
+using FluentFTP;
+using SharpGrip.FileSystem.Models;
+
+namespace SharpGrip.FileSystem.Adapters.Ftp
+{
+ public static class ModelFactory
+ {
+ public static IFile CreateFile(FtpListItem file, string virtualPath)
+ {
+ return new FileModel
+ {
+ Name = file.Name,
+ Path = file.FullName,
+ VirtualPath = virtualPath,
+ Length = file.Size,
+ LastModifiedDateTime = file.Modified,
+ CreatedDateTime = file.Created
+ };
+ }
+
+ public static DirectoryModel CreateDirectory(FtpListItem directory, string virtualPath)
+ {
+ return new DirectoryModel
+ {
+ Name = directory.Name,
+ Path = directory.FullName,
+ VirtualPath = virtualPath,
+ LastModifiedDateTime = directory.Modified,
+ CreatedDateTime = directory.Created
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs
index c0f7f28..0b26edc 100644
--- a/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs
+++ b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs
@@ -40,9 +40,10 @@ public override void Dispose()
client.Dispose();
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
index 60d0ff5..15adbed 100644
--- a/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
+++ b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
@@ -29,9 +29,10 @@ public override void Dispose()
{
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
diff --git a/FileSystem.Adapters.Sftp/FileSystem.Adapters.Sftp.csproj b/FileSystem.Adapters.Sftp/FileSystem.Adapters.Sftp.csproj
index a8c6114..6fd1441 100644
--- a/FileSystem.Adapters.Sftp/FileSystem.Adapters.Sftp.csproj
+++ b/FileSystem.Adapters.Sftp/FileSystem.Adapters.Sftp.csproj
@@ -21,7 +21,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/FileSystem.Adapters.Sftp/src/SftpAdapter.cs b/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
index 8c0aad4..dc9e6ce 100644
--- a/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
+++ b/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
@@ -35,7 +35,7 @@ public override void Dispose()
((IBaseClient) client).Dispose();
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
if (client.IsConnected)
{
@@ -45,7 +45,7 @@ public override void Connect()
try
{
Logger.LogStartConnectingAdapter(this);
- client.Connect();
+ await client.ConnectAsync(cancellationToken);
Logger.LogFinishedConnectingAdapter(this);
}
catch (Exception exception)
@@ -224,7 +224,7 @@ public override async Task WriteFileAsync(string virtualPath, Stream contents, b
using var writeStream = client.OpenWrite(GetPath(virtualPath));
- await contents.CopyToAsync(writeStream, AdapterConstants.DefaultMemoryStreamBufferSize, cancellationToken);
+ await contents.CopyToAsync(writeStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
await writeStream.FlushAsync(cancellationToken);
}
catch (Exception exception)
diff --git a/FileSystem.sln b/FileSystem.sln
index 39ed7fe..46362fa 100644
--- a/FileSystem.sln
+++ b/FileSystem.sln
@@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.AzureFi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.Dropbox", "FileSystem.Adapters.Dropbox\FileSystem.Adapters.Dropbox.csproj", "{FA70DD02-3D25-44C6-ACC8-D61BA55F6C6F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.Ftp", "FileSystem.Adapters.Ftp\FileSystem.Adapters.Ftp.csproj", "{ED38402A-3667-4701-A974-4D7710E1544A}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.GoogleDrive", "FileSystem.Adapters.GoogleDrive\FileSystem.Adapters.GoogleDrive.csproj", "{CB1F411C-3FB5-4441-9541-423951C17BAF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.MicrosoftOneDrive", "FileSystem.Adapters.MicrosoftOneDrive\FileSystem.Adapters.MicrosoftOneDrive.csproj", "{9955422E-4A50-43CD-BC2B-91E6820F206C}"
@@ -44,6 +46,10 @@ Global
{FA70DD02-3D25-44C6-ACC8-D61BA55F6C6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA70DD02-3D25-44C6-ACC8-D61BA55F6C6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA70DD02-3D25-44C6-ACC8-D61BA55F6C6F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED38402A-3667-4701-A974-4D7710E1544A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED38402A-3667-4701-A974-4D7710E1544A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED38402A-3667-4701-A974-4D7710E1544A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED38402A-3667-4701-A974-4D7710E1544A}.Release|Any CPU.Build.0 = Release|Any CPU
{CB1F411C-3FB5-4441-9541-423951C17BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB1F411C-3FB5-4441-9541-423951C17BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB1F411C-3FB5-4441-9541-423951C17BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/FileSystem/FileSystem.csproj b/FileSystem/FileSystem.csproj
index 7c7fffc..2285baa 100644
--- a/FileSystem/FileSystem.csproj
+++ b/FileSystem/FileSystem.csproj
@@ -9,7 +9,7 @@
SharpGrip.FileSystem
SharpGrip FileSystem
SharpGrip FileSystem is a file system abstraction supporting multiple adapters.
- sharpgrip;file-system;amazon-s3;azure-blob-storage;azure-file-storage;dropbox;microsoft-onedrive;sftp
+ sharpgrip;file-system;amazon-s3;azure-blob-storage;azure-file-storage;dropbox;ftp;google-drive;microsoft-onedrive;sftp
diff --git a/FileSystem/src/Adapters/Adapter.cs b/FileSystem/src/Adapters/Adapter.cs
index aa39db7..a977c21 100644
--- a/FileSystem/src/Adapters/Adapter.cs
+++ b/FileSystem/src/Adapters/Adapter.cs
@@ -20,7 +20,7 @@ namespace SharpGrip.FileSystem.Adapters
{
public string Prefix { get; }
public string RootPath { get; }
- public string Name => GetType().FullName!;
+ public string Name { get; }
public IAdapterConfiguration AdapterConfiguration => Configuration;
protected TAdapterConfiguration Configuration { get; }
protected ILogger Logger { get; } = NullLogger>.Instance;
@@ -32,6 +32,7 @@ protected Adapter(string prefix, string rootPath, Action?
var adapterConfiguration = new TAdapterConfiguration();
configuration?.Invoke(adapterConfiguration);
+ Name = GetType().FullName!;
Configuration = adapterConfiguration;
if (Configuration.EnableLogging)
@@ -96,7 +97,7 @@ public virtual async Task AppendFileAsync(string virtualPath, Stream contents, C
using var memoryStream = await StreamUtilities.CopyContentsToMemoryStreamAsync(fileContents, false, cancellationToken);
- await contents.CopyToAsync(memoryStream, AdapterConstants.DefaultMemoryStreamBufferSize, cancellationToken);
+ await contents.CopyToAsync(memoryStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
memoryStream.Seek(0, SeekOrigin.Begin);
await DeleteFileAsync(virtualPath, cancellationToken);
@@ -116,7 +117,7 @@ public virtual async Task AppendFileAsync(string virtualPath, Stream contents, C
}
public abstract void Dispose();
- public abstract void Connect();
+ public abstract Task ConnectAsync(CancellationToken cancellationToken = default);
public abstract Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default);
public abstract Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default);
public abstract Task> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default);
diff --git a/FileSystem/src/Adapters/IAdapter.cs b/FileSystem/src/Adapters/IAdapter.cs
index 3839204..496959f 100644
--- a/FileSystem/src/Adapters/IAdapter.cs
+++ b/FileSystem/src/Adapters/IAdapter.cs
@@ -14,7 +14,7 @@ public interface IAdapter : IDisposable
public string RootPath { get; }
public string Name { get; }
public IAdapterConfiguration AdapterConfiguration { get; }
- public void Connect();
+ public Task ConnectAsync(CancellationToken cancellationToken = default);
public void ClearCache();
public Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default);
public Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default);
diff --git a/FileSystem/src/Adapters/LocalAdapter.cs b/FileSystem/src/Adapters/LocalAdapter.cs
index 1837a92..3a52558 100644
--- a/FileSystem/src/Adapters/LocalAdapter.cs
+++ b/FileSystem/src/Adapters/LocalAdapter.cs
@@ -22,9 +22,10 @@ public override void Dispose()
{
}
- public override void Connect()
+ public override async Task ConnectAsync(CancellationToken cancellationToken = default)
{
Logger.LogStartConnectingAdapter(this);
+ await Task.CompletedTask;
Logger.LogFinishedConnectingAdapter(this);
}
@@ -196,7 +197,7 @@ public override async Task WriteFileAsync(string virtualPath, Stream contents, b
using var fileStream = new FileStream(GetPath(virtualPath), FileMode.Create);
contents.Seek(0, SeekOrigin.Begin);
- await contents.CopyToAsync(fileStream, AdapterConstants.DefaultMemoryStreamBufferSize, cancellationToken);
+ await contents.CopyToAsync(fileStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
}
catch (Exception exception)
{
@@ -213,7 +214,7 @@ public override async Task AppendFileAsync(string virtualPath, Stream contents,
using var fileStream = new FileStream(GetPath(virtualPath), FileMode.Append);
contents.Seek(0, SeekOrigin.Begin);
- await contents.CopyToAsync(fileStream, AdapterConstants.DefaultMemoryStreamBufferSize, cancellationToken);
+ await contents.CopyToAsync(fileStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
}
catch (Exception exception)
{
diff --git a/FileSystem/src/Constants/AdapterConstants.cs b/FileSystem/src/Constants/AdapterConstants.cs
deleted file mode 100644
index 6b18777..0000000
--- a/FileSystem/src/Constants/AdapterConstants.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace SharpGrip.FileSystem.Constants
-{
- public static class AdapterConstants
- {
- public const int DefaultMemoryStreamBufferSize = 81920;
- }
-}
\ No newline at end of file
diff --git a/FileSystem/src/Constants/FileSystemConstants.cs b/FileSystem/src/Constants/FileSystemConstants.cs
new file mode 100644
index 0000000..2c14b4c
--- /dev/null
+++ b/FileSystem/src/Constants/FileSystemConstants.cs
@@ -0,0 +1,10 @@
+namespace SharpGrip.FileSystem.Constants
+{
+ public static class FileSystemConstants
+ {
+ public static class Streaming
+ {
+ public const int DefaultMemoryStreamBufferSize = 81920;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Exceptions/InvalidVirtualPathException.cs b/FileSystem/src/Exceptions/InvalidVirtualPathException.cs
new file mode 100644
index 0000000..71d26b5
--- /dev/null
+++ b/FileSystem/src/Exceptions/InvalidVirtualPathException.cs
@@ -0,0 +1,17 @@
+namespace SharpGrip.FileSystem.Exceptions
+{
+ public class InvalidVirtualPathException : FileSystemException
+ {
+ public string VirtualPath { get; }
+
+ public InvalidVirtualPathException(string virtualPath) : base(GetMessage(virtualPath))
+ {
+ VirtualPath = virtualPath;
+ }
+
+ private static string GetMessage(string virtualPath)
+ {
+ return $"Invalid virtual path '{virtualPath}'.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/FileSystem.cs b/FileSystem/src/FileSystem.cs
index 983b7c2..28d62ce 100644
--- a/FileSystem/src/FileSystem.cs
+++ b/FileSystem/src/FileSystem.cs
@@ -154,23 +154,25 @@ public IFile GetFile(string virtualPath)
/// Thrown when an adapter could not be found via the provided prefix.
/// Thrown when a prefix in the provided path could not be found.
/// Thrown if the file does not exists at the given path.
- public Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default)
+ public async Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default)
{
try
{
Logger.LogStartExecutingMethod(nameof(GetFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
adapter.ClearCache();
}
- return adapter.GetFileAsync(virtualPath, cancellationToken);
+ return await adapter.GetFileAsync(virtualPath, cancellationToken);
}
catch (FileSystemException fileSystemException)
{
@@ -213,23 +215,25 @@ public IDirectory GetDirectory(string virtualPath)
/// Thrown when an adapter could not be found via the provided prefix.
/// Thrown when a prefix in the provided path could not be found.
/// Thrown if the directory does not exists at the given path.
- public Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ public async Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
{
try
{
Logger.LogStartExecutingMethod(nameof(GetDirectoryAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
adapter.ClearCache();
}
- return adapter.GetDirectoryAsync(virtualPath, cancellationToken);
+ return await adapter.GetDirectoryAsync(virtualPath, cancellationToken);
}
catch (FileSystemException fileSystemException)
{
@@ -272,23 +276,25 @@ public IEnumerable GetFiles(string virtualPath = "")
/// Thrown when an adapter could not be found via the provided prefix.
/// Thrown when a prefix in the provided path could not be found.
/// Thrown if the directory does not exists at the given path.
- public Task> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
+ public async Task> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
{
try
{
Logger.LogStartExecutingMethod(nameof(GetFilesAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
adapter.ClearCache();
}
- return adapter.GetFilesAsync(virtualPath, cancellationToken);
+ return await adapter.GetFilesAsync(virtualPath, cancellationToken);
}
catch (FileSystemException fileSystemException)
{
@@ -331,23 +337,25 @@ public IEnumerable GetDirectories(string virtualPath = "")
/// Thrown when an adapter could not be found via the provided prefix.
/// Thrown when a prefix in the provided path could not be found.
/// Thrown if the directory does not exists at the given path.
- public Task> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
+ public async Task> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
{
try
{
Logger.LogStartExecutingMethod(nameof(GetDirectoriesAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
adapter.ClearCache();
}
- return adapter.GetDirectoriesAsync(virtualPath, cancellationToken);
+ return await adapter.GetDirectoriesAsync(virtualPath, cancellationToken);
}
catch (FileSystemException fileSystemException)
{
@@ -394,10 +402,12 @@ public async Task FileExistsAsync(string virtualPath, CancellationToken ca
{
Logger.LogStartExecutingMethod(nameof(FileExistsAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -451,10 +461,12 @@ public async Task DirectoryExistsAsync(string virtualPath, CancellationTok
{
Logger.LogStartExecutingMethod(nameof(DirectoryExistsAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -508,10 +520,12 @@ public async Task CreateDirectoryAsync(string virtualPath, CancellationToken can
{
Logger.LogStartExecutingMethod(nameof(CreateDirectoryAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -565,10 +579,12 @@ public async Task DeleteFileAsync(string virtualPath, CancellationToken cancella
{
Logger.LogStartExecutingMethod(nameof(DeleteFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -622,10 +638,12 @@ public async Task DeleteDirectoryAsync(string virtualPath, CancellationToken can
{
Logger.LogStartExecutingMethod(nameof(DeleteDirectoryAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -663,10 +681,12 @@ public async Task ReadFileStreamAsync(string virtualPath, CancellationTo
{
Logger.LogStartExecutingMethod(nameof(ReadFileStreamAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -722,6 +742,8 @@ public async Task ReadFileAsync(string virtualPath, CancellationToken ca
{
Logger.LogStartExecutingMethod(nameof(ReadFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var stream = await ReadFileStreamAsync(virtualPath, cancellationToken);
using var memoryStream = await StreamUtilities.CopyContentsToMemoryStreamAsync(stream, true, cancellationToken);
@@ -774,6 +796,8 @@ public async Task ReadTextFileAsync(string virtualPath, CancellationToke
{
Logger.LogStartExecutingMethod(nameof(ReadTextFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
using var stream = await ReadFileStreamAsync(virtualPath, cancellationToken);
using var streamReader = new StreamReader(stream);
@@ -830,20 +854,23 @@ public async Task CopyFileAsync(string virtualSourcePath, string virtualDestinat
{
Logger.LogStartExecutingMethod(nameof(CopyFileAsync));
+ virtualSourcePath = PathUtilities.NormalizeVirtualPath(virtualSourcePath);
+ virtualDestinationPath = PathUtilities.NormalizeVirtualPath(virtualDestinationPath);
+
var sourcePrefix = PathUtilities.GetPrefix(virtualSourcePath);
var sourceAdapter = GetAdapter(sourcePrefix);
var destinationPrefix = PathUtilities.GetPrefix(virtualDestinationPath);
var destinationAdapter = GetAdapter(destinationPrefix);
- sourceAdapter.Connect();
+ await sourceAdapter.ConnectAsync(cancellationToken);
if (!sourceAdapter.AdapterConfiguration.EnableCache)
{
sourceAdapter.ClearCache();
}
- destinationAdapter.Connect();
+ await destinationAdapter.ConnectAsync(cancellationToken);
if (!destinationAdapter.AdapterConfiguration.EnableCache)
{
@@ -904,20 +931,23 @@ public async Task MoveFileAsync(string virtualSourcePath, string virtualDestinat
{
Logger.LogStartExecutingMethod(nameof(MoveFileAsync));
+ virtualSourcePath = PathUtilities.NormalizeVirtualPath(virtualSourcePath);
+ virtualDestinationPath = PathUtilities.NormalizeVirtualPath(virtualDestinationPath);
+
var sourcePrefix = PathUtilities.GetPrefix(virtualSourcePath);
var sourceAdapter = GetAdapter(sourcePrefix);
var destinationPrefix = PathUtilities.GetPrefix(virtualDestinationPath);
var destinationAdapter = GetAdapter(destinationPrefix);
- sourceAdapter.Connect();
+ await sourceAdapter.ConnectAsync(cancellationToken);
if (!sourceAdapter.AdapterConfiguration.EnableCache)
{
sourceAdapter.ClearCache();
}
- destinationAdapter.Connect();
+ await destinationAdapter.ConnectAsync(cancellationToken);
if (!destinationAdapter.AdapterConfiguration.EnableCache)
{
@@ -961,10 +991,12 @@ public async Task WriteFileAsync(string virtualPath, Stream contents, bool overw
{
Logger.LogStartExecutingMethod(nameof(WriteFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
@@ -1078,10 +1110,12 @@ public async Task AppendFileAsync(string virtualPath, Stream contents, Cancellat
{
Logger.LogStartExecutingMethod(nameof(AppendFileAsync));
+ virtualPath = PathUtilities.NormalizeVirtualPath(virtualPath);
+
var prefix = PathUtilities.GetPrefix(virtualPath);
var adapter = GetAdapter(prefix);
- adapter.Connect();
+ await adapter.ConnectAsync(cancellationToken);
if (!adapter.AdapterConfiguration.EnableCache)
{
diff --git a/FileSystem/src/Utilities/PathUtilities.cs b/FileSystem/src/Utilities/PathUtilities.cs
index 2077fb8..61a008e 100644
--- a/FileSystem/src/Utilities/PathUtilities.cs
+++ b/FileSystem/src/Utilities/PathUtilities.cs
@@ -36,6 +36,28 @@ public static string NormalizeRootPath(string rootPath)
return rootPath.Replace(InvalidPathSeparator, PathSeparator).RemoveTrailingForwardSlash();
}
+ ///
+ /// Normalizes a virtual path.
+ ///
+ /// The virtual path.
+ /// The normalized virtual path.
+ public static string NormalizeVirtualPath(string virtualPath)
+ {
+ if (!virtualPath.Contains(AdapterPrefixSeparator))
+ {
+ throw new InvalidVirtualPathException(virtualPath);
+ }
+
+ var prefixAndPath = ResolvePrefixAndPath(virtualPath);
+
+ if (prefixAndPath.Length == 1)
+ {
+ return virtualPath;
+ }
+
+ return prefixAndPath[0] + AdapterPrefixSeparator + prefixAndPath[1].RemoveLeadingForwardSlash();
+ }
+
///
/// Returns the path from a prefixed path.
///
diff --git a/FileSystem/src/Utilities/StreamUtilities.cs b/FileSystem/src/Utilities/StreamUtilities.cs
index 4462951..aba6178 100644
--- a/FileSystem/src/Utilities/StreamUtilities.cs
+++ b/FileSystem/src/Utilities/StreamUtilities.cs
@@ -10,7 +10,7 @@ public static class StreamUtilities
public static async Task CopyContentsToMemoryStreamAsync(Stream sourceStream, bool setPositionToStart, CancellationToken cancellationToken = default)
{
var memoryStream = new MemoryStream();
- await sourceStream.CopyToAsync(memoryStream, AdapterConstants.DefaultMemoryStreamBufferSize, cancellationToken);
+ await sourceStream.CopyToAsync(memoryStream, FileSystemConstants.Streaming.DefaultMemoryStreamBufferSize, cancellationToken);
if (setPositionToStart)
{
diff --git a/README.md b/README.md
index 7eadfd1..aeb6ee4 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ For adapters other than the local file system (included in the `SharpGrip.FileSy
| [AzureBlobStorage](#azureblobstorage-adapter) | `SharpGrip.FileSystem.Adapters.AzureBlobStorage` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.AzureBlobStorage) |
| [AzureFileStorage](#azurefilestorage-adapter) | `SharpGrip.FileSystem.Adapters.AzureFileStorage` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.AzureFileStorage) |
| [Dropbox](#dropbox-adapter) | `SharpGrip.FileSystem.Adapters.Dropbox` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.Dropbox) |
+| [FTP](#ftp-adapter) | `SharpGrip.FileSystem.Adapters.Ftp` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.Ftp) |
| [GoogleDrive](#googledrive-adapter) | `SharpGrip.FileSystem.Adapters.GoogleDrive` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.GoogleDrive) |
| [MicrosoftOneDrive](#microsoftonedrive-adapter) | `SharpGrip.FileSystem.Adapters.MicrosoftOneDrive` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.MicrosoftOneDrive) |
| [SFTP](#sftp-adapter) | `SharpGrip.FileSystem.Adapters.Sftp` | [](https://www.nuget.org/packages/SharpGrip.FileSystem.Adapters.Sftp) |
@@ -128,6 +129,21 @@ var adapters = new List
var fileSystem = new FileSystem(adapters);
```
+### FTP adapter
+
+```
+// FTP connection.
+var ftpClient = new AsyncFtpClient("hostname", "username", "password");
+
+var adapters = new List
+{
+ new LocalAdapter("local", "/var/files"),
+ new FtpAdapter("ftp", "/var/files", ftpClient)
+};
+
+var fileSystem = new FileSystem(adapters);
+```
+
### GoogleDrive adapter
```
diff --git a/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs b/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
index 0342332..45f1a59 100644
--- a/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
@@ -36,14 +36,12 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var amazonS3Client = Substitute.For();
var amazonS3Adapter = new AmazonS3Adapter("prefix", "/root-path", amazonS3Client, "bucket");
- amazonS3Adapter.Connect();
-
- return Task.CompletedTask;
+ await amazonS3Adapter.ConnectAsync();
}
[Fact]
diff --git a/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs b/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
index 38e18a5..b57bc9a 100644
--- a/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
@@ -19,14 +19,12 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var blobContainerClient = Substitute.For();
var azureBlobStorageAdapter = new AzureBlobStorageAdapter("prefix", "/root-path", blobContainerClient);
- azureBlobStorageAdapter.Connect();
-
- return Task.CompletedTask;
+ await azureBlobStorageAdapter.ConnectAsync();
}
[Fact]
diff --git a/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs b/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
index 3dedba0..9ada96b 100644
--- a/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
@@ -19,14 +19,12 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var shareClient = Substitute.For();
var azureFileStorageAdapter = new AzureFileStorageAdapter("prefix", "/root-path", shareClient);
- azureFileStorageAdapter.Connect();
-
- return Task.CompletedTask;
+ await azureFileStorageAdapter.ConnectAsync();
}
[Fact]
diff --git a/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs b/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs
index c19ea3a..ed76597 100644
--- a/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs
@@ -19,14 +19,12 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var googleDriveClient = Substitute.For();
var googleDriveAdapter = new GoogleDriveAdapter("prefix", "/root-path", googleDriveClient);
- googleDriveAdapter.Connect();
-
- return Task.CompletedTask;
+ await googleDriveAdapter.ConnectAsync();
}
[Fact]
diff --git a/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs b/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
index e6cc23d..78072aa 100644
--- a/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
@@ -21,15 +21,13 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var delegateAuthenticationProvider = new DelegateAuthenticationProvider(message => Task.FromResult(message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "12345")));
var graphServiceClient = Substitute.For(delegateAuthenticationProvider, null);
var microsoftOneDriveAdapter = new MicrosoftOneDriveAdapter("prefix", "/root-path", graphServiceClient, "driveId");
- microsoftOneDriveAdapter.Connect();
-
- return Task.CompletedTask;
+ await microsoftOneDriveAdapter.ConnectAsync();
}
[Fact]
diff --git a/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs b/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
index d410219..df4b1a4 100644
--- a/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
@@ -31,14 +31,12 @@ public void Test_Instantiation()
}
[Fact]
- public Task Test_Connect()
+ public async Task Test_Connect()
{
var sftpClient = Substitute.For();
var sftpAdapter = new SftpAdapter("prefix", "/root-path", sftpClient);
- sftpAdapter.Connect();
-
- return Task.CompletedTask;
+ await sftpAdapter.ConnectAsync();
}
[Fact]