1 
2 //          Copyright Ferdinand Majerech 2011 - 2012.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 
8 module dgamevfs.vfs;
9 
10 
11 ///Base classes and structs defining the general API of D:GameVFS.
12 import std.container;
13 import std.exception;
14 import std.path;
15 import std..string;
16 import std.typecons;
17 
18 import dgamevfs.exceptions;
19 
20 
21 /**
22  * A directory in the VFS.
23  *
24  * Provides basic directory information and access to files and
25  * subdirectories within the directory.
26  *
27  * Directory names in the VFS can contain any characters except $(B /),
28  * which is used as directory separator, and the $(B ::) sequence, which is
29  * used for explicit package lookup (see  $(D StackDir)).
30  *
31  * Examples:
32  * --------------------
33  * //Construct the directory (ordinary physical file system directory in this case):
34  * VFSDir dir = new FSDir("main", "./user_data/main", Yes.writable);
35  *
36  * //Print information about the directory:
37  * writeln("name: ", dir.name,
38  *         ", full path: ", dir.path,
39  *         ", writable: ", dir.writable,
40  *         ", exists: ", dir.exists);
41  *
42  * //Access a file. If it does not exist, it will be created when writing:
43  * auto file = dir.file("logs/memory.log");
44  *
45  * //Access a subdirectory:
46  * auto shaders = dir.dir("shaders");
47  *
48  * //Create a subdirectory. If the directory exists, nothing happens (no error):
49  * auto shaders = dir.dir("does_not_exist").create();
50  *
51  *
52  * //dirs() and files() methods can be used to get ranges of files and subdirectories:
53  *
54  * //Print paths of all immediate subdirectories and their files:
55  * foreach(subdir; dir.dirs())
56  * {
57  *     writeln(dir.path, ":");
58  *     foreach(file; subdir.files())
59  *     {
60  *         writeln("    ", file.path);
61  *     }
62  * }
63  *
64  * //Print paths of all subdirectories and their subdirectories, etc. recursively:
65  * foreach(subdir; dir.dirs(Yes.deep))
66  * {
67  *     writeln(dir.path);
68  * }
69  *
70  * //Glob patterns can be used to filter the results:
71  *
72  * //Print paths of all immediate subdirectories with paths containg "doc":
73  * foreach(subdir; dir.dirs(No.deep, "*doc*"))
74  * {
75  *     writeln(dir.path);
76  * }
77  *
78  * //Print paths of all files in the directory and in subdirectories with paths ending with ".txt":
79  * foreach(file; dir.files(Yes.deep, "*.txt"))
80  * {
81  *     writeln(file.path);
82  * }
83  * --------------------
84  */
85 abstract class VFSDir
86 {
87     private:
88         //Parent directory. If null, the directory has no parent.
89         VFSDir parent_ = null;
90 
91         //Path of this directory within the parent (i.e. the name of this directory).
92         string pathInParent_;
93 
94     public:
95         ///Get the _name of this directory.
96         final @property string name() @safe const pure nothrow @nogc {return pathInParent_;}
97 
98         ///Get full _path of this directory in the VFS.
99         final @property string path() @safe const pure nothrow
100         {
101             return parent_ is null ? pathInParent_ : parent_.composePath(this);
102         }
103 
104         ///Is it possible to write to the directory?
105         @property bool writable() @safe pure nothrow const @nogc;
106 
107         ///Does the directory exist?
108         @property bool exists() @safe const nothrow;
109 
110         /**
111          * Get _file with specified _path in the directory.
112          *
113          * The _file will be returned even if it does not exist -
114          * it will be created when writing into it.
115          *
116          * Params:  path = Path of the _file to get.
117          *
118          * Throws:  $(D VFSNotFoundException) if the directory does not exist
119          *          or the _file is in a nonexistent subdirectory.
120          *
121          *          $(D VFSInvalidPathException) if the _path is invalid.
122          *
123          * Returns: File with specified _path.
124          */
125         VFSFile file(string path);
126 
127         /**
128          * Get a subdirectory with specified _path in the directory.
129          *
130          * The subdirectory will be returned even if it does not exist -
131          * it can be created with the $(D create()) method.
132          *
133          * Params:  path = Path of the subdirectory to get.
134          *
135          * Throws:  $(D VFSNotFoundException) if this VFSDir does not exist
136          *          or the subdirectory is in a nonexistent subdirectory.
137          *
138          *          $(D VFSInvalidPathException) if the _path is invalid.
139          *
140          * Returns: Subdirectory with specified _path.
141          */
142         VFSDir dir(string path) @safe; //!pure //!nothrow //!@nogc
143 
144         /**
145          * Get a range of _files in the directory.
146          *
147          * Params:  deep = If true, recursively get _files in subdirectories.
148          *                 Otherwise only get _files directly in this directory.
149          *          glob = Glob pattern used to filter the results.
150          *                 If null (default), all _files will be returned.
151          *                 Otherwise only _files whose VFS paths within this
152          *                 directory match glob (case sensitive) will be
153          *                 returned. Some characters of _glob patterns
154          *                 have special meanings: For instance, $(I *.txt)
155          *                 matches any path ending with the $(B .txt) extension.
156          *
157          * Returns: Range of the _files.
158          *
159          * Throws:  $(D VFSNotFoundException) if the directory does not exist.
160          *
161          * See_also:
162          * $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
163          */
164         VFSFiles files(Flag!"deep" deep = No.deep, string glob = null) @safe;
165 
166         /**
167          * Get a range of subdirectories.
168          *
169          * Params:  deep = If true, recursively get all subdirectories.
170          *                 Otherwise just get subdirectories of this directory.
171          *          glob = Glob pattern used to filter the results.
172          *                 If null (default), all subdirectories will be
173          *                 returned. Otherwise only subdirectories whose VFS
174          *                 paths within this directory match glob
175          *                 (case sensitive) will be returned. Some characters of
176          *                 _glob patterns have special meanings: For instance,
177          *                 $(I *.txt) matches any path ending with the $(B .txt)
178          *                 extension.
179          *
180          * Returns: Range of the directories.
181          *
182          * Throws:  $(D VFSNotFoundException) if the directory does not exist.
183          */
184         VFSDirs dirs(Flag!"deep" deep = No.deep, string glob = null) @safe;
185 
186         /**
187          * Create the directory if it does not exist (otherwise do nothing).
188          *
189          * Throws:  $(D VFSIOException) if the directory could not be created.
190          */
191         final void create() @safe
192         {
193             enforce(writable,
194                     ioError("Cannot create a non-writable directory (path: " ~ path ~ ")"));
195             create_();
196         }
197 
198         /**
199          * Remove the directory if it exists (otherwise do nothing).
200          *
201          * Removes recursively, together with any subdirectories and files.
202          *
203          * Warning: This will make any references to subdirectories or
204          *          files in this directory invalid.
205          *
206          * Throws:  $(D VFSIOException) if the directory could not be removed.
207          */
208         void remove() @system;
209 
210     protected:
211         /**
212          * Constructor to initialize state common for $(D VFSDir) implementations.
213          *
214          * Params:  parent       = Parent directory. If null, this directory has no _parent.
215          *          pathInParent = Path of the directory within the _parent.
216          */
217         this(VFSDir parent, string pathInParent) @safe pure nothrow @nogc
218         {
219             parent_ = parent;
220             pathInParent_ = pathInParent;
221         }
222 
223         ///Construct a range from a set of directories.
224         static VFSDirs dirsRange(VFSDirs.Items dirs) @safe {return VFSDirs(dirs);}
225 
226         ///Construct a range from a set of _files.
227         static VFSFiles filesRange(VFSFiles.Items files) @safe {return VFSFiles(files);}
228 
229         ///Compose path for a _child directory. Used e.g. to allow $(D StackDir) to set children's paths.
230         string composePath(const VFSDir child) @safe const pure nothrow
231         {
232             return path ~ "/" ~ child.name;
233         }
234 
235         ///Implementation of $(D create()). Caller contract guarantees that the directory is writable.
236         void create_() @trusted; //!pure !nothrow !nogc
237 
238         ///Return a copy of this VFSDir without a parent. Used for mounting.
239         VFSDir copyWithoutParent() @safe;
240 
241         ///Access for derived classes to call copyWithoutParent() of other instances.
242         final VFSDir getCopyWithoutParent(VFSDir dir) @safe
243         {
244             return dir.copyWithoutParent();}
245         ;
246 
247     package:
248         //Get the parent directory.
249         final @property VFSDir parent() @safe pure nothrow @nogc {return parent_;}
250 
251         //Set the parent directory.
252         final @property void parent(VFSDir parent) @safe pure nothrow @nogc {parent_ = parent;}
253 }
254 
255 /**
256  * A bidirectional range of VFS items (files or directories).
257  *
258  * Examples:
259  * --------------------
260  * //VFSDirs is a VFSRange of directories - VFSFiles of files.
261  * VFSDirs dirs;
262  *
263  * //Get the first directory.
264  * auto f = dirs.front;
265  *
266  * //Get the last directory.
267  * auto b = dirs.back;
268  *
269  * //Remove the first directory from the range (this will not remove the directory itself).
270  * dirs.popFront();
271  *
272  * //Remove the last directory from the range (this will not remove the directory itself).
273  * dirs.popBack();
274  *
275  * //Are there no files/directories ?
276  * bool empty = r.empty;
277  * --------------------
278  */
279 struct VFSRange(T) if(is(T == VFSDir) || is(T == VFSFile))
280 {
281     public:
282         ///Function used to _compare items alphabetically.
283         static bool compare(T a, T b){return 0 < cmp(a.path, b.path);}
284 
285         ///Type used to store the items.
286         alias RedBlackTree!(T, compare, false) Items;
287 
288     private:
289         //Item storage.
290         Items items_;
291 
292         //Number of items.
293         size_t length_;
294 
295     public:
296         //Range used to access the items. (should be private, but DMD complains... DMD bug?)
297         Items.Range range_;
298 
299         alias range_ this;
300 
301         //Destructor.
302         @trusted ~this()
303         {
304             destroy(items_);
305         }
306 
307         ///Get number of items in the range.
308         @property size_t length() @safe pure nothrow const @nogc  {return length_;}
309 
310         ///Pop the front element from the range.
311         void popFront() @safe pure nothrow @nogc
312         {
313             assert(length_, "Trying to popFront from an empty VFSRange");
314             --length_;
315             range_.popFront();
316         }
317 
318         ///Pop the back element from the range.
319         void popBack() @safe pure nothrow @nogc
320         {
321             assert(length_, "Trying to popBack from an empty VFSRange");
322             --length_;
323             range_.popBack();
324         }
325 
326     package:
327         //Construct a VFSRange for specified items.
328         this(Items items) @safe pure nothrow @nogc
329         {
330             items_ = items;
331             length_ = items_.length;
332             range_ = items_[];
333         }
334 }
335 
336 ///A VFSRange of directories.
337 alias VFSRange!VFSDir VFSDirs;
338 
339 ///A VFSRange of files.
340 alias VFSRange!VFSFile VFSFiles;
341 
342 
343 /**
344  * A file in the VFS.
345  *
346  * Provides basic file information and access to I/O.
347  *
348  * File names in the VFS can contain any characters except $(B /),
349  * which is used as directory separator, and the $(B ::) sequence, which is
350  * used for explicit package lookup (see  $(D StackDir)).
351  *
352  * Examples:
353  * --------------------
354  * VFSDir dir = new FSDir("main", "./user_data/main", Yes.writable);
355  *
356  * //Get the file from a directory.
357  * VFSFile file = dir.file("logs/memory.log");
358  *
359  * //Print information about the file (note that we can only get file size of an existing file):
360  * writeln("name: ", file.name, ", full path: ", file.path,
361  *         ", writable: ", file.writable, ", exists: ", file.exists,
362  *         ", size in bytes: ", file.bytes);
363  *
364  * //Get access to read from the file:
365  * auto input = file.input;
366  *
367  * //Simply read the file to a buffer:
368  * auto buffer = new ubyte[file.bytes];
369  * file.input.read(buffer);
370  *
371  * //Get access to write to the file:
372  * auto output = file.output;
373  *
374  * //Simply write a buffer to the file:
375  * file.output.write(cast(const void[])"The answer is 42");
376  * --------------------
377  */
378 abstract class VFSFile
379 {
380     protected:
381         ///File mode (used by implementations);
382         enum Mode
383         {
384             Closed,
385             Read,
386             Write,
387             Append
388         }
389 
390     private:
391         // Is this a standalone VFSFile without a parent directory?
392         //
393         // Used for files outside of the VFS used through the D:GameVFS API.
394         bool noParent_ = false;
395 
396         // Parent directory of this file.
397         VFSDir parent_;
398 
399         // Path of this file within the parent directory (name of the file).
400         // If noParent is true, this is actually the absolute path.
401         string pathInParent_;
402 
403     public:
404         ///Get _name of the file.
405         final @property string name() @safe const nothrow
406         {
407             invariant_(); scope(exit){invariant_();}
408             return pathInParent_;
409         }
410 
411         ///Get full _path of the file in the VFS.
412         final @property string path() @safe const nothrow
413         {
414             invariant_(); scope(exit){invariant_();}
415             return noParent_ ? pathInParent_ : (parent_.path ~ "/" ~ pathInParent_);
416         }
417 
418         /**
419          * Get file size in _bytes.
420          *
421          * Throws:  $(D VFSNotFoundException) if the file does not exist.
422          */
423         @property ulong bytes() @safe const;
424 
425         ///Does the file exist?
426         @property bool exists() @safe const nothrow;
427 
428         ///Is it possible to write to this file?
429         @property bool writable() @safe const nothrow
430         {
431             invariant_(); scope(exit){invariant_();}
432             // Assume writable for non-relative files.
433             return noParent_ ? true : parent_.writable;
434         }
435 
436         ///Is the file _open?
437         @property bool open() @safe pure nothrow const @nogc;
438 
439         /**
440          * Open the file and get reading access.
441          *
442          * Returns: $(D VFSFileInput) providing _input access to the file.
443          *
444          * Throws:  $(D VFSIOException) if the file does not exist or is already open.
445          */
446         final @property VFSFileInput input()
447         {
448             invariant_(); scope(exit){invariant_();}
449             enforce(exists,
450                     ioError("Trying to open a nonexistent file for reading: ", path));
451             enforce(!open,
452                     ioError("Trying to open for reading a file that is already open: ", path));
453             return VFSFileInput(this);
454         }
455 
456         /**
457          * Open the file and get writing access. Must not already be open.
458          *
459          * Returns: $(D VFSFileOutput) providing _output access to the file.
460          *
461          * Throws:  $(D VFSIOException) if the file is not writable or is already open.
462          */
463         final @property VFSFileOutput output(Flag!"append" append = No.append)
464         {
465             invariant_(); scope(exit){invariant_();}
466             enforce(writable,
467                     ioError("Trying to open a nonwritable file for writing: ", path));
468             enforce(!open,
469                     ioError("Trying to open for writing a file that is already open: ", path));
470 
471             return VFSFileOutput(this, append);
472         }
473 
474     protected:
475         /**
476          * Constructor to initialize state common for $(D VFSFile) implementations.
477          *
478          * Params:  parent       = Parent directory. Must not be null.
479          *          pathInParent = Path of the file within the _parent.
480          */
481         this(VFSDir parent, string pathInParent) @safe nothrow
482         {
483             assert(parent !is null, "Can't construct a file with no parent");
484             parent_ = parent;
485             pathInParent_ = pathInParent;
486             invariant_();
487         }
488 
489         /**
490          * Constructor to initialize a $(D VFSFile) without a parent.
491          *
492          * Params:  noParent       = Parent directory. Must not be null.
493          *          pathInParent = Path of the file within the _parent.
494          */
495         this(string absolutePath) @safe nothrow
496         {
497             noParent_     = true;
498             parent_       = null;
499             pathInParent_ = absolutePath;
500             invariant_();
501         }
502 
503         ///Open the file for reading.
504         void openRead();
505 
506         ///Open the file for writing/appending.
507         void openWrite(Flag!"append" append);
508 
509         /**
510          * Read up to $(D target.length) bytes to target from current file position.
511          *
512          * Params:  target = Buffer to _read to.
513          *
514          * Returns: Slice of _target containing the read data.
515          */
516         void[] read(void[] target);
517 
518         ///Write $(D data.length) bytes to file from current file position.
519         void write(in void[] data);
520 
521         ///Seek offset bytes from origin within the file.
522         void seek(long offset, Seek origin);
523 
524         ///Close the file, finalizing any file operations.
525         void close();
526 
527         ///Proxies to for derived VFSFiles to call protected members of other VFSFiles.
528         static void openReadProxy(VFSFile file){file.openRead();}
529         ///Ditto
530         static void openWriteProxy(VFSFile file, Flag!"append" append){file.openWrite(append);}
531         ///Ditto
532         static void[] readProxy(VFSFile file, void[] target){return file.read(target);}
533         ///Ditto
534         static void writeProxy(VFSFile file, const void[] data){file.write(data);}
535         ///Ditto
536         static void seekProxy(VFSFile file, long offset, Seek origin){file.seek(offset, origin);}
537         ///Ditto
538         static void closeProxy(VFSFile file){file.close();}
539 
540     private:
541         //Using this due invariant related compiler bugs.
542         void invariant_() @safe const nothrow
543         {
544             assert(noParent_ || parent_.exists,
545                    "File with a nonexistent parent directory "
546                    " - this shouldn't happen as a directory should only "
547                    "provide access to its files if it exists");
548         }
549 }
550 
551 ///File seeking positions.
552 enum Seek
553 {
554     ///Beginning of file.
555     Set,
556     ///_Current file position.
557     Current,
558     ///_End of file.
559     End
560 }
561 
562 /**
563  * Provides basic file input functionality - seeking and reading.
564  *
565  * $(D VFSFileInput) uses reference counting so that the file is closed
566  * when the last instance of $(D VFSFileInput) provided by the file is destroyed.
567  *
568  * Examples:
569  * --------------------
570  * VFSFile file; //initialized somewhere before
571  *
572  * auto input = file.input;
573  * with(input)
574  * {
575  *     auto buffer = new ubyte[32];
576  *
577  *     //Read the first 32 bytes from the file:
578  *     read(buffer);
579  *
580  *     //Read the next 32 bytes:
581  *     read(buffer);
582  *
583  *     //Read the last 32 bytes in the file:
584  *     seek(-32, file);
585  *     read(buffer);
586  * }
587  * --------------------
588  */
589 struct VFSFileInput
590 {
591     private:
592         //Used for simple reference counting. Should be replaced by RefCounted once that is not bugged.
593         class RefCount{int count;}
594 
595         RefCount refCount_ = null;
596 
597         //File we're working with.
598         VFSFile file_;
599 
600         //Is this VFSFileInput "null" (uninitialized)?
601         bool isNull_ = true;
602 
603     public:
604         /**
605          * Read at most $(D target.length) bytes starting at current file position to target.
606          *
607          * If the file does not have enough bytes to fill target or a _reading
608          * error is encountered, it reads as much data as possible and returns
609          * the part of target containing the _read data.
610          *
611          * Params:  target = Buffer to _read to.
612          *
613          * Returns: Slice of _target containing the read data.
614          */
615         void[] read(void[] target)
616         {
617             assert(!isNull_, "Trying to read using an uninitialized VFSFileInput");
618             invariant_(); scope(exit){invariant_();}
619             return file_.read(target);
620         }
621 
622         /**
623          * Set file position to offset bytes from specified origin.
624          *
625          * Params:  offset = Number of bytes to set file position relative to origin.
626          *          origin = Position to which offset is added.
627          *
628          * Throws:  $(D VFSIOException) if trying to _seek before the beginning or beyond
629          *          the end of file.
630          */
631         void seek(long offset, Seek origin)
632         {
633             assert(!isNull_, "Trying to seek using an uninitialized VFSFileInput");
634             invariant_(); scope(exit){invariant_();}
635             file_.seek(offset, origin);
636         }
637 
638         //Postblit ctor (refcountng)
639         this(this)
640         {
641             invariant_(); scope(exit){invariant_();}
642             if(isNull_){return;}
643             ++refCount_.count;
644         }
645 
646         //Postblit dtor (refcountng)
647         ~this()
648         {
649             invariant_();
650             if(isNull_){return;}
651             --refCount_.count;
652             if(refCount_.count == 0){file_.close();}
653         }
654 
655         //Assignment operator (refcounting)
656         void opAssign(VFSFileInput rhs)
657         {
658             invariant_(); scope(exit){invariant_();}
659             if(!rhs.isNull_){++rhs.refCount_.count;}
660             if(!isNull_){--refCount_.count;}
661             refCount_ = rhs.refCount_;
662             file_     = rhs.file_;
663             isNull_   = rhs.isNull_;
664         }
665 
666     package:
667         //Construct a VFSFileInput reading from specified file.
668         this(VFSFile file)
669         {
670             isNull_ = false;
671             refCount_ = new RefCount();
672             refCount_.count = 1;
673             file_ = file;
674             file_.openRead();
675             invariant_();
676         }
677 
678     private:
679         //Due to a compiler bug, invariant segfaults - so using this instead.
680         void invariant_() const
681         {
682             assert(isNull_ || (file_ !is null && file_.open),
683                    "File worked on by VFSFileInput must be open during FileInput's lifetime");
684         }
685 }
686 
687 /**
688  * Provides basic file output functionality - seeking and writing.
689  *
690  * $(D VFSFileOutput) uses reference counting so that the file is closed
691  * when the last instance of $(D VFSFileOutput) provided by the file is destroyed.
692  *
693  * Examples:
694  * --------------------
695  * VFSFile file; //initialized somewhere before
696  *
697  * auto output = file.output;
698  * with(output)
699  * {
700  *     //Write to the file:
701  *     write(cast(const void[])"The answer is ??");
702  *
703  *     //Change the last two characters in the file:
704  *     seek(-2, Seek.End);
705  *     write(cast(const void[])"42");
706  * }
707  * --------------------
708  *
709  * --------------------
710  * //Appending:
711  * //When appending to the file, every write writes to the end of file
712  * //regardless of any calls to seek(), and sets the file position
713  * //to end of file. This
714  * //(This is to stay in line with the C standard so we can use C functions directly)
715  * auto output = file.output(Yes.append);
716  * with(output)
717  * {
718  *     //Append to the file:
719  *     write(cast(const void[])"The answer is ??");
720  *
721  *     //This will NOT change the last 2 characters: it will append anyway:
722  *     seek(-2, Seek.End);
723  *     write(cast(const void[])"42");
724  * }
725  * --------------------
726  */
727 struct VFSFileOutput
728 {
729     private:
730         //Used for simple reference counting. Should be replaced by RefCounted once that is not bugged.
731         class RefCount{int count;}
732 
733         RefCount refCount_ = null;
734 
735         //File we're working with.
736         VFSFile file_;
737 
738         //Is this VFSFileOutput "null" (uninitialized)?
739         bool isNull_ = true;
740 
741     public:
742         /**
743          * Write data to file starting at current file position.
744          *
745          * Note:
746          *
747          * In append mode, any _write will _write to the end of file regardless
748          * of the current file position and file position will be set to the
749          * end of file.
750          *
751          * (This is to stay in line with the C standard so we can use C I/O functions directly)
752          *
753          * Params:  data = Data to _write to the file.
754          *
755          * Throws:  $(D VFSIOException) on error (e.g. after running out of disk space).
756          */
757         void write(const void[] data)
758         {
759             assert(!isNull_, "Trying to write using an uninitialized VFSFileOutput");
760             invariant_(); scope(exit){invariant_();}
761             file_.write(data);
762         }
763 
764         /**
765          * Set file position to offset bytes from specified origin.
766          *
767          * Params:  offset = Number of bytes to set file position relative to origin.
768          *          origin = Position to which offset is added.
769          *
770          * Throws:  $(D VFSIOException) if trying to _seek before the beginning or behind
771          *          the end of file, or on a different error.
772          */
773         void seek(long offset, Seek origin)
774         {
775             assert(!isNull_, "Trying to seek using an uninitialized VFSFileOutput");
776             invariant_(); scope(exit){invariant_();}
777             file_.seek(offset, origin);
778         }
779 
780         //Postblit ctor (refcountng)
781         this(this)
782         {
783             invariant_(); scope(exit){invariant_();}
784             if(isNull_){return;}
785             ++refCount_.count;
786         }
787 
788         //Postblit dtor (refcountng)
789         ~this()
790         {
791             invariant_();
792             if(isNull_){return;}
793             --refCount_.count;
794             if(refCount_.count == 0){file_.close();}
795         }
796 
797         //Assignment operator (refcounting)
798         void opAssign(VFSFileOutput rhs)
799         {
800             invariant_(); scope(exit){invariant_();}
801             if(!rhs.isNull_){++rhs.refCount_.count;}
802             if(!isNull_){--refCount_.count;}
803             refCount_ = rhs.refCount_;
804             file_     = rhs.file_;
805             isNull_   = rhs.isNull_;
806         }
807 
808     package:
809         //Construct a VFSFileOutput writing/appending to specified file.
810         this(VFSFile file, Flag!"append" append)
811         {
812             isNull_ = false;
813             refCount_ = new RefCount();
814 
815             refCount_.count = 1;
816             file_ = file;
817             file_.openWrite(append);
818 
819             invariant_();
820         }
821 
822     private:
823         //Due to a compiler bug(?), invariant segfaults - so using this instead.
824         void invariant_() const
825         {
826             assert(isNull_ || (file_ !is null && file_.open),
827                    "File worked on by VFSFileOutput must be open during VFSFileOutput's lifetime");
828         }
829 }