package dependencies

This commit is contained in:
David Rose 2009-08-13 01:08:06 +00:00
parent d426b9179f
commit 687961b626
14 changed files with 686 additions and 726 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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