| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 | /******************************************************************************* 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 const(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 (const(char)[] path, bool create=false) { this.path = open (Path.standard(path.dup), create); } /*********************************************************************** create a FileFolder as a Group member ***********************************************************************/ private this (const(char)[] path, const(char)[] name) { this.path = Path.join (path, name); } /*********************************************************************** explicitly create() or open() a named folder ***********************************************************************/ private this (FileFolder parent, const(char)[] name, bool create=false) { assert (parent); this.path = open (Path.join(parent.path, name), create); } /*********************************************************************** Return a short name ***********************************************************************/ @property final const(char)[] name () { return Path.parse(path).name; } /*********************************************************************** Return a long name ***********************************************************************/ override final string toString () { return path.idup; } /*********************************************************************** 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.idup~"' and '"~src.idup~"' overlap"); } } /*********************************************************************** Return a contained file representation ***********************************************************************/ @property final VfsFile file (const(char)[] name) { return new FileHost (Path.join (path, name)); } /*********************************************************************** Return a contained folder representation ***********************************************************************/ @property final VfsFolderEntry folder (const(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? ***********************************************************************/ @property final bool writable () { return Path.isWritable (path); } /*********************************************************************** Returns content information about this folder ***********************************************************************/ @property final VfsFolders self () { return new FolderGroup (this, false); } /*********************************************************************** Returns a subtree of folders matching the given name ***********************************************************************/ @property 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 (scope 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 ***********************************************************************/ @property 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 const(char)[] error (string msg) { throw new VfsException (msg); } /*********************************************************************** Create or open the given path, and detect path errors ***********************************************************************/ private const(char)[] open (const(char)[] path, bool create) { if (Path.exists (path)) { if (! Path.isFolder (path)) error ("FileFolder.open :: path exists but not as a folder: "~path.idup); } else if (create) Path.createPath (path); else error ("FileFolder.open :: path does not exist: "~path.idup); return path; } } /******************************************************************************* Represents a group of files (need this declared here to avoid a bunch of bizarre compiler warnings) *******************************************************************************/ class FileGroup : VfsFiles { private const(char)[][] group; // set of filtered filenames private const(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 (scope int delegate(ref VfsFile) dg) { int result; auto host = new FileHost; foreach (file; group) { VfsFile x = host; host.path = Path.parse(file); if ((result = dg(x)) != 0) break; } return result; } /*********************************************************************** Return the total number of entries ***********************************************************************/ @property final size_t files () { return group.length; } /*********************************************************************** Return the total size of all files ***********************************************************************/ @property 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 (scope 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 ***********************************************************************/ @property final size_t files () { size_t files; foreach (folder; members) files += folder.stats.files; return files; } /*********************************************************************** Return the total size of all files in this group ***********************************************************************/ @property final ulong bytes () { ulong bytes; foreach (folder; members) bytes += folder.stats.bytes; return bytes; } /*********************************************************************** Return the number of folders in this group ***********************************************************************/ @property final size_t folders () { if (members.length is 1) return members[0].stats.folders; return members.length; } /*********************************************************************** Return the total number of entries in this group ***********************************************************************/ @property final size_t entries () { return files + folders; } /*********************************************************************** Return a subset of folders matching the given pattern ***********************************************************************/ final VfsFolders subset (const(char)[] pattern) { auto set = new FolderGroup; foreach (folder; members) if (Path.patternMatch (Path.PathParser!(const(char))(folder.path).name, pattern)) set.members ~= folder; return set; } /*********************************************************************** Return a set of files matching the given pattern ***********************************************************************/ @property final FileGroup catalog (const(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 ***********************************************************************/ @property 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 const(char)[] path; private FileFolder parent; /*********************************************************************** ***********************************************************************/ private this (FileFolder parent, const(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 ***********************************************************************/ @property 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!(const(char)) path; /*********************************************************************** ***********************************************************************/ this (const(char)[] path = null) { this.path = Path.PathParser!(const(char))(path); } /*********************************************************************** Return a short name ***********************************************************************/ @property final const(char)[] name() { return path.file; } /*********************************************************************** Return a long name ***********************************************************************/ override final string toString () { return path.toString().idup; } /*********************************************************************** Does this file exist? ***********************************************************************/ @property final bool exists() { return Path.exists (path.toString()); } /*********************************************************************** Return the file size ***********************************************************************/ @property 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 ***********************************************************************/ @property final InputStream input () { return new File (path.toString()); } /*********************************************************************** Return the output stream. Don't forget to close it ***********************************************************************/ @property 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 ***********************************************************************/ @property final VfsFile dup() { auto ret = new FileHost; ret.path = path.dup; return ret; } /*********************************************************************** Modified time of the file ***********************************************************************/ @property 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()); } } |