wip: host support

This commit is contained in:
David Rose 2009-08-21 15:47:39 +00:00
parent 494d61083e
commit ae8c460b1b
22 changed files with 749 additions and 387 deletions

View File

@ -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 \

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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,10 +1179,11 @@ 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(); if (_panda3d != NULL) {
splash_image_url = inst_mgr->get_download_url(); splash_image_url = _panda3d->get_host()->get_host_url_prefix();
splash_image_url += "coreapi/splash.jpg"; splash_image_url += "coreapi/splash.jpg";
} }
}
if (splash_image_url.empty()) { if (splash_image_url.empty()) {
// No splash image. Never mind. // No splash image. Never mind.

View File

@ -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

View File

@ -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 (!download_url.empty() && !contents_filename.empty()) {
if (!contents_filename.empty()) { // Attempt to pre-read the supplied contents.xml file, to avoid an
if (!read_contents_file(contents_filename)) { // 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. // download host.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
P3DPackage *P3DInstanceManager:: P3DHost *P3DInstanceManager::
get_package(const string &package_name, const string &package_version) { get_host(const string &host_url) {
string package_platform = get_platform(); Hosts::iterator pi = _hosts.find(host_url);
string key = package_name + "_" + package_platform + "_" + package_version; if (pi != _hosts.end()) {
Packages::iterator pi = _packages.find(key);
if (pi != _packages.end()) {
return (*pi).second; return (*pi).second;
} }
P3DPackage *package = P3DHost *host = new P3DHost(this, host_url);
new P3DPackage(package_name, package_platform, package_version); bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second;
bool inserted = _packages.insert(Packages::value_type(key, package)).second;
assert(inserted); assert(inserted);
return package; return host;
}
////////////////////////////////////////////////////////////////////
// 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;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -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 // each session, but we can't call back into the plugin host space
// read thread, since if the host immediately response to a callback // from the read thread, since if the host immediately response to a
// by calling back into the p3d_plugin space, now we have our read // callback by calling back into the p3d_plugin space, now we have
// thread doing stuff in here that's not related to the read thread. // our read thread doing stuff in here that's not related to the
// Even worse, some of the things it might need to do might require // read thread. Even worse, some of the things it might need to do
// a separate read thread to be running! // might require a separate read thread to be running!
_notify_ready.acquire(); _notify_ready.acquire();
while (_notify_thread_continue) { while (_notify_thread_continue) {

View File

@ -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, P3DHost *get_host(const string &host_url);
const string &package_version);
bool get_package_desc_file(FileSpec &desc_file,
const string &package_name,
const string &package_version);
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; typedef map<string, P3DHost *> Hosts;
Packages _packages; Hosts _hosts;
typedef set<string> TempFilenames; typedef set<string> TempFilenames;
TempFilenames _temp_filenames; TempFilenames _temp_filenames;

View File

@ -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

View File

@ -36,21 +36,14 @@ static const double extract_portion = 0.05;
// Description: // Description:
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
P3DPackage:: P3DPackage::
P3DPackage(const string &package_name, P3DPackage(P3DHost *host, const string &package_name,
const string &package_platform,
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; _package_dir = _host->get_host_dir() + string("/packages/") + _package_name;
if (!_package_platform.empty()) {
_package_fullname += string("_") + _package_platform;
_package_dir += string("/") + _package_platform;
}
_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 (_host->has_contents_file()) {
if (inst_mgr->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 (!_host->has_contents_file()) {
if (!success || !_host->read_contents_file(_temp_contents_file->get_filename())) {
if (!inst_mgr->has_contents_file()) {
if (!success || !inst_mgr->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"; string standard_filename = _host->get_host_dir() + "/contents.xml";
if (!inst_mgr->read_contents_file(standard_filename)) { 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) { // The desc file might have a different path on the host server than
nout << "Wrong pathname for desc file: " // it has locally, because we strip out the platform locally.
<< desc_file.get_pathname(root_dir) // Adjust desc_file to point to the local file.
<< " instead of " << _desc_file_pathname << "\n"; 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(); string url = _host->get_host_url_prefix();
url += _package_name; url += url_filename;
if (!_package_platform.empty()) {
url += "/" + _package_platform;
}
url += "/" + _package_version;
url += "/" + _desc_file_basename;
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 = _host->get_host_url_prefix();
string url = inst_mgr->get_download_url();
url += _package_name; url += _package_name;
if (!_package_platform.empty()) { if (!_package_platform.empty()) {
url += "/" + _package_platform; url += "/" + _package_platform;

View File

@ -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 // The core API is responsible for managing these
// on disk, downloading new versions when needed, and // packages on disk, downloading new versions when
// removing stale versions to limit disk space waste. // needed, and removing stale versions to limit disk
// space waste.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
class P3DPackage { class P3DPackage {
public: private:
P3DPackage(const string &package_name, P3DPackage(P3DHost *host,
const string &package_platform, 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"

View File

@ -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;
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////

View File

@ -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"

View File

@ -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 should contact. The contents.xml file that defines this particular
location; as well as the contents.xml file that defines where the "coreapi" package should be found at this location. */
various Panda3D packages will be found. */ #$[]define PANDA_PACKAGE_HOST_URL "$[PANDA_PACKAGE_HOST_URL]"
#$[]define P3D_PLUGIN_DOWNLOAD "$[P3D_PLUGIN_DOWNLOAD]$[if $[notdir $[P3D_PLUGIN_DOWNLOAD]],/]"
/* 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. */

View File

@ -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 += '/';
} }

View File

@ -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 \""

View File

@ -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 # The download URL at which these packages will eventually be
# installed packages. We query it from a config variable # hosted. This may also be changed with the "host" command.
# initially, but we may also be extending it at runtime. 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, if not PandaSystem.getPackageVersionString() or not PandaSystem.getPackageHostUrl():
## # we will be generating a brand new publish with no previous 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.'
## # 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())
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 if host is None and not p3dApplication:
# version number matches what we've been compiled with. # 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 host is None:
host = PandaSystem.getPackageHostUrl()
if version != PandaSystem.getPackageVersionString(): if version != PandaSystem.getPackageVersionString():
message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString()) message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString())
raise PackageError, message 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) break
self.packages[(packageName, platform, version)] = package
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 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,6 +2029,7 @@ 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
if w:
words.append(int(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 # A special case when requiring the "panda3d" package. We
# version number matches what we've been compiled with. # 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 # A special case when requiring the "panda3d" package. We
# version number matches what we've been compiled with. # 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)

View File

@ -12,17 +12,24 @@ make_contents.py [opts]
Options: 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 -n "host descriptive name"
the local machine that contains a copy of the web server Specifies a descriptive name of the download server that will
contents. The default is the current directory. 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,14 +183,17 @@ 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__
sys.exit(1) sys.exit(1)

View File

@ -119,6 +119,7 @@ def makePackedApp(args):
packager.installDir = appDir packager.installDir = appDir
getModelPath().appendDirectory(root) getModelPath().appendDirectory(root)
try:
packager.setup() packager.setup()
packager.beginPackage(appBase, p3dApplication = True) packager.beginPackage(appBase, p3dApplication = True)
for requireName in requires: for requireName in requires:
@ -128,6 +129,11 @@ def makePackedApp(args):
packager.mainModule(mainModule) packager.mainModule(mainModule)
packager.endPackage(appBase, p3dApplication = True) 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

View File

@ -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 copy this directory structure to a server, which will have the
downloaded by the client. 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() try:
packages = packager.readPackageDef(packageDef) 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()