From ae8c460b1b9c6ea5551c145150661a6cafd00bb8 Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 21 Aug 2009 15:47:39 +0000 Subject: [PATCH] wip: host support --- direct/src/plugin/Sources.pp | 2 + direct/src/plugin/fileSpec.I | 11 + direct/src/plugin/fileSpec.h | 1 + direct/src/plugin/p3dHost.I | 73 ++++++ direct/src/plugin/p3dHost.cxx | 265 ++++++++++++++++++++ direct/src/plugin/p3dHost.h | 71 ++++++ direct/src/plugin/p3dInstance.cxx | 13 +- direct/src/plugin/p3dInstanceManager.I | 23 -- direct/src/plugin/p3dInstanceManager.cxx | 232 +++-------------- direct/src/plugin/p3dInstanceManager.h | 23 +- direct/src/plugin/p3dPackage.I | 11 + direct/src/plugin/p3dPackage.cxx | 65 ++--- direct/src/plugin/p3dPackage.h | 20 +- direct/src/plugin/p3dPythonRun.cxx | 2 +- direct/src/plugin/p3d_plugin_composite1.cxx | 1 + direct/src/plugin/p3d_plugin_config.h.pp | 7 +- direct/src/plugin_npapi/ppInstance.cxx | 4 +- direct/src/plugin_standalone/panda3d.cxx | 4 +- direct/src/showutil/Packager.py | 180 ++++++++----- direct/src/showutil/make_contents.py | 65 ++++- direct/src/showutil/packp3d.py | 24 +- direct/src/showutil/ppackage.py | 39 ++- 22 files changed, 749 insertions(+), 387 deletions(-) create mode 100644 direct/src/plugin/p3dHost.I create mode 100644 direct/src/plugin/p3dHost.cxx create mode 100644 direct/src/plugin/p3dHost.h diff --git a/direct/src/plugin/Sources.pp b/direct/src/plugin/Sources.pp index 468c9e194e..209f0f62e5 100644 --- a/direct/src/plugin/Sources.pp +++ b/direct/src/plugin/Sources.pp @@ -32,6 +32,7 @@ p3dFileDownload.h p3dFileDownload.I \ p3dFileParams.h p3dFileParams.I \ p3dFloatObject.h \ + p3dHost.h p3dHost.I \ p3dInstance.h p3dInstance.I \ p3dInstanceManager.h p3dInstanceManager.I \ p3dIntObject.h \ @@ -62,6 +63,7 @@ p3dFileDownload.cxx \ p3dFileParams.cxx \ p3dFloatObject.cxx \ + p3dHost.cxx \ p3dInstance.cxx \ p3dInstanceManager.cxx \ p3dIntObject.cxx \ diff --git a/direct/src/plugin/fileSpec.I b/direct/src/plugin/fileSpec.I index e72c8abb67..9096839d21 100755 --- a/direct/src/plugin/fileSpec.I +++ b/direct/src/plugin/fileSpec.I @@ -24,6 +24,17 @@ get_filename() const { 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 // Access: Private diff --git a/direct/src/plugin/fileSpec.h b/direct/src/plugin/fileSpec.h index a7c4a79410..45bd086b20 100755 --- a/direct/src/plugin/fileSpec.h +++ b/direct/src/plugin/fileSpec.h @@ -32,6 +32,7 @@ public: void load_xml(TiXmlElement *element); inline const string &get_filename() const; + inline void set_filename(const string &filename); inline string get_pathname(const string &package_dir) const; inline size_t get_size() const; diff --git a/direct/src/plugin/p3dHost.I b/direct/src/plugin/p3dHost.I new file mode 100644 index 0000000000..e4d36ec8fc --- /dev/null +++ b/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); +} diff --git a/direct/src/plugin/p3dHost.cxx b/direct/src/plugin/p3dHost.cxx new file mode 100644 index 0000000000..7c791dd48e --- /dev/null +++ b/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; +} diff --git a/direct/src/plugin/p3dHost.h b/direct/src/plugin/p3dHost.h new file mode 100644 index 0000000000..156bb0e863 --- /dev/null +++ b/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 + +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 Packages; + Packages _packages; + + friend class P3DInstanceManager; +}; + +#include "p3dHost.I" + +#endif diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index 2d3fd0c405..fa55ee5a7a 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -884,12 +884,14 @@ scan_app_desc_file(TiXmlDocument *doc) { TiXmlElement *xrequires = xpackage->FirstChildElement("requires"); while (xrequires != NULL) { 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"); if (version == NULL) { 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); } @@ -1177,9 +1179,10 @@ make_splash_window() { if (!_fparams.has_token("splash_img")) { // No specific splash image is specified; get the default splash // 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()) { diff --git a/direct/src/plugin/p3dInstanceManager.I b/direct/src/plugin/p3dInstanceManager.I index 2fa2fa7e2a..886f95f5af 100644 --- a/direct/src/plugin/p3dInstanceManager.I +++ b/direct/src/plugin/p3dInstanceManager.I @@ -36,18 +36,6 @@ get_root_dir() const { 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 // Access: Public @@ -74,17 +62,6 @@ get_log_directory() const { 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 // Access: Public diff --git a/direct/src/plugin/p3dInstanceManager.cxx b/direct/src/plugin/p3dInstanceManager.cxx index 4a97a4a243..74d4f75009 100644 --- a/direct/src/plugin/p3dInstanceManager.cxx +++ b/direct/src/plugin/p3dInstanceManager.cxx @@ -15,7 +15,7 @@ #include "p3dInstanceManager.h" #include "p3dInstance.h" #include "p3dSession.h" -#include "p3dPackage.h" +#include "p3dHost.h" #include "p3d_plugin_config.h" #include "p3dWinSplashWindow.h" #include "p3dUndefinedObject.h" @@ -51,7 +51,6 @@ P3DInstanceManager() { _is_initialized = false; _next_temp_filename_counter = 0; _unique_id = 0; - _xcontents = NULL; _notify_thread_continue = false; _started_notify_thread = false; @@ -103,13 +102,15 @@ P3DInstanceManager:: sigaction(SIGPIPE, &_old_sigpipe, NULL); #endif // _WIN32 - if (_xcontents != NULL) { - delete _xcontents; - } - assert(_instances.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. TempFilenames::iterator ti; for (ti = _temp_filenames.begin(); ti != _temp_filenames.end(); ++ti) { @@ -145,7 +146,7 @@ P3DInstanceManager:: //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::initialize // 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, // false if it should be immediately shut down and // redownloaded. @@ -156,12 +157,7 @@ initialize(const string &contents_filename, const string &download_url, const string &log_basename) { _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; if (_platform.empty()) { _platform = DTOOL_PLATFORM; @@ -197,7 +193,7 @@ initialize(const string &contents_filename, const string &download_url, } delete[] buffer_2; - // Also make sure the directory actually exists. + // And make sure the directory actually exists. mkdir_complete(_temp_directory, nout); #else @@ -216,11 +212,6 @@ initialize(const string &contents_filename, const string &download_url, } #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. if (!_temp_directory.empty() && _temp_directory[_temp_directory.size() - 1] != '/') { #ifdef _WIN32 @@ -251,8 +242,6 @@ initialize(const string &contents_filename, const string &download_url, } nout << "_root_dir = " << _root_dir - << ", contents = " << contents_filename - << ", download = " << _download_url << ", platform = " << _platform << "\n"; @@ -263,9 +252,11 @@ initialize(const string &contents_filename, const string &download_url, _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"; } } @@ -273,42 +264,6 @@ initialize(const string &contents_filename, const string &download_url, 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 // Access: Public @@ -473,67 +428,23 @@ wait_request() { } //////////////////////////////////////////////////////////////////// -// Function: P3DInstanceManager::get_package +// Function: P3DInstanceManager::get_host // Access: Public // Description: Returns a (possibly shared) pointer to the indicated -// package. +// download host. //////////////////////////////////////////////////////////////////// -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()) { +P3DHost *P3DInstanceManager:: +get_host(const string &host_url) { + Hosts::iterator pi = _hosts.find(host_url); + if (pi != _hosts.end()) { 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); - 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 // Access: Private @@ -791,12 +619,12 @@ nt_thread_run() { // The notify thread exists because we need to be able to send // asynchronous notifications of request events. These request // 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(); while (_notify_thread_continue) { diff --git a/direct/src/plugin/p3dInstanceManager.h b/direct/src/plugin/p3dInstanceManager.h index 6d56a9b082..ffb50484ef 100644 --- a/direct/src/plugin/p3dInstanceManager.h +++ b/direct/src/plugin/p3dInstanceManager.h @@ -28,7 +28,7 @@ class P3DInstance; class P3DSession; -class P3DPackage; +class P3DHost; class FileSpec; class TiXmlElement; @@ -52,13 +52,9 @@ public: inline bool is_initialized() 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_log_directory() const; - inline bool has_contents_file() const; - bool read_contents_file(const string &contents_filename); - P3DInstance * create_instance(P3D_request_ready_func *func, const P3D_token tokens[], size_t num_tokens, @@ -74,11 +70,7 @@ public: P3DInstance *check_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; @@ -97,10 +89,6 @@ public: static P3DInstanceManager *get_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: // The notify thread. This thread runs only for the purpose of // generating asynchronous notifications of requests, to callers who @@ -111,15 +99,12 @@ private: private: bool _is_initialized; string _root_dir; - string _download_url; string _platform; string _log_directory; string _log_basename; string _log_pathname; string _temp_directory; - TiXmlElement *_xcontents; - P3D_object *_undefined_object; P3D_object *_none_object; P3D_object *_true_object; @@ -131,8 +116,8 @@ private: typedef map Sessions; Sessions _sessions; - typedef map Packages; - Packages _packages; + typedef map Hosts; + Hosts _hosts; typedef set TempFilenames; TempFilenames _temp_filenames; diff --git a/direct/src/plugin/p3dPackage.I b/direct/src/plugin/p3dPackage.I index 578d273ceb..c7220a5f5c 100755 --- a/direct/src/plugin/p3dPackage.I +++ b/direct/src/plugin/p3dPackage.I @@ -81,6 +81,17 @@ get_failed() const { 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 // Access: Public diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 1383c47187..217a3bc144 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -36,21 +36,14 @@ static const double extract_portion = 0.05; // Description: //////////////////////////////////////////////////////////////////// P3DPackage:: -P3DPackage(const string &package_name, - const string &package_platform, +P3DPackage(P3DHost *host, const string &package_name, const string &package_version) : + _host(host), _package_name(package_name), - _package_platform(package_platform), _package_version(package_version) { - P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); - _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_dir += string("/") + _package_version; @@ -169,16 +162,14 @@ begin_info_download() { //////////////////////////////////////////////////////////////////// void P3DPackage:: 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 // package desc file. download_desc_file(); return; } - string url = inst_mgr->get_download_url(); + string url = _host->get_host_url_prefix(); url += "contents.xml"; // Download contents.xml to a temporary filename first, in case @@ -196,15 +187,13 @@ download_contents_file() { //////////////////////////////////////////////////////////////////// void P3DPackage:: 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"; // 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. report_done(false); delete _temp_contents_file; @@ -225,27 +214,31 @@ contents_file_download_finished(bool success) { //////////////////////////////////////////////////////////////////// // Function: P3DPackage::download_desc_file // 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:: download_desc_file() { - P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); - // Attempt to check the desc file for freshness. If it already // exists, and is consistent with the server contents file, we don't // need to re-download it. - string root_dir = inst_mgr->get_root_dir() + "/packages"; 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 << " 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"; } else { @@ -258,13 +251,8 @@ download_desc_file() { } // 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); } @@ -415,8 +403,7 @@ begin_data_download() { //////////////////////////////////////////////////////////////////// void P3DPackage:: 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; if (!_package_platform.empty()) { url += "/" + _package_platform; diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index ff9917625e..c1d65db8f2 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -20,6 +20,7 @@ #include "fileSpec.h" #include "get_tinyxml.h" +class P3DHost; class P3DInstance; class P3DTemporaryFile; @@ -31,23 +32,26 @@ class P3DTemporaryFile; // runtime, which consists of a bunch of dll's // 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 { -public: - P3DPackage(const string &package_name, - const string &package_platform, +private: + P3DPackage(P3DHost *host, + const string &package_name, const string &package_version); ~P3DPackage(); +public: inline bool get_info_ready() const; inline size_t get_download_size() const; inline void activate_download(); inline bool get_ready() const; inline bool get_failed() const; + inline P3DHost *get_host() const; inline const string &get_package_dir() const; inline const string &get_package_name() const; inline const string &get_package_version() const; @@ -101,6 +105,8 @@ private: bool is_extractable(const string &filename) const; private: + P3DHost *_host; + string _package_name; string _package_version; string _package_platform; @@ -132,6 +138,8 @@ private: friend class Download; friend class P3DMultifileReader; + + friend class P3DHost; }; #include "p3dPackage.I" diff --git a/direct/src/plugin/p3dPythonRun.cxx b/direct/src/plugin/p3dPythonRun.cxx index 009cf3801d..175c31487d 100755 --- a/direct/src/plugin/p3dPythonRun.cxx +++ b/direct/src/plugin/p3dPythonRun.cxx @@ -18,7 +18,7 @@ // There is only one P3DPythonRun object in any given process space. // 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; //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3d_plugin_composite1.cxx b/direct/src/plugin/p3d_plugin_composite1.cxx index fd6c806cab..6ce5801458 100644 --- a/direct/src/plugin/p3d_plugin_composite1.cxx +++ b/direct/src/plugin/p3d_plugin_composite1.cxx @@ -7,6 +7,7 @@ #include "p3dFileDownload.cxx" #include "p3dFileParams.cxx" #include "p3dFloatObject.cxx" +#include "p3dHost.cxx" #include "p3dInstance.cxx" #include "p3dInstanceManager.cxx" #include "p3dIntObject.cxx" diff --git a/direct/src/plugin/p3d_plugin_config.h.pp b/direct/src/plugin/p3d_plugin_config.h.pp index 891f63dc75..1547bdec82 100644 --- a/direct/src/plugin/p3d_plugin_config.h.pp +++ b/direct/src/plugin/p3d_plugin_config.h.pp @@ -6,10 +6,9 @@ /********************************** DO NOT EDIT ****************************/ /* 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. For debugging purposes only. */ diff --git a/direct/src/plugin_npapi/ppInstance.cxx b/direct/src/plugin_npapi/ppInstance.cxx index 97c94bf32a..0f84a95e37 100644 --- a/direct/src/plugin_npapi/ppInstance.cxx +++ b/direct/src/plugin_npapi/ppInstance.cxx @@ -108,7 +108,7 @@ void PPInstance:: begin() { if (!is_plugin_loaded()) { // 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] != '/') { url += '/'; } @@ -837,7 +837,7 @@ get_core_api(TiXmlElement *xplugin) { } else { // 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] != '/') { url += '/'; } diff --git a/direct/src/plugin_standalone/panda3d.cxx b/direct/src/plugin_standalone/panda3d.cxx index 17a9e58b3a..ca66a74a63 100644 --- a/direct/src/plugin_standalone/panda3d.cxx +++ b/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"; bool allow_multiple = false; - string download_url = P3D_PLUGIN_DOWNLOAD; + string download_url = PANDA_PACKAGE_HOST_URL; string this_platform = DTOOL_PLATFORM; bool force_download = false; @@ -752,7 +752,7 @@ usage() { << " -u url\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" << " Specify the platform to masquerade as. The default is \"" diff --git a/direct/src/showutil/Packager.py b/direct/src/showutil/Packager.py index d840a646fb..e96d64c87f 100644 --- a/direct/src/showutil/Packager.py +++ b/direct/src/showutil/Packager.py @@ -133,8 +133,9 @@ class Packager: def __init__(self, packageName, packager): self.packageName = packageName self.packager = packager - self.version = None self.platform = None + self.version = None + self.host = None self.p3dApplication = False self.compressionLevel = 0 self.importedMapsDir = 'imported_maps' @@ -672,6 +673,7 @@ class Packager: xrequires.SetAttribute('name', package.packageName) if package.version: xrequires.SetAttribute('version', package.version) + xrequires.SetAttribute('host', package.host) xpackage.InsertEndChild(xrequires) doc.InsertEndChild(xpackage) @@ -699,6 +701,9 @@ class Packager: raise PackagerError, message def writeDescFile(self): + """ Makes the package.xml file that describes the package + and its contents, for download. """ + packageDescFullpath = Filename(self.packager.installDir, self.packageDesc) doc = TiXmlDocument(packageDescFullpath.toOsSpecific()) decl = TiXmlDeclaration("1.0", "utf-8", "") @@ -724,6 +729,7 @@ class Packager: xrequires.SetAttribute('platform', package.platform) if package.version: xrequires.SetAttribute('version', package.version) + xrequires.SetAttribute('host', package.host) xpackage.InsertEndChild(xrequires) xuncompressedArchive = self.getFileSpec( @@ -743,6 +749,10 @@ class Packager: doc.SaveFile() 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) doc = TiXmlDocument(packageImportDescFullpath.toOsSpecific()) decl = TiXmlDeclaration("1.0", "utf-8", "") @@ -754,6 +764,7 @@ class Packager: xpackage.SetAttribute('platform', self.platform) if self.version: xpackage.SetAttribute('version', self.version) + xpackage.SetAttribute('host', self.host) for package in self.requires: xrequires = TiXmlElement('requires') @@ -762,6 +773,7 @@ class Packager: xrequires.SetAttribute('platform', package.platform) if package.version: xrequires.SetAttribute('version', package.version) + xrequires.SetAttribute('host', package.host) xpackage.InsertEndChild(xrequires) self.components.sort() @@ -785,6 +797,7 @@ class Packager: self.packageName = xpackage.Attribute('name') self.platform = xpackage.Attribute('platform') self.version = xpackage.Attribute('version') + self.host = xpackage.Attribute('host') self.requires = [] xrequires = xpackage.FirstChildElement('requires') @@ -792,8 +805,11 @@ class Packager: packageName = xrequires.Attribute('name') platform = xrequires.Attribute('platform') version = xrequires.Attribute('version') + host = xrequires.Attribute('host') 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: self.requires.append(package) xrequires = xrequires.NextSiblingElement('requires') @@ -1012,9 +1028,12 @@ class Packager: self.installDir = 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') # The system PATH, for searching dll's and exe's. @@ -1195,23 +1214,8 @@ class Packager: # We must have an actual install directory. 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): """ Given that line[p] is a dollar sign beginning a variable @@ -1441,6 +1445,32 @@ class Packager: value = ExecutionEnvironment.expandString(value.strip()) 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): """ model_path directory @@ -1469,10 +1499,10 @@ class Packager: 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: command, packageName = words @@ -1480,8 +1510,10 @@ class Packager: raise ArgumentNumber 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): """ @@ -1539,10 +1571,10 @@ class Packager: 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: command, packageName = words @@ -1550,7 +1582,8 @@ class Packager: raise ArgumentError 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): """ @@ -1798,7 +1831,8 @@ class Packager: 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 basename of the package. Follow this with a number of calls to file() etc., and close the package with endPackage(). """ @@ -1806,20 +1840,33 @@ class Packager: if self.currentPackage: 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 version is None: 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) self.currentPackage = package package.version = version + package.host = host package.p3dApplication = p3dApplication if package.p3dApplication: @@ -1855,7 +1902,7 @@ class Packager: self.currentPackage = None def findPackage(self, packageName, platform = None, version = None, - requires = None): + host = None, requires = None): """ Searches for the named package from a previous publish operation along the install search path. @@ -1872,25 +1919,32 @@ class Packager: platform = self.platform # 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: return package # Look on the searchlist. 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: - package = self.__scanPackageDir(dirname, packageName, None, version, requires = requires) + package = self.__scanPackageDir(dirname, packageName, None, version, host, requires = requires) 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 def __scanPackageDir(self, rootDir, packageName, platform, version, - requires = None): + host, requires = None): """ Scans a directory on disk, looking for *_import.xml files that match the indicated packageName and optional version. If a suitable xml file is found, reads it and returns the assocated @@ -1917,6 +1971,9 @@ class Packager: packageDir = Filename(packageDir, '*') 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' filename = Filename(packageDir, basename) filelist = glob.glob(filename.toOsSpecific()) @@ -1934,6 +1991,10 @@ class Packager: return None + def __findPackageOnHost(self, packageName, platform, version, host, requires = None): + # TODO. + return None + def __sortPackageImportFilelist(self, filelist): """ Given a list of *_import.xml filenames, sorts them in 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: w += version[p] p += 1 - words.append(int(w)) + if w: + words.append(int(w)) return tuple(words) @@ -2024,7 +2086,7 @@ class Packager: 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 a name. @@ -2035,13 +2097,17 @@ class Packager: if not self.currentPackage: 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 version is None: 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: message = 'Unknown package %s, version "%s"' % (packageName, version) raise PackagerError, message @@ -2059,20 +2125,14 @@ class Packager: if not self.currentPackage: 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.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) diff --git a/direct/src/showutil/make_contents.py b/direct/src/showutil/make_contents.py index 19791c3fe7..c46d085ff3 100755 --- a/direct/src/showutil/make_contents.py +++ b/direct/src/showutil/make_contents.py @@ -12,17 +12,24 @@ make_contents.py [opts] Options: - -d stage_dir + -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. - 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. + -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 getopt import os +import types try: import hashlib @@ -56,6 +63,7 @@ class FileSpec: class ContentsMaker: def __init__(self): self.installDir = None + self.hostDescriptiveName = None def build(self): if not self.installDir: @@ -67,20 +75,56 @@ class ContentsMaker: if not self.packages: raise ArgumentError, "No packages found." - # Now write the contents.xml file. contentsFileBasename = 'contents.xml' contentsFilePathname = os.path.join(self.installDir, contentsFileBasename) + contentsLine = None + if self.hostDescriptiveName is not None: + if self.hostDescriptiveName: + contentsLine = '' % ( + self.quoteString(self.hostDescriptiveName)) + else: + contentsLine = self.readContentsLine(contentsFilePathname) + if not contentsLine: + contentsLine = '' + # Now write the contents.xml file. f = open(contentsFilePathname, 'w') - print >> f, '' + print >> f, '' print >> f, '' - print >> f, '' + print >> f, contentsLine for type, packageName, packagePlatform, packageVersion, file in self.packages: print >> f, ' <%s name="%s" platform="%s" version="%s" %s />' % ( type, packageName, packagePlatform or '', packageVersion, file.getParams()) print >> f, '' 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('', '>') + return str + def scanDirectory(self): """ Walks through all the files in the stage directory and looks for the package directory xml files. """ @@ -139,13 +183,16 @@ class ContentsMaker: def makeContents(args): - opts, args = getopt.getopt(args, 'd:h') + opts, args = getopt.getopt(args, 'i:n:h') cm = ContentsMaker() cm.installDir = '.' for option, value in opts: - if option == '-d': + if option == '-i': cm.installDir = value + + elif option == '-n': + cm.hostDescriptiveName = value elif option == '-h': print __doc__ diff --git a/direct/src/showutil/packp3d.py b/direct/src/showutil/packp3d.py index e48694a3e8..0342f17ecb 100755 --- a/direct/src/showutil/packp3d.py +++ b/direct/src/showutil/packp3d.py @@ -119,15 +119,21 @@ def makePackedApp(args): packager.installDir = appDir 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): """ This function is called when this module is invoked as diff --git a/direct/src/showutil/ppackage.py b/direct/src/showutil/ppackage.py index 540ded12bc..4419548146 100755 --- a/direct/src/showutil/ppackage.py +++ b/direct/src/showutil/ppackage.py @@ -38,12 +38,13 @@ Options: ready-to-be-published files into. This directory structure may contain multiple different packages from multiple different 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 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 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 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 Specify the platform to masquerade as. The default is whatever platform Panda has been built for. It is probably unwise to set @@ -82,7 +98,7 @@ def usage(code, msg = ''): packager = Packager.Packager() 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: usage(1, msg) @@ -95,6 +111,10 @@ for opt, arg in opts: packager.persistDir = Filename.fromOsSpecific(arg) elif opt == '-p': packager.platform = arg + elif opt == '-u': + package.host = arg + elif opt == '-n': + package.hostDescriptiveName = arg elif opt == '-h': usage(0) @@ -117,8 +137,14 @@ if not packager.installDir: packager.installDir = Filename('install') 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 # p3d files. @@ -133,5 +159,6 @@ if anyPackages: # the root of the install directory. cm = make_contents.ContentsMaker() cm.installDir = packager.installDir.toOsSpecific() + cm.hostDescriptiveName = packager.hostDescriptiveName cm.build()