2
0
David Rose 14 жил өмнө
parent
commit
e986614cfc

+ 35 - 0
dtool/src/dtoolutil/filename.cxx

@@ -1412,6 +1412,41 @@ is_regular_file() const {
   return isreg;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::is_writable
+//       Access: Published
+//  Description: Returns true if the filename exists and is either a
+//               directory or a regular file that can be written to,
+//               or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool Filename::
+is_writable() const {
+  bool writable = false;
+
+#ifdef WIN32_VC
+  wstring os_specific = get_filename_index(0).to_os_specific_w();
+
+  DWORD results = GetFileAttributesW(os_specific.c_str());
+  if (results != -1) {
+    if ((results & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+      // Assume directories are writable.
+      writable = true;
+    } else if ((results & FILE_ATTRIBUTE_READONLY) == 0) {
+      // Not read-only means writable.
+      writable = true;
+    }
+  }
+#else  // WIN32_VC
+  string os_specific = get_filename_index(0).to_os_specific();
+
+  if (access(os_specific.c_str(), W_OK) == 0) {
+    writable = true;
+  }
+#endif
+
+  return writable;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Filename::is_directory
 //       Access: Published

+ 1 - 0
dtool/src/dtoolutil/filename.h

@@ -174,6 +174,7 @@ PUBLISHED:
 
   bool exists() const;
   bool is_regular_file() const;
+  bool is_writable() const;
   bool is_directory() const;
   bool is_executable() const;
   int compare_timestamps(const Filename &other,

+ 2 - 2
panda/src/downloader/virtualFileHTTP.cxx

@@ -29,11 +29,11 @@ TypeHandle VirtualFileHTTP::_type_handle;
 ////////////////////////////////////////////////////////////////////
 VirtualFileHTTP::
 VirtualFileHTTP(VirtualFileMountHTTP *mount, const Filename &local_filename,
-                bool implicit_pz_file, bool status_only) :
+                bool implicit_pz_file, int open_flags) :
   _mount(mount),
   _local_filename(local_filename),
   _implicit_pz_file(implicit_pz_file),
-  _status_only(status_only)
+  _status_only(open_flags != 0)
 {
   URLSpec url(_mount->get_root());
   url.set_path(_mount->get_root().get_path() + _local_filename.c_str());

+ 1 - 1
panda/src/downloader/virtualFileHTTP.h

@@ -36,7 +36,7 @@ public:
   VirtualFileHTTP(VirtualFileMountHTTP *mount,
                   const Filename &local_filename,
                   bool implicit_pz_file,
-                  bool status_only);
+                  int open_flags);
   virtual ~VirtualFileHTTP();
 
   virtual VirtualFileSystem *get_file_system() const;

+ 2 - 2
panda/src/downloader/virtualFileMountHTTP.cxx

@@ -182,9 +182,9 @@ is_regular_file(const Filename &) const {
 PT(VirtualFile) VirtualFileMountHTTP::
 make_virtual_file(const Filename &local_filename,
                   const Filename &original_filename, bool implicit_pz_file,
-                  bool status_only) {
+                  int open_flags) {
   PT(VirtualFileHTTP) vfile = 
-    new VirtualFileHTTP(this, local_filename, implicit_pz_file, status_only);
+    new VirtualFileHTTP(this, local_filename, implicit_pz_file, open_flags);
   vfile->set_original_filename(original_filename);
 
   return vfile.p();

+ 1 - 1
panda/src/downloader/virtualFileMountHTTP.h

@@ -45,7 +45,7 @@ public:
   virtual PT(VirtualFile) make_virtual_file(const Filename &local_filename,
                                             const Filename &original_filename, 
                                             bool implicit_pz_file,
-                                            bool status_only);
+                                            int open_flags);
 
   virtual bool has_file(const Filename &file) const;
   virtual bool is_directory(const Filename &file) const;

+ 11 - 0
panda/src/express/virtualFile.I

@@ -46,6 +46,17 @@ read_file(bool auto_unwrap) const {
   return result;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::write_file
+//       Access: Public
+//  Description: Writes the entire contents of the file as a string,
+//               if it is writable.
+////////////////////////////////////////////////////////////////////
+INLINE bool VirtualFile::
+write_file(const string &data, bool auto_wrap) {
+  return write_file((const unsigned char *)data.data(), data.size(), auto_wrap);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::set_original_filename
 //       Access: Public

+ 93 - 40
panda/src/express/virtualFile.cxx

@@ -52,6 +52,18 @@ is_regular_file() const {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::is_writable
+//       Access: Published, Virtual
+//  Description: Returns true if this file may be written to, which
+//               implies write_file() may be called (unless it is a
+//               directory instead of a regular file).
+////////////////////////////////////////////////////////////////////
+bool VirtualFile::
+is_writable() const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::scan_directory
 //       Access: Published
@@ -169,6 +181,76 @@ open_read_file(bool auto_unwrap) const {
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::close_read_file
+//       Access: Published
+//  Description: Closes a file opened by a previous call to
+//               open_read_file().  This really just deletes the
+//               istream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFile::
+close_read_file(istream *stream) const {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::was_read_successful
+//       Access: Published, Virtual
+//  Description: Call this method after a reading the istream returned
+//               by open_read_file() to completion.  If it returns
+//               true, the file was read completely and without error;
+//               if it returns false, there may have been some errors
+//               or a truncated file read.  This is particularly
+//               likely if the stream is a VirtualFileHTTP.
+////////////////////////////////////////////////////////////////////
+bool VirtualFile::
+was_read_successful() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::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 *VirtualFile::
+open_write_file(bool auto_wrap) {
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::close_write_file
+//       Access: Published
+//  Description: Closes a file opened by a previous call to
+//               open_write_file().  This really just deletes the
+//               ostream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFile::
+close_write_file(ostream *stream) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::was_write_successful
+//       Access: Published, Virtual
+//  Description: Call this method after a writing the ostream returned
+//               by open_write_file() to completion.  If it returns
+//               true, the file was written completely and without
+//               error; if it returns false, there may have been some
+//               errors or a truncated file write.
+////////////////////////////////////////////////////////////////////
+bool VirtualFile::
+was_write_successful() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::get_file_size
 //       Access: Published, Virtual
@@ -228,46 +310,6 @@ get_system_info(SubfileInfo &info) {
   return false;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: VirtualFile::close_read_file
-//       Access: Public
-//  Description: Closes a file opened by a previous call to
-//               open_read_file().  This really just deletes the
-//               istream pointer, but it is recommended to use this
-//               interface instead of deleting it explicitly, to help
-//               work around compiler issues.
-////////////////////////////////////////////////////////////////////
-void VirtualFile::
-close_read_file(istream *stream) const {
-  if (stream != (istream *)NULL) {
-    // For some reason--compiler bug in gcc 3.2?--explicitly deleting
-    // the stream pointer does not call the appropriate global delete
-    // function; instead apparently calling the system delete
-    // function.  So we call the delete function by hand instead.
-#if !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
-    stream->~istream();
-    (*global_operator_delete)(stream);
-#else
-    delete stream;
-#endif
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: VirtualFile::was_read_successful
-//       Access: Public
-//  Description: Call this method after a reading the istream returned
-//               by open_read_file() to completion.  If it returns
-//               true, the file was read completely and without error;
-//               if it returns false, there may have been some errors
-//               or a truncated file read.  This is particularly
-//               likely if the stream is a VirtualFileHTTP.
-////////////////////////////////////////////////////////////////////
-bool VirtualFile::
-was_read_successful() const {
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::read_file
 //       Access: Public
@@ -303,6 +345,17 @@ read_file(pvector<unsigned char> &result, bool auto_unwrap) const {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::write_file
+//       Access: Public, Virtual
+//  Description: Writes the indicated data to the file, if it is
+//               writable.  Returns true on success, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFile::
+write_file(const unsigned char *data, size_t data_size, bool auto_wrap) {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::simple_read_file
 //       Access: Public, Static

+ 9 - 2
panda/src/express/virtualFile.h

@@ -46,6 +46,7 @@ PUBLISHED:
   virtual bool has_file() const;
   virtual bool is_directory() const;
   virtual bool is_regular_file() const;
+  virtual bool is_writable() const;
 
   BLOCKING PT(VirtualFileList) scan_directory() const;
 
@@ -55,8 +56,14 @@ PUBLISHED:
 
   BLOCKING INLINE string read_file(bool auto_unwrap) const;
   BLOCKING virtual istream *open_read_file(bool auto_unwrap) const;
-  BLOCKING void close_read_file(istream *stream) const;
+  BLOCKING virtual void close_read_file(istream *stream) const;
   virtual bool was_read_successful() const;
+
+  BLOCKING INLINE bool write_file(const string &data, bool auto_wrap);
+  BLOCKING virtual ostream *open_write_file(bool auto_wrap);
+  BLOCKING virtual void close_write_file(ostream *stream);
+  virtual bool was_write_successful() const;
+
   BLOCKING virtual off_t get_file_size(istream *stream) const;
   BLOCKING virtual off_t get_file_size() const;
   BLOCKING virtual time_t get_timestamp() const;
@@ -67,11 +74,11 @@ public:
   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;
+  virtual bool write_file(const unsigned char *data, size_t data_size, bool auto_wrap);
 
   static bool simple_read_file(istream *stream, pvector<unsigned char> &result);
   static bool simple_read_file(istream *stream, pvector<unsigned char> &result, size_t max_bytes);
 
-
 protected:
   virtual bool scan_local_directory(VirtualFileList *file_list, 
                                     const ov_set<string> &mount_points) const;

+ 128 - 14
panda/src/express/virtualFileMount.cxx

@@ -42,18 +42,62 @@ VirtualFileMount::
 PT(VirtualFile) VirtualFileMount::
 make_virtual_file(const Filename &local_filename,
                   const Filename &original_filename, bool implicit_pz_file,
-                  bool) {
+                  int open_flags) {
   Filename local(local_filename);
   if (original_filename.is_text()) {
     local.set_text();
   }
   PT(VirtualFileSimple) file =
-    new VirtualFileSimple(this, local, implicit_pz_file);
+    new VirtualFileSimple(this, local, implicit_pz_file, open_flags);
   file->set_original_filename(original_filename);
 
+  if ((open_flags & VirtualFileSystem::OF_create_file) != 0) {
+    create_file(local);
+  } else if ((open_flags & VirtualFileSystem::OF_make_directory) != 0) {
+    make_directory(local);
+  }
+
   return file.p();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::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 VirtualFileMount::
+create_file(const Filename &file) {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::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 VirtualFileMount::
+make_directory(const Filename &file) {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::is_writable
+//       Access: Public, Virtual
+//  Description: Returns true if the named file or directory may be
+//               written to, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMount::
+is_writable(const Filename &file) const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMount::read_file
 //       Access: Public, Virtual
@@ -89,6 +133,34 @@ read_file(const Filename &file, bool do_uncompress,
   return okflag;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::write_file
+//       Access: Public, Virtual
+//  Description: Writes the indicated data to the file, if it is a
+//               writable file.  Returns true on success, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMount::
+write_file(const Filename &file, bool do_compress,
+           const unsigned char *data, size_t data_size) {
+  ostream *out = open_write_file(file, do_compress);
+  if (out == (ostream *)NULL) {
+    express_cat.info()
+      << "Unable to write " << file << "\n";
+    return false;
+  }
+
+  out->write((const char *)data, data_size);
+  bool okflag = (!out->fail());
+  close_write_file(out);
+
+  if (!okflag) {
+    express_cat.info()
+      << "Error while writing " << file << "\n";
+  }
+  return okflag;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMount::open_read_file
 //       Access: Published, Virtual
@@ -117,7 +189,7 @@ open_read_file(const Filename &file, bool do_uncompress) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMount::close_read_file
-//       Access: Public
+//       Access: Public, Virtual
 //  Description: Closes a file opened by a previous call to
 //               open_read_file().  This really just deletes the
 //               istream pointer, but it is recommended to use this
@@ -126,18 +198,60 @@ open_read_file(const Filename &file, bool do_uncompress) const {
 ////////////////////////////////////////////////////////////////////
 void VirtualFileMount::
 close_read_file(istream *stream) const {
-  if (stream != (istream *)NULL) {
-    // For some reason--compiler bug in gcc 3.2?--explicitly deleting
-    // the stream pointer does not call the appropriate global delete
-    // function; instead apparently calling the system delete
-    // function.  So we call the delete function by hand instead.
-#if !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
-    stream->~istream();
-    (*global_operator_delete)(stream);
-#else
-    delete stream;
-#endif
+  VirtualFileSystem::close_read_file(stream);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::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 *VirtualFileMount::
+open_write_file(const Filename &file) {
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::open_write_file
+//       Access: Published
+//  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.
+//
+//               If do_compress is true, the file is also
+//               compressed on-the-fly using zlib.
+////////////////////////////////////////////////////////////////////
+ostream *VirtualFileMount::
+open_write_file(const Filename &file, bool do_compress) {
+  ostream *result = open_write_file(file);
+
+#ifdef HAVE_ZLIB
+  if (result != (ostream *)NULL && do_compress) {
+    // We have to slip in a layer to compress the file on the fly.
+    OCompressStream *wrapper = new OCompressStream(result, true);
+    result = wrapper;
   }
+#endif  // HAVE_ZLIB
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::close_write_file
+//       Access: Public, Virtual
+//  Description: Closes a file opened by a previous call to
+//               open_write_file().  This really just deletes the
+//               ostream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFileMount::
+close_write_file(ostream *stream) {
+  VirtualFileSystem::close_write_file(stream);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 12 - 2
panda/src/express/virtualFileMount.h

@@ -43,18 +43,28 @@ public:
   virtual PT(VirtualFile) make_virtual_file(const Filename &local_filename,
                                             const Filename &original_filename,
                                             bool implicit_pz_file,
-                                            bool status_only);
+                                            int open_flags);
 
   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 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;
 
   virtual bool read_file(const Filename &file, bool do_uncompress,
                          pvector<unsigned char> &result) const;
+  virtual bool write_file(const Filename &file, bool do_compress,
+                          const unsigned char *data, size_t data_size);
 
   virtual istream *open_read_file(const Filename &file) const=0;
   istream *open_read_file(const Filename &file, bool do_uncompress) const;
-  void close_read_file(istream *stream) const;
+  virtual void close_read_file(istream *stream) const;
+
+  virtual ostream *open_write_file(const Filename &file);
+  ostream *open_write_file(const Filename &file, bool do_compress);
+  virtual void close_write_file(ostream *stream);
+
   virtual off_t get_file_size(const Filename &file, istream *stream) const=0;
   virtual off_t get_file_size(const Filename &file) const=0;
   virtual time_t get_timestamp(const Filename &file) const=0;

+ 85 - 0
panda/src/express/virtualFileMountSystem.cxx

@@ -43,6 +43,37 @@ has_file(const Filename &file) const {
   return pathname.exists();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountSystem::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 VirtualFileMountSystem::
+create_file(const Filename &file) {
+  Filename pathname(_physical_filename, file);
+  pathname.set_binary();
+  ofstream stream;
+  return pathname.open_write(stream, false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountSystem::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 VirtualFileMountSystem::
+make_directory(const Filename &file) {
+  Filename pathname(_physical_filename, file);
+  return pathname.mkdir();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMountSystem::is_directory
 //       Access: Public, Virtual
@@ -83,6 +114,26 @@ is_regular_file(const Filename &file) const {
   return pathname.is_regular_file();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountSystem::is_writable
+//       Access: Public, Virtual
+//  Description: Returns true if the named file or directory may be
+//               written to, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountSystem::
+is_writable(const Filename &file) 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 false;
+    }
+  }
+#endif  // WIN32
+  Filename pathname(_physical_filename, file);
+  return pathname.is_writable();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMountSystem::open_read_file
 //       Access: Public, Virtual
@@ -117,6 +168,40 @@ open_read_file(const Filename &file) const {
   return stream;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountSystem::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 *VirtualFileMountSystem::
+open_write_file(const Filename &file) {
+#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);
+  if (file.is_text()) {
+    pathname.set_text();
+  } else {
+    pathname.set_binary();
+  }
+  pofstream *stream = new pofstream;
+  if (!pathname.open_write(*stream)) {
+    // Couldn't open the file for some reason.
+    close_write_file(stream);
+    return NULL;
+  }
+
+  return stream;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMountSystem::get_file_size
 //       Access: Published, Virtual

+ 4 - 0
panda/src/express/virtualFileMountSystem.h

@@ -32,10 +32,14 @@ PUBLISHED:
 
 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 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);
   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;

+ 3 - 2
panda/src/express/virtualFileSimple.I

@@ -20,10 +20,11 @@
 ////////////////////////////////////////////////////////////////////
 INLINE VirtualFileSimple::
 VirtualFileSimple(VirtualFileMount *mount, const Filename &local_filename,
-                  bool implicit_pz_file) :
+                  bool implicit_pz_file, int open_flags) :
   _mount(mount),
   _local_filename(local_filename),
-  _implicit_pz_file(implicit_pz_file)
+  _implicit_pz_file(implicit_pz_file),
+  _open_flags(open_flags)
 {
 }
 

+ 86 - 1
panda/src/express/virtualFileSimple.cxx

@@ -87,6 +87,18 @@ is_regular_file() const {
   return _mount->is_regular_file(_local_filename);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::is_writable
+//       Access: Published, Virtual
+//  Description: Returns true if this file represents a writable
+//               regular file (and write_file() may be called), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileSimple::
+is_writable() const {
+  return _mount->is_writable(_local_filename);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSimple::open_read_file
 //       Access: Published, Virtual
@@ -116,6 +128,59 @@ open_read_file(bool auto_unwrap) const {
   return _mount->open_read_file(local_filename, do_uncompress);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::close_read_file
+//       Access: Published
+//  Description: Closes a file opened by a previous call to
+//               open_read_file().  This really just deletes the
+//               istream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFileSimple::
+close_read_file(istream *stream) const {
+  _mount->close_read_file(stream);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::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.
+//
+//               If auto_wrap is true, an explicitly-named .pz file
+//               is automatically compressed.
+////////////////////////////////////////////////////////////////////
+ostream *VirtualFileSimple::
+open_write_file(bool auto_wrap) const {
+  // Will we be automatically wrapping a .pz file?
+  bool do_compress = (_implicit_pz_file || (auto_wrap && _local_filename.get_extension() == "pz"));
+
+  Filename local_filename(_local_filename);
+  if (do_compress) {
+    // .pz files are always binary, of course.
+    local_filename.set_binary();
+  }
+
+  return _mount->open_write_file(local_filename, do_compress);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::close_write_file
+//       Access: Published
+//  Description: Closes a file opened by a previous call to
+//               open_write_file().  This really just deletes the
+//               ostream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFileSimple::
+close_write_file(ostream *stream) const {
+  _mount->close_write_file(stream);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSimple::get_file_size
 //       Access: Published, Virtual
@@ -197,6 +262,26 @@ read_file(pvector<unsigned char> &result, bool auto_unwrap) const {
   return _mount->read_file(local_filename, do_uncompress, result);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::write_file
+//       Access: Public, Virtual
+//  Description: Writes the indicated data to the file, if it is
+//               writable.  Returns true on success, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileSimple::
+write_file(const unsigned char *data, size_t data_size, bool auto_wrap) {
+  // Will we be automatically wrapping a .pz file?
+  bool do_compress = (_implicit_pz_file || (auto_wrap && _local_filename.get_extension() == "pz"));
+
+  Filename local_filename(_local_filename);
+  if (do_compress) {
+    // .pz files are always binary, of course.
+    local_filename.set_binary();
+  }
+
+  return _mount->write_file(local_filename, do_compress, data, data_size);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSimple::scan_local_directory
 //       Access: Protected, Virtual
@@ -226,7 +311,7 @@ scan_local_directory(VirtualFileList *file_list,
     const string &basename = (*ni);
     if (mount_points.find(basename) == mount_points.end()) {
       Filename filename(_local_filename, basename);
-      VirtualFileSimple *file = new VirtualFileSimple(_mount, filename, false);
+      VirtualFileSimple *file = new VirtualFileSimple(_mount, filename, false, 0);
       file_list->add_file(file);
     }
   }

+ 8 - 1
panda/src/express/virtualFileSimple.h

@@ -30,7 +30,8 @@ class EXPCL_PANDAEXPRESS VirtualFileSimple : public VirtualFile {
 public:
   INLINE VirtualFileSimple(VirtualFileMount *mount,
                            const Filename &local_filename,
-                           bool implicit_pz_file);
+                           bool implicit_pz_file,
+                           int open_flags);
 
 PUBLISHED:
   virtual VirtualFileSystem *get_file_system() const;
@@ -40,9 +41,13 @@ PUBLISHED:
   virtual bool has_file() const;
   virtual bool is_directory() const;
   virtual bool is_regular_file() const;
+  virtual bool is_writable() const;
   INLINE bool is_implicit_pz_file() const;
 
   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) const;
+  virtual void close_write_file(ostream *stream) const;
   virtual off_t get_file_size(istream *stream) const;
   virtual off_t get_file_size() const;
   virtual time_t get_timestamp() const;
@@ -50,6 +55,7 @@ PUBLISHED:
 
 public:
   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);
 
 protected:
   virtual bool scan_local_directory(VirtualFileList *file_list, 
@@ -59,6 +65,7 @@ private:
   VirtualFileMount *_mount;
   Filename _local_filename;
   bool _implicit_pz_file;
+  int _open_flags;
 
 public:
   virtual TypeHandle get_type() const {

+ 29 - 0
panda/src/express/virtualFileSystem.I

@@ -122,6 +122,20 @@ read_file(const Filename &filename, bool auto_unwrap) const {
   return result;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::write_file
+//       Access: Published
+//  Description: Convenience function; writes the entire contents of
+//               the indicated file as a string.
+//
+//               If auto_wrap is true, an explicitly-named .pz file
+//               is automatically compressed while writing.
+////////////////////////////////////////////////////////////////////
+INLINE bool VirtualFileSystem::
+write_file(const Filename &filename, const string &data, bool auto_wrap) {
+  return write_file(filename, (const unsigned char *)data.data(), data.size(), auto_wrap);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSystem::read_file
 //       Access: Public
@@ -159,3 +173,18 @@ read_file(const Filename &filename, pvector<unsigned char> &result, bool auto_un
   PT(VirtualFile) file = get_file(filename, false);
   return (file != (VirtualFile *)NULL && file->read_file(result, auto_unwrap));
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::write_file
+//       Access: Public
+//  Description: Convenience function; writes the entire contents of
+//               the indicated file as a block of data.
+//
+//               If auto_wrap is true, an explicitly-named .pz file
+//               is automatically compressed while writing.
+////////////////////////////////////////////////////////////////////
+INLINE bool VirtualFileSystem::
+write_file(const Filename &filename, const unsigned char *data, size_t data_size, bool auto_wrap) {
+  PT(VirtualFile) file = create_file(filename);
+  return (file != (VirtualFile *)NULL && file->write_file(data, data_size, auto_wrap));
+}

+ 122 - 15
panda/src/express/virtualFileSystem.cxx

@@ -476,7 +476,7 @@ chdir(const Filename &new_directory) {
     return true;
   }
 
-  PT(VirtualFile) file = do_get_file(new_directory, true);
+  PT(VirtualFile) file = do_get_file(new_directory, OF_status_only);
   if (file != (VirtualFile *)NULL && file->is_directory()) {
     _cwd = file->get_filename();
     _lock.release();
@@ -499,6 +499,23 @@ get_cwd() const {
   return result;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::make_directory
+//       Access: Published
+//  Description: Attempts to create a directory within the file
+//               system.  Returns true on success, false on failure
+//               (for instance, because the parent directory does not
+//               exist, or is read-only).  If the directory already
+//               existed prior to this call, returns true.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileSystem::
+make_directory(const Filename &filename) {
+  ((VirtualFileSystem *)this)->_lock.acquire();
+  PT(VirtualFile) result = do_get_file(filename, OF_make_directory);
+  ((VirtualFileSystem *)this)->_lock.release();
+  return result->is_directory();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSystem::get_file
 //       Access: Published
@@ -517,8 +534,27 @@ get_cwd() const {
 ////////////////////////////////////////////////////////////////////
 PT(VirtualFile) VirtualFileSystem::
 get_file(const Filename &filename, bool status_only) const {
+  int open_flags = status_only ? 0 : OF_status_only;
+  ((VirtualFileSystem *)this)->_lock.acquire();
+  PT(VirtualFile) result = do_get_file(filename, open_flags);
+  ((VirtualFileSystem *)this)->_lock.release();
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::create_file
+//       Access: Published
+//  Description: Attempts to create a file by the indicated name in
+//               the filesystem, if possible, and returns it.  If a
+//               file by this name already exists, returns the same
+//               thing as get_file().  If the filename is located
+//               within a read-only directory, or the directory
+//               doesn't exist, returns NULL.
+////////////////////////////////////////////////////////////////////
+PT(VirtualFile) VirtualFileSystem::
+create_file(const Filename &filename) {
   ((VirtualFileSystem *)this)->_lock.acquire();
-  PT(VirtualFile) result = do_get_file(filename, status_only);
+  PT(VirtualFile) result = do_get_file(filename, OF_create_file);
   ((VirtualFileSystem *)this)->_lock.release();
   return result;
 }
@@ -843,6 +879,76 @@ close_read_file(istream *stream) {
   }
 }
 
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::__py__write_file
+//       Access: Published
+//  Description: Convenience function; writes the entire contents of
+//               the indicated file as a string.
+//
+//               This variant on write_file() is implemented directly
+//               for Python, as a small optimization, to avoid the
+//               double-construction of a string object that would be
+//               otherwise required.
+////////////////////////////////////////////////////////////////////
+PyObject *VirtualFileSystem::
+__py__write_file(const Filename &filename, PyObject *data, bool auto_wrap) {
+  char *buffer;
+  Py_ssize_t length;
+  if (PyString_AsStringAndSize(data, &buffer, &length) == -1) {
+    return NULL;
+  }
+   
+  bool result = write_file(filename, (const unsigned char *)buffer, length, auto_wrap);
+  return PyBool_FromLong(result);
+}
+#endif  // HAVE_PYTHON
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::open_write_file
+//       Access: Published
+//  Description: Convenience function; returns a newly allocated
+//               ostream if the file exists and can be written, or
+//               NULL otherwise.  Does not return an invalid ostream.
+//
+//               If auto_wrap is true, an explicitly-named .pz file
+//               is automatically compressed while writing.
+////////////////////////////////////////////////////////////////////
+ostream *VirtualFileSystem::
+open_write_file(const Filename &filename, bool auto_wrap) {
+  PT(VirtualFile) file = create_file(filename);
+  if (file == (VirtualFile *)NULL) {
+    return NULL;
+  }
+  ostream *str = file->open_write_file(auto_wrap);
+  if (str != (ostream *)NULL && str->fail()) {
+    close_write_file(str);
+    str = (ostream *)NULL;
+  }
+  return str;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSystem::close_write_file
+//       Access: Published, Static
+//  Description: Closes a file opened by a previous call to
+//               open_write_file().  This really just deletes the
+//               ostream pointer, but it is recommended to use this
+//               interface instead of deleting it explicitly, to help
+//               work around compiler issues.
+////////////////////////////////////////////////////////////////////
+void VirtualFileSystem::
+close_write_file(ostream *stream) {
+  if (stream != (ostream *)NULL) {
+#if (!defined(WIN32_VC) && !defined(WIN64_VC)) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
+    stream->~ostream();
+    (*global_operator_delete)(stream);
+#else
+    delete stream;
+#endif
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSystem::scan_mount_points
 //       Access: Public
@@ -948,11 +1054,12 @@ do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSystem::do_get_file
 //       Access: Private
-//  Description: The private implementation of get_file().  Assumes
-//               the lock is already held.
+//  Description: The private implementation of get_file(),
+//               create_file(), and make_directory().  Assumes the
+//               lock is already held.
 ////////////////////////////////////////////////////////////////////
 PT(VirtualFile) VirtualFileSystem::
-do_get_file(const Filename &filename, bool status_only) const {
+do_get_file(const Filename &filename, int open_flags) const {
   nassertr(!filename.empty(), NULL);
   Filename pathname(filename);
   if (pathname.is_local()) {
@@ -984,19 +1091,19 @@ do_get_file(const Filename &filename, bool status_only) const {
       // Here's an exact match on the mount point.  This filename is
       // the root directory of this mount object.
       if (consider_match(found_file, composite_file, mount, "", pathname,
-                         false, status_only)) {
+                         false, open_flags)) {
         return found_file;
       }
     } else if (mount_point.empty()) {
       // This is the root mount point; all files are in here.
       if (consider_match(found_file, composite_file, mount, strpath, 
-                         pathname, false, status_only)) {
+                         pathname, false, open_flags)) {
         return found_file;
       }
 #ifdef HAVE_ZLIB
       if (vfs_implicit_pz) {
         if (consider_match(found_file, composite_file, mount, strpath_pz, 
-                           pathname, true, status_only)) {
+                           pathname, true, open_flags)) {
           return found_file;
         }
       }
@@ -1009,14 +1116,14 @@ do_get_file(const Filename &filename, bool status_only) const {
       Filename local_filename = strpath.substr(mount_point.length() + 1);
       Filename local_filename_pz = strpath_pz.substr(mount_point.length() + 1);
       if (consider_match(found_file, composite_file, mount, local_filename, 
-                         pathname, false, status_only)) {
+                         pathname, false, open_flags)) {
         return found_file;
       }
 #ifdef HAVE_ZLIB
       if (vfs_implicit_pz) {
         // Bingo!
         if (consider_match(found_file, composite_file, mount, local_filename_pz,
-                           pathname, true, status_only)) {
+                           pathname, true, open_flags)) {
           return found_file;
         }
       }
@@ -1040,7 +1147,7 @@ do_get_file(const Filename &filename, bool status_only) const {
     if (start_seq != _mount_seq) {
       // Yes, it was, or some nested file was.  Now that we've
       // implicitly mounted the .mf file, go back and look again.
-      return do_get_file(filename, status_only);
+      return do_get_file(filename, open_flags);
     }
   }
 
@@ -1064,9 +1171,9 @@ bool VirtualFileSystem::
 consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
                VirtualFileMount *mount, const Filename &local_filename,
                const Filename &original_filename, bool implicit_pz_file,
-               bool status_only) const {
+               int open_flags) const {
   PT(VirtualFile) vfile = 
-    mount->make_virtual_file(local_filename, original_filename, false, status_only);
+    mount->make_virtual_file(local_filename, original_filename, false, open_flags);
   if (!vfile->has_file()) {
     // Keep looking.
     return false;
@@ -1075,8 +1182,8 @@ consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_fil
   if (found_file == (VirtualFile *)NULL) {
     // This was our first match.  Save it.
     found_file = vfile;
-    if (!found_file->is_directory()) {
-      // If it's not a directory, we're done.
+    if (!found_file->is_directory() || ((open_flags & OF_make_directory) != 0)) {
+      // If it's not a directory (or we wanted to make a directory), we're done.
       return true;
     }
     // It is a directory, so save it for later.

+ 23 - 2
panda/src/express/virtualFileSystem.h

@@ -68,11 +68,14 @@ PUBLISHED:
 
   BLOCKING bool chdir(const Filename &new_directory);
   BLOCKING Filename get_cwd() const;
+  BLOCKING bool make_directory(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 resolve_filename(Filename &filename, const DSearchPath &searchpath,
                                  const string &default_extension = string()) const;
   BLOCKING int find_all_files(const Filename &filename, const DSearchPath &searchpath,
@@ -99,9 +102,17 @@ PUBLISHED:
   BLOCKING istream *open_read_file(const Filename &filename, bool auto_unwrap) const;
   BLOCKING static void close_read_file(istream *stream);
 
+#ifdef HAVE_PYTHON
+  BLOCKING PyObject *__py__write_file(const Filename &filename, PyObject *data, bool auto_wrap);
+#endif  // HAVE_PYTHON
+  BLOCKING INLINE bool write_file(const Filename &filename, const string &data, bool auto_wrap);
+  BLOCKING ostream *open_write_file(const Filename &filename, bool auto_wrap);
+  BLOCKING static void close_write_file(ostream *stream);
+
 public:
   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;
 
@@ -109,6 +120,15 @@ public:
                            int &flags, string &password);
 
 public:
+  // These flags are passed to do_get_file() and
+  // VirtualFileMount::make_virtual_file() to quality the kind of
+  // VirtualFile pointer we want to get.
+  enum OpenFlags {
+    OF_status_only    = 0x0001,
+    OF_create_file    = 0x0002,
+    OF_make_directory = 0x0004,
+  };
+
   // These are declared as class instances, instead of as globals, to
   // guarantee they will be initialized by the time the
   // VirtualFileSystem's constructor runs.
@@ -119,11 +139,12 @@ public:
 private:
   Filename normalize_mount_point(const Filename &mount_point) const;
   bool do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags);
-  PT(VirtualFile) do_get_file(const Filename &filename, bool status_only) const;
+  PT(VirtualFile) do_get_file(const Filename &filename, int open_flags) const;
+
   bool consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
                       VirtualFileMount *mount, const Filename &local_filename,
                       const Filename &original_filename, bool implicit_pz_file,
-                      bool status_only) const;
+                      int open_flags) const;
   bool consider_mount_mf(const Filename &filename);
 
   MutexImpl _lock;