Panda3D
virtualFileMountRamdisk.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file virtualFileMountRamdisk.cxx
10  * @author drose
11  * @date 2011-09-19
12  */
13 
15 #include "subStream.h"
16 #include "dcast.h"
17 
18 using std::iostream;
19 using std::istream;
20 using std::ostream;
21 using std::string;
22 
23 TypeHandle VirtualFileMountRamdisk::_type_handle;
24 TypeHandle VirtualFileMountRamdisk::FileBase::_type_handle;
25 TypeHandle VirtualFileMountRamdisk::File::_type_handle;
26 TypeHandle VirtualFileMountRamdisk::Directory::_type_handle;
27 
28 /**
29  *
30  */
31 VirtualFileMountRamdisk::
32 VirtualFileMountRamdisk() : _root("") {
33 }
34 
35 /**
36  * Returns true if the indicated file exists within the mount system.
37  */
39 has_file(const Filename &file) const {
40  _lock.lock();
41  PT(FileBase) f = _root.do_find_file(file);
42  _lock.unlock();
43  return (f != nullptr);
44 }
45 
46 /**
47  * Attempts to create the indicated file within the mount, if it does not
48  * already exist. Returns true on success (or if the file already exists), or
49  * false if it cannot be created.
50  */
52 create_file(const Filename &file) {
53  _lock.lock();
54  PT(File) f = _root.do_create_file(file);
55  _lock.unlock();
56  return (f != nullptr);
57 }
58 
59 /**
60  * Attempts to delete the indicated file or directory within the mount. This
61  * can remove a single file or an empty directory. It will not remove a
62  * nonempty directory. Returns true on success, false on failure.
63  */
65 delete_file(const Filename &file) {
66  _lock.lock();
67  PT(FileBase) f = _root.do_delete_file(file);
68  _lock.unlock();
69  return (f != nullptr);
70 }
71 
72 /**
73  * Attempts to rename the contents of the indicated file to the indicated
74  * file. Both filenames will be within the mount. Returns true on success,
75  * false on failure. If this returns false, this will be attempted again with
76  * a copy-and-delete operation.
77  */
79 rename_file(const Filename &orig_filename, const Filename &new_filename) {
80  _lock.lock();
81  PT(FileBase) orig_fb = _root.do_find_file(orig_filename);
82  if (orig_fb == nullptr) {
83  _lock.unlock();
84  return false;
85  }
86 
87  if (orig_fb->is_directory()) {
88  // Rename the directory.
89  Directory *orig_d = DCAST(Directory, orig_fb);
90  PT(Directory) new_d = _root.do_make_directory(new_filename);
91  if (new_d == nullptr || !new_d->_files.empty()) {
92  _lock.unlock();
93  return false;
94  }
95 
96  if (express_cat.is_debug()) {
97  express_cat.debug()
98  << "Renaming ramdisk directory " << orig_filename << " to " << new_filename << "\n";
99  }
100 
101  new_d->_files.swap(orig_d->_files);
102  _root.do_delete_file(orig_filename);
103  _lock.unlock();
104  return true;
105  }
106 
107  // Rename the file.
108  File *orig_f = DCAST(File, orig_fb);
109  PT(File) new_f = _root.do_create_file(new_filename);
110  if (new_f == nullptr) {
111  _lock.unlock();
112  return false;
113  }
114 
115  if (express_cat.is_debug()) {
116  express_cat.debug()
117  << "Renaming ramdisk file " << orig_filename << " to " << new_filename << "\n";
118  }
119 
120  new_f->_data.str(orig_f->_data.str());
121  _root.do_delete_file(orig_filename);
122 
123  _lock.unlock();
124  return true;
125 }
126 
127 /**
128  * Attempts to copy the contents of the indicated file to the indicated file.
129  * Both filenames will be within the mount. Returns true on success, false on
130  * failure. If this returns false, the copy will be performed by explicit
131  * read-and-write operations.
132  */
134 copy_file(const Filename &orig_filename, const Filename &new_filename) {
135  _lock.lock();
136  PT(FileBase) orig_fb = _root.do_find_file(orig_filename);
137  if (orig_fb == nullptr || orig_fb->is_directory()) {
138  _lock.unlock();
139  return false;
140  }
141 
142  // Copy the file.
143  File *orig_f = DCAST(File, orig_fb);
144  PT(File) new_f = _root.do_create_file(new_filename);
145  if (new_f == nullptr) {
146  _lock.unlock();
147  return false;
148  }
149 
150  if (express_cat.is_debug()) {
151  express_cat.debug()
152  << "Copying ramdisk file " << orig_filename << " to " << new_filename << "\n";
153  }
154 
155  new_f->_data.str(orig_f->_data.str());
156 
157  _lock.unlock();
158  return true;
159 }
160 
161 /**
162  * Attempts to create the indicated file within the mount, if it does not
163  * already exist. Returns true on success, or false if it cannot be created.
164  * If the directory already existed prior to this call, may return either true
165  * or false.
166  */
168 make_directory(const Filename &file) {
169  _lock.lock();
170  PT(Directory) f = _root.do_make_directory(file);
171  _lock.unlock();
172  return (f != nullptr);
173 }
174 
175 /**
176  * Returns true if the indicated file exists within the mount system and is a
177  * directory.
178  */
180 is_directory(const Filename &file) const {
181  _lock.lock();
182  PT(FileBase) f = _root.do_find_file(file);
183  _lock.unlock();
184  return (f != nullptr && f->is_directory());
185 }
186 
187 /**
188  * Returns true if the indicated file exists within the mount system and is a
189  * regular file.
190  */
192 is_regular_file(const Filename &file) const {
193  _lock.lock();
194  PT(FileBase) f = _root.do_find_file(file);
195  _lock.unlock();
196  return (f != nullptr && !f->is_directory());
197 }
198 
199 /**
200  * Returns true if the named file or directory may be written to, false
201  * otherwise.
202  */
204 is_writable(const Filename &file) const {
205  return has_file(file);
206 }
207 
208 /**
209  * Opens the file for reading, if it exists. Returns a newly allocated
210  * istream on success (which you should eventually delete when you are done
211  * reading). Returns NULL on failure.
212  */
214 open_read_file(const Filename &file) const {
215  _lock.lock();
216  PT(FileBase) f = _root.do_find_file(file);
217  _lock.unlock();
218  if (f == nullptr || f->is_directory()) {
219  return nullptr;
220  }
221 
222  File *f2 = DCAST(File, f);
223  return new ISubStream(&f2->_wrapper, 0, 0);
224 }
225 
226 /**
227  * Opens the file for writing. Returns a newly allocated ostream on success
228  * (which you should eventually delete when you are done writing). Returns
229  * NULL on failure.
230  */
232 open_write_file(const Filename &file, bool truncate) {
233  _lock.lock();
234  PT(File) f = _root.do_create_file(file);
235  _lock.unlock();
236  if (f == nullptr) {
237  return nullptr;
238  }
239 
240  if (truncate) {
241  // Reset to an empty string.
242  f->_data.str(string());
243 
244  // Instead of setting the time, we ensure that we always store a newer time.
245  // This is a workarround for the case that a file is written twice per
246  // second, since the timer only has a one second precision. The proper
247  // solution to fix this would be to switch to a higher precision
248  // timer everywhere.
249  f->_timestamp = std::max(f->_timestamp + 1, time(nullptr));
250  }
251 
252  return new OSubStream(&f->_wrapper, 0, 0);
253 }
254 
255 /**
256  * Works like open_write_file(), but the file is opened in append mode. Like
257  * open_write_file, the returned pointer should eventually be passed to
258  * close_write_file().
259  */
262  _lock.lock();
263  PT(File) f = _root.do_create_file(file);
264  _lock.unlock();
265  if (f == nullptr) {
266  return nullptr;
267  }
268 
269  return new OSubStream(&f->_wrapper, 0, 0, true);
270 }
271 
272 /**
273  * Opens the file for writing. Returns a newly allocated iostream on success
274  * (which you should eventually delete when you are done writing). Returns
275  * NULL on failure.
276  */
278 open_read_write_file(const Filename &file, bool truncate) {
279  _lock.lock();
280  PT(File) f = _root.do_create_file(file);
281  _lock.unlock();
282  if (f == nullptr) {
283  return nullptr;
284  }
285 
286  if (truncate) {
287  // Reset to an empty string.
288  f->_data.str(string());
289 
290  // See open_write_file
291  f->_timestamp = std::max(f->_timestamp + 1, time(nullptr));
292  }
293 
294  return new SubStream(&f->_wrapper, 0, 0);
295 }
296 
297 /**
298  * Works like open_read_write_file(), but the file is opened in append mode.
299  * Like open_read_write_file, the returned pointer should eventually be passed
300  * to close_read_write_file().
301  */
304  _lock.lock();
305  PT(FileBase) f = _root.do_find_file(file);
306  _lock.unlock();
307  if (f == nullptr || f->is_directory()) {
308  return nullptr;
309  }
310 
311  File *f2 = DCAST(File, f);
312  return new SubStream(&f2->_wrapper, 0, 0, true);
313 }
314 
315 /**
316  * Returns the current size on disk (or wherever it is) of the already-open
317  * file. Pass in the stream that was returned by open_read_file(); some
318  * implementations may require this stream to determine the size.
319  */
320 std::streamsize VirtualFileMountRamdisk::
321 get_file_size(const Filename &file, istream *stream) const {
322  _lock.lock();
323  PT(FileBase) f = _root.do_find_file(file);
324  _lock.unlock();
325  if (f == nullptr || f->is_directory()) {
326  return 0;
327  }
328 
329  File *f2 = DCAST(File, f);
330  return f2->_data.str().length();
331 }
332 
333 /**
334  * Returns the current size on disk (or wherever it is) of the file before it
335  * has been opened.
336  */
337 std::streamsize VirtualFileMountRamdisk::
338 get_file_size(const Filename &file) const {
339  _lock.lock();
340  PT(FileBase) f = _root.do_find_file(file);
341  _lock.unlock();
342  if (f == nullptr || f->is_directory()) {
343  return 0;
344  }
345 
346  File *f2 = DCAST(File, f);
347  return f2->_data.str().length();
348 }
349 
350 /**
351  * Returns a time_t value that represents the time the file was last modified,
352  * to within whatever precision the operating system records this information
353  * (on a Windows95 system, for instance, this may only be accurate to within 2
354  * seconds).
355  *
356  * If the timestamp cannot be determined, either because it is not supported
357  * by the operating system or because there is some error (such as file not
358  * found), returns 0.
359  */
361 get_timestamp(const Filename &file) const {
362  _lock.lock();
363  PT(FileBase) f = _root.do_find_file(file);
364  if (f.is_null()) {
365  _lock.unlock();
366  return 0;
367  }
368  time_t timestamp = f->_timestamp;
369  _lock.unlock();
370  return timestamp;
371 }
372 
373 /**
374  * Fills the given vector up with the list of filenames that are local to this
375  * directory, if the filename is a directory. Returns true if successful, or
376  * false if the file is not a directory or cannot be read.
377  */
379 scan_directory(vector_string &contents, const Filename &dir) const {
380  _lock.lock();
381  PT(FileBase) f = _root.do_find_file(dir);
382  if (f == nullptr || !f->is_directory()) {
383  _lock.unlock();
384  return false;
385  }
386 
387  Directory *f2 = DCAST(Directory, f);
388  bool result = f2->do_scan_directory(contents);
389 
390  _lock.unlock();
391  return result;
392 }
393 
394 /**
395  * See Filename::atomic_compare_and_exchange_contents().
396  */
398 atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents,
399  const string &old_contents,
400  const string &new_contents) {
401  _lock.lock();
402  PT(FileBase) f = _root.do_find_file(file);
403  if (f == nullptr || f->is_directory()) {
404  _lock.unlock();
405  return false;
406  }
407 
408  bool retval = false;
409  File *f2 = DCAST(File, f);
410  orig_contents = f2->_data.str();
411  if (orig_contents == old_contents) {
412  f2->_data.str(new_contents);
413  f2->_timestamp = time(nullptr);
414  retval = true;
415  }
416 
417  _lock.unlock();
418  return retval;
419 }
420 
421 /**
422  * See Filename::atomic_read_contents().
423  */
425 atomic_read_contents(const Filename &file, string &contents) const {
426  _lock.lock();
427  PT(FileBase) f = _root.do_find_file(file);
428  if (f == nullptr || f->is_directory()) {
429  _lock.unlock();
430  return false;
431  }
432 
433  File *f2 = DCAST(File, f);
434  contents = f2->_data.str();
435 
436  _lock.unlock();
437  return true;
438 }
439 
440 
441 /**
442  *
443  */
444 void VirtualFileMountRamdisk::
445 output(ostream &out) const {
446  out << "VirtualFileMountRamdisk";
447 }
448 
449 /**
450  *
451  */
452 VirtualFileMountRamdisk::FileBase::
453 ~FileBase() {
454 }
455 
456 /**
457  *
458  */
459 bool VirtualFileMountRamdisk::FileBase::
460 is_directory() const {
461  return false;
462 }
463 
464 /**
465  *
466  */
467 bool VirtualFileMountRamdisk::Directory::
468 is_directory() const {
469  return true;
470 }
471 
472 /**
473  * Recursively search for the file with the indicated name in this directory
474  * hierarchy.
475  */
476 PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
477 do_find_file(const string &filename) const {
478  size_t slash = filename.find('/');
479  if (slash == string::npos) {
480  // Search for a file within the local directory.
481  FileBase tfile(filename);
482  tfile.local_object();
483  Files::const_iterator fi = _files.find(&tfile);
484  if (fi != _files.end()) {
485  return (*fi);
486  }
487  return nullptr;
488  }
489 
490  // A nested directory. Search for the directory name, then recurse.
491  string dirname = filename.substr(0, slash);
492  string remainder = filename.substr(slash + 1);
493  FileBase tfile(dirname);
494  tfile.local_object();
495  Files::const_iterator fi = _files.find(&tfile);
496  if (fi != _files.end()) {
497  PT(FileBase) file = (*fi);
498  if (file->is_directory()) {
499  return DCAST(Directory, file.p())->do_find_file(remainder);
500  }
501  }
502 
503  return nullptr;
504 }
505 
506 /**
507  * Recursively search for the file with the indicated name in this directory
508  * hierarchy. If not found, creates a new file.
509  */
510 PT(VirtualFileMountRamdisk::File) VirtualFileMountRamdisk::Directory::
511 do_create_file(const string &filename) {
512  size_t slash = filename.find('/');
513  if (slash == string::npos) {
514  // Search for a file within the local directory.
515  FileBase tfile(filename);
516  tfile.local_object();
517  Files::iterator fi = _files.find(&tfile);
518  if (fi != _files.end()) {
519  PT(FileBase) file = (*fi);
520  if (!file->is_directory()) {
521  return DCAST(File, file.p());
522  }
523  // Cannot create: a directory by the same name already exists.
524  return nullptr;
525  }
526 
527  // Create a new file.
528  if (express_cat.is_debug()) {
529  express_cat.debug()
530  << "Making ramdisk file " << filename << "\n";
531  }
532  PT(File) file = new File(filename);
533  _files.insert(file.p());
534  _timestamp = time(nullptr);
535  return file;
536  }
537 
538  // A nested directory. Search for the directory name, then recurse.
539  string dirname = filename.substr(0, slash);
540  string remainder = filename.substr(slash + 1);
541  FileBase tfile(dirname);
542  tfile.local_object();
543  Files::iterator fi = _files.find(&tfile);
544  if (fi != _files.end()) {
545  PT(FileBase) file = (*fi);
546  if (file->is_directory()) {
547  return DCAST(Directory, file.p())->do_create_file(remainder);
548  }
549  }
550 
551  return nullptr;
552 }
553 
554 /**
555  * Recursively search for the file with the indicated name in this directory
556  * hierarchy. If not found, creates a new directory.
557  */
558 PT(VirtualFileMountRamdisk::Directory) VirtualFileMountRamdisk::Directory::
559 do_make_directory(const string &filename) {
560  size_t slash = filename.find('/');
561  if (slash == string::npos) {
562  // Search for a file within the local directory.
563  FileBase tfile(filename);
564  tfile.local_object();
565  Files::iterator fi = _files.find(&tfile);
566  if (fi != _files.end()) {
567  PT(FileBase) file = (*fi);
568  if (file->is_directory()) {
569  return DCAST(Directory, file.p());
570  }
571  // Cannot create: a file by the same name already exists.
572  return nullptr;
573  }
574 
575  // Create a new directory.
576  if (express_cat.is_debug()) {
577  express_cat.debug()
578  << "Making ramdisk directory " << filename << "\n";
579  }
580  PT(Directory) file = new Directory(filename);
581  _files.insert(file.p());
582  _timestamp = time(nullptr);
583  return file;
584  }
585 
586  // A nested directory. Search for the directory name, then recurse.
587  string dirname = filename.substr(0, slash);
588  string remainder = filename.substr(slash + 1);
589  FileBase tfile(dirname);
590  tfile.local_object();
591  Files::iterator fi = _files.find(&tfile);
592  if (fi != _files.end()) {
593  PT(FileBase) file = (*fi);
594  if (file->is_directory()) {
595  return DCAST(Directory, file.p())->do_make_directory(remainder);
596  }
597  }
598 
599  return nullptr;
600 }
601 
602 /**
603  * Recursively search for the file with the indicated name in this directory
604  * hierarchy, and removes it. Returns the removed FileBase object.
605  */
606 PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
607 do_delete_file(const string &filename) {
608  size_t slash = filename.find('/');
609  if (slash == string::npos) {
610  // Search for a file within the local directory.
611  FileBase tfile(filename);
612  tfile.local_object();
613  Files::iterator fi = _files.find(&tfile);
614  if (fi != _files.end()) {
615  PT(FileBase) file = (*fi);
616  if (file->is_directory()) {
617  Directory *dir = DCAST(Directory, file.p());
618  if (!dir->_files.empty()) {
619  // Can't delete a nonempty directory.
620  return nullptr;
621  }
622  }
623  _files.erase(fi);
624  _timestamp = time(nullptr);
625  return file;
626  }
627  return nullptr;
628  }
629 
630  // A nested directory. Search for the directory name, then recurse.
631  string dirname = filename.substr(0, slash);
632  string remainder = filename.substr(slash + 1);
633  FileBase tfile(dirname);
634  tfile.local_object();
635  Files::iterator fi = _files.find(&tfile);
636  if (fi != _files.end()) {
637  PT(FileBase) file = (*fi);
638  if (file->is_directory()) {
639  return DCAST(Directory, file.p())->do_delete_file(remainder);
640  }
641  }
642 
643  return nullptr;
644 }
645 
646 /**
647  *
648  */
649 bool VirtualFileMountRamdisk::Directory::
650 do_scan_directory(vector_string &contents) const {
651  Files::const_iterator fi;
652  for (fi = _files.begin(); fi != _files.end(); ++fi) {
653  FileBase *file = (*fi);
654  contents.push_back(file->_basename);
655  }
656 
657  return true;
658 }
virtual bool atomic_compare_and_exchange_contents(const Filename &file, std::string &orig_contents, const std::string &old_contents, const std::string &new_contents)
See Filename::atomic_compare_and_exchange_contents().
virtual std::iostream * open_read_write_file(const Filename &file, bool truncate)
Opens the file for writing.
virtual std::istream * open_read_file(const Filename &file) const
Opens the file for reading, if it exists.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool create_file(const Filename &file)
Attempts to create the indicated file within the mount, if it does not already exist.
virtual bool atomic_read_contents(const Filename &file, std::string &contents) const
See Filename::atomic_read_contents().
virtual time_t get_timestamp(const Filename &file) const
Returns a time_t value that represents the time the file was last modified, to within whatever precis...
virtual bool copy_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to copy the contents of the indicated file to the indicated file.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual std::ostream * open_write_file(const Filename &file, bool truncate)
Opens the file for writing.
virtual bool has_file(const Filename &file) const
Returns true if the indicated file exists within the mount system.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An istream object that presents a subwindow into another istream.
Definition: subStream.h:30
virtual bool make_directory(const Filename &file)
Attempts to create the indicated file within the mount, if it does not already exist.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
virtual bool is_writable(const Filename &file) const
Returns true if the named file or directory may be written to, false otherwise.
virtual std::ostream * open_append_file(const Filename &file)
Works like open_write_file(), but the file is opened in append mode.
virtual std::iostream * open_read_append_file(const Filename &file)
Works like open_read_write_file(), but the file is opened in append mode.
virtual bool is_regular_file(const Filename &file) const
Returns true if the indicated file exists within the mount system and is a regular file.
virtual bool is_directory(const Filename &file) const
Returns true if the indicated file exists within the mount system and is a directory.
virtual std::streamsize get_file_size(const Filename &file, std::istream *stream) const
Returns the current size on disk (or wherever it is) of the already-open file.
virtual bool rename_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to rename the contents of the indicated file to the indicated file.
virtual bool delete_file(const Filename &file)
Attempts to delete the indicated file or directory within the mount.
virtual bool scan_directory(vector_string &contents, const Filename &dir) const
Fills the given vector up with the list of filenames that are local to this directory,...
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
An ostream object that presents a subwindow into another ostream.
Definition: subStream.h:55
Combined ISubStream and OSubStream for bidirectional I/O.
Definition: subStream.h:74