/* 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; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Tesses.VirtualFilesystem { public sealed class NonExistantPointer : EntryPointer { public override bool IsNonExistantPointer => true; public NonExistantPointer(IVirtualFilesystem fs,UnixPath path) : base(fs,path) { } public override bool Exists => false; public override string Name { get => Path.Name; set {} } public override void Delete() { } } public abstract class EntryPointer { public virtual bool IsNonExistantPointer {get{return false;}} public virtual bool IsFilePointer {get{return false;}} public virtual bool IsDirectoryPointer {get{return false;}} public virtual bool IsSymlinkPointer {get{return false;}} public EntryPointer this[UnixPath path] { get{ return _fs.OpenEntry(Path / path); } } public DirectoryPointer AsDirectoryPointer() { return new DirectoryPointer(_fs,Path); } public FilePointer AsFilePointer() { return new FilePointer(_fs,Path); } public SymlinkPointer AsSymlinkPointer() { return new SymlinkPointer(_fs,Path); } internal EntryPointer(IVirtualFilesystem fs) { _fs=fs; } internal EntryPointer(IVirtualFilesystem fs,UnixPath path) : this(fs) { Path = path; } protected IVirtualFilesystem _fs; public IVirtualFilesystem Filesystem {get{return _fs;}} public UnixPath Path {get;set;}="/"; public abstract string Name {get;set;} public UnixPath WithPath(UnixPath path) { return Path / path; } public DirectoryPointer Up() { return new DirectoryPointer(_fs,_fs.UpPath(Path)); } public abstract void Delete(); public abstract bool Exists {get;} } public sealed class SymlinkPointer : EntryPointer { public override bool IsSymlinkPointer => true; public SymlinkPointer(IVirtualFilesystem fs,UnixPath path) : base(fs,path) { } private void _setName(string name) { UnixPath oldPath = Path; UnixPath target= Target; Delete(); Path.Name = name; Target= target; } public override string Name { get => Path.Name; set => _setName(value); } public override bool Exists => _fs.SymlinkExists(Path); public override void Delete() { if(Exists) _fs.DeleteFile(Path); } public UnixPath Target { get{ return _fs.ReadLink(Path); } set{ Delete(); _fs.CreateSymlink(value,Path); } } public EntryPointer TargetPointer { get { return _fs.OpenEntry(Target); } } } public sealed class FilePointer : EntryPointer { public override bool IsFilePointer => true; public FilePointer(IVirtualFilesystem fs,UnixPath path) : base(fs,path) { } public Stream Create() { return _fs.Open(Path,FileMode.Create,FileAccess.Write,FileShare.Inheritable); } public Stream OpenRead() { return _fs.Open(Path,FileMode.Open,FileAccess.Read,FileShare.Read); } public Stream OpenWrite() { return _fs.Open(Path,FileMode.OpenOrCreate,FileAccess.Write,FileShare.Inheritable); } public void WriteAllText(string text,Encoding encoding) { using(var f = Create()) StreamHelper.WriteAllText(f,text,encoding); } public void WriteAllText(string text) { WriteAllText(text,Encoding.UTF8); } public string ReadAllText(Encoding encoding) { using(var f = OpenRead()) return StreamHelper.ReadAllText(f,encoding); } public override bool Exists => _fs.FileExists(Path); public void WriteAllBytes(byte[] data) { using(var f = Create()) StreamHelper.WriteAllBytes(f,data); } public byte[] ReadAllBytes() { using(var f = OpenRead()) return StreamHelper.ReadAllBytes(f); } public override void Delete() { _fs.DeleteFile(Path); } public override string Name { get { return Path.Name; } set { UnixPath old = new UnixPath(Path); Path.Name = value; _fs.MoveFile(old,Path); } } } public class DirectoryPointer : EntryPointer, IEnumerable { public override bool IsDirectoryPointer => true; public DirectoryPointer(IVirtualFilesystem fs) : base(fs) { } public DirectoryPointer(IVirtualFilesystem fs,UnixPath path) : this(fs) { Path = path; } public void Create() { _fs.CreateDirectory(Path); } public override void Delete() { _fs.DeleteDirectory(Path); } public void DeleteRecursive() { _fs.DeleteDirectory(Path,true); } public IVirtualFilesystem SubDirectoryFilesystem {get{return _fs.GetSubdirFilesystem(Path);}} public DirectoryPointer[] Directories { get{ return EnumerateDirectories().ToArray(); } } public FilePointer[] Files { get{ return EnumerateFiles().ToArray(); } } public SymlinkPointer[] Symlinks { get{ return EnumerateSymlinks().ToArray(); } } public EntryPointer[] Entries { get { return EnumerateEntries().ToArray(); } } public IEnumerable EnumerateDirectories() { foreach(var item in _fs.EnumerateDirectories(Path)) { yield return new DirectoryPointer(_fs,item); } } public IEnumerable EnumerateFiles() { foreach(var item in _fs.EnumerateFiles(Path)) { yield return new FilePointer(_fs,item); } } public IEnumerable EnumerateSymlinks() { foreach(var item in _fs.EnumerateSymlinks(Path)) { yield return new SymlinkPointer(_fs,item); } } public static DirectoryPointer operator/(DirectoryPointer pointer,string path) { return new DirectoryPointer(pointer._fs,pointer.Path / path); } public static DirectoryPointer operator/(DirectoryPointer pointer,UnixPath path) { return new DirectoryPointer(pointer._fs,pointer.Path / path); } public IEnumerable EnumerateEntries() { foreach(var item in _fs.EnumerateFileSystemEntries(Path)) { yield return _fs.OpenEntry(item); } } public IEnumerator GetEnumerator() { return EnumerateEntries().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return EnumerateEntries().GetEnumerator(); } public override bool Exists {get{return _fs.DirectoryExists(Path);}} public override string Name { get { return Path.Name; } set { UnixPath old = new UnixPath(Path); Path.Name = value; _fs.MoveDirectory(old,Path); } } } public static class StreamHelper { public static string ReadAllText(Stream strm,Encoding enc) { using(var sr = new StreamReader(strm,enc)) { return sr.ReadToEnd(); } } public static string ReadAllText(Stream strm) { return ReadAllText(strm,Encoding.UTF8); } public static byte[] ReadAllBytes(Stream strm) { MemoryStream ms=new MemoryStream(); strm.CopyTo(ms); return ms.ToArray(); } public static void WriteAllBytes(Stream strm,byte[] data) { using(MemoryStream ms = new MemoryStream(data)) ms.CopyTo(strm); } public static void WriteAllText(Stream strm,string text,Encoding enc) { using(var sw = new StreamWriter(strm,enc)) { sw.Write(text); } } public static void WriteAllText(Stream strm,string text) { WriteAllText(strm,text,Encoding.UTF8); } public static string[] ReadAllLines(Stream strm,Encoding enc) { List ls = new List(); using(var sr = new StreamReader(strm,enc)) { string line; while((line=sr.ReadLine()) != null) { ls.Add(line); } } return ls.ToArray(); } public static string[] ReadAllLines(Stream strm) { return ReadAllLines(strm,Encoding.UTF8); } public static void WriteAllLines(Stream strm,IEnumerable lines,Encoding enc) { using(var sw = new StreamWriter(strm,enc)) { foreach(var line in lines) { sw.WriteLine(line); } } } public static void WriteAllLines(Stream strm,IEnumerable lines) { WriteAllLines(strm,lines,Encoding.UTF8); } } public interface IVirtualFilesystem : IDisposable { IVirtualFilesystem GetSubdirFilesystem(UnixPath path); EntryPointer OpenEntry(UnixPath path); FilePointer OpenFile(UnixPath path); SymlinkPointer OpenSymlink(UnixPath path); DirectoryPointer OpenDirectory(); DirectoryPointer OpenDirectory(UnixPath path); UnixPath UpPath(UnixPath path); UnixPath ConvertPathToUnixPath(string path); string ConvertPathFromUnixPath(UnixPath path); Stream Open(UnixPath path,FileMode mode,FileAccess access,FileShare share); Task OpenAsync(UnixPath path,FileMode mode,FileAccess access,FileShare share,CancellationToken token=default(CancellationToken)); void CreateDirectory(UnixPath directory); Task CreateDirectoryAsync(UnixPath directory,CancellationToken token=default(CancellationToken)); IEnumerable EnumerateFileSystemEntries(UnixPath path); IEnumerable EnumerateFiles(UnixPath path); IEnumerable EnumerateDirectories(UnixPath path); IEnumerable EnumerateSymlinks(UnixPath path); IAsyncEnumerable EnumerateFilesAsync(UnixPath path,CancellationToken token=default(CancellationToken)); IAsyncEnumerable EnumerateDirectoriesAsync(UnixPath path,CancellationToken token=default(CancellationToken)); IAsyncEnumerable EnumerateSymlinksAsync(UnixPath path,CancellationToken token=default(CancellationToken)); IAsyncEnumerable EnumerateFileSystemEntriesAsync(UnixPath path,CancellationToken token =default(CancellationToken)); bool DirectoryExists(UnixPath path); Task DirectoryExistsAsync(UnixPath path,CancellationToken token=default(CancellationToken)); bool FileExists(UnixPath path); Task FileExistsAsync(UnixPath path,CancellationToken token=default(CancellationToken)); void DeleteFile(UnixPath path); void DeleteDirectory(UnixPath path); void DeleteDirectory(UnixPath path,bool recursive); void CreateSymlink(UnixPath src,UnixPath dest); void CreateHardlink(UnixPath src,UnixPath dest); UnixPath ReadLink(UnixPath file); Task ReadLinkAsync(UnixPath file,CancellationToken token=default(CancellationToken)); bool SymlinkExists(UnixPath file); Task SymlinkExistsAsync(UnixPath file,CancellationToken token=default(CancellationToken)); Task CreateSymlinkAsync(UnixPath src,UnixPath dest,CancellationToken token=default(CancellationToken)); Task CreateHardlinkAsync(UnixPath src,UnixPath dest,CancellationToken token=default(CancellationToken)); Task SameFileSystemAsync(UnixPath src,UnixPath dest,CancellationToken token=default(CancellationToken)); bool SameFileSystem(UnixPath src,UnixPath dest); bool CanHandleSymlinks(UnixPath path); Task DeleteFileAsync(UnixPath path,CancellationToken token=default(CancellationToken)); Task DeleteDirectoryAsync(UnixPath path,CancellationToken token=default(CancellationToken)); Task DeleteDirectoryAsync(UnixPath path,bool recursive,CancellationToken token=default(CancellationToken)); bool CanWatch(UnixPath path); IVirtualWatcher WatchDirectory(UnixPath dir); void MoveFile(UnixPath src,UnixPath dest); void MoveDirectory(UnixPath src,UnixPath dest); Task MoveFileAsync(UnixPath src,UnixPath dest); Task MoveDirectoryAsync(UnixPath src,UnixPath dest); DateTime GetCreationTime(UnixPath path); DateTime GetLastAccessTime(UnixPath path); DateTime GetLastWriteTime(UnixPath path); void SetCreationTime(UnixPath path,DateTime time); void SetLastAccessTime(UnixPath path,DateTime time); void SetLastWriteTime(UnixPath path,DateTime time); void SetAttributes(UnixPath path,FileAttributes attributes); FileAttributes GetAttributes(UnixPath path); Task GetCreationTimeAsync(UnixPath path,CancellationToken token=default(CancellationToken)); Task GetLastAccessTimeAsync(UnixPath path,CancellationToken token=default(CancellationToken)); Task GetLastWriteTimeAsync(UnixPath path,CancellationToken token=default(CancellationToken)); Task SetCreationTimeAsync(UnixPath path,DateTime time,CancellationToken token=default(CancellationToken)); Task SetLastAccessTimeAsync(UnixPath path,DateTime time,CancellationToken token=default(CancellationToken)); Task SetLastWriteTimeAsync(UnixPath path,DateTime time,CancellationToken token=default(CancellationToken)); Task SetAttributesAsync(UnixPath path,FileAttributes attributes,CancellationToken token=default(CancellationToken)); Task GetAttributesAsync(UnixPath path,CancellationToken token=default(CancellationToken)); } public interface IVirtualWatcher { event EventHandler Changed; event EventHandler Created; event EventHandler Deleted; event EventHandler Renamed; event EventHandler Error; /// /// Implementation-defined buffer size for storing events. /// int InternalBufferSize { get; set; } /// /// Implementation-defined filters for filtering events. /// NotifyFilters NotifyFilter { get; set; } /// /// True to enable raising events, false to never raise them. Default false. /// bool EnableRaisingEvents { get; set; } /// /// File name and extension filter. Use "*" to specify variable length placeholder, "?" /// for a single character placeholder. Default is "*.*" for all files. /// string Filter { get; set; } /// /// True to watch all subdirectories in , false to only watch entries directly /// in . /// bool IncludeSubdirectories { get; set; } } public class VirtualWatcherRenamedEventArgs : VirtualWatcherChangedArgs { public VirtualWatcherRenamedEventArgs(IVirtualFilesystem fs,WatcherChangeTypes changeTypes,UnixPath newPath,UnixPath oldPath) : base(fs,changeTypes,newPath) { OldFullPath = oldPath; OldName = oldPath.Name; } public UnixPath OldFullPath {get;} public string OldName {get;} } public class VirtualWatcherChangedArgs : EventArgs { public VirtualWatcherChangedArgs(IVirtualFilesystem fs,WatcherChangeTypes changeTypes,UnixPath path) { FullPath=path; Name = path.Name; Filesystem = fs; ChangeType = changeTypes; } public IVirtualFilesystem Filesystem {get;} public WatcherChangeTypes ChangeType {get;} public UnixPath FullPath { get; } public string Name {get;} } /* public class UnixPathList : UnixPath, IReadOnlyCollection { private string tostr(int i,UnixPath[] paths) { if(i == 0) { return paths[0].ToString(); } else { var p = paths[i]; if(!string.IsNullOrWhiteSpace(p._scheme)) { return $"{p._scheme}://[{tostr(i-1,paths)}]{p._path}"; } else { return $"[{tostr(i - 1, paths)}]{p._path}"; } } } private bool LastIsRoot { get { if (Count > 0) { return _paths[_paths.Length - 1].IsRoot; } return false; } } public UnixPathList ParentPL { get { if (LastIsRoot) { List paths = new List(); for (int i = 0; i < Count - 2; i++) { paths.Add(new UnixPath(this[i])); } if (Count > 1) { paths.Add(this[Count - 2].Parent); } return new UnixPathList(paths); } else { List paths = new List(); for (int i = 0; i < Count - 1; i++) { paths.Add(new UnixPath(this[i])); } if (Count > 0) { paths.Add(this[Count - 1].Parent); } return new UnixPathList(paths); } } } UnixPath[] _paths; public UnixPathList(params UnixPath[] paths) { _paths = paths; if (_paths.Length > 0) { _path = _paths[0]._path; _parts = _paths[0]._parts; _scheme = _paths[0]._scheme; } } public UnixPathList(IEnumerable paths) : this(paths.ToArray()) { } public static UnixPathList Parse(string path) { List paths = new List(); Parse(path, paths); return new UnixPathList(paths); } private static void Parse(string path, List paths) { string scheme = ""; if (path.Contains("://")) { var res = path.Split(new string[] { "://" }, 2, StringSplitOptions.None); scheme = res[0] + "://"; path = res[1]; } if (path[0] == '[') { int br = 1; int i = 1; for (; i < path.Length; i++) { if (path[i] == '[') br++; if (path[i] == ']') br--; if (br <= 0) break; } Parse(path.Substring(1, i - 1), paths); paths.Add(scheme + path.Substring(i + 1)); } else { paths.Add(scheme + path); } } public IEnumerator GetEnumerator() { return _G().GetEnumerator(); } private IEnumerable _G() { foreach(UnixPath p in _paths) { yield return p; } } IEnumerator IEnumerable.GetEnumerator() { return _G().GetEnumerator(); } public UnixPath this[int i] { get { return _paths[i]; } set { _paths[i] = value; } } public int Count { get { return _paths.Length; } } public override string AsText { get { if (Count < 2) { return ToString(); } else { return tostr(Count-1,_paths); } } } private string GetFs() { return ""; } }*/ public class Special { public static UnixPath CurDir {get{return UnixPath.FromLocal(Environment.CurrentDirectory);} set{Environment.CurrentDirectory = value.ToLocal();}} public static UnixPath Root {get;} = new UnixPath(); public static UnixPath Home {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.UserProfile);}} public static UnixPath Videos {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.MyVideos);}} public static UnixPath Pictures {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.MyPictures);}} public static UnixPath Documents {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.MyDocuments);}} public static UnixPath LocalAppData {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.LocalApplicationData);}} public static UnixPath RoamingAppData {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.ApplicationData);}} public static UnixPath Music {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.MyMusic);}} public static UnixPath DesktopFolder {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.DesktopDirectory);}} public static UnixPath PublicVideos {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonVideos);}} public static UnixPath PublicPictures {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonPictures);}} public static UnixPath PublicDocuments {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonDocuments);}} public static UnixPath PublicAppData {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonApplicationData);}} public static UnixPath PublicStart {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonStartMenu);}} public static UnixPath PublicTemplates {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonTemplates);}} public static UnixPath Start {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.StartMenu);}} public static UnixPath Templates {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.Templates);}} public static UnixPath ProgramFiles {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.ProgramFiles);}} public static UnixPath ProgramFilesX86 {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.ProgramFilesX86);}} public static UnixPath Programs {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.Programs);}} public static UnixPath CommonProgramFiles {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonProgramFiles);}} public static UnixPath CommonProgramFilesX86 {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonProgramFilesX86);}} public static UnixPath CommonPrograms {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonPrograms);}} public static UnixPath PublicDesktopFolder {get {return UnixPath.FromSpecialDirectory(Environment.SpecialFolder.CommonDesktopDirectory);}} } public class UnixPath { public string ToLocal() { if(Environment.OSVersion.Platform == PlatformID.Win32NT) { // /mnt/driveLetter/ if(Parts[0] != "mnt") { return ""; }else{ List p = new List(); for(int i = 0;i paths=new List(); paths.Add("mnt"); paths.Add(p[0].ToLower()); foreach(var path0 in p[1].Split('\\')) { paths.Add(path0); } return new UnixPath(paths); } else { return path; } } public UnixPath() { _path = "/"; _parts = new string[0]; } public UnixPath(IEnumerable pathParts) { UnixPath path = new UnixPath(); foreach (var item in pathParts) { path /= new UnixPath(item); } _parts = path._parts; path = path._path; } public UnixPath(UnixPath path1, UnixPath path2) { List pathParts = new List(); pathParts.AddRange(path1.Parts); pathParts.AddRange(path2.Parts); SetPath(pathParts); } private void SetPath(IEnumerable p) { _parts = p.ToArray(); StringBuilder pa = new StringBuilder(); for (int i = 0; i < _parts.Length; i++) { pa.Append($"/{_parts[i]}"); } _path = pa.ToString(); } public UnixPath(UnixPath path1) { _parts = path1._parts.ToArray(); _path = path1._path; } public UnixPath(params string[] pathParts) : this((IEnumerable)pathParts) { } public UnixPath(UnixPath path, params string[] pathParts) : this(path, new UnixPath(pathParts)) { } public UnixPath(string path) { SetPath(path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)); } public static DirectoryPointer operator/(IVirtualFilesystem fs,UnixPath path) { return new DirectoryPointer(fs,path); } public static UnixPath operator /(UnixPath path, string path2) { return new UnixPath(path, path2); } public static UnixPath operator /(UnixPath path1, UnixPath path2) { return new UnixPath( path1, path2 ); } public static UnixPath operator +(UnixPath path1, string path2) { return path1 + new UnixPath(path2); } public static UnixPath operator +(UnixPath path1, UnixPath path2) { List p = new List(); StringBuilder b = new StringBuilder(); for (int i = 0; i < path1._parts.Length - 1; i++) { p.Add(path1._parts[i]); } if (path1._parts.Length - 1 >= 0) { b.Append(path1._parts[path1._parts.Length - 1]); } if (path2._parts.Length > 0) { b.Append(path2._parts[0]); } if (b.Length > 0) { p.Add(b.ToString()); } for (int i = 1; i < path2._parts.Length; i++) { p.Add(path2._parts[i]); } var path = new UnixPath(); path.SetPath(p); return path; } public static implicit operator UnixPath(string path) { return new UnixPath(path); } public static implicit operator UnixPath(string[] path) { return new UnixPath((IEnumerable)path); } public bool MyPathEquals(UnixPath path) { if(path.Parts.Length != Parts.Length) return false; for(int i = 0;i 0) _parts[_parts.Length-1] = value; } } public bool IsRoot { get { return _parts.Length == 0; } } public bool ParentIsRoot { get { return _parts.Length <= 1; } } public UnixPath Parent { get { var p = new UnixPath(); if (!ParentIsRoot) { p.SetPath(_parts.Take(_parts.Length - 1)); } return p; } } internal string[] _parts; public string[] Parts { get { return _parts; } } public virtual string AsText { get { return ToString(); } } public override string ToString() { return _path; } } }