| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 | /******************************************************************************* copyright: Copyright (c) 2007 Kris Bell. All rights reserved license: BSD style: $(LICENSE) version: Oct 2007: Initial version author: Kris *******************************************************************************/ module tango.io.vfs.FileFolder; private import tango.io.device.File; private import Path = tango.io.Path; private import tango.core.Exception; public import tango.io.vfs.model.Vfs; private import tango.io.model.IConduit; private import tango.time.Time : Time; /******************************************************************************* Represents a physical folder in a file system. Use one of these to address specific paths (sub-trees) within the file system. *******************************************************************************/ class FileFolder : VfsFolder { private char[] path; private VfsStats stats; /*********************************************************************** Create a file folder with the given path. Option 'create' will create the path when set true, or reference an existing path otherwise. ***********************************************************************/ this (char[] path, bool create=false) { this.path = open (Path.standard(path.dup), create); } /*********************************************************************** Create a FileFolder as a Group member. ***********************************************************************/ private this (char[] path, char[] name) { this.path = Path.join (path, name); } /*********************************************************************** Explicitly create() or open() a named folder. ***********************************************************************/ private this (FileFolder parent, char[] name, bool create=false) { assert (parent); this.path = open (Path.join(parent.path, name), create); } /*********************************************************************** Return a short name. ***********************************************************************/ final char[] name () { return Path.parse(path).name; } /*********************************************************************** Return a long name. ***********************************************************************/ final char[] toString () { return path; } /*********************************************************************** A folder is being added or removed from the hierarchy. Use this to test for validity (or whatever) and throw exceptions as necessary Here we test for folder overlap, and bail-out when found. ***********************************************************************/ final void verify (VfsFolder folder, bool mounting) { if (mounting && cast(FileFolder) folder) { auto src = Path.FS.padded (this.toString); auto dst = Path.FS.padded (folder.toString); auto len = src.length; if (len > dst.length) len = dst.length; if (src[0..len] == dst[0..len]) error ("folders '"~dst~"' and '"~src~"' overlap"); } } /*********************************************************************** Return a contained file representation. ***********************************************************************/ final VfsFile file (char[] name) { return new FileHost (Path.join (path, name)); } /*********************************************************************** Return a contained folder representation. ***********************************************************************/ final VfsFolderEntry folder (char[] path) { return new FolderHost (this, path); } /*********************************************************************** Remove the folder subtree. Use with care! ***********************************************************************/ final VfsFolder clear () { Path.remove (Path.collate(path, "*", true)); return this; } /*********************************************************************** Is folder writable? ***********************************************************************/ final bool writable () { return Path.isWritable (path); } /*********************************************************************** Returns content information about this folder. ***********************************************************************/ final VfsFolders self () { return new FolderGroup (this, false); } /*********************************************************************** Returns a subtree of folders matching the given name. ***********************************************************************/ final VfsFolders tree () { return new FolderGroup (this, true); } /*********************************************************************** Iterate over the set of immediate child folders. This is useful for reflecting the hierarchy. ***********************************************************************/ final int opApply (int delegate(ref VfsFolder) dg) { int result; foreach (folder; folders(true)) { VfsFolder x = folder; if ((result = dg(x)) != 0) break; } return result; } /*********************************************************************** Close and/or synchronize changes made to this folder. Each driver should take advantage of this as appropriate, perhaps combining multiple files together, or possibly copying to a remote location. ***********************************************************************/ VfsFolder close (bool commit = true) { return this; } /*********************************************************************** Sweep owned folders. ***********************************************************************/ private FileFolder[] folders (bool collect) { FileFolder[] folders; stats = stats.init; foreach (info; Path.children (path)) if (info.folder) { if (collect) folders ~= new FileFolder (info.path, info.name); ++stats.folders; } else { stats.bytes += info.bytes; ++stats.files; } return folders; } /*********************************************************************** Sweep owned files. ***********************************************************************/ private char[][] files (ref VfsStats stats, VfsFilter filter = null) { char[][] files; foreach (info; Path.children (path)) if (info.folder is false) if (filter is null || filter(&info)) { files ~= Path.join (info.path, info.name); stats.bytes += info.bytes; ++stats.files; } return files; } /*********************************************************************** Throw an exception. ***********************************************************************/ private char[] error (char[] msg) { throw new VfsException (msg); } /*********************************************************************** Create or open the given path, and detect path errors. ***********************************************************************/ private char[] open (char[] path, bool create) { if (Path.exists (path)) { if (! Path.isFolder (path)) error ("FileFolder.open :: path exists but not as a folder: "~path); } else if (create) Path.createPath (path); else error ("FileFolder.open :: path does not exist: "~path); return path; } } /******************************************************************************* Represents a group of files (need this declared here to avoid a bunch of bizarre compiler warnings.) *******************************************************************************/ class FileGroup : VfsFiles { private char[][] group; // set of filtered filenames private char[][] hosts; // set of containing folders private VfsStats stats; // stats for contained files /*********************************************************************** ***********************************************************************/ this (FolderGroup host, VfsFilter filter) { foreach (folder; host.members) { auto files = folder.files (stats, filter); if (files.length) { group ~= files; //hosts ~= folder.toString; } } } /*********************************************************************** Iterate over the set of contained VfsFile instances. ***********************************************************************/ final int opApply (int delegate(ref VfsFile) dg) { int result; auto host = new FileHost; foreach (file; group) { VfsFile x = host; host.path.parse (file); if ((result = dg(x)) != 0) break; } return result; } /*********************************************************************** Return the total number of entries. ***********************************************************************/ final uint files () { return group.length; } /*********************************************************************** Return the total size of all files. ***********************************************************************/ final ulong bytes () { return stats.bytes; } } /******************************************************************************* A set of folders representing a selection. This is where file selection is made, and pattern-matched folder subsets can be extracted. You need one of these to expose statistics (such as file or folder count) of a selected folder group. *******************************************************************************/ private class FolderGroup : VfsFolders { private FileFolder[] members; // folders in group /*********************************************************************** Create a subset group. ***********************************************************************/ private this () {} /*********************************************************************** Create a folder group including the provided folder and (optionally) all child folders. ***********************************************************************/ private this (FileFolder root, bool recurse) { members = root ~ scan (root, recurse); } /*********************************************************************** Iterate over the set of contained VfsFolder instances. ***********************************************************************/ final int opApply (int delegate(ref VfsFolder) dg) { int result; foreach (folder; members) { VfsFolder x = folder; if ((result = dg(x)) != 0) break; } return result; } /*********************************************************************** Return the number of files in this group. ***********************************************************************/ final uint files () { uint files; foreach (folder; members) files += folder.stats.files; return files; } /*********************************************************************** Return the total size of all files in this group. ***********************************************************************/ final ulong bytes () { ulong bytes; foreach (folder; members) bytes += folder.stats.bytes; return bytes; } /*********************************************************************** Return the number of folders in this group. ***********************************************************************/ final uint folders () { if (members.length is 1) return members[0].stats.folders; return members.length; } /*********************************************************************** Return the total number of entries in this group. ***********************************************************************/ final uint entries () { return files + folders; } /*********************************************************************** Return a subset of folders matching the given pattern. ***********************************************************************/ final VfsFolders subset (char[] pattern) { Path.PathParser parser; auto set = new FolderGroup; foreach (folder; members) if (Path.patternMatch (parser.parse(folder.path).name, pattern)) set.members ~= folder; return set; } /*********************************************************************** Return a set of files matching the given pattern. ***********************************************************************/ final FileGroup catalog (char[] pattern) { bool foo (VfsInfo info) { return Path.patternMatch (info.name, pattern); } return catalog (&foo); } /*********************************************************************** Returns a set of files conforming to the given filter. ***********************************************************************/ final FileGroup catalog (VfsFilter filter = null) { return new FileGroup (this, filter); } /*********************************************************************** Internal routine to traverse the folder tree. ***********************************************************************/ private final FileFolder[] scan (FileFolder root, bool recurse) { auto folders = root.folders (recurse); if (recurse) foreach (child; folders) folders ~= scan (child, recurse); return folders; } } /******************************************************************************* A host for folders, currently used to harbor create() and open() methods only. *******************************************************************************/ private class FolderHost : VfsFolderEntry { private char[] path; private FileFolder parent; /*********************************************************************** ***********************************************************************/ private this (FileFolder parent, char[] path) { this.path = path; this.parent = parent; } /*********************************************************************** ***********************************************************************/ final VfsFolder create () { return new FileFolder (parent, path, true); } /*********************************************************************** ***********************************************************************/ final VfsFolder open () { return new FileFolder (parent, path, false); } /*********************************************************************** Test to see if a folder exists. ***********************************************************************/ bool exists () { try { open(); return true; } catch (IOException x) {} return false; } } /******************************************************************************* Represents things you can do with a file. *******************************************************************************/ private class FileHost : VfsFile { private Path.PathParser path; /*********************************************************************** ***********************************************************************/ this (char[] path = null) { this.path.parse (path); } /*********************************************************************** Return a short name. ***********************************************************************/ final char[] name() { return path.file; } /*********************************************************************** Return a long name. ***********************************************************************/ final char[] toString () { return path.toString; } /*********************************************************************** Does this file exist? ***********************************************************************/ final bool exists() { return Path.exists (path.toString); } /*********************************************************************** Return the file size. ***********************************************************************/ final ulong size() { return Path.fileSize(path.toString); } /*********************************************************************** Create a new file instance. ***********************************************************************/ final VfsFile create () { Path.createFile(path.toString); return this; } /*********************************************************************** Create a new file instance and populate with stream. ***********************************************************************/ final VfsFile create (InputStream input) { create.output.copy(input).close; return this; } /*********************************************************************** Create and copy the given source. ***********************************************************************/ VfsFile copy (VfsFile source) { auto input = source.input; scope (exit) input.close; return create (input); } /*********************************************************************** Create and copy the given source, and remove the source. ***********************************************************************/ final VfsFile move (VfsFile source) { copy (source); source.remove; return this; } /*********************************************************************** Return the input stream. Don't forget to close it. ***********************************************************************/ final InputStream input () { return new File (path.toString); } /*********************************************************************** Return the output stream. Don't forget to close it. ***********************************************************************/ final OutputStream output () { return new File (path.toString, File.WriteExisting); } /*********************************************************************** Remove this file. ***********************************************************************/ final VfsFile remove () { Path.remove (path.toString); return this; } /*********************************************************************** Duplicate this entry. ***********************************************************************/ final VfsFile dup() { auto ret = new FileHost; ret.path = path.dup; return ret; } /*********************************************************************** Modified time of the file. ***********************************************************************/ final Time modified () { return Path.timeStamps(path.toString).modified; } } debug (FileFolder) { /******************************************************************************* *******************************************************************************/ import tango.io.Stdout; import tango.io.device.Array; void main() { auto root = new FileFolder ("d:/d/import/temp", true); root.folder("test").create; root.file("test.txt").create(new Array("hello")); Stdout.formatln ("test.txt.length = {}", root.file("test.txt").size); root = new FileFolder ("c:/"); auto set = root.self; Stdout.formatln ("self.files = {}", set.files); Stdout.formatln ("self.bytes = {}", set.bytes); Stdout.formatln ("self.folders = {}", set.folders); Stdout.formatln ("self.entries = {}", set.entries); /+ set = root.tree; Stdout.formatln ("tree.files = {}", set.files); Stdout.formatln ("tree.bytes = {}", set.bytes); Stdout.formatln ("tree.folders = {}", set.folders); Stdout.formatln ("tree.entries = {}", set.entries); //foreach (folder; set) //Stdout.formatln ("tree.folder '{}' has {} files", folder.name, folder.self.files); auto cat = set.catalog ("s*"); Stdout.formatln ("cat.files = {}", cat.files); Stdout.formatln ("cat.bytes = {}", cat.bytes); +/ //foreach (file; cat) // Stdout.formatln ("cat.name '{}' '{}'", file.name, file.toString); } } |