using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using FubarDev.FtpServer.FileSystem; namespace ShadowEditor.Server.Remote.FileSystem { /// /// A implementation that uses the /// standard .NET functionality to access the file system. /// public class RemoteFileSystem : IUnixFileSystem { private bool _disposedValue; /// /// Initializes a new instance of the class. /// /// The path to use as root /// Allow deletion of non-empty directories? public RemoteFileSystem(string rootPath, bool allowNonEmptyDirectoryDelete) { FileSystemEntryComparer = StringComparer.OrdinalIgnoreCase; Root = new RemoteDirectoryEntry(this, Directory.CreateDirectory(rootPath), true); SupportsNonEmptyDirectoryDelete = allowNonEmptyDirectoryDelete; } /// public bool SupportsNonEmptyDirectoryDelete { get; } /// public StringComparer FileSystemEntryComparer { get; } /// public IUnixDirectoryEntry Root { get; } /// public bool SupportsAppend => true; /// public Task> GetEntriesAsync(IUnixDirectoryEntry directoryEntry, CancellationToken cancellationToken) { var result = new List(); var searchDirInfo = ((RemoteDirectoryEntry)directoryEntry).Info; foreach (var info in searchDirInfo.EnumerateFileSystemInfos()) { var dirInfo = info as DirectoryInfo; if (dirInfo != null) { result.Add(new RemoteDirectoryEntry(this, dirInfo, false)); } else { var fileInfo = info as FileInfo; if (fileInfo != null) { result.Add(new RemoteFileEntry(this, fileInfo)); } } } return Task.FromResult>(result); } /// public Task GetEntryByNameAsync(IUnixDirectoryEntry directoryEntry, string name, CancellationToken cancellationToken) { var searchDirInfo = ((RemoteDirectoryEntry)directoryEntry).Info; var fullPath = Path.Combine(searchDirInfo.FullName, name); IUnixFileSystemEntry result; if (File.Exists(fullPath)) result = new RemoteFileEntry(this, new FileInfo(fullPath)); else if (Directory.Exists(fullPath)) result = new RemoteDirectoryEntry(this, new DirectoryInfo(fullPath), false); else result = null; return Task.FromResult(result); } /// public Task MoveAsync(IUnixDirectoryEntry parent, IUnixFileSystemEntry source, IUnixDirectoryEntry target, string fileName, CancellationToken cancellationToken) { var targetEntry = (RemoteDirectoryEntry)target; var targetName = Path.Combine(targetEntry.Info.FullName, fileName); var sourceFileEntry = source as RemoteFileEntry; if (sourceFileEntry != null) { sourceFileEntry.Info.MoveTo(targetName); return Task.FromResult(new RemoteFileEntry(this, new FileInfo(targetName))); } var sourceDirEntry = (RemoteDirectoryEntry)source; sourceDirEntry.Info.MoveTo(targetName); return Task.FromResult(new RemoteDirectoryEntry(this, new DirectoryInfo(targetName), false)); } /// public Task UnlinkAsync(IUnixFileSystemEntry entry, CancellationToken cancellationToken) { var dirEntry = entry as RemoteDirectoryEntry; if (dirEntry != null) { dirEntry.Info.Delete(SupportsNonEmptyDirectoryDelete); } else { var fileEntry = (RemoteFileEntry)entry; fileEntry.Info.Delete(); } return Task.FromResult(0); } /// public Task CreateDirectoryAsync(IUnixDirectoryEntry targetDirectory, string directoryName, CancellationToken cancellationToken) { var targetEntry = (RemoteDirectoryEntry)targetDirectory; var newDirInfo = targetEntry.Info.CreateSubdirectory(directoryName); return Task.FromResult(new RemoteDirectoryEntry(this, newDirInfo, false)); } /// public Task OpenReadAsync(IUnixFileEntry fileEntry, long startPosition, CancellationToken cancellationToken) { var fileInfo = ((RemoteFileEntry)fileEntry).Info; var input = fileInfo.OpenRead(); if (startPosition != 0) input.Seek(startPosition, SeekOrigin.Begin); return Task.FromResult(input); } /// public async Task AppendAsync(IUnixFileEntry fileEntry, long? startPosition, Stream data, CancellationToken cancellationToken) { var fileInfo = ((RemoteFileEntry)fileEntry).Info; using (var output = fileInfo.OpenWrite()) { if (startPosition == null) startPosition = fileInfo.Length; output.Seek(startPosition.Value, SeekOrigin.Begin); await data.CopyToAsync(output, 4096, cancellationToken); } return null; } /// public async Task CreateAsync(IUnixDirectoryEntry targetDirectory, string fileName, Stream data, CancellationToken cancellationToken) { var targetEntry = (RemoteDirectoryEntry)targetDirectory; var fileInfo = new FileInfo(Path.Combine(targetEntry.Info.FullName, fileName)); using (var output = fileInfo.Create()) { await data.CopyToAsync(output, 4096, cancellationToken); } return null; } /// public async Task ReplaceAsync(IUnixFileEntry fileEntry, Stream data, CancellationToken cancellationToken) { var fileInfo = ((RemoteFileEntry)fileEntry).Info; using (var output = fileInfo.OpenWrite()) { await data.CopyToAsync(output, 4096, cancellationToken); output.SetLength(output.Position); } return null; } /// /// Sets the modify/access/create timestamp of a file system item /// /// The to change the timestamp for /// The modification timestamp /// The access timestamp /// The creation timestamp /// The cancellation token /// The modified public Task SetMacTimeAsync(IUnixFileSystemEntry entry, DateTimeOffset? modify, DateTimeOffset? access, DateTimeOffset? create, CancellationToken cancellationToken) { var dirEntry = entry as RemoteDirectoryEntry; var fileEntry = entry as RemoteFileEntry; var item = dirEntry == null ? (FileSystemInfo)fileEntry.Info : dirEntry.Info; if (access != null) item.LastAccessTimeUtc = access.Value.UtcDateTime; if (modify != null) item.LastWriteTimeUtc = modify.Value.UtcDateTime; if (create != null) item.CreationTimeUtc = create.Value.UtcDateTime; if (dirEntry != null) return Task.FromResult(new RemoteDirectoryEntry(this, (DirectoryInfo)item, dirEntry.IsRoot)); return Task.FromResult(new RemoteFileEntry(this, (FileInfo)item)); } /// public void Dispose() { Dispose(true); } /// /// Dispose the object /// /// true when called from protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { // Nothing to dispose } _disposedValue = true; } } } }