From 083e7cbe78e8d7d08f58c8494e7e89a762fa0089 Mon Sep 17 00:00:00 2001 From: David Rose Date: Sun, 27 Sep 2009 15:47:43 +0000 Subject: [PATCH] robustify download on the C++ side too --- direct/src/p3d/PackageInfo.py | 10 +- direct/src/plugin/fileSpec.cxx | 37 +- direct/src/plugin/fileSpec.h | 1 + direct/src/plugin/p3dDownload.I | 10 + direct/src/plugin/p3dDownload.h | 1 + direct/src/plugin/p3dHost.I | 46 +- direct/src/plugin/p3dHost.cxx | 20 +- direct/src/plugin/p3dHost.h | 9 +- direct/src/plugin/p3dInstance.cxx | 1 + direct/src/plugin/p3dMultifileReader.cxx | 23 +- direct/src/plugin/p3dMultifileReader.h | 8 +- direct/src/plugin/p3dPackage.I | 46 ++ direct/src/plugin/p3dPackage.cxx | 831 +++++++++++++++-------- direct/src/plugin/p3dPackage.h | 103 ++- 14 files changed, 835 insertions(+), 311 deletions(-) diff --git a/direct/src/p3d/PackageInfo.py b/direct/src/p3d/PackageInfo.py index e51f0a5733..c3aa09e26c 100644 --- a/direct/src/p3d/PackageInfo.py +++ b/direct/src/p3d/PackageInfo.py @@ -86,7 +86,7 @@ class PackageInfo: self.downloadProgress = 0 # This is set true when the package file has been fully - # downloaded and unpackaged. + # downloaded and unpacked. self.hasPackage = False # This is set true when the package has been "installed", @@ -270,10 +270,10 @@ class PackageInfo: return True # Still have to download it. - self.__buildInstallPlan() + self.__buildInstallPlans() return True - def __buildInstallPlan(self): + def __buildInstallPlans(self): """ Sets up self.installPlans, a list of one or more "plans" to download and install the package. """ @@ -297,8 +297,8 @@ class PackageInfo: # download, and build a plan (or two) to download it all. self.installPlans = None - # We know we will at least need to unpackage the archive at - # the end. + # We know we will at least need to unpack the archive contents + # at the end. unpackSize = 0 for file in self.extracts: unpackSize += file.size diff --git a/direct/src/plugin/fileSpec.cxx b/direct/src/plugin/fileSpec.cxx index 9542d0d012..7690428246 100755 --- a/direct/src/plugin/fileSpec.cxx +++ b/direct/src/plugin/fileSpec.cxx @@ -229,7 +229,7 @@ check_hash(const string &pathname) const { MD5_CTX ctx; MD5_Init(&ctx); - static const int buffer_size = 1024; + static const int buffer_size = 4096; char buffer[buffer_size]; stream.read(buffer, buffer_size); @@ -245,6 +245,41 @@ check_hash(const string &pathname) const { return (memcmp(md, _hash, hash_size) == 0); } +//////////////////////////////////////////////////////////////////// +// Function: FileSpec::read_hash +// Access: Public +// Description: Computes the hash from the indicated pathname and +// stores it within the FileSpec. +//////////////////////////////////////////////////////////////////// +bool FileSpec:: +read_hash(const string &pathname) { + memset(_hash, 0, sizeof(_hash)); + + ifstream stream(pathname.c_str(), ios::in | ios::binary); + if (!stream) { + //cerr << "unable to read " << pathname << "\n"; + return false; + } + + MD5_CTX ctx; + MD5_Init(&ctx); + + static const int buffer_size = 4096; + char buffer[buffer_size]; + + stream.read(buffer, buffer_size); + size_t count = stream.gcount(); + while (count != 0) { + MD5_Update(&ctx, buffer, count); + stream.read(buffer, buffer_size); + count = stream.gcount(); + } + + MD5_Final(_hash, &ctx); + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: FileSpec::decode_hex // Access: Private, Static diff --git a/direct/src/plugin/fileSpec.h b/direct/src/plugin/fileSpec.h index 8656c8e736..e16569d23b 100755 --- a/direct/src/plugin/fileSpec.h +++ b/direct/src/plugin/fileSpec.h @@ -42,6 +42,7 @@ public: bool full_verify(const string &package_dir) const; bool check_hash(const string &pathname) const; + bool read_hash(const string &pathname); private: static inline int decode_hexdigit(char c); diff --git a/direct/src/plugin/p3dDownload.I b/direct/src/plugin/p3dDownload.I index 05dcb55751..59c4bbf747 100755 --- a/direct/src/plugin/p3dDownload.I +++ b/direct/src/plugin/p3dDownload.I @@ -39,6 +39,16 @@ get_download_progress() const { return (double)_total_data / (double)_total_expected_data; } +//////////////////////////////////////////////////////////////////// +// Function: P3DDownload::get_total_data +// Access: Public +// Description: Returns the total number of bytes downloaded so far. +//////////////////////////////////////////////////////////////////// +inline size_t P3DDownload:: +get_total_data() const { + return _total_data; +} + //////////////////////////////////////////////////////////////////// // Function: P3DDownload::get_download_finished // Access: Public diff --git a/direct/src/plugin/p3dDownload.h b/direct/src/plugin/p3dDownload.h index 8393197eab..5062ea09a0 100755 --- a/direct/src/plugin/p3dDownload.h +++ b/direct/src/plugin/p3dDownload.h @@ -40,6 +40,7 @@ public: inline double get_download_progress() const; inline bool get_download_finished() const; inline bool get_download_success() const; + inline size_t get_total_data() const; void cancel(); diff --git a/direct/src/plugin/p3dHost.I b/direct/src/plugin/p3dHost.I index e4d36ec8fc..c2d4caf61e 100644 --- a/direct/src/plugin/p3dHost.I +++ b/direct/src/plugin/p3dHost.I @@ -40,15 +40,31 @@ get_host_url() const { // Function: P3DHost::get_host_url_prefix // Access: Public // Description: Returns the root URL of this host, for constructing -// full URL sequences. This is the same as -// get_host_url(), except it is guaranteed to end in a -// slash character. +// the URL to download contents.xml only. This is the +// same as get_host_url(), except it is guaranteed to +// end in a slash character. +// +// Also see get_download_url_prefix(). //////////////////////////////////////////////////////////////////// inline const string &P3DHost:: get_host_url_prefix() const { return _host_url_prefix; } +//////////////////////////////////////////////////////////////////// +// Function: P3DHost::get_download_url_prefix +// Access: Public +// Description: Returns the root URL of this host, for downloading +// everything other than the contents.xml file. This is +// often the same as get_host_url_prefix(), but it may +// be different in the case of an https server for +// contents.xml. +//////////////////////////////////////////////////////////////////// +inline const string &P3DHost:: +get_download_url_prefix() const { + return _download_url_prefix; +} + //////////////////////////////////////////////////////////////////// // Function: P3DHost::get_descriptive_name // Access: Public @@ -71,3 +87,27 @@ inline bool P3DHost:: has_contents_file() const { return (_xcontents != NULL); } + +//////////////////////////////////////////////////////////////////// +// Function: P3DHost::get_contents_seq +// Access: Public +// Description: Returns a number that increments whenever a new +// version of the contents.xml file has been read. This +// can be used by packages to determine whether they +// need to redownload from scratch. +//////////////////////////////////////////////////////////////////// +inline int P3DHost:: +get_contents_seq() const { + return _contents_seq; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DHost::check_contents_hash +// Access: Public +// Description: Returns true if the indicated pathname has the same +// md5 hash as the contents.xml file, false otherwise. +//////////////////////////////////////////////////////////////////// +inline bool P3DHost:: +check_contents_hash(const string &pathname) const { + return _contents_spec.check_hash(pathname); +} diff --git a/direct/src/plugin/p3dHost.cxx b/direct/src/plugin/p3dHost.cxx index 8774a1b678..565d979ea1 100644 --- a/direct/src/plugin/p3dHost.cxx +++ b/direct/src/plugin/p3dHost.cxx @@ -34,8 +34,10 @@ P3DHost(const string &host_url) : if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.size() - 1] != '/') { _host_url_prefix += "/"; } + _download_url_prefix = _host_url_prefix; _xcontents = NULL; + _contents_seq = 0; determine_host_dir(); } @@ -137,6 +139,8 @@ read_contents_file(const string &contents_filename) { delete _xcontents; } _xcontents = (TiXmlElement *)xcontents->Clone(); + ++_contents_seq; + _contents_spec.read_hash(contents_filename); TiXmlElement *xhost = _xcontents->FirstChildElement("host"); if (xhost != NULL) { @@ -463,9 +467,23 @@ determine_host_dir() { void P3DHost:: read_xhost(TiXmlElement *xhost) { const char *descriptive_name = xhost->Attribute("descriptive_name"); - if (descriptive_name != NULL) { + if (descriptive_name != NULL && _descriptive_name.empty()) { _descriptive_name = descriptive_name; } + + // Get the "download" URL, which is the source from which we + // download everything other than the contents.xml file. + const char *download_url = xhost->Attribute("download_url"); + if (download_url != NULL) { + _download_url_prefix = download_url; + } + if (!_download_url_prefix.empty()) { + if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') { + _download_url_prefix += "/"; + } + } else { + _download_url_prefix = _host_url_prefix; + } TiXmlElement *xmirror = xhost->FirstChildElement("mirror"); while (xmirror != NULL) { diff --git a/direct/src/plugin/p3dHost.h b/direct/src/plugin/p3dHost.h index 90e9aa3cb5..3d4f98db22 100644 --- a/direct/src/plugin/p3dHost.h +++ b/direct/src/plugin/p3dHost.h @@ -16,7 +16,7 @@ #define P3DHOST_H #include "p3d_plugin_common.h" - +#include "fileSpec.h" #include class FileSpec; @@ -37,11 +37,15 @@ public: inline const string &get_host_dir() const; inline const string &get_host_url() const; inline const string &get_host_url_prefix() const; + inline const string &get_download_url_prefix() const; inline const string &get_descriptive_name() const; P3DHost *get_alt_host(const string &alt_host); inline bool has_contents_file() const; + inline int get_contents_seq() const; + inline bool check_contents_hash(const string &pathname) const; + bool read_contents_file(); bool read_contents_file(const string &contents_filename); @@ -70,8 +74,11 @@ private: string _host_dir; string _host_url; string _host_url_prefix; + string _download_url_prefix; string _descriptive_name; TiXmlElement *_xcontents; + int _contents_seq; + FileSpec _contents_spec; typedef vector Mirrors; Mirrors _mirrors; diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index 860a952791..d09f714dcc 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -844,6 +844,7 @@ get_packages_failed() const { void P3DInstance:: start_download(P3DDownload *download) { assert(download->get_download_id() == 0); + assert(!download->get_url().empty()); P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); diff --git a/direct/src/plugin/p3dMultifileReader.cxx b/direct/src/plugin/p3dMultifileReader.cxx index caf96a5ab3..826d813833 100644 --- a/direct/src/plugin/p3dMultifileReader.cxx +++ b/direct/src/plugin/p3dMultifileReader.cxx @@ -87,14 +87,12 @@ close() { // directory. Returns true on success, false on // failure. // -// The parameters package, start_progress, and -// progress_size are provided to make the appropriate -// status updates on the package's progress callbacks -// during this operation. +// Upates the "step" object with the progress through +// this operation. //////////////////////////////////////////////////////////////////// bool P3DMultifileReader:: -extract_all(const string &to_dir, - P3DPackage *package, double start_progress, double progress_size) { +extract_all(const string &to_dir, P3DPackage *package, + P3DPackage::InstallStep *step) { assert(_is_open); if (_in.fail()) { return false; @@ -102,12 +100,6 @@ extract_all(const string &to_dir, // 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); @@ -141,10 +133,9 @@ extract_all(const string &to_dir, // program or something. chmod(output_pathname.c_str(), 0555); - ++num_processed; - if (package != NULL) { - double progress = (double)num_processed / (double)num_expected; - package->report_progress(start_progress + progress * progress_size); + if (step != NULL && package != NULL) { + step->_bytes_done += s._data_length; + step->report_step_progress(); } } diff --git a/direct/src/plugin/p3dMultifileReader.h b/direct/src/plugin/p3dMultifileReader.h index c90a2ca099..a4ba0d3711 100644 --- a/direct/src/plugin/p3dMultifileReader.h +++ b/direct/src/plugin/p3dMultifileReader.h @@ -17,8 +17,7 @@ #include "p3d_plugin_common.h" #include "p3dInstanceManager.h" // for openssl - -class P3DPackage; +#include "p3dPackage.h" //////////////////////////////////////////////////////////////////// // Class : P3DMultifileReader @@ -35,9 +34,8 @@ public: inline bool is_open() const; void close(); - bool extract_all(const string &to_dir, - P3DPackage *package, double start_progress, - double progress_size); + bool extract_all(const string &to_dir, P3DPackage *package, + P3DPackage::InstallStep *step); bool extract_one(ostream &out, const string &filename); diff --git a/direct/src/plugin/p3dPackage.I b/direct/src/plugin/p3dPackage.I index a113e9c480..04c8948c65 100755 --- a/direct/src/plugin/p3dPackage.I +++ b/direct/src/plugin/p3dPackage.I @@ -144,6 +144,18 @@ get_desc_file_pathname() const { return _desc_file_pathname; } +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::get_desc_file_dirname +// Access: Public +// Description: Returns the relative path, on the host, of the +// directory that contains the desc file (and to which +// all of the paths in the desc file are relative). +//////////////////////////////////////////////////////////////////// +inline const string &P3DPackage:: +get_desc_file_dirname() const { + return _desc_file_dirname; +} + //////////////////////////////////////////////////////////////////// // Function: P3DPackage::get_archive_file_pathname // Access: Public @@ -156,3 +168,37 @@ get_archive_file_pathname() const { return _uncompressed_archive.get_pathname(_package_dir); } + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStep::get_effort +// Access: Public +// Description: Returns the relative amount of effort of this step. +//////////////////////////////////////////////////////////////////// +inline double P3DPackage::InstallStep:: +get_effort() const { + return _bytes_needed * _bytes_factor; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStep::get_progress +// Access: Public +// Description: Returns the progress of this step, in the range 0..1. +//////////////////////////////////////////////////////////////////// +inline double P3DPackage::InstallStep:: +get_progress() const { + if (_bytes_needed == 0) { + return 1.0; + } + return min((double)_bytes_done / (double)_bytes_needed, 1.0); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStep::report_step_progress +// Access: Public +// Description: Notifies the Package that progress has been made on +// this particular step. +//////////////////////////////////////////////////////////////////// +inline void P3DPackage::InstallStep:: +report_step_progress() { + _package->report_progress(this); +} diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 87e3d98233..944b6cc12b 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -28,11 +28,12 @@ #include // chmod() #endif -// The relative breakdown of the full install process. Each phase is -// worth this fraction of the total movement of the progress bar. -static const double download_portion = 0.9; -static const double uncompress_portion = 0.05; -static const double extract_portion = 0.05; +// Weight factors for computing download progress. This attempts to +// reflect the relative time-per-byte of each of these operations. +const double P3DPackage::_download_factor = 1.0; +const double P3DPackage::_uncompress_factor = 0.01; +const double P3DPackage::_unpack_factor = 0.01; +const double P3DPackage::_patch_factor = 0.01; //////////////////////////////////////////////////////////////////// // Function: P3DPackage::Constructor @@ -57,6 +58,8 @@ P3DPackage(P3DHost *host, const string &package_name, // file, instead of an xml file and a multifile to unpack. _package_solo = false; + _host_contents_seq = 0; + _xconfig = NULL; _temp_contents_file = NULL; @@ -66,6 +69,7 @@ P3DPackage(P3DHost *host, const string &package_name, _ready = false; _failed = false; _active_download = NULL; + _saved_download = NULL; } //////////////////////////////////////////////////////////////////// @@ -89,6 +93,11 @@ P3DPackage:: delete _active_download; _active_download = NULL; } + if (_saved_download != NULL) { + _saved_download->cancel(); + delete _saved_download; + _saved_download = NULL; + } if (_temp_contents_file != NULL) { delete _temp_contents_file; @@ -125,7 +134,7 @@ activate_download() { // Otherwise, if we've already got the desc file, then start the // download. if (_info_ready) { - begin_data_download(); + follow_install_plans(true); } } } @@ -254,8 +263,9 @@ begin_info_download() { // Function: P3DPackage::download_contents_file // Access: Private // Description: Starts downloading the root-level contents.xml file. -// This is only done for the first package, and only if -// the host doesn't have the file already. +// This is only done for the first package downloaded +// from a particular host, and only if the host doesn't +// have the file already. //////////////////////////////////////////////////////////////////// void P3DPackage:: download_contents_file() { @@ -273,15 +283,6 @@ download_contents_file() { return; } - // Get the URL for contents.xml. - ostringstream strm; - strm << "contents.xml"; - // Append a uniquifying query string to the URL to force the - // download to go all the way through any caches. We use the time - // in seconds; that's unique enough. - strm << "?" << time(NULL); - string urlbase = strm.str(); - // Download contents.xml to a temporary filename first, in case // multiple packages are downloading it simultaneously. if (_temp_contents_file != NULL) { @@ -290,8 +291,8 @@ download_contents_file() { } _temp_contents_file = new P3DTemporaryFile(".xml"); - start_download(DT_contents_file, urlbase, _temp_contents_file->get_filename(), - FileSpec()); + start_download(DT_contents_file, "contents.xml", + _temp_contents_file->get_filename(), FileSpec()); } //////////////////////////////////////////////////////////////////// @@ -326,11 +327,108 @@ contents_file_download_finished(bool success) { host_got_contents_file(); } +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::redownload_contents_file +// Access: Private +// Description: Starts a new download attempt of contents.xml, to +// check to see whether our local copy is stale. This +// is called only from Download::download_finished(). +// +// If it turns out a new version can be downloaded, the +// indicated Download object (and the current install +// plan) is discarded, and the package download is +// restarted from the beginning. +// +// If there is no new version available, calls +// resume_download_finished() on the indicated Download +// object, to carry on as if nothing had happened. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +redownload_contents_file(P3DPackage::Download *download) { + assert(_active_download == NULL); + assert(_saved_download == NULL); + + if (_host->get_contents_seq() != _host_contents_seq) { + // If the contents_seq number has changed, we don't even need to + // download anything--just go restart the download. + host_got_contents_file(); + return; + } + + _saved_download = download; + _saved_download->ref(); + + // Download contents.xml to a temporary filename first. + if (_temp_contents_file != NULL) { + delete _temp_contents_file; + _temp_contents_file = NULL; + } + _temp_contents_file = new P3DTemporaryFile(".xml"); + + start_download(DT_redownload_contents_file, "contents.xml", + _temp_contents_file->get_filename(), FileSpec()); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::contents_file_redownload_finished +// Access: Private +// Description: Called when the redownload attempt on contents.xml +// has finished. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +contents_file_redownload_finished(bool success) { + bool contents_changed = false; + + if (_host->get_contents_seq() != _host_contents_seq) { + // If the contents_seq number has changed, we don't even need to + // bother reading what we just downloaded. + contents_changed = true; + } + + if (!contents_changed && success) { + // If we successfully downloaded something, see if it's different + // from what we had before. + if (!_host->check_contents_hash(_temp_contents_file->get_filename())) { + // It changed! Now see if we can read the new contents. + if (!_host->read_contents_file(_temp_contents_file->get_filename())) { + // Huh, appears to have changed to something bad. Never mind. + nout << "Couldn't read " << *_temp_contents_file << "\n"; + + } else { + // The new contents file is read and in place. + contents_changed = true; + } + } + } + + // We no longer need the temporary file. + delete _temp_contents_file; + _temp_contents_file = NULL; + + assert(_saved_download != NULL); + if (contents_changed) { + // OK, the contents.xml has changed; this means we have to restart + // the whole download process from the beginning. + unref_delete(_saved_download); + _saved_download = NULL; + host_got_contents_file(); + + } else { + // Nothing's changed. This was just a useless diversion. We now + // return you to our regularly scheduled download. + Download *download = _saved_download; + _saved_download = NULL; + download->resume_download_finished(false); + unref_delete(download); + } +} + //////////////////////////////////////////////////////////////////// // Function: P3DPackage::host_got_contents_file // Access: Private // Description: We come here when we've successfully downloaded and -// read the host's contents.xml file. +// read the host's contents.xml file. This begins the +// rest of the download process. //////////////////////////////////////////////////////////////////// void P3DPackage:: host_got_contents_file() { @@ -356,6 +454,11 @@ host_got_contents_file() { } } + // Record this now, so we'll know later whether the host has been + // reloaded (e.g. due to some other package, from some other + // instance, reloading it). + _host_contents_seq = _host->get_contents_seq(); + // Now that we have a valid host, we can define the _package_dir. _package_dir = _host->get_host_dir() + string("/") + _package_name; if (!_package_version.empty()) { @@ -503,12 +606,14 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) { _compressed_archive.load_xml(xcompressed_archive); // Now get all the extractable components. + _unpack_size = 0; _extracts.clear(); TiXmlElement *extract = xpackage->FirstChildElement("extract"); while (extract != NULL) { FileSpec file; file.load_xml(extract); _extracts.push_back(file); + _unpack_size += file.get_size(); extract = extract->NextSiblingElement("extract"); } @@ -567,25 +672,49 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) { } else { // We need to get the file data still, but at least we know all // about it by this point. + build_install_plans(); + if (!_allow_data_download) { // Not authorized to start downloading yet; just report that // we're ready. report_info_ready(); } else { // We've already been authorized to start downloading, so do it. - begin_data_download(); + follow_install_plans(true); } } } //////////////////////////////////////////////////////////////////// -// Function: P3DPackage::begin_data_download +// Function: P3DPackage::clear_install_plans // Access: Private -// Description: Begins downloading and installing the package data -// itself, if needed. +// Description:a Empties _install_plans cleanly. //////////////////////////////////////////////////////////////////// void P3DPackage:: -begin_data_download() { +clear_install_plans() { + InstallPlans::iterator pi; + for (pi = _install_plans.begin(); pi != _install_plans.end(); ++pi) { + InstallPlan &plan = (*pi); + InstallPlan::iterator si; + for (si = plan.begin(); si != plan.end(); ++si) { + InstallStep *step = (*si); + delete step; + } + } + + _install_plans.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::build_install_plans +// Access: Private +// Description: Sets up _install_plans, a list of one or more "plans" +// to download and install the package. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +build_install_plans() { + clear_install_plans(); + if (_instances.empty()) { // Can't download without any instances. return; @@ -596,249 +725,115 @@ begin_data_download() { return; } - if (_active_download != NULL) { - // In the middle of downloading. - return; + _install_plans.push_back(InstallPlan()); + InstallPlan &plan = _install_plans.back(); + + InstallStep *step; + if (!_uncompressed_archive.quick_verify(_package_dir)) { + // The uncompressed archive is no good. + + if (!_compressed_archive.quick_verify(_package_dir)) { + // The compressed archive is no good either. Download a new + // compressed archive. + step = new InstallStepDownloadFile(this, _compressed_archive); + plan.push_back(step); + } + + // Uncompress the compressed archive to generate the uncompressed + // archive. + step = new InstallStepUncompressFile(this, _compressed_archive, _uncompressed_archive); + plan.push_back(step); } + // Unpack the uncompressed archive. + step = new InstallStepUnpackArchive(this, _unpack_size); + plan.push_back(step); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::follow_install_plans +// Access: Private +// Description: Performs the next step in the current install plan. +// If download_finished is false, there is a pending +// download that has not fully completed yet; otherwise, +// download_finished should be set true. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +follow_install_plans(bool download_finished) { if (!_allow_data_download) { // Not authorized yet. return; } - if (_uncompressed_archive.quick_verify(_package_dir)) { - // We need to re-extract the archive. - extract_archive(); + while (!_install_plans.empty()) { + // Pull the next step off the current plan. - } else if (_compressed_archive.quick_verify(_package_dir)) { - // We need to uncompress the archive. - uncompress_archive(); + InstallPlan &plan = _install_plans.front(); + bool plan_failed = false; - } else { - // Shoot, we need to download the archive. - download_compressed_archive(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DPackage::download_compressed_archive -// Access: Private -// Description: Starts downloading the archive file for the package. -//////////////////////////////////////////////////////////////////// -void P3DPackage:: -download_compressed_archive() { - string urlbase = _desc_file_dirname; - urlbase += "/"; - urlbase += _compressed_archive.get_filename(); - - string target_pathname = _package_dir + "/" + _compressed_archive.get_filename(); - - start_download(DT_compressed_archive, urlbase, target_pathname, - _compressed_archive); -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DPackage::compressed_archive_download_progress -// Access: Private -// Description: Called as the file is downloaded. -//////////////////////////////////////////////////////////////////// -void P3DPackage:: -compressed_archive_download_progress(double progress) { - report_progress(download_portion * progress); -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DPackage::compressed_archive_download_finished -// Access: Private -// Description: Called when the desc file has been fully downloaded. -//////////////////////////////////////////////////////////////////// -void P3DPackage:: -compressed_archive_download_finished(bool success) { - if (!success) { - report_done(false); - return; - } - - // Go on to uncompress the archive. - uncompress_archive(); -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DPackage::uncompress_archive -// Access: Private -// Description: Uncompresses the archive file. -//////////////////////////////////////////////////////////////////// -void P3DPackage:: -uncompress_archive() { - string source_pathname = _package_dir + "/" + _compressed_archive.get_filename(); - string target_pathname = _package_dir + "/" + _uncompressed_archive.get_filename(); - - ifstream source(source_pathname.c_str(), ios::in | ios::binary); - if (!source) { - nout << "Couldn't open " << source_pathname << "\n"; - report_done(false); - return; - } - - if (!mkfile_complete(target_pathname, nout)) { - report_done(false); - return; - } - - ofstream target(target_pathname.c_str(), ios::out | ios::binary); - if (!target) { - nout << "Couldn't write to " << target_pathname << "\n"; - report_done(false); - return; - } - - static const int decompress_buffer_size = 81920; - char decompress_buffer[decompress_buffer_size]; - static const int write_buffer_size = 81920; - char write_buffer[write_buffer_size]; - - z_stream z; - z.next_in = Z_NULL; - z.avail_in = 0; - z.next_out = Z_NULL; - z.avail_out = 0; - z.zalloc = Z_NULL; - z.zfree = Z_NULL; - z.opaque = Z_NULL; - z.msg = (char *)"no error message"; - - bool eof = false; - int flush = 0; - - source.read(decompress_buffer, decompress_buffer_size); - size_t read_count = source.gcount(); - eof = (read_count == 0 || source.eof() || source.fail()); - - z.next_in = (Bytef *)decompress_buffer; - z.avail_in = read_count; - - int result = inflateInit(&z); - if (result < 0) { - nout << z.msg << "\n"; - report_done(false); - return; - } - - size_t total_out = 0; - while (true) { - if (z.avail_in == 0 && !eof) { - source.read(decompress_buffer, decompress_buffer_size); - size_t read_count = source.gcount(); - eof = (read_count == 0 || source.eof() || source.fail()); - - z.next_in = (Bytef *)decompress_buffer; - z.avail_in = read_count; + _total_plan_size = 0.0; + InstallPlan::iterator si; + for (si = plan.begin(); si != plan.end(); ++si) { + _total_plan_size += (*si)->get_effort(); } - z.next_out = (Bytef *)write_buffer; - z.avail_out = write_buffer_size; - int result = inflate(&z, flush); - if (z.avail_out < write_buffer_size) { - target.write(write_buffer, write_buffer_size - z.avail_out); - if (!target) { - nout << "Couldn't write entire file to " << target_pathname << "\n"; - report_done(false); + _total_plan_completed = 0.0; + _download_progress = 0.0; + + while (!plan.empty() && !plan_failed) { + InstallStep *step = plan.front(); + _current_step_effort = step->get_effort(); + + InstallToken token = step->do_step(download_finished); + switch (token) { + case IT_step_failed: + // This plan has failed. + plan_failed = true; + break; + + case IT_continue: + // A callback hook has been attached; we'll come back later. return; - } - total_out += (write_buffer_size - z.avail_out); - if (_uncompressed_archive.get_size() != 0) { - double progress = (double)total_out / (double)_uncompressed_archive.get_size(); - progress = min(progress, 1.0); - report_progress(download_portion + uncompress_portion * progress); + + case IT_step_complete: + // So far, so good. Go on to the next step. + _total_plan_completed += _current_step_effort; + delete step; + plan.pop_front(); + break; } } - if (result == Z_STREAM_END) { - // Here's the end of the file. - break; - - } else if (result == Z_BUF_ERROR && flush == 0) { - // We might get this if no progress is possible, for instance if - // the input stream is truncated. In this case, tell zlib to - // dump everything it's got. - flush = Z_FINISH; - - } else if (result < 0) { - nout << z.msg << "\n"; - inflateEnd(&z); - report_done(false); + if (!plan_failed) { + // We've finished the plan successfully. + clear_install_plans(); + report_done(true); return; } + + // That plan failed. Go on to the next plan. + _install_plans.pop_front(); } - result = inflateEnd(&z); - if (result < 0) { - nout << z.msg << "\n"; - report_done(false); - return; - } - - source.close(); - target.close(); - - if (!_uncompressed_archive.full_verify(_package_dir)) { - nout << "after uncompressing " << target_pathname - << ", failed hash check\n"; - report_done(false); - return; - } - - // Now that we've verified the archive, make it read-only. - chmod(target_pathname.c_str(), 0444); - - // Now we can safely remove the compressed archive. -#ifdef _WIN32 - chmod(source_pathname.c_str(), 0644); -#endif - unlink(source_pathname.c_str()); - - // All done uncompressing. - extract_archive(); -} - -//////////////////////////////////////////////////////////////////// -// Function: P3DPackage::extract_archive -// Access: Private -// Description: Extracts the components from the archive file. -//////////////////////////////////////////////////////////////////// -void P3DPackage:: -extract_archive() { - string source_pathname = _package_dir + "/" + _uncompressed_archive.get_filename(); - P3DMultifileReader reader; - if (!reader.open_read(source_pathname)) { - nout << "Couldn't read " << _uncompressed_archive.get_filename() << "\n"; - report_done(false); - return; - } - - if (!reader.extract_all(_package_dir, this, - download_portion + uncompress_portion, - extract_portion)) { - nout << "Failure extracting " << _uncompressed_archive.get_filename() - << "\n"; - report_done(false); - return; - } - - report_done(true); + // All plans failed. Too bad for us. + report_done(false); } //////////////////////////////////////////////////////////////////// // Function: P3DPackage::report_progress // Access: Private -// Description: Reports the indicated install progress to all +// Description: Reports the current install progress to all // interested instances. //////////////////////////////////////////////////////////////////// void P3DPackage:: -report_progress(double progress) { +report_progress(P3DPackage::InstallStep *step) { + double size = _total_plan_completed + _current_step_effort * step->get_progress(); + _download_progress = min(size / _total_plan_size, 1.0); + // nout << get_package_name() << " progress " << _download_progress << "\n"; + Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { - (*ii)->report_package_progress(this, progress); + (*ii)->report_package_progress(this, _download_progress); } } @@ -901,40 +896,52 @@ report_done(bool success) { //////////////////////////////////////////////////////////////////// // Function: P3DPackage::start_download // Access: Private -// Description: Initiates a download of the indicated file. +// Description: Initiates a download of the indicated file. Returns +// the new Download object. //////////////////////////////////////////////////////////////////// -void P3DPackage:: +P3DPackage::Download *P3DPackage:: start_download(P3DPackage::DownloadType dtype, const string &urlbase, const string &pathname, const FileSpec &file_spec) { // Only one download should be active at a time assert(_active_download == NULL); - // TODO: support partial downloads. - static const bool allow_partial = false; - if (!allow_partial) { + // We can't explicitly support partial downloads here, because + // Mozilla provides no interface to ask for one. We have to trust + // that Mozilla's use of the browser cache handles partial downloads + // for us automatically. + + // Delete the target file before we begin. #ifdef _WIN32 - // Windows can't delete a file if it's read-only. - chmod(pathname.c_str(), 0644); + // Windows can't delete a file if it's read-only. + chmod(pathname.c_str(), 0644); #endif - unlink(pathname.c_str()); - } else { - // Make sure the file is writable. - chmod(pathname.c_str(), 0644); - } + unlink(pathname.c_str()); Download *download = new Download(this, dtype, file_spec); // Fill up the _try_urls vector for URL's to try getting this file // from, in reverse order. + bool is_contents_file = (dtype == DT_contents_file || dtype == DT_redownload_contents_file); - // The last thing we try is the actual authoritative host. - string url = _host->get_host_url_prefix() + urlbase; + // The last thing we try is the actual authoritative host, with a + // cache-busting query string. + ostringstream strm; + if (is_contents_file) { + strm << _host->get_host_url_prefix(); + } else { + strm << _host->get_download_url_prefix(); + } + strm << urlbase << "?" << time(NULL); + string url = strm.str(); download->_try_urls.push_back(url); - // The first thing we try is a couple of mirrors, chosen at random - // (except for the contents.xml file, which always goes straight to - // the host). - if (dtype != DT_contents_file) { + if (!is_contents_file) { + // Before we try the cache-buster out of desperation, we try the + // authoritative host, allowing caches. + url = _host->get_download_url_prefix() + urlbase; + download->_try_urls.push_back(url); + + // Before *that*, we try a couple of mirrors, chosen at random. vector mirrors; _host->choose_random_mirrors(mirrors, 2); for (vector::iterator si = mirrors.begin(); @@ -947,8 +954,8 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase, P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); - if (dtype == DT_contents_file && inst_mgr->get_verify_contents()) { - // When we're dowloading the contents file with verify_contents + if (is_contents_file && inst_mgr->get_verify_contents()) { + // When we're downloading the contents file with verify_contents // true, we always go straight to the authoritative host, not even // to the super-mirror. @@ -972,6 +979,7 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase, assert(!_instances.empty()); _instances[0]->start_download(download); + return download; } //////////////////////////////////////////////////////////////////// @@ -1034,8 +1042,8 @@ download_progress() { case DT_desc_file: break; - case DT_compressed_archive: - _package->compressed_archive_download_progress(get_download_progress()); + case DT_install_step: + _package->follow_install_plans(false); break; } } @@ -1061,9 +1069,34 @@ download_finished(bool success) { } } + close_file(); + + if (!success) { + // Maybe it failed because our contents.xml file is out-of-date. + // Go try to freshen it. + bool is_contents_file = (_dtype == DT_contents_file || _dtype == DT_redownload_contents_file); + if (!is_contents_file) { + _package->redownload_contents_file(this); + return; + } + } + + // Carry on. + resume_download_finished(success); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::Download::resume_download_finished +// Access: Public +// Description: Continuing the work begun in download_finished(). +// This is a separate entry point so that it can be +// called again after determining that the host's +// contents.xml file is *not* stale. +//////////////////////////////////////////////////////////////////// +void P3DPackage::Download:: +resume_download_finished(bool success) { if (!success && !_try_urls.empty()) { - // Well, that URL failed, but we can try another mirror. - close_file(); + // Try the next mirror. string url = _try_urls.back(); _try_urls.pop_back(); @@ -1083,12 +1116,280 @@ download_finished(bool success) { _package->contents_file_download_finished(success); break; + case DT_redownload_contents_file: + _package->contents_file_redownload_finished(success); + break; + case DT_desc_file: _package->desc_file_download_finished(success); break; - case DT_compressed_archive: - _package->compressed_archive_download_finished(success); + case DT_install_step: + _package->follow_install_plans(true); break; } } + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStep::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStep:: +InstallStep(P3DPackage *package, size_t bytes, double factor) : + _package(package), + _bytes_needed(bytes), + _bytes_done(0), + _bytes_factor(factor) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStep::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStep:: +~InstallStep() { +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepDownloadFile::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStepDownloadFile:: +InstallStepDownloadFile(P3DPackage *package, const FileSpec &file) : + InstallStep(package, file.get_size(), _download_factor), + _file(file) +{ + _urlbase = _package->get_desc_file_dirname(); + _urlbase += "/"; + _urlbase += _file.get_filename(); + + _pathname = _package->get_package_dir() + "/" + _file.get_filename(); + + _download = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepDownloadFile::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStepDownloadFile:: +~InstallStepDownloadFile() { + if (_download != NULL) { + unref_delete(_download); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepDownloadFile::do_step +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallToken P3DPackage::InstallStepDownloadFile:: +do_step(bool download_finished) { + if (_download == NULL) { + // First, we have to start the download going. + assert(_package->_active_download == NULL); + + _download = _package->start_download(DT_install_step, _urlbase, + _pathname, _file); + assert(_download != NULL); + _download->ref(); + } + + _bytes_done = _download->get_total_data(); + report_step_progress(); + + if (!_download->get_download_finished() || !download_finished) { + // Wait for it. + return IT_continue; + } + + if (_download->get_download_success()) { + // The Download object has already validated the hash. + return IT_step_complete; + } else { + // The Download object has already tried all of the mirrors, and + // they all failed. + return IT_step_failed; + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepUncompressFile::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStepUncompressFile:: +InstallStepUncompressFile(P3DPackage *package, const FileSpec &source, + const FileSpec &target) : + InstallStep(package, target.get_size(), _uncompress_factor), + _source(source), + _target(target) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepUncompressFile::do_step +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallToken P3DPackage::InstallStepUncompressFile:: +do_step(bool download_finished) { + string source_pathname = _package->get_package_dir() + "/" + _source.get_filename(); + string target_pathname = _package->get_package_dir() + "/" + _target.get_filename(); + + ifstream source(source_pathname.c_str(), ios::in | ios::binary); + if (!source) { + nout << "Couldn't open " << source_pathname << "\n"; + return IT_step_failed; + } + + if (!mkfile_complete(target_pathname, nout)) { + return IT_step_failed; + } + + ofstream target(target_pathname.c_str(), ios::out | ios::binary); + if (!target) { + nout << "Couldn't write to " << target_pathname << "\n"; + return IT_step_failed; + } + + static const int decompress_buffer_size = 81920; + char decompress_buffer[decompress_buffer_size]; + static const int write_buffer_size = 81920; + char write_buffer[write_buffer_size]; + + z_stream z; + z.next_in = Z_NULL; + z.avail_in = 0; + z.next_out = Z_NULL; + z.avail_out = 0; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + z.msg = (char *)"no error message"; + + bool eof = false; + int flush = 0; + + source.read(decompress_buffer, decompress_buffer_size); + size_t read_count = source.gcount(); + eof = (read_count == 0 || source.eof() || source.fail()); + + z.next_in = (Bytef *)decompress_buffer; + z.avail_in = read_count; + + int result = inflateInit(&z); + if (result < 0) { + nout << z.msg << "\n"; + return IT_step_failed; + } + + while (true) { + if (z.avail_in == 0 && !eof) { + source.read(decompress_buffer, decompress_buffer_size); + size_t read_count = source.gcount(); + eof = (read_count == 0 || source.eof() || source.fail()); + + z.next_in = (Bytef *)decompress_buffer; + z.avail_in = read_count; + } + + z.next_out = (Bytef *)write_buffer; + z.avail_out = write_buffer_size; + int result = inflate(&z, flush); + if (z.avail_out < write_buffer_size) { + target.write(write_buffer, write_buffer_size - z.avail_out); + if (!target) { + nout << "Couldn't write entire file to " << target_pathname << "\n"; + return IT_step_failed; + } + _bytes_done += (write_buffer_size - z.avail_out); + report_step_progress(); + } + + if (result == Z_STREAM_END) { + // Here's the end of the file. + break; + + } else if (result == Z_BUF_ERROR && flush == 0) { + // We might get this if no progress is possible, for instance if + // the input stream is truncated. In this case, tell zlib to + // dump everything it's got. + flush = Z_FINISH; + + } else if (result < 0) { + nout << z.msg << "\n"; + inflateEnd(&z); + return IT_step_failed; + } + } + + result = inflateEnd(&z); + if (result < 0) { + nout << z.msg << "\n"; + return IT_step_failed; + } + + source.close(); + target.close(); + + if (!_target.full_verify(_package->get_package_dir())) { + nout << "after uncompressing " << target_pathname + << ", failed hash check\n"; + return IT_step_failed; + } + + // Now that we've verified the target, make it read-only. + chmod(target_pathname.c_str(), 0444); + + // Now we can safely remove the source. +#ifdef _WIN32 + chmod(source_pathname.c_str(), 0644); +#endif + unlink(source_pathname.c_str()); + + // All done uncompressing. + return IT_step_complete; +} + + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepUnpackArchive::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallStepUnpackArchive:: +InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size) : + InstallStep(package, unpack_size, _unpack_factor) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::InstallStepUnpackArchive::do_step +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +P3DPackage::InstallToken P3DPackage::InstallStepUnpackArchive:: +do_step(bool download_finished) { + string source_pathname = _package->get_archive_file_pathname(); + P3DMultifileReader reader; + if (!reader.open_read(source_pathname)) { + nout << "Couldn't read " << source_pathname << "\n"; + return IT_step_failed; + } + + if (!reader.extract_all(_package->get_package_dir(), _package, this)) { + nout << "Failure extracting " << source_pathname << "\n"; + return IT_step_failed; + } + + return IT_step_complete; +} + diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index 1334e53f41..ab32fff642 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -19,6 +19,7 @@ #include "p3dFileDownload.h" #include "fileSpec.h" #include "get_tinyxml.h" +#include class P3DHost; class P3DInstance; @@ -61,6 +62,7 @@ public: inline const TiXmlElement *get_xconfig() const; inline const string &get_desc_file_pathname() const; + inline const string &get_desc_file_dirname() const; inline string get_archive_file_pathname() const; void add_instance(P3DInstance *inst); @@ -69,10 +71,13 @@ public: TiXmlElement *make_xml(); private: + typedef vector Extracts; + enum DownloadType { DT_contents_file, + DT_redownload_contents_file, DT_desc_file, - DT_compressed_archive + DT_install_step, }; typedef vector TryUrls; @@ -87,6 +92,9 @@ private: virtual void download_progress(); virtual void download_finished(bool success); + public: + void resume_download_finished(bool success); + public: // URL's to try downloading from, in reverse order. TryUrls _try_urls; @@ -99,33 +107,94 @@ private: FileSpec _file_spec; }; + enum InstallToken { + IT_step_complete, + IT_step_failed, + IT_continue, + }; + + class InstallStep { + public: + InstallStep(P3DPackage *package, size_t bytes, double factor); + virtual ~InstallStep(); + + virtual InstallToken do_step(bool download_finished) = 0; + + inline double get_effort() const; + inline double get_progress() const; + inline void report_step_progress(); + + P3DPackage *_package; + size_t _bytes_needed; + size_t _bytes_done; + double _bytes_factor; + }; + + class InstallStepDownloadFile : public InstallStep { + public: + InstallStepDownloadFile(P3DPackage *package, const FileSpec &file); + virtual ~InstallStepDownloadFile(); + + virtual InstallToken do_step(bool download_finished); + + string _urlbase; + string _pathname; + FileSpec _file; + Download *_download; + }; + + class InstallStepUncompressFile : public InstallStep { + public: + InstallStepUncompressFile(P3DPackage *package, const FileSpec &source, + const FileSpec &target); + virtual InstallToken do_step(bool download_finished); + + FileSpec _source; + FileSpec _target; + }; + + class InstallStepUnpackArchive : public InstallStep { + public: + InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size); + virtual InstallToken do_step(bool download_finished); + }; + + typedef deque InstallPlan; + typedef deque InstallPlans; + InstallPlans _install_plans; + + double _total_plan_size; + double _total_plan_completed; + double _download_progress; + double _current_step_effort; + void begin_info_download(); void download_contents_file(); void contents_file_download_finished(bool success); + void redownload_contents_file(Download *download); + void contents_file_redownload_finished(bool success); void host_got_contents_file(); void download_desc_file(); void desc_file_download_finished(bool success); void got_desc_file(TiXmlDocument *doc, bool freshly_downloaded); - void begin_data_download(); - void download_compressed_archive(); - void compressed_archive_download_progress(double progress); - void compressed_archive_download_finished(bool success); + void clear_install_plans(); + void build_install_plans(); + void follow_install_plans(bool download_finished); - void uncompress_archive(); - void extract_archive(); - - void report_progress(double progress); + class InstallStep; + void report_progress(InstallStep *step); void report_info_ready(); void report_done(bool success); - void start_download(DownloadType dtype, const string &urlbase, - const string &pathname, const FileSpec &file_spec); + Download *start_download(DownloadType dtype, const string &urlbase, + const string &pathname, const FileSpec &file_spec); bool is_extractable(const string &filename) const; private: P3DHost *_host; + int _host_contents_seq; string _package_name; string _package_version; @@ -151,6 +220,7 @@ private: bool _ready; bool _failed; Download *_active_download; + Download *_saved_download; typedef vector Instances; Instances _instances; @@ -158,12 +228,17 @@ private: FileSpec _compressed_archive; FileSpec _uncompressed_archive; - typedef vector Extracts; + size_t _unpack_size; Extracts _extracts; - friend class Download; - friend class P3DMultifileReader; + static const double _download_factor; + static const double _uncompress_factor; + static const double _unpack_factor; + static const double _patch_factor; + friend class Download; + friend class InstallStep; + friend class P3DMultifileReader; friend class P3DHost; };