using Microsoft.Extensions.FileProviders.Physical;
using System.IO;
namespace Finch.FileStorage;
public class PhysicalFileSystem : IFileSystem
{
readonly string _root;
public PhysicalFileSystem(string root)
{
_root = root;
}
///
public virtual string Map(string path)
{
return ResolvePath(path);
}
///
public virtual string MapToPublicPath(string path)
{
return path.EnsureStartsWith('/');
}
///
public virtual Task Exists(string path, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
return Task.FromResult(File.Exists(resolvedPath) || Directory.Exists(resolvedPath));
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not get file exists info for path '{path}'", ex);
}
}
///
public virtual Task GetFileInfo(string path, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
PhysicalFileInfo fileInfo = new(new FileInfo(resolvedPath));
if (!fileInfo.Exists)
{
return Task.FromResult(default);
}
return Task.FromResult(new PhysicalFileMeta(fileInfo, path));
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not get file info for path '{path}'", ex);
}
}
///
public virtual Task StreamFile(string path, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
if (resolvedPath.IsNullOrEmpty() || !File.Exists(resolvedPath))
{
throw new FileSystemException($"The path '{path}' does not exist in the file system");
}
return Task.FromResult(File.OpenRead(resolvedPath));
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not get file stream for path '{path}'", ex);
}
}
///
public virtual async Task CreateFile(string path, Stream fileStream, bool overwrite = false, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
if ((!overwrite && File.Exists(resolvedPath)) || Directory.Exists(resolvedPath))
{
throw new FileSystemException($"A file/directory at the path '{path}' already existsin the file system");
}
string directoryPath = Path.GetDirectoryName(resolvedPath);
Directory.CreateDirectory(directoryPath);
FileInfo fileInfo = new(resolvedPath);
using var outputStream = fileInfo.Create();
await fileStream.CopyToAsync(outputStream, cancellationToken);
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not get file stream for path '{path}'", ex);
}
}
///
public virtual Task DeleteFile(string path, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
if (!File.Exists(resolvedPath))
{
return Task.CompletedTask;
}
File.Delete(resolvedPath);
return Task.CompletedTask;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not delete file for path '{path}'", ex);
}
}
///
public virtual Task MoveFile(string oldPath, string newPath, CancellationToken cancellationToken = default)
{
try
{
string resolvedOldPath = ResolvePath(oldPath);
string resolvedNewPath = ResolvePath(newPath);
if (!File.Exists(resolvedOldPath))
{
throw new FileSystemException($"The path '{oldPath}' does not exist in the file system");
}
if (File.Exists(resolvedNewPath) || Directory.Exists(resolvedNewPath))
{
throw new FileSystemException($"A file/directory at the path '{newPath}' already exists in the file system");
}
File.Move(resolvedOldPath, resolvedNewPath);
return Task.CompletedTask;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not move file from path '{oldPath}' to '{newPath}'", ex);
}
}
///
public virtual Task CopyFile(string oldPath, string newPath, CancellationToken cancellationToken = default)
{
try
{
string resolvedOldPath = ResolvePath(oldPath);
string resolvedNewPath = ResolvePath(newPath);
if (!File.Exists(resolvedOldPath))
{
throw new FileSystemException($"The path '{oldPath}' does not exist in the file system");
}
if (File.Exists(resolvedNewPath) || Directory.Exists(resolvedNewPath))
{
throw new FileSystemException($"A file/directory at the path '{newPath}' already exists in the file system");
}
File.Copy(resolvedOldPath, resolvedNewPath);
return Task.CompletedTask;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not copy file from path '{oldPath}' to '{newPath}'", ex);
}
}
///
public virtual Task GetDirectoryInfo(string path, CancellationToken cancellationToken = default) => GetFileInfo(path, cancellationToken);
///
public virtual IEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
List results = [];
SearchOption searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if (!Directory.Exists(resolvedPath))
{
return results;
}
results.AddRange(Directory.GetDirectories(resolvedPath, "*", searchOption).Select(f =>
{
PhysicalDirectoryInfo fileSystemInfo = new(new DirectoryInfo(f));
return new PhysicalFileMeta(fileSystemInfo, ResolvePath(f.Substring(_root.Length)));
}));
results.AddRange(Directory.GetFiles(resolvedPath, "*", searchOption).Select(f =>
{
PhysicalFileInfo fileSystemInfo = new(new FileInfo(f));
return new PhysicalFileMeta(fileSystemInfo, ResolvePath(f.Substring(_root.Length)));
}));
return results;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not get directory content for path '{path}'", ex);
}
}
///
public virtual Task CreateDirectory(string path, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
if (File.Exists(resolvedPath))
{
throw new FileSystemException($"A file at the path '{path}' already exists in the file system");
}
if (!Directory.Exists(resolvedPath))
{
Directory.CreateDirectory(resolvedPath);
}
return Task.CompletedTask;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not create directory at path '{path}'", ex);
}
}
///
public virtual Task DeleteDirectory(string path, bool recursive = false, CancellationToken cancellationToken = default)
{
try
{
string resolvedPath = ResolvePath(path);
if (!Directory.Exists(resolvedPath))
{
return Task.CompletedTask;
}
Directory.Delete(resolvedPath, recursive);
return Task.CompletedTask;
}
catch (Exception ex) when (ex is not FileSystemException)
{
throw new FileSystemException($"Could not delete directory at path '{path}'", ex);
}
}
///
/// Creates a fully-usable absolte path from the given path
///
protected virtual string ResolvePath(string path)
{
try
{
if (path.IsNullOrWhiteSpace())
{
return _root;
}
// normalize path
path = path.Replace('\\', '/').FullTrim().Trim('/');
string physicalPath = Path.Combine(_root, path);
string rootPath = Path.GetFullPath(_root);
// do not allow paths which are outside this file system
// the boundaries are set be the basePath which is assigned in the file system constructor
if (!Path.GetFullPath(physicalPath).StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase))
{
throw new FileSystemException($"The path '{path}' is outside the file system");
}
return physicalPath;
}
catch (FileSystemException)
{
throw;
}
catch (Exception ex)
{
throw new FileSystemException($"Could not resolve path '{path}'", ex);
}
}
}