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());
}
}
|