/* Tesses.VirtualFilesystem a library for virtual filesystems in .NET Copyright (C) 2023 Mike Nolan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Tesses.VirtualFilesystem; using Tesses.VirtualFilesystem.Extensions; using System.Linq; namespace Tesses.VirtualFilesystem { public class SubdirFilesystem : IVirtualFilesystem { IVirtualFilesystem _fs; UnixPath _path; public SubdirFilesystem(IVirtualFilesystem fs,UnixPath path) { this._fs=fs; this._path=path; } public bool CanHandleSymlinks(UnixPath path) { return _fs.CanHandleSymlinks(_path / path); } public bool CanWatch(UnixPath path) { return _fs.CanWatch(_path / path); } private UnixPath ChopOff(UnixPath path) { if(path.Parts.Length > _path.Parts.Length) { return new UnixPath(path.Parts.Skip(_path.Parts.Length)); } return new UnixPath(); } public string ConvertPathFromUnixPath(UnixPath path) { return _fs.ConvertPathFromUnixPath(path); } public UnixPath ConvertPathToUnixPath(string path) { return _fs.ConvertPathToUnixPath(path); } public void CreateDirectory(UnixPath directory) { _fs.CreateDirectory(_path / directory); } public async Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default) { await _fs.CreateDirectoryAsync(_path / directory,token); } public void CreateHardlink(UnixPath src, UnixPath dest) { _fs.CreateHardlink(_path / src,_path / dest); } public async Task CreateHardlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { await _fs.CreateHardlinkAsync(_path / src,_path /dest,token); } public void CreateSymlink(UnixPath src, UnixPath dest) { _fs.CreateSymlink(_path / src,_path / dest); } public async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { await _fs.CreateSymlinkAsync(_path / src,_path / dest,token); } public void DeleteDirectory(UnixPath path) { _fs.DeleteDirectory(_path / path); } public async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default) { await _fs.DeleteDirectoryAsync(_path / path,token); } public void DeleteFile(UnixPath path) { _fs.DeleteFile(_path / path); } public async Task DeleteFileAsync(UnixPath path, CancellationToken token = default) { await _fs.DeleteFileAsync(_path / path,token); } public bool DirectoryExists(UnixPath path) { return _fs.DirectoryExists(_path / path); } public async Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default) { return await _fs.DirectoryExistsAsync(_path / path,token); } public void Dispose() { } public IEnumerable EnumerateDirectories(UnixPath path) { foreach(var dir in _fs.EnumerateDirectories(_path / path)) { yield return ChopOff(dir); } } public async IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var dir in _fs.EnumerateDirectoriesAsync(_path / path,token)) { if(token.IsCancellationRequested) yield break; yield return ChopOff(dir); } } public IEnumerable EnumerateFiles(UnixPath path) { foreach(var file in _fs.EnumerateFiles(_path / path)) { yield return ChopOff(file); } } public async IAsyncEnumerable EnumerateFilesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { await foreach(var file in _fs.EnumerateFilesAsync(_path / path,token)) { if(token.IsCancellationRequested) yield break; yield return ChopOff(file); } } public bool FileExists(UnixPath path) { return _fs.FileExists(_path / path); } public async Task FileExistsAsync(UnixPath path, CancellationToken token = default) { return await _fs.FileExistsAsync(_path / path,token); } public FileAttributes GetAttributes(UnixPath path) { return _fs.GetAttributes(_path / path); } public async Task GetAttributesAsync(UnixPath path, CancellationToken token = default) { return await _fs.GetAttributesAsync(_path / path,token); } public DateTime GetCreationTime(UnixPath path) { return _fs.GetCreationTime(_path / path); } public async Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default) { return await _fs.GetCreationTimeAsync(_path / path,token); } public DateTime GetLastAccessTime(UnixPath path) { return _fs.GetLastAccessTime(_path / path); } public async Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default) { return await _fs.GetLastAccessTimeAsync(_path / path,token); } public DateTime GetLastWriteTime(UnixPath path) { return _fs.GetLastWriteTime(_path / path); } public async Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default) { return await _fs.GetLastWriteTimeAsync(_path / path,token); } public IVirtualFilesystem GetSubdirFilesystem(UnixPath path) { return new SubdirFilesystem(this,path); } public void MoveDirectory(UnixPath src, UnixPath dest) { _fs.MoveDirectory(_path / src,_path / dest); } public async Task MoveDirectoryAsync(UnixPath src, UnixPath dest) { await _fs.MoveDirectoryAsync(_path / src, _path / dest); } public void MoveFile(UnixPath src, UnixPath dest) { _fs.MoveFile(_path / src,_path / dest); } public async Task MoveFileAsync(UnixPath src, UnixPath dest) { await _fs.MoveFileAsync(_path / src, _path / dest); } public Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share) { return _fs.Open(_path / path,mode,access,share); } public async Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default) { return await _fs.OpenAsync(_path / path,mode,access,share,token); } public DirectoryPointer OpenDirectory() { return new DirectoryPointer(this); } public DirectoryPointer OpenDirectory(UnixPath path) { return new DirectoryPointer(this,path); } public UnixPath ReadLink(UnixPath file) { return _fs.ReadLink(_path / file); } public async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { return await _fs.ReadLinkAsync(_path / file,token); } public bool SameFileSystem(UnixPath src, UnixPath dest) { return _fs.SameFileSystem(_path / src,_path / dest); } public async Task SameFileSystemAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { return await _fs.SameFileSystemAsync(_path / src, _path / dest,token); } public void SetAttributes(UnixPath path, FileAttributes attributes) { _fs.SetAttributes(_path / path,attributes); } public async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { await _fs.SetAttributesAsync(_path / path,attributes,token); } public void SetCreationTime(UnixPath path, DateTime time) { _fs.SetCreationTime(_path / path,time); } public async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await _fs.SetCreationTimeAsync(_path / path,time,token); } public void SetLastAccessTime(UnixPath path, DateTime time) { _fs.SetLastAccessTime(_path / path,time); } public async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await _fs.SetLastAccessTimeAsync(_path / path,time,token); } public void SetLastWriteTime(UnixPath path, DateTime time) { _fs.SetLastWriteTime(_path / path,time); } public async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await _fs.SetLastWriteTimeAsync(_path / path,time,token); } public bool SymlinkExists(UnixPath file) { return _fs.SymlinkExists(_path / file); } public async Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { return await _fs.SymlinkExistsAsync(_path / file,token); } public UnixPath UpPath(UnixPath path) { return path.Parent; } public IVirtualWatcher WatchDirectory(UnixPath dir) { return _fs.WatchDirectory(_path / dir); } public EntryPointer OpenEntry(UnixPath path) { return _fs.OpenEntry(_path / path); } public FilePointer OpenFile(UnixPath path) { return _fs.OpenFile(path); } public SymlinkPointer OpenSymlink(UnixPath path) { return _fs.OpenSymlink(path); } public IEnumerable EnumerateFileSystemEntries(UnixPath path) { foreach(var item in _fs.EnumerateFileSystemEntries(_path/path)) { yield return ChopOff(item); } } public IEnumerable EnumerateSymlinks(UnixPath path) { foreach(var item in _fs.EnumerateSymlinks(_path/path)) { yield return ChopOff(item); } } public async IAsyncEnumerable EnumerateSymlinksAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in _fs.EnumerateSymlinksAsync(_path/path)) { if(token.IsCancellationRequested) yield break; yield return await Task.FromResult(ChopOff(item)); } } public async IAsyncEnumerable EnumerateFileSystemEntriesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in _fs.EnumerateFileSystemEntriesAsync(_path/path)) { if(token.IsCancellationRequested) yield break; yield return await Task.FromResult(ChopOff(item)); } } public void DeleteDirectory(UnixPath path, bool recursive) { _fs.DeleteDirectory(_path/path,recursive); } public async Task DeleteDirectoryAsync(UnixPath path, bool recursive, CancellationToken token = default) { await _fs.DeleteDirectoryAsync(_path/path,token); } } public abstract class SyncFileSystem : IVirtualFilesystem { public virtual void Dispose() { } public virtual UnixPath UpPath(UnixPath path) { return path.Parent; } public virtual UnixPath ConvertPathToUnixPath(string path) { return path; } public virtual string ConvertPathFromUnixPath(UnixPath path) { return path.ToString(); } public virtual bool CanWatch(UnixPath path) { return false; } public virtual bool CanHandleSymlinks(UnixPath path) { return false; } public abstract void CreateDirectory(UnixPath directory); public virtual async Task CreateDirectoryAsync(UnixPath directory,CancellationToken token=default) { if(token.IsCancellationRequested) return; await Task.Run(()=>CreateDirectory(directory)); } public virtual void CreateHardlink(UnixPath src, UnixPath dest) { this.CopyFile(src,dest); } public virtual async Task CreateHardlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>CreateHardlink(src,dest)); } public virtual void CreateSymlink(UnixPath src, UnixPath dest) { } public virtual async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>CreateHardlink(src,dest)); } public abstract void DeleteDirectory(UnixPath path); public virtual async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>DeleteDirectory(path)); } public abstract void DeleteFile(UnixPath path); public virtual async Task DeleteFileAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>DeleteFile(path)); } public abstract bool DirectoryExists(UnixPath path); public async Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return false; return await Task.Run(()=>DirectoryExists(path)); } public IEnumerable EnumerateDirectories(UnixPath path) { foreach(var item in EnumerateFileSystemEntries(path)) { if(DirectoryExists(item) && !SymlinkExists(item)) { yield return item; } } } public async IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path)) { if(token.IsCancellationRequested) yield break; if(await DirectoryExistsAsync(item) && !await SymlinkExistsAsync(item)) yield return await Task.FromResult(item); } } public IEnumerable EnumerateFiles(UnixPath path) { foreach(var item in EnumerateFileSystemEntries(path)) { if(FileExists(item) && !SymlinkExists(item)) { yield return item; } } } public async IAsyncEnumerable EnumerateFilesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path)) { if(token.IsCancellationRequested) yield break; if(await FileExistsAsync(item) && !await SymlinkExistsAsync(item)) yield return await Task.FromResult(item); } } public abstract bool FileExists(UnixPath path); public virtual async Task FileExistsAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return false; return await Task.Run(()=>FileExists(path)); } public virtual FileAttributes GetAttributes(UnixPath path) { return FileAttributes.Normal; } public virtual async Task GetAttributesAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return FileAttributes.Normal; return await Task.Run(()=>GetAttributes(path)); } public abstract DateTime GetCreationTime(UnixPath path); public virtual async Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return DateTime.Now; return await Task.Run(()=>GetCreationTime(path)); } public abstract DateTime GetLastAccessTime(UnixPath path); public virtual async Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return DateTime.Now; return await Task.Run(()=>GetLastAccessTime(path)); } public abstract DateTime GetLastWriteTime(UnixPath path); public virtual async Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default) { if(token.IsCancellationRequested) return DateTime.Now; return await Task.Run(()=>GetLastWriteTime(path)); } public abstract void MoveDirectory(UnixPath src, UnixPath dest); public virtual async Task MoveDirectoryAsync(UnixPath src, UnixPath dest) { await Task.Run(()=>MoveDirectory(src,dest)); } public abstract void MoveFile(UnixPath src, UnixPath dest); public virtual async Task MoveFileAsync(UnixPath src, UnixPath dest) { await Task.Run(()=>MoveFile(src,dest)); } public abstract Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share); public virtual async Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default) { if(token.IsCancellationRequested) return Stream.Null; return await Task.Run(()=>Open(path,mode,access,share)); } public virtual UnixPath ReadLink(UnixPath file) { return file; } public virtual async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { if(token.IsCancellationRequested) return file; return await Task.Run(()=>ReadLink(file)); } public virtual async Task SameFileSystemAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(token.IsCancellationRequested) return false; return await Task.Run(()=>SameFileSystem(src,dest)); } public virtual bool SameFileSystem(UnixPath src, UnixPath dest) { return false; } public virtual void SetAttributes(UnixPath path, FileAttributes attributes) { } public virtual async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>SetAttributes(path,attributes)); } public abstract void SetCreationTime(UnixPath path, DateTime time); public virtual async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>SetCreationTime(path,time)); } public abstract void SetLastAccessTime(UnixPath path, DateTime time); public virtual async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>SetLastAccessTime(path,time)); } public abstract void SetLastWriteTime(UnixPath path, DateTime time); public virtual async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(token.IsCancellationRequested) return; await Task.Run(()=>SetLastWriteTimeAsync(path,time)); } public virtual async Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { if(token.IsCancellationRequested) return false; return await Task.Run(()=>SymlinkExists(file)); } public virtual bool SymlinkExists(UnixPath file) { return false; } public virtual IVirtualWatcher WatchDirectory(UnixPath dir) { return null; } public IVirtualFilesystem GetSubdirFilesystem(UnixPath path) { return new SubdirFilesystem(this,path); } public SymlinkPointer OpenSymlink(UnixPath path) { return new SymlinkPointer(this,path); } public FilePointer OpenFile(UnixPath path) { return new FilePointer(this,path); } public DirectoryPointer OpenDirectory(UnixPath path) { return new DirectoryPointer(this,path); } public DirectoryPointer OpenDirectory() { return new DirectoryPointer(this); } public EntryPointer OpenEntry(UnixPath path) { if(FileExists(path)) { return new FilePointer(this,path); }else if(SymlinkExists(path)) { return new SymlinkPointer(this,path); } else if(DirectoryExists(path)){ return new DirectoryPointer(this,path); } return new NonExistantPointer(this,path); } public abstract IEnumerable EnumerateFileSystemEntries(UnixPath path); public IEnumerable EnumerateSymlinks(UnixPath path) { foreach(var item in EnumerateFileSystemEntries(path)) { if(SymlinkExists(item)) { yield return item; } } } public async IAsyncEnumerable EnumerateSymlinksAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path)) { if(token.IsCancellationRequested) yield break; if(await SymlinkExistsAsync(item)) yield return await Task.FromResult(item); } } public virtual async IAsyncEnumerable EnumerateFileSystemEntriesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { foreach(var item in EnumerateFileSystemEntries(path)) { if(token.IsCancellationRequested) yield break; yield return await Task.FromResult(item); } } public virtual void DeleteDirectory(UnixPath path, bool recursive) { if(recursive) { foreach(var item in EnumerateFileSystemEntries(path)) { if(DirectoryExists(item)) { DeleteDirectory(item,true); }else{ DeleteFile(item); } } } DeleteDirectory(path); } public virtual async Task DeleteDirectoryAsync(UnixPath path, bool recursive, CancellationToken token = default) { if(recursive){ await foreach(var item in EnumerateFileSystemEntriesAsync(path)) { if(token.IsCancellationRequested) return; if(await DirectoryExistsAsync(item)) { await DeleteDirectoryAsync(item,true,token); }else{ await DeleteFileAsync(item,token); } } } await DeleteDirectoryAsync(path,token); } } public abstract class AsyncFileSystem : IVirtualFilesystem { public SymlinkPointer OpenSymlink(UnixPath path) { return new SymlinkPointer(this,path); } public FilePointer OpenFile(UnixPath path) { return new FilePointer(this,path); } public EntryPointer OpenEntry(UnixPath path) { if(FileExists(path)) { return new FilePointer(this,path); }else if(SymlinkExists(path)) { return new SymlinkPointer(this,path); } else if(DirectoryExists(path)){ return new DirectoryPointer(this,path); } return new NonExistantPointer(this,path); } public virtual void DeleteDirectory(UnixPath path, bool recursive) { if(recursive) { foreach(var item in EnumerateFileSystemEntries(path)) { if(DirectoryExists(item)) { DeleteDirectory(item,true); }else{ DeleteFile(item); } } } DeleteDirectory(path); } public virtual async Task DeleteDirectoryAsync(UnixPath path, bool recursive, CancellationToken token = default) { if(recursive){ await foreach(var item in EnumerateFileSystemEntriesAsync(path)) { if(token.IsCancellationRequested) return; if(await DirectoryExistsAsync(item)) { await DeleteDirectoryAsync(item,true,token); }else{ await DeleteFileAsync(item,token); } } } await DeleteDirectoryAsync(path,token); } public IVirtualFilesystem GetSubdirFilesystem(UnixPath path) { return new SubdirFilesystem(this,path); } public DirectoryPointer OpenDirectory() { return new DirectoryPointer(this); } public DirectoryPointer OpenDirectory(UnixPath path) { return new DirectoryPointer(this,path); } public virtual void Dispose() { } public virtual UnixPath UpPath(UnixPath path) { return path.Parent; } public virtual UnixPath ConvertPathToUnixPath(string path) { return path; } public virtual string ConvertPathFromUnixPath(UnixPath path) { return path.ToString(); } public virtual bool CanWatch(UnixPath path) { return false; } public virtual void CreateDirectory(UnixPath directory) { Task.Run(async()=>await CreateDirectoryAsync(directory)).Wait(); } public abstract Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default); public virtual void CreateHardlink(UnixPath src, UnixPath dest) { Task.Run(async()=>await CreateHardlinkAsync(src,dest)).Wait(); } public virtual async Task CreateHardlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { //await this.CopyFile() await this.CopyFileAsync(src,dest,token); } public virtual void CreateSymlink(UnixPath src, UnixPath dest) { Task.Run(async()=>await CreateSymlinkAsync(src,dest)).Wait(); } public virtual async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { await Task.FromResult(0); } public virtual bool CanHandleSymlinks(UnixPath path) { return false; } public virtual void DeleteDirectory(UnixPath path) { Task.Run(async()=>await DeleteDirectoryAsync(path)).Wait(); } public abstract Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default); public virtual void DeleteFile(UnixPath path) { Task.Run(async()=>await DeleteFileAsync(path)).Wait(); } public abstract Task DeleteFileAsync(UnixPath path, CancellationToken token = default); public virtual bool DirectoryExists(UnixPath path) { bool res=false; Task.Run(async()=>res=await DirectoryExistsAsync(path)).Wait(); return res; } public abstract Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default); public virtual IEnumerable EnumerateDirectories(UnixPath path) { var enumerator = EnumerateDirectoriesAsync(path).GetAsyncEnumerator(); while(Task.Run(async()=>await enumerator.MoveNextAsync()).GetAwaiter().GetResult()) { yield return enumerator.Current; } Task.Run(enumerator.DisposeAsync).Wait(); } public virtual IEnumerable EnumerateFiles(UnixPath path) { var enumerator = EnumerateFilesAsync(path).GetAsyncEnumerator(); while(Task.Run(async()=>await enumerator.MoveNextAsync()).GetAwaiter().GetResult()) { yield return enumerator.Current; } Task.Run(enumerator.DisposeAsync).Wait(); } public virtual async IAsyncEnumerable EnumerateFilesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path,token)) { if(token.IsCancellationRequested) yield break; if(await FileExistsAsync(item,token) && !await SymlinkExistsAsync(item,token)) { yield return await Task.FromResult(item); } } } public virtual async IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path,token)) { if(token.IsCancellationRequested) yield break; if(await DirectoryExistsAsync(item,token) && !await SymlinkExistsAsync(item,token)) { yield return await Task.FromResult(item); } } } public virtual async IAsyncEnumerable EnumerateSymlinksAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { await foreach(var item in EnumerateFileSystemEntriesAsync(path,token)) { if(token.IsCancellationRequested) yield break; if(await SymlinkExistsAsync(item,token)) { yield return await Task.FromResult(item); } } } public virtual bool FileExists(UnixPath path) { return Task.Run(async()=>await FileExistsAsync(path)).GetAwaiter().GetResult(); } public abstract Task FileExistsAsync(UnixPath path, CancellationToken token = default); public virtual FileAttributes GetAttributes(UnixPath path) { return Task.Run(async()=>await GetAttributesAsync(path)).GetAwaiter().GetResult(); } public virtual Task GetAttributesAsync(UnixPath path, CancellationToken token = default) { return Task.FromResult(FileAttributes.Normal); } public virtual DateTime GetCreationTime(UnixPath path) { return Task.Run(async()=>await GetCreationTimeAsync(path)).GetAwaiter().GetResult(); } public abstract Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default); public virtual DateTime GetLastAccessTime(UnixPath path) { return Task.Run(async()=>await GetLastAccessTimeAsync(path)).GetAwaiter().GetResult(); } public abstract Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default); public virtual DateTime GetLastWriteTime(UnixPath path) { return Task.Run(async()=>await GetLastWriteTimeAsync(path)).GetAwaiter().GetResult(); } public abstract Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default); public virtual void MoveDirectory(UnixPath src, UnixPath dest) { Task.Run(async()=>await MoveDirectoryAsync(src,dest)).Wait(); } public abstract Task MoveDirectoryAsync(UnixPath src, UnixPath dest); public virtual void MoveFile(UnixPath src, UnixPath dest) { Task.Run(async()=>await MoveFileAsync(src,dest)).Wait(); } public abstract Task MoveFileAsync(UnixPath src, UnixPath dest); public Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share) { return Task.Run(async()=>await OpenAsync(path,mode,access,share)).GetAwaiter().GetResult(); } public abstract Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default); public virtual UnixPath ReadLink(UnixPath file) { return Task.Run(async()=>await ReadLinkAsync(file)).GetAwaiter().GetResult(); } public virtual async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { return await Task.FromResult(file); } public virtual async Task SameFileSystemAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { return await Task.FromResult(false); } public virtual bool SameFileSystem(UnixPath src, UnixPath dest) { return Task.Run(async()=>await SameFileSystemAsync(src,dest)).GetAwaiter().GetResult(); } public virtual void SetAttributes(UnixPath path, FileAttributes attributes) { Task.Run(async()=>await SetAttributesAsync(path,attributes)).Wait(); } public virtual async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { await Task.FromResult(0); } public virtual void SetCreationTime(UnixPath path, DateTime time) { Task.Run(async()=>await SetCreationTimeAsync(path,time)).Wait(); } public abstract Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default); public virtual void SetLastAccessTime(UnixPath path, DateTime time) { Task.Run(async()=>await SetLastAccessTimeAsync(path,time)).Wait(); } public abstract Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default); public virtual void SetLastWriteTime(UnixPath path, DateTime time) { Task.Run(async()=>await SetLastWriteTimeAsync(path,time)).Wait(); } public abstract Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default); public virtual async Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { return await Task.FromResult(false); } public virtual bool SymlinkExists(UnixPath file) { return Task.Run(async()=>await SymlinkExistsAsync(file)).GetAwaiter().GetResult(); } public virtual IVirtualWatcher WatchDirectory(UnixPath dir) { return null; } public IEnumerable EnumerateFileSystemEntries(UnixPath path) { var enumerator = EnumerateFileSystemEntriesAsync(path).GetAsyncEnumerator(); while(Task.Run(async()=>await enumerator.MoveNextAsync()).GetAwaiter().GetResult()) { yield return enumerator.Current; } Task.Run(enumerator.DisposeAsync).Wait(); } public IEnumerable EnumerateSymlinks(UnixPath path) { var enumerator = EnumerateSymlinksAsync(path).GetAsyncEnumerator(); while(Task.Run(async()=>await enumerator.MoveNextAsync()).GetAwaiter().GetResult()) { yield return enumerator.Current; } Task.Run(enumerator.DisposeAsync).Wait(); } public abstract IAsyncEnumerable EnumerateFileSystemEntriesAsync(UnixPath path, CancellationToken token = default); } /* public sealed class MultiLayerFS : AsyncFileSystem { private delegate Task OpenAsyncDel(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default); private class FileSystemWithCustomOpen : IVirtualFilesystem { public FileSystemWithCustomOpen(IVirtualFilesystem fs,OpenAsyncDel del) { this.del = del; this.fs = fs; } OpenAsyncDel del; public IVirtualFilesystem fs; public bool CanHandleSymlinks(UnixPath path) { return fs.CanHandleSymlinks(path); } public bool CanWatch(UnixPath path) { return fs.CanWatch(path); } public string ConvertPathFromUnixPath(UnixPath path) { return fs.ConvertPathFromUnixPath(path); } public UnixPath ConvertPathToUnixPath(string path) { return fs.ConvertPathToUnixPath(path); } public void CreateDirectory(UnixPath directory) { fs.CreateDirectory(directory); } public async Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default) { await fs.CreateDirectoryAsync(directory,token); } public void CreateHardlink(UnixPath src, UnixPath dest) { fs.CreateHardlink(src,dest); } public async Task CreateHardlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { await fs.CreateHardlinkAsync(src,dest,token); } public void CreateSymlink(UnixPath src, UnixPath dest) { fs.CreateSymlink(src,dest); } public async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { await fs.CreateSymlinkAsync(src,dest,token); } public void DeleteDirectory(UnixPath path) { fs.DeleteDirectory(path); } public async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default) { await fs.DeleteDirectoryAsync(path,token); } public void DeleteFile(UnixPath path) { fs.DeleteFile(path); } public async Task DeleteFileAsync(UnixPath path, CancellationToken token = default) { await fs.DeleteFileAsync(path,token); } public bool DirectoryExists(UnixPath path) { return fs.DirectoryExists(path); } public async Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default) { return await fs.DirectoryExistsAsync(path,token); } public void Dispose() { fs.Dispose(); } public IEnumerable EnumerateDirectories(UnixPath path) { foreach(var item in fs.EnumerateDirectories(path)) { yield return item; } } public async IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in fs.EnumerateDirectoriesAsync(path,token)) { if(token.IsCancellationRequested) yield break; yield return item; } } public IEnumerable EnumerateFiles(UnixPath path) { foreach(var item in fs.EnumerateFiles(path)) { yield return item; } } public async IAsyncEnumerable EnumerateFilesAsync(UnixPath path, [EnumeratorCancellation]CancellationToken token = default) { await foreach(var item in fs.EnumerateFilesAsync(path,token)) { if(token.IsCancellationRequested) yield break; yield return path; } } public bool FileExists(UnixPath path) { return fs.FileExists(path); } public async Task FileExistsAsync(UnixPath path, CancellationToken token = default) { return await fs.FileExistsAsync(path,token); } public FileAttributes GetAttributes(UnixPath path) { return fs.GetAttributes(path); } public async Task GetAttributesAsync(UnixPath path, CancellationToken token = default) { return await fs.GetAttributesAsync(path,token); } public DateTime GetCreationTime(UnixPath path) { return fs.GetCreationTime(path); } public async Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default) { return await fs.GetCreationTimeAsync(path,token); } public DateTime GetLastAccessTime(UnixPath path) { return fs.GetLastAccessTime(path); } public async Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default) { return await fs.GetLastAccessTimeAsync(path,token); } public DateTime GetLastWriteTime(UnixPath path) { return fs.GetLastAccessTime(path); } public async Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default) { return await fs.GetLastWriteTimeAsync(path,token); } public void MoveDirectory(UnixPath src, UnixPath dest) { fs.MoveDirectory(src,dest); } public async Task MoveDirectoryAsync(UnixPath src, UnixPath dest) { await fs.MoveDirectoryAsync(src,dest); } public void MoveFile(UnixPath src, UnixPath dest) { fs.MoveFile(src,dest); } public async Task MoveFileAsync(UnixPath src, UnixPath dest) { await fs.MoveFileAsync(src,dest); } public Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share) { return Task.Run(async()=>await del(path,mode,access,share)).GetAwaiter().GetResult(); } public async Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default) { return await del(path,mode,access,share,token); } public UnixPath ReadLink(UnixPath file) { return fs.ReadLink(file); } public async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { return await fs.ReadLinkAsync(file,token); } public bool SameFileSystem(UnixPath src, UnixPath dest) { return fs.SameFileSystem(src,dest); } public async Task SameFileSystemAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { return await fs.SameFileSystemAsync(src,dest,token); } public void SetAttributes(UnixPath path, FileAttributes attributes) { fs.SetAttributes(path,attributes); } public async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { await fs.SetAttributesAsync(path,attributes,token); } public void SetCreationTime(UnixPath path, DateTime time) { fs.SetCreationTime(path,time); } public async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await fs.SetCreationTimeAsync(path,time,token); } public void SetLastAccessTime(UnixPath path, DateTime time) { fs.SetLastAccessTime(path,time); } public async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await fs.SetLastAccessTimeAsync(path,time,token); } public void SetLastWriteTime(UnixPath path, DateTime time) { fs.SetLastWriteTime(path,time); } public async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { await fs.SetLastWriteTimeAsync(path,time,token); } public bool SymlinkExists(UnixPath file) { return fs.SymlinkExists(file); } public async Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { return await fs.SymlinkExistsAsync(file,token); } public UnixPath UpPath(UnixPath path) { return fs.UpPath(path); } public IVirtualWatcher WatchDirectory(UnixPath dir) { return fs.WatchDirectory(dir); } } IVirtualFilesystem root; public MultiLayerFS(IVirtualFilesystem root) { this.root=root; } public class CloseEvtStream : Stream { Stream parent; Action cstrm; bool destroyParent; public CloseEvtStream(Stream parent,Action closed,bool destroyParent=true) { this.destroyParent=destroyParent; this.parent = parent; this.cstrm = closed; } public override bool CanRead => parent.CanRead; public override bool CanSeek => parent.CanSeek; public override bool CanWrite => parent.CanWrite; public override long Length => parent.Length; public override long Position { get => parent.Position; set => parent.Position = value; } public override void Flush() { parent.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return parent.Read(buffer,offset,count); } public override long Seek(long offset, SeekOrigin origin) { return parent.Seek(offset,origin); } public override void SetLength(long value) { parent.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { parent.Write(buffer,offset,count); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return parent.BeginRead(buffer, offset, count, callback, state); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return parent.BeginWrite(buffer, offset, count, callback, state); } public override bool CanTimeout => parent.CanTimeout; public override void Close() { if(destroyParent) parent.Close(); cstrm(); } protected override void Dispose(bool disposing) { if(disposing && destroyParent) parent.Dispose(); cstrm(); } public override void EndWrite(IAsyncResult asyncResult) { parent.EndWrite(asyncResult); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return parent.WriteAsync(buffer, offset, count, cancellationToken); } public override Task FlushAsync(CancellationToken cancellationToken) { return parent.FlushAsync(cancellationToken); } public override void WriteByte(byte value) { parent.WriteByte(value); } public override int EndRead(IAsyncResult asyncResult) { return parent.EndRead(asyncResult); } public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { return parent.CopyToAsync(destination, bufferSize, cancellationToken); } } List fs2=new List(); Dictionary StreamList,Action2Event Destroy)> streams = new Dictionary StreamList,Action2Event Destroy)>(); Dictionary fs=new Dictionary(); public override UnixPath UpPath(UnixPath path) { var unixPathLs = path as UnixPathList; if(unixPathLs != null) { return unixPathLs.ParentPL; }else{ return path.Parent; } } Dictionary _creators=new Dictionary(); public void Register(string scheme,IVirtualFileSystemCreator fs) { _creators.Add(scheme,fs); } public void Register(IVirtualFileSystemCreator fs) { _creators.Add(fs.Scheme,fs); } public override void Dispose() { while(fs2.Count>0 && streams.Count > 0) { if(streams.ContainsKey(fs2[0])) { var strm = streams[fs2[0]]; foreach(var strm0 in strm.StreamList) { strm0.Dispose(); } strm.StreamList.Clear(); CloseFileSystemIfNoStreams(fs2[0]); streams.Remove(fs2[0]); fs2.Remove(fs2[0]); } } streams.Clear(); fs.Clear(); } public override async Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default) { var fs=await OpenTillAsync(directory,token); await fs.CreateDirectoryAsync(Last(directory)); CloseFileSystemIfNoStreams(fs); } private async Task OpenTillAsync(UnixPath path,CancellationToken token=default) { UnixPathList pathLs = path as UnixPathList; if(pathLs != null && pathLs.Count > 1) { return await OpenTillAsync(pathLs,token); } return root; } private async Task OpenTillAsync(UnixPathList path,CancellationToken token=default) { IVirtualFilesystem fs=root; List paths=new List(); for(int i = 0;i OpenStreamOnBehalf(IVirtualFilesystem fs,UnixPath p,FileMode mode,FileAccess access,FileShare share,CancellationToken token=default(CancellationToken)) { Stream strm = await fs.OpenAsync(p,mode,access,share); CloseEvtStream strmI=new CloseEvtStream(strm,()=>{ mtx.WaitOne(); if(streams.ContainsKey(fs)) { var strmItem = streams[fs]; if(strmItem.StreamList.Contains(strm)) { strmItem.StreamList.Remove(strm); } if(strmItem.StreamList.Count == 0) { strmItem.Destroy.Invoke(); streams.Remove(fs); string key = ""; foreach(var i in this.fs) { if(i.Value == fs) { key = i.Key; break; } } if(this.fs.ContainsKey(key)) { this.fs.Remove(key); } } } mtx.ReleaseMutex(); }); mtx.WaitOne(); if(streams.ContainsKey(fs)) { var strmItem = streams[fs]; if(!strmItem.StreamList.Contains(strm)) { strmItem.StreamList.Add(strm); } } mtx.ReleaseMutex(); return strmI; } public UnixPath ExceptLast(UnixPath p) { UnixPathList pl = p as UnixPathList; if(pl != null && pl.Count > 0) { List paths=new List(); for(int i = 0;i0) { return pl[pl.Count-1]; } return p; } private async Task OpenFileSystemAsync(IVirtualFilesystem parent,UnixPath p,CancellationToken token=default) { if(string.IsNullOrWhiteSpace(p.Scheme)) return parent; mtx.WaitOne(); var last=Last(p); if(!_creators.ContainsKey(last.Scheme)){ mtx.ReleaseMutex();return null;} if(parent == root) { if(fs.ContainsKey(p.AsText)) { var res0= fs[p.AsText]; mtx.ReleaseMutex(); return res0; } var res=_creators[p.Scheme]; if(res.IsDirectoryOrganizer) { var c = res.Open(root,last); fs.Add(p.AsText,c); return c; }else{ var strm = await OpenAsync(last,FileMode.Open,FileAccess.ReadWrite,FileShare.None,token); var c = res.Open(strm); Action a =()=>{ c.Dispose(); strm.Dispose(); }; streams.Add(c,(new List(),a)); fs.Add(p.AsText,c); return c; } }else{mtx.ReleaseMutex(); if(fs.ContainsKey(p.AsText)) { var res0= fs[p.AsText]; mtx.ReleaseMutex(); return res0; } var res=_creators[p.Scheme]; if(res.IsDirectoryOrganizer) { var parent2 = new FileSystemWithCustomOpen(parent,async(path,mode,access,share,token)=>{ return await OpenStreamOnBehalf(parent,path,mode,access,share,token); }); var c = res.Open(parent2,last); streams[parent].Destroy.Add(c.Dispose); fs2.Insert(0,c); fs.Add(p.AsText,c); return c; }else{ var res0=await OpenStreamOnBehalf(parent,last,FileMode.Open,FileAccess.ReadWrite,FileShare.None,token); var c = res.Open(res0); Action a =()=>{ if(fs2.Contains(c)) fs2.Remove(c); c.Dispose(); res0.Dispose(); }; streams.Add(c,(new List(),a)); fs.Add(p.AsText,c); fs2.Insert(0,c); mtx.ReleaseMutex(); return c; } } } private void CloseFileSystemIfNoStreams(IVirtualFilesystem fs) { if(fs == root) return; mtx.WaitOne(); if(streams.ContainsKey(fs)) { var strmItem = streams[fs]; if(strmItem.StreamList.Count == 0) { strmItem.Destroy.Invoke(); } } mtx.ReleaseMutex(); } public override async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); await fs.DeleteDirectoryAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); } public override async Task DeleteFileAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); await fs.DeleteFileAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); } public override async Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); var res=await fs.DirectoryExistsAsync(Last(path)); CloseFileSystemIfNoStreams(fs); return res; } public override async IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { var fs=await OpenTillAsync(path,token); using(var reg= token.Register(()=>{ CloseFileSystemIfNoStreams(fs); })){ await foreach(var item in fs.EnumerateDirectoriesAsync(Last(path))) { yield return item; } CloseFileSystemIfNoStreams(fs); } } public override async IAsyncEnumerable EnumerateFilesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { var fs=await OpenTillAsync(path,token); using(var reg= token.Register(()=>{ CloseFileSystemIfNoStreams(fs); })){ await foreach(var item in fs.EnumerateFilesAsync(Last(path))) { yield return item; } CloseFileSystemIfNoStreams(fs); } } public override async Task FileExistsAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); bool res = await fs.FileExistsAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); return res; } public override async Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); DateTime res = await fs.GetCreationTimeAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); return res; } public override async Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); DateTime res = await fs.GetLastAccessTimeAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); return res; } public override async Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path,token); DateTime res = await fs.GetLastWriteTimeAsync(Last(path),token); CloseFileSystemIfNoStreams(fs); return res; } public override async Task MoveDirectoryAsync(UnixPath src, UnixPath dest) { if(ExceptLast(src).AsText == ExceptLast(dest).AsText) { var fs=await OpenTillAsync(src); await fs.MoveDirectoryAsync(Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); }else{ var fs=await OpenTillAsync(src); var fs2=await OpenTillAsync(dest); await fs.MoveDirectoryAsync(fs2,Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); CloseFileSystemIfNoStreams(fs2); } } public override async Task MoveFileAsync(UnixPath src, UnixPath dest) { if(ExceptLast(src).AsText == ExceptLast(dest).AsText) { var fs=await OpenTillAsync(src); await fs.MoveFileAsync(Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); }else{ var fs=await OpenTillAsync(src); var fs2=await OpenTillAsync(dest); await fs.MoveFileAsync(fs2,Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); CloseFileSystemIfNoStreams(fs2); } } public override async Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default) { var fs = await OpenTillAsync(path,token); using(var t = token.Register(()=>{CloseFileSystemIfNoStreams(fs);})) { return await OpenStreamOnBehalf(fs,Last(path),mode,access,share,token); } } public override async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { var fs=await OpenTillAsync(path); await fs.SetCreationTimeAsync(Last(path),time,token); CloseFileSystemIfNoStreams(fs); } public override async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { var fs=await OpenTillAsync(path); await fs.SetLastAccessTimeAsync(Last(path),time,token); CloseFileSystemIfNoStreams(fs); } public override async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { var fs=await OpenTillAsync(path); await fs.SetAttributesAsync(Last(path),attributes,token); CloseFileSystemIfNoStreams(fs); } public override bool CanWatch(UnixPath path) { return false; } public override async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { var fs=await OpenTillAsync(file); var rlink=await fs.ReadLinkAsync(Last(file)); CloseFileSystemIfNoStreams(fs); return rlink; } public override async Task SameFileSystemAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { return await Task.FromResult(ExceptLast(src).AsText == ExceptLast(dest).AsText); } public override Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { return base.SymlinkExistsAsync(file, token); } public override async Task GetAttributesAsync(UnixPath path, CancellationToken token = default) { var fs=await OpenTillAsync(path); var res=await fs.GetAttributesAsync(Last(path)); CloseFileSystemIfNoStreams(fs); return res; } public override async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(ExceptLast(src).AsText != ExceptLast(dest).AsText) return; var fs=await OpenTillAsync(src); await fs.CreateSymlinkAsync(Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); } public override async Task CreateHardlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(ExceptLast(src).AsText != ExceptLast(dest).AsText) return; var fs=await OpenTillAsync(src); await fs.CreateHardlinkAsync(Last(src),Last(dest)); CloseFileSystemIfNoStreams(fs); } public override bool CanHandleSymlinks(UnixPath path) { var fs=Task.Run(async()=>await OpenTillAsync(path)).GetAwaiter().GetResult(); var can=fs.CanHandleSymlinks(Last(path)); CloseFileSystemIfNoStreams(fs); return can; } public override async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { var fs=await OpenTillAsync(path); await fs.SetLastWriteTimeAsync(Last(path),time,token); CloseFileSystemIfNoStreams(fs); } } */ public class Action2Event { public Action2Event(Action a) { Event+=a; } public void Add(Action a) { Event+=a; } public void Invoke() { Event?.Invoke(); } public event Action Event; public static implicit operator Action2Event(Action a) { return new Action2Event(a); } } }