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