From 687961b626dde681969d9f9c2c045459b5aace2e Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 13 Aug 2009 01:08:06 +0000 Subject: [PATCH] package dependencies --- direct/src/plugin/p3dInstance.cxx | 135 ++++++- direct/src/plugin/p3dInstance.h | 13 +- direct/src/plugin/p3dInstanceManager.cxx | 7 +- direct/src/plugin/p3dInstanceManager.h | 3 +- direct/src/plugin/p3dMultifileReader.cxx | 196 ++++++---- direct/src/plugin/p3dMultifileReader.h | 14 +- direct/src/plugin/p3dPackage.cxx | 137 +++---- direct/src/plugin/p3dPackage.h | 22 +- direct/src/plugin/p3dSession.cxx | 120 ++---- direct/src/plugin/p3dSession.h | 20 +- direct/src/showutil/Packager.py | 317 +++++++++++----- .../src/{plugin => showutil}/make_contents.py | 55 ++- direct/src/showutil/packp3d.py | 356 +++--------------- direct/src/showutil/ppackage.py | 17 +- 14 files changed, 686 insertions(+), 726 deletions(-) rename direct/src/{plugin => showutil}/make_contents.py (73%) diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index 39cbfb1e68..6eaadd0c53 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -24,6 +24,7 @@ #include "p3dObject.h" #include "p3dToplevelObject.h" #include "p3dUndefinedObject.h" +#include "p3dMultifileReader.h" #include #include @@ -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); + } + } } } diff --git a/direct/src/plugin/p3dInstance.h b/direct/src/plugin/p3dInstance.h index 6b53377ae2..4efccaed29 100644 --- a/direct/src/plugin/p3dInstance.h +++ b/direct/src/plugin/p3dInstance.h @@ -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 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 Downloads; Downloads _downloads; @@ -175,6 +183,7 @@ private: friend class P3DSession; friend class SplashDownload; friend class P3DWindowParams; + friend class P3DPackage; }; #include "p3dInstance.I" diff --git a/direct/src/plugin/p3dInstanceManager.cxx b/direct/src/plugin/p3dInstanceManager.cxx index 8b4bfb0eb9..e7aa854a71 100644 --- a/direct/src/plugin/p3dInstanceManager.cxx +++ b/direct/src/plugin/p3dInstanceManager.cxx @@ -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); diff --git a/direct/src/plugin/p3dInstanceManager.h b/direct/src/plugin/p3dInstanceManager.h index 03411dea70..d8699e9b1b 100644 --- a/direct/src/plugin/p3dInstanceManager.h +++ b/direct/src/plugin/p3dInstanceManager.h @@ -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); diff --git a/direct/src/plugin/p3dMultifileReader.cxx b/direct/src/plugin/p3dMultifileReader.cxx index 828c65d4e1..5b9eef39ea 100644 --- a/direct/src/plugin/p3dMultifileReader.cxx +++ b/direct/src/plugin/p3dMultifileReader.cxx @@ -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; +} + diff --git a/direct/src/plugin/p3dMultifileReader.h b/direct/src/plugin/p3dMultifileReader.h index e1fdd7ab6a..bb7dd13111 100644 --- a/direct/src/plugin/p3dMultifileReader.h +++ b/direct/src/plugin/p3dMultifileReader.h @@ -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(); diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index c487785102..f5d2c4d819 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -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; } //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index b394e5248a..18d7c56931 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -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 Callbacks; - Callbacks _callbacks; - typedef vector Instances; Instances _instances; FileSpec _compressed_archive; FileSpec _uncompressed_archive; - typedef vector Components; - Components _components; + typedef vector Extracts; + Extracts _extracts; friend class Download; friend class P3DMultifileReader; diff --git a/direct/src/plugin/p3dSession.cxx b/direct/src/plugin/p3dSession.cxx index f19a6d9425..cae2e475be 100644 --- a/direct/src/plugin/p3dSession.cxx +++ b/direct/src/plugin/p3dSession.cxx @@ -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"; - } -} diff --git a/direct/src/plugin/p3dSession.h b/direct/src/plugin/p3dSession.h index 6277229215..dbe3f1d0cb 100644 --- a/direct/src/plugin/p3dSession.h +++ b/direct/src/plugin/p3dSession.h @@ -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 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" diff --git a/direct/src/showutil/Packager.py b/direct/src/showutil/Packager.py index eab13475af..0fd0da0b8b 100644 --- a/direct/src/showutil/Packager.py +++ b/direct/src/showutil/Packager.py @@ -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)) diff --git a/direct/src/plugin/make_contents.py b/direct/src/showutil/make_contents.py similarity index 73% rename from direct/src/plugin/make_contents.py rename to direct/src/showutil/make_contents.py index a4ce5d88cf..363f277349 100755 --- a/direct/src/plugin/make_contents.py +++ b/direct/src/showutil/make_contents.py @@ -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, '' print >> f, '' print >> f, '' @@ -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) diff --git a/direct/src/showutil/packp3d.py b/direct/src/showutil/packp3d.py index b7ff3c67a6..96e99bba0c 100755 --- a/direct/src/showutil/packp3d.py +++ b/direct/src/showutil/packp3d.py @@ -2,11 +2,14 @@ """ -This module will pack a Panda application, consisting of a directory +This command will pack a Panda application, consisting of a directory tree of .py files and models, into a p3d file for convenient 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: diff --git a/direct/src/showutil/ppackage.py b/direct/src/showutil/ppackage.py index 42f28c0bdc..5d28db9e19 100755 --- a/direct/src/showutil/ppackage.py +++ b/direct/src/showutil/ppackage.py @@ -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() +