From 5496c4a9ec6eec0ccbea7c6ec6ac577758a295eb Mon Sep 17 00:00:00 2001 From: David Rose Date: Tue, 27 Sep 2011 16:10:19 +0000 Subject: [PATCH] more writable vfs stuff --- direct/src/stdpy/file.py | 4 + dtool/src/prc/streamWrapper.cxx | 36 + dtool/src/prc/streamWrapper.h | 2 + panda/src/egg/eggData.cxx | 6 +- panda/src/express/Sources.pp | 16 +- panda/src/express/config_express.cxx | 2 + panda/src/express/express_composite2.cxx | 1 + panda/src/express/subStream.I | 102 ++- panda/src/express/subStream.h | 41 + panda/src/express/subStreamBuf.cxx | 328 +++++--- panda/src/express/subStreamBuf.h | 12 +- panda/src/express/virtualFile.cxx | 69 ++ panda/src/express/virtualFile.h | 7 + panda/src/express/virtualFileMount.cxx | 64 ++ panda/src/express/virtualFileMount.h | 6 + panda/src/express/virtualFileMountRamdisk.I | 54 ++ panda/src/express/virtualFileMountRamdisk.cxx | 707 ++++++++++++++++++ panda/src/express/virtualFileMountRamdisk.h | 186 +++++ panda/src/express/virtualFileMountSystem.cxx | 87 +++ panda/src/express/virtualFileMountSystem.h | 6 + panda/src/express/virtualFileSimple.cxx | 133 +++- panda/src/express/virtualFileSimple.h | 9 +- panda/src/express/virtualFileSystem.cxx | 211 +++++- panda/src/express/virtualFileSystem.h | 14 +- panda/src/pgraph/bamFile.cxx | 3 +- panda/src/putil/bamCache.cxx | 80 +- panda/src/testbed/pview.cxx | 6 +- 27 files changed, 2011 insertions(+), 181 deletions(-) create mode 100644 panda/src/express/virtualFileMountRamdisk.I create mode 100644 panda/src/express/virtualFileMountRamdisk.cxx create mode 100644 panda/src/express/virtualFileMountRamdisk.h diff --git a/direct/src/stdpy/file.py b/direct/src/stdpy/file.py index 08f33f0b7e..062e801d29 100644 --- a/direct/src/stdpy/file.py +++ b/direct/src/stdpy/file.py @@ -224,11 +224,15 @@ class file: xreadlines = readlines def seek(self, offset, whence = 0): + print "seek(%s)" % (offset) if self.__stream: + print "clear" self.__stream.clear() # clear eof flag if self.__reader: + print "seekg" self.__stream.seekg(offset, whence) if self.__writer: + print "seekp" self.__stream.seekp(offset, whence) def tell(self): diff --git a/dtool/src/prc/streamWrapper.cxx b/dtool/src/prc/streamWrapper.cxx index bc5ca10f7f..ad298a8bba 100644 --- a/dtool/src/prc/streamWrapper.cxx +++ b/dtool/src/prc/streamWrapper.cxx @@ -216,6 +216,42 @@ seek_write(streamsize pos, const char *buffer, streamsize num_bytes, release(); } +//////////////////////////////////////////////////////////////////// +// Function: OStreamWrapper::seek_eof_write +// Access: Public +// Description: Atomically seeks to the end of the file, and writes a +// number of bytes to the stream. Returns whether a +// failure condition was detected by the operation. +//////////////////////////////////////////////////////////////////// +void OStreamWrapper:: +seek_eof_write(const char *buffer, streamsize num_bytes, bool &fail) { + acquire(); + _ostream->clear(); + _ostream->seekp(0, ios::end); + _ostream->write(buffer, num_bytes); + fail = _ostream->fail(); + release(); +} + +//////////////////////////////////////////////////////////////////// +// Function: OStreamWrapper::seek_ppos_eof +// Access: Public +// Description: Atomically seeks to EOF and returns the ppos there; +// that is, returns the file size. Note that the EOF +// might have been moved in another thread by the time +// this method returns. +//////////////////////////////////////////////////////////////////// +streamsize OStreamWrapper:: +seek_ppos_eof() { + streamsize pos; + acquire(); + _ostream->seekp(0, ios::end); + pos = _ostream->tellp(); + release(); + + return pos; +} + //////////////////////////////////////////////////////////////////// // Function: StreamWrapper::Destructor // Access: Published diff --git a/dtool/src/prc/streamWrapper.h b/dtool/src/prc/streamWrapper.h index 2e603162e7..c8ce024b1b 100644 --- a/dtool/src/prc/streamWrapper.h +++ b/dtool/src/prc/streamWrapper.h @@ -91,7 +91,9 @@ public: void write(const char *buffer, streamsize num_bytes); void write(const char *buffer, streamsize num_bytes, bool &fail); void seek_write(streamsize pos, const char *buffer, streamsize num_bytes, bool &fail); + void seek_eof_write(const char *buffer, streamsize num_bytes, bool &fail); INLINE bool put(char c); + streamsize seek_ppos_eof(); private: ostream *_ostream; diff --git a/panda/src/egg/eggData.cxx b/panda/src/egg/eggData.cxx index 2bbcdc6a29..69fcfedcb6 100644 --- a/panda/src/egg/eggData.cxx +++ b/panda/src/egg/eggData.cxx @@ -234,11 +234,9 @@ collapse_equivalent_materials() { //////////////////////////////////////////////////////////////////// bool EggData:: write_egg(Filename filename) { - filename.unlink(); - filename.set_text(); - VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); - + filename.set_text(); + vfs->delete_file(filename); ostream *file = vfs->open_write_file(filename, true, true); if (file == (ostream *)NULL) { egg_cat.error() << "Unable to open " << filename << " for writing.\n"; diff --git a/panda/src/express/Sources.pp b/panda/src/express/Sources.pp index 2e8054df8a..49e6a7b897 100644 --- a/panda/src/express/Sources.pp +++ b/panda/src/express/Sources.pp @@ -63,8 +63,10 @@ virtualFile.I virtualFileList.I virtualFileList.h virtualFileMount.h \ virtualFileComposite.h virtualFileComposite.I virtualFile.h \ virtualFileMount.I virtualFileMountMultifile.h \ - virtualFileMountMultifile.I virtualFileMountSystem.h \ - virtualFileMountSystem.I virtualFileSimple.h virtualFileSimple.I \ + virtualFileMountMultifile.I \ + virtualFileMountRamdisk.h virtualFileMountRamdisk.I \ + virtualFileMountSystem.h virtualFileMountSystem.I \ + virtualFileSimple.h virtualFileSimple.I \ virtualFileSystem.h virtualFileSystem.I \ weakPointerCallback.I weakPointerCallback.h \ weakPointerTo.I weakPointerTo.h \ @@ -116,7 +118,9 @@ vector_uchar.cxx vector_float.cxx \ virtualFileComposite.cxx virtualFile.cxx virtualFileList.cxx \ virtualFileMount.cxx \ - virtualFileMountMultifile.cxx virtualFileMountSystem.cxx \ + virtualFileMountMultifile.cxx \ + virtualFileMountRamdisk.cxx \ + virtualFileMountSystem.cxx \ virtualFileSimple.cxx virtualFileSystem.cxx \ weakPointerCallback.cxx \ weakPointerTo.cxx \ @@ -180,8 +184,10 @@ virtualFile.I virtualFileList.I virtualFileList.h virtualFileMount.h \ virtualFileComposite.h virtualFileComposite.I virtualFile.h \ virtualFileMount.I virtualFileMountMultifile.h \ - virtualFileMountMultifile.I virtualFileMountSystem.h \ - virtualFileMountSystem.I virtualFileSimple.h virtualFileSimple.I \ + virtualFileMountMultifile.I \ + virtualFileMountRamdisk.h virtualFileMountRamdisk.I \ + virtualFileMountSystem.h virtualFileMountSystem.I \ + virtualFileSimple.h virtualFileSimple.I \ virtualFileSystem.h virtualFileSystem.I \ weakPointerCallback.I weakPointerCallback.h \ weakPointerTo.I weakPointerTo.h \ diff --git a/panda/src/express/config_express.cxx b/panda/src/express/config_express.cxx index 350a0f3ca2..7bb47565d9 100644 --- a/panda/src/express/config_express.cxx +++ b/panda/src/express/config_express.cxx @@ -23,6 +23,7 @@ #include "virtualFileComposite.h" #include "virtualFileMount.h" #include "virtualFileMountMultifile.h" +#include "virtualFileMountRamdisk.h" #include "virtualFileMountSystem.h" #include "virtualFileSimple.h" #include "fileReference.h" @@ -106,6 +107,7 @@ init_libexpress() { VirtualFileComposite::init_type(); VirtualFileMount::init_type(); VirtualFileMountMultifile::init_type(); + VirtualFileMountRamdisk::init_type(); VirtualFileMountSystem::init_type(); VirtualFileSimple::init_type(); FileReference::init_type(); diff --git a/panda/src/express/express_composite2.cxx b/panda/src/express/express_composite2.cxx index ad4a0543a0..f7d4177826 100644 --- a/panda/src/express/express_composite2.cxx +++ b/panda/src/express/express_composite2.cxx @@ -18,6 +18,7 @@ #include "virtualFileList.cxx" #include "virtualFileMount.cxx" #include "virtualFileMountMultifile.cxx" +#include "virtualFileMountRamdisk.cxx" #include "virtualFileMountSystem.cxx" #include "virtualFileSimple.cxx" #include "virtualFileSystem.cxx" diff --git a/panda/src/express/subStream.I b/panda/src/express/subStream.I index 702e9a6211..cfa5009bcf 100644 --- a/panda/src/express/subStream.I +++ b/panda/src/express/subStream.I @@ -48,7 +48,7 @@ ISubStream(IStreamWrapper *source, streampos start, streampos end) : istream(&_b INLINE ISubStream &ISubStream:: open(IStreamWrapper *source, streampos start, streampos end) { clear((ios_iostate)0); - _buf.open(source, start, end); + _buf.open(source, NULL, start, end, false); return *this; } @@ -64,3 +64,103 @@ close() { return *this; } +//////////////////////////////////////////////////////////////////// +// Function: OSubStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OSubStream:: +OSubStream() : ostream(&_buf) { +} + +//////////////////////////////////////////////////////////////////// +// Function: OSubStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OSubStream:: +OSubStream(OStreamWrapper *dest, streampos start, streampos end, bool append) : ostream(&_buf) { + open(dest, start, end, append); +} + +//////////////////////////////////////////////////////////////////// +// Function: OSubStream::open +// Access: Public +// Description: Starts the SubStream reading from the indicated +// dest, with the first character being the character +// at position "start" within the dest, for end - +// start total characters. The character at "end" +// within the dest will never be read; this will +// appear to be EOF. +// +// If end is zero, it indicates that the OSubStream will +// continue until the end of the dest stream. +//////////////////////////////////////////////////////////////////// +INLINE OSubStream &OSubStream:: +open(OStreamWrapper *dest, streampos start, streampos end, bool append) { + clear((ios_iostate)0); + _buf.open(NULL, dest, start, end, append); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: OSubStream::close +// Access: Public +// Description: Resets the SubStream to empty, but does not actually +// close the dest ostream. +//////////////////////////////////////////////////////////////////// +INLINE OSubStream &OSubStream:: +close() { + _buf.close(); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: SubStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE SubStream:: +SubStream() : iostream(&_buf) { +} + +//////////////////////////////////////////////////////////////////// +// Function: SubStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE SubStream:: +SubStream(StreamWrapper *nested, streampos start, streampos end, bool append) : iostream(&_buf) { + open(nested, start, end, append); +} + +//////////////////////////////////////////////////////////////////// +// Function: SubStream::open +// Access: Public +// Description: Starts the SubStream reading and writing from the +// indicated nested stream, within the indicated range. +// "end" is the first character outside of the range. +// +// If end is zero, it indicates that the SubStream will +// continue until the end of the nested stream. +//////////////////////////////////////////////////////////////////// +INLINE SubStream &SubStream:: +open(StreamWrapper *nested, streampos start, streampos end, bool append) { + clear((ios_iostate)0); + _buf.open(nested, nested, start, end, append); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: SubStream::close +// Access: Public +// Description: Resets the SubStream to empty, but does not actually +// close the nested ostream. +//////////////////////////////////////////////////////////////////// +INLINE SubStream &SubStream:: +close() { + _buf.close(); + return *this; +} + + diff --git a/panda/src/express/subStream.h b/panda/src/express/subStream.h index f2407df756..71171f350a 100644 --- a/panda/src/express/subStream.h +++ b/panda/src/express/subStream.h @@ -43,6 +43,47 @@ private: SubStreamBuf _buf; }; +//////////////////////////////////////////////////////////////////// +// Class : OSubStream +// Description : An ostream object that presents a subwindow into +// another ostream. The first character written to this +// stream will be the "start" character in the dest +// istream; no characters may be written to character +// "end" or later (unless end is zero). +// +// The dest stream must be one that we can randomly +// seek within. The resulting OSubStream will also +// support arbitrary seeks. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS OSubStream : public ostream { +PUBLISHED: + INLINE OSubStream(); + INLINE OSubStream(OStreamWrapper *dest, streampos start, streampos end, bool append = false); + + INLINE OSubStream &open(OStreamWrapper *dest, streampos start, streampos end, bool append = false); + INLINE OSubStream &close(); + +private: + SubStreamBuf _buf; +}; + +//////////////////////////////////////////////////////////////////// +// Class : SubStream +// Description : Combined ISubStream and OSubStream for bidirectional +// I/O. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS SubStream : public iostream { +PUBLISHED: + INLINE SubStream(); + INLINE SubStream(StreamWrapper *nested, streampos start, streampos end, bool append = false); + + INLINE SubStream &open(StreamWrapper *nested, streampos start, streampos end, bool append = false); + INLINE SubStream &close(); + +private: + SubStreamBuf _buf; +}; + #include "subStream.I" #endif diff --git a/panda/src/express/subStreamBuf.cxx b/panda/src/express/subStreamBuf.cxx index 17b188973f..42988d7766 100644 --- a/panda/src/express/subStreamBuf.cxx +++ b/panda/src/express/subStreamBuf.cxx @@ -20,6 +20,8 @@ typedef int streamsize; #endif /* HAVE_STREAMSIZE */ +static const size_t substream_buffer_size = 4096; + //////////////////////////////////////////////////////////////////// // Function: SubStreamBuf::Constructor // Access: Public @@ -28,6 +30,7 @@ typedef int streamsize; SubStreamBuf:: SubStreamBuf() { _source = (IStreamWrapper *)NULL; + _dest = (OStreamWrapper *)NULL; // _start is the streampos of the first byte of the SubStream within // its parent stream. @@ -38,28 +41,29 @@ SubStreamBuf() { // continues to the end of the parent stream, wherever that is. _end = 0; - // _cur is the streampos of the end of the read buffer (that is, - // egptr()) within the parent stream. By comparing _cur to gpos(), - // we can determine the actual current file position. - _cur = 0; - - // _unused counts the number of bytes at the beginning of the buffer - // that are unused. Usually this is 0 after a read, but when we - // reach the end of the file we might not need the whole buffer to - // read the last bit, so the first part of the buffer is unused. - // This is important to prevent us from inadvertently seeking into - // the unused part of the buffer. - _unused = 0; + // _gpos is the streampos of the end of the read buffer (that is, + // egptr()) within the parent stream. By comparing _gpos to gpos(), + // we can determine the actual current file position. _ppos is the + // similar pos, for the write pointer. + _gpos = 0; + _ppos = 0; #ifdef PHAVE_IOSTREAM - // The new-style iostream library doesn't seem to support allocate(). - _buffer = (char *)PANDA_MALLOC_ARRAY(4096); - char *ebuf = _buffer + 4096; - setg(_buffer, ebuf, ebuf); + _buffer = (char *)PANDA_MALLOC_ARRAY(substream_buffer_size * 2); + char *ebuf = _buffer + substream_buffer_size * 2; + char *mbuf = _buffer + substream_buffer_size; + setg(_buffer, mbuf, mbuf); + setp(mbuf, ebuf); #else allocate(); - setg(base(), ebuf(), ebuf()); + // Chop the buffer in half. The bottom half goes to the get buffer; + // the top half goes to the put buffer. + char *b = base(); + char *t = ebuf(); + char *m = b + (t - b) / 2; + setg(b, m, m); + setp(b, m); #endif } @@ -82,14 +86,14 @@ SubStreamBuf:: // Description: //////////////////////////////////////////////////////////////////// void SubStreamBuf:: -open(IStreamWrapper *source, streampos start, streampos end) { +open(IStreamWrapper *source, OStreamWrapper *dest, streampos start, streampos end, bool append) { _source = source; + _dest = dest; _start = start; _end = end; - _cur = _start; - - // Initially, the entire buffer is unused. Duh. - _unused = egptr() - eback(); + _append = append; + _gpos = _start; + _ppos = _start; } //////////////////////////////////////////////////////////////////// @@ -99,11 +103,19 @@ open(IStreamWrapper *source, streampos start, streampos end) { //////////////////////////////////////////////////////////////////// void SubStreamBuf:: close() { + // Make sure the write buffer is flushed. + sync(); + _source = (IStreamWrapper *)NULL; + _dest = (OStreamWrapper *)NULL; _start = 0; _end = 0; - _cur = 0; - _unused = 0; + + _gpos = 0; + _ppos = 0; + + pbump(pbase() - pptr()); + gbump(egptr() - gptr()); } //////////////////////////////////////////////////////////////////// @@ -112,55 +124,110 @@ close() { // Description: Implements seeking within the stream. //////////////////////////////////////////////////////////////////// streampos SubStreamBuf:: -seekoff(streamoff off, ios_seekdir dir, ios_openmode mode) { - // Invariant: _cur points to the file location of the buffer at - // egptr(). +seekoff(streamoff off, ios_seekdir dir, ios_openmode which) { + streampos result = -1; - // Use this to determine the actual file position right now. - size_t n = egptr() - gptr(); - streampos cur_pos = _cur - (streampos)n; - streampos new_pos = cur_pos; + // Sync the iostream buffer first. + sync(); - // Now adjust the data pointer appropriately. - - // Casting this to int to prevent GCC 3.2 compiler warnings. Very - // suspicious, need to investigate further. - switch ((int)dir) { - case ios::beg: - new_pos = _start + off; - break; - - case ios::cur: - new_pos = cur_pos + off; - break; - - case ios::end: - if (_end == (streampos)0) { - // If the end of the file is unspecified, we have to seek to - // find it. - new_pos = _source->seek_gpos_eof() + off; - - } else { - new_pos = _end + off; - } - break; - } - - new_pos = max(_start, new_pos); - streamsize delta = new_pos - cur_pos; - - if (gptr() + delta >= eback() + _unused && gptr() + delta <= egptr()) { - // If we can get away with just bumping the gptr within the - // buffer, do so. - gbump(delta); - - } else { - // Otherwise, empty the buffer and force it to call underflow(). + if (which & ios::in) { + // Determine the current file position. + size_t n = egptr() - gptr(); gbump(n); - _cur = new_pos; + _gpos -= n; + nassertr(_gpos >= 0, EOF); + streampos cur_pos = _gpos; + streampos new_pos = cur_pos; + + // Now adjust the data pointer appropriately. + switch (dir) { + case ios::beg: + new_pos = (streampos)off + _start; + break; + + case ios::cur: + new_pos = (streampos)((streamoff)cur_pos + off); + break; + + case ios::end: + if (_end == (streampos)0) { + // If the end of the file is unspecified, we have to seek to + // find it. + new_pos = _source->seek_gpos_eof() + off; + + } else { + new_pos = _end + off; + } + break; + + default: + // Shouldn't get here. + break; + } + + if (new_pos < _start) { + // Can't seek before beginning of file. + return EOF; + } + + if (_end != 0 && new_pos > _end) { + // Can't seek past end of file. + return EOF; + } + + _gpos = new_pos; + nassertr(_gpos >= 0, EOF); + result = new_pos - _start; } - return new_pos - _start; + if (which & ios::out) { + // Determine the current file position. + size_t n = pptr() - pbase(); + streampos cur_pos = _ppos + (streamoff)n; + streampos new_pos = cur_pos; + + // Now adjust the data pointer appropriately. + switch (dir) { + case ios::beg: + new_pos = (streampos)off + _start; + break; + + case ios::cur: + new_pos = (streampos)((streamoff)cur_pos + off); + break; + + case ios::end: + if (_end == (streampos)0) { + // If the end of the file is unspecified, we have to seek to + // find it. + new_pos = _dest->seek_ppos_eof() + off; + + } else { + new_pos = _end + off; + } + break; + + default: + // Shouldn't get here. + break; + } + + if (new_pos < _start) { + // Can't seek before beginning of file. + return EOF; + } + + if (_end != 0 && new_pos > _end) { + // Can't seek past end of file. + return EOF; + } + + _ppos = new_pos; + nassertr(_ppos >= 0, EOF); + result = new_pos - _start; + } + + return result; } //////////////////////////////////////////////////////////////////// @@ -178,8 +245,8 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode mode) { // function as well. //////////////////////////////////////////////////////////////////// streampos SubStreamBuf:: -seekpos(streampos pos, ios_openmode mode) { - return seekoff(pos, ios::beg, mode); +seekpos(streampos pos, ios_openmode which) { + return seekoff(pos, ios::beg, which); } //////////////////////////////////////////////////////////////////// @@ -189,8 +256,48 @@ seekpos(streampos pos, ios_openmode mode) { // internal buffer is filled, plus one character. //////////////////////////////////////////////////////////////////// int SubStreamBuf:: -overflow(int c) { - // We don't support ostream. +overflow(int ch) { + bool okflag = true; + + size_t n = pptr() - pbase(); + if (n != 0) { + if (_end != 0 && _ppos + (streampos)n > _end) { + // Don't allow reading past the end of the file. + n = (size_t)(_end - _ppos); + if (n == 0) { + // No room. + return EOF; + } + } + + nassertr(_dest != NULL, EOF); + bool fail = false; + if (_append) { + _dest->seek_eof_write(pbase(), n, fail); + } else { + _dest->seek_write(_ppos, pbase(), n, fail); + } + _ppos += n; + pbump(-(int)n); + if (fail) { + okflag = false; + } + } + + if (okflag && ch != EOF) { + if (pptr() != epptr()) { + // Store the extra character back in the buffer. + *(pptr()) = ch; + pbump(1); + } else { + // No room to store ch. + okflag = false; + } + } + + if (!okflag) { + return EOF; + } return 0; } @@ -202,8 +309,23 @@ overflow(int c) { //////////////////////////////////////////////////////////////////// int SubStreamBuf:: sync() { - size_t n = egptr() - gptr(); - gbump(n); + size_t n = pptr() - pbase(); + + if (n != 0) { + nassertr(_dest != NULL, EOF); + bool fail = false; + if (_append) { + _dest->seek_eof_write(pbase(), n, fail); + } else { + _dest->seek_write(_ppos, pbase(), n, fail); + } + _ppos += n; + pbump(-(int)n); + + if (fail) { + return EOF; + } + } return 0; } @@ -218,54 +340,48 @@ int SubStreamBuf:: underflow() { // Sometimes underflow() is called even if the buffer is not empty. if (gptr() >= egptr()) { - if (_end != (streampos)0 && _cur >= _end) { - // We're done. - return EOF; - } - + sync(); + + // Mark the buffer filled (with buffer_size bytes). size_t buffer_size = egptr() - eback(); - size_t num_bytes; - if (_end == (streampos)0 || _end - _cur > (streampos)buffer_size) { - // We have enough bytes in the input stream to fill our buffer. - num_bytes = buffer_size; - } else { - // We won't quite fill the buffer. - num_bytes = (size_t)(_end - _cur); + gbump(-(int)buffer_size); + + streamsize num_bytes = buffer_size; + if (_end != 0 && _gpos + (streampos)num_bytes > _end) { + // Don't allow reading past the end of the file. + streamsize new_num_bytes = _end - _gpos; + if (new_num_bytes == 0) { + gbump(buffer_size); + return EOF; + } + + // We won't be filling the entire buffer. Fill in only at the + // end of the buffer. + size_t delta = num_bytes - new_num_bytes; + gbump(delta); + num_bytes = new_num_bytes; + nassertr(egptr() - gptr() == num_bytes, EOF); } - - gbump(-(int)num_bytes); - nassertr(gptr() + num_bytes <= egptr(), EOF); - + + nassertr(_source != NULL, EOF); streamsize read_count; bool eof; - _source->seek_read(_cur, gptr(), num_bytes, read_count, eof); + _source->seek_read(_gpos, gptr(), num_bytes, read_count, eof); + _gpos += read_count; - if (read_count != (streamsize)num_bytes) { + if (read_count != num_bytes) { // Oops, we didn't read what we thought we would. if (read_count == 0) { - _unused = buffer_size; - if (_end != (streampos)0) { - _end = _cur; - } gbump(num_bytes); return EOF; } // Slide what we did read to the top of the buffer. - nassertr(read_count < (int)num_bytes, EOF); + nassertr(read_count < num_bytes, EOF); size_t delta = num_bytes - read_count; memmove(gptr() + delta, gptr(), read_count); gbump(delta); } - - // Now record whatever bytes at the beginning of the buffer are - // unused, so we won't try to seek into that area. - _unused = buffer_size - read_count; - - // Invariant: _cur points to the file location of the buffer at - // egptr(). - - _cur += read_count; } return (unsigned char)*gptr(); diff --git a/panda/src/express/subStreamBuf.h b/panda/src/express/subStreamBuf.h index 05d7e38067..817d224ab3 100644 --- a/panda/src/express/subStreamBuf.h +++ b/panda/src/express/subStreamBuf.h @@ -27,11 +27,11 @@ public: SubStreamBuf(); virtual ~SubStreamBuf(); - void open(IStreamWrapper *source, streampos start, streampos end); + void open(IStreamWrapper *source, OStreamWrapper *dest, streampos start, streampos end, bool append); void close(); - virtual streampos seekoff(streamoff off, ios_seekdir dir, ios_openmode mode); - virtual streampos seekpos(streampos pos, ios_openmode mode); + virtual streampos seekoff(streamoff off, ios_seekdir dir, ios_openmode which); + virtual streampos seekpos(streampos pos, ios_openmode which); protected: virtual int overflow(int c); @@ -40,10 +40,12 @@ protected: private: IStreamWrapper *_source; + OStreamWrapper *_dest; streampos _start; streampos _end; - streampos _cur; - size_t _unused; + bool _append; + streampos _gpos; + streampos _ppos; char *_buffer; }; diff --git a/panda/src/express/virtualFile.cxx b/panda/src/express/virtualFile.cxx index 1c32e6bd50..afac7f62bc 100644 --- a/panda/src/express/virtualFile.cxx +++ b/panda/src/express/virtualFile.cxx @@ -64,6 +64,53 @@ is_writable() const { return false; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::delete_file +// Access: Public +// Description: Attempts to delete this file or directory. This can +// remove a single file or an empty directory. It will +// not remove a nonempty directory. Returns true on +// success, false on failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFile:: +delete_file() { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::rename_file +// Access: Public +// Description: Attempts to move or rename this file or directory. +// If the original file is an ordinary file, it will +// quietly replace any already-existing file in the new +// filename (but not a directory). If the original file +// is a directory, the new filename must not already +// exist. +// +// If the file is a directory, the new filename must be +// within the same mount point. If the file is an +// ordinary file, the new filename may be anywhere; but +// if it is not within the same mount point then the +// rename operation is automatically performed as a +// two-step copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFile:: +rename_file(VirtualFile *new_file) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::copy_file +// Access: Public +// Description: Attempts to copy the contents of this file to the +// indicated file. Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFile:: +copy_file(VirtualFile *new_file) { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFile::scan_directory // Access: Published @@ -349,6 +396,28 @@ get_system_info(SubfileInfo &info) { return false; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::atomic_compare_and_exchange_contents +// Access: Public, Virtual +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFile:: +atomic_compare_and_exchange_contents(string &orig_contents, + const string &old_contents, + const string &new_contents) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFile::atomic_read_contents +// Access: Public, Virtual +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFile:: +atomic_read_contents(string &contents) const { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFile::read_file // Access: Public diff --git a/panda/src/express/virtualFile.h b/panda/src/express/virtualFile.h index 4eb5cdaf0a..39b5cff6cf 100644 --- a/panda/src/express/virtualFile.h +++ b/panda/src/express/virtualFile.h @@ -48,6 +48,10 @@ PUBLISHED: virtual bool is_regular_file() const; virtual bool is_writable() const; + BLOCKING virtual bool delete_file(); + BLOCKING virtual bool rename_file(VirtualFile *new_file); + BLOCKING virtual bool copy_file(VirtualFile *new_file); + BLOCKING PT(VirtualFileList) scan_directory() const; void output(ostream &out) const; @@ -75,6 +79,9 @@ PUBLISHED: virtual bool get_system_info(SubfileInfo &info); public: + virtual bool atomic_compare_and_exchange_contents(string &orig_contents, const string &old_contents, const string &new_contents); + virtual bool atomic_read_contents(string &contents) const; + INLINE void set_original_filename(const Filename &filename); bool read_file(string &result, bool auto_unwrap) const; virtual bool read_file(pvector &result, bool auto_unwrap) const; diff --git a/panda/src/express/virtualFileMount.cxx b/panda/src/express/virtualFileMount.cxx index e372389957..2603889c38 100644 --- a/panda/src/express/virtualFileMount.cxx +++ b/panda/src/express/virtualFileMount.cxx @@ -75,6 +75,48 @@ create_file(const Filename &file) { return false; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMount::delete_file +// Access: Public, Virtual +// Description: Attempts to delete the indicated file or directory +// within the mount. This can remove a single file or +// an empty directory. It will not remove a nonempty +// directory. Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMount:: +delete_file(const Filename &file) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMount::rename_file +// Access: Public +// Description: Attempts to rename the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, this will be +// attempted again with a copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMount:: +rename_file(const Filename &orig_filename, const Filename &new_filename) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMount::copy_file +// Access: Public +// Description: Attempts to copy the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, the copy will be +// performed by explicit read-and-write operations. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMount:: +copy_file(const Filename &orig_filename, const Filename &new_filename) { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMount::make_directory // Access: Public, Virtual @@ -324,6 +366,28 @@ get_system_info(const Filename &file, SubfileInfo &info) { return false; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMount::atomic_compare_and_exchange_contents +// Access: Public, Virtual +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMount:: +atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, + const string &old_contents, + const string &new_contents) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMount::atomic_read_contents +// Access: Public, Virtual +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMount:: +atomic_read_contents(const Filename &file, string &contents) const { + return false; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMount::output // Access: Public, Virtual diff --git a/panda/src/express/virtualFileMount.h b/panda/src/express/virtualFileMount.h index 555d7f9f70..fa36b26062 100644 --- a/panda/src/express/virtualFileMount.h +++ b/panda/src/express/virtualFileMount.h @@ -48,6 +48,9 @@ public: virtual bool has_file(const Filename &file) const=0; virtual bool create_file(const Filename &file); virtual bool make_directory(const Filename &file); + virtual bool delete_file(const Filename &file); + virtual bool rename_file(const Filename &orig_filename, const Filename &new_filename); + virtual bool copy_file(const Filename &orig_filename, const Filename &new_filename); virtual bool is_directory(const Filename &file) const=0; virtual bool is_regular_file(const Filename &file) const=0; virtual bool is_writable(const Filename &file) const; @@ -78,6 +81,9 @@ public: virtual bool scan_directory(vector_string &contents, const Filename &dir) const=0; + virtual bool atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, const string &old_contents, const string &new_contents); + virtual bool atomic_read_contents(const Filename &file, string &contents) const; + PUBLISHED: virtual void output(ostream &out) const; virtual void write(ostream &out) const; diff --git a/panda/src/express/virtualFileMountRamdisk.I b/panda/src/express/virtualFileMountRamdisk.I new file mode 100644 index 0000000000..3aba493399 --- /dev/null +++ b/panda/src/express/virtualFileMountRamdisk.I @@ -0,0 +1,54 @@ +// Filename: virtualFileMountRamdisk.I +// Created by: drose (19Sep11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::FileBase::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE VirtualFileMountRamdisk::FileBase:: +FileBase(const string &basename) : _basename(basename), _timestamp(0) { +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::FileBase::operator < +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool VirtualFileMountRamdisk::FileBase:: +operator < (const FileBase &other) const { + return _basename < other._basename; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::File::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE VirtualFileMountRamdisk::File:: +File(const string &basename) : + FileBase(basename), + _wrapper(_data) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE VirtualFileMountRamdisk::Directory:: +Directory(const string &basename) : FileBase(basename) { +} diff --git a/panda/src/express/virtualFileMountRamdisk.cxx b/panda/src/express/virtualFileMountRamdisk.cxx new file mode 100644 index 0000000000..7ceeb8fc32 --- /dev/null +++ b/panda/src/express/virtualFileMountRamdisk.cxx @@ -0,0 +1,707 @@ +// Filename: virtualFileMountRamdisk.cxx +// Created by: drose (19Sep11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "virtualFileMountRamdisk.h" +#include "subStream.h" +#include "dcast.h" + +TypeHandle VirtualFileMountRamdisk::_type_handle; +TypeHandle VirtualFileMountRamdisk::FileBase::_type_handle; +TypeHandle VirtualFileMountRamdisk::File::_type_handle; +TypeHandle VirtualFileMountRamdisk::Directory::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +VirtualFileMountRamdisk:: +VirtualFileMountRamdisk() : _root("") { +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::has_file +// Access: Public, Virtual +// Description: Returns true if the indicated file exists within the +// mount system. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +has_file(const Filename &file) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return (f != NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::create_file +// Access: Public, Virtual +// Description: Attempts to create the indicated file within the +// mount, if it does not already exist. Returns true on +// success (or if the file already exists), or false if +// it cannot be created. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +create_file(const Filename &file) { + _lock.acquire(); + PT(File) f = _root.do_create_file(file); + _lock.release(); + return (f != NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::delete_file +// Access: Public, Virtual +// Description: Attempts to delete the indicated file or directory +// within the mount. This can remove a single file or +// an empty directory. It will not remove a nonempty +// directory. Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +delete_file(const Filename &file) { + _lock.acquire(); + PT(FileBase) f = _root.do_delete_file(file); + _lock.release(); + return (f != NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::rename_file +// Access: Public +// Description: Attempts to rename the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, this will be +// attempted again with a copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +rename_file(const Filename &orig_filename, const Filename &new_filename) { + _lock.acquire(); + PT(FileBase) orig_fb = _root.do_find_file(orig_filename); + if (orig_fb == NULL) { + _lock.release(); + return false; + } + + if (orig_fb->is_directory()) { + // Rename the directory. + Directory *orig_d = DCAST(Directory, orig_fb); + PT(Directory) new_d = _root.do_make_directory(new_filename); + if (new_d == NULL || !new_d->_files.empty()) { + _lock.release(); + return false; + } + + if (express_cat.is_debug()) { + express_cat.debug() + << "Renaming ramdisk directory " << orig_filename << " to " << new_filename << "\n"; + } + + new_d->_files.swap(orig_d->_files); + _root.do_delete_file(orig_filename); + _lock.release(); + return true; + } + + // Rename the file. + File *orig_f = DCAST(File, orig_fb); + PT(File) new_f = _root.do_create_file(new_filename); + if (new_f == NULL) { + _lock.release(); + return false; + } + + if (express_cat.is_debug()) { + express_cat.debug() + << "Renaming ramdisk file " << orig_filename << " to " << new_filename << "\n"; + } + + new_f->_data.str(orig_f->_data.str()); + _root.do_delete_file(orig_filename); + + _lock.release(); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::copy_file +// Access: Public +// Description: Attempts to copy the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, the copy will be +// performed by explicit read-and-write operations. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +copy_file(const Filename &orig_filename, const Filename &new_filename) { + _lock.acquire(); + PT(FileBase) orig_fb = _root.do_find_file(orig_filename); + if (orig_fb == NULL || orig_fb->is_directory()) { + _lock.release(); + return false; + } + + // Copy the file. + File *orig_f = DCAST(File, orig_fb); + PT(File) new_f = _root.do_create_file(new_filename); + if (new_f == NULL) { + _lock.release(); + return false; + } + + if (express_cat.is_debug()) { + express_cat.debug() + << "Copying ramdisk file " << orig_filename << " to " << new_filename << "\n"; + } + + new_f->_data.str(orig_f->_data.str()); + + _lock.release(); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::make_directory +// Access: Public, Virtual +// Description: Attempts to create the indicated file within the +// mount, if it does not already exist. Returns true on +// success, or false if it cannot be created. If the +// directory already existed prior to this call, may +// return either true or false. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +make_directory(const Filename &file) { + _lock.acquire(); + PT(Directory) f = _root.do_make_directory(file); + _lock.release(); + return (f != NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::is_directory +// Access: Public, Virtual +// Description: Returns true if the indicated file exists within the +// mount system and is a directory. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +is_directory(const Filename &file) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return (f != NULL && f->is_directory()); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::is_regular_file +// Access: Public, Virtual +// Description: Returns true if the indicated file exists within the +// mount system and is a regular file. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +is_regular_file(const Filename &file) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return (f != NULL && !f->is_directory()); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::is_writable +// Access: Public, Virtual +// Description: Returns true if the named file or directory may be +// written to, false otherwise. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +is_writable(const Filename &file) const { + return has_file(file); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::open_read_file +// Access: Public, Virtual +// Description: Opens the file for reading, if it exists. Returns a +// newly allocated istream on success (which you should +// eventually delete when you are done reading). +// Returns NULL on failure. +//////////////////////////////////////////////////////////////////// +istream *VirtualFileMountRamdisk:: +open_read_file(const Filename &file) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + if (f == (FileBase *)NULL || f->is_directory()) { + return false; + } + + File *f2 = DCAST(File, f); + return new ISubStream(&f2->_wrapper, 0, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::open_write_file +// Access: Published, Virtual +// Description: Opens the file for writing. Returns a newly +// allocated ostream on success (which you should +// eventually delete when you are done writing). +// Returns NULL on failure. +//////////////////////////////////////////////////////////////////// +ostream *VirtualFileMountRamdisk:: +open_write_file(const Filename &file, bool truncate) { + _lock.acquire(); + PT(File) f = _root.do_create_file(file); + _lock.release(); + if (f == (File *)NULL) { + return false; + } + + if (truncate) { + // Reset to an empty string. + f->_data.str(string()); + } + + return new OSubStream(&f->_wrapper, 0, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::open_append_file +// Access: Published +// Description: Works like open_write_file(), but the file is opened +// in append mode. Like open_write_file, the returned +// pointer should eventually be passed to +// close_write_file(). +//////////////////////////////////////////////////////////////////// +ostream *VirtualFileMountRamdisk:: +open_append_file(const Filename &file) { + _lock.acquire(); + PT(File) f = _root.do_create_file(file); + _lock.release(); + if (f == (File *)NULL) { + return false; + } + + return new OSubStream(&f->_wrapper, 0, 0, true); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::open_read_write_file +// Access: Published, Virtual +// Description: Opens the file for writing. Returns a newly +// allocated iostream on success (which you should +// eventually delete when you are done writing). +// Returns NULL on failure. +//////////////////////////////////////////////////////////////////// +iostream *VirtualFileMountRamdisk:: +open_read_write_file(const Filename &file, bool truncate) { + _lock.acquire(); + PT(File) f = _root.do_create_file(file); + _lock.release(); + if (f == (File *)NULL) { + return false; + } + + return new SubStream(&f->_wrapper, 0, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::open_read_append_file +// Access: Published, Virtual +// Description: Works like open_read_write_file(), but the file is opened +// in append mode. Like open_read_write_file, the returned +// pointer should eventually be passed to +// close_read_write_file(). +//////////////////////////////////////////////////////////////////// +iostream *VirtualFileMountRamdisk:: +open_read_append_file(const Filename &file) { + _lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + _lock.release(); + if (f == (FileBase *)NULL || f->is_directory()) { + return false; + } + + File *f2 = DCAST(File, f); + return new SubStream(&f2->_wrapper, 0, 0, true); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::get_file_size +// Access: Published, Virtual +// Description: Returns the current size on disk (or wherever it is) +// of the already-open file. Pass in the stream that +// was returned by open_read_file(); some +// implementations may require this stream to determine +// the size. +//////////////////////////////////////////////////////////////////// +off_t VirtualFileMountRamdisk:: +get_file_size(const Filename &file, istream *stream) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + if (f == (FileBase *)NULL || f->is_directory()) { + return false; + } + + File *f2 = DCAST(File, f); + return f2->_data.str().length(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::get_file_size +// Access: Published, Virtual +// Description: Returns the current size on disk (or wherever it is) +// of the file before it has been opened. +//////////////////////////////////////////////////////////////////// +off_t VirtualFileMountRamdisk:: +get_file_size(const Filename &file) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + ((VirtualFileMountRamdisk *)this)->_lock.release(); + if (f == (FileBase *)NULL || f->is_directory()) { + return false; + } + + File *f2 = DCAST(File, f); + return f2->_data.str().length(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::get_timestamp +// Access: Published, Virtual +// Description: Returns a time_t value that represents the time the +// file was last modified, to within whatever precision +// the operating system records this information (on a +// Windows95 system, for instance, this may only be +// accurate to within 2 seconds). +// +// If the timestamp cannot be determined, either because +// it is not supported by the operating system or +// because there is some error (such as file not found), +// returns 0. +//////////////////////////////////////////////////////////////////// +time_t VirtualFileMountRamdisk:: +get_timestamp(const Filename &file) const { + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::scan_directory +// Access: Public, Virtual +// Description: Fills the given vector up with the list of filenames +// that are local to this directory, if the filename is +// a directory. Returns true if successful, or false if +// the file is not a directory or cannot be read. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +scan_directory(vector_string &contents, const Filename &dir) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(dir); + if (f == (FileBase *)NULL || !f->is_directory()) { + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return false; + } + + Directory *f2 = DCAST(Directory, f); + bool result = f2->do_scan_directory(contents); + + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::atomic_compare_and_exchange_contents +// Access: Public, Virtual +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, + const string &old_contents, + const string &new_contents) { + _lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + if (f == (FileBase *)NULL || f->is_directory()) { + _lock.release(); + return false; + } + + bool retval = false; + File *f2 = DCAST(File, f); + orig_contents = f2->_data.str(); + if (orig_contents == old_contents) { + f2->_data.str(new_contents); + retval = true; + } + + _lock.release(); + return retval; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::atomic_read_contents +// Access: Public, Virtual +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk:: +atomic_read_contents(const Filename &file, string &contents) const { + ((VirtualFileMountRamdisk *)this)->_lock.acquire(); + PT(FileBase) f = _root.do_find_file(file); + if (f == (FileBase *)NULL || f->is_directory()) { + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return false; + } + + File *f2 = DCAST(File, f); + contents = f2->_data.str(); + + ((VirtualFileMountRamdisk *)this)->_lock.release(); + return true; +} + + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::output +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void VirtualFileMountRamdisk:: +output(ostream &out) const { + out << "VirtualFileMountRamdisk"; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::FileBase::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +VirtualFileMountRamdisk::FileBase:: +~FileBase() { +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::FileBase::is_directory +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk::FileBase:: +is_directory() const { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::is_directory +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk::Directory:: +is_directory() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::do_find_file +// Access: Public +// Description: Recursively search for the file with the indicated +// name in this directory hierarchy. +//////////////////////////////////////////////////////////////////// +PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory:: +do_find_file(const string &filename) const { + size_t slash = filename.find('/'); + if (slash == string::npos) { + // Search for a file within the local directory. + FileBase tfile(filename); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + return (*fi); + } + return NULL; + } + + // A nested directory. Search for the directory name, then recurse. + string dirname = filename.substr(0, slash); + string remainder = filename.substr(slash + 1); + FileBase tfile(dirname); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + return DCAST(Directory, file.p())->do_find_file(remainder); + } + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::do_create_file +// Access: Public +// Description: Recursively search for the file with the indicated +// name in this directory hierarchy. If not found, +// creates a new file. +//////////////////////////////////////////////////////////////////// +PT(VirtualFileMountRamdisk::File) VirtualFileMountRamdisk::Directory:: +do_create_file(const string &filename) { + size_t slash = filename.find('/'); + if (slash == string::npos) { + // Search for a file within the local directory. + FileBase tfile(filename); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (!file->is_directory()) { + return DCAST(File, file.p()); + } + // Cannot create: a directory by the same name already exists. + return NULL; + } + + // Create a new file. + if (express_cat.is_debug()) { + express_cat.debug() + << "Making ramdisk file " << filename << "\n"; + } + PT(File) file = new File(filename); + _files.insert(file.p()); + return file; + } + + // A nested directory. Search for the directory name, then recurse. + string dirname = filename.substr(0, slash); + string remainder = filename.substr(slash + 1); + FileBase tfile(dirname); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + return DCAST(Directory, file.p())->do_create_file(remainder); + } + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::do_make_directory +// Access: Public +// Description: Recursively search for the file with the indicated +// name in this directory hierarchy. If not found, +// creates a new directory. +//////////////////////////////////////////////////////////////////// +PT(VirtualFileMountRamdisk::Directory) VirtualFileMountRamdisk::Directory:: +do_make_directory(const string &filename) { + size_t slash = filename.find('/'); + if (slash == string::npos) { + // Search for a file within the local directory. + FileBase tfile(filename); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + return DCAST(Directory, file.p()); + } + // Cannot create: a file by the same name already exists. + return NULL; + } + + // Create a new directory. + if (express_cat.is_debug()) { + express_cat.debug() + << "Making ramdisk directory " << filename << "\n"; + } + PT(Directory) file = new Directory(filename); + _files.insert(file.p()); + return file; + } + + // A nested directory. Search for the directory name, then recurse. + string dirname = filename.substr(0, slash); + string remainder = filename.substr(slash + 1); + FileBase tfile(dirname); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + return DCAST(Directory, file.p())->do_make_directory(remainder); + } + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::do_delete_file +// Access: Public +// Description: Recursively search for the file with the indicated +// name in this directory hierarchy, and removes it. +// Returns the removed FileBase object. +//////////////////////////////////////////////////////////////////// +PT(VirtualFileMountRamdisk::FileBase) VirtualFileMountRamdisk::Directory:: +do_delete_file(const string &filename) { + size_t slash = filename.find('/'); + if (slash == string::npos) { + // Search for a file within the local directory. + FileBase tfile(filename); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + Directory *dir = DCAST(Directory, file.p()); + if (!dir->_files.empty()) { + // Can't delete a nonempty directory. + return NULL; + } + } + _files.erase(fi); + return file; + } + return NULL; + } + + // A nested directory. Search for the directory name, then recurse. + string dirname = filename.substr(0, slash); + string remainder = filename.substr(slash + 1); + FileBase tfile(dirname); + tfile.local_object(); + Files::iterator fi = _files.find(&tfile); + if (fi != _files.end()) { + PT(FileBase) file = (*fi); + if (file->is_directory()) { + return DCAST(Directory, file.p())->do_delete_file(remainder); + } + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountRamdisk::Directory::do_scan_directory +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountRamdisk::Directory:: +do_scan_directory(vector_string &contents) const { + Files::const_iterator fi; + for (fi = _files.begin(); fi != _files.end(); ++fi) { + FileBase *file = (*fi); + contents.push_back(file->_basename); + } + + return true; +} diff --git a/panda/src/express/virtualFileMountRamdisk.h b/panda/src/express/virtualFileMountRamdisk.h new file mode 100644 index 0000000000..20c83982ed --- /dev/null +++ b/panda/src/express/virtualFileMountRamdisk.h @@ -0,0 +1,186 @@ +// Filename: virtualFileMountRamdisk.h +// Created by: drose (19Sep11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef VIRTUALFILEMOUNTRAMDISK_H +#define VIRTUALFILEMOUNTRAMDISK_H + +#include "pandabase.h" + +#include "virtualFileMount.h" +#include "mutexImpl.h" +#include "streamWrapper.h" + +//////////////////////////////////////////////////////////////////// +// Class : VirtualFileMountRamdisk +// Description : Simulates an actual directory on disk with in-memory +// storage. This is useful mainly for performing high +// level functions that expect disk I/O without actually +// writing files to disk. Naturally, there are +// significant limits to the size of the files that may +// be written with this system; and "files" written here +// are not automatically persistent between sessions. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS VirtualFileMountRamdisk : public VirtualFileMount { +PUBLISHED: + VirtualFileMountRamdisk(); + +public: + virtual bool has_file(const Filename &file) const; + virtual bool create_file(const Filename &file); + virtual bool make_directory(const Filename &file); + virtual bool delete_file(const Filename &file); + virtual bool rename_file(const Filename &orig_filename, const Filename &new_filename); + virtual bool copy_file(const Filename &orig_filename, const Filename &new_filename); + virtual bool is_directory(const Filename &file) const; + virtual bool is_regular_file(const Filename &file) const; + virtual bool is_writable(const Filename &file) const; + + virtual istream *open_read_file(const Filename &file) const; + virtual ostream *open_write_file(const Filename &file, bool truncate); + virtual ostream *open_append_file(const Filename &file); + virtual iostream *open_read_write_file(const Filename &file, bool truncate); + virtual iostream *open_read_append_file(const Filename &file); + + virtual off_t get_file_size(const Filename &file, istream *stream) const; + virtual off_t get_file_size(const Filename &file) const; + virtual time_t get_timestamp(const Filename &file) const; + + virtual bool scan_directory(vector_string &contents, + const Filename &dir) const; + + virtual bool atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, const string &old_contents, const string &new_contents); + virtual bool atomic_read_contents(const Filename &file, string &contents) const; + + virtual void output(ostream &out) const; + +private: + class FileBase; + class File; + class Directory; + + class FileBase : public TypedReferenceCount { + public: + INLINE FileBase(const string &basename); + virtual ~FileBase(); + INLINE bool operator < (const FileBase &other) const; + + virtual bool is_directory() const; + + string _basename; + time_t _timestamp; + + public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "VirtualFileMountRamdisk::FileBase", + TypedReferenceCount::get_class_type()); + } + + private: + static TypeHandle _type_handle; + }; + + class File : public FileBase { + public: + INLINE File(const string &basename); + + stringstream _data; + StreamWrapper _wrapper; + + public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FileBase::init_type(); + register_type(_type_handle, "VirtualFileMountRamdisk::File", + FileBase::get_class_type()); + } + + private: + static TypeHandle _type_handle; + }; + + typedef pset > Files; + + class Directory : public FileBase { + public: + INLINE Directory(const string &basename); + + virtual bool is_directory() const; + + PT(FileBase) do_find_file(const string &filename) const; + PT(File) do_create_file(const string &filename); + PT(Directory) do_make_directory(const string &filename); + PT(FileBase) do_delete_file(const string &filename); + bool do_scan_directory(vector_string &contents) const; + + Files _files; + + public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FileBase::init_type(); + register_type(_type_handle, "VirtualFileMountRamdisk::Directory", + FileBase::get_class_type()); + } + + private: + static TypeHandle _type_handle; + }; + + Directory _root; + MutexImpl _lock; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + VirtualFileMount::init_type(); + register_type(_type_handle, "VirtualFileMountRamdisk", + VirtualFileMount::get_class_type()); + FileBase::init_type(); + File::init_type(); + Directory::init_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#include "virtualFileMountRamdisk.I" + +#endif diff --git a/panda/src/express/virtualFileMountSystem.cxx b/panda/src/express/virtualFileMountSystem.cxx index 94150e4932..2e19fa05ea 100644 --- a/panda/src/express/virtualFileMountSystem.cxx +++ b/panda/src/express/virtualFileMountSystem.cxx @@ -59,6 +59,53 @@ create_file(const Filename &file) { return pathname.open_write(stream, false); } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::delete_file +// Access: Public, Virtual +// Description: Attempts to delete the indicated file or directory +// within the mount. This can remove a single file or +// an empty directory. It will not remove a nonempty +// directory. Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountSystem:: +delete_file(const Filename &file) { + Filename pathname(_physical_filename, file); + return pathname.unlink() || pathname.rmdir(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::rename_file +// Access: Public +// Description: Attempts to rename the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, this will be +// attempted again with a copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountSystem:: +rename_file(const Filename &orig_filename, const Filename &new_filename) { + Filename orig_pathname(_physical_filename, orig_filename); + Filename new_pathname(_physical_filename, new_filename); + return orig_pathname.rename_to(new_pathname); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::copy_file +// Access: Public +// Description: Attempts to copy the contents of the indicated file +// to the indicated file. Both filenames will be within +// the mount. Returns true on success, false on +// failure. If this returns false, the copy will be +// performed by explicit read-and-write operations. +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountSystem:: +copy_file(const Filename &orig_filename, const Filename &new_filename) { + Filename orig_pathname(_physical_filename, orig_filename); + Filename new_pathname(_physical_filename, new_filename); + return orig_pathname.copy_to(new_pathname); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMountSystem::make_directory // Access: Public, Virtual @@ -383,6 +430,46 @@ scan_directory(vector_string &contents, const Filename &dir) const { } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::atomic_compare_and_exchange_contents +// Access: Public, Virtual +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountSystem:: +atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, + const string &old_contents, + const string &new_contents) { +#ifdef WIN32 + // First ensure that the file exists to validate its case. + if (VirtualFileSystem::get_global_ptr()->vfs_case_sensitive) { + if (!has_file(file)) { + return NULL; + } + } +#endif // WIN32 + Filename pathname(_physical_filename, file); + return pathname.atomic_compare_and_exchange_contents(orig_contents, old_contents, new_contents); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileMountSystem::atomic_read_contents +// Access: Public, Virtual +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileMountSystem:: +atomic_read_contents(const Filename &file, string &contents) const { +#ifdef WIN32 + // First ensure that the file exists to validate its case. + if (VirtualFileSystem::get_global_ptr()->vfs_case_sensitive) { + if (!has_file(file)) { + return NULL; + } + } +#endif // WIN32 + Filename pathname(_physical_filename, file); + return pathname.atomic_read_contents(contents); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileMountSystem::output // Access: Public, Virtual diff --git a/panda/src/express/virtualFileMountSystem.h b/panda/src/express/virtualFileMountSystem.h index 8f33db266b..3a0281eece 100644 --- a/panda/src/express/virtualFileMountSystem.h +++ b/panda/src/express/virtualFileMountSystem.h @@ -34,6 +34,9 @@ public: virtual bool has_file(const Filename &file) const; virtual bool create_file(const Filename &file); virtual bool make_directory(const Filename &file); + virtual bool delete_file(const Filename &file); + virtual bool rename_file(const Filename &orig_filename, const Filename &new_filename); + virtual bool copy_file(const Filename &orig_filename, const Filename &new_filename); virtual bool is_directory(const Filename &file) const; virtual bool is_regular_file(const Filename &file) const; virtual bool is_writable(const Filename &file) const; @@ -52,6 +55,9 @@ public: virtual bool scan_directory(vector_string &contents, const Filename &dir) const; + virtual bool atomic_compare_and_exchange_contents(const Filename &file, string &orig_contents, const string &old_contents, const string &new_contents); + virtual bool atomic_read_contents(const Filename &file, string &contents) const; + virtual void output(ostream &out) const; private: diff --git a/panda/src/express/virtualFileSimple.cxx b/panda/src/express/virtualFileSimple.cxx index 8648f8446f..2209be3f8d 100644 --- a/panda/src/express/virtualFileSimple.cxx +++ b/panda/src/express/virtualFileSimple.cxx @@ -99,6 +99,115 @@ is_writable() const { return _mount->is_writable(_local_filename); } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::delete_file +// Access: Public +// Description: Attempts to delete this file or directory. This can +// remove a single file or an empty directory. It will +// not remove a nonempty directory. Returns true on +// success, false on failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSimple:: +delete_file() { + return _mount->delete_file(_local_filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::rename_file +// Access: Public +// Description: Attempts to move or rename this file or directory. +// If the original file is an ordinary file, it will +// quietly replace any already-existing file in the new +// filename (but not a directory). If the original file +// is a directory, the new filename must not already +// exist. +// +// If the file is a directory, the new filename must be +// within the same mount point. If the file is an +// ordinary file, the new filename may be anywhere; but +// if it is not within the same mount point then the +// rename operation is automatically performed as a +// two-step copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSimple:: +rename_file(VirtualFile *new_file) { + if (new_file->is_of_type(VirtualFileSimple::get_class_type())) { + VirtualFileSimple *new_file_simple = DCAST(VirtualFileSimple, new_file); + if (new_file_simple->_mount == _mount) { + // Same mount pount. + if (_mount->rename_file(_local_filename, new_file_simple->_local_filename)) { + return true; + } + } + } + + // Different mount point, or the mount doesn't support renaming. Do + // it by hand. + if (is_regular_file() && !new_file->is_directory()) { + // copy-and-delete. + new_file->delete_file(); + if (copy_file(new_file)) { + delete_file(); + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::copy_file +// Access: Public +// Description: Attempts to copy the contents of this file to the +// indicated file. Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSimple:: +copy_file(VirtualFile *new_file) { + if (new_file->is_of_type(VirtualFileSimple::get_class_type())) { + VirtualFileSimple *new_file_simple = DCAST(VirtualFileSimple, new_file); + if (new_file_simple->_mount == _mount) { + // Same mount pount. + if (_mount->copy_file(_local_filename, new_file_simple->_local_filename)) { + return true; + } + } + } + + // Different mount point, or the mount doesn't support copying. Do + // it by hand. + ostream *out = new_file->open_write_file(false, true); + istream *in = open_read_file(false); + + static const size_t buffer_size = 4096; + char buffer[buffer_size]; + + in->read(buffer, buffer_size); + size_t count = in->gcount(); + while (count != 0) { + out->write(buffer, count); + if (out->fail()) { + new_file->close_write_file(out); + close_read_file(in); + new_file->delete_file(); + return false; + } + in->read(buffer, buffer_size); + count = in->gcount(); + } + + if (!in->eof()) { + new_file->close_write_file(out); + close_read_file(in); + new_file->delete_file(); + return false; + } + + new_file->close_write_file(out); + close_read_file(in); + return true; +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileSimple::open_read_file // Access: Published, Virtual @@ -192,7 +301,7 @@ open_append_file() { // work around compiler issues. //////////////////////////////////////////////////////////////////// void VirtualFileSimple:: -close_write_file(ostream *stream) const { +close_write_file(ostream *stream) { _mount->close_write_file(stream); } @@ -295,6 +404,28 @@ get_system_info(SubfileInfo &info) { return _mount->get_system_info(_local_filename, info); } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::atomic_compare_and_exchange_contents +// Access: Public, Virtual +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileSimple:: +atomic_compare_and_exchange_contents(string &orig_contents, + const string &old_contents, + const string &new_contents) { + return _mount->atomic_compare_and_exchange_contents(_local_filename, orig_contents, old_contents, new_contents); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSimple::atomic_read_contents +// Access: Public, Virtual +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileSimple:: +atomic_read_contents(string &contents) const { + return _mount->atomic_read_contents(_local_filename, contents); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileSimple::read_file // Access: Public, Virtual diff --git a/panda/src/express/virtualFileSimple.h b/panda/src/express/virtualFileSimple.h index a66c64dc84..6f6cc5d3c8 100644 --- a/panda/src/express/virtualFileSimple.h +++ b/panda/src/express/virtualFileSimple.h @@ -44,11 +44,15 @@ PUBLISHED: virtual bool is_writable() const; INLINE bool is_implicit_pz_file() const; + virtual bool delete_file(); + virtual bool rename_file(VirtualFile *new_file); + virtual bool copy_file(VirtualFile *new_file); + virtual istream *open_read_file(bool auto_unwrap) const; virtual void close_read_file(istream *stream) const; virtual ostream *open_write_file(bool auto_wrap, bool truncate); virtual ostream *open_append_file(); - virtual void close_write_file(ostream *stream) const; + virtual void close_write_file(ostream *stream); virtual iostream *open_read_write_file(bool truncate); virtual iostream *open_read_append_file(); virtual void close_read_write_file(iostream *stream); @@ -59,6 +63,9 @@ PUBLISHED: virtual bool get_system_info(SubfileInfo &info); public: + virtual bool atomic_compare_and_exchange_contents(string &orig_contents, const string &old_contents, const string &new_contents); + virtual bool atomic_read_contents(string &contents) const; + virtual bool read_file(pvector &result, bool auto_unwrap) const; virtual bool write_file(const unsigned char *data, size_t data_size, bool auto_wrap); diff --git a/panda/src/express/virtualFileSystem.cxx b/panda/src/express/virtualFileSystem.cxx index 6f7860ee63..e3fd29cda9 100644 --- a/panda/src/express/virtualFileSystem.cxx +++ b/panda/src/express/virtualFileSystem.cxx @@ -17,6 +17,7 @@ #include "virtualFileComposite.h" #include "virtualFileMount.h" #include "virtualFileMountMultifile.h" +#include "virtualFileMountRamdisk.h" #include "virtualFileMountSystem.h" #include "streamWrapper.h" #include "dSearchPath.h" @@ -510,9 +511,37 @@ get_cwd() const { //////////////////////////////////////////////////////////////////// bool VirtualFileSystem:: make_directory(const Filename &filename) { - ((VirtualFileSystem *)this)->_lock.acquire(); + _lock.acquire(); PT(VirtualFile) result = do_get_file(filename, OF_make_directory); - ((VirtualFileSystem *)this)->_lock.release(); + _lock.release(); + return result->is_directory(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::make_directory_full +// Access: Published +// Description: Attempts to create a directory within the file +// system. Will also create any intervening directories +// needed. Returns true on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +make_directory_full(const Filename &filename) { + _lock.acquire(); + + // First, make sure everything up to the last path is known. We + // don't care too much if any of these fail; maybe they failed + // because the directory was already there. + string dirname = filename; + size_t slash = dirname.find('/', 1); + while (slash != string::npos) { + Filename component(dirname.substr(0, slash)); + do_get_file(component, OF_make_directory); + slash = dirname.find('/', slash + 1); + } + + // Now make the last one, and check the return value. + PT(VirtualFile) result = do_get_file(filename, OF_make_directory); + _lock.release(); return result->is_directory(); } @@ -594,6 +623,82 @@ find_file(const Filename &filename, const DSearchPath &searchpath, return NULL; } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::delete_file +// Access: Public +// Description: Attempts to delete the indicated file or directory. +// This can remove a single file or an empty directory. +// It will not remove a nonempty directory. Returns +// true on success, false on failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +delete_file(const Filename &filename) { + PT(VirtualFile) file = get_file(filename, true); + if (file == (VirtualFile *)NULL) { + return false; + } + + return file->delete_file(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::rename_file +// Access: Public +// Description: Attempts to move or rename the indicated file or +// directory. If the original file is an ordinary file, +// it will quietly replace any already-existing file in +// the new filename (but not a directory). If the +// original file is a directory, the new filename must +// not already exist. +// +// If the file is a directory, the new filename must be +// within the same mount point. If the file is an +// ordinary file, the new filename may be anywhere; but +// if it is not within the same mount point then the +// rename operation is automatically performed as a +// two-step copy-and-delete operation. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +rename_file(const Filename &orig_filename, const Filename &new_filename) { + _lock.acquire(); + PT(VirtualFile) orig_file = do_get_file(orig_filename, OF_status_only); + if (orig_file == (VirtualFile *)NULL) { + _lock.release(); + return false; + } + + PT(VirtualFile) new_file = do_get_file(new_filename, OF_status_only | OF_allow_nonexist); + if (new_file == (VirtualFile *)NULL) { + _lock.release(); + return false; + } + + _lock.release(); + + return orig_file->rename_file(new_file); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::copy_file +// Access: Public +// Description: Attempts to copy the contents of the indicated file +// to the indicated file. Returns true on success, +// false on failure. +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +copy_file(const Filename &orig_filename, const Filename &new_filename) { + PT(VirtualFile) orig_file = get_file(orig_filename, true); + if (orig_file == (VirtualFile *)NULL) { + return false; + } + + PT(VirtualFile) new_file = get_file(new_filename, true); + if (new_file == (VirtualFile *)NULL) { + return false; + } + + return orig_file->copy_file(new_file); +} //////////////////////////////////////////////////////////////////// // Function: VirtualFileSystem::resolve_filename @@ -779,23 +884,37 @@ get_global_ptr() { mount_desc = ExecutionEnvironment::expand_string(mount_desc); Filename physical_filename = Filename::from_os_specific(mount_desc); - int flags = 0; + int flags; string password; - - // Split the options up by commas. - size_t p = 0; - size_t q = options.find(',', p); - while (q != string::npos) { - parse_option(options.substr(p, q - p), - flags, password); - p = q + 1; - q = options.find(',', p); - } - parse_option(options.substr(p), flags, password); - + parse_options(options, flags, password); _global_ptr->mount(physical_filename, mount_point, flags, password); } } + + ConfigVariableString vfs_mount_ramdisk + ("vfs-mount-ramdisk", "", + PRC_DESC("vfs-mount-ramdisk mount-point [options]")); + if (!vfs_mount_ramdisk.empty()) { + string mount_point = vfs_mount_ramdisk; + string options; + + size_t space = mount_point.rfind(' '); + if (space != string::npos) { + // If there's a space, we have the optional options field. + options = mount_point.substr(space + 1); + while (space > 0 && isspace(mount_point[space - 1])) { + --space; + } + mount_point = mount_point.substr(0, space); + } + + int flags; + string password; + parse_options(options, flags, password); + + PT(VirtualFileMount) ramdisk = new VirtualFileMountRamdisk; + _global_ptr->mount(ramdisk, mount_point, flags); + } } return _global_ptr; @@ -1037,6 +1156,38 @@ close_read_write_file(iostream *stream) { } } +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::atomic_compare_and_exchange_contents +// Access: Public +// Description: See Filename::atomic_compare_and_exchange_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +atomic_compare_and_exchange_contents(const Filename &filename, string &orig_contents, + const string &old_contents, + const string &new_contents) { + PT(VirtualFile) file = create_file(filename); + if (file == NULL) { + return false; + } + + return file->atomic_compare_and_exchange_contents(orig_contents, old_contents, new_contents); +} + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::atomic_read_contents +// Access: Public +// Description: See Filename::atomic_read_contents(). +//////////////////////////////////////////////////////////////////// +bool VirtualFileSystem:: +atomic_read_contents(const Filename &filename, string &contents) const { + PT(VirtualFile) file = get_file(filename, false); + if (file == NULL) { + return false; + } + + return file->atomic_read_contents(contents); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileSystem::scan_mount_points // Access: Public @@ -1081,6 +1232,30 @@ scan_mount_points(vector_string &names, const Filename &path) const { } } + +//////////////////////////////////////////////////////////////////// +// Function: VirtualFileSystem::parse_options +// Access: Public, Static +// Description: Parses all of the option flags in the options list on +// the vfs-mount Config.prc line. +//////////////////////////////////////////////////////////////////// +void VirtualFileSystem:: +parse_options(const string &options, int &flags, string &password) { + flags = 0; + password = string(); + + // Split the options up by commas. + size_t p = 0; + size_t q = options.find(',', p); + while (q != string::npos) { + parse_option(options.substr(p, q - p), + flags, password); + p = q + 1; + q = options.find(',', p); + } + parse_option(options.substr(p), flags, password); +} + //////////////////////////////////////////////////////////////////// // Function: VirtualFileSystem::parse_option // Access: Public, Static @@ -1148,7 +1323,9 @@ do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags) { //////////////////////////////////////////////////////////////////// PT(VirtualFile) VirtualFileSystem:: do_get_file(const Filename &filename, int open_flags) const { - nassertr(!filename.empty(), NULL); + if (filename.empty()) { + return NULL; + } Filename pathname(filename); if (pathname.is_local()) { pathname = Filename(_cwd, filename); @@ -1262,7 +1439,7 @@ consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_fil int open_flags) const { PT(VirtualFile) vfile = mount->make_virtual_file(local_filename, original_filename, false, open_flags); - if (!vfile->has_file()) { + if (!vfile->has_file() && ((open_flags & OF_allow_nonexist) == 0)) { // Keep looking. return false; } diff --git a/panda/src/express/virtualFileSystem.h b/panda/src/express/virtualFileSystem.h index bd99e4f4fd..48b849b3fc 100644 --- a/panda/src/express/virtualFileSystem.h +++ b/panda/src/express/virtualFileSystem.h @@ -69,12 +69,16 @@ PUBLISHED: BLOCKING bool chdir(const Filename &new_directory); BLOCKING Filename get_cwd() const; BLOCKING bool make_directory(const Filename &filename); + BLOCKING bool make_directory_full(const Filename &filename); BLOCKING PT(VirtualFile) get_file(const Filename &filename, bool status_only = false) const; BLOCKING PT(VirtualFile) create_file(const Filename &filename); BLOCKING PT(VirtualFile) find_file(const Filename &filename, const DSearchPath &searchpath, bool status_only = false) const; + BLOCKING bool delete_file(const Filename &filename); + BLOCKING bool rename_file(const Filename &orig_filename, const Filename &new_filename); + BLOCKING bool copy_file(const Filename &orig_filename, const Filename &new_filename); BLOCKING bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const string &default_extension = string()) const; @@ -115,14 +119,19 @@ PUBLISHED: BLOCKING static void close_read_write_file(iostream *stream); public: + bool atomic_compare_and_exchange_contents(const Filename &filename, string &orig_contents, const string &old_contents, const string &new_contents); + bool atomic_read_contents(const Filename &filename, string &contents) const; + INLINE bool read_file(const Filename &filename, string &result, bool auto_unwrap) const; INLINE bool read_file(const Filename &filename, pvector &result, bool auto_unwrap) const; INLINE bool write_file(const Filename &filename, const unsigned char *data, size_t data_size, bool auto_wrap); void scan_mount_points(vector_string &names, const Filename &path) const; - + + static void parse_options(const string &options, + int &flags, string &password); static void parse_option(const string &option, - int &flags, string &password); + int &flags, string &password); public: // These flags are passed to do_get_file() and @@ -132,6 +141,7 @@ public: OF_status_only = 0x0001, OF_create_file = 0x0002, OF_make_directory = 0x0004, + OF_allow_nonexist = 0x0008, }; // These are declared as class instances, instead of as globals, to diff --git a/panda/src/pgraph/bamFile.cxx b/panda/src/pgraph/bamFile.cxx index ce053bf028..9ed36b128d 100644 --- a/panda/src/pgraph/bamFile.cxx +++ b/panda/src/pgraph/bamFile.cxx @@ -222,7 +222,8 @@ open_write(const Filename &bam_filename, bool report_errors) { loader_cat.info() << "Writing " << bam_filename << "\n"; - bam_filename.unlink(); + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->delete_file(bam_filename); if (!_dout.open(bam_filename)) { if (report_errors) { loader_cat.error() << "Unable to open " << bam_filename << "\n"; diff --git a/panda/src/putil/bamCache.cxx b/panda/src/putil/bamCache.cxx index f1bf27e02c..f8b9d1954d 100644 --- a/panda/src/putil/bamCache.cxx +++ b/panda/src/putil/bamCache.cxx @@ -118,22 +118,19 @@ set_root(const Filename &root) { flush_index(); _root = root; - // For now, the filename must be a directory. Maybe eventually we - // will support writing caches to a Panda multifile (though maybe it - // would be better to implement this kind of thing at a lower level, - // via a writable VFS, in which case the specified root filename - // will still be a "directory"). - if (!root.is_directory()) { - Filename dirname(_root, Filename(".")); - dirname.make_dir(); + // The root filename must be a directory. + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + if (!vfs->is_directory(_root)) { + vfs->make_directory_full(_root); } - nassertv(root.is_directory()); delete _index; _index = new BamCacheIndex; _index_stale_since = 0; read_index(); check_cache_size(); + + nassertv(vfs->is_directory(_root)); } //////////////////////////////////////////////////////////////////// @@ -193,6 +190,7 @@ lookup(const Filename &source_filename, const string &cache_extension) { //////////////////////////////////////////////////////////////////// bool BamCache:: store(BamCacheRecord *record) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); ReMutexHolder holder(_lock); nassertr(!record->_cache_pathname.empty(), false); nassertr(record->has_data(), false); @@ -227,7 +225,7 @@ store(BamCacheRecord *record) { if (!dout.open(temp_pathname)) { util_cat.error() << "Could not write cache file: " << temp_pathname << "\n"; - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); emergency_read_only(); return false; } @@ -235,14 +233,14 @@ store(BamCacheRecord *record) { if (!dout.write_header(_bam_header)) { util_cat.error() << "Unable to write to " << temp_pathname << "\n"; - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); return false; } { BamWriter writer(&dout); if (!writer.init()) { - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); return false; } @@ -257,12 +255,12 @@ store(BamCacheRecord *record) { } if (!writer.write_object(record)) { - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); return false; } if (!writer.write_object(record->get_data())) { - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); return false; } @@ -275,13 +273,13 @@ store(BamCacheRecord *record) { dout.close(); // Now move the file into place. - if (!temp_pathname.rename_to(cache_pathname) && temp_pathname.exists()) { - cache_pathname.unlink(); - if (!temp_pathname.rename_to(cache_pathname)) { + if (!vfs->rename_file(temp_pathname, cache_pathname) && vfs->exists(temp_pathname)) { + vfs->delete_file(cache_pathname); + if (!vfs->rename_file(temp_pathname, cache_pathname)) { util_cat.error() << "Unable to rename " << temp_pathname << " to " << cache_pathname << "\n"; - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); return false; } } @@ -351,15 +349,17 @@ flush_index() { // Now atomically write the name of this index file to the index // reference file. + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); Filename index_ref_pathname(_root, Filename("index_name.txt")); string old_index = _index_ref_contents; string new_index = temp_pathname.get_basename() + "\n"; string orig_index; - if (index_ref_pathname.atomic_compare_and_exchange_contents(orig_index, old_index, new_index)) { + + if (vfs->atomic_compare_and_exchange_contents(index_ref_pathname, orig_index, old_index, new_index)) { // We successfully wrote our version of the index, and no other // process beat us to it. Our index is now the official one. // Remove the old index. - _index_pathname.unlink(); + vfs->delete_file(_index_pathname); _index_pathname = temp_pathname; _index_ref_contents = new_index; _index_stale_since = 0; @@ -369,7 +369,7 @@ flush_index() { // Shoot, some other process updated the index while we were // trying to update it, and they beat us to it. We have to merge, // and try again. - temp_pathname.unlink(); + vfs->delete_file(temp_pathname); _index_pathname = Filename(_root, Filename(trim(orig_index))); _index_ref_contents = orig_index; read_index(); @@ -411,7 +411,8 @@ read_index() { if (old_index_pathname == _index_pathname) { // Nope, we just couldn't read it. Delete it and build a new // one. - _index_pathname.unlink(); + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->delete_file(_index_pathname); rebuild_index(); flush_index(); return; @@ -428,9 +429,10 @@ read_index() { //////////////////////////////////////////////////////////////////// bool BamCache:: read_index_pathname(Filename &index_pathname, string &index_ref_contents) const { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); index_ref_contents.clear(); Filename index_ref_pathname(_root, Filename("index_name.txt")); - if (!index_ref_pathname.atomic_read_contents(index_ref_contents)) { + if (!vfs->atomic_read_contents(index_ref_pathname, index_ref_contents)) { return false; } @@ -555,8 +557,10 @@ merge_index(BamCacheIndex *new_index) { //////////////////////////////////////////////////////////////////// void BamCache:: rebuild_index() { - vector_string contents; - if (!_root.scan_directory(contents)) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + PT(VirtualFileList) contents = vfs->scan_directory(_root); + if (contents == NULL) { util_cat.error() << "Unable to read directory " << _root << ", caching disabled.\n"; set_active(false); @@ -566,9 +570,10 @@ rebuild_index() { delete _index; _index = new BamCacheIndex; - vector_string::const_iterator ci; - for (ci = contents.begin(); ci != contents.end(); ++ci) { - Filename filename(*ci); + int num_files = contents->get_num_files(); + for (int ci = 0; ci < num_files; ++ci) { + VirtualFile *file = contents->get_file(ci); + Filename filename = file->get_filename(); if (filename.get_extension() == "bam" || filename.get_extension() == "txo") { Filename pathname(_root, filename); @@ -576,7 +581,7 @@ rebuild_index() { PT(BamCacheRecord) record = do_read_record(pathname, false); if (record == (BamCacheRecord *)NULL) { // Well, it was invalid, so blow it away. - pathname.unlink(); + file->delete_file(); } else { record->_record_access_time = record->_recorded_time; @@ -585,7 +590,7 @@ rebuild_index() { if (!inserted) { util_cat.info() << "Multiple cache files defining " << record->get_source_pathname() << "\n"; - pathname.unlink(); + file->delete_file(); } } } @@ -646,8 +651,9 @@ check_cache_size() { // Never mind; the cache is empty. break; } + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); Filename cache_pathname(_root, record->get_cache_filename()); - cache_pathname.unlink(); + vfs->delete_file(cache_pathname); } mark_index_stale(); } @@ -722,30 +728,31 @@ do_read_index(const Filename &index_pathname) { //////////////////////////////////////////////////////////////////// bool BamCache:: do_write_index(const Filename &index_pathname, const BamCacheIndex *index) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); DatagramOutputFile dout; if (!dout.open(index_pathname)) { util_cat.error() << "Could not write index file: " << index_pathname << "\n"; - index_pathname.unlink(); + vfs->delete_file(index_pathname); return false; } if (!dout.write_header(_bam_header)) { util_cat.error() << "Unable to write to " << index_pathname << "\n"; - index_pathname.unlink(); + vfs->delete_file(index_pathname); return false; } { BamWriter writer(&dout); if (!writer.init()) { - index_pathname.unlink(); + vfs->delete_file(index_pathname); return false; } if (!writer.write_object(index)) { - index_pathname.unlink(); + vfs->delete_file(index_pathname); return false; } } @@ -788,6 +795,7 @@ PT(BamCacheRecord) BamCache:: read_record(const Filename &source_pathname, const Filename &cache_filename, int pass) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); Filename cache_pathname(_root, cache_filename); if (pass != 0) { ostringstream strm; @@ -806,7 +814,7 @@ read_record(const Filename &source_pathname, PT(BamCacheRecord) record = do_read_record(cache_pathname, true); if (record == (BamCacheRecord *)NULL) { // Well, it was invalid, so blow it away, and make a new one. - cache_pathname.unlink(); + vfs->delete_file(cache_pathname); remove_from_index(source_pathname); PT(BamCacheRecord) record = diff --git a/panda/src/testbed/pview.cxx b/panda/src/testbed/pview.cxx index 042e9a1642..16b20c75e0 100644 --- a/panda/src/testbed/pview.cxx +++ b/panda/src/testbed/pview.cxx @@ -22,6 +22,7 @@ #include "partGroup.h" #include "cardMaker.h" #include "bamCache.h" +#include "virtualFileSystem.h" #include "panda_getopt.h" // By including checkPandaVersion.h, we guarantee that runtime @@ -324,11 +325,12 @@ main(int argc, char *argv[]) { window->load_models(framework.get_models(), argc, argv); if (delete_models) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); for (int i = 1; i < argc && argv[i] != (char *)NULL; i++) { Filename model = Filename::from_os_specific(argv[i]); - if (model.exists()) { + if (vfs->exists(model)) { nout << "Deleting " << model << "\n"; - model.unlink(); + vfs->delete_file(model); } } }