1 // Copyright Ferdinand Majerech 2012. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 7 //Memory based VFS file/directory implementation used for testing. 8 module dgamevfs.memory; 9 10 11 import std.algorithm; 12 import std.exception; 13 import std.typecons; 14 15 import dgamevfs.exceptions; 16 import dgamevfs.vfs; 17 import dgamevfs.util; 18 19 20 package: 21 22 /* 23 * VFSDir implementation that works with memory as a file system. 24 * 25 * Used for testing. 26 */ 27 class MemoryDir : VFSDir 28 { 29 private: 30 //Subdirectories of this directory (these might contain more files/subdirectories). 31 MemoryDir[] subdirs_; 32 33 //Files immediately in this directory. 34 MemoryFile[] files_; 35 36 //Is it possible to write to this directory? 37 Flag!"writable" writable_; 38 39 //Does this directory exist? 40 Flag!"exists" exists_; 41 42 public: 43 /* 44 * Construct an empty MemoryDir. 45 * 46 * Params: name = Name of the directory. 47 * writable = Is the directory writable? 48 * exists = Does the directory exist from start? 49 * 50 * Throws: VFSInvalidPathException if name is not valid (contains '/' or "::"). 51 */ 52 this(string name, Flag!"writable" writable = Yes.writable, 53 Flag!"exists" exists = No.exists) @safe pure 54 { 55 this(null, name, writable, exists); 56 } 57 58 override @property bool writable() @safe pure nothrow const @nogc {return writable_;} 59 60 //Set whether this directory (and its subdirectories) should be writable or not. 61 @property void writable(bool rhs) @safe pure nothrow @nogc 62 { 63 writable_ = rhs ? Yes.writable : No.writable; 64 foreach(dir; subdirs_) {dir.writable = rhs;} 65 } 66 67 override @property bool exists() @safe pure const nothrow @nogc {return exists_;} 68 69 override VFSFile file(string path) 70 { 71 enforce(exists, 72 notFound("Trying to access file ", path, " in memory directory ", 73 this.path, " that does not exist")); 74 string rest; 75 string neededSubdir = expectSubdir(path, rest); 76 //Dir is in a subdirectory. 77 if(neededSubdir !is null) 78 { 79 foreach(dir; subdirs_) if(dir.exists && dir.name == neededSubdir) 80 { 81 return dir.file(rest); 82 } 83 throw notFound("Unable to find subdirectory ", neededSubdir, 84 " in directory ", this.path, 85 " when looking for file ", path); 86 } 87 88 //File is in this directory. 89 foreach(file; files_) if(file.name == path) 90 { 91 return file; 92 } 93 94 //File not found - create it. 95 auto file = new MemoryFile(this, path); 96 files_ ~= file; 97 return file; 98 } 99 100 override VFSDir dir(string path) 101 { 102 enforce(exists, 103 notFound("Trying to access subdirectory ", path, " in memory " 104 "directory ", this.path, " that does not exist")); 105 string rest; 106 string neededSubdir = expectSubdir(path, rest); 107 //Dir is in a subdirectory. 108 if(neededSubdir !is null) 109 { 110 foreach(dir; subdirs_) if(dir.exists && dir.name == neededSubdir) 111 { 112 return dir.dir(rest); 113 } 114 throw notFound("Unable to find subdirectory ", neededSubdir, 115 " in directory ", this.path, 116 " when looking for directory ", path); 117 } 118 119 //Dir is in this directory. 120 foreach(dir; subdirs_) if(dir.name == path) 121 { 122 return dir; 123 } 124 125 //Dir not found - create it. 126 auto dir = new MemoryDir(this, path, writable_, No.exists); 127 subdirs_ ~= dir; 128 return dir; 129 } 130 131 override VFSFiles files(Flag!"deep" deep = No.deep, string glob = null) 132 @trusted 133 { 134 enforce(exists, 135 notFound("Trying to access files of memory directory ", 136 this.path, " that does not exist")); 137 138 auto files = new VFSFiles.Items; 139 140 //files in this directory 141 foreach(file; files_) 142 { 143 if(!file.exists){continue;} 144 145 if(subPathMatch(file.path, path, glob)) 146 { 147 files.insert(file); 148 } 149 } 150 //files in subdirectories 151 if(deep) foreach(dir; subdirs_) 152 { 153 foreach(file; dir.files(deep)) 154 { 155 if(subPathMatch(file.path, path, glob)) 156 { 157 files.insert(file); 158 } 159 } 160 } 161 162 return filesRange(files); 163 } 164 165 override VFSDirs dirs(Flag!"deep" deep = No.deep, string glob = null) @trusted 166 { 167 enforce(exists, 168 notFound("Trying to access subdirectories of memory directory ", 169 this.path, " that does not exist")); 170 171 auto dirs = new VFSDirs.Items; 172 173 foreach(dir; subdirs_) 174 { 175 if(!dir.exists){continue;} 176 //this subdirectory 177 178 if(subPathMatch(dir.path, path, glob)) 179 { 180 dirs.insert(dir); 181 } 182 183 //subdirs of this subdirectory 184 if(deep) foreach(subdir; dir.dirs(deep)) 185 { 186 if(subPathMatch(subdir.path, path, glob)) 187 { 188 dirs.insert(subdir); 189 } 190 } 191 } 192 193 return dirsRange(dirs); 194 } 195 196 override void remove() 197 { 198 exists_ = No.exists; 199 } 200 201 protected: 202 override void create_() 203 { 204 exists_ = Yes.exists; 205 } 206 207 override VFSDir copyWithoutParent() 208 { 209 auto result = new MemoryDir(name, writable_, exists_); 210 result.subdirs_ = subdirs_; 211 result.files_ = files_; 212 return result; 213 } 214 215 private: 216 /* 217 * Construct a MemoryDir with a parent. 218 * 219 * Params: parent = Parent directory (can be null). 220 * name = Name of the directory. 221 * writable = Is the directory writable? 222 * exists = Does the directory exist from start? 223 * 224 * Throws: VFSInvalidPathException if pathInParent is not valid 225 * (contains '/' or "::"). 226 */ 227 this(VFSDir parent, string pathInParent, 228 Flag!"writable" writable, Flag!"exists" exists) 229 @safe pure 230 { 231 enforce(noSeparators(pathInParent), 232 invalidPath("Invalid directory name: ", pathInParent)); 233 writable_ = writable; 234 exists_ = exists; 235 super(parent, pathInParent); 236 } 237 } 238 239 /* 240 * VFSFile that implements a file in memory. 241 * 242 * Used for testing. 243 */ 244 class MemoryFile : VFSFile 245 { 246 private: 247 //File buffer. If null, the file does not exist. 248 ubyte[] buffer_ = null; 249 250 //File mode (open, reading, writing, appending). 251 Mode mode_ = Mode.Closed; 252 253 //Current position within the file. 254 ulong seekPosition_ = 0; 255 256 public: 257 override @property ulong bytes() const 258 { 259 enforce(buffer_ !is null, 260 notFound("Trying to get size of MemoryFile ", path, 261 " that does not exist")); 262 return buffer_.length; 263 } 264 265 override @property bool exists() @safe pure nothrow const @nogc {return buffer_ !is null;} 266 267 override @property bool open() const 268 { 269 return mode_ != Mode.Closed; 270 } 271 272 protected: 273 override void openRead() 274 { 275 assert(exists, "Trying to open a nonexistent file for reading: " ~ path); 276 assert(mode_ == Mode.Closed, "Trying to open a file that is already open: " ~ path); 277 278 if(buffer_ !is null) 279 { 280 mode_ = Mode.Read; 281 return; 282 } 283 } 284 285 override void openWrite(Flag!"append" append) 286 { 287 assert(mode_ == Mode.Closed, "Trying to open a file that is already open" ~ path); 288 assert(writable, "Trying open a non-writable file for writing: " ~ path); 289 290 mode_ = (append ? Mode.Append : Mode.Write); 291 292 if(buffer_ is null){buffer_ = [];} 293 //Write overwrites the buffer. 294 else if(!append) {destroy(buffer_);} 295 } 296 297 override void[] read(void[] target) 298 { 299 assert(mode_ == Mode.Read, 300 "Trying to read from a file not opened for reading: " ~ path); 301 302 const seek = cast(size_t)seekPosition_; 303 const read_size = max(cast(size_t)0, min(buffer_.length - seek, 304 target.length)); 305 target[0 .. read_size] = buffer_[seek .. seek + read_size]; 306 seekPosition_ += read_size; 307 return target[0 .. read_size]; 308 } 309 310 override void write(in void[] data) 311 { 312 assert(mode_ == Mode.Write || mode_ == Mode.Append, 313 "Trying to write to a file not opened for writing/appending: " ~ path); 314 assert(writable, "Trying to write to a non-writable file: " ~ path); 315 316 const data_bytes = cast(ubyte[])data; 317 //Appending always to the end of file (C-like behavior). 318 if(mode_ == Mode.Append) 319 { 320 buffer_ ~= data_bytes; 321 seekPosition_ = buffer_.length; 322 return; 323 } 324 const needed = cast(size_t)seekPosition_ + data_bytes.length; 325 buffer_.length = max(buffer_.length, needed); 326 buffer_[cast(size_t)seekPosition_ .. needed] = data_bytes[0 .. $]; 327 seekPosition_ += data_bytes.length; 328 } 329 330 override void seek(long offset, Seek origin) 331 { 332 assert(mode_ != Mode.Closed, "Trying to seek in an unopened file: " ~ path); 333 334 const long base = origin == Seek.Set ? 0 : 335 origin == Seek.Current ? seekPosition_ : 336 buffer_.length; 337 const long position = base + offset; 338 enforce(position >= 0, 339 ioError("Trying to seek before the beginning of file: " ~ path)); 340 enforce(position <= buffer_.length, 341 ioError("Trying to seek beyond the end of file: " ~ path)); 342 seekPosition_ = cast(ulong)position; 343 } 344 345 override void close() 346 { 347 assert(mode_ != Mode.Closed, "Trying to close an unopened file: " ~ path); 348 349 mode_ = Mode.Closed; 350 seekPosition_ = 0; 351 } 352 353 private: 354 /* 355 * Construct a MemoryFile with specified parent and path. 356 * 357 * Params: parent = Parent directory. 358 * pathInParent = Path of the file in the parent directory (aka file name). 359 * 360 * Throws: VFSInvalidPathException if pathInParent is not valid 361 * (contains '/' or "::"). 362 */ 363 this(MemoryDir parent, string pathInParent) 364 { 365 enforce(noSeparators(pathInParent), 366 invalidPath("Invalid file name: ", pathInParent)); 367 super(parent, pathInParent); 368 } 369 } 370