diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml
index 13db036..d262036 100644
--- a/.github/workflows/Build.yaml
+++ b/.github/workflows/Build.yaml
@@ -3,7 +3,11 @@ name: FileSystem [Build]
env:
JAVA_VERSION: 17
JAVA_DISTRIBUTION: microsoft
- DOTNET_VERSION: 7.0.x
+ DOTNET_VERSION: |
+ 3.1.x
+ 6.0.x
+ 7.0.x
+ 8.0.x
DOTNET_BUILD_CONFIGURATION: Release
SONAR_PATH: .\.sonar\scanner
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -34,7 +38,7 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- - name: Setup .NET
+ - name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
@@ -55,7 +59,7 @@ jobs:
- name: Test solution
run: dotnet test --no-build -c ${{ env.DOTNET_BUILD_CONFIGURATION }} --verbosity normal
- - name: Cleanup solution
+ - name: Clean solution
run: dotnet clean
- name: Analyze solution
diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml
index a055080..7d50678 100644
--- a/.github/workflows/Release.yaml
+++ b/.github/workflows/Release.yaml
@@ -22,7 +22,7 @@ jobs:
with:
fetch-depth: 0
- - name: Setup .NET
+ - name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
diff --git a/Directory.Build.props b/Directory.Build.props
index 3613eb8..3c4fe1b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- netstandard2.0;netstandard2.1
+ netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0
enable
8.0
NU1701
diff --git a/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs b/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
index 04f8bb3..fdf4501 100644
--- a/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
+++ b/FileSystem.Adapters.AmazonS3/src/AmazonS3Adapter.cs
@@ -14,12 +14,12 @@
namespace SharpGrip.FileSystem.Adapters.AmazonS3
{
- public class AmazonS3Adapter : Adapter
+ public class AmazonS3Adapter : Adapter
{
private readonly IAmazonS3 client;
private readonly string bucketName;
- public AmazonS3Adapter(string prefix, string rootPath, IAmazonS3 client, string bucketName) : base(prefix, rootPath)
+ public AmazonS3Adapter(string prefix, string rootPath, IAmazonS3 client, string bucketName, Action? configuration = null) : base(prefix, rootPath, configuration)
{
this.client = client;
this.bucketName = bucketName;
@@ -61,12 +61,7 @@ public override async Task GetFileAsync(string virtualPath, CancellationT
public override async Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
{
- var path = GetPath(virtualPath);
-
- if (!path.EndsWith("/"))
- {
- path += "/";
- }
+ var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
try
{
@@ -109,12 +104,7 @@ public override async Task GetDirectoryAsync(string virtualPath, Can
public override async Task> GetFilesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
{
await GetDirectoryAsync(virtualPath, cancellationToken);
- var path = GetPath(virtualPath);
-
- if (!path.EndsWith("/"))
- {
- path += "/";
- }
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash();
if (path == "/")
{
@@ -134,7 +124,6 @@ public override async Task> GetFilesAsync(string virtualPath
foreach (var item in response.S3Objects)
{
- // var itemName = item.Key.Substring(0, item.Key.Length - path.Length);
var itemName = item.Key.Substring(path.Length).RemoveLeadingForwardSlash();
if (!item.Key.EndsWith("/") && !itemName.Contains('/'))
@@ -157,12 +146,7 @@ public override async Task> GetFilesAsync(string virtualPath
public override async Task> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
{
await GetDirectoryAsync(virtualPath, cancellationToken);
- var path = GetPath(virtualPath);
-
- if (!path.EndsWith("/"))
- {
- path += "/";
- }
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash();
if (path == "/")
{
@@ -208,8 +192,7 @@ public override async Task CreateDirectoryAsync(string virtualPath, Cancellation
throw new DirectoryExistsException(GetPath(virtualPath), Prefix);
}
- var path = GetPath(virtualPath);
- path = path.EndsWith("/") ? path : path + "/";
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash();
try
{
@@ -226,8 +209,7 @@ public override async Task DeleteDirectoryAsync(string virtualPath, Cancellation
{
await GetDirectoryAsync(virtualPath, cancellationToken);
- var path = GetPath(virtualPath);
- path = path.EndsWith("/") ? path : path + "/";
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash();
try
{
diff --git a/FileSystem.Adapters.AmazonS3/src/AmazonS3AdapterConfiguration.cs b/FileSystem.Adapters.AmazonS3/src/AmazonS3AdapterConfiguration.cs
new file mode 100644
index 0000000..b669788
--- /dev/null
+++ b/FileSystem.Adapters.AmazonS3/src/AmazonS3AdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.AmazonS3
+{
+ public class AmazonS3AdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
index 0d74a80..ef97030 100644
--- a/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
+++ b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapter.cs
@@ -14,11 +14,12 @@
namespace SharpGrip.FileSystem.Adapters.AzureBlobStorage
{
- public class AzureBlobStorageAdapter : Adapter
+ public class AzureBlobStorageAdapter : Adapter
{
private readonly BlobContainerClient client;
- public AzureBlobStorageAdapter(string prefix, string rootPath, BlobContainerClient client) : base(prefix, rootPath)
+ public AzureBlobStorageAdapter(string prefix, string rootPath, BlobContainerClient client, Action? configuration = null)
+ : base(prefix, rootPath, configuration)
{
this.client = client;
}
diff --git a/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapterConfiguration.cs b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapterConfiguration.cs
new file mode 100644
index 0000000..603709a
--- /dev/null
+++ b/FileSystem.Adapters.AzureBlobStorage/src/AzureBlobStorageAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.AzureBlobStorage
+{
+ public class AzureBlobStorageAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
index a5068f8..906047f 100644
--- a/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
+++ b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapter.cs
@@ -12,11 +12,11 @@
namespace SharpGrip.FileSystem.Adapters.AzureFileStorage
{
- public class AzureFileStorageAdapter : Adapter
+ public class AzureFileStorageAdapter : Adapter
{
private readonly ShareClient client;
- public AzureFileStorageAdapter(string prefix, string rootPath, ShareClient client) : base(prefix, rootPath)
+ public AzureFileStorageAdapter(string prefix, string rootPath, ShareClient client, Action? configuration = null) : base(prefix, rootPath, configuration)
{
this.client = client;
}
diff --git a/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapterConfiguration.cs b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapterConfiguration.cs
new file mode 100644
index 0000000..c8c8f26
--- /dev/null
+++ b/FileSystem.Adapters.AzureFileStorage/src/AzureFileStorageAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.AzureFileStorage
+{
+ public class AzureFileStorageAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs b/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
index 3e1dc26..3ed37c8 100644
--- a/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
+++ b/FileSystem.Adapters.Dropbox/src/DropboxAdapter.cs
@@ -13,11 +13,11 @@
namespace SharpGrip.FileSystem.Adapters.Dropbox
{
- public class DropboxAdapter : Adapter
+ public class DropboxAdapter : Adapter
{
private readonly DropboxClient client;
- public DropboxAdapter(string prefix, string rootPath, DropboxClient client) : base(prefix, rootPath)
+ public DropboxAdapter(string prefix, string rootPath, DropboxClient client, Action? configuration = null) : base(prefix, rootPath, configuration)
{
this.client = client;
}
diff --git a/FileSystem.Adapters.Dropbox/src/DropboxAdapterConfiguration.cs b/FileSystem.Adapters.Dropbox/src/DropboxAdapterConfiguration.cs
new file mode 100644
index 0000000..7feb351
--- /dev/null
+++ b/FileSystem.Adapters.Dropbox/src/DropboxAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.Dropbox
+{
+ public class DropboxAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.GoogleDrive/FileSystem.Adapters.GoogleDrive.csproj b/FileSystem.Adapters.GoogleDrive/FileSystem.Adapters.GoogleDrive.csproj
new file mode 100644
index 0000000..72aea8b
--- /dev/null
+++ b/FileSystem.Adapters.GoogleDrive/FileSystem.Adapters.GoogleDrive.csproj
@@ -0,0 +1,27 @@
+
+
+
+ SharpGrip.FileSystem.Adapters.GoogleDrive
+
+
+
+ SharpGrip.FileSystem.Adapters.GoogleDrive
+ SharpGrip.FileSystem.Adapters.GoogleDrive
+ SharpGrip FileSystem Google Drive adapter
+ The SharpGrip FileSystem Google Drive adapter.
+ sharpgrip;file-system;google-drive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs
new file mode 100644
index 0000000..0e38d80
--- /dev/null
+++ b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapter.cs
@@ -0,0 +1,469 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.Apis.Drive.v3;
+using Google.Apis.Drive.v3.Data;
+using SharpGrip.FileSystem.Cache;
+using SharpGrip.FileSystem.Exceptions;
+using SharpGrip.FileSystem.Extensions;
+using SharpGrip.FileSystem.Models;
+using SharpGrip.FileSystem.Utilities;
+using DirectoryNotFoundException = SharpGrip.FileSystem.Exceptions.DirectoryNotFoundException;
+using File = Google.Apis.Drive.v3.Data.File;
+using FileNotFoundException = SharpGrip.FileSystem.Exceptions.FileNotFoundException;
+
+namespace SharpGrip.FileSystem.Adapters.GoogleDrive
+{
+ public class GoogleDriveAdapter : Adapter
+ {
+ private readonly DriveService client;
+
+ private const string DirectoryMimeType = "application/vnd.google-apps.folder";
+ private const string SingleRequestFields = "id, name, size, modifiedTime, createdTime, parents";
+ private static readonly string MultipleRequestFields = $"nextPageToken, files({SingleRequestFields})";
+ private static readonly string FileRequestQuery = $"mimeType != '{DirectoryMimeType}' and trashed = false";
+ private static readonly string DirectoryRequestQuery = $"mimeType = '{DirectoryMimeType}' and trashed = false";
+
+ public GoogleDriveAdapter(string prefix, string rootPath, DriveService client, Action? configuration = null) : base(prefix, rootPath, configuration)
+ {
+ this.client = client;
+ }
+
+ public override void Dispose()
+ {
+ client.Dispose();
+ }
+
+ public override void Connect()
+ {
+ }
+
+ public override async Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ var path = GetPath(virtualPath).RemoveLeadingForwardSlash();
+
+ try
+ {
+ var file = await RequestFileByPath(path, cancellationToken);
+
+ if (file == null)
+ {
+ throw new FileNotFoundException(path, Prefix);
+ }
+
+ return ModelFactory.CreateFile(file, path, virtualPath);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task GetDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash();
+
+ try
+ {
+ if (path == "/")
+ {
+ return ModelFactory.CreateDirectory(new File {Name = "/"}, path, virtualPath);
+ }
+
+ path = path.RemoveLeadingForwardSlash();
+
+ var directory = await RequestDirectoryByPath(path, cancellationToken);
+
+ if (directory == null)
+ {
+ throw new DirectoryNotFoundException(path, Prefix);
+ }
+
+ return ModelFactory.CreateDirectory(directory, path, 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).EnsureTrailingForwardSlash().RemoveLeadingForwardSlash();
+
+ if (path == "")
+ {
+ path = "/";
+ }
+
+ try
+ {
+ var request = client.Files.List();
+ FileList fileList;
+
+ request.Q = FileRequestQuery;
+ request.Fields = MultipleRequestFields;
+
+ var files = new List();
+
+ do
+ {
+ fileList = await request.ExecuteAsync(cancellationToken);
+
+ foreach (var file in fileList.Files)
+ {
+ TryAddCacheEntry(new CacheEntry(file.Id, file));
+
+ var filePath = await GetAbsolutePath(file);
+ var directoryPath = GetParentPathPart(filePath).EnsureTrailingForwardSlash();
+
+ if (directoryPath == path)
+ {
+ files.Add(ModelFactory.CreateFile(file, filePath, GetVirtualPath(filePath)));
+ }
+ }
+
+ request.PageToken = fileList.NextPageToken;
+ } while (fileList.NextPageToken != null);
+
+ return files;
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task> GetDirectoriesAsync(string virtualPath = "", CancellationToken cancellationToken = default)
+ {
+ await GetDirectoryAsync(virtualPath, cancellationToken);
+
+ var path = GetPath(virtualPath).EnsureTrailingForwardSlash().RemoveLeadingForwardSlash();
+
+ if (path == "")
+ {
+ path = "/";
+ }
+
+ try
+ {
+ var request = client.Files.List();
+ FileList directoryList;
+
+ request.Q = DirectoryRequestQuery;
+ request.Fields = MultipleRequestFields;
+
+ var directories = new List();
+
+ do
+ {
+ directoryList = await request.ExecuteAsync(cancellationToken);
+
+ foreach (var directory in directoryList.Files)
+ {
+ TryAddCacheEntry(new CacheEntry(directory.Id, directory));
+
+ var directoryPath = (await GetAbsolutePath(directory)).EnsureTrailingForwardSlash();
+ var directoryParentPathPart = GetParentPathPart(directoryPath).EnsureTrailingForwardSlash();
+
+ if (directoryParentPathPart == path)
+ {
+ directories.Add(ModelFactory.CreateDirectory(directory, directoryPath, GetVirtualPath(directoryPath)));
+ }
+ }
+
+ request.PageToken = directoryList.NextPageToken;
+ } while (directoryList.NextPageToken != null);
+
+ return directories;
+ }
+ 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);
+ }
+
+ var path = GetPath(virtualPath).RemoveTrailingForwardSlash().RemoveLeadingForwardSlash();
+
+ if (path == "")
+ {
+ path = "/";
+ }
+
+ try
+ {
+ var parent = await RequestParentDirectory(path, cancellationToken);
+
+ var directory = new File
+ {
+ Name = GetLastPathPart(path),
+ Parents = new List {parent.Id},
+ MimeType = DirectoryMimeType
+ };
+
+ var request = client.Files.Create(directory);
+ request.Fields = SingleRequestFields;
+
+ var response = await request.ExecuteAsync(cancellationToken);
+
+ TryAddCacheEntry(new CacheEntry(response.Id, response));
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task DeleteDirectoryAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetDirectoryAsync(virtualPath, cancellationToken);
+
+ var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
+
+ try
+ {
+ var directory = await RequestDirectoryByPath(path, cancellationToken);
+
+ if (directory == null)
+ {
+ throw new DirectoryNotFoundException(GetPath(virtualPath), Prefix);
+ }
+
+ var deleteRequest = client.Files.Delete(directory.Id);
+ await deleteRequest.ExecuteAsync(cancellationToken);
+
+ TryRemoveCacheEntry(directory.Id);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task DeleteFileAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetFileAsync(virtualPath, cancellationToken);
+
+ var path = GetPath(virtualPath).RemoveLeadingForwardSlash().EnsureTrailingForwardSlash();
+
+ try
+ {
+ var file = await RequestFileByPath(path, cancellationToken);
+
+ if (file == null)
+ {
+ throw new FileNotFoundException(GetPath(virtualPath), Prefix);
+ }
+
+ var deleteRequest = client.Files.Delete(file.Id);
+ await deleteRequest.ExecuteAsync(cancellationToken);
+
+ TryRemoveCacheEntry(file.Id);
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ public override async Task ReadFileStreamAsync(string virtualPath, CancellationToken cancellationToken = default)
+ {
+ await GetFileAsync(virtualPath, cancellationToken);
+
+ var path = GetPath(virtualPath).RemoveLeadingForwardSlash();
+
+ try
+ {
+ var file = await RequestFileByPath(path, cancellationToken);
+
+ var memoryStream = new MemoryStream();
+ var request = client.Files.Get(file!.Id);
+
+ await request.DownloadAsync(memoryStream, cancellationToken);
+
+ memoryStream.Position = 0;
+
+ return memoryStream;
+ }
+ 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);
+ }
+
+ var path = GetPath(virtualPath);
+
+ try
+ {
+ var parent = await RequestParentDirectory(path, cancellationToken);
+ var contentType = ContentTypeProvider.GetContentType(path);
+
+ var file = new File
+ {
+ Name = GetLastPathPart(path),
+ Parents = new List {parent.Id},
+ MimeType = contentType
+ };
+
+ var request = client.Files.Create(file, contents, contentType);
+ request.Fields = SingleRequestFields;
+
+ await request.UploadAsync(cancellationToken);
+
+ var response = request.ResponseBody;
+
+ TryAddCacheEntry(new CacheEntry(response.Id, response));
+ }
+ catch (Exception exception)
+ {
+ throw Exception(exception);
+ }
+ }
+
+ protected override Exception Exception(Exception exception)
+ {
+ if (exception is FileSystemException)
+ {
+ return exception;
+ }
+
+ return new AdapterRuntimeException(exception);
+ }
+
+ private async Task GetAbsolutePath(File file)
+ {
+ if (file.Parents == null || !file.Parents.Any())
+ {
+ return file.Name;
+ }
+
+ var pathParts = new List {file.Name};
+
+ while (file.Parents != null && file.Parents.Any())
+ {
+ var parentId = file.Parents[0];
+ var cacheEntry = await GetOrCreateCacheEntryAsync(parentId, async () => new CacheEntry(parentId, await RequestFileById(parentId)));
+ var parent = cacheEntry.Value;
+
+ if (parent.Parents == null || !parent.Parents.Any())
+ {
+ break;
+ }
+
+ pathParts.Insert(0, parent.Name);
+ file = parent;
+ }
+
+ return string.Join("/", pathParts);
+ }
+
+ private async Task RequestParentDirectory(string path, CancellationToken cancellationToken = default)
+ {
+ var parentPathPart = GetParentPathPart(path);
+
+ var parentDirectory = await RequestDirectoryByPath(parentPathPart, cancellationToken);
+
+ if (parentDirectory != null)
+ {
+ return parentDirectory;
+ }
+
+ return await RequestRootDrive(cancellationToken);
+ }
+
+ private async Task RequestRootDrive(CancellationToken cancellationToken = default)
+ {
+ return await RequestFileById("root", cancellationToken);
+ }
+
+ private async Task RequestFileById(string id, CancellationToken cancellationToken = default)
+ {
+ var request = client.Files.Get(id);
+ request.Fields = SingleRequestFields;
+
+ var file = await request.ExecuteAsync(cancellationToken);
+
+ TryAddCacheEntry(new CacheEntry(file.Id, file));
+
+ return file;
+ }
+
+ private async Task RequestFileByPath(string path, CancellationToken cancellationToken = default)
+ {
+ var request = client.Files.List();
+
+ request.Q = $"mimeType != '{DirectoryMimeType}' and name = '{GetLastPathPart(path)}' and trashed = false";
+ request.Fields = MultipleRequestFields;
+
+ do
+ {
+ var fileList = await request.ExecuteAsync(cancellationToken);
+
+ foreach (var file in fileList.Files)
+ {
+ TryAddCacheEntry(new CacheEntry(file.Id, file));
+
+ var filePath = await GetAbsolutePath(file);
+
+ if (filePath == path)
+ {
+ return file;
+ }
+ }
+
+ request.PageToken = fileList.NextPageToken;
+ } while (request.PageToken != null);
+
+ return null;
+ }
+
+ private async Task RequestDirectoryByPath(string path, CancellationToken cancellationToken = default)
+ {
+ var request = client.Files.List();
+ FileList directoryList;
+
+ request.Q = $"mimeType = '{DirectoryMimeType}' and name = '{GetLastPathPart(path)}' and trashed = false";
+ request.Fields = MultipleRequestFields;
+
+ do
+ {
+ directoryList = await request.ExecuteAsync(cancellationToken);
+
+ foreach (var directory in directoryList.Files)
+ {
+ TryAddCacheEntry(new CacheEntry(directory.Id, directory));
+
+ var directoryPath = (await GetAbsolutePath(directory)).EnsureTrailingForwardSlash();
+
+ if (directoryPath == path)
+ {
+ return directory;
+ }
+ }
+
+ request.PageToken = directoryList.NextPageToken;
+ } while (directoryList.NextPageToken != null);
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapterConfiguration.cs b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapterConfiguration.cs
new file mode 100644
index 0000000..e258ddb
--- /dev/null
+++ b/FileSystem.Adapters.GoogleDrive/src/GoogleDriveAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.GoogleDrive
+{
+ public class GoogleDriveAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.GoogleDrive/src/ModelFactory.cs b/FileSystem.Adapters.GoogleDrive/src/ModelFactory.cs
new file mode 100644
index 0000000..4a079e2
--- /dev/null
+++ b/FileSystem.Adapters.GoogleDrive/src/ModelFactory.cs
@@ -0,0 +1,33 @@
+using Google.Apis.Drive.v3.Data;
+using SharpGrip.FileSystem.Models;
+
+namespace SharpGrip.FileSystem.Adapters.GoogleDrive
+{
+ public static class ModelFactory
+ {
+ public static FileModel CreateFile(File file, string path, string virtualPath)
+ {
+ return new FileModel
+ {
+ Name = file.Name,
+ Path = path,
+ VirtualPath = virtualPath,
+ Length = file.Size,
+ LastModifiedDateTime = file.ModifiedByMeTimeDateTimeOffset?.DateTime,
+ CreatedDateTime = file.CreatedTimeDateTimeOffset?.DateTime
+ };
+ }
+
+ public static DirectoryModel CreateDirectory(File directory, string path, string virtualPath)
+ {
+ return new DirectoryModel
+ {
+ Name = directory.Name,
+ Path = path,
+ VirtualPath = virtualPath,
+ LastModifiedDateTime = directory.ModifiedByMeTimeDateTimeOffset?.DateTime,
+ CreatedDateTime = directory.CreatedTimeDateTimeOffset?.DateTime
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
index 5340d29..69f3082 100644
--- a/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
+++ b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapter.cs
@@ -12,12 +12,13 @@
namespace SharpGrip.FileSystem.Adapters.MicrosoftOneDrive
{
- public class MicrosoftOneDriveAdapter : Adapter
+ public class MicrosoftOneDriveAdapter : Adapter
{
private readonly GraphServiceClient client;
private readonly string driveId;
- public MicrosoftOneDriveAdapter(string prefix, string rootPath, GraphServiceClient client, string driveId) : base(prefix, rootPath)
+ public MicrosoftOneDriveAdapter(string prefix, string rootPath, GraphServiceClient client, string driveId, Action? configuration = null)
+ : base(prefix, rootPath, configuration)
{
this.client = client;
this.driveId = driveId;
diff --git a/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapterConfiguration.cs b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapterConfiguration.cs
new file mode 100644
index 0000000..9472e76
--- /dev/null
+++ b/FileSystem.Adapters.MicrosoftOneDrive/src/MicrosoftOneDriveAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.MicrosoftOneDrive
+{
+ public class MicrosoftOneDriveAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.Adapters.Sftp/src/SftpAdapter.cs b/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
index bfe2356..5143e6e 100644
--- a/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
+++ b/FileSystem.Adapters.Sftp/src/SftpAdapter.cs
@@ -16,11 +16,11 @@
namespace SharpGrip.FileSystem.Adapters.Sftp
{
- public class SftpAdapter : Adapter
+ public class SftpAdapter : Adapter
{
private readonly SftpClient client;
- public SftpAdapter(string prefix, string rootPath, SftpClient client) : base(prefix, rootPath)
+ public SftpAdapter(string prefix, string rootPath, SftpClient client, Action? configuration = null) : base(prefix, rootPath, configuration)
{
this.client = client;
}
diff --git a/FileSystem.Adapters.Sftp/src/SftpAdapterConfiguration.cs b/FileSystem.Adapters.Sftp/src/SftpAdapterConfiguration.cs
new file mode 100644
index 0000000..c3a20fe
--- /dev/null
+++ b/FileSystem.Adapters.Sftp/src/SftpAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters.Sftp
+{
+ public class SftpAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem.sln b/FileSystem.sln
index 9750f15..39ed7fe 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.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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSystem.Adapters.Sftp", "FileSystem.Adapters.Sftp\FileSystem.Adapters.Sftp.csproj", "{DFDEAACC-CCFA-4A78-8E7D-AC61FB1A002D}"
@@ -42,6 +44,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
+ {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
+ {CB1F411C-3FB5-4441-9541-423951C17BAF}.Release|Any CPU.Build.0 = Release|Any CPU
{9955422E-4A50-43CD-BC2B-91E6820F206C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9955422E-4A50-43CD-BC2B-91E6820F206C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9955422E-4A50-43CD-BC2B-91E6820F206C}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/FileSystem/FileSystem.csproj b/FileSystem/FileSystem.csproj
index 3543ee4..2e55a29 100644
--- a/FileSystem/FileSystem.csproj
+++ b/FileSystem/FileSystem.csproj
@@ -21,6 +21,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/FileSystem/src/Adapters/Adapter.cs b/FileSystem/src/Adapters/Adapter.cs
index a77f32f..837e137 100644
--- a/FileSystem/src/Adapters/Adapter.cs
+++ b/FileSystem/src/Adapters/Adapter.cs
@@ -5,8 +5,9 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using SharpGrip.FileSystem.Cache;
+using SharpGrip.FileSystem.Configuration;
using SharpGrip.FileSystem.Constants;
-using SharpGrip.FileSystem.Extensions;
using SharpGrip.FileSystem.Models;
using SharpGrip.FileSystem.Utilities;
using DirectoryNotFoundException = SharpGrip.FileSystem.Exceptions.DirectoryNotFoundException;
@@ -14,15 +15,26 @@
namespace SharpGrip.FileSystem.Adapters
{
- public abstract class Adapter : IAdapter
+ public abstract class Adapter : IAdapter
+ where TAdapterConfiguration : IAdapterConfiguration, new()
+ where TCacheKey : class
{
public string Prefix { get; }
public string RootPath { get; }
+ public TAdapterConfiguration Configuration { get; }
+ public IAdapterConfiguration AdapterConfiguration => Configuration;
- protected Adapter(string prefix, string rootPath)
+ private IDictionary> Cache { get; set; } = new Dictionary>();
+
+ protected Adapter(string prefix, string rootPath, Action? configuration)
{
Prefix = prefix;
- RootPath = rootPath;
+ RootPath = PathUtilities.NormalizeRootPath(rootPath);
+
+ var adapterConfiguration = new TAdapterConfiguration();
+ configuration?.Invoke(adapterConfiguration);
+
+ Configuration = adapterConfiguration;
}
public IFile GetFile(string virtualPath)
@@ -230,29 +242,54 @@ protected string GetVirtualPath(string path)
protected string[] GetPathParts(string path)
{
- return path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
+ return PathUtilities.GetPathParts(path);
}
protected string GetLastPathPart(string path)
{
- if (path.IsNullOrEmpty())
+ return PathUtilities.GetLastPathPart(path);
+ }
+
+ protected string GetParentPathPart(string path)
+ {
+ return PathUtilities.GetParentPathPart(path);
+ }
+
+ public void ClearCache()
+ {
+ Cache = new Dictionary>();
+ }
+
+ protected async Task> GetOrCreateCacheEntryAsync(TCacheKey cacheKey, Func>> factory)
+ {
+ var cacheEntry = GetCacheEntry(cacheKey);
+
+ if (cacheEntry == null)
{
- return "";
+ cacheEntry = await factory();
+
+ TryAddCacheEntry(cacheEntry);
}
- return GetPathParts(path).Last();
+ return cacheEntry;
}
- protected string GetParentPathPart(string path)
+ protected void TryAddCacheEntry(CacheEntry cacheEntry)
{
- if (path.IsNullOrEmpty())
+ if (!Cache.ContainsKey(cacheEntry.Key))
{
- path = "/";
+ Cache.Add(cacheEntry.Key, cacheEntry);
}
+ }
- var pathParts = GetPathParts(path);
+ protected void TryRemoveCacheEntry(TCacheKey cacheKey)
+ {
+ Cache.Remove(cacheKey);
+ }
- return string.Join("/", pathParts.Take(pathParts.Length - 1));
+ private CacheEntry? GetCacheEntry(TCacheKey cacheKey)
+ {
+ return Cache.TryGetValue(cacheKey, out var cacheEntry) ? cacheEntry : null;
}
}
}
\ No newline at end of file
diff --git a/FileSystem/src/Adapters/IAdapter.cs b/FileSystem/src/Adapters/IAdapter.cs
index 4546220..1303719 100644
--- a/FileSystem/src/Adapters/IAdapter.cs
+++ b/FileSystem/src/Adapters/IAdapter.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using SharpGrip.FileSystem.Configuration;
using SharpGrip.FileSystem.Models;
namespace SharpGrip.FileSystem.Adapters
@@ -11,7 +12,9 @@ public interface IAdapter : IDisposable
{
string Prefix { get; }
string RootPath { get; }
+ public IAdapterConfiguration AdapterConfiguration { get; }
public void Connect();
+ public void ClearCache();
IFile GetFile(string virtualPath);
Task GetFileAsync(string virtualPath, CancellationToken cancellationToken = default);
IDirectory GetDirectory(string virtualPath);
diff --git a/FileSystem/src/Adapters/LocalAdapter.cs b/FileSystem/src/Adapters/LocalAdapter.cs
index fe7c555..a29ce51 100644
--- a/FileSystem/src/Adapters/LocalAdapter.cs
+++ b/FileSystem/src/Adapters/LocalAdapter.cs
@@ -11,9 +11,9 @@
namespace SharpGrip.FileSystem.Adapters
{
- public class LocalAdapter : Adapter
+ public class LocalAdapter : Adapter
{
- public LocalAdapter(string prefix, string rootPath) : base(prefix, rootPath)
+ public LocalAdapter(string prefix, string rootPath, Action? configuration = null) : base(prefix, rootPath, configuration)
{
}
diff --git a/FileSystem/src/Adapters/LocalAdapterConfiguration.cs b/FileSystem/src/Adapters/LocalAdapterConfiguration.cs
new file mode 100644
index 0000000..c1f1132
--- /dev/null
+++ b/FileSystem/src/Adapters/LocalAdapterConfiguration.cs
@@ -0,0 +1,8 @@
+using SharpGrip.FileSystem.Configuration;
+
+namespace SharpGrip.FileSystem.Adapters
+{
+ public class LocalAdapterConfiguration : AdapterConfiguration
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Cache/CacheEntry.cs b/FileSystem/src/Cache/CacheEntry.cs
new file mode 100644
index 0000000..09b4353
--- /dev/null
+++ b/FileSystem/src/Cache/CacheEntry.cs
@@ -0,0 +1,14 @@
+namespace SharpGrip.FileSystem.Cache
+{
+ public class CacheEntry : ICacheEntry
+ {
+ public TKey Key { get; set; }
+ public TValue Value { get; set; }
+
+ public CacheEntry(TKey key, TValue value)
+ {
+ Key = key;
+ Value = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Cache/ICacheEntry.cs b/FileSystem/src/Cache/ICacheEntry.cs
new file mode 100644
index 0000000..e750156
--- /dev/null
+++ b/FileSystem/src/Cache/ICacheEntry.cs
@@ -0,0 +1,6 @@
+namespace SharpGrip.FileSystem.Cache
+{
+ public interface ICacheEntry
+ {
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Configuration/AdapterConfiguration.cs b/FileSystem/src/Configuration/AdapterConfiguration.cs
new file mode 100644
index 0000000..0a8fb39
--- /dev/null
+++ b/FileSystem/src/Configuration/AdapterConfiguration.cs
@@ -0,0 +1,7 @@
+namespace SharpGrip.FileSystem.Configuration
+{
+ public abstract class AdapterConfiguration : IAdapterConfiguration
+ {
+ public bool EnableCache { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Configuration/IAdapterConfiguration.cs b/FileSystem/src/Configuration/IAdapterConfiguration.cs
new file mode 100644
index 0000000..a348fa2
--- /dev/null
+++ b/FileSystem/src/Configuration/IAdapterConfiguration.cs
@@ -0,0 +1,7 @@
+namespace SharpGrip.FileSystem.Configuration
+{
+ public interface IAdapterConfiguration
+ {
+ public bool EnableCache { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Exceptions/AdapterNotFoundException.cs b/FileSystem/src/Exceptions/AdapterNotFoundException.cs
index eca5aa2..191b035 100644
--- a/FileSystem/src/Exceptions/AdapterNotFoundException.cs
+++ b/FileSystem/src/Exceptions/AdapterNotFoundException.cs
@@ -18,7 +18,7 @@ public AdapterNotFoundException(string prefix, IList adapters) : base(
private static string GetMessage(string prefix, IEnumerable adapters)
{
var adaptersString = string.Join("', '", adapters.Select(adapter => adapter.Prefix).ToArray());
-
+
return $"No adapter found with prefix '{prefix}'. Registered adapters are: '{adaptersString}'.";
}
}
diff --git a/FileSystem/src/FileSystem.cs b/FileSystem/src/FileSystem.cs
index 211d339..19ee188 100644
--- a/FileSystem/src/FileSystem.cs
+++ b/FileSystem/src/FileSystem.cs
@@ -113,6 +113,11 @@ public Task GetFileAsync(string virtualPath, CancellationToken cancellati
adapter.Connect();
+ if (adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return adapter.GetFileAsync(virtualPath, cancellationToken);
}
@@ -153,6 +158,11 @@ public Task GetDirectoryAsync(string virtualPath, CancellationToken
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return adapter.GetDirectoryAsync(virtualPath, cancellationToken);
}
@@ -193,6 +203,11 @@ public Task> GetFilesAsync(string virtualPath = "", Cancellat
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return adapter.GetFilesAsync(virtualPath, cancellationToken);
}
@@ -233,6 +248,11 @@ public Task> GetDirectoriesAsync(string virtualPath = ""
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return adapter.GetDirectoriesAsync(virtualPath, cancellationToken);
}
@@ -271,6 +291,11 @@ public async Task FileExistsAsync(string virtualPath, CancellationToken ca
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return await adapter.FileExistsAsync(virtualPath, cancellationToken);
}
@@ -309,6 +334,11 @@ public async Task DirectoryExistsAsync(string virtualPath, CancellationTok
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return await adapter.DirectoryExistsAsync(virtualPath, cancellationToken);
}
@@ -347,6 +377,11 @@ public async Task CreateDirectoryAsync(string virtualPath, CancellationToken can
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.CreateDirectoryAsync(virtualPath, cancellationToken);
}
@@ -385,6 +420,11 @@ public async Task DeleteFileAsync(string virtualPath, CancellationToken cancella
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.DeleteFileAsync(virtualPath, cancellationToken);
}
@@ -423,6 +463,11 @@ public async Task DeleteDirectoryAsync(string virtualPath, CancellationToken can
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.DeleteDirectoryAsync(virtualPath, cancellationToken);
}
@@ -446,6 +491,11 @@ public async Task ReadFileStreamAsync(string virtualPath, CancellationTo
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return await adapter.ReadFileStreamAsync(virtualPath, cancellationToken);
}
@@ -486,6 +536,11 @@ public async Task ReadFileAsync(string virtualPath, CancellationToken ca
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return await adapter.ReadFileAsync(virtualPath, cancellationToken);
}
@@ -526,6 +581,11 @@ public async Task ReadTextFileAsync(string virtualPath, CancellationToke
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
return await adapter.ReadTextFileAsync(virtualPath, cancellationToken);
}
@@ -572,8 +632,19 @@ public async Task CopyFileAsync(string virtualSourcePath, string virtualDestinat
var destinationAdapter = GetAdapter(destinationPrefix);
sourceAdapter.Connect();
+
+ if (!sourceAdapter.AdapterConfiguration.EnableCache)
+ {
+ sourceAdapter.ClearCache();
+ }
+
destinationAdapter.Connect();
+ if (!destinationAdapter.AdapterConfiguration.EnableCache)
+ {
+ destinationAdapter.ClearCache();
+ }
+
var contents = await sourceAdapter.ReadFileAsync(virtualSourcePath, cancellationToken);
await destinationAdapter.WriteFileAsync(virtualDestinationPath, contents, overwrite, cancellationToken);
}
@@ -621,8 +692,19 @@ public async Task MoveFileAsync(string virtualSourcePath, string virtualDestinat
var destinationAdapter = GetAdapter(destinationPrefix);
sourceAdapter.Connect();
+
+ if (!sourceAdapter.AdapterConfiguration.EnableCache)
+ {
+ sourceAdapter.ClearCache();
+ }
+
destinationAdapter.Connect();
+ if (!destinationAdapter.AdapterConfiguration.EnableCache)
+ {
+ destinationAdapter.ClearCache();
+ }
+
var contents = await sourceAdapter.ReadFileAsync(virtualSourcePath, cancellationToken);
await destinationAdapter.WriteFileAsync(virtualDestinationPath, contents, overwrite, cancellationToken);
await sourceAdapter.DeleteFileAsync(virtualSourcePath, cancellationToken);
@@ -649,6 +731,11 @@ public async Task WriteFileAsync(string virtualPath, Stream contents, bool overw
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.WriteFileAsync(virtualPath, contents, overwrite, cancellationToken);
}
@@ -691,6 +778,11 @@ public async Task WriteFileAsync(string virtualPath, byte[] contents, bool overw
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.WriteFileAsync(virtualPath, contents, overwrite, cancellationToken);
}
@@ -733,6 +825,11 @@ public async Task WriteFileAsync(string virtualPath, string contents, bool overw
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.WriteFileAsync(virtualPath, contents, overwrite, cancellationToken);
}
@@ -756,6 +853,11 @@ public async Task AppendFileAsync(string virtualPath, Stream contents, Cancellat
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.AppendFileAsync(virtualPath, contents, cancellationToken);
}
@@ -796,6 +898,11 @@ public async Task AppendFileAsync(string virtualPath, byte[] contents, Cancellat
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.AppendFileAsync(virtualPath, contents, cancellationToken);
}
@@ -836,6 +943,11 @@ public async Task AppendFileAsync(string virtualPath, string contents, Cancellat
adapter.Connect();
+ if (!adapter.AdapterConfiguration.EnableCache)
+ {
+ adapter.ClearCache();
+ }
+
await adapter.AppendFileAsync(virtualPath, contents, cancellationToken);
}
}
diff --git a/FileSystem/src/IFileSystem.cs b/FileSystem/src/IFileSystem.cs
index a875272..15a57d9 100644
--- a/FileSystem/src/IFileSystem.cs
+++ b/FileSystem/src/IFileSystem.cs
@@ -28,7 +28,7 @@ public interface IFileSystem
public IAdapter GetAdapter(string prefix);
///
- /// Return a file.
+ /// Returns a file.
///
/// The virtual path (including prefix) to the file.
/// The file.
@@ -43,7 +43,7 @@ public interface IFileSystem
public IFile GetFile(string virtualPath);
///
- /// Return a file.
+ /// Returns a file.
///
/// The virtual path (including prefix) to the file.
/// Optional to propagate notifications that the operation should be cancelled.
diff --git a/FileSystem/src/Models/Model.cs b/FileSystem/src/Models/Model.cs
index e32e261..be74c8f 100644
--- a/FileSystem/src/Models/Model.cs
+++ b/FileSystem/src/Models/Model.cs
@@ -7,7 +7,7 @@ public abstract class Model
public string Name { get; set; } = "";
public string Path { get; set; } = "";
public string VirtualPath { get; set; } = "";
- public DateTime? LastModifiedDateTime {get; set; }
- public DateTime? CreatedDateTime {get; set; }
+ public DateTime? LastModifiedDateTime { get; set; }
+ public DateTime? CreatedDateTime { get; set; }
}
}
\ No newline at end of file
diff --git a/FileSystem/src/Utilities/ContentTypeProvider.cs b/FileSystem/src/Utilities/ContentTypeProvider.cs
new file mode 100644
index 0000000..e94cc58
--- /dev/null
+++ b/FileSystem/src/Utilities/ContentTypeProvider.cs
@@ -0,0 +1,12 @@
+using MimeTypes;
+
+namespace SharpGrip.FileSystem.Utilities
+{
+ public static class ContentTypeProvider
+ {
+ public static string GetContentType(string path)
+ {
+ return MimeTypeMap.GetMimeType(path);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileSystem/src/Utilities/PathUtilities.cs b/FileSystem/src/Utilities/PathUtilities.cs
index b6cf6f3..e85673d 100644
--- a/FileSystem/src/Utilities/PathUtilities.cs
+++ b/FileSystem/src/Utilities/PathUtilities.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using SharpGrip.FileSystem.Exceptions;
using SharpGrip.FileSystem.Extensions;
@@ -8,6 +9,7 @@ public static class PathUtilities
{
private const string AdapterPrefixSeparator = "://";
private const string PathSeparator = "/";
+ private const string InvalidPathSeparator = "\\";
///
/// Returns the prefix from a prefixed path.
@@ -19,6 +21,21 @@ public static string GetPrefix(string virtualPath)
return ResolvePrefixAndPath(virtualPath)[0];
}
+ ///
+ /// Normalizes the adapter's root path.
+ ///
+ /// The adapter's root path.
+ /// The path.
+ public static string NormalizeRootPath(string rootPath)
+ {
+ if (string.IsNullOrWhiteSpace(rootPath) || rootPath == PathSeparator)
+ {
+ return PathSeparator;
+ }
+
+ return rootPath.Replace(InvalidPathSeparator, PathSeparator).RemoveTrailingForwardSlash();
+ }
+
///
/// Returns the path from a prefixed path.
///
@@ -27,12 +44,19 @@ public static string GetPrefix(string virtualPath)
/// The path.
public static string GetPath(string virtualPath, string rootPath)
{
- if (string.IsNullOrWhiteSpace(rootPath))
+ if (rootPath == PathSeparator)
{
- return string.Join(PathSeparator, ResolvePrefixAndPath(virtualPath)[1]);
+ rootPath = "";
}
- return string.Join(PathSeparator, rootPath, ResolvePrefixAndPath(virtualPath)[1]);
+ var prefixAndPath = ResolvePrefixAndPath(virtualPath);
+
+ if (prefixAndPath.Length == 1)
+ {
+ return string.Join(PathSeparator, rootPath);
+ }
+
+ return string.Join(PathSeparator, rootPath, prefixAndPath[1]);
}
///
@@ -49,19 +73,58 @@ public static string GetVirtualPath(string path, string prefix, string rootPath)
throw new InvalidPathException(path);
}
- path = path.Replace('\\', '/');
+ path = path.Replace(InvalidPathSeparator, PathSeparator);
if (!rootPath.IsNullOrEmpty() && rootPath != "/")
{
path = path.Replace(rootPath, "");
}
- if (path.StartsWith("/"))
+ path = path.RemoveLeadingForwardSlash();
+
+ return string.Join(AdapterPrefixSeparator, prefix, path);
+ }
+
+ ///
+ /// Returns the parent path part from a path.
+ ///
+ /// The prefixed path.
+ /// The parent path part.
+ public static string GetParentPathPart(string path)
+ {
+ if (path.IsNullOrEmpty())
{
- path = path.Substring(1);
+ path = "/";
}
- return string.Join(AdapterPrefixSeparator, prefix, path);
+ var pathParts = GetPathParts(path);
+
+ return string.Join("/", pathParts.Take(pathParts.Length - 1));
+ }
+
+ ///
+ /// Returns the last path part from a path.
+ ///
+ /// The path.
+ /// The last path part.
+ public static string GetLastPathPart(string path)
+ {
+ if (path.IsNullOrEmpty())
+ {
+ return "";
+ }
+
+ return GetPathParts(path).Last();
+ }
+
+ ///
+ /// Returns the path parts from a path.
+ ///
+ /// The path.
+ /// The path parts.
+ public static string[] GetPathParts(string path)
+ {
+ return path.Split(new[] {PathSeparator}, StringSplitOptions.RemoveEmptyEntries);
}
///
@@ -76,7 +139,7 @@ private static string[] ResolvePrefixAndPath(string path)
throw new PrefixNotFoundInPathException(path);
}
- return path.Split(new[] {AdapterPrefixSeparator}, StringSplitOptions.None);
+ return path.Split(new[] {AdapterPrefixSeparator}, StringSplitOptions.RemoveEmptyEntries);
}
}
}
\ No newline at end of file
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index 7264d21..e386134 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -1,9 +1,10 @@
- net6.0;net7.0
+ net6.0;net7.0;net8.0
latest
false
+ SharpGrip.FileSystem.Tests
@@ -38,8 +39,9 @@
+
-
+
\ No newline at end of file
diff --git a/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs b/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
index 4a37370..12fdf4d 100644
--- a/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AmazonS3/AmazonS3AdapterTest.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Net;
+using System.Text;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.S3;
@@ -16,7 +17,7 @@
using DirectoryNotFoundException = SharpGrip.FileSystem.Exceptions.DirectoryNotFoundException;
using FileNotFoundException = SharpGrip.FileSystem.Exceptions.FileNotFoundException;
-namespace Tests.FileSystem.Adapters.AmazonS3
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.AmazonS3
{
public class AmazonS3AdapterTest : IAdapterTests
{
@@ -428,7 +429,7 @@ public async Task Test_Read_File_Async()
var fileContents = await fileSystem.ReadFileAsync("prefix-1://test1.txt");
- Assert.Equal("test1", System.Text.Encoding.UTF8.GetString(fileContents));
+ Assert.Equal("test1", Encoding.UTF8.GetString(fileContents));
await Assert.ThrowsAsync(() => fileSystem.ReadFileAsync("prefix-1://test2.txt"));
await Assert.ThrowsAsync(() => fileSystem.ReadFileAsync("prefix-1://test3.txt"));
await Assert.ThrowsAsync(() => fileSystem.ReadFileAsync("prefix-1://test4.txt"));
diff --git a/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs b/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
index 0e57584..a09c3a0 100644
--- a/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AzureBlobStorage/AzureBlobStorageAdapterTest.cs
@@ -4,7 +4,7 @@
using SharpGrip.FileSystem.Adapters.AzureBlobStorage;
using Xunit;
-namespace Tests.FileSystem.Adapters.AzureBlobStorage
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.AzureBlobStorage
{
public class AzureBlobStorageAdapterTest
{
diff --git a/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs b/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
index c8ed7c4..43701b0 100644
--- a/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.AzureFileStorage/AzureFileStorageAdapterTest.cs
@@ -4,7 +4,7 @@
using SharpGrip.FileSystem.Adapters.AzureFileStorage;
using Xunit;
-namespace Tests.FileSystem.Adapters.AzureFileStorage
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.AzureFileStorage
{
public class AzureFileStorageAdapterTest
{
diff --git a/Tests/src/FileSystem.Adapters.Dropbox/DropboxAdapterTest.cs b/Tests/src/FileSystem.Adapters.Dropbox/DropboxAdapterTest.cs
index 1bdb4b7..fbe51e5 100644
--- a/Tests/src/FileSystem.Adapters.Dropbox/DropboxAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.Dropbox/DropboxAdapterTest.cs
@@ -1,4 +1,4 @@
-namespace Tests.FileSystem.Adapters.Dropbox
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.Dropbox
{
public class DropboxAdapterTest
{
diff --git a/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs b/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs
new file mode 100644
index 0000000..2601477
--- /dev/null
+++ b/Tests/src/FileSystem.Adapters.GoogleDrive/GoogleDriveAdapterTest.cs
@@ -0,0 +1,116 @@
+using System.Threading.Tasks;
+using Google.Apis.Drive.v3;
+using NSubstitute;
+using SharpGrip.FileSystem.Adapters.GoogleDrive;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.GoogleDrive
+{
+ public class GoogleDriveAdapterTest : IAdapterTests
+ {
+ [Fact]
+ public void Test_Instantiation()
+ {
+ var googleDriveClient = Substitute.For();
+ var googleDriveAdapter = new GoogleDriveAdapter("prefix", "/root-path", googleDriveClient);
+
+ Assert.Equal("prefix", googleDriveAdapter.Prefix);
+ Assert.Equal("/root-path", googleDriveAdapter.RootPath);
+ }
+
+ [Fact]
+ public Task Test_Connect()
+ {
+ var googleDriveClient = Substitute.For();
+ var googleDriveAdapter = new GoogleDriveAdapter("prefix", "/root-path", googleDriveClient);
+
+ googleDriveAdapter.Connect();
+
+ return Task.CompletedTask;
+ }
+
+ [Fact]
+ public Task Test_Get_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Get_Directory_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Get_Files_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Get_Directories_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_File_Exists_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Directory_Exists_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Create_Directory_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Delete_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Delete_Directory_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Read_File_Stream_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Read_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Read_Text_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Write_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+
+ [Fact]
+ public Task Test_Append_File_Async()
+ {
+ return Task.FromResult(Task.CompletedTask);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs b/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
index 451b67d..bc8dcc1 100644
--- a/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.MicrosoftOneDrive/MicrosoftOneDriveAdapterTest.cs
@@ -5,7 +5,7 @@
using SharpGrip.FileSystem.Adapters.MicrosoftOneDrive;
using Xunit;
-namespace Tests.FileSystem.Adapters.MicrosoftOneDrive
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.MicrosoftOneDrive
{
public class MicrosoftOneDriveAdapterTest
{
diff --git a/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs b/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
index ae30675..493386e 100644
--- a/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
+++ b/Tests/src/FileSystem.Adapters.Sftp/SftpAdapterTest.cs
@@ -4,7 +4,7 @@
using SharpGrip.FileSystem.Exceptions;
using Xunit;
-namespace Tests.FileSystem.Adapters.Sftp
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters.Sftp
{
public class SftpAdapterTest
{
diff --git a/Tests/src/FileSystem/LocalAdapterTest.cs b/Tests/src/FileSystem/Adapters/LocalAdapterTest.cs
similarity index 98%
rename from Tests/src/FileSystem/LocalAdapterTest.cs
rename to Tests/src/FileSystem/Adapters/LocalAdapterTest.cs
index 8b3dd23..e344bea 100644
--- a/Tests/src/FileSystem/LocalAdapterTest.cs
+++ b/Tests/src/FileSystem/Adapters/LocalAdapterTest.cs
@@ -3,7 +3,7 @@
using SharpGrip.FileSystem.Exceptions;
using Xunit;
-namespace Tests.FileSystem
+namespace SharpGrip.FileSystem.Tests.FileSystem.Adapters
{
public class LocalAdapterTest : IAdapterTests
{
diff --git a/Tests/src/FileSystem/Extensions/StringExtensionsTest.cs b/Tests/src/FileSystem/Extensions/StringExtensionsTest.cs
new file mode 100644
index 0000000..cd6df73
--- /dev/null
+++ b/Tests/src/FileSystem/Extensions/StringExtensionsTest.cs
@@ -0,0 +1,35 @@
+using SharpGrip.FileSystem.Extensions;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Extensions
+{
+ public class StringExtensionsTest
+ {
+ [Fact]
+ public void Test_IsNullOrEmpty()
+ {
+ Assert.True("".IsNullOrEmpty());
+ }
+
+ [Fact]
+ public void Test_RemoveLeadingForwardSlash()
+ {
+ Assert.Equal("test/", "/test/".RemoveLeadingForwardSlash());
+ Assert.Equal("test/", "test/".RemoveLeadingForwardSlash());
+ }
+
+ [Fact]
+ public void Test_RemoveTrailingForwardSlash()
+ {
+ Assert.Equal("/test", "/test/".RemoveTrailingForwardSlash());
+ Assert.Equal("/test", "/test".RemoveTrailingForwardSlash());
+ }
+
+ [Fact]
+ public void Test_EnsureTrailingForwardSlash()
+ {
+ Assert.Equal("/test/", "/test/".EnsureTrailingForwardSlash());
+ Assert.Equal("/test/", "/test".EnsureTrailingForwardSlash());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/FileSystem/FileSystemTest.cs b/Tests/src/FileSystem/FileSystemTest.cs
index 6e7fb12..4f14438 100644
--- a/Tests/src/FileSystem/FileSystemTest.cs
+++ b/Tests/src/FileSystem/FileSystemTest.cs
@@ -3,7 +3,7 @@
using SharpGrip.FileSystem.Exceptions;
using Xunit;
-namespace Tests.FileSystem
+namespace SharpGrip.FileSystem.Tests.FileSystem
{
public class FileSystemTest
{
diff --git a/Tests/src/FileSystem/Models/ModelFactoryTest.cs b/Tests/src/FileSystem/Models/ModelFactoryTest.cs
new file mode 100644
index 0000000..3b94f53
--- /dev/null
+++ b/Tests/src/FileSystem/Models/ModelFactoryTest.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using SharpGrip.FileSystem.Models;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Models
+{
+ public class ModelFactoryTest
+ {
+ [Fact]
+ public void Test_CreateFile()
+ {
+ var lastModifiedDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local);
+ var createdDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local);
+
+ var file = new FileInfo("../../../var/data/test.txt")
+ {
+ LastWriteTime = lastModifiedDateTime,
+ CreationTime = createdDateTime
+ };
+
+ var fileModel = ModelFactory.CreateFile(file, "test://test.txt");
+
+ Assert.Equal("test.txt", fileModel.Name);
+ Assert.Contains(@"\var\data\test.txt", fileModel.Path);
+ Assert.Equal("test://test.txt", fileModel.VirtualPath);
+ Assert.Equal(lastModifiedDateTime, fileModel.LastModifiedDateTime);
+ Assert.Equal(createdDateTime, fileModel.CreatedDateTime);
+ Assert.Equal(0, fileModel.Length);
+ }
+
+ [Fact]
+ public void Test_CreateDirectory()
+ {
+ var lastModifiedDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local);
+ var createdDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local);
+
+ var directory = new DirectoryInfo("../../../var/data")
+ {
+ LastWriteTime = lastModifiedDateTime,
+ CreationTime = createdDateTime
+ };
+
+ var directoryModel = ModelFactory.CreateDirectory(directory, "test://data");
+
+ Assert.Equal("data", directoryModel.Name);
+ Assert.Contains(@"\var\data", directoryModel.Path);
+ Assert.Equal("test://data", directoryModel.VirtualPath);
+ Assert.Equal(lastModifiedDateTime, directoryModel.LastModifiedDateTime);
+ Assert.Equal(createdDateTime, directoryModel.CreatedDateTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/FileSystem/Utilities/ContentTypeProviderTest.cs b/Tests/src/FileSystem/Utilities/ContentTypeProviderTest.cs
new file mode 100644
index 0000000..a125789
--- /dev/null
+++ b/Tests/src/FileSystem/Utilities/ContentTypeProviderTest.cs
@@ -0,0 +1,18 @@
+using SharpGrip.FileSystem.Utilities;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Utilities
+{
+ public class ContentTypeProviderTest
+ {
+ [Fact]
+ public void Test_GetContentType()
+ {
+ Assert.Equal("text/plain", ContentTypeProvider.GetContentType("text.txt"));
+ Assert.Equal("application/pdf", ContentTypeProvider.GetContentType("text.pdf"));
+ Assert.Equal("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ContentTypeProvider.GetContentType("text.docx"));
+ Assert.Equal("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ContentTypeProvider.GetContentType("text.xlsx"));
+ Assert.Equal("application/vnd.openxmlformats-officedocument.presentationml.presentation", ContentTypeProvider.GetContentType("text.pptx"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/FileSystem/Utilities/PathUtilitiesTest.cs b/Tests/src/FileSystem/Utilities/PathUtilitiesTest.cs
new file mode 100644
index 0000000..18f5756
--- /dev/null
+++ b/Tests/src/FileSystem/Utilities/PathUtilitiesTest.cs
@@ -0,0 +1,69 @@
+using SharpGrip.FileSystem.Exceptions;
+using SharpGrip.FileSystem.Utilities;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Utilities
+{
+ public class PathUtilitiesTest
+ {
+ [Fact]
+ public void Test_GetPrefix()
+ {
+ Assert.Equal("prefix", PathUtilities.GetPrefix("prefix://test.txt"));
+ Assert.Throws(() => PathUtilities.GetPrefix("test.txt"));
+ }
+
+ [Fact]
+ public void Test_NormalizeRootPath()
+ {
+ Assert.Equal("/", PathUtilities.NormalizeRootPath(""));
+ Assert.Equal("/", PathUtilities.NormalizeRootPath("/"));
+ Assert.Equal("/test1/test2/test3", PathUtilities.NormalizeRootPath(@"\test1\test2\test3\"));
+ Assert.Equal("/test1/test2/test3", PathUtilities.NormalizeRootPath(@"\test1\test2\test3"));
+ }
+
+ [Fact]
+ public void Test_GetPath()
+ {
+ Assert.Equal("/root-path/virtual-path", PathUtilities.GetPath("prefix://virtual-path", "/root-path"));
+ Assert.Equal("/root-path", PathUtilities.GetPath("prefix://", "/root-path"));
+ Assert.Equal("", PathUtilities.GetPath("prefix://", ""));
+ }
+
+ [Fact]
+ public void Test_GetVirtualPath()
+ {
+ Assert.Equal("prefix://test.txt", PathUtilities.GetVirtualPath("root-path/test.txt", "prefix", "root-path"));
+ Assert.Equal("prefix://root-path/test.txt", PathUtilities.GetVirtualPath("root-path/test.txt", "prefix", "/"));
+ Assert.Equal("prefix://root-path/test.txt", PathUtilities.GetVirtualPath("root-path/test.txt", "prefix", ""));
+ Assert.Throws(() => PathUtilities.GetVirtualPath("test://test.txt", "prefix", "root-path"));
+ }
+
+ [Fact]
+ public void Test_GetParentPathPart()
+ {
+ Assert.Equal("", PathUtilities.GetParentPathPart(""));
+ Assert.Equal("", PathUtilities.GetParentPathPart("/test-1"));
+ Assert.Equal("test-1", PathUtilities.GetParentPathPart("/test-1/test-2"));
+ Assert.Equal("test-1/test-2", PathUtilities.GetParentPathPart("/test-1/test-2/test-3"));
+ }
+
+ [Fact]
+ public void Test_GetLastPathPart()
+ {
+ Assert.Equal("", PathUtilities.GetLastPathPart(""));
+ Assert.Equal("test-1", PathUtilities.GetLastPathPart("/test-1"));
+ Assert.Equal("test-2", PathUtilities.GetLastPathPart("/test-1/test-2"));
+ Assert.Equal("test-3", PathUtilities.GetLastPathPart("/test-1/test-2/test-3"));
+ }
+
+ [Fact]
+ public void Test_GetPathParts()
+ {
+ Assert.Equal(new string[] { }, PathUtilities.GetPathParts(""));
+ Assert.Equal(new[] {"test-1"}, PathUtilities.GetPathParts("/test-1"));
+ Assert.Equal(new[] {"test-1", "test-2"}, PathUtilities.GetPathParts("/test-1/test-2"));
+ Assert.Equal(new[] {"test-1", "test-2", "test-3"}, PathUtilities.GetPathParts("/test-1/test-2/test-3"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/FileSystem/Utilities/StreamUtilitiesTest.cs b/Tests/src/FileSystem/Utilities/StreamUtilitiesTest.cs
new file mode 100644
index 0000000..f88ef4f
--- /dev/null
+++ b/Tests/src/FileSystem/Utilities/StreamUtilitiesTest.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using System.Threading.Tasks;
+using SharpGrip.FileSystem.Utilities;
+using Xunit;
+
+namespace SharpGrip.FileSystem.Tests.FileSystem.Utilities
+{
+ public class StreamUtilitiesTest
+ {
+ [Fact]
+ public async Task Test_CopyContentsToMemoryStreamAsync()
+ {
+ var memoryStream1 = new MemoryStream("test"u8.ToArray());
+ var memoryStream2 = await StreamUtilities.CopyContentsToMemoryStreamAsync(memoryStream1);
+
+ var memoryStreamContents1 = await new StreamReader(memoryStream1).ReadToEndAsync();
+ var memoryStreamContents2 = await new StreamReader(memoryStream2).ReadToEndAsync();
+
+ Assert.Equal(memoryStreamContents1, memoryStreamContents2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/src/IAdapterTests.cs b/Tests/src/IAdapterTests.cs
index 3704d4e..7e19179 100644
--- a/Tests/src/IAdapterTests.cs
+++ b/Tests/src/IAdapterTests.cs
@@ -1,6 +1,6 @@
using System.Threading.Tasks;
-namespace Tests;
+namespace SharpGrip.FileSystem.Tests;
public interface IAdapterTests
{
diff --git a/Tests/var/data/test.txt b/Tests/var/data/test.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/publish.ps1 b/scripts/publish.ps1
deleted file mode 100644
index bcf7989..0000000
--- a/scripts/publish.ps1
+++ /dev/null
@@ -1,5 +0,0 @@
-$outputPath = ".nuget"
-
-New-Item -ItemType Directory -Force $outputPath
-
-dotnet pack -o $outputPath
\ No newline at end of file