Browse Source

wip: host support

David Rose 16 years ago
parent
commit
ae8c460b1b

+ 2 - 0
direct/src/plugin/Sources.pp

@@ -32,6 +32,7 @@
     p3dFileDownload.h p3dFileDownload.I \
     p3dFileDownload.h p3dFileDownload.I \
     p3dFileParams.h p3dFileParams.I \
     p3dFileParams.h p3dFileParams.I \
     p3dFloatObject.h \
     p3dFloatObject.h \
+    p3dHost.h p3dHost.I \
     p3dInstance.h p3dInstance.I \
     p3dInstance.h p3dInstance.I \
     p3dInstanceManager.h p3dInstanceManager.I \
     p3dInstanceManager.h p3dInstanceManager.I \
     p3dIntObject.h \
     p3dIntObject.h \
@@ -62,6 +63,7 @@
     p3dFileDownload.cxx \
     p3dFileDownload.cxx \
     p3dFileParams.cxx \
     p3dFileParams.cxx \
     p3dFloatObject.cxx \
     p3dFloatObject.cxx \
+    p3dHost.cxx \
     p3dInstance.cxx \
     p3dInstance.cxx \
     p3dInstanceManager.cxx \
     p3dInstanceManager.cxx \
     p3dIntObject.cxx \
     p3dIntObject.cxx \

+ 11 - 0
direct/src/plugin/fileSpec.I

@@ -24,6 +24,17 @@ get_filename() const {
   return _filename;
   return _filename;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::set_filename
+//       Access: Private
+//  Description: Changes the relative path to this file on disk,
+//               within the package root directory.
+////////////////////////////////////////////////////////////////////
+inline void FileSpec::
+set_filename(const string &filename) {
+  _filename = filename;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::get_pathname
 //     Function: FileSpec::get_pathname
 //       Access: Private
 //       Access: Private

+ 1 - 0
direct/src/plugin/fileSpec.h

@@ -32,6 +32,7 @@ public:
   void load_xml(TiXmlElement *element);
   void load_xml(TiXmlElement *element);
 
 
   inline const string &get_filename() const;
   inline const string &get_filename() const;
+  inline void set_filename(const string &filename);
   inline string get_pathname(const string &package_dir) const;
   inline string get_pathname(const string &package_dir) const;
   inline size_t get_size() const;
   inline size_t get_size() const;
   
   

+ 73 - 0
direct/src/plugin/p3dHost.I

@@ -0,0 +1,73 @@
+// Filename: p3dHost.I
+// Created by:  drose (21Aug09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_host_dir
+//       Access: Public
+//  Description: Returns the local directory into which files
+//               downloaded from this host will be installed.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DHost::
+get_host_dir() const {
+  return _host_dir;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_host_url
+//       Access: Public
+//  Description: Returns the root URL of this particular host, as
+//               passed from the package file.  This is a unique
+//               string that identifies each host.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DHost::
+get_host_url() const {
+  return _host_url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_host_url_prefix
+//       Access: Public
+//  Description: Returns the root URL of this host, for constructing
+//               full URL sequences.  This is the same as
+//               get_host_url(), except it is guaranteed to end in a
+//               slash character.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DHost::
+get_host_url_prefix() const {
+  return _host_url_prefix;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_descriptive_name
+//       Access: Public
+//  Description: Returns the descriptive name provided for this host,
+//               if any.  This will be available after
+//               read_contents_file() has been called.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DHost::
+get_descriptive_name() const {
+  return _descriptive_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::has_contents_file
+//       Access: Public
+//  Description: Returns true if a contents.xml file has been
+//               successfully read for this host, false otherwise.
+////////////////////////////////////////////////////////////////////
+inline bool P3DHost::
+has_contents_file() const {
+  return (_xcontents != NULL);
+}

+ 265 - 0
direct/src/plugin/p3dHost.cxx

@@ -0,0 +1,265 @@
+// Filename: p3dHost.cxx
+// Created by:  drose (21Aug09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "p3dHost.h"
+#include "p3dInstanceManager.h"
+#include "p3dPackage.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::Constructor
+//       Access: Private
+//  Description: Use P3DInstanceManager::get_host() to construct a new
+//               P3DHost.
+////////////////////////////////////////////////////////////////////
+P3DHost::
+P3DHost(P3DInstanceManager *inst_mgr, const string &host_url) :
+  _host_url(host_url) 
+{
+  _host_dir = inst_mgr->get_root_dir();
+  _host_dir += "/host";  // TODO.
+
+  // Ensure that the download URL ends with a slash.
+  _host_url_prefix = _host_url;
+  if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.size() - 1] != '/') {
+    _host_url_prefix += "/";
+  }
+
+  _xcontents = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::Destructor
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DHost::
+~P3DHost() {
+  if (_xcontents != NULL) {
+    delete _xcontents;
+  }
+
+  Packages::iterator pi;
+  for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
+    delete (*pi).second;
+  }
+  _packages.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::read_contents_file
+//       Access: Public
+//  Description: Reads the contents.xml file in the indicated
+//               filename.  On success, copies the contents.xml file
+//               into the standard location (if it's not there
+//               already).
+//
+//               Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DHost::
+read_contents_file(const string &contents_filename) {
+  TiXmlDocument doc(contents_filename.c_str());
+  if (!doc.LoadFile()) {
+    return false;
+  }
+
+  TiXmlElement *xcontents = doc.FirstChildElement("contents");
+  if (xcontents == NULL) {
+    return false;
+  }
+
+  if (_xcontents != NULL) {
+    delete _xcontents;
+  }
+  _xcontents = (TiXmlElement *)xcontents->Clone();
+
+  string standard_filename = _host_dir + "/contents.xml";
+  if (standardize_filename(standard_filename) != 
+      standardize_filename(contents_filename)) {
+    copy_file(contents_filename, standard_filename);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_package
+//       Access: Public
+//  Description: Returns a (possibly shared) pointer to the indicated
+//               package.
+////////////////////////////////////////////////////////////////////
+P3DPackage *P3DHost::
+get_package(const string &package_name, const string &package_version) {
+  string key = package_name + "_" + package_version;
+  Packages::iterator pi = _packages.find(key);
+  if (pi != _packages.end()) {
+    return (*pi).second;
+  }
+
+  P3DPackage *package = 
+    new P3DPackage(this, package_name, package_version);
+  bool inserted = _packages.insert(Packages::value_type(key, package)).second;
+  assert(inserted);
+
+  return package;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::get_package_desc_file
+//       Access: Public
+//  Description: Fills the indicated FileSpec with the hash
+//               information for the package's desc file, and also
+//               determines the package's platform.  Returns true if
+//               successful, false if the package is unknown.  This
+//               requires has_contents_file() to return true in order
+//               to be successful.
+////////////////////////////////////////////////////////////////////
+bool P3DHost::
+get_package_desc_file(FileSpec &desc_file,              // out
+                      string &package_platform,         // out
+                      const string &package_name,       // in
+                      const string &package_version) {  // in
+  if (_xcontents == NULL) {
+    return false;
+  }
+
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+
+  // Scan the contents data for the indicated package.  First, we look
+  // for a platform-specific version.
+  TiXmlElement *xpackage = _xcontents->FirstChildElement("package");
+  while (xpackage != NULL) {
+    const char *name = xpackage->Attribute("name");
+    const char *platform = xpackage->Attribute("platform");
+    const char *version = xpackage->Attribute("version");
+    if (name != NULL && platform != NULL && version != NULL &&
+        package_name == name && 
+        inst_mgr->get_platform() == platform &&
+        package_version == version) {
+      // Here's the matching package definition.
+      desc_file.load_xml(xpackage);
+      package_platform = platform;
+      return true;
+    }
+
+    xpackage = xpackage->NextSiblingElement("package");
+  }
+
+  // Look again, this time looking for a non-platform-specific version.
+  xpackage = _xcontents->FirstChildElement("package");
+  while (xpackage != NULL) {
+    const char *name = xpackage->Attribute("name");
+    const char *platform = xpackage->Attribute("platform");
+    const char *version = xpackage->Attribute("version");
+    if (platform == NULL) {
+      platform = "";
+    }
+    if (name != NULL && version != NULL &&
+        package_name == name && 
+        *platform == '\0' &&
+        package_version == version) {
+      // Here's the matching package definition.
+      desc_file.load_xml(xpackage);
+      package_platform = platform;
+      return true;
+    }
+
+    xpackage = xpackage->NextSiblingElement("package");
+  }
+
+  // Couldn't find the named package.
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::standardize_filename
+//       Access: Private, Static
+//  Description: Attempts to change the filename into some standard
+//               form for comparison with other filenames.  On a
+//               case-insensitive filesystem, this converts the
+//               filename to lowercase.  On Windows, it further
+//               replaces forward slashes with backslashes.
+////////////////////////////////////////////////////////////////////
+string P3DHost::
+standardize_filename(const string &filename) {
+#if defined(_WIN32) || defined(__APPLE__)
+  string new_filename;
+  for (string::const_iterator si = filename.begin();
+       si != filename.end();
+       ++si) {
+    char ch = *si;
+#ifdef _WIN32
+    if (ch == '/') {
+      ch = '\\';
+    }
+#endif  // _WIN32
+    new_filename += tolower(ch);
+  }
+  return new_filename;
+#else  // _WIN32 || __APPLE__
+  return filename;
+#endif  // _WIN32 || __APPLE__
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::copy_file
+//       Access: Private, Static
+//  Description: Copies the data in the file named by from_filename
+//               into the file named by to_filename.
+////////////////////////////////////////////////////////////////////
+bool P3DHost::
+copy_file(const string &from_filename, const string &to_filename) {
+  ifstream in(from_filename.c_str(), ios::in | ios::binary);
+
+  // Copy to a temporary file first, in case (a) the filenames
+  // actually refer to the same file, or (b) in case we have different
+  // processes writing to the same file, and (c) to prevent
+  // partially overwriting the file should something go wrong.
+  ostringstream strm;
+  strm << to_filename << ".t";
+#ifdef _WIN32
+  strm << GetCurrentProcessId() << "_" << GetCurrentThreadId();
+#else
+  strm << getpid();
+#endif
+  string temp_filename = strm.str();
+  ofstream out(temp_filename.c_str(), ios::out | ios::binary);
+        
+  static const size_t buffer_size = 4096;
+  char buffer[buffer_size];
+  
+  in.read(buffer, buffer_size);
+  size_t count = in.gcount();
+  while (count != 0) {
+    out.write(buffer, count);
+    if (out.fail()) {
+      unlink(temp_filename.c_str());
+      return false;
+    }
+    in.read(buffer, buffer_size);
+    count = in.gcount();
+  }
+  out.close();
+
+  if (!in.eof()) {
+    unlink(temp_filename.c_str());
+    return false;
+  }
+
+  if (rename(temp_filename.c_str(), to_filename.c_str()) == 0) {
+    return true;
+  }
+
+  unlink(temp_filename.c_str());
+  return false;
+}

+ 71 - 0
direct/src/plugin/p3dHost.h

@@ -0,0 +1,71 @@
+// Filename: p3dHost.h
+// Created by:  drose (21Aug09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DHOST_H
+#define P3DHOST_H
+
+#include "p3d_plugin_common.h"
+
+#include <map>
+
+class FileSpec;
+class P3DInstanceManager;
+class P3DPackage;
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DHost
+// Description : Represents a particular download host serving up
+//               Panda3D packages.
+////////////////////////////////////////////////////////////////////
+class P3DHost {
+private:
+  P3DHost(P3DInstanceManager *inst_mgr, const string &host_url);
+  ~P3DHost();
+
+public:
+  inline const string &get_host_dir() const;
+  inline const string &get_host_url() const;
+  inline const string &get_host_url_prefix() const;
+  inline const string &get_descriptive_name() const;
+
+  inline bool has_contents_file() const;
+  bool read_contents_file(const string &contents_filename);
+
+  P3DPackage *get_package(const string &package_name, 
+                          const string &package_version);
+  bool get_package_desc_file(FileSpec &desc_file, 
+                             string &package_platform,
+                             const string &package_name,
+                             const string &package_version);
+
+private:
+  static string standardize_filename(const string &filename);
+  static bool copy_file(const string &from_filename, const string &to_filename);
+
+private:
+  string _host_dir;
+  string _host_url;
+  string _host_url_prefix;
+  string _descriptive_name;
+  TiXmlElement *_xcontents;
+
+  typedef map<string, P3DPackage *> Packages;
+  Packages _packages;
+
+  friend class P3DInstanceManager;
+};
+
+#include "p3dHost.I"
+
+#endif

+ 8 - 5
direct/src/plugin/p3dInstance.cxx

@@ -884,12 +884,14 @@ scan_app_desc_file(TiXmlDocument *doc) {
   TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
   TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
   while (xrequires != NULL) {
   while (xrequires != NULL) {
     const char *name = xrequires->Attribute("name");
     const char *name = xrequires->Attribute("name");
-    if (name != NULL) {
+    const char *host_url = xrequires->Attribute("host");
+    if (name != NULL && host_url != NULL) {
       const char *version = xrequires->Attribute("version");
       const char *version = xrequires->Attribute("version");
       if (version == NULL) {
       if (version == NULL) {
         version = "";
         version = "";
       }
       }
-      P3DPackage *package = inst_mgr->get_package(name, version);
+      P3DHost *host = inst_mgr->get_host(host_url);
+      P3DPackage *package = host->get_package(name, version);
       add_package(package);
       add_package(package);
     }
     }
 
 
@@ -1177,9 +1179,10 @@ make_splash_window() {
   if (!_fparams.has_token("splash_img")) {
   if (!_fparams.has_token("splash_img")) {
     // No specific splash image is specified; get the default splash
     // No specific splash image is specified; get the default splash
     // image.
     // image.
-    P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-    splash_image_url = inst_mgr->get_download_url();
-    splash_image_url += "coreapi/splash.jpg";
+    if (_panda3d != NULL) {
+      splash_image_url = _panda3d->get_host()->get_host_url_prefix();
+      splash_image_url += "coreapi/splash.jpg";
+    }
   }
   }
 
 
   if (splash_image_url.empty()) {
   if (splash_image_url.empty()) {

+ 0 - 23
direct/src/plugin/p3dInstanceManager.I

@@ -36,18 +36,6 @@ get_root_dir() const {
   return _root_dir;
   return _root_dir;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::get_download_url
-//       Access: Public
-//  Description: Returns the URL of the download server.  All
-//               downloadable files will be retrieved from various
-//               subdirectories of this URL root.
-////////////////////////////////////////////////////////////////////
-inline const string &P3DInstanceManager::
-get_download_url() const {
-  return _download_url;
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_platform
 //     Function: P3DInstanceManager::get_platform
 //       Access: Public
 //       Access: Public
@@ -74,17 +62,6 @@ get_log_directory() const {
   return _log_directory;
   return _log_directory;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::has_contents_file
-//       Access: Public
-//  Description: Returns true if a contents.xml file has been
-//               successfully read, false otherwise.
-////////////////////////////////////////////////////////////////////
-inline bool P3DInstanceManager::
-has_contents_file() const {
-  return (_xcontents != NULL);
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_num_instances
 //     Function: P3DInstanceManager::get_num_instances
 //       Access: Public
 //       Access: Public

+ 31 - 203
direct/src/plugin/p3dInstanceManager.cxx

@@ -15,7 +15,7 @@
 #include "p3dInstanceManager.h"
 #include "p3dInstanceManager.h"
 #include "p3dInstance.h"
 #include "p3dInstance.h"
 #include "p3dSession.h"
 #include "p3dSession.h"
-#include "p3dPackage.h"
+#include "p3dHost.h"
 #include "p3d_plugin_config.h"
 #include "p3d_plugin_config.h"
 #include "p3dWinSplashWindow.h"
 #include "p3dWinSplashWindow.h"
 #include "p3dUndefinedObject.h"
 #include "p3dUndefinedObject.h"
@@ -51,7 +51,6 @@ P3DInstanceManager() {
   _is_initialized = false;
   _is_initialized = false;
   _next_temp_filename_counter = 0;
   _next_temp_filename_counter = 0;
   _unique_id = 0;
   _unique_id = 0;
-  _xcontents = NULL;
 
 
   _notify_thread_continue = false;
   _notify_thread_continue = false;
   _started_notify_thread = false;
   _started_notify_thread = false;
@@ -103,13 +102,15 @@ P3DInstanceManager::
   sigaction(SIGPIPE, &_old_sigpipe, NULL);
   sigaction(SIGPIPE, &_old_sigpipe, NULL);
 #endif  // _WIN32
 #endif  // _WIN32
 
 
-  if (_xcontents != NULL) {
-    delete _xcontents;
-  }
-
   assert(_instances.empty());
   assert(_instances.empty());
   assert(_sessions.empty());
   assert(_sessions.empty());
 
 
+  Hosts::iterator hi;
+  for (hi = _hosts.begin(); hi != _hosts.end(); ++hi) {
+    delete (*hi).second;
+  }
+  _hosts.clear();
+
   // Delete any remaining temporary files.
   // Delete any remaining temporary files.
   TempFilenames::iterator ti;
   TempFilenames::iterator ti;
   for (ti = _temp_filenames.begin(); ti != _temp_filenames.end(); ++ti) {
   for (ti = _temp_filenames.begin(); ti != _temp_filenames.end(); ++ti) {
@@ -145,7 +146,7 @@ P3DInstanceManager::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::initialize
 //     Function: P3DInstanceManager::initialize
 //       Access: Public
 //       Access: Public
-//  Description: Called by the host at application startup.  It
+//  Description: Called by the plugin host at application startup.  It
 //               returns true if the DLL is successfully initialized,
 //               returns true if the DLL is successfully initialized,
 //               false if it should be immediately shut down and
 //               false if it should be immediately shut down and
 //               redownloaded.
 //               redownloaded.
@@ -156,12 +157,7 @@ initialize(const string &contents_filename, const string &download_url,
            const string &log_basename) {
            const string &log_basename) {
 
 
   _root_dir = find_root_dir();
   _root_dir = find_root_dir();
-  _download_url = download_url;
-#ifdef P3D_PLUGIN_DOWNLOAD
-  if (_download_url.empty()) {
-    _download_url = P3D_PLUGIN_DOWNLOAD;
-  }
-#endif
+
   _platform = platform;
   _platform = platform;
   if (_platform.empty()) {
   if (_platform.empty()) {
     _platform = DTOOL_PLATFORM;
     _platform = DTOOL_PLATFORM;
@@ -197,7 +193,7 @@ initialize(const string &contents_filename, const string &download_url,
   }
   }
   delete[] buffer_2;
   delete[] buffer_2;
 
 
-  // Also make sure the directory actually exists.
+  // And make sure the directory actually exists.
   mkdir_complete(_temp_directory, nout);
   mkdir_complete(_temp_directory, nout);
 
 
 #else
 #else
@@ -216,11 +212,6 @@ initialize(const string &contents_filename, const string &download_url,
   }
   }
 #endif
 #endif
 
 
-  // Ensure that the download URL ends with a slash.
-  if (!_download_url.empty() && _download_url[_download_url.size() - 1] != '/') {
-    _download_url += "/";
-  }
-
   // Ensure that the temp directory ends with a slash.
   // Ensure that the temp directory ends with a slash.
   if (!_temp_directory.empty() && _temp_directory[_temp_directory.size() - 1] != '/') {
   if (!_temp_directory.empty() && _temp_directory[_temp_directory.size() - 1] != '/') {
 #ifdef _WIN32
 #ifdef _WIN32
@@ -251,8 +242,6 @@ initialize(const string &contents_filename, const string &download_url,
   }
   }
 
 
   nout << "_root_dir = " << _root_dir
   nout << "_root_dir = " << _root_dir
-       << ", contents = " << contents_filename
-       << ", download = " << _download_url
        << ", platform = " << _platform
        << ", platform = " << _platform
        << "\n";
        << "\n";
 
 
@@ -263,9 +252,11 @@ initialize(const string &contents_filename, const string &download_url,
 
 
   _is_initialized = true;
   _is_initialized = true;
 
 
-  // Attempt to read the supplied contents.xml file.
-  if (!contents_filename.empty()) {
-    if (!read_contents_file(contents_filename)) {
+  if (!download_url.empty() && !contents_filename.empty()) {
+    // Attempt to pre-read the supplied contents.xml file, to avoid an
+    // unnecessary download later.
+    P3DHost *host = get_host(download_url);
+    if (!host->read_contents_file(contents_filename)) {
       nout << "Couldn't read " << contents_filename << "\n";
       nout << "Couldn't read " << contents_filename << "\n";
     }
     }
   }
   }
@@ -273,42 +264,6 @@ initialize(const string &contents_filename, const string &download_url,
   return true;
   return true;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::read_contents_file
-//       Access: Public
-//  Description: Reads the contents.xml file in the indicated
-//               filename.  On success, copies the contents.xml file
-//               into the standard location (if it's not there
-//               already).
-//
-//               Returns true on success, false on failure.
-////////////////////////////////////////////////////////////////////
-bool P3DInstanceManager::
-read_contents_file(const string &contents_filename) {
-  TiXmlDocument doc(contents_filename.c_str());
-  if (!doc.LoadFile()) {
-    return false;
-  }
-
-  TiXmlElement *xcontents = doc.FirstChildElement("contents");
-  if (xcontents == NULL) {
-    return false;
-  }
-
-  if (_xcontents != NULL) {
-    delete _xcontents;
-  }
-  _xcontents = (TiXmlElement *)xcontents->Clone();
-
-  string standard_filename = _root_dir + "/contents.xml";
-  if (standardize_filename(standard_filename) != 
-      standardize_filename(contents_filename)) {
-    copy_file(contents_filename, standard_filename);
-  }
-
-  return true;
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::create_instance
 //     Function: P3DInstanceManager::create_instance
 //       Access: Public
 //       Access: Public
@@ -473,67 +428,23 @@ wait_request() {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::get_package
+//     Function: P3DInstanceManager::get_host
 //       Access: Public
 //       Access: Public
 //  Description: Returns a (possibly shared) pointer to the indicated
 //  Description: Returns a (possibly shared) pointer to the indicated
-//               package.
-////////////////////////////////////////////////////////////////////
-P3DPackage *P3DInstanceManager::
-get_package(const string &package_name, const string &package_version) {
-  string package_platform = get_platform();
-  string key = package_name + "_" + package_platform + "_" + package_version;
-  Packages::iterator pi = _packages.find(key);
-  if (pi != _packages.end()) {
+//               download host.
+////////////////////////////////////////////////////////////////////
+P3DHost *P3DInstanceManager::
+get_host(const string &host_url) {
+  Hosts::iterator pi = _hosts.find(host_url);
+  if (pi != _hosts.end()) {
     return (*pi).second;
     return (*pi).second;
   }
   }
 
 
-  P3DPackage *package = 
-    new P3DPackage(package_name, package_platform, package_version);
-  bool inserted = _packages.insert(Packages::value_type(key, package)).second;
+  P3DHost *host = new P3DHost(this, host_url);
+  bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second;
   assert(inserted);
   assert(inserted);
 
 
-  return package;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::get_package_desc_file
-//       Access: Public
-//  Description: Fills the indicated FileSpec with the hash
-//               information for the package's desc file.  Returns
-//               true if successful, false if the package is unknown.
-//               This requires has_contents_file() to return true in
-//               order to be successful.
-////////////////////////////////////////////////////////////////////
-bool P3DInstanceManager::
-get_package_desc_file(FileSpec &desc_file,
-                      const string &package_name,
-                      const string &package_version) {
-  if (_xcontents == NULL) {
-    return false;
-  }
-
-  string package_platform = get_platform();
-
-  // Scan the contents data for the indicated package.
-  TiXmlElement *xpackage = _xcontents->FirstChildElement("package");
-  while (xpackage != NULL) {
-    const char *name = xpackage->Attribute("name");
-    const char *platform = xpackage->Attribute("platform");
-    const char *version = xpackage->Attribute("version");
-    if (name != NULL && platform != NULL && version != NULL &&
-        package_name == name && 
-        package_platform == platform &&
-        package_version == version) {
-      // Here's the matching package definition.
-      desc_file.load_xml(xpackage);
-      return true;
-    }
-
-    xpackage = xpackage->NextSiblingElement("package");
-  }
-
-  // Couldn't find the named package.
-  return false;
+  return host;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -698,89 +609,6 @@ delete_global_ptr() {
   }
   }
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::standardize_filename
-//       Access: Private, Static
-//  Description: Attempts to change the filename into some standard
-//               form for comparison with other filenames.  On a
-//               case-insensitive filesystem, this converts the
-//               filename to lowercase.  On Windows, it further
-//               replaces forward slashes with backslashes.
-////////////////////////////////////////////////////////////////////
-string P3DInstanceManager::
-standardize_filename(const string &filename) {
-#if defined(_WIN32) || defined(__APPLE__)
-  string new_filename;
-  for (string::const_iterator si = filename.begin();
-       si != filename.end();
-       ++si) {
-    char ch = *si;
-#ifdef _WIN32
-    if (ch == '/') {
-      ch = '\\';
-    }
-#endif  // _WIN32
-    new_filename += tolower(ch);
-  }
-  return new_filename;
-#else  // _WIN32 || __APPLE__
-  return filename;
-#endif  // _WIN32 || __APPLE__
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DInstanceManager::copy_file
-//       Access: Private, Static
-//  Description: Copies the data in the file named by from_filename
-//               into the file named by to_filename.
-////////////////////////////////////////////////////////////////////
-bool P3DInstanceManager::
-copy_file(const string &from_filename, const string &to_filename) {
-  ifstream in(from_filename.c_str(), ios::in | ios::binary);
-
-  // Copy to a temporary file first, in case (a) the filenames
-  // actually refer to the same file, or (b) in case we have different
-  // processes writing to the same file, and (c) to prevent
-  // partially overwriting the file should something go wrong.
-  ostringstream strm;
-  strm << to_filename << ".t";
-#ifdef _WIN32
-  strm << GetCurrentProcessId() << "_" << GetCurrentThreadId();
-#else
-  strm << getpid();
-#endif
-  string temp_filename = strm.str();
-  ofstream out(temp_filename.c_str(), ios::out | ios::binary);
-        
-  static const size_t buffer_size = 4096;
-  char buffer[buffer_size];
-  
-  in.read(buffer, buffer_size);
-  size_t count = in.gcount();
-  while (count != 0) {
-    out.write(buffer, count);
-    if (out.fail()) {
-      unlink(temp_filename.c_str());
-      return false;
-    }
-    in.read(buffer, buffer_size);
-    count = in.gcount();
-  }
-  out.close();
-
-  if (!in.eof()) {
-    unlink(temp_filename.c_str());
-    return false;
-  }
-
-  if (rename(temp_filename.c_str(), to_filename.c_str()) == 0) {
-    return true;
-  }
-
-  unlink(temp_filename.c_str());
-  return false;
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::nt_thread_run
 //     Function: P3DInstanceManager::nt_thread_run
 //       Access: Private
 //       Access: Private
@@ -791,12 +619,12 @@ nt_thread_run() {
   // The notify thread exists because we need to be able to send
   // The notify thread exists because we need to be able to send
   // asynchronous notifications of request events.  These request
   // asynchronous notifications of request events.  These request
   // events were detected in the various read threads associated with
   // events were detected in the various read threads associated with
-  // each session, but we can't call back into the host space from the
-  // read thread, since if the host immediately response to a callback
-  // by calling back into the p3d_plugin space, now we have our read
-  // thread doing stuff in here that's not related to the read thread.
-  // Even worse, some of the things it might need to do might require
-  // a separate read thread to be running!
+  // each session, but we can't call back into the plugin host space
+  // from the read thread, since if the host immediately response to a
+  // callback by calling back into the p3d_plugin space, now we have
+  // our read thread doing stuff in here that's not related to the
+  // read thread.  Even worse, some of the things it might need to do
+  // might require a separate read thread to be running!
 
 
   _notify_ready.acquire();
   _notify_ready.acquire();
   while (_notify_thread_continue) {
   while (_notify_thread_continue) {

+ 4 - 19
direct/src/plugin/p3dInstanceManager.h

@@ -28,7 +28,7 @@
 
 
 class P3DInstance;
 class P3DInstance;
 class P3DSession;
 class P3DSession;
-class P3DPackage;
+class P3DHost;
 class FileSpec;
 class FileSpec;
 class TiXmlElement;
 class TiXmlElement;
 
 
@@ -52,13 +52,9 @@ public:
   inline bool is_initialized() const;
   inline bool is_initialized() const;
 
 
   inline const string &get_root_dir() const;
   inline const string &get_root_dir() const;
-  inline const string &get_download_url() const;
   inline const string &get_platform() const;
   inline const string &get_platform() const;
   inline const string &get_log_directory() const;
   inline const string &get_log_directory() const;
 
 
-  inline bool has_contents_file() const;
-  bool read_contents_file(const string &contents_filename);
-
   P3DInstance *
   P3DInstance *
   create_instance(P3D_request_ready_func *func, 
   create_instance(P3D_request_ready_func *func, 
                   const P3D_token tokens[], size_t num_tokens, 
                   const P3D_token tokens[], size_t num_tokens, 
@@ -74,11 +70,7 @@ public:
   P3DInstance *check_request();
   P3DInstance *check_request();
   void wait_request();
   void wait_request();
 
 
-  P3DPackage *get_package(const string &package_name, 
-                          const string &package_version);
-  bool get_package_desc_file(FileSpec &desc_file, 
-                             const string &package_name,
-                             const string &package_version);
+  P3DHost *get_host(const string &host_url);
 
 
   inline int get_num_instances() const;
   inline int get_num_instances() const;
 
 
@@ -97,10 +89,6 @@ public:
   static P3DInstanceManager *get_global_ptr();
   static P3DInstanceManager *get_global_ptr();
   static void delete_global_ptr();
   static void delete_global_ptr();
 
 
-private:
-  static string standardize_filename(const string &filename);
-  static bool copy_file(const string &from_filename, const string &to_filename);
-
 private:
 private:
   // The notify thread.  This thread runs only for the purpose of
   // The notify thread.  This thread runs only for the purpose of
   // generating asynchronous notifications of requests, to callers who
   // generating asynchronous notifications of requests, to callers who
@@ -111,15 +99,12 @@ private:
 private:
 private:
   bool _is_initialized;
   bool _is_initialized;
   string _root_dir;
   string _root_dir;
-  string _download_url;
   string _platform;
   string _platform;
   string _log_directory;
   string _log_directory;
   string _log_basename;
   string _log_basename;
   string _log_pathname;
   string _log_pathname;
   string _temp_directory;
   string _temp_directory;
 
 
-  TiXmlElement *_xcontents;
-
   P3D_object *_undefined_object;
   P3D_object *_undefined_object;
   P3D_object *_none_object;
   P3D_object *_none_object;
   P3D_object *_true_object;
   P3D_object *_true_object;
@@ -131,8 +116,8 @@ private:
   typedef map<string, P3DSession *> Sessions;
   typedef map<string, P3DSession *> Sessions;
   Sessions _sessions;
   Sessions _sessions;
 
 
-  typedef map<string, P3DPackage *> Packages;
-  Packages _packages;
+  typedef map<string, P3DHost *> Hosts;
+  Hosts _hosts;
 
 
   typedef set<string> TempFilenames;
   typedef set<string> TempFilenames;
   TempFilenames _temp_filenames;
   TempFilenames _temp_filenames;

+ 11 - 0
direct/src/plugin/p3dPackage.I

@@ -81,6 +81,17 @@ get_failed() const {
   return _failed;
   return _failed;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::get_host
+//       Access: Public
+//  Description: Returns the host server which offers this package for
+//               download.
+////////////////////////////////////////////////////////////////////
+inline P3DHost *P3DPackage::
+get_host() const {
+  return _host;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::get_package_dir
 //     Function: P3DPackage::get_package_dir
 //       Access: Public
 //       Access: Public

+ 26 - 39
direct/src/plugin/p3dPackage.cxx

@@ -36,21 +36,14 @@ static const double extract_portion = 0.05;
 //  Description: 
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 P3DPackage::
 P3DPackage::
-P3DPackage(const string &package_name,
-           const string &package_platform,
+P3DPackage(P3DHost *host, const string &package_name,
            const string &package_version) :
            const string &package_version) :
+  _host(host),
   _package_name(package_name),
   _package_name(package_name),
-  _package_platform(package_platform),
   _package_version(package_version)
   _package_version(package_version)
 {
 {
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-
   _package_fullname = _package_name;
   _package_fullname = _package_name;
-  _package_dir = inst_mgr->get_root_dir() + string("/packages/") + _package_name;
-  if (!_package_platform.empty()) {
-    _package_fullname += string("_") + _package_platform;
-    _package_dir += string("/") + _package_platform;
-  }
+  _package_dir = _host->get_host_dir() + string("/packages/") + _package_name;
   _package_fullname += string("_") + _package_version;
   _package_fullname += string("_") + _package_version;
   _package_dir += string("/") + _package_version;
   _package_dir += string("/") + _package_version;
 
 
@@ -169,16 +162,14 @@ begin_info_download() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 download_contents_file() {
 download_contents_file() {
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-
-  if (inst_mgr->has_contents_file()) {
+  if (_host->has_contents_file()) {
     // We've already got a contents.xml file; go straight to the
     // We've already got a contents.xml file; go straight to the
     // package desc file.
     // package desc file.
     download_desc_file();
     download_desc_file();
     return;
     return;
   }
   }
 
 
-  string url = inst_mgr->get_download_url();
+  string url = _host->get_host_url_prefix();
   url += "contents.xml";
   url += "contents.xml";
 
 
   // Download contents.xml to a temporary filename first, in case
   // Download contents.xml to a temporary filename first, in case
@@ -196,15 +187,13 @@ download_contents_file() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 contents_file_download_finished(bool success) {
 contents_file_download_finished(bool success) {
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-
-  if (!inst_mgr->has_contents_file()) {
-    if (!success || !inst_mgr->read_contents_file(_temp_contents_file->get_filename())) {
+  if (!_host->has_contents_file()) {
+    if (!success || !_host->read_contents_file(_temp_contents_file->get_filename())) {
       nout << "Couldn't read " << *_temp_contents_file << "\n";
       nout << "Couldn't read " << *_temp_contents_file << "\n";
 
 
       // Maybe we can read an already-downloaded contents.xml file.
       // Maybe we can read an already-downloaded contents.xml file.
-      string standard_filename = inst_mgr->get_root_dir() + "/contents.xml";
-      if (!inst_mgr->read_contents_file(standard_filename)) {
+      string standard_filename = _host->get_host_dir() + "/contents.xml";
+      if (!_host->read_contents_file(standard_filename)) {
         // Couldn't even read that.  Fail.
         // Couldn't even read that.  Fail.
         report_done(false);
         report_done(false);
         delete _temp_contents_file;
         delete _temp_contents_file;
@@ -225,27 +214,31 @@ contents_file_download_finished(bool success) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::download_desc_file
 //     Function: P3DPackage::download_desc_file
 //       Access: Private
 //       Access: Private
-//  Description: Starts downloading the desc file for the package.
+//  Description: Starts downloading the desc file for the package, if
+//               it's needed; or read to local version if it's fresh
+//               enough.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 download_desc_file() {
 download_desc_file() {
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-
   // Attempt to check the desc file for freshness.  If it already
   // Attempt to check the desc file for freshness.  If it already
   // exists, and is consistent with the server contents file, we don't
   // exists, and is consistent with the server contents file, we don't
   // need to re-download it.
   // need to re-download it.
-  string root_dir = inst_mgr->get_root_dir() + "/packages";
   FileSpec desc_file;
   FileSpec desc_file;
-  if (!inst_mgr->get_package_desc_file(desc_file, _package_name, _package_version)) {
+  if (!_host->get_package_desc_file(desc_file, _package_platform,
+                                    _package_name, _package_version)) {
     nout << "Couldn't find package " << _package_fullname
     nout << "Couldn't find package " << _package_fullname
          << " in contents file.\n";
          << " in contents file.\n";
+    return;
+  }
 
 
-  } else if (desc_file.get_pathname(root_dir) != _desc_file_pathname) {
-    nout << "Wrong pathname for desc file: " 
-         << desc_file.get_pathname(root_dir) 
-         << " instead of " << _desc_file_pathname << "\n";
+  // The desc file might have a different path on the host server than
+  // it has locally, because we strip out the platform locally.
+  // Adjust desc_file to point to the local file.
+  string url_filename = desc_file.get_filename();
+  desc_file.set_filename(_desc_file_basename);
+  assert (desc_file.get_pathname(_package_dir) == _desc_file_pathname);
 
 
-  } else if (!desc_file.full_verify(root_dir)) {
+  if (!desc_file.full_verify(_package_dir)) {
     nout << _desc_file_pathname << " is stale.\n";
     nout << _desc_file_pathname << " is stale.\n";
 
 
   } else {
   } else {
@@ -258,13 +251,8 @@ download_desc_file() {
   }
   }
 
 
   // The desc file is not current.  Go download it.
   // The desc file is not current.  Go download it.
-  string url = inst_mgr->get_download_url();
-  url += _package_name;
-  if (!_package_platform.empty()) {
-    url += "/" + _package_platform;
-  }
-  url += "/" + _package_version;
-  url += "/" + _desc_file_basename;
+  string url = _host->get_host_url_prefix();
+  url += url_filename;
 
 
   start_download(DT_desc_file, url, _desc_file_pathname, false);
   start_download(DT_desc_file, url, _desc_file_pathname, false);
 }
 }
@@ -415,8 +403,7 @@ begin_data_download() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 download_compressed_archive(bool allow_partial) {
 download_compressed_archive(bool allow_partial) {
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-  string url = inst_mgr->get_download_url();
+  string url = _host->get_host_url_prefix();
   url += _package_name;
   url += _package_name;
   if (!_package_platform.empty()) {
   if (!_package_platform.empty()) {
     url += "/" + _package_platform;
     url += "/" + _package_platform;

+ 14 - 6
direct/src/plugin/p3dPackage.h

@@ -20,6 +20,7 @@
 #include "fileSpec.h"
 #include "fileSpec.h"
 #include "get_tinyxml.h"
 #include "get_tinyxml.h"
 
 
+class P3DHost;
 class P3DInstance;
 class P3DInstance;
 class P3DTemporaryFile;
 class P3DTemporaryFile;
 
 
@@ -31,23 +32,26 @@ class P3DTemporaryFile;
 //               runtime, which consists of a bunch of dll's
 //               runtime, which consists of a bunch of dll's
 //               downloaded in a single tar file, is a package.
 //               downloaded in a single tar file, is a package.
 //
 //
-//               The plugin is responsible for managing these packages
-//               on disk, downloading new versions when needed, and
-//               removing stale versions to limit disk space waste.
+//               The core API is responsible for managing these
+//               packages on disk, downloading new versions when
+//               needed, and removing stale versions to limit disk
+//               space waste.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class P3DPackage {
 class P3DPackage {
-public:
-  P3DPackage(const string &package_name, 
-             const string &package_platform,
+private:
+  P3DPackage(P3DHost *host,
+             const string &package_name, 
              const string &package_version);
              const string &package_version);
   ~P3DPackage();
   ~P3DPackage();
 
 
+public:
   inline bool get_info_ready() const;
   inline bool get_info_ready() const;
   inline size_t get_download_size() const;
   inline size_t get_download_size() const;
 
 
   inline void activate_download();
   inline void activate_download();
   inline bool get_ready() const;
   inline bool get_ready() const;
   inline bool get_failed() const;
   inline bool get_failed() const;
+  inline P3DHost *get_host() const;
   inline const string &get_package_dir() const;
   inline const string &get_package_dir() const;
   inline const string &get_package_name() const;
   inline const string &get_package_name() const;
   inline const string &get_package_version() const;
   inline const string &get_package_version() const;
@@ -101,6 +105,8 @@ private:
   bool is_extractable(const string &filename) const;
   bool is_extractable(const string &filename) const;
 
 
 private:
 private:
+  P3DHost *_host;
+
   string _package_name;
   string _package_name;
   string _package_version;
   string _package_version;
   string _package_platform;
   string _package_platform;
@@ -132,6 +138,8 @@ private:
 
 
   friend class Download;
   friend class Download;
   friend class P3DMultifileReader;
   friend class P3DMultifileReader;
+
+  friend class P3DHost;
 };
 };
 
 
 #include "p3dPackage.I"
 #include "p3dPackage.I"

+ 1 - 1
direct/src/plugin/p3dPythonRun.cxx

@@ -18,7 +18,7 @@
 
 
 // There is only one P3DPythonRun object in any given process space.
 // There is only one P3DPythonRun object in any given process space.
 // Makes the statics easier to deal with, and we don't need multiple
 // Makes the statics easier to deal with, and we don't need multiple
-// instances of this think.
+// instances of this thing.
 P3DPythonRun *P3DPythonRun::_global_ptr = NULL;
 P3DPythonRun *P3DPythonRun::_global_ptr = NULL;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 1 - 0
direct/src/plugin/p3d_plugin_composite1.cxx

@@ -7,6 +7,7 @@
 #include "p3dFileDownload.cxx"
 #include "p3dFileDownload.cxx"
 #include "p3dFileParams.cxx"
 #include "p3dFileParams.cxx"
 #include "p3dFloatObject.cxx"
 #include "p3dFloatObject.cxx"
+#include "p3dHost.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstanceManager.cxx"
 #include "p3dInstanceManager.cxx"
 #include "p3dIntObject.cxx"
 #include "p3dIntObject.cxx"

+ 3 - 4
direct/src/plugin/p3d_plugin_config.h.pp

@@ -6,10 +6,9 @@
 /********************************** DO NOT EDIT ****************************/
 /********************************** DO NOT EDIT ****************************/
 
 
 /* The URL that is the root of the download server that this plugin
 /* The URL that is the root of the download server that this plugin
-   should contact.  The nppanda3d.dll file should be found at this
-   location; as well as the contents.xml file that defines where the
-   various Panda3D packages will be found. */
-#$[]define P3D_PLUGIN_DOWNLOAD "$[P3D_PLUGIN_DOWNLOAD]$[if $[notdir $[P3D_PLUGIN_DOWNLOAD]],/]"
+   should contact.  The contents.xml file that defines this particular
+   "coreapi" package should be found at this location. */
+#$[]define PANDA_PACKAGE_HOST_URL "$[PANDA_PACKAGE_HOST_URL]"
 
 
 /* The filename(s) to generate output to when the plugin is running.
 /* The filename(s) to generate output to when the plugin is running.
    For debugging purposes only. */
    For debugging purposes only. */

+ 2 - 2
direct/src/plugin_npapi/ppInstance.cxx

@@ -108,7 +108,7 @@ void PPInstance::
 begin() {
 begin() {
   if (!is_plugin_loaded()) {
   if (!is_plugin_loaded()) {
     // Go download the contents file, so we can download the core DLL.
     // Go download the contents file, so we can download the core DLL.
-    string url = P3D_PLUGIN_DOWNLOAD;
+    string url = PANDA_PACKAGE_HOST_URL;
     if (!url.empty() && url[url.length() - 1] != '/') {
     if (!url.empty() && url[url.length() - 1] != '/') {
       url += '/';
       url += '/';
     }
     }
@@ -837,7 +837,7 @@ get_core_api(TiXmlElement *xplugin) {
 
 
   } else {
   } else {
     // The DLL file needs to be downloaded.  Go get it.
     // The DLL file needs to be downloaded.  Go get it.
-    string url = P3D_PLUGIN_DOWNLOAD;
+    string url = PANDA_PACKAGE_HOST_URL;
     if (!url.empty() && url[url.length() - 1] != '/') {
     if (!url.empty() && url[url.length() - 1] != '/') {
       url += '/';
       url += '/';
     }
     }

+ 2 - 2
direct/src/plugin_standalone/panda3d.cxx

@@ -64,7 +64,7 @@ run(int argc, char *argv[]) {
   const char *optstr = "+mu:p:ft:s:o:l:h";
   const char *optstr = "+mu:p:ft:s:o:l:h";
 
 
   bool allow_multiple = false;
   bool allow_multiple = false;
-  string download_url = P3D_PLUGIN_DOWNLOAD;
+  string download_url = PANDA_PACKAGE_HOST_URL;
   string this_platform = DTOOL_PLATFORM;
   string this_platform = DTOOL_PLATFORM;
   bool force_download = false;
   bool force_download = false;
 
 
@@ -752,7 +752,7 @@ usage() {
 
 
     << "  -u url\n"
     << "  -u url\n"
     << "    Specify the URL of the Panda3D download server.  The default is\n"
     << "    Specify the URL of the Panda3D download server.  The default is\n"
-    << "    \"" << P3D_PLUGIN_DOWNLOAD << "\" .\n\n"
+    << "    \"" << PANDA_PACKAGE_HOST_URL << "\" .\n\n"
 
 
     << "  -p platform\n"
     << "  -p platform\n"
     << "    Specify the platform to masquerade as.  The default is \""
     << "    Specify the platform to masquerade as.  The default is \""

+ 120 - 60
direct/src/showutil/Packager.py

@@ -133,8 +133,9 @@ class Packager:
         def __init__(self, packageName, packager):
         def __init__(self, packageName, packager):
             self.packageName = packageName
             self.packageName = packageName
             self.packager = packager
             self.packager = packager
-            self.version = None
             self.platform = None
             self.platform = None
+            self.version = None
+            self.host = None
             self.p3dApplication = False
             self.p3dApplication = False
             self.compressionLevel = 0
             self.compressionLevel = 0
             self.importedMapsDir = 'imported_maps'
             self.importedMapsDir = 'imported_maps'
@@ -672,6 +673,7 @@ class Packager:
                 xrequires.SetAttribute('name', package.packageName)
                 xrequires.SetAttribute('name', package.packageName)
                 if package.version:
                 if package.version:
                     xrequires.SetAttribute('version', package.version)
                     xrequires.SetAttribute('version', package.version)
+                xrequires.SetAttribute('host', package.host)
                 xpackage.InsertEndChild(xrequires)
                 xpackage.InsertEndChild(xrequires)
 
 
             doc.InsertEndChild(xpackage)
             doc.InsertEndChild(xpackage)
@@ -699,6 +701,9 @@ class Packager:
                 raise PackagerError, message
                 raise PackagerError, message
 
 
         def writeDescFile(self):
         def writeDescFile(self):
+            """ Makes the package.xml file that describes the package
+            and its contents, for download. """
+            
             packageDescFullpath = Filename(self.packager.installDir, self.packageDesc)
             packageDescFullpath = Filename(self.packager.installDir, self.packageDesc)
             doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
             doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             decl = TiXmlDeclaration("1.0", "utf-8", "")
@@ -724,6 +729,7 @@ class Packager:
                     xrequires.SetAttribute('platform', package.platform)
                     xrequires.SetAttribute('platform', package.platform)
                 if package.version:
                 if package.version:
                     xrequires.SetAttribute('version', package.version)
                     xrequires.SetAttribute('version', package.version)
+                xrequires.SetAttribute('host', package.host)
                 xpackage.InsertEndChild(xrequires)
                 xpackage.InsertEndChild(xrequires)
 
 
             xuncompressedArchive = self.getFileSpec(
             xuncompressedArchive = self.getFileSpec(
@@ -743,6 +749,10 @@ class Packager:
             doc.SaveFile()
             doc.SaveFile()
 
 
         def writeImportDescFile(self):
         def writeImportDescFile(self):
+            """ Makes the package_import.xml file that describes the
+            package and its contents, for other packages and
+            applications that may wish to "require" this one. """
+        
             packageImportDescFullpath = Filename(self.packager.installDir, self.packageImportDesc)
             packageImportDescFullpath = Filename(self.packager.installDir, self.packageImportDesc)
             doc = TiXmlDocument(packageImportDescFullpath.toOsSpecific())
             doc = TiXmlDocument(packageImportDescFullpath.toOsSpecific())
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             decl = TiXmlDeclaration("1.0", "utf-8", "")
@@ -754,6 +764,7 @@ class Packager:
                 xpackage.SetAttribute('platform', self.platform)
                 xpackage.SetAttribute('platform', self.platform)
             if self.version:
             if self.version:
                 xpackage.SetAttribute('version', self.version)
                 xpackage.SetAttribute('version', self.version)
+            xpackage.SetAttribute('host', self.host)
 
 
             for package in self.requires:
             for package in self.requires:
                 xrequires = TiXmlElement('requires')
                 xrequires = TiXmlElement('requires')
@@ -762,6 +773,7 @@ class Packager:
                     xrequires.SetAttribute('platform', package.platform)
                     xrequires.SetAttribute('platform', package.platform)
                 if package.version:
                 if package.version:
                     xrequires.SetAttribute('version', package.version)
                     xrequires.SetAttribute('version', package.version)
+                xrequires.SetAttribute('host', package.host)
                 xpackage.InsertEndChild(xrequires)
                 xpackage.InsertEndChild(xrequires)
 
 
             self.components.sort()
             self.components.sort()
@@ -785,6 +797,7 @@ class Packager:
             self.packageName = xpackage.Attribute('name')
             self.packageName = xpackage.Attribute('name')
             self.platform = xpackage.Attribute('platform')
             self.platform = xpackage.Attribute('platform')
             self.version = xpackage.Attribute('version')
             self.version = xpackage.Attribute('version')
+            self.host = xpackage.Attribute('host')
 
 
             self.requires = []
             self.requires = []
             xrequires = xpackage.FirstChildElement('requires')
             xrequires = xpackage.FirstChildElement('requires')
@@ -792,8 +805,11 @@ class Packager:
                 packageName = xrequires.Attribute('name')
                 packageName = xrequires.Attribute('name')
                 platform = xrequires.Attribute('platform')
                 platform = xrequires.Attribute('platform')
                 version = xrequires.Attribute('version')
                 version = xrequires.Attribute('version')
+                host = xrequires.Attribute('host')
                 if packageName:
                 if packageName:
-                    package = self.packager.findPackage(packageName, platform = platform, version = version, requires = self.requires)
+                    package = self.packager.findPackage(
+                        packageName, platform = platform, version = version,
+                        host = host, requires = self.requires)
                     if package:
                     if package:
                         self.requires.append(package)
                         self.requires.append(package)
                 xrequires = xrequires.NextSiblingElement('requires')
                 xrequires = xrequires.NextSiblingElement('requires')
@@ -1012,9 +1028,12 @@ class Packager:
         self.installDir = None
         self.installDir = None
         self.persistDir = None
         self.persistDir = None
 
 
-        # A search list of directories and/or URL's to search for
-        # installed packages.  We query it from a config variable
-        # initially, but we may also be extending it at runtime.
+        # The download URL at which these packages will eventually be
+        # hosted.  This may also be changed with the "host" command.
+        self.host = PandaSystem.getPackageHostUrl()
+        self.hostDescriptiveName = None
+
+        # A search list for previously-built local packages.
         self.installSearch = ConfigVariableSearchPath('pdef-path')
         self.installSearch = ConfigVariableSearchPath('pdef-path')
 
 
         # The system PATH, for searching dll's and exe's.
         # The system PATH, for searching dll's and exe's.
@@ -1195,23 +1214,8 @@ class Packager:
         # We must have an actual install directory.
         # We must have an actual install directory.
         assert(self.installDir)
         assert(self.installDir)
 
 
-##         # If the persist dir names an empty or nonexistent directory,
-##         # we will be generating a brand new publish with no previous
-##         # patches.
-##         self.persistDir.makeDir()
-
-##         # Within the persist dir, we make a temporary holding dir for
-##         # generating multifiles.
-##         self.mfTempDir = Filename(self.persistDir, Filename('mftemp/'))
-##         self.mfTempDir.makeDir()
-
-##         # We also need a temporary holding dir for squeezing py files.
-##         self.pyzTempDir = Filename(self.persistDir, Filename('pyz/'))
-##         self.pyzTempDir.makeDir()
-
-##         # Change to the persist directory so the temp files will be
-##         # created there
-##         os.chdir(self.persistDir.toOsSpecific())
+        if not PandaSystem.getPackageVersionString() or not PandaSystem.getPackageHostUrl():
+            raise PackagerError, 'This script must be run using a version of Panda3D that has been built\nfor distribution.  Try using ppackage.p3d or packp3d.p3d instead.'
 
 
     def __expandVariable(self, line, p):
     def __expandVariable(self, line, p):
         """ Given that line[p] is a dollar sign beginning a variable
         """ Given that line[p] is a dollar sign beginning a variable
@@ -1441,6 +1445,32 @@ class Packager:
         value = ExecutionEnvironment.expandString(value.strip())
         value = ExecutionEnvironment.expandString(value.strip())
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
 
 
+    def parse_host(self, words):
+        """
+        host "url" ["descriptive name"]
+        """
+
+        hostDescriptiveName = None
+        try:
+            if len(words) == 2:
+                command, host = words
+            else:
+                command, host, hostDescriptiveName = words
+        except ValueError:
+            raise ArgumentNumber
+
+        if self.currentPackage:
+            self.currentPackage.host = host
+        else:
+            # Outside of a package, the "host" command specifies the
+            # host for all future packages.
+            self.host = host
+
+        # The descriptive name, if specified, is kept until the end,
+        # where it may be passed to make_contents by ppackage.py.
+        if hostDescriptiveName:
+            self.hostDescriptiveName = hostDescriptiveName
+
     def parse_model_path(self, words):
     def parse_model_path(self, words):
         """
         """
         model_path directory
         model_path directory
@@ -1469,10 +1499,10 @@ class Packager:
 
 
     def parse_begin_package(self, words):
     def parse_begin_package(self, words):
         """
         """
-        begin_package packageName [version=v]
+        begin_package packageName [version=v] [host=host]
         """
         """
 
 
-        args = self.__parseArgs(words, ['version'])
+        args = self.__parseArgs(words, ['version', 'host'])
 
 
         try:
         try:
             command, packageName = words
             command, packageName = words
@@ -1480,8 +1510,10 @@ class Packager:
             raise ArgumentNumber
             raise ArgumentNumber
 
 
         version = args.get('version', None)
         version = args.get('version', None)
+        host = args.get('host', None)
 
 
-        self.beginPackage(packageName, version = version, p3dApplication = False)
+        self.beginPackage(packageName, version = version, host = host,
+                          p3dApplication = False)
 
 
     def parse_end_package(self, words):
     def parse_end_package(self, words):
         """
         """
@@ -1539,10 +1571,10 @@ class Packager:
 
 
     def parse_require(self, words):
     def parse_require(self, words):
         """
         """
-        require packageName [version=v]
+        require packageName [version=v] [host=url]
         """
         """
 
 
-        args = self.__parseArgs(words, ['version'])
+        args = self.__parseArgs(words, ['version', 'host'])
 
 
         try:
         try:
             command, packageName = words
             command, packageName = words
@@ -1550,7 +1582,8 @@ class Packager:
             raise ArgumentError
             raise ArgumentError
 
 
         version = args.get('version', None)
         version = args.get('version', None)
-        self.require(packageName, version = version)
+        host = args.get('host', None)
+        self.require(packageName, version = version, host = host)
 
 
     def parse_module(self, words):
     def parse_module(self, words):
         """
         """
@@ -1798,7 +1831,8 @@ class Packager:
             del words[-1]
             del words[-1]
                 
                 
     
     
-    def beginPackage(self, packageName, version = None, p3dApplication = False):
+    def beginPackage(self, packageName, version = None, host = None,
+                     p3dApplication = False):
         """ Begins a new package specification.  packageName is the
         """ Begins a new package specification.  packageName is the
         basename of the package.  Follow this with a number of calls
         basename of the package.  Follow this with a number of calls
         to file() etc., and close the package with endPackage(). """
         to file() etc., and close the package with endPackage(). """
@@ -1806,20 +1840,33 @@ class Packager:
         if self.currentPackage:
         if self.currentPackage:
             raise PackagerError, 'unmatched end_package %s' % (self.currentPackage.packageName)
             raise PackagerError, 'unmatched end_package %s' % (self.currentPackage.packageName)
 
 
-        # A special case for the Panda3D package.  We enforce that the
-        # version number matches what we've been compiled with.
+        if host is None and not p3dApplication:
+            # Every package that doesn't specify otherwise uses the
+            # current download host.
+            host = self.host
+
+        # A special case when building the "panda3d" package.  We
+        # enforce that the version number matches what we've been
+        # compiled with.
         if packageName == 'panda3d':
         if packageName == 'panda3d':
             if version is None:
             if version is None:
                 version = PandaSystem.getPackageVersionString()
                 version = PandaSystem.getPackageVersionString()
-            else:
-                if version != PandaSystem.getPackageVersionString():
-                    message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString())
-                    raise PackageError, message
+            if host is None:
+                host = PandaSystem.getPackageHostUrl()
+
+            if version != PandaSystem.getPackageVersionString():
+                message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString())
+                raise PackageError, message
+
+            if host != PandaSystem.getPackageHostUrl():
+                message = 'mismatched Panda3D host: requested %s, but Panda3D is built as %s' % (host, PandaSystem.getPackageHostUrl())
+                raise PackageError, message
 
 
         package = self.Package(packageName, self)
         package = self.Package(packageName, self)
         self.currentPackage = package
         self.currentPackage = package
 
 
         package.version = version
         package.version = version
+        package.host = host
         package.p3dApplication = p3dApplication
         package.p3dApplication = p3dApplication
 
 
         if package.p3dApplication:
         if package.p3dApplication:
@@ -1855,7 +1902,7 @@ class Packager:
         self.currentPackage = None
         self.currentPackage = None
 
 
     def findPackage(self, packageName, platform = None, version = None,
     def findPackage(self, packageName, platform = None, version = None,
-                    requires = None):
+                    host = None, requires = None):
         """ Searches for the named package from a previous publish
         """ Searches for the named package from a previous publish
         operation along the install search path.
         operation along the install search path.
 
 
@@ -1872,25 +1919,32 @@ class Packager:
             platform = self.platform
             platform = self.platform
 
 
         # Is it a package we already have resident?
         # Is it a package we already have resident?
-        package = self.packages.get((packageName, platform, version), None)
+        package = self.packages.get((packageName, platform, version, host), None)
         if package:
         if package:
             return package
             return package
 
 
         # Look on the searchlist.
         # Look on the searchlist.
         for dirname in self.installSearch.getDirectories():
         for dirname in self.installSearch.getDirectories():
-            package = self.__scanPackageDir(dirname, packageName, platform, version, requires = requires)
+            package = self.__scanPackageDir(dirname, packageName, platform, version, host, requires = requires)
             if not package:
             if not package:
-                package = self.__scanPackageDir(dirname, packageName, None, version, requires = requires)
+                package = self.__scanPackageDir(dirname, packageName, None, version, host, requires = requires)
 
 
             if package:
             if package:
-                package = self.packages.setdefault((package.packageName, package.platform, package.version), package)
-                self.packages[(packageName, platform, version)] = package
-                return package
+                break
+
+        if not package:
+            # Query the indicated host.
+            package = self.__findPackageOnHost(packageName, platform, version, host, requires = requires)
+
+        if package:
+            package = self.packages.setdefault((package.packageName, package.platform, package.version, package.host), package)
+            self.packages[(packageName, platform, version, host)] = package
+            return package
                 
                 
         return None
         return None
 
 
     def __scanPackageDir(self, rootDir, packageName, platform, version,
     def __scanPackageDir(self, rootDir, packageName, platform, version,
-                         requires = None):
+                         host, requires = None):
         """ Scans a directory on disk, looking for *_import.xml files
         """ Scans a directory on disk, looking for *_import.xml files
         that match the indicated packageName and optional version.  If a
         that match the indicated packageName and optional version.  If a
         suitable xml file is found, reads it and returns the assocated
         suitable xml file is found, reads it and returns the assocated
@@ -1917,6 +1971,9 @@ class Packager:
             packageDir = Filename(packageDir, '*')
             packageDir = Filename(packageDir, '*')
             basename += '_%s' % ('*')
             basename += '_%s' % ('*')
 
 
+        # Actually, the host means little for this search, since we're
+        # only looking in a local directory at this point.
+
         basename += '_import.xml'
         basename += '_import.xml'
         filename = Filename(packageDir, basename)
         filename = Filename(packageDir, basename)
         filelist = glob.glob(filename.toOsSpecific())
         filelist = glob.glob(filename.toOsSpecific())
@@ -1934,6 +1991,10 @@ class Packager:
 
 
         return None
         return None
 
 
+    def __findPackageOnHost(self, packageName, platform, version, host, requires = None):
+        # TODO.
+        return None
+
     def __sortPackageImportFilelist(self, filelist):
     def __sortPackageImportFilelist(self, filelist):
         """ Given a list of *_import.xml filenames, sorts them in
         """ Given a list of *_import.xml filenames, sorts them in
         reverse order by version, so that the highest-numbered
         reverse order by version, so that the highest-numbered
@@ -1968,7 +2029,8 @@ class Packager:
             while p < len(version) and version[p] in string.digits:
             while p < len(version) and version[p] in string.digits:
                 w += version[p]
                 w += version[p]
                 p += 1
                 p += 1
-            words.append(int(w))
+            if w:
+                words.append(int(w))
 
 
         return tuple(words)
         return tuple(words)
 
 
@@ -2024,7 +2086,7 @@ class Packager:
 
 
         self.currentPackage.configs[variable] = value
         self.currentPackage.configs[variable] = value
 
 
-    def require(self, packageName, version = None):
+    def require(self, packageName, version = None, host = None):
         """ Indicates a dependency on the named package, supplied as
         """ Indicates a dependency on the named package, supplied as
         a name.
         a name.
 
 
@@ -2035,13 +2097,17 @@ class Packager:
         if not self.currentPackage:
         if not self.currentPackage:
             raise OutsideOfPackageError
             raise OutsideOfPackageError
 
 
-        # A special case for the Panda3D package.  We enforce that the
-        # version number matches what we've been compiled with.
+        # A special case when requiring the "panda3d" package.  We
+        # supply the version number what we've been compiled with as a
+        # default.
         if packageName == 'panda3d':
         if packageName == 'panda3d':
             if version is None:
             if version is None:
                 version = PandaSystem.getPackageVersionString()
                 version = PandaSystem.getPackageVersionString()
+            if host is None:
+                host = PandaSystem.getPackageHostUrl()
         
         
-        package = self.findPackage(packageName, version = version, requires = self.currentPackage.requires)
+        package = self.findPackage(packageName, version = version, host = host,
+                                   requires = self.currentPackage.requires)
         if not package:
         if not package:
             message = 'Unknown package %s, version "%s"' % (packageName, version)
             message = 'Unknown package %s, version "%s"' % (packageName, version)
             raise PackagerError, message
             raise PackagerError, message
@@ -2059,20 +2125,14 @@ class Packager:
         if not self.currentPackage:
         if not self.currentPackage:
             raise OutsideOfPackageError
             raise OutsideOfPackageError
 
 
-        # A special case for the Panda3D package.  We enforce that the
-        # version number matches what we've been compiled with.
+        # A special case when requiring the "panda3d" package.  We
+        # complain if the version number doesn't match what we've been
+        # compiled with.
         if package.packageName == 'panda3d':
         if package.packageName == 'panda3d':
             if package.version != PandaSystem.getPackageVersionString():
             if package.version != PandaSystem.getPackageVersionString():
-                if not PandaSystem.getPackageVersionString():
-                    # We haven't been compiled with any particular
-                    # version of Panda.  This is a warning, not an
-                    # error.
-                    print "Warning: requiring panda3d version %s, which may or may not match the current build of Panda.  Recommend that you use only the official Panda3D build for making distributable applications to ensure compatibility." % (package.version)
-                else:
-                    # This particular version of Panda doesn't match
-                    # the requested version.  Again, a warning, not an
-                    # error.
-                    print "Warning: requiring panda3d version %s, which does not match the current build of Panda, which is version %s." % (package, PandaSystem.getPackageVersionString())
+                print "Warning: requiring panda3d version %s, which does not match the current build of Panda, which is version %s." % (package, PandaSystem.getPackageVersionString())
+            elif package.host != PandaSystem.getPackageHostUrl():
+                print "Warning: requiring panda3d host %s, which does not match the current build of Panda, which is host %s." % (package, PandaSystem.getPackageHostUrl())
 
 
         self.currentPackage.requirePackage(package)
         self.currentPackage.requirePackage(package)
 
 

+ 57 - 10
direct/src/showutil/make_contents.py

@@ -12,17 +12,24 @@ make_contents.py [opts]
 
 
 Options:
 Options:
 
 
-  -d stage_dir
-
-     Specify the staging directory.  This is a temporary directory on
-     the local machine that contains a copy of the web server
-     contents.  The default is the current directory.
+  -i install_dir
+     The full path to a local directory that contains the
+     ready-to-be-published files, as populated by one or more
+     iterations of the ppackage script.  It is the user's
+     responsibility to copy this directory structure to a server.
+
+  -n "host descriptive name"
+     Specifies a descriptive name of the download server that will
+     host these contents.  This name may be presented to the user when
+     managing installed packages.  If this option is omitted, the name
+     is unchanged from the previous pass.
 
 
 """
 """
 
 
 import sys
 import sys
 import getopt
 import getopt
 import os
 import os
+import types
 
 
 try:
 try:
     import hashlib
     import hashlib
@@ -56,6 +63,7 @@ class FileSpec:
 class ContentsMaker:
 class ContentsMaker:
     def __init__(self):
     def __init__(self):
         self.installDir = None
         self.installDir = None
+        self.hostDescriptiveName = None
 
 
     def build(self):
     def build(self):
         if not self.installDir:
         if not self.installDir:
@@ -67,20 +75,56 @@ class ContentsMaker:
         if not self.packages:
         if not self.packages:
             raise ArgumentError, "No packages found."
             raise ArgumentError, "No packages found."
 
 
-        # Now write the contents.xml file.
         contentsFileBasename = 'contents.xml'
         contentsFileBasename = 'contents.xml'
         contentsFilePathname = os.path.join(self.installDir, contentsFileBasename)
         contentsFilePathname = os.path.join(self.installDir, contentsFileBasename)
+        contentsLine = None
+        if self.hostDescriptiveName is not None:
+            if self.hostDescriptiveName:
+                contentsLine = '<contents descriptive_name="%s">' % (
+                    self.quoteString(self.hostDescriptiveName))
+        else:
+            contentsLine = self.readContentsLine(contentsFilePathname)
+        if not contentsLine:
+            contentsLine = '<contents>'
 
 
+        # Now write the contents.xml file.
         f = open(contentsFilePathname, 'w')
         f = open(contentsFilePathname, 'w')
-        print >> f, '<?xml version="1.0" ?>'
+        print >> f, '<?xml version="1.0" encoding="utf-8" ?>'
         print >> f, ''
         print >> f, ''
-        print >> f, '<contents>'
+        print >> f, contentsLine
         for type, packageName, packagePlatform, packageVersion, file in self.packages:
         for type, packageName, packagePlatform, packageVersion, file in self.packages:
             print >> f, '  <%s name="%s" platform="%s" version="%s" %s />' % (
             print >> f, '  <%s name="%s" platform="%s" version="%s" %s />' % (
                 type, packageName, packagePlatform or '', packageVersion, file.getParams())
                 type, packageName, packagePlatform or '', packageVersion, file.getParams())
         print >> f, '</contents>'
         print >> f, '</contents>'
         f.close()
         f.close()
 
 
+    def readContentsLine(self, contentsFilePathname):
+        """ Reads the previous iteration of contents.xml to get the
+        previous top-level contents line, which contains the
+        hostDescriptiveName. """
+
+        try:
+            f = open(contentsFilePathname, 'r')
+        except OSError:
+            return None
+
+        for line in f.readlines():
+            if line.startswith('<contents'):
+                return line.rstrip()
+
+        return None
+
+    def quoteString(self, str):
+        """ Correctly quotes a string for embedding in the xml file. """
+        if isinstance(str, types.UnicodeType):
+            str = str.encode('utf-8')
+        str = str.replace('&', '&amp;')
+        str = str.replace('"', '&quot;')
+        str = str.replace('\'', '&apos;')
+        str = str.replace('<', '&lt;')
+        str = str.replace('>', '&gt;')
+        return str
+    
     def scanDirectory(self):
     def scanDirectory(self):
         """ 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. """
@@ -139,13 +183,16 @@ class ContentsMaker:
         
         
                 
                 
 def makeContents(args):
 def makeContents(args):
-    opts, args = getopt.getopt(args, 'd:h')
+    opts, args = getopt.getopt(args, 'i:n:h')
 
 
     cm = ContentsMaker()
     cm = ContentsMaker()
     cm.installDir = '.'
     cm.installDir = '.'
     for option, value in opts:
     for option, value in opts:
-        if option == '-d':
+        if option == '-i':
             cm.installDir = value
             cm.installDir = value
+
+        elif option == '-n':
+            cm.hostDescriptiveName = value
             
             
         elif option == '-h':
         elif option == '-h':
             print __doc__
             print __doc__

+ 15 - 9
direct/src/showutil/packp3d.py

@@ -119,15 +119,21 @@ def makePackedApp(args):
     packager.installDir = appDir
     packager.installDir = appDir
     getModelPath().appendDirectory(root)
     getModelPath().appendDirectory(root)
 
 
-    packager.setup()
-    packager.beginPackage(appBase, p3dApplication = True)
-    for requireName in requires:
-        packager.require(requireName)
-        
-    packager.dir(root)
-    packager.mainModule(mainModule)
-        
-    packager.endPackage(appBase, p3dApplication = True)
+    try:
+        packager.setup()
+        packager.beginPackage(appBase, p3dApplication = True)
+        for requireName in requires:
+            packager.require(requireName)
+
+        packager.dir(root)
+        packager.mainModule(mainModule)
+
+        packager.endPackage(appBase, p3dApplication = True)
+    except Packager.PackagerError:
+        # Just print the error message and exit gracefully.
+        inst = sys.exc_info()[1]
+        print inst.args[0]
+        sys.exit(1)
 
 
 def main(appRunner):
 def main(appRunner):
     """ This function is called when this module is invoked as
     """ This function is called when this module is invoked as

+ 33 - 6
direct/src/showutil/ppackage.py

@@ -38,12 +38,13 @@ Options:
      ready-to-be-published files into.  This directory structure may
      ready-to-be-published files into.  This directory structure may
      contain multiple different packages from multiple different
      contain multiple different packages from multiple different
      invocations of this script.  It is the user's responsibility to
      invocations of this script.  It is the user's responsibility to
-     copy this directory structure to a web host where it may be
-     downloaded by the client.
+     copy this directory structure to a server, which will have the
+     URL specified by -u, below.
 
 
   -s search_dir
   -s search_dir
      Additional directories to search for previously-built packages.
      Additional directories to search for previously-built packages.
-     This option may be repeated as necessary.
+     This option may be repeated as necessary.  These directories may
+     also be specified with the pdef-path Config.prc variable.
 
 
   -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
@@ -54,6 +55,21 @@ Options:
      empty, patches will not be created for this publish; but the
      empty, patches will not be created for this publish; but the
      directory structure will be populated for the next publish.
      directory structure will be populated for the next publish.
 
 
+  -u host_url
+     Specifies the URL to the download server that will eventually
+     host these packages (that is, the public URL of the install
+     directory).  This may also be overridden with a "host" command
+     appearing within the pdef file.  This is used for packages only;
+     it is ignored for p3d applications, which are not specific to a
+     particular host.
+
+  -n "host descriptive name"
+     Specifies a descriptive name of the download server named by -u.
+     This name may be presented to the user when managing installed
+     packages.  This may also be overridden with a "host" command
+     appearing within the pdef file.  This information is written to
+     the contents.xml file at the top of the install directory.
+
   -p platform
   -p platform
      Specify the platform to masquerade as.  The default is whatever
      Specify the platform to masquerade as.  The default is whatever
      platform Panda has been built for.  It is probably unwise to set
      platform Panda has been built for.  It is probably unwise to set
@@ -82,7 +98,7 @@ def usage(code, msg = ''):
 packager = Packager.Packager()
 packager = Packager.Packager()
 
 
 try:
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 'i:s:d:p:Hh')
+    opts, args = getopt.getopt(sys.argv[1:], 'i:s:d:p:u:n:Hh')
 except getopt.error, msg:
 except getopt.error, msg:
     usage(1, msg)
     usage(1, msg)
 
 
@@ -95,6 +111,10 @@ for opt, arg in opts:
         packager.persistDir = Filename.fromOsSpecific(arg)
         packager.persistDir = Filename.fromOsSpecific(arg)
     elif opt == '-p':
     elif opt == '-p':
         packager.platform = arg
         packager.platform = arg
+    elif opt == '-u':
+        package.host = arg
+    elif opt == '-n':
+        package.hostDescriptiveName = arg
         
         
     elif opt == '-h':
     elif opt == '-h':
         usage(0)
         usage(0)
@@ -117,8 +137,14 @@ if not packager.installDir:
     packager.installDir = Filename('install')
     packager.installDir = Filename('install')
 packager.installSearch.prependDirectory(packager.installDir)
 packager.installSearch.prependDirectory(packager.installDir)
 
 
-packager.setup()
-packages = packager.readPackageDef(packageDef)
+try:
+    packager.setup()
+    packages = packager.readPackageDef(packageDef)
+except Packager.PackagerError:
+    # Just print the error message and exit gracefully.
+    inst = sys.exc_info()[1]
+    print inst.args[0]
+    sys.exit(1)
 
 
 # Look to see if we built any true packages, or if all of them were
 # Look to see if we built any true packages, or if all of them were
 # p3d files.
 # p3d files.
@@ -133,5 +159,6 @@ if anyPackages:
     # the root of the install directory.
     # the root of the install directory.
     cm = make_contents.ContentsMaker()
     cm = make_contents.ContentsMaker()
     cm.installDir = packager.installDir.toOsSpecific()
     cm.installDir = packager.installDir.toOsSpecific()
+    cm.hostDescriptiveName = packager.hostDescriptiveName
     cm.build()
     cm.build()