Panda3D
Loading...
Searching...
No Matches
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
18using std::iostream;
19using std::istream;
20using std::ostream;
21using std::string;
22
23TypeHandle VirtualFileMountRamdisk::_type_handle;
24TypeHandle VirtualFileMountRamdisk::FileBase::_type_handle;
25TypeHandle VirtualFileMountRamdisk::File::_type_handle;
26TypeHandle VirtualFileMountRamdisk::Directory::_type_handle;
27
28/**
29 *
30 */
31VirtualFileMountRamdisk::
32VirtualFileMountRamdisk() : _root("") {
33}
34
35/**
36 * Returns true if the indicated file exists within the mount system.
37 */
39has_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 */
52create_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 */
65delete_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 */
79rename_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 */
134copy_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 */
168make_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 */
180is_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 */
192is_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 */
204is_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 */
214open_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 */
232open_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 */
261open_append_file(const Filename &file) {
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 */
278open_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 */
303open_read_append_file(const Filename &file) {
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 */
321get_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 */
338get_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 */
361get_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 */
379scan_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 */
398atomic_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 */
425atomic_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 */
444void VirtualFileMountRamdisk::
445output(ostream &out) const {
446 out << "VirtualFileMountRamdisk";
447}
448
449/**
450 *
451 */
452VirtualFileMountRamdisk::FileBase::
453~FileBase() {
454}
455
456/**
457 *
458 */
459bool VirtualFileMountRamdisk::FileBase::
460is_directory() const {
461 return false;
462}
463
464/**
465 *
466 */
467bool VirtualFileMountRamdisk::Directory::
468is_directory() const {
469 return true;
470}
471
472/**
473 * Recursively search for the file with the indicated name in this directory
474 * hierarchy.
475 */
476PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
477do_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 */
510PT(VirtualFileMountRamdisk::File) VirtualFileMountRamdisk::Directory::
511do_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 */
558PT(VirtualFileMountRamdisk::Directory) VirtualFileMountRamdisk::Directory::
559do_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 */
606PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory::
607do_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 */
649bool VirtualFileMountRamdisk::Directory::
650do_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}
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
An istream object that presents a subwindow into another istream.
Definition subStream.h:30
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
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
virtual bool atomic_read_contents(const Filename &file, std::string &contents) const
See Filename::atomic_read_contents().
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 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,...
virtual bool is_directory(const Filename &file) const
Returns true if the indicated file exists within the mount system and is a directory.
virtual bool make_directory(const Filename &file)
Attempts to create the indicated file within the mount, if it does not already exist.
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 create_file(const Filename &file)
Attempts to create the indicated file within the mount, if it does not already exist.
virtual bool is_writable(const Filename &file) const
Returns true if the named file or directory may be written to, false otherwise.
virtual std::iostream * open_read_write_file(const Filename &file, bool truncate)
Opens the file for writing.
virtual bool delete_file(const Filename &file)
Attempts to delete the indicated file or directory within the mount.
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.
virtual bool has_file(const Filename &file) const
Returns true if the indicated file exists within the mount system.
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::istream * open_read_file(const Filename &file) const
Opens the file for reading, if it exists.
virtual std::ostream * open_append_file(const Filename &file)
Works like open_write_file(), but the file is opened in append mode.
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 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 std::ostream * open_write_file(const Filename &file, bool truncate)
Opens the file for writing.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.