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 }