Browse Source

package dependencies

David Rose 16 years ago
parent
commit
687961b626

+ 118 - 17
direct/src/plugin/p3dInstance.cxx

@@ -24,6 +24,7 @@
 #include "p3dObject.h"
 #include "p3dObject.h"
 #include "p3dToplevelObject.h"
 #include "p3dToplevelObject.h"
 #include "p3dUndefinedObject.h"
 #include "p3dUndefinedObject.h"
+#include "p3dMultifileReader.h"
 
 
 #include <sstream>
 #include <sstream>
 #include <algorithm>
 #include <algorithm>
@@ -68,6 +69,7 @@ P3DInstance(P3D_request_ready_func *func,
   INIT_LOCK(_request_lock);
   INIT_LOCK(_request_lock);
 
 
   _session = NULL;
   _session = NULL;
+  _panda3d = NULL;
   _splash_window = NULL;
   _splash_window = NULL;
   _instance_window_opened = false;
   _instance_window_opened = false;
   _requested_stop = false;
   _requested_stop = false;
@@ -157,15 +159,25 @@ set_p3d_filename(const string &p3d_filename) {
   // This also sets up some internal data based on the contents of the
   // This also sets up some internal data based on the contents of the
   // above file and the associated tokens.
   // above file and the associated tokens.
 
 
-  // For the moment, all sessions will be unique.
+  // Extract the application desc file from the p3d file.
+  P3DMultifileReader reader;
+  stringstream sstream;
+  if (!reader.extract_one(p3d_filename, sstream, "p3d_info.xml")) {
+    nout << "No p3d_info.xml file found in " << p3d_filename << "\n";
+  } else {
+    sstream.seekg(0);
+    TiXmlDocument doc;
+    sstream >> doc;
+    scan_app_desc_file(&doc);
+  }
+
+  // For the moment, all sessions will be unique.  TODO: support
+  // multiple instances per session.
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   ostringstream strm;
   ostringstream strm;
   strm << inst_mgr->get_unique_id();
   strm << inst_mgr->get_unique_id();
   _session_key = strm.str();
   _session_key = strm.str();
 
 
-  // TODO.
-  _python_version = "python24";
-
   // Generate a special notification: onpluginload, indicating the
   // Generate a special notification: onpluginload, indicating the
   // plugin has read its parameters and is ready to be queried (even
   // plugin has read its parameters and is ready to be queried (even
   // if Python has not yet started).
   // if Python has not yet started).
@@ -605,15 +617,62 @@ handle_event(P3D_event_data event) {
 //       Access: Public
 //       Access: Public
 //  Description: Adds the package to the list of packages used by this
 //  Description: Adds the package to the list of packages used by this
 //               instance.  The instance will share responsibility for
 //               instance.  The instance will share responsibility for
-//               downloading the package will any of the other
+//               downloading the package with any of the other
 //               instances that use the same package.
 //               instances that use the same package.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 void P3DInstance::
 add_package(P3DPackage *package) {
 add_package(P3DPackage *package) {
-  assert(find(_packages.begin(), _packages.end(), package) == _packages.end());
+  if (find(_packages.begin(), _packages.end(), package) != _packages.end()) {
+    // Already have this package.
+    return;
+  }
 
 
   _packages.push_back(package);
   _packages.push_back(package);
   package->set_instance(this);
   package->set_instance(this);
+
+  if (package->get_package_name() == "panda3d") {
+    _panda3d = package;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::get_packages_ready
+//       Access: Public
+//  Description: Returns true if all of the packages required by the
+//               instance (as specified in previous calls to
+//               add_package()) have been fully downloaded and are
+//               ready to run, or false if one or more of them still
+//               requires downloading.
+////////////////////////////////////////////////////////////////////
+bool P3DInstance::
+get_packages_ready() const {
+  Packages::const_iterator pi;
+  for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
+    if (!(*pi)->get_ready()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::get_packages_failed
+//       Access: Public
+//  Description: Returns true if any of the packages required by the
+//               instance have failed to download (and thus we will
+//               never be ready).
+////////////////////////////////////////////////////////////////////
+bool P3DInstance::
+get_packages_failed() const {
+  Packages::const_iterator pi;
+  for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
+    if ((*pi)->get_failed()) {
+      return true;
+    }
+  }
+
+  return false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -700,6 +759,37 @@ make_xml() {
   return xinstance;
   return xinstance;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::scan_app_desc_file
+//       Access: Private
+//  Description: Reads the p3d_info.xml file at instance startup, to
+//               determine the set of required packages and so forth.
+////////////////////////////////////////////////////////////////////
+void P3DInstance::
+scan_app_desc_file(TiXmlDocument *doc) {
+  TiXmlElement *xpackage = doc->FirstChildElement("package");
+  if (xpackage == NULL) {
+    return;
+  }
+
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+
+  TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
+  while (xrequires != NULL) {
+    const char *name = xrequires->Attribute("name");
+    if (name != NULL) {
+      const char *version = xrequires->Attribute("version");
+      if (version == NULL) {
+        version = "";
+      }
+      P3DPackage *package = inst_mgr->get_package(name, version);
+      add_package(package);
+    }
+
+    xrequires = xrequires->NextSiblingElement("requires");
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstance::send_browser_script_object
 //     Function: P3DInstance::send_browser_script_object
 //       Access: Private
 //       Access: Private
@@ -1010,31 +1100,42 @@ start_package_download(P3DPackage *package) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DInstance::install_progress
+//     Function: P3DInstance::report_package_progress
 //       Access: Private
 //       Access: Private
-//  Description: Notified as the _panda3d package is downloaded.
+//  Description: Notified as a required package is downloaded.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 void P3DInstance::
-install_progress(P3DPackage *package, double progress) {
+report_package_progress(P3DPackage *package, double progress) {
   if (_splash_window != NULL) {
   if (_splash_window != NULL) {
-    _splash_window->set_install_label("Installing Panda3D");
+    if (!package->get_package_display_name().empty()) {
+      _splash_window->set_install_label("Installing " + package->get_package_display_name());
+    }
     _splash_window->set_install_progress(progress);
     _splash_window->set_install_progress(progress);
   }
   }
   _panda_script_object->set_float_property("downloadProgress", progress);
   _panda_script_object->set_float_property("downloadProgress", progress);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DInstance::package_ready
+//     Function: P3DInstance::report_package_done
 //       Access: Private
 //       Access: Private
-//  Description: Notified when the package is fully downloaded.
+//  Description: Notified when a required package is fully downloaded,
+//               or failed.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 void P3DInstance::
-package_ready(P3DPackage *package, bool success) {
+report_package_done(P3DPackage *package, bool success) {
   if (success) {
   if (success) {
-    install_progress(package, 1.0);
-    _panda_script_object->set_bool_property("downloadComplete", true);
-    _panda_script_object->set_string_property("status", "starting");
-    send_notify("ondownloadcomplete");
+    report_package_progress(package, 1.0);
+
+    if (get_packages_ready()) {
+      _panda_script_object->set_bool_property("downloadComplete", true);
+      _panda_script_object->set_string_property("status", "starting");
+      send_notify("ondownloadcomplete");
+
+      // Notify the session also.
+      if (_session != NULL) {
+        _session->report_packages_done(this, success);
+      }
+    }
   }
   }
 }
 }
 
 

+ 11 - 2
direct/src/plugin/p3dInstance.h

@@ -80,6 +80,8 @@ public:
   inline P3D_request_ready_func *get_request_ready_func() const;
   inline P3D_request_ready_func *get_request_ready_func() const;
 
 
   void add_package(P3DPackage *package);
   void add_package(P3DPackage *package);
+  bool get_packages_ready() const;
+  bool get_packages_failed() const;
   
   
   void start_download(P3DDownload *download);
   void start_download(P3DDownload *download);
   inline bool is_started() const;
   inline bool is_started() const;
@@ -100,6 +102,8 @@ private:
     P3DInstance *_inst;
     P3DInstance *_inst;
   };
   };
 
 
+  void scan_app_desc_file(TiXmlDocument *doc);
+
   void send_browser_script_object();
   void send_browser_script_object();
   P3D_request *make_p3d_request(TiXmlElement *xrequest);
   P3D_request *make_p3d_request(TiXmlElement *xrequest);
   void handle_notify_request(const string &message);
   void handle_notify_request(const string &message);
@@ -108,8 +112,8 @@ private:
                              bool needs_response, int unique_id);
                              bool needs_response, int unique_id);
   void make_splash_window();
   void make_splash_window();
   void start_package_download(P3DPackage *package);
   void start_package_download(P3DPackage *package);
-  void install_progress(P3DPackage *package, double progress);
-  void package_ready(P3DPackage *package, bool success);
+  void report_package_progress(P3DPackage *package, double progress);
+  void report_package_done(P3DPackage *package, bool progress);
 
 
   void paint_window();
   void paint_window();
   void add_modifier_flags(unsigned int &swb_flags, int modifiers);
   void add_modifier_flags(unsigned int &swb_flags, int modifiers);
@@ -157,6 +161,10 @@ private:
   typedef vector<P3DPackage *> Packages;
   typedef vector<P3DPackage *> Packages;
   Packages _packages;
   Packages _packages;
 
 
+  // We keep the _panda3d pointer separately because it's so
+  // important, but it's in the above vector also.
+  P3DPackage *_panda3d;  
+
   typedef map<int, P3DDownload *> Downloads;
   typedef map<int, P3DDownload *> Downloads;
   Downloads _downloads;
   Downloads _downloads;
 
 
@@ -175,6 +183,7 @@ private:
   friend class P3DSession;
   friend class P3DSession;
   friend class SplashDownload;
   friend class SplashDownload;
   friend class P3DWindowParams;
   friend class P3DWindowParams;
+  friend class P3DPackage;
 };
 };
 
 
 #include "p3dInstance.I"
 #include "p3dInstance.I"

+ 3 - 4
direct/src/plugin/p3dInstanceManager.cxx

@@ -352,8 +352,7 @@ wait_request() {
 //               package.
 //               package.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 P3DPackage *P3DInstanceManager::
 P3DPackage *P3DInstanceManager::
-get_package(const string &package_name, const string &package_version, 
-            const string &package_display_name) {
+get_package(const string &package_name, const string &package_version) {
   string package_platform = get_platform();
   string package_platform = get_platform();
   string key = package_name + "_" + package_platform + "_" + package_version;
   string key = package_name + "_" + package_platform + "_" + package_version;
   Packages::iterator pi = _packages.find(key);
   Packages::iterator pi = _packages.find(key);
@@ -361,8 +360,8 @@ get_package(const string &package_name, const string &package_version,
     return (*pi).second;
     return (*pi).second;
   }
   }
 
 
-  P3DPackage *package = new P3DPackage(package_name, package_platform, 
-                                       package_version, package_display_name);
+  P3DPackage *package = 
+    new P3DPackage(package_name, package_platform, package_version);
   bool inserted = _packages.insert(Packages::value_type(key, package)).second;
   bool inserted = _packages.insert(Packages::value_type(key, package)).second;
   assert(inserted);
   assert(inserted);
 
 

+ 1 - 2
direct/src/plugin/p3dInstanceManager.h

@@ -66,8 +66,7 @@ public:
   void wait_request();
   void wait_request();
 
 
   P3DPackage *get_package(const string &package_name, 
   P3DPackage *get_package(const string &package_name, 
-                          const string &package_version,
-                          const string &package_display_name);
+                          const string &package_version);
   bool get_package_desc_file(FileSpec &desc_file, 
   bool get_package_desc_file(FileSpec &desc_file, 
                              const string &package_name,
                              const string &package_name,
                              const string &package_version);
                              const string &package_version);

+ 132 - 64
direct/src/plugin/p3dMultifileReader.cxx

@@ -48,11 +48,12 @@ P3DMultifileReader() {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DMultifileReader::extract
+//     Function: P3DMultifileReader::extract_all
 //       Access: Public
 //       Access: Public
-//  Description: Reads the named multifile, and extracts all files
-//               within it to the indicated directory.  Returns true
-//               on success, false on failure.
+//  Description: Reads the named multifile, and extracts all the
+//               expected extractable components within it to the
+//               indicated directory.  Returns true on success, false
+//               on failure.
 //
 //
 //               The parameters package, start_progress, and
 //               The parameters package, start_progress, and
 //               progress_size are provided to make the appropriate
 //               progress_size are provided to make the appropriate
@@ -60,50 +61,26 @@ P3DMultifileReader() {
 //               during this operation.
 //               during this operation.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool P3DMultifileReader::
 bool P3DMultifileReader::
-extract(const string &pathname, const string &to_dir,
-        P3DPackage *package, double start_progress, double progress_size) {
-  _subfiles.clear();
-
-  _in.open(pathname.c_str(), ios::in | ios::binary);
-  if (!_in) {
-    nout << "Couldn't open " << pathname << "\n";
+extract_all(const string &pathname, const string &to_dir,
+            P3DPackage *package, double start_progress, double progress_size) {
+  if (!read_header(pathname)) {
     return false;
     return false;
   }
   }
 
 
-  for (size_t i = 0; i < _header_size; ++i) {
-    int ch = _in.get();
-    if (ch != _header[i]) {
-      nout << "Failed header check: " << pathname << "\n";
-      return false;
-    }
-  }
-
-  unsigned int major = read_uint16();
-  unsigned int minor = read_uint16();
-  if (major != _current_major_ver || minor != _current_minor_ver) {
-    nout << "Incompatible multifile version: " << pathname << "\n";
-    return false;
-  }
-
-  unsigned int scale = read_uint32();
-  if (scale != 1) {
-    nout << "Unsupported scale factor in " << pathname << "\n";
-    return false;
-  }
-
-  // We don't care about the overall timestamp.
-  read_uint32();
-
-  if (!read_index()) {
-    nout << "Error reading multifile index\n";
-    return false;
+  // Now walk through all of the files, and extract only the ones we
+  // expect to encounter.
+  size_t num_processed = 0;
+  size_t num_expected = _subfiles.size();
+  if (package != NULL) {
+    num_expected = package->_extracts.size();
   }
   }
 
 
-  // Now walk through all of the files.
-  size_t num_processed = 0;
   Subfiles::iterator si;
   Subfiles::iterator si;
   for (si = _subfiles.begin(); si != _subfiles.end(); ++si) {
   for (si = _subfiles.begin(); si != _subfiles.end(); ++si) {
     const Subfile &s = (*si);
     const Subfile &s = (*si);
+    if (package != NULL && !package->is_extractable(s._filename)) {
+      continue;
+    }
 
 
     string output_pathname = to_dir + "/" + s._filename;
     string output_pathname = to_dir + "/" + s._filename;
     if (!mkfile_complete(output_pathname, nout)) {
     if (!mkfile_complete(output_pathname, nout)) {
@@ -116,23 +93,7 @@ extract(const string &pathname, const string &to_dir,
       return false;
       return false;
     }
     }
 
 
-    _in.seekg(s._start);
-
-    static const size_t buffer_size = 1024;
-    char buffer[buffer_size];
-
-    size_t remaining_data = s._length;
-    _in.read(buffer, min(buffer_size, remaining_data));
-    size_t count = _in.gcount();
-    while (count != 0) {
-      remaining_data -= count;
-      out.write(buffer, count);
-      _in.read(buffer, min(buffer_size, remaining_data));
-      count = _in.gcount();
-    }
-
-    if (remaining_data != 0) {
-      nout << "Unable to extract " << s._filename << "\n";
+    if (!extract_subfile(out, s)) {
       return false;
       return false;
     }
     }
 
 
@@ -146,7 +107,7 @@ extract(const string &pathname, const string &to_dir,
 
 
     ++num_processed;
     ++num_processed;
     if (package != NULL) {
     if (package != NULL) {
-      double progress = (double)num_processed / (double)_subfiles.size();
+      double progress = (double)num_processed / (double)num_expected;
       package->report_progress(start_progress + progress * progress_size);
       package->report_progress(start_progress + progress * progress_size);
     }
     }
   }
   }
@@ -155,8 +116,83 @@ extract(const string &pathname, const string &to_dir,
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DMultifileReader::read_index
+//     Function: P3DMultifileReader::extract_one
 //       Access: Public
 //       Access: Public
+//  Description: Reads the named multifile, and extracts only the
+//               named component to the indicated stream.  Returns
+//               true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DMultifileReader::
+extract_one(const string &pathname, ostream &out, const string &filename) {
+  if (!read_header(pathname)) {
+    return false;
+  }
+
+  // Look for the named component.
+  Subfiles::iterator si;
+  for (si = _subfiles.begin(); si != _subfiles.end(); ++si) {
+    const Subfile &s = (*si);
+    if (s._filename == filename) {
+      return extract_subfile(out, s);
+    }
+  }
+
+  nout << "Could not extract " << filename << ": not found.\n";
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DMultifileReader::read_header
+//       Access: Private
+//  Description: Opens the named multifile and reads the header
+//               information and index, returning true on success,
+//               false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DMultifileReader::
+read_header(const string &pathname) {
+  _subfiles.clear();
+
+  _in.open(pathname.c_str(), ios::in | ios::binary);
+  if (!_in) {
+    nout << "Couldn't open " << pathname << "\n";
+    return false;
+  }
+
+  for (size_t i = 0; i < _header_size; ++i) {
+    int ch = _in.get();
+    if (ch != _header[i]) {
+      nout << "Failed header check: " << pathname << "\n";
+      return false;
+    }
+  }
+
+  unsigned int major = read_uint16();
+  unsigned int minor = read_uint16();
+  if (major != _current_major_ver || minor != _current_minor_ver) {
+    nout << "Incompatible multifile version: " << pathname << "\n";
+    return false;
+  }
+
+  unsigned int scale = read_uint32();
+  if (scale != 1) {
+    nout << "Unsupported scale factor in " << pathname << "\n";
+    return false;
+  }
+
+  // We don't care about the overall timestamp.
+  read_uint32();
+
+  if (!read_index()) {
+    nout << "Error reading multifile index\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DMultifileReader::read_index
+//       Access: Private
 //  Description: Assuming the file stream is positioned at the first
 //  Description: Assuming the file stream is positioned at the first
 //               record, reads all of the records into the _subfiles
 //               record, reads all of the records into the _subfiles
 //               list.  Returns true on success, false on failure.
 //               list.  Returns true on success, false on failure.
@@ -172,10 +208,6 @@ read_index() {
     s._start = read_uint32();
     s._start = read_uint32();
     s._length = read_uint32();
     s._length = read_uint32();
     unsigned int flags = read_uint16();
     unsigned int flags = read_uint16();
-    if (flags != 0) {
-      nout << "Unsupported per-subfile options in multifile\n";
-      return false;
-    }
     s._timestamp = read_uint32();
     s._timestamp = read_uint32();
     size_t name_length = read_uint16();
     size_t name_length = read_uint16();
     char *buffer = new char[name_length];
     char *buffer = new char[name_length];
@@ -189,7 +221,10 @@ read_index() {
     s._filename = string(buffer, name_length);
     s._filename = string(buffer, name_length);
     delete[] buffer;
     delete[] buffer;
 
 
-    _subfiles.push_back(s);
+    if (flags == 0) {
+      // We can only support subfiles with no particular flags set.
+      _subfiles.push_back(s);
+    }
 
 
     _in.seekg(next_entry);
     _in.seekg(next_entry);
     next_entry = read_uint32();
     next_entry = read_uint32();
@@ -200,3 +235,36 @@ read_index() {
 
 
   return true;
   return true;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DMultifileReader::extract_subfile
+//       Access: Private
+//  Description: Extracts the indicated subfile and writes it to the
+//               indicated stream.  Returns true on success, false on
+//               failure.
+////////////////////////////////////////////////////////////////////
+bool P3DMultifileReader::
+extract_subfile(ostream &out, const Subfile &s) {
+  _in.seekg(s._start);
+
+  static const size_t buffer_size = 1024;
+  char buffer[buffer_size];
+  
+  size_t remaining_data = s._length;
+  _in.read(buffer, min(buffer_size, remaining_data));
+  size_t count = _in.gcount();
+  while (count != 0) {
+    remaining_data -= count;
+    out.write(buffer, count);
+    _in.read(buffer, min(buffer_size, remaining_data));
+    count = _in.gcount();
+  }
+  
+  if (remaining_data != 0) {
+    nout << "Unable to extract " << s._filename << "\n";
+    return false;
+  }
+
+  return true;
+}
+

+ 11 - 3
direct/src/plugin/p3dMultifileReader.h

@@ -31,12 +31,20 @@ class P3DMultifileReader {
 public:
 public:
   P3DMultifileReader();
   P3DMultifileReader();
 
 
-  bool extract(const string &pathname, const string &to_dir,
-               P3DPackage *package, double start_progress, 
-               double progress_size);
+  bool extract_all(const string &pathname, const string &to_dir,
+                   P3DPackage *package, double start_progress, 
+                   double progress_size);
+
+  bool extract_one(const string &pathname, ostream &out, 
+                   const string &filename);
 
 
 private:
 private:
+  class Subfile;
+
+  bool read_header(const string &pathname);
   bool read_index();
   bool read_index();
+  bool extract_subfile(ostream &out, const Subfile &s);
+
   inline unsigned int read_uint16();
   inline unsigned int read_uint16();
   inline unsigned int read_uint32();
   inline unsigned int read_uint32();
 
 

+ 45 - 94
direct/src/plugin/p3dPackage.cxx

@@ -37,12 +37,10 @@ static const double extract_portion = 0.05;
 P3DPackage::
 P3DPackage::
 P3DPackage(const string &package_name,
 P3DPackage(const string &package_name,
            const string &package_platform,
            const string &package_platform,
-           const string &package_version,
-           const string &package_display_name) :
+           const string &package_version) :
   _package_name(package_name),
   _package_name(package_name),
   _package_platform(package_platform),
   _package_platform(package_platform),
-  _package_version(package_version),
-  _package_display_name(package_display_name)
+  _package_version(package_version)
 {
 {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
 
 
@@ -87,43 +85,6 @@ P3DPackage::
   assert(_instances.empty());
   assert(_instances.empty());
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::set_callback
-//       Access: Public
-//  Description: Registers a callback on the package.  The callback
-//               object will be notified when the package is ready for
-//               use (or when it has failed to download properly).
-////////////////////////////////////////////////////////////////////
-void P3DPackage::
-set_callback(Callback *callback) {
-  if (_ready || _failed) {
-    // Actually, we're already done.  Signal the callback immediately.
-    callback->package_ready(this, _ready);
-    delete callback;
-  } else {
-    // Bootstrap still in progress.  Save the callback.
-    _callbacks.push_back(callback);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::cancel_callback
-//       Access: Public
-//  Description: Unregisters a particular callback object.  This
-//               object will no longer be notified when the package is
-//               ready.
-////////////////////////////////////////////////////////////////////
-void P3DPackage::
-cancel_callback(Callback *callback) {
-  Callbacks::iterator ci;
-  ci = find(_callbacks.begin(), _callbacks.end(), callback);
-  if (ci != _callbacks.end()) {
-    _callbacks.erase(ci);
-  } else {
-    nout << "Canceling unknown callback on " << _package_fullname << "\n";
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::set_instance
 //     Function: P3DPackage::set_instance
 //       Access: Public
 //       Access: Public
@@ -334,6 +295,11 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   TiXmlElement *compressed_archive = NULL;
   TiXmlElement *compressed_archive = NULL;
   
   
   if (xpackage != NULL) {
   if (xpackage != NULL) {
+    const char *display_name_cstr = xpackage->Attribute("display_name");
+    if (display_name_cstr != NULL) {
+      _package_display_name = display_name_cstr;
+    }
+
     uncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
     uncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
     compressed_archive = xpackage->FirstChildElement("compressed_archive");
     compressed_archive = xpackage->FirstChildElement("compressed_archive");
   }
   }
@@ -351,27 +317,27 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   _uncompressed_archive.load_xml(uncompressed_archive);
   _uncompressed_archive.load_xml(uncompressed_archive);
   _compressed_archive.load_xml(compressed_archive);
   _compressed_archive.load_xml(compressed_archive);
 
 
-  // Now get all the components.
-  _components.clear();
-  TiXmlElement *component = xpackage->FirstChildElement("component");
-  while (component != NULL) {
+  // Now get all the extractable components.
+  _extracts.clear();
+  TiXmlElement *extract = xpackage->FirstChildElement("extract");
+  while (extract != NULL) {
     FileSpec file;
     FileSpec file;
-    file.load_xml(component);
-    _components.push_back(file);
-    component = component->NextSiblingElement("component");
+    file.load_xml(extract);
+    _extracts.push_back(file);
+    extract = extract->NextSiblingElement("extract");
   }
   }
 
 
-  // Verify all of the components.
-  bool all_components_ok = true;
-  Components::iterator ci;
-  for (ci = _components.begin(); ci != _components.end(); ++ci) {
+  // Verify all of the extracts.
+  bool all_extracts_ok = true;
+  Extracts::iterator ci;
+  for (ci = _extracts.begin(); ci != _extracts.end(); ++ci) {
     if (!(*ci).quick_verify(_package_dir)) {
     if (!(*ci).quick_verify(_package_dir)) {
-      all_components_ok = false;
+      all_extracts_ok = false;
       break;
       break;
     }
     }
   }
   }
 
 
-  if (all_components_ok) {
+  if (all_extracts_ok) {
     // Great, we're ready to begin.
     // Great, we're ready to begin.
     report_done(true);
     report_done(true);
 
 
@@ -589,8 +555,8 @@ void P3DPackage::
 extract_archive() {
 extract_archive() {
   string source_pathname = _package_dir + "/" + _uncompressed_archive.get_filename();
   string source_pathname = _package_dir + "/" + _uncompressed_archive.get_filename();
   P3DMultifileReader reader;
   P3DMultifileReader reader;
-  if (!reader.extract(source_pathname, _package_dir,
-                      this, download_portion + uncompress_portion, extract_portion)) {
+  if (!reader.extract_all(source_pathname, _package_dir,
+                          this, download_portion + uncompress_portion, extract_portion)) {
     nout << "Failure extracting " << _uncompressed_archive.get_filename()
     nout << "Failure extracting " << _uncompressed_archive.get_filename()
          << "\n";
          << "\n";
     report_done(false);
     report_done(false);
@@ -604,13 +570,13 @@ extract_archive() {
 //     Function: P3DPackage::report_progress
 //     Function: P3DPackage::report_progress
 //       Access: Private
 //       Access: Private
 //  Description: Reports the indicated install progress to all
 //  Description: Reports the indicated install progress to all
-//               listening callbacks.
+//               interested instances.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 report_progress(double progress) {
 report_progress(double progress) {
-  Callbacks::iterator ci;
-  for (ci = _callbacks.begin(); ci != _callbacks.end(); ++ci) {
-    (*ci)->install_progress(this, progress);
+  Instances::iterator ii;
+  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
+    (*ii)->report_package_progress(this, progress);
   }
   }
 }
 }
 
 
@@ -618,8 +584,8 @@ report_progress(double progress) {
 //     Function: P3DPackage::report_done
 //     Function: P3DPackage::report_done
 //       Access: Private
 //       Access: Private
 //  Description: Transitions the package to "ready" or "failure"
 //  Description: Transitions the package to "ready" or "failure"
-//               state, and reports this change to all the listening
-//               callbacks.
+//               state, and reports this change to all the interested
+//               instances.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 report_done(bool success) {
 report_done(bool success) {
@@ -631,16 +597,10 @@ report_done(bool success) {
     _failed = true;
     _failed = true;
   }
   }
 
 
-  Callbacks orig_callbacks;
-  orig_callbacks.swap(_callbacks);
-  Callbacks::iterator ci;
-  for (ci = orig_callbacks.begin(); ci != orig_callbacks.end(); ++ci) {
-    (*ci)->package_ready(this, _ready);
-    delete (*ci);
+  Instances::iterator ii;
+  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
+    (*ii)->report_package_done(this, success);
   }
   }
-
-  // We shouldn't have added any more callbacks during the above loop.
-  assert(_callbacks.empty());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -674,30 +634,21 @@ start_download(P3DPackage::DownloadType dtype, const string &url,
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::Callback::Destructor
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-P3DPackage::Callback::
-~Callback() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::Callback::install_progress
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void P3DPackage::Callback::
-install_progress(P3DPackage *package, double progress) {
-}
+//     Function: P3DPackage::is_extractable
+//       Access: Private
+//  Description: Returns true if the name file is on the extract list,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool P3DPackage::
+is_extractable(const string &filename) const {
+  Extracts::const_iterator ci;
+  for (ci = _extracts.begin(); ci != _extracts.end(); ++ci) {
+    if ((*ci).get_filename() == filename) {
+      return true;
+    }
+  }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::Callback::package_ready
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void P3DPackage::Callback::
-package_ready(P3DPackage *package, bool success) {
+  return false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 5 - 17
direct/src/plugin/p3dPackage.h

@@ -38,17 +38,9 @@ class P3DPackage {
 public:
 public:
   P3DPackage(const string &package_name, 
   P3DPackage(const string &package_name, 
              const string &package_platform,
              const string &package_platform,
-             const string &package_version,
-             const string &package_display_name);
+             const string &package_version);
   ~P3DPackage();
   ~P3DPackage();
 
 
-  class Callback {
-  public:
-    virtual ~Callback();
-    virtual void install_progress(P3DPackage *package, double progress);
-    virtual void package_ready(P3DPackage *package, bool success);
-  };
-
   inline bool get_ready() const;
   inline bool get_ready() const;
   inline bool get_failed() const;
   inline bool get_failed() const;
   inline const string &get_package_dir() const;
   inline const string &get_package_dir() const;
@@ -56,9 +48,6 @@ public:
   inline const string &get_package_version() const;
   inline const string &get_package_version() const;
   inline const string &get_package_display_name() const;
   inline const string &get_package_display_name() const;
 
 
-  void set_callback(Callback *callback);
-  void cancel_callback(Callback *callback);
-
   void set_instance(P3DInstance *inst);
   void set_instance(P3DInstance *inst);
   void cancel_instance(P3DInstance *inst);
   void cancel_instance(P3DInstance *inst);
 
 
@@ -102,6 +91,8 @@ private:
   void start_download(DownloadType dtype, const string &url, 
   void start_download(DownloadType dtype, const string &url, 
                       const string &pathname, bool allow_partial);
                       const string &pathname, bool allow_partial);
 
 
+  bool is_extractable(const string &filename) const;
+
 private:
 private:
   string _package_name;
   string _package_name;
   string _package_version;
   string _package_version;
@@ -120,17 +111,14 @@ private:
   Download *_active_download;
   Download *_active_download;
   bool _partial_download;
   bool _partial_download;
 
 
-  typedef vector<Callback *> Callbacks;
-  Callbacks _callbacks;
-
   typedef vector<P3DInstance *> Instances;
   typedef vector<P3DInstance *> Instances;
   Instances _instances;
   Instances _instances;
 
 
   FileSpec _compressed_archive;
   FileSpec _compressed_archive;
   FileSpec _uncompressed_archive;
   FileSpec _uncompressed_archive;
 
 
-  typedef vector<FileSpec> Components;
-  Components _components;
+  typedef vector<FileSpec> Extracts;
+  Extracts _extracts;
 
 
   friend class Download;
   friend class Download;
   friend class P3DMultifileReader;
   friend class P3DMultifileReader;

+ 22 - 94
direct/src/plugin/p3dSession.cxx

@@ -65,14 +65,8 @@ P3DSession(P3DInstance *inst) {
   _output_filename = "/tmp/panda3d.3.log";
   _output_filename = "/tmp/panda3d.3.log";
 #endif  // _WIN32
 #endif  // _WIN32
 
 
-  _panda3d_callback = NULL;
-
   INIT_LOCK(_instances_lock);
   INIT_LOCK(_instances_lock);
   INIT_THREAD(_read_thread);
   INIT_THREAD(_read_thread);
-
-  _panda3d = inst_mgr->get_package("panda3d", "dev", "Panda3D");
-  _python_root_dir = _panda3d->get_package_dir();
-  inst->add_package(_panda3d);
 }
 }
 
 
 
 
@@ -95,12 +89,6 @@ P3DSession::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DSession::
 void P3DSession::
 shutdown() {
 shutdown() {
-  if (_panda3d_callback != NULL) {
-    _panda3d->cancel_callback(_panda3d_callback);
-    delete _panda3d_callback;
-    _panda3d_callback = NULL;
-  }
-
   if (_p3dpython_running) {
   if (_p3dpython_running) {
     // Tell the process we're going away.
     // Tell the process we're going away.
     TiXmlDocument doc;
     TiXmlDocument doc;
@@ -226,16 +214,14 @@ start_instance(P3DInstance *inst) {
   send_command(doc);
   send_command(doc);
   inst->send_browser_script_object();
   inst->send_browser_script_object();
 
 
-  if (_panda3d->get_ready()) {
+  if (inst->get_packages_ready()) {
     // If it's ready immediately, go ahead and start.
     // If it's ready immediately, go ahead and start.
-    start_p3dpython();
+    start_p3dpython(inst);
+
   } else {
   } else {
-    // Otherwise, set a callback, so we'll know when it is ready.
-    if (_panda3d_callback == NULL) {
-      _panda3d_callback = new PackageCallback(this);
-      _panda3d->set_callback(_panda3d_callback);
-    }
-    inst->start_package_download(_panda3d);
+    // Otherwise, wait for the instance to download itself.  We'll
+    // automatically get a callback to report_packages_done() when
+    // it's done.
   }
   }
 }
 }
 
 
@@ -644,53 +630,36 @@ drop_p3dobj(int object_id) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::install_progress
+//     Function: P3DSession::report_packages_done
 //       Access: Private
 //       Access: Private
-//  Description: Notified as the _panda3d package is downloaded.
+//  Description: Notified when a child instance is fully downloaded.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DSession::
 void P3DSession::
-install_progress(P3DPackage *package, double progress) {
-  Instances::iterator ii;
-  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
-    P3DInstance *inst = (*ii).second;
-    inst->install_progress(package, progress);
+report_packages_done(P3DInstance *inst, bool success) {
+  if (success) {
+    start_p3dpython(inst);
   }
   }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::package_ready
+//     Function: P3DSession::start_p3dpython
 //       Access: Private
 //       Access: Private
-//  Description: Notified when the package is fully downloaded.
+//  Description: Starts Python running in a child process.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DSession::
 void P3DSession::
-package_ready(P3DPackage *package, bool success) {
-  _panda3d_callback = NULL;
-
-  Instances::iterator ii;
-  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
-    P3DInstance *inst = (*ii).second;
-    inst->package_ready(package, success);
+start_p3dpython(P3DInstance *inst) {
+  if (_p3dpython_running) {
+    // Already started.
+    return;
   }
   }
 
 
-  if (package == _panda3d) {
-    if (success) {
-      start_p3dpython();
-    } else {
-      nout << "Failed to install " << package->get_package_name()
-           << "_" << package->get_package_version() << "\n";
-    }
-  } else {
-    nout << "Unexpected panda3d package: " << package << "\n";
+  if (inst->_panda3d == NULL) {
+    nout << "Couldn't start Python: no panda3d dependency.\n";
+    return;
   }
   }
-}
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::start_p3dpython
-//       Access: Private
-//  Description: Starts Python running in a child process.
-////////////////////////////////////////////////////////////////////
-void P3DSession::
-start_p3dpython() {
+  _python_root_dir = inst->_panda3d->get_package_dir();
+
   string p3dpython = P3D_PLUGIN_P3DPYTHON;
   string p3dpython = P3D_PLUGIN_P3DPYTHON;
   if (p3dpython.empty()) {
   if (p3dpython.empty()) {
     p3dpython = _python_root_dir + "/p3dpython";
     p3dpython = _python_root_dir + "/p3dpython";
@@ -1116,44 +1085,3 @@ posix_create_process(const string &program, const string &start_dir,
   return child;
   return child;
 }
 }
 #endif  // _WIN32
 #endif  // _WIN32
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::PackageCallback::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-P3DSession::PackageCallback::
-PackageCallback(P3DSession *session) :
-  _session(session)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::PackageCallback::install_progress
-//       Access: Public, Virtual
-//  Description: This callback is received during the download process
-//               to inform us how much has been installed so far.
-////////////////////////////////////////////////////////////////////
-void P3DSession::PackageCallback::
-install_progress(P3DPackage *package, double progress) {
-  if (this == _session->_panda3d_callback) {
-    _session->install_progress(package, progress);
-  } else {
-    nout << "Unexpected callback for P3DSession\n";
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::PackageCallback::package_ready
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void P3DSession::PackageCallback::
-package_ready(P3DPackage *package, bool success) {
-  if (this == _session->_panda3d_callback) {
-    _session->package_ready(package, success);
-  } else {
-    nout << "Unexpected callback for P3DSession\n";
-  }
-}

+ 3 - 17
direct/src/plugin/p3dSession.h

@@ -61,9 +61,8 @@ public:
   void drop_p3dobj(int object_id);
   void drop_p3dobj(int object_id);
 
 
 private:
 private:
-  void install_progress(P3DPackage *package, double progress);
-  void package_ready(P3DPackage *package, bool success);
-  void start_p3dpython();
+  void report_packages_done(P3DInstance *inst, bool success);
+  void start_p3dpython(P3DInstance *inst);
 
 
   void spawn_read_thread();
   void spawn_read_thread();
   void join_read_thread();
   void join_read_thread();
@@ -87,23 +86,11 @@ private:
                        HandleStream &pipe_read, HandleStream &pipe_write);
                        HandleStream &pipe_read, HandleStream &pipe_write);
 #endif
 #endif
 
 
-  class PackageCallback : public P3DPackage::Callback {
-  public:
-    PackageCallback(P3DSession *session);
-    
-    virtual void install_progress(P3DPackage *package, double progress);
-    virtual void package_ready(P3DPackage *package, bool success);
-    
-  protected:
-    P3DSession *_session;
-  };
-
 private:
 private:
   int _session_id;
   int _session_id;
   string _session_key;
   string _session_key;
   string _python_version;
   string _python_version;
   string _output_filename;
   string _output_filename;
-
   string _python_root_dir;
   string _python_root_dir;
 
 
   typedef map<int, P3DInstance *> Instances;
   typedef map<int, P3DInstance *> Instances;
@@ -123,7 +110,6 @@ private:
   SentObjects _sent_objects;
   SentObjects _sent_objects;
 
 
   P3DPackage *_panda3d;
   P3DPackage *_panda3d;
-  PackageCallback *_panda3d_callback;
 
 
   // Members for communicating with the p3dpython child process.
   // Members for communicating with the p3dpython child process.
 #ifdef _WIN32
 #ifdef _WIN32
@@ -146,7 +132,7 @@ private:
   bool _read_thread_continue;
   bool _read_thread_continue;
   THREAD _read_thread;
   THREAD _read_thread;
 
 
-  friend class PackageCallback;
+  friend class P3DInstance;
 };
 };
 
 
 #include "p3dSession.I"
 #include "p3dSession.I"

+ 222 - 95
direct/src/showutil/Packager.py

@@ -8,6 +8,7 @@ import os
 import glob
 import glob
 import marshal
 import marshal
 import new
 import new
+import string
 from direct.showbase import Loader
 from direct.showbase import Loader
 from direct.showutil import FreezeTool
 from direct.showutil import FreezeTool
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.directnotify.DirectNotifyGlobal import *
@@ -29,7 +30,7 @@ class Packager:
 
 
     class PackFile:
     class PackFile:
         def __init__(self, filename, newName = None, deleteTemp = False,
         def __init__(self, filename, newName = None, deleteTemp = False,
-                     extract = False):
+                     extract = None):
             assert isinstance(filename, Filename)
             assert isinstance(filename, Filename)
             self.filename = filename
             self.filename = filename
             self.newName = newName
             self.newName = newName
@@ -43,6 +44,7 @@ class Packager:
             self.version = None
             self.version = None
             self.platform = None
             self.platform = None
             self.p3dApplication = False
             self.p3dApplication = False
+            self.displayName = None
             self.files = []
             self.files = []
             self.compressionLevel = 0
             self.compressionLevel = 0
             self.importedMapsDir = 'imported_maps'
             self.importedMapsDir = 'imported_maps'
@@ -208,13 +210,14 @@ class Packager:
             if self.version:
             if self.version:
                 xpackage.SetAttribute('version', self.version)
                 xpackage.SetAttribute('version', self.version)
 
 
+            if self.displayName:
+                xpackage.SetAttribute('display_name', self.displayName)
+
             xpackage.SetAttribute('main_module', self.mainModule)
             xpackage.SetAttribute('main_module', self.mainModule)
 
 
             for package in self.requires:
             for package in self.requires:
                 xrequires = TiXmlElement('requires')
                 xrequires = TiXmlElement('requires')
                 xrequires.SetAttribute('name', package.packageName)
                 xrequires.SetAttribute('name', package.packageName)
-                if package.platform:
-                    xrequires.SetAttribute('platform', package.platform)
                 if package.version:
                 if package.version:
                     xrequires.SetAttribute('version', package.version)
                     xrequires.SetAttribute('version', package.version)
                 xpackage.InsertEndChild(xrequires)
                 xpackage.InsertEndChild(xrequires)
@@ -225,7 +228,11 @@ class Packager:
             # can add it to the multifile.
             # can add it to the multifile.
             filename = Filename.temporary('', 'p3d_', '.xml')
             filename = Filename.temporary('', 'p3d_', '.xml')
             doc.SaveFile(filename.toOsSpecific())
             doc.SaveFile(filename.toOsSpecific())
-            self.multifile.addSubfile('p3d_info.xml', filename, self.compressionLevel)
+
+            # It's important not to compress this file: the core API
+            # runtime can't decode compressed subfiles.
+            self.multifile.addSubfile('p3d_info.xml', filename, 0)
+            
             self.multifile.flush()
             self.multifile.flush()
             filename.unlink()
             filename.unlink()
             
             
@@ -252,6 +259,9 @@ class Packager:
             if self.version:
             if self.version:
                 xpackage.SetAttribute('version', self.version)
                 xpackage.SetAttribute('version', self.version)
 
 
+            if self.displayName:
+                xpackage.SetAttribute('display_name', self.displayName)
+
             xuncompressedArchive = self.getFileSpec(
             xuncompressedArchive = self.getFileSpec(
                 'uncompressed_archive', self.packageFullpath,
                 'uncompressed_archive', self.packageFullpath,
                 self.packageBasename)
                 self.packageBasename)
@@ -485,13 +495,13 @@ class Packager:
             # not further compressible.
             # not further compressible.
             self.addComponent(file, compressible = False)
             self.addComponent(file, compressible = False)
 
 
-        def addComponent(self, file, compressible = True, extract = False):
+        def addComponent(self, file, compressible = True, extract = None):
             ext = Filename(file.newName).getExtension()
             ext = Filename(file.newName).getExtension()
             if ext in self.packager.uncompressibleExtensions:
             if ext in self.packager.uncompressibleExtensions:
                 compressible = False
                 compressible = False
 
 
             extract = file.extract
             extract = file.extract
-            if ext in self.packager.extractExtensions:
+            if extract is None and ext in self.packager.extractExtensions:
                 extract = True
                 extract = True
 
 
             if ext in self.packager.platformSpecificExtensions:
             if ext in self.packager.platformSpecificExtensions:
@@ -599,7 +609,7 @@ class Packager:
         self.musicManager = None
         self.musicManager = None
 
 
         # This is filled in during readPackageDef().
         # This is filled in during readPackageDef().
-        self.packageList = None
+        self.packageList = []
 
 
         # A table of all known packages by name.
         # A table of all known packages by name.
         self.packages = {}
         self.packages = {}
@@ -636,54 +646,152 @@ class Packager:
 ##         # created there
 ##         # created there
 ##         os.chdir(self.persistDir.toOsSpecific())
 ##         os.chdir(self.persistDir.toOsSpecific())
 
 
+    def __expandVariable(self, line, p):
+        """ Given that line[p] is a dollar sign beginning a variable
+        reference, advances p to the first dollar sign following the
+        reference, and looks up the variable referenced.
+
+        Returns (value, p) where value is the value of the named
+        variable, and p is the first character following the variable
+        reference. """
+
+        p += 1
+        if p >= len(line):
+            return '', p
+        
+        var = ''
+        if line[p] == '{':
+            # Curly braces exactly delimit the variable name.
+            p += 1
+            while p < len(line) and line[p] != '}':
+                var += line[p]
+                p += 1
+        else:
+            # Otherwise, a string of alphanumeric characters,
+            # including underscore, delimits the variable name.
+            var += line[p]
+            p += 1
+            while p < len(line) and (line[p] in string.letters or line[p] in string.digits or line[p] == '_'):
+                var += line[p]
+                p += 1
+
+        return ExecutionEnvironment.getEnvironmentVariable(var), p
+        
+
+    def __splitLine(self, line):
+        """ Separates the indicated line into words at whitespace.
+        Quotation marks and escape characters protect spaces.  This is
+        designed to be similar to the algorithm employed by the Unix
+        shell. """
+
+        words = []
+
+        p = 0
+        while p < len(line):
+            if line[p] == '#':
+                # A word that begins with a hash mark indicates an
+                # inline comment, and the end of the parsing.
+                break
+                
+            # Scan to the end of the word.
+            word = ''
+            while p < len(line) and line[p] not in string.whitespace:
+                if line[p] == '\\':
+                    # Output an escaped character.
+                    p += 1
+                    if p < len(line):
+                        word += line[p]
+                        p += 1
+
+                elif line[p] == '$':
+                    # Output a variable reference.
+                    expand, p = self.__expandVariable(line, p)
+                    word += expand
+
+                elif line[p] == '"':
+                    # Output a double-quoted string.
+                    p += 1
+                    while p < len(line) and line[p] != '"':
+                        if line[p] == '\\':
+                            # Output an escaped character.
+                            p += 1
+                            if p < len(line):
+                                word += line[p]
+                                p += 1
+                        elif line[p] == '$':
+                            # Output a variable reference.
+                            expand, p = self.__expandVariable(line, p)
+                            word += expand
+                        else:
+                            word += line[p]
+                            p += 1
+
+                elif line[p] == "'":
+                    # Output a single-quoted string.  Escape
+                    # characters and dollar signs within single quotes
+                    # are not special.
+                    p += 1
+                    while p < len(line) and line[p] != "'":
+                        word += line[p]
+                        p += 1
+
+                else:
+                    # Output a single character.
+                    word += line[p]
+                    p += 1
+
+            words.append(word)
+
+            # Scan to the beginning of the next word.
+            while p < len(line) and line[p] in string.whitespace:
+                p += 1
+
+        return words
+
+    def __getNextLine(self, file):
+        """ Extracts the next line from the input file, and splits it
+        into words.  Returns the list of words, or None at end of
+        file. """
+
+        line = file.readline()
+        while line:
+            line = line.strip()
+            if not line:
+                # Skip the line, it was just a blank line
+                pass
+            
+            elif line[0] == '#':
+                # Eat python-style comments.
+                pass
+
+            else:
+                return self.__splitLine(line)
+
+            line = file.readline()
+
+        # End of file.
+        return None
+
     def readPackageDef(self, packageDef):
     def readPackageDef(self, packageDef):
         """ Reads the lines in the .pdef file named by packageDef and
         """ Reads the lines in the .pdef file named by packageDef and
         dispatches to the appropriate handler method for each
         dispatches to the appropriate handler method for each
         line.  Returns the list of package files."""
         line.  Returns the list of package files."""
 
 
-        assert self.packageList is None
         self.packageList = []
         self.packageList = []
 
 
         self.notify.info('Reading %s' % (packageDef))
         self.notify.info('Reading %s' % (packageDef))
         file = open(packageDef.toOsSpecific())
         file = open(packageDef.toOsSpecific())
-        lines = file.readlines()
-        file.close()
-
-        lineNum = [0]
-        def getNextLine(lineNum = lineNum):
-            """
-            Read in the next line of the packageDef
-            """
-            while lineNum[0] < len(lines):
-                line = lines[lineNum[0]].strip()
-                lineNum[0] += 1
-                if not line:
-                    # Skip the line, it was just a blank line
-                    pass
-                elif line[0] == '#':
-                    # Eat python-style comments.
-                    pass
-                else:
-                    # Remove any trailing comment.
-                    hash = line.find(' #')
-                    if hash != -1:
-                        line = line[:hash].strip()
-                    # Return the line as an array split at whitespace.
-                    return line.split()
-
-            # EOF.
-            return None
 
 
         # Now start parsing the packageDef lines
         # Now start parsing the packageDef lines
         try:
         try:
-            lineList = getNextLine()
-            while lineList:
-                command = lineList[0]
+            words = self.__getNextLine(file)
+            while words:
+                command = words[0]
                 try:
                 try:
                     methodName = 'parse_%s' % (command)
                     methodName = 'parse_%s' % (command)
                     method = getattr(self, methodName, None)
                     method = getattr(self, methodName, None)
                     if method:
                     if method:
-                        method(lineList)
+                        method(words)
 
 
                     else:
                     else:
                         message = 'Unknown command %s' % (command)
                         message = 'Unknown command %s' % (command)
@@ -695,7 +803,7 @@ class Packager:
                     message = '%s command encounted outside of package specification' %(command)
                     message = '%s command encounted outside of package specification' %(command)
                     raise OutsideOfPackageError, message
                     raise OutsideOfPackageError, message
 
 
-                lineList = getNextLine()
+                words = self.__getNextLine(file)
 
 
         except PackagerError:
         except PackagerError:
             # Append the line number and file name to the exception
             # Append the line number and file name to the exception
@@ -705,17 +813,17 @@ class Packager:
             raise
             raise
 
 
         packageList = self.packageList
         packageList = self.packageList
-        self.packageList = None
+        self.packageList = []
 
 
         return packageList
         return packageList
 
 
-    def parse_set(self, lineList):
+    def parse_set(self, words):
         """
         """
         set variable=value
         set variable=value
         """
         """
         
         
         try:
         try:
-            command, assign = lineList
+            command, assign = words
         except ValueError:
         except ValueError:
             raise ArgumentNumber
             raise ArgumentNumber
         
         
@@ -728,15 +836,15 @@ class Packager:
         value = ExecutionEnvironment.expandString(value.strip())
         value = ExecutionEnvironment.expandString(value.strip())
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
 
 
-    def parse_begin_package(self, lineList):
+    def parse_begin_package(self, words):
         """
         """
         begin_package packageName [version=v]
         begin_package packageName [version=v]
         """
         """
 
 
-        args = self.parseArgs(lineList, ['version'])
+        args = self.__parseArgs(words, ['version'])
 
 
         try:
         try:
-            command, packageName = lineList
+            command, packageName = words
         except ValueError:
         except ValueError:
             raise ArgumentNumber
             raise ArgumentNumber
 
 
@@ -744,127 +852,149 @@ class Packager:
 
 
         self.beginPackage(packageName, version = version, p3dApplication = False)
         self.beginPackage(packageName, version = version, p3dApplication = False)
 
 
-    def parse_end_package(self, lineList):
+    def parse_end_package(self, words):
         """
         """
         end_package packageName
         end_package packageName
         """
         """
 
 
         try:
         try:
-            command, packageName = lineList
+            command, packageName = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.endPackage(packageName, p3dApplication = False)
         self.endPackage(packageName, p3dApplication = False)
 
 
-    def parse_begin_p3d(self, lineList):
+    def parse_begin_p3d(self, words):
         """
         """
         begin_p3d appName
         begin_p3d appName
         """
         """
 
 
         try:
         try:
-            command, packageName = lineList
+            command, packageName = words
         except ValueError:
         except ValueError:
             raise ArgumentNumber
             raise ArgumentNumber
 
 
         self.beginPackage(packageName, p3dApplication = True)
         self.beginPackage(packageName, p3dApplication = True)
 
 
-    def parse_end_p3d(self, lineList):
+    def parse_end_p3d(self, words):
         """
         """
         end_p3d appName
         end_p3d appName
         """
         """
 
 
         try:
         try:
-            command, packageName = lineList
+            command, packageName = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.endPackage(packageName, p3dApplication = True)
         self.endPackage(packageName, p3dApplication = True)
 
 
-    def parse_require(self, lineList):
+    def parse_display_name(self, words):
+        """
+        display_name "name"
+        """
+
+        try:
+            command, displayName = words
+        except ValueError:
+            raise ArgumentError
+
+        if not self.currentPackage:
+            raise OutsideOfPackageError
+
+        self.currentPackage.displayName = displayName
+
+    def parse_require(self, words):
         """
         """
         require packageName [version=v]
         require packageName [version=v]
         """
         """
 
 
-        args = self.parseArgs(lineList, ['version'])
+        args = self.__parseArgs(words, ['version'])
 
 
         try:
         try:
-            command, packageName = lineList
+            command, packageName = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         version = args.get('version', None)
         version = args.get('version', None)
         self.require(packageName, version = version)
         self.require(packageName, version = version)
 
 
-    def parse_module(self, lineList):
+    def parse_module(self, words):
         """
         """
         module moduleName [newName]
         module moduleName [newName]
         """
         """
         newName = None
         newName = None
 
 
         try:
         try:
-            if len(lineList) == 2:
-                command, moduleName = lineList
+            if len(words) == 2:
+                command, moduleName = words
             else:
             else:
-                command, moduleName, newName = lineList
+                command, moduleName, newName = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.module(moduleName, newName = newName)
         self.module(moduleName, newName = newName)
 
 
-    def parse_main_module(self, lineList):
+    def parse_main_module(self, words):
         """
         """
         main_module moduleName
         main_module moduleName
         """
         """
 
 
         try:
         try:
-            command, moduleName = lineList
+            command, moduleName = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.mainModule(moduleName)
         self.mainModule(moduleName)
 
 
-    def parse_freeze_exe(self, lineList):
+    def parse_freeze_exe(self, words):
         """
         """
         freeze_exe path/to/basename
         freeze_exe path/to/basename
         """
         """
 
 
         try:
         try:
-            command, filename = lineList
+            command, filename = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.freeze(filename, compileToExe = True)
         self.freeze(filename, compileToExe = True)
 
 
-    def parse_freeze_dll(self, lineList):
+    def parse_freeze_dll(self, words):
         """
         """
         freeze_dll path/to/basename
         freeze_dll path/to/basename
         """
         """
 
 
         try:
         try:
-            command, filename = lineList
+            command, filename = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
         self.freeze(filename, compileToExe = False)
         self.freeze(filename, compileToExe = False)
 
 
-    def parse_file(self, lineList):
+    def parse_file(self, words):
         """
         """
-        file filename [newNameOrDir]
+        file filename [newNameOrDir] [extract=1]
         """
         """
 
 
+        args = self.__parseArgs(words, ['extract'])
+
         newNameOrDir = None
         newNameOrDir = None
 
 
         try:
         try:
-            if len(lineList) == 2:
-                command, filename = lineList
+            if len(words) == 2:
+                command, filename = words
             else:
             else:
-                command, filename, newNameOrDir = lineList
+                command, filename, newNameOrDir = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
-        self.file(filename, newNameOrDir = newNameOrDir)
+        extract = args.get('extract', None)
+        if extract is not None:
+            extract = int(extract)
+
+        self.file(Filename.fromOsSpecific(filename),
+                  newNameOrDir = newNameOrDir, extract = extract)
 
 
-    def parse_dir(self, lineList):
+    def parse_dir(self, words):
         """
         """
         dir dirname [newDir]
         dir dirname [newDir]
         """
         """
@@ -872,20 +1002,20 @@ class Packager:
         newDir = None
         newDir = None
 
 
         try:
         try:
-            if len(lineList) == 2:
-                command, dirname = lineList
+            if len(words) == 2:
+                command, dirname = words
             else:
             else:
-                command, dirname, newDir = lineList
+                command, dirname, newDir = words
         except ValueError:
         except ValueError:
             raise ArgumentError
             raise ArgumentError
 
 
-        self.dir(dirname, newDir = newDir)
+        self.dir(Filename.fromOsSpecific(dirname), newDir = newDir)
 
 
-    def parseArgs(self, lineList, argList):
+    def __parseArgs(self, words, argList):
         args = {}
         args = {}
         
         
-        while len(lineList) > 1:
-            arg = lineList[-1]
+        while len(words) > 1:
+            arg = words[-1]
             if '=' not in arg:
             if '=' not in arg:
                 return args
                 return args
 
 
@@ -901,7 +1031,7 @@ class Packager:
 
 
             args[parameter] = value
             args[parameter] = value
 
 
-            del lineList[-1]
+            del words[-1]
                 
                 
     
     
     def beginPackage(self, packageName, version = None, p3dApplication = False):
     def beginPackage(self, packageName, version = None, p3dApplication = False):
@@ -1026,11 +1156,11 @@ class Packager:
 
 
     def require(self, packageName, version = None):
     def require(self, packageName, version = None):
         """ Indicates a dependency on the named package, supplied as
         """ Indicates a dependency on the named package, supplied as
-        name.
+        a name.
 
 
         Attempts to install this package will implicitly install the
         Attempts to install this package will implicitly install the
         named package also.  Files already included in the named
         named package also.  Files already included in the named
-        package will be omitted from this one. """
+        package will be omitted from this one when building it. """
 
 
         # A special case for the Panda3D package.  We enforce that the
         # A special case for the Panda3D package.  We enforce that the
         # version number matches what we've been compiled with.
         # version number matches what we've been compiled with.
@@ -1136,14 +1266,13 @@ class Packager:
         package.mainModule = None
         package.mainModule = None
 
 
 
 
-    def file(self, filename, newNameOrDir = None):
+    def file(self, filename, newNameOrDir = None, extract = None):
         """ Adds the indicated arbitrary file to the current package.
         """ Adds the indicated arbitrary file to the current package.
 
 
         The file is placed in the named directory, or the toplevel
         The file is placed in the named directory, or the toplevel
         directory if no directory is specified.
         directory if no directory is specified.
 
 
-        The filename may include environment variable references and
-        shell globbing characters.
+        The filename may include shell globbing characters.
 
 
         Certain special behavior is invoked based on the filename
         Certain special behavior is invoked based on the filename
         extension.  For instance, .py files may be automatically
         extension.  For instance, .py files may be automatically
@@ -1166,10 +1295,10 @@ class Packager:
         if not self.currentPackage:
         if not self.currentPackage:
             raise OutsideOfPackageError
             raise OutsideOfPackageError
 
 
-        expanded = Filename.expandFrom(filename)
-        files = glob.glob(expanded.toOsSpecific())
+        filename = Filename(filename)
+        files = glob.glob(filename.toOsSpecific())
         if not files:
         if not files:
-            self.notify.warning("No such file: %s" % (expanded))
+            self.notify.warning("No such file: %s" % (filename))
             return
             return
 
 
         newName = None
         newName = None
@@ -1188,9 +1317,9 @@ class Packager:
             filename = Filename.fromOsSpecific(filename)
             filename = Filename.fromOsSpecific(filename)
             basename = filename.getBasename()
             basename = filename.getBasename()
             if newName:
             if newName:
-                self.addFile(filename, newName = newName)
+                self.addFile(filename, newName = newName, extract = extract)
             else:
             else:
-                self.addFile(filename, newName = prefix + basename)
+                self.addFile(filename, newName = prefix + basename, extract = extract)
 
 
     def dir(self, dirname, newDir = None):
     def dir(self, dirname, newDir = None):
 
 
@@ -1198,8 +1327,6 @@ class Packager:
         package.  The directory hierarchy is walked recursively, and
         package.  The directory hierarchy is walked recursively, and
         all files that match a known extension are added to the package.
         all files that match a known extension are added to the package.
 
 
-        The dirname may include environment variable references.
-
         newDir specifies the directory name within the package which
         newDir specifies the directory name within the package which
         the contents of the named directory should be installed to.
         the contents of the named directory should be installed to.
         If it is omitted, the contents of the named directory are
         If it is omitted, the contents of the named directory are
@@ -1209,7 +1336,7 @@ class Packager:
         if not self.currentPackage:
         if not self.currentPackage:
             raise OutsideOfPackageError
             raise OutsideOfPackageError
 
 
-        dirname = Filename.expandFrom(dirname)
+        dirname = Filename(dirname)
         if not newDir:
         if not newDir:
             newDir = ''
             newDir = ''
 
 
@@ -1242,7 +1369,7 @@ class Packager:
             if ext in self.knownExtensions:
             if ext in self.knownExtensions:
                 self.addFile(filename, newName = newName)
                 self.addFile(filename, newName = newName)
 
 
-    def addFile(self, filename, newName = None):
+    def addFile(self, filename, newName = None, extract = None):
         """ Adds the named file, giving it the indicated name within
         """ Adds the named file, giving it the indicated name within
         the package. """
         the package. """
 
 
@@ -1250,4 +1377,4 @@ class Packager:
             raise OutsideOfPackageError
             raise OutsideOfPackageError
 
 
         self.currentPackage.files.append(
         self.currentPackage.files.append(
-            self.PackFile(filename, newName = newName))
+            self.PackFile(filename, newName = newName, extract = extract))

+ 36 - 19
direct/src/plugin/make_contents.py → direct/src/showutil/make_contents.py

@@ -23,21 +23,37 @@ Options:
 import sys
 import sys
 import getopt
 import getopt
 import os
 import os
-
-import direct
-from pandac.PandaModules import *
-
-from FileSpec import FileSpec
+import md5
 
 
 class ArgumentError(AttributeError):
 class ArgumentError(AttributeError):
     pass
     pass
 
 
+class FileSpec:
+    """ Represents a single file in the directory, and its associated
+    timestamp, size, and md5 hash. """
+    
+    def __init__(self, filename, pathname):
+        self.filename = filename
+        self.pathname = pathname
+
+        s = os.stat(pathname)
+        self.size = s.st_size
+        self.timestamp = s.st_mtime
+
+        m = md5.new()
+        m.update(open(pathname, 'rb').read())
+        self.hash = m.hexdigest()
+
+    def getParams(self):
+        return 'filename="%s" size="%s" timestamp="%s" hash="%s"' % (
+            self.filename, self.size, self.timestamp, self.hash)
+
 class ContentsMaker:
 class ContentsMaker:
     def __init__(self):
     def __init__(self):
-        self.stageDir = None
+        self.installDir = None
 
 
     def build(self):
     def build(self):
-        if not self.stageDir:
+        if not self.installDir:
             raise ArgumentError, "Stage directory not specified."
             raise ArgumentError, "Stage directory not specified."
 
 
         self.packages = []
         self.packages = []
@@ -48,9 +64,9 @@ class ContentsMaker:
 
 
         # Now write the contents.xml file.
         # Now write the contents.xml file.
         contentsFileBasename = 'contents.xml'
         contentsFileBasename = 'contents.xml'
-        contentsFilePathname = Filename(self.stageDir, contentsFileBasename)
+        contentsFilePathname = os.path.join(self.installDir, contentsFileBasename)
 
 
-        f = open(contentsFilePathname.toOsSpecific(), 'w')
+        f = open(contentsFilePathname, 'w')
         print >> f, '<?xml version="1.0" ?>'
         print >> f, '<?xml version="1.0" ?>'
         print >> f, ''
         print >> f, ''
         print >> f, '<contents>'
         print >> f, '<contents>'
@@ -64,7 +80,7 @@ class ContentsMaker:
         """ Walks through all the files in the stage directory and
         """ Walks through all the files in the stage directory and
         looks for the package directory xml files. """
         looks for the package directory xml files. """
 
 
-        startDir = self.stageDir.toOsSpecific()
+        startDir = self.installDir
         if startDir.endswith(os.sep):
         if startDir.endswith(os.sep):
             startDir = startDir[:-1]
             startDir = startDir[:-1]
         prefix = startDir + os.sep
         prefix = startDir + os.sep
@@ -91,14 +107,14 @@ class ContentsMaker:
                 packageName, packageVersion, junk = localpath.split('/')
                 packageName, packageVersion, junk = localpath.split('/')
                 packagePlatform = None
                 packagePlatform = None
                 file = FileSpec(localpath + xml,
                 file = FileSpec(localpath + xml,
-                                Filename(self.stageDir, localpath + xml))
+                                os.path.join(self.installDir, localpath + xml))
                 print file.filename
                 print file.filename
                 self.packages.append((packageName, packagePlatform, packageVersion, file))
                 self.packages.append((packageName, packagePlatform, packageVersion, file))
 
 
             if localpath.count('/') == 3:
             if localpath.count('/') == 3:
                 packageName, packagePlatform, packageVersion, junk = localpath.split('/')
                 packageName, packagePlatform, packageVersion, junk = localpath.split('/')
                 file = FileSpec(localpath + xml,
                 file = FileSpec(localpath + xml,
-                                Filename(self.stageDir, localpath + xml))
+                                os.path.join(self.installDir, localpath + xml))
                 print file.filename
                 print file.filename
                 self.packages.append((packageName, packagePlatform, packageVersion, file))
                 self.packages.append((packageName, packagePlatform, packageVersion, file))
         
         
@@ -107,10 +123,10 @@ def makeContents(args):
     opts, args = getopt.getopt(args, 'd:h')
     opts, args = getopt.getopt(args, 'd:h')
 
 
     cm = ContentsMaker()
     cm = ContentsMaker()
-    cm.stageDir = Filename('.')
+    cm.installDir = '.'
     for option, value in opts:
     for option, value in opts:
         if option == '-d':
         if option == '-d':
-            cm.stageDir = Filename.fromOsSpecific(value)
+            cm.installDir = value
             
             
         elif option == '-h':
         elif option == '-h':
             print __doc__
             print __doc__
@@ -119,8 +135,9 @@ def makeContents(args):
     cm.build()
     cm.build()
         
         
 
 
-try:
-    makeContents(sys.argv[1:])
-except ArgumentError, e:
-    print e.args[0]
-    sys.exit(1)
+if __name__ == '__main__':
+    try:
+        makeContents(sys.argv[1:])
+    except ArgumentError, e:
+        print e.args[0]
+        sys.exit(1)

+ 61 - 295
direct/src/showutil/packp3d.py

@@ -2,11 +2,14 @@
 
 
 """
 """
 
 
-This module will pack a Panda application, consisting of a directory
+This command will pack a Panda application, consisting of a directory
 tree of .py files and models, into a p3d file for convenient
 tree of .py files and models, into a p3d file for convenient
 distribution.  The resulting p3d file can be run by the Panda3D
 distribution.  The resulting p3d file can be run by the Panda3D
 runtime executable, or by the Panda3D web browser plugin.
 runtime executable, or by the Panda3D web browser plugin.
 
 
+Also see ppackage.py, which can be used to build p3d files more
+generally, using a pdef description file.
+
 Usage:
 Usage:
 
 
   packp3d.py [opts] app.p3d
   packp3d.py [opts] app.p3d
@@ -14,13 +17,11 @@ Usage:
 Options:
 Options:
 
 
   -r application_root
   -r application_root
-
      Specify the root directory of the application source; this is a
      Specify the root directory of the application source; this is a
      directory tree that contains all of your .py files and models.
      directory tree that contains all of your .py files and models.
      If this is omitted, the default is the current directory.
      If this is omitted, the default is the current directory.
 
 
   -m main.py
   -m main.py
-  
      Names the Python file that begins the application.  This should
      Names the Python file that begins the application.  This should
      be a file within the root directory. If this is omitted, the
      be a file within the root directory. If this is omitted, the
      default is a file named "main.py", or if there is only one Python
      default is a file named "main.py", or if there is only one Python
@@ -29,313 +30,48 @@ Options:
      (this is preferable to having the module start itself immediately
      (this is preferable to having the module start itself immediately
      upon importing).
      upon importing).
 
 
-  -c [py,pyc,pyo]
+  -s search_dir
+     Additional directories to search for previously-built packages.
+     This option may be repeated as necessary.
 
 
-     Specifies the compilation mode of python files.  'py' means to
-     leave them as source files, 'pyc' and 'pyo' are equivalent, and
-     mean to compile to byte code.  pyc files will be written if the
-     interpreter is running in normal debug mode, while pyo files will
-     be written if it is running in optimize mode (-O or -OO).
+  -x
+     If this is specified, a version-independent application is built.
+     This stores source py files instead of compiled pyc files, and
+     egg files instead of bam files.  This application can then be run
+     with any version of Panda (provided you are careful not to make
+     any Python or Panda calls that are version-specific).  This is
+     not recommended except for very small, simple applications.
 
 
 """
 """
 
 
 import sys
 import sys
+import os
 import getopt
 import getopt
-import imp
-import marshal
+import glob
 import direct
 import direct
-from direct.stdpy.file import open
-from direct.showbase import Loader
+from direct.showutil import Packager 
 from pandac.PandaModules import *
 from pandac.PandaModules import *
 
 
-vfs = VirtualFileSystem.getGlobalPtr()
-
-class ArgumentError(AttributeError):
+class ArgumentError(StandardError):
     pass
     pass
 
 
-class AppPacker:
-
-    compression_level = 6
-    imported_maps = 'imported_maps'
-
-    # Text files that are copied (and compressed) to the multifile
-    # without processing.
-    text_extensions = [ 'prc' ]
-
-    # Binary files that are copied and compressed without processing.
-    binary_extensions = [ 'ttf', 'wav', 'mid' ]
-
-    # Binary files that are considered uncompressible, and are copied
-    # without compression.
-    uncompressible_extensions = [ 'mp3' ]
-
-    # Specifies how or if python files are compiled.
-    compilation_mode = 'pyc'
-
-    def __init__(self, multifile_name):
-        # Make sure any pre-existing file is removed.
-        Filename(multifile_name).unlink()
-            
-        self.multifile = Multifile()
-        if not self.multifile.openReadWrite(multifile_name):
-            raise ArgumentError, 'Could not open %s for writing' % (multifile_name)
-        self.imported_textures = {}
-
-        # Get the list of filename extensions that are recognized as
-        # image files.
-        self.image_extensions = []
-        for type in PNMFileTypeRegistry.getGlobalPtr().getTypes():
-            self.image_extensions += type.getExtensions()
-
-        self.loader = Loader.Loader(self)
-        
-    def scan(self, root, main):
-        if self.compilation_mode != 'py':
-            if __debug__:
-                self.compilation_mode = 'pyc'
-            else:
-                self.compilation_mode = 'pyo'
-
-        self.root = Filename(root)
-        self.root.makeAbsolute(vfs.getCwd())
-
-        # Check if there is just one .py file.
-        pyFiles = self.findPyFiles(self.root)
-        if main == None:
-            if len(pyFiles) == 1:
-                main = pyFiles[0]
-            else:
-                main = 'main.py'
-        if main not in pyFiles:
-            raise StandardError, 'No file %s in root directory.' % (main)
-        self.main = Filename(self.root, main)
-        
-        self._recurse(self.root)
-
-        self.multifile.repack()
-
-    def findPyFiles(self, dirname):
-        """ Returns a list of Python filenames at the root directory
-        level. """
-        
-        dirList = vfs.scanDirectory(dirname)
-        pyFiles = []
-        for file in dirList:
-            if file.getFilename().getExtension() == 'py':
-                pyFiles.append(file.getFilename().getBasename())
-
-        return pyFiles
-
-    def _recurse(self, filename):
-        dirList = vfs.scanDirectory(filename)
-        if dirList:
-            # It's a directory name.  Recurse.
-            for subfile in dirList:
-                self._recurse(subfile.getFilename())
-            return
-
-        # It's a real file.  Is it something we care about?
-        ext = filename.getExtension().lower()
-        outFilename = filename
-        if ext == 'pz':
-            # Strip off an implicit .pz extension.
-            outFilename = Filename(filename)
-            outFilename.setExtension('')
-            outFilename = Filename(outFilename.cStr())
-            ext = outFilename.getExtension().lower()
-            
-        if ext == 'py':
-            self.addPyFile(filename)
-        elif ext == 'egg':
-            self.addEggFile(filename, outFilename)
-        elif ext == 'bam':
-            self.addBamFile(filename, outFilename)
-        elif ext in self.image_extensions:
-            self.addTexture(filename)
-        elif ext in self.text_extensions:
-            self.addTextFile(filename)
-        elif ext in self.binary_extensions:
-            self.addBinaryFile(filename)
-        elif ext in self.uncompressible_extensions:
-            self.addUncompressibleFile(filename)
-
-    def addPyFile(self, filename):
-        targetFilename = self.makeRelFilename(filename)
-
-        if filename == self.main:
-            # This one is the "main.py"; the starter file.
-            targetFilename = Filename('main.py')
-
-        if self.compilation_mode == 'py':
-            # Add python files as source files.
-            self.multifile.addSubfile(targetFilename.cStr(), filename, self.compression_level)
-        elif self.compilation_mode == 'pyc' or self.compilation_mode == 'pyo':
-            # Compile it to bytecode.
-            targetFilename.setExtension(self.compilation_mode)
-            source = open(filename, 'r').read()
-            if source and source[-1] != '\n':
-                source = source + '\n'
-            code = compile(source, targetFilename.cStr(), 'exec')
-            data = imp.get_magic() + '\0\0\0\0' + marshal.dumps(code)
-
-            stream = StringStream(data)
-            self.multifile.addSubfile(targetFilename.cStr(), stream, self.compression_level)
-            self.multifile.flush()
-        else:
-            raise StandardError, 'Unsupported compilation mode %s' % (self.compilation_mode)
-            
-    def addEggFile(self, filename, outFilename):
-        # Precompile egg files to bam's.
-        np = self.loader.loadModel(filename, okMissing = True)
-        if not np:
-            raise StandardError, 'Could not read egg file %s' % (filename)
-
-        self.addNode(np.node(), outFilename)
-
-    def addBamFile(self, filename, outFilename):
-        # Load the bam file so we can massage its textures.
-        bamFile = BamFile()
-        if not bamFile.openRead(filename):
-            raise StandardError, 'Could not read bam file %s' % (filename)
-
-        if not bamFile.resolve():
-            raise StandardError, 'Could not resolve bam file %s' % (filename)
-
-        node = bamFile.readNode()
-        if not node:
-            raise StandardError, 'Not a model file: %s' % (filename)
-            
-        self.addNode(node, outFilename)
-
-    def addNode(self, node, filename):
-        """ Converts the indicated node to a bam stream, and adds the
-        bam file to the multifile under the indicated filename. """
-        
-        # Be sure to import all of the referenced textures, and tell
-        # them their new location within the multifile.
-        
-        for tex in NodePath(node).findAllTextures():
-            if not tex.hasFullpath() and tex.hasRamImage():
-                # We need to store this texture as a raw-data image.
-                # Clear the filename so this will happen
-                # automatically.
-                tex.clearFilename()
-                tex.clearAlphaFilename()
-
-            else:
-                # We can store this texture as a file reference to its
-                # image.  Copy the file into our multifile, and rename
-                # its reference in the texture.
-                if tex.hasFilename():
-                    tex.setFilename(self.addTexture(tex.getFullpath()))
-                if tex.hasAlphaFilename():
-                    tex.setAlphaFilename(self.addTexture(tex.getAlphaFullpath()))
-                
-        # Now generate an in-memory bam file.  Tell the bam writer to
-        # keep the textures referenced by their in-multifile path.
-        bamFile = BamFile()
-        stream = StringStream()
-        bamFile.openWrite(stream)
-        bamFile.getWriter().setFileTextureMode(bamFile.BTMUnchanged)
-        bamFile.writeObject(node)
-        bamFile.close()
-
-        # Clean the node out of memory.
-        node.removeAllChildren()
-
-        # Now we have an in-memory bam file.
-        rel = self.makeRelFilename(filename)
-        rel.setExtension('bam')
-        stream.seekg(0)
-        self.multifile.addSubfile(rel.cStr(), stream, self.compression_level)
-
-        # Flush it so the data gets written to disk immediately, so we
-        # don't have to keep it around in ram.
-        self.multifile.flush()
-    
-    def addTexture(self, filename):
-        """ Adds the texture to the multifile, if it has not already
-        been added.  If it is not within the root directory, copies it
-        in (virtually) into a directory within the multifile named
-        imported_maps.  Returns the new filename within the
-        multifile. """
-
-        assert not filename.isLocal()
-
-        filename = Filename(filename)
-        filename.makeAbsolute(vfs.getCwd())
-        filename.makeTrueCase()
-
-        rel = self.imported_textures.get(filename.cStr())
-        if not rel:
-            rel = Filename(filename)
-            if not rel.makeRelativeTo(self.root, False):
-                # Not within the multifile.
-                rel = Filename(self.imported_maps, filename.getBasename())
-
-            # Need to add it now.
-            self.imported_textures[filename.cStr()] = rel
-            filename = Filename(filename)
-            filename.setBinary()
-            self.multifile.addSubfile(rel.cStr(), filename, 0)
-
-        return rel
-
-    def mapTextureFilename(self, filename):
-        """ Returns the filename within the multifile of the
-        already-added texture. """
-
-        filename = Filename(filename)
-        filename.makeAbsolute(vfs.getCwd())
-        filename.makeTrueCase()
-
-        return self.imported_textures[filename.cStr()]
-
-    def addTextFile(self, filename):
-        """ Adds a generic text file to the multifile. """
-
-        rel = self.makeRelFilename(filename)
-        filename = Filename(filename)
-        filename.setText()
-        self.multifile.addSubfile(rel.cStr(), filename, self.compression_level)
-
-    def addBinaryFile(self, filename):
-        """ Adds a generic binary file to the multifile. """
-
-        rel = self.makeRelFilename(filename)
-        filename = Filename(filename)
-        filename.setBinary()
-        self.multifile.addSubfile(rel.cStr(), filename, self.compression_level)
-
-    def addUncompressibleFile(self, filename):
-        """ Adds a generic binary file to the multifile, without compression. """
-
-        rel = self.makeRelFilename(filename)
-        filename = Filename(filename)
-        filename.setBinary()
-        self.multifile.addSubfile(rel.cStr(), filename, 0)
-
-    def makeRelFilename(self, filename):
-        """ Returns the same filename, relative to self.root """
-        rel = Filename(filename)
-        result = rel.makeRelativeTo(self.root, False)
-        assert result
-        return rel
-
-
 def makePackedApp(args):
 def makePackedApp(args):
-    opts, args = getopt.getopt(args, 'r:m:c:h')
+    opts, args = getopt.getopt(args, 'r:m:s:xh')
+
+    packager = Packager.Packager()
 
 
     root = '.'
     root = '.'
     main = None
     main = None
-    compilation_mode = AppPacker.compilation_mode
+    versionIndependent = False
     for option, value in opts:
     for option, value in opts:
         if option == '-r':
         if option == '-r':
-            root = value
+            root = Filename.fromOsSpecific(value)
         elif option == '-m':
         elif option == '-m':
             main = value
             main = value
-        elif option == '-c':
-            compilation_mode = value
+        elif option == '-s':
+            packager.installSearch.append(Filename.fromOsSpecific(value))
+        elif option == '-x':
+            versionIndependent = True
         elif option == '-h':
         elif option == '-h':
             print __doc__
             print __doc__
             sys.exit(1)
             sys.exit(1)
@@ -343,13 +79,43 @@ def makePackedApp(args):
     if not args:
     if not args:
         raise ArgumentError, "No destination app specified.  Use:\npackp3d.py app.p3d"
         raise ArgumentError, "No destination app specified.  Use:\npackp3d.py app.p3d"
 
 
-    multifile_name = args[0]
     if len(args) > 1:
     if len(args) > 1:
         raise ArgumentError, "Too many arguments."
         raise ArgumentError, "Too many arguments."
 
 
-    p = AppPacker(multifile_name)
-    p.compilation_mode = compilation_mode
-    p.scan(root = root, main = main)
+    appFilename = Filename.fromOsSpecific(args[0])
+    if appFilename.getExtension() != 'p3d':
+        raise ArgumentError, 'Application filename must end in ".p3d".'
+
+    appDir = Filename(appFilename.getDirname())
+    appBase = appFilename.getBasenameWoExtension()
+
+    if not main:
+        main = Filename(root, 'main.py')
+        if main.exists():
+            main = 'main.py'
+        else:
+            main = glob.glob(os.path.join(root.toOsSpecific(), '*.py'))
+            if len(main) == 0:
+                raise ArgumentError, 'No Python files in root directory.'
+            elif len(main) > 1:
+                raise ArgumentError, 'Multiple Python files in root directory; specify the main application with -m "main".'
+            main = os.path.split(main[0])[1]
+
+    main = Filename.fromOsSpecific(main)
+    main.setExtension('')
+
+    mainModule = main.cStr().replace('/', '.')
+    
+    packager.installDir = appDir
+
+    packager.setup()
+    packager.beginPackage(appBase, p3dApplication = True)
+
+    packager.require('panda3d')
+    packager.dir(root)
+    packager.mainModule(mainModule)
+        
+    packager.endPackage(appBase, p3dApplication = True)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     try:
     try:

+ 15 - 2
direct/src/showutil/ppackage.py

@@ -34,6 +34,10 @@ Options:
      copy this directory structure to a web host where it may be
      copy this directory structure to a web host where it may be
      downloaded by the client.
      downloaded by the client.
 
 
+  -s search_dir
+     Additional directories to search for previously-built packages.
+     This option may be repeated as necessary.
+
   -d persist_dir
   -d persist_dir
      The full path to a local directory that retains persistant state
      The full path to a local directory that retains persistant state
      between publishes.  This directory structure keeps files that are
      between publishes.  This directory structure keeps files that are
@@ -60,6 +64,7 @@ import getopt
 import os
 import os
 
 
 from direct.showutil import Packager
 from direct.showutil import Packager
+from direct.showutil import make_contents
 from pandac.PandaModules import *
 from pandac.PandaModules import *
 
 
 def usage(code, msg = ''):
 def usage(code, msg = ''):
@@ -70,13 +75,15 @@ def usage(code, msg = ''):
 packager = Packager.Packager()
 packager = Packager.Packager()
 
 
 try:
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 'i:d:p:Hh')
+    opts, args = getopt.getopt(sys.argv[1:], 'i:s:d:p:Hh')
 except getopt.error, msg:
 except getopt.error, msg:
     usage(1, msg)
     usage(1, msg)
 
 
 for opt, arg in opts:
 for opt, arg in opts:
     if opt == '-i':
     if opt == '-i':
         packager.installDir = Filename.fromOsSpecific(arg)
         packager.installDir = Filename.fromOsSpecific(arg)
+    elif opt == '-s':
+        packager.installSearch.append(Filename.fromOsSpecific(arg))
     elif opt == '-d':
     elif opt == '-d':
         packager.persistDir = Filename.fromOsSpecific(arg)
         packager.persistDir = Filename.fromOsSpecific(arg)
     elif opt == '-p':
     elif opt == '-p':
@@ -101,7 +108,13 @@ packageDef = Filename.fromOsSpecific(args[0])
 
 
 if not packager.installDir:
 if not packager.installDir:
     packager.installDir = Filename('install')
     packager.installDir = Filename('install')
-packager.installSearch = [packager.installDir]
+packager.installSearch = [packager.installDir] + packager.installSearch
 
 
 packager.setup()
 packager.setup()
 packager.readPackageDef(packageDef)
 packager.readPackageDef(packageDef)
+
+# Update the contents.xml at the root of the install directory.
+cm = make_contents.ContentsMaker()
+cm.installDir = packager.installDir.toOsSpecific()
+cm.build()
+