Browse Source

more writable vfs stuff

David Rose 14 years ago
parent
commit
5496c4a9ec

+ 4 - 0
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):

+ 36 - 0
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

+ 2 - 0
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;

+ 2 - 4
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";

+ 11 - 5
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 \

+ 2 - 0
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();

+ 1 - 0
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"

+ 101 - 1
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;
+}
+
+

+ 41 - 0
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

+ 219 - 103
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().
-
-  // 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;
-
-  // 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;
+seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
+  streampos result = -1;
 
-    } else {
-      new_pos = _end + off;
+  // Sync the iostream buffer first.
+  sync();
+
+  if (which & ios::in) {
+    // Determine the current file position.
+    size_t n = egptr() - gptr();
+    gbump(n);
+    _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;
     }
-    break;
+
+    _gpos = new_pos;
+    nassertr(_gpos >= 0, EOF);
+    result = new_pos - _start;
   }
 
-  new_pos = max(_start, new_pos);
-  streamsize delta = new_pos - cur_pos;
+  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;
 
-  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);
+    default:
+      // Shouldn't get here.
+      break;
+    }
 
-  } else {
-    // Otherwise, empty the buffer and force it to call underflow().
-    gbump(n);
-    _cur = new_pos;
+    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 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;
-    }
-    
-    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);
-    }
+    sync();
 
-    gbump(-(int)num_bytes);
-    nassertr(gptr() + num_bytes <= egptr(), EOF);
+    // Mark the buffer filled (with buffer_size bytes).
+    size_t buffer_size = egptr() - eback();
+    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);
+    }
+      
+    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();

+ 7 - 5
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;
 };
 

+ 69 - 0
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

+ 7 - 0
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<unsigned char> &result, bool auto_unwrap) const;

+ 64 - 0
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

+ 6 - 0
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;

+ 54 - 0
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) {
+}

+ 707 - 0
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;
+}

+ 186 - 0
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<PT(FileBase), indirect_less<FileBase *> > 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

+ 87 - 0
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

+ 6 - 0
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:

+ 132 - 1
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

+ 8 - 1
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<unsigned char> &result, bool auto_unwrap) const;
   virtual bool write_file(const unsigned char *data, size_t data_size, bool auto_wrap);
 

+ 193 - 16
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,22 +884,36 @@ 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;
+        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;
         
-        // 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);
+      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;
         }
-        parse_option(options.substr(p), flags, password);
-        
-        _global_ptr->mount(physical_filename, mount_point, flags, password);
+        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);
     }
   }
 
@@ -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;
   }

+ 12 - 2
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<unsigned char> &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

+ 2 - 1
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";

+ 44 - 36
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 =

+ 4 - 2
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);
           }
         }
       }