using System; using System.Linq; using System.IO; using System.Collections.Generic; namespace Tesses.Http.VFSCollection { public static class Extensions { public static void ExtractTessesArchive(this VirtualStorage storage,Stream strm,bool ownStream) { using(TArchiveReader reader=new TArchiveReader(strm,ownStream)) { reader.ExtractEverything((t,n,len)=>{ if(t == EntryType.File) { string pardir = Path.GetDirectoryName(n.TrimStart('.').TrimStart('/')); storage.CreateDirectory(pardir); return storage.Open(pardir,FileMode.Create,FileAccess.Write,FileShare.None); } if(t == EntryType.Directory) { storage.CreateDirectory(n.TrimStart('.').TrimStart('/')); } return Stream.Null; }); } } } public enum EntryType : byte { File=0b00000000, Directory=0b00000001, Symlink=0b00000010 } public class TArchiveReader : IDisposable { public delegate Stream ExtractEverythingDelegate(EntryType type,string name,long entLen); public delegate void DoneWriting(Stream strm,string name); public delegate void Symlink(string linkPath,string linkDest); Stream strm; bool own; public TArchiveReader(Stream strm,bool ownStream=false) { this.strm=strm; own=ownStream; } public TArchiveReader(string filename) { this.strm = File.OpenRead(filename); own =true; } private void ReadLittleEndian(byte[] arrayToFill) { strm.Read(arrayToFill,0,arrayToFill.Length); if(!BitConverter.IsLittleEndian) { Array.Reverse(arrayToFill); } } private bool ReadEntryAttributes(out EntryType type, out string name,out long len) { // strm.WriteByte((byte)type); //WriteString(name); //WriteLittleEndian(BitConverter.GetBytes(len)); int t=strm.ReadByte(); if(t == -1) { type=EntryType.File; name="ENDOFSTREAM.BIN"; len=0; return false; } type=(EntryType)t; name=ReadString(); byte[] len0 = new byte[8]; ReadLittleEndian(len0); len=BitConverter.ToInt64(len0,0); return true; } private string ReadString() { byte[] len = new byte[4]; ReadLittleEndian(len); byte[] strdat=new byte[BitConverter.ToInt32(len,0)]; strm.Read(strdat,0,strdat.Length); return System.Text.Encoding.UTF8.GetString(strdat); } private void ReadAndDiscard(long l) { if(strm.CanSeek) { strm.Seek(l,SeekOrigin.Current); }else{ _copyTo(Stream.Null,l); } } private void _copyTo(Stream strm,long len) { byte[] buff=new byte[1024]; long pos=0; int read=0; do{ read=(int)Math.Min(1024,len-pos); if(read == 0) return; read=this.strm.Read(buff,0,read); strm.Write(buff,0,read); pos+=read; }while(read != 0); } public void ExtractStartingWith(string archivePath,string outputDir,Symlink symlink=null) { Dictionary> _f=new Dictionary>(); ExtractEverything((type,name,len)=>{ if(name.StartsWith(archivePath)){ switch(type) { case EntryType.File: string fname=Path.Combine(outputDir,name); string dirname = Path.GetDirectoryName(fname); if(!string.IsNullOrWhiteSpace(dirname)) { Directory.CreateDirectory(dirname); } return File.Create(fname); case EntryType.Directory: string fname0=Path.Combine(outputDir,name); Directory.CreateDirectory(fname0); break; } } return Stream.Null; },(strm0,name)=>{strm0.Dispose();},symlink); } public void Extract(string archivePath,Stream strm) { bool hasGone=false;; ExtractEverything((type,name,len)=>{ if(hasGone) return null; if(type == EntryType.File && name==archivePath){ hasGone=true; return strm; } return Stream.Null; }); } public void ExtractEverything(string outputDir,Symlink symlink=null) { Dictionary> _f=new Dictionary>(); ExtractEverything((type,name,len)=>{ switch(type) { case EntryType.File: string fname=Path.Combine(outputDir,name); string dirname = Path.GetDirectoryName(fname); if(!string.IsNullOrWhiteSpace(dirname)) { Directory.CreateDirectory(dirname); } return File.Create(fname); case EntryType.Directory: string fname0=Path.Combine(outputDir,name); Directory.CreateDirectory(fname0); break; } return Stream.Null; },(strm0,name)=>{strm0.Dispose();},symlink); } public void ExtractEverything(ExtractEverythingDelegate del,DoneWriting doneWriting=null,Symlink symlink=null) { if(del == null) return; if(hasRead && strm.CanSeek) { strm.Seek(0,SeekOrigin.Begin); } if(hasRead && !strm.CanSeek) { throw new Exception("Can't Seek Stream"); }else{ bool read=false; do{ EntryType type; string name; long len; read=ReadEntryAttributes(out type,out name,out len); if(read) { if(type == EntryType.Symlink) { MemoryStream strm=new MemoryStream(); _copyTo(strm,len); using(StreamReader rdr=new StreamReader(strm)) { string res= rdr.ReadToEnd(); symlink?.Invoke(name,res); } }else{ Stream strm=del(type,name,len); if(strm == Stream.Null) { if(len>0) ReadAndDiscard(len); } else if(strm != null) { _copyTo(strm,len); if(doneWriting != null) { doneWriting(strm,name); }else{ strm.Close(); } }else{ hasRead=true; return; } }} }while(read); hasRead=true; } } bool hasRead=false; public void Dispose() { if(own) strm.Dispose(); } } public class MemoryStorage : VirtualStorage { protected override void DeleteEmptyDirectory(string dir) { string[] _p=dir.Split('/'); if(_p.Length < 1) return; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { if(!(dir0.Entries[_p[_p.Length-1]].IsFile)) dir0.Entries.Remove(_p[_p.Length-1]); } } public override string ActualPath => "mem:///"; public override void SetCreationTime(string filename, DateTime time) { string[] _p=filename.Split('/'); if(_p.Length < 1) return; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { dir0.Entries[_p[_p.Length]].Created=time; } } public override void SetLastAccessTime(string filename, DateTime time) { string[] _p=filename.Split('/'); if(_p.Length < 1) return; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { dir0.Entries[_p[_p.Length]].LastAccess=time; } } public override void SetLastWriteTime(string filename, DateTime time) { string[] _p=filename.Split('/'); if(_p.Length < 1) return; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { dir0.Entries[_p[_p.Length]].LastWrite=time; } } public override DateTime GetCreationTime(string filename) { string[] _p=filename.Split('/'); if(_p.Length < 1) return DateTime.Now; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { return dir0.Entries[_p[_p.Length]].Created; }else{ return DateTime.Now; } } public override DateTime GetLastWriteTime(string filename) { string[] _p=filename.Split('/'); if(_p.Length < 1) return DateTime.Now; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { return dir0.Entries[_p[_p.Length]].LastWrite; }else{ return DateTime.Now; } } public override DateTime GetLastAccessTime(string filename) { string[] _p=filename.Split('/'); if(_p.Length < 1) return DateTime.Now; List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p.Take(_p.Length -1)) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir0.Entries.ContainsKey(_p[_p.Length-1])) { return dir0.Entries[_p[_p.Length]].LastAccess; }else{ return DateTime.Now; } } private abstract class MemoryEntry { public abstract bool IsFile {get;} public DateTime Created {get;set;} public DateTime LastWrite {get;set;} public DateTime LastAccess {get;set;} } private class MemoryFile : MemoryEntry { public MemoryFile() { LastAccess=DateTime.Now; LastWrite=DateTime.Now; Created=DateTime.Now; } public bool HasWriteAccess=false; public override bool IsFile => true; public byte[] Data=new byte[0]; public void Overwrite(byte[] data) { lock(this) { Data=data; LastWrite = DateTime.Now; } } } private class MemoryStream2 : MemoryStream { MemoryFile _f; bool _canWrite; public MemoryStream2(MemoryFile file,bool canWrite) : base(file.Data.ToArray(),canWrite) //copies data into file { file.LastAccess = DateTime.Now; _f=file; _canWrite=canWrite; if(file.HasWriteAccess && _canWrite) { throw new IOException("MemoryStorage doesn't support simulatious writers"); } if(!file.HasWriteAccess) file.HasWriteAccess=_canWrite; } public override void Close() { if(_canWrite) { _f.Overwrite(this.ToArray()); _f.HasWriteAccess=false; } base.Close(); } } private class MemoryDirectory : MemoryEntry { public MemoryDirectory() { Created = DateTime.Now; LastWrite=DateTime.Now; LastAccess=DateTime.Now; Entries=new Dictionary(); } public override bool IsFile => false; public Dictionary Entries {get;set;} } MemoryDirectory root=new MemoryDirectory(); public static MemoryStorage FromTessesArchive(Stream strm,bool ownStream) { MemoryStorage storage=new MemoryStorage(); storage.ExtractTessesArchive(strm,ownStream); //public delegate Stream ExtractEverythingDelegate(EntryType type,string name,long entLen); return storage; } private MemoryDirectory _create(MemoryDirectory dir,string p) { if(!dir.Entries.ContainsKey(p)) { dir.Entries.Add(p,new MemoryDirectory()); } var dir2= dir.Entries[p] as MemoryDirectory; return dir2; } private MemoryEntry _get(MemoryDirectory dir,string p) { if(!dir.Entries.ContainsKey(p)) { return null; } return dir.Entries[p]; } public override void CreateDirectory(string path) { string[] _p=path.Split('/'); var dir = root; List __c=new List(); foreach(var item in _p) { dir= _create(root,item); __c.Add(item); if(dir == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } } public override void Delete(string file) { string[] _p=file.Split('/'); if(_p.Length < 1) return; List __c=new List(); MemoryDirectory dir=root; foreach(var item in _p.Take(_p.Length -1)) { dir=_get(dir,item) as MemoryDirectory; __c.Add(item); if(dir == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir.Entries.ContainsKey(_p[_p.Length-1])) { if(dir.Entries[_p[_p.Length-1]].IsFile) dir.Entries.Remove(_p[_p.Length-1]); } } public override bool DirectoryExists(string filename) { string[] _p=filename.Split('/'); if(_p.Length < 1) return false; List __c=new List(); MemoryDirectory dir=root; foreach(var item in _p.Take(_p.Length -1)) { dir=_get(dir,item) as MemoryDirectory; __c.Add(item); if(dir == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir.Entries.ContainsKey(_p[_p.Length-1])) { return !(dir.Entries[_p[_p.Length-1]].IsFile); } return false; } public override IEnumerable EnumerateDirectories(string dir) { string[] _p=dir.Split('/'); List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } foreach(var items in dir0.Entries) { if(!items.Value.IsFile) yield return items.Key; } } public override IEnumerable EnumerateFiles(string dir) { string[] _p=dir.Split('/'); List __c=new List(); MemoryDirectory dir0=root; foreach(var item in _p) { dir0=_get(dir0,item) as MemoryDirectory; __c.Add(item); if(dir0 == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } foreach(var items in dir0.Entries) { if(items.Value.IsFile) yield return items.Key; } } public override bool FileExists(string filename) { string[] _p=filename.Split('/'); if(_p.Length < 1) return false; List __c=new List(); MemoryDirectory dir=root; foreach(var item in _p.Take(_p.Length -1)) { dir=_get(dir,item) as MemoryDirectory; __c.Add(item); if(dir == null) { throw new Exception($"The path {PathCombine(__c)} is a file, should be a directory"); } } if(dir.Entries.ContainsKey(_p[_p.Length-1])) { return (dir.Entries[_p[_p.Length-1]].IsFile); } return false; } public override Stream Open(string file, FileMode mode, FileAccess access, FileShare share) { string[] _p=file.Split('/'); if(_p.Length < 1) throw new IOException("Path empty"); List __c=new List(); MemoryDirectory dir=root; foreach(var item in _p.Take(_p.Length -1)) { dir=_get(dir,item) as MemoryDirectory; __c.Add(item); if(dir == null) { throw new IOException($"The path {PathCombine(__c)} is a file, should be a directory"); } } //we will now try to get the file string filename=_p[_p.Length -1]; if(dir.Entries.ContainsKey(filename)) { if(!dir.Entries[filename].IsFile) throw new IOException("Not a file"); //the file exists if(mode == FileMode.CreateNew) throw new IOException("File Exists"); var _file= dir.Entries[filename] as MemoryFile; lock(_file) { if(mode == FileMode.Create) { _file.Data=new byte[0]; } //we need to open file MemoryStream2 strm=new MemoryStream2(_file,access != FileAccess.Read); if(mode != FileMode.Truncate) { strm.Position=0; }else{ strm.Position=strm.Length; } return strm; } }else{ if(mode == FileMode.Open) throw new IOException("File Doesn't Exist"); MemoryFile _file=new MemoryFile(); dir.Entries.Add(filename,_file); lock(_file) { MemoryStream2 strm=new MemoryStream2(_file,access != FileAccess.Read); return strm; } //the file doesnt exist } } } public class SSHStorage : VirtualStorage { public override void CreateDirectory(string path) { throw new NotImplementedException(); } public override void Delete(string file) { throw new NotImplementedException(); } public override bool DirectoryExists(string filename) { throw new NotImplementedException(); } public override IEnumerable EnumerateDirectories(string dir) { throw new NotImplementedException(); } public override IEnumerable EnumerateFiles(string dir) { throw new NotImplementedException(); } public override bool FileExists(string filename) { throw new NotImplementedException(); } public override Stream Open(string file, FileMode mode, FileAccess access, FileShare share) { throw new NotImplementedException(); } } }