/* 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 Tesses.VirtualFilesystem; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Linq; using System.Collections.Generic; using System.Text; namespace Tesses.VirtualFilesystem.Extensions { public static class Extensions { public static StreamReader OpenText(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { Stream strm=fs.OpenRead(path); return new StreamReader(strm,encoding,false,4096,false); } public static async Task OpenTextAsync(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { Stream strm=await fs.OpenReadAsync(path); return new StreamReader(strm,encoding,false,4096,false); } public static StreamReader OpenText(this IVirtualFilesystem fs,UnixPath path) { Stream strm = fs.OpenRead(path); return new StreamReader(strm,Encoding.UTF8,false,4096,false); } public static async Task OpenTextAsync(this IVirtualFilesystem fs,UnixPath path) { Stream strm = await fs.OpenReadAsync(path); return new StreamReader(strm,Encoding.UTF8,false,4096,false); } public static StreamWriter CreateText(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { Stream strm = fs.Create(path); return new StreamWriter(strm,encoding,4096,false); } public static async Task CreateTextAsync(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { Stream strm = await fs.CreateAsync(path); return new StreamWriter(strm,encoding,4096,false); } public static StreamWriter CreateText(this IVirtualFilesystem fs,UnixPath path) { Stream strm = fs.Create(path); return new StreamWriter(strm,Encoding.UTF8,4096,false); } public static async Task CreateTextAsync(this IVirtualFilesystem fs,UnixPath path) { Stream strm = await fs.CreateAsync(path); return new StreamWriter(strm,Encoding.UTF8,4096,false); } public static void WriteAllText(this IVirtualFilesystem fs,UnixPath path,string text,Encoding encoding) { using(var sw=fs.CreateText(path,encoding)) sw.Write(text); } public static async Task WriteAllTextAsync(this IVirtualFilesystem fs,UnixPath path,string text,Encoding encoding) { using(var sw=await fs.CreateTextAsync(path,encoding)) await sw.WriteAsync(text); } public static void WriteAllText(this IVirtualFilesystem fs,UnixPath path,string text) { using(var sw=fs.CreateText(path,Encoding.UTF8)) sw.Write(text); } public static async Task WriteAllTextAsync(this IVirtualFilesystem fs,UnixPath path,string text) { using(var sw=await fs.CreateTextAsync(path,Encoding.UTF8)) await sw.WriteAsync(text); } public static string ReadAllText(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { using(var sr = fs.OpenText(path,encoding)) return sr.ReadToEnd(); } public static async Task ReadAllTextAsync(this IVirtualFilesystem fs,UnixPath path,Encoding encoding) { using(var sr = await fs.OpenTextAsync(path,encoding)) return await sr.ReadToEndAsync(); } public static string ReadAllText(this IVirtualFilesystem fs,UnixPath path) { using(var sr = fs.OpenText(path,Encoding.UTF8)) return sr.ReadToEnd(); } public static async Task ReadAllTextAsync(this IVirtualFilesystem fs,UnixPath path) { using(var sr = await fs.OpenTextAsync(path,Encoding.UTF8)) return await sr.ReadToEndAsync(); } public static void WriteAllBytes(this IVirtualFilesystem fs,UnixPath path,byte[] data) { using(var s = fs.Create(path)) StreamHelper.WriteAllBytes(s,data); } public static async Task WriteAllBytesAsync(this IVirtualFilesystem fs,UnixPath path,byte[] data,CancellationToken token=default) { using(var strm = await fs.CreateAsync(path,token)) { using(MemoryStream ms = new MemoryStream(data)) await ms.CopyToAsync(strm,4096,token); } } public static byte[] ReadAllBytes(this IVirtualFilesystem fs,UnixPath path) { using(var s = fs.OpenRead(path)) return StreamHelper.ReadAllBytes(s); } public static async Task ReadAllBytesAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token=default) { using(var strm = await fs.OpenReadAsync(path,token)) { MemoryStream ms=new MemoryStream(); await strm.CopyToAsync(ms,4096,token); return ms.ToArray(); } } public static Stream OpenRead(this IVirtualFilesystem fs,UnixPath path) { return fs.Open(path,FileMode.Open,FileAccess.Read,FileShare.Read); } public static async Task OpenReadAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token=default) { return await fs.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token); } public static Stream OpenWrite(this IVirtualFilesystem fs,UnixPath path) { return fs.Open(path,FileMode.OpenOrCreate,FileAccess.Write,FileShare.None); } public static async Task OpenWriteAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token=default) { return await fs.OpenAsync(path,FileMode.OpenOrCreate,FileAccess.Write,FileShare.None,token); } public static Stream Create(this IVirtualFilesystem fs,UnixPath path) { return fs.Open(path,FileMode.Create,FileAccess.ReadWrite,FileShare.None); } public static async Task CreateAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token=default) { return await fs.OpenAsync(path,FileMode.Create,FileAccess.ReadWrite,FileShare.None,token); } public static DirectoryPointer OpenDirectory(this IVirtualFilesystem fs,Environment.SpecialFolder specialFolder) { return new DirectoryPointer(fs,UnixPath.FromSpecialDirectory(specialFolder)); } public static bool MoveFile(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { return srcfs.MoveFile(destfs,path,path,progress,bufferLength); } public static bool MoveDirectory(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { return srcfs.MoveDirectory(destfs,path,path,progress,bufferLength,deleteDir); } public static async Task MoveFileAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { return await srcfs.MoveFileAsync(destfs,path,path,token,progress,bufferLength); } public static async Task MoveDirectoryAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { return await srcfs.MoveDirectoryAsync(destfs,path,path,token,progress,bufferLength,deleteDir); } public static async Task MoveDirectoryAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { DateTime laccess=DateTime.Now; DateTime lwrite = DateTime.Now; try{ laccess = await srcfs.GetLastAccessTimeAsync(source,token); lwrite = await srcfs.GetLastWriteTimeAsync(source,token); if(!await destfs.DirectoryExistsAsync(destination,token)) await destfs.CreateDirectoryAsync(destination,token); await foreach(var directory in srcfs.EnumerateDirectoriesAsync(source,token)) { if(token.IsCancellationRequested) return false; var newPath = destination / directory.Name; var res=await srcfs.MoveDirectoryAsync(destfs,directory,newPath,token,progress,bufferLength); if(!res) return false; if(token.IsCancellationRequested) return false; } await foreach(var file in srcfs.EnumerateFilesAsync(source,token)) { if(token.IsCancellationRequested) return false; var newPath = destination / file.Name; var res=await srcfs.MoveFileAsync(destfs,file,newPath,token,progress,bufferLength); if(!res) return false; if(token.IsCancellationRequested) return false; } }catch(Exception ex) { _=ex; return false; } if(token.IsCancellationRequested) return false; await destfs.SetCreationTimeAsync(destination,DateTime.Now,token); await destfs.SetLastWriteTimeAsync(destination,lwrite,token); await destfs.SetLastAccessTimeAsync(destination,laccess,token); if(deleteDir && !token.IsCancellationRequested) await srcfs.DeleteDirectoryAsync(source,token); return true; } public static async Task MoveFileAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { var success=await srcfs.CopyFileAsync(destfs,source,destination,token,progress,bufferLength); if(!token.IsCancellationRequested && success) await srcfs.DeleteFileAsync(source,token); return success; } public static bool MoveFile(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,IProgress progress=null,int bufferLength=4096) { bool success= srcfs.CopyFile(destfs,source,destination,progress,bufferLength); if(success) srcfs.DeleteFile(source); return success; } public static bool MoveDirectory(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,IProgress progress=null,int bufferLength=4096,bool deleteDir=true) { DateTime laccess = DateTime.Now; DateTime lwrite = DateTime.Now; try{ laccess = srcfs.GetLastAccessTime(source); lwrite = srcfs.GetLastWriteTime(source); if(!destfs.DirectoryExists(destination)) destfs.CreateDirectory(destination); foreach(var directory in srcfs.EnumerateDirectories(source)) { var newPath = destination / directory.Name; srcfs.MoveDirectory(destfs,directory,newPath,progress,bufferLength); } foreach(var file in srcfs.EnumerateFiles(source)) { var newPath = destination / file.Name; srcfs.MoveFile(destfs,file,newPath,progress,bufferLength); } }catch(Exception ex) { _=ex; return false; } destfs.SetCreationTime(destination,DateTime.Now); destfs.SetLastWriteTime(destination,lwrite); destfs.SetLastAccessTime(destination,laccess); if(deleteDir) srcfs.DeleteDirectory(source); return true; } public static UnixPath[] GetFiles(this IVirtualFilesystem fs,UnixPath path) { return fs.EnumerateFiles(path).ToArray(); } public static UnixPath[] GetDirectories(this IVirtualFilesystem fs,UnixPath path) { return fs.EnumerateDirectories(path).ToArray(); } public static async Task GetFilesAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token = default(CancellationToken)) { return await fs.EnumerateFilesAsync(path,token).ToArrayAsync(token); } public static async Task ToArrayAsync(this IAsyncEnumerable enumerator,CancellationToken token=default(CancellationToken)) { List paths=new List(); await foreach(var item in enumerator) { if(token.IsCancellationRequested) return paths.ToArray(); paths.Add(item); } return paths.ToArray(); } public static async Task GetDirectoriesAsync(this IVirtualFilesystem fs,UnixPath path,CancellationToken token=default(CancellationToken)) { return await fs.EnumerateDirectoriesAsync(path).ToArrayAsync(token); } public static async Task DeleteDirectoryAsync(this IVirtualFilesystem fs,UnixPath path,bool recursive,CancellationToken token=default(CancellationToken)) { if(recursive) { await foreach(var dir in fs.EnumerateDirectoriesAsync(path,token)) { if(token.IsCancellationRequested) return; await fs.DeleteDirectoryAsync(dir,recursive,token); } await foreach(var file in fs.EnumerateFilesAsync(path,token)) { if(token.IsCancellationRequested) return; await fs.DeleteFileAsync(file,token); } } } public static void DeleteDirectory(this IVirtualFilesystem fs,UnixPath path,bool recursive) { if(recursive) { foreach(var dir in fs.EnumerateDirectories(path)) { fs.DeleteDirectory(dir,true); } foreach(var file in fs.EnumerateFiles(path)) { fs.DeleteFile(file); } } fs.DeleteDirectory(path); } public static async Task CopyDirectoryAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { return await srcfs.CopyDirectoryAsync(destfs,path,path,token,progress,bufferLength); } public static async Task CopyDirectoryAsync(this IVirtualFilesystem srcfs,UnixPath srcPath,UnixPath destPath,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { return await srcfs.CopyDirectoryAsync(srcfs,srcPath,destPath,token,progress,bufferLength); } public static async Task CopyFileAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { return await srcfs.CopyFileAsync(destfs,path,path,token,progress,bufferLength); } public static async Task CopyFileAsync(this IVirtualFilesystem srcfs,UnixPath source,UnixPath destination,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { return await srcfs.CopyFileAsync(srcfs,source,destination,token,progress,bufferLength); } public static async Task CopyDirectoryAsync(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,CancellationToken token = default(CancellationToken),IProgress progress=null,int bufferLength=4096) { DateTime laccess=DateTime.Now; try{ laccess = await srcfs.GetLastAccessTimeAsync(source); if(!await destfs.DirectoryExistsAsync(destination,token)) await destfs.CreateDirectoryAsync(destination,token); await foreach(var directory in srcfs.EnumerateDirectoriesAsync(source,token)) { if(token.IsCancellationRequested) return false; var newPath = destination / directory.Name; var res= await srcfs.CopyDirectoryAsync(destfs,directory,newPath,token,progress,bufferLength); if(!res) return false; } await foreach(var file in srcfs.EnumerateFilesAsync(source,token)) { if(token.IsCancellationRequested) return false; var newPath = destination / file.Name; var res= await srcfs.CopyFileAsync(destfs,file,newPath,token,progress,bufferLength); if(!res) return false; } }catch(Exception ex) { _=ex; return false; } await destfs.SetCreationTimeAsync(destination,DateTime.Now,token); await destfs.SetLastWriteTimeAsync(destination,await srcfs.GetLastWriteTimeAsync(source,token),token); await destfs.SetLastAccessTimeAsync(destination,laccess,token); try { await srcfs.SetLastAccessTimeAsync(source,DateTime.Now,token); }catch(Exception ex) { _=ex; } return true; } public static async Task CopyFileAsync(this IVirtualFilesystem srcfs, IVirtualFilesystem destfs,UnixPath source,UnixPath destination,CancellationToken token=default(CancellationToken),IProgress progress=null,int bufferLength=4096) { DateTime laccess=DateTime.Now; try{ laccess= await srcfs.GetLastAccessTimeAsync(source,token); if(await destfs.DirectoryExistsAsync(destination,token)) destination /= source.Name; using(var src = await srcfs.OpenAsync(source,FileMode.Open,FileAccess.Read,FileShare.Read,token)) { using(var dest = await destfs.OpenAsync(destination,FileMode.OpenOrCreate,FileAccess.Write,FileShare.None,token)) { byte[] buffer = new byte[bufferLength]; int read=0; long readTotal=0; long length = src.Length; do { if(token.IsCancellationRequested) return false; read = await src.ReadAsync(buffer,0,buffer.Length,token); readTotal += read; if(token.IsCancellationRequested) return false; await dest.WriteAsync(buffer,0,read,token); if(token.IsCancellationRequested) return false; progress?.Report(new TransferProgress(readTotal,length,source,destination)); } while(read != 0); } } }catch(Exception ex) { _=ex; return false; } await destfs.SetCreationTimeAsync(destination,DateTime.Now,token); await destfs.SetLastWriteTimeAsync(destination,await srcfs.GetLastWriteTimeAsync(source,token),token); await destfs.SetLastAccessTimeAsync(destination,laccess,token); try { await srcfs.SetLastAccessTimeAsync(source,DateTime.Now,token); }catch(Exception ex) { _=ex; } return true; } public static bool CopyFile(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath path,IProgress progress=null,int bufferLength=4096) { return srcfs.CopyFile(destfs,path,path,progress,bufferLength); } public static bool CopyFile(this IVirtualFilesystem srcfs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,IProgress progress=null,int bufferLength=4096) { DateTime laccess=DateTime.Now; try{ laccess = srcfs.GetLastAccessTime(source); if(destfs.DirectoryExists(destination)) destination /= source.Name; using(var src = srcfs.Open(source,FileMode.Open,FileAccess.Read,FileShare.Read)) { using(var dest = destfs.Open(destination,FileMode.OpenOrCreate,FileAccess.Write,FileShare.None)) { byte[] buffer = new byte[bufferLength]; int read=0; long readTotal=0; long length = src.Length; do { read = src.Read(buffer,0,buffer.Length); readTotal += read; dest.Write(buffer,0,read); progress?.Report(new TransferProgress(readTotal,length,source,destination)); } while(read != 0); } } }catch(Exception ex) { _=ex; return false; } destfs.SetCreationTime(destination,DateTime.Now); destfs.SetLastWriteTime(destination,srcfs.GetLastWriteTime(source)); destfs.SetLastAccessTime(destination,laccess); try { srcfs.SetLastAccessTime(source,DateTime.Now); }catch(Exception ex) { _=ex; } return true; } public static bool CopyFile(this IVirtualFilesystem fs,UnixPath source,UnixPath dest,IProgress progress = null,int bufferLength=4096) { return fs.CopyFile(fs,source,dest,progress,bufferLength); } public static bool CopyDirectory(this IVirtualFilesystem fs,IVirtualFilesystem destfs,UnixPath path,IProgress progress=null,int bufferLength=4096) { return fs.CopyDirectory(destfs,path,path,progress,bufferLength); } public static bool CopyDirectory(this IVirtualFilesystem fs,IVirtualFilesystem destfs,UnixPath source,UnixPath destination,IProgress progress=null,int bufferLength=4096) { DateTime laccess=DateTime.Now; try{ laccess = fs.GetLastAccessTime(source); if(!destfs.DirectoryExists(destination)) destfs.CreateDirectory(destination); foreach(var directory in fs.EnumerateDirectories(source)) { var newPath = destination / directory.Name; fs.CopyDirectory(destfs,directory,newPath,progress,bufferLength); } foreach(var file in fs.EnumerateFiles(source)) { var newPath = destination / file.Name; fs.CopyFile(destfs,file,newPath,progress,bufferLength); } }catch(Exception ex) { _=ex; return false; } destfs.SetCreationTime(destination,DateTime.Now); destfs.SetLastWriteTime(destination,fs.GetLastWriteTime(source)); destfs.SetLastAccessTime(destination,laccess); try { fs.SetLastAccessTime(source,DateTime.Now); }catch(Exception ex) { _=ex; } return true; } public static bool CopyDirectory(this IVirtualFilesystem fs,UnixPath source,UnixPath destination,IProgress progress=null,int bufferLength = 4096) { return fs.CopyDirectory(fs,source,destination,progress,bufferLength); } public static double DivideByWith2Digits(long value,long divBy) { return Math.Round((double)value / (double)divBy,2); } public static double DivideByPowWith2Digits(long value,int unitSz,int pow) { return DivideByWith2Digits(value,(long)Math.Pow(unitSz,pow)); } public static string ToDataUnits(this long val,bool binary=true) { int unitSz = binary ? 1024 : 1000; string bytesSuffix = binary ? "iB" : "B"; if(val < unitSz) { return $"{val} B"; } else if(val < Math.Pow(unitSz,2)) { return $"{DivideByPowWith2Digits(val,unitSz,1)} k{bytesSuffix}"; } else if(val < Math.Pow(unitSz,3)) { return $"{DivideByPowWith2Digits(val,unitSz,2)} M{bytesSuffix}"; } else if(val < Math.Pow(unitSz,4)) { return $"{DivideByPowWith2Digits(val,unitSz,3)} G{bytesSuffix}"; } return $"{DivideByPowWith2Digits(val,unitSz,4)} T{bytesSuffix}"; } } public class TransferProgress { public bool HasFiles {get;private set;} public UnixPath DestinationPath {get;private set;} public UnixPath SourcePath {get;private set;} public long CopiedBytes {get;private set;} public long RemainingBytes {get{return Length - CopiedBytes;}} public long Length {get;private set;} public double ProgressRaw {get;private set;} public int Progress {get{return (int)(Math.Floor((double)(Progress * 100)) % 101);}} public TransferProgress(long offset,long length,UnixPath src,UnixPath dest) { SourcePath = src; DestinationPath = dest; HasFiles = true; CopiedBytes = offset; Length = length; if(length != 0) { ProgressRaw = offset / length; }else{ ProgressRaw=0; } } public TransferProgress(long offset,long length) : this(offset,length,new UnixPath(),new UnixPath()) { HasFiles=false; } public override string ToString() { return ToString(true); } public string ToString(bool binary) { return $"{Progress}% {CopiedBytes.ToDataUnits(binary)}/{Length.ToDataUnits(binary)}"; } } }