Browse Source

robustify download on the C++ side too

David Rose 16 years ago
parent
commit
083e7cbe78

+ 5 - 5
direct/src/p3d/PackageInfo.py

@@ -86,7 +86,7 @@ class PackageInfo:
         self.downloadProgress = 0
         self.downloadProgress = 0
         
         
         # This is set true when the package file has been fully
         # This is set true when the package file has been fully
-        # downloaded and unpackaged.
+        # downloaded and unpacked.
         self.hasPackage = False
         self.hasPackage = False
 
 
         # This is set true when the package has been "installed",
         # This is set true when the package has been "installed",
@@ -270,10 +270,10 @@ class PackageInfo:
             return True
             return True
 
 
         # Still have to download it.
         # Still have to download it.
-        self.__buildInstallPlan()
+        self.__buildInstallPlans()
         return True
         return True
 
 
-    def __buildInstallPlan(self):
+    def __buildInstallPlans(self):
         """ Sets up self.installPlans, a list of one or more "plans"
         """ Sets up self.installPlans, a list of one or more "plans"
         to download and install the package. """
         to download and install the package. """
 
 
@@ -297,8 +297,8 @@ class PackageInfo:
         # download, and build a plan (or two) to download it all.
         # download, and build a plan (or two) to download it all.
         self.installPlans = None
         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
         unpackSize = 0
         for file in self.extracts:
         for file in self.extracts:
             unpackSize += file.size
             unpackSize += file.size

+ 36 - 1
direct/src/plugin/fileSpec.cxx

@@ -229,7 +229,7 @@ check_hash(const string &pathname) const {
   MD5_CTX ctx;
   MD5_CTX ctx;
   MD5_Init(&ctx);
   MD5_Init(&ctx);
 
 
-  static const int buffer_size = 1024;
+  static const int buffer_size = 4096;
   char buffer[buffer_size];
   char buffer[buffer_size];
 
 
   stream.read(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);
   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
 //     Function: FileSpec::decode_hex
 //       Access: Private, Static
 //       Access: Private, Static

+ 1 - 0
direct/src/plugin/fileSpec.h

@@ -42,6 +42,7 @@ public:
   bool full_verify(const string &package_dir) const;
   bool full_verify(const string &package_dir) const;
   
   
   bool check_hash(const string &pathname) const;
   bool check_hash(const string &pathname) const;
+  bool read_hash(const string &pathname);
 
 
 private:
 private:
   static inline int decode_hexdigit(char c);
   static inline int decode_hexdigit(char c);

+ 10 - 0
direct/src/plugin/p3dDownload.I

@@ -39,6 +39,16 @@ get_download_progress() const {
   return (double)_total_data / (double)_total_expected_data;
   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
 //     Function: P3DDownload::get_download_finished
 //       Access: Public
 //       Access: Public

+ 1 - 0
direct/src/plugin/p3dDownload.h

@@ -40,6 +40,7 @@ public:
   inline double get_download_progress() const;
   inline double get_download_progress() const;
   inline bool get_download_finished() const;
   inline bool get_download_finished() const;
   inline bool get_download_success() const;
   inline bool get_download_success() const;
+  inline size_t get_total_data() const;
 
 
   void cancel();
   void cancel();
 
 

+ 43 - 3
direct/src/plugin/p3dHost.I

@@ -40,15 +40,31 @@ get_host_url() const {
 //     Function: P3DHost::get_host_url_prefix
 //     Function: P3DHost::get_host_url_prefix
 //       Access: Public
 //       Access: Public
 //  Description: Returns the root URL of this host, for constructing
 //  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::
 inline const string &P3DHost::
 get_host_url_prefix() const {
 get_host_url_prefix() const {
   return _host_url_prefix;
   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
 //     Function: P3DHost::get_descriptive_name
 //       Access: Public
 //       Access: Public
@@ -71,3 +87,27 @@ inline bool P3DHost::
 has_contents_file() const {
 has_contents_file() const {
   return (_xcontents != NULL);
   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);
+}

+ 19 - 1
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] != '/') {
   if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.size() - 1] != '/') {
     _host_url_prefix += "/";
     _host_url_prefix += "/";
   }
   }
+  _download_url_prefix = _host_url_prefix;
 
 
   _xcontents = NULL;
   _xcontents = NULL;
+  _contents_seq = 0;
 
 
   determine_host_dir();
   determine_host_dir();
 }
 }
@@ -137,6 +139,8 @@ read_contents_file(const string &contents_filename) {
     delete _xcontents;
     delete _xcontents;
   }
   }
   _xcontents = (TiXmlElement *)xcontents->Clone();
   _xcontents = (TiXmlElement *)xcontents->Clone();
+  ++_contents_seq;
+  _contents_spec.read_hash(contents_filename);
 
 
   TiXmlElement *xhost = _xcontents->FirstChildElement("host");
   TiXmlElement *xhost = _xcontents->FirstChildElement("host");
   if (xhost != NULL) {
   if (xhost != NULL) {
@@ -463,9 +467,23 @@ determine_host_dir() {
 void P3DHost::
 void P3DHost::
 read_xhost(TiXmlElement *xhost) {
 read_xhost(TiXmlElement *xhost) {
   const char *descriptive_name = xhost->Attribute("descriptive_name");
   const char *descriptive_name = xhost->Attribute("descriptive_name");
-  if (descriptive_name != NULL) {
+  if (descriptive_name != NULL && _descriptive_name.empty()) {
     _descriptive_name = descriptive_name;
     _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");
   TiXmlElement *xmirror = xhost->FirstChildElement("mirror");
   while (xmirror != NULL) {
   while (xmirror != NULL) {

+ 8 - 1
direct/src/plugin/p3dHost.h

@@ -16,7 +16,7 @@
 #define P3DHOST_H
 #define P3DHOST_H
 
 
 #include "p3d_plugin_common.h"
 #include "p3d_plugin_common.h"
-
+#include "fileSpec.h"
 #include <map>
 #include <map>
 
 
 class FileSpec;
 class FileSpec;
@@ -37,11 +37,15 @@ public:
   inline const string &get_host_dir() const;
   inline const string &get_host_dir() const;
   inline const string &get_host_url() const;
   inline const string &get_host_url() const;
   inline const string &get_host_url_prefix() const;
   inline const string &get_host_url_prefix() const;
+  inline const string &get_download_url_prefix() const;
   inline const string &get_descriptive_name() const;
   inline const string &get_descriptive_name() const;
 
 
   P3DHost *get_alt_host(const string &alt_host);
   P3DHost *get_alt_host(const string &alt_host);
 
 
   inline bool has_contents_file() const;
   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();
   bool read_contents_file(const string &contents_filename);
   bool read_contents_file(const string &contents_filename);
 
 
@@ -70,8 +74,11 @@ private:
   string _host_dir;
   string _host_dir;
   string _host_url;
   string _host_url;
   string _host_url_prefix;
   string _host_url_prefix;
+  string _download_url_prefix;
   string _descriptive_name;
   string _descriptive_name;
   TiXmlElement *_xcontents;
   TiXmlElement *_xcontents;
+  int _contents_seq;
+  FileSpec _contents_spec;
 
 
   typedef vector<string> Mirrors;
   typedef vector<string> Mirrors;
   Mirrors _mirrors;
   Mirrors _mirrors;

+ 1 - 0
direct/src/plugin/p3dInstance.cxx

@@ -844,6 +844,7 @@ get_packages_failed() const {
 void P3DInstance::
 void P3DInstance::
 start_download(P3DDownload *download) {
 start_download(P3DDownload *download) {
   assert(download->get_download_id() == 0);
   assert(download->get_download_id() == 0);
+  assert(!download->get_url().empty());
 
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
 
 

+ 7 - 16
direct/src/plugin/p3dMultifileReader.cxx

@@ -87,14 +87,12 @@ close() {
 //               directory.  Returns true on success, false on
 //               directory.  Returns true on success, false on
 //               failure.
 //               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::
 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);
   assert(_is_open);
   if (_in.fail()) {
   if (_in.fail()) {
     return false;
     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
   // Now walk through all of the files, and extract only the ones we
   // expect to encounter.
   // 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;
   Subfiles::iterator si;
   for (si = _subfiles.begin(); si != _subfiles.end(); ++si) {
   for (si = _subfiles.begin(); si != _subfiles.end(); ++si) {
     const Subfile &s = (*si);
     const Subfile &s = (*si);
@@ -141,10 +133,9 @@ extract_all(const string &to_dir,
     // program or something.
     // program or something.
     chmod(output_pathname.c_str(), 0555);
     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();
     }
     }
   }
   }
 
 

+ 3 - 5
direct/src/plugin/p3dMultifileReader.h

@@ -17,8 +17,7 @@
 
 
 #include "p3d_plugin_common.h"
 #include "p3d_plugin_common.h"
 #include "p3dInstanceManager.h"  // for openssl
 #include "p3dInstanceManager.h"  // for openssl
-
-class P3DPackage;
+#include "p3dPackage.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : P3DMultifileReader
 //       Class : P3DMultifileReader
@@ -35,9 +34,8 @@ public:
   inline bool is_open() const;
   inline bool is_open() const;
   void close();
   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);
   bool extract_one(ostream &out, const string &filename);
 
 

+ 46 - 0
direct/src/plugin/p3dPackage.I

@@ -144,6 +144,18 @@ get_desc_file_pathname() const {
   return _desc_file_pathname;
   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
 //     Function: P3DPackage::get_archive_file_pathname
 //       Access: Public
 //       Access: Public
@@ -156,3 +168,37 @@ get_archive_file_pathname() const {
   return _uncompressed_archive.get_pathname(_package_dir);
   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);
+}

+ 556 - 255
direct/src/plugin/p3dPackage.cxx

@@ -28,11 +28,12 @@
 #include <io.h>    // chmod()
 #include <io.h>    // chmod()
 #endif
 #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
 //     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.
   // file, instead of an xml file and a multifile to unpack.
   _package_solo = false;
   _package_solo = false;
 
 
+  _host_contents_seq = 0;
+
   _xconfig = NULL;
   _xconfig = NULL;
   _temp_contents_file = NULL;
   _temp_contents_file = NULL;
 
 
@@ -66,6 +69,7 @@ P3DPackage(P3DHost *host, const string &package_name,
   _ready = false;
   _ready = false;
   _failed = false;
   _failed = false;
   _active_download = NULL;
   _active_download = NULL;
+  _saved_download = NULL;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -89,6 +93,11 @@ P3DPackage::
     delete _active_download;
     delete _active_download;
     _active_download = NULL;
     _active_download = NULL;
   }
   }
+  if (_saved_download != NULL) {
+    _saved_download->cancel();
+    delete _saved_download;
+    _saved_download = NULL;
+  }
 
 
   if (_temp_contents_file != NULL) {
   if (_temp_contents_file != NULL) {
     delete _temp_contents_file;
     delete _temp_contents_file;
@@ -125,7 +134,7 @@ activate_download() {
     // Otherwise, if we've already got the desc file, then start the
     // Otherwise, if we've already got the desc file, then start the
     // download.
     // download.
     if (_info_ready) {
     if (_info_ready) {
-      begin_data_download();
+      follow_install_plans(true);
     }
     }
   }
   }
 }
 }
@@ -254,8 +263,9 @@ begin_info_download() {
 //     Function: P3DPackage::download_contents_file
 //     Function: P3DPackage::download_contents_file
 //       Access: Private
 //       Access: Private
 //  Description: Starts downloading the root-level contents.xml file.
 //  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::
 void P3DPackage::
 download_contents_file() {
 download_contents_file() {
@@ -273,15 +283,6 @@ download_contents_file() {
     return;
     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
   // Download contents.xml to a temporary filename first, in case
   // multiple packages are downloading it simultaneously.
   // multiple packages are downloading it simultaneously.
   if (_temp_contents_file != NULL) {
   if (_temp_contents_file != NULL) {
@@ -290,8 +291,8 @@ download_contents_file() {
   }
   }
   _temp_contents_file = new P3DTemporaryFile(".xml");
   _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();
   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
 //     Function: P3DPackage::host_got_contents_file
 //       Access: Private
 //       Access: Private
 //  Description: We come here when we've successfully downloaded and
 //  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::
 void P3DPackage::
 host_got_contents_file() {
 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.
   // Now that we have a valid host, we can define the _package_dir.
   _package_dir = _host->get_host_dir() + string("/") + _package_name;
   _package_dir = _host->get_host_dir() + string("/") + _package_name;
   if (!_package_version.empty()) {
   if (!_package_version.empty()) {
@@ -503,12 +606,14 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   _compressed_archive.load_xml(xcompressed_archive);
   _compressed_archive.load_xml(xcompressed_archive);
 
 
   // Now get all the extractable components.
   // Now get all the extractable components.
+  _unpack_size = 0;
   _extracts.clear();
   _extracts.clear();
   TiXmlElement *extract = xpackage->FirstChildElement("extract");
   TiXmlElement *extract = xpackage->FirstChildElement("extract");
   while (extract != NULL) {
   while (extract != NULL) {
     FileSpec file;
     FileSpec file;
     file.load_xml(extract);
     file.load_xml(extract);
     _extracts.push_back(file);
     _extracts.push_back(file);
+    _unpack_size += file.get_size();
     extract = extract->NextSiblingElement("extract");
     extract = extract->NextSiblingElement("extract");
   }
   }
 
 
@@ -567,25 +672,49 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   } else {
   } else {
     // We need to get the file data still, but at least we know all
     // We need to get the file data still, but at least we know all
     // about it by this point.
     // about it by this point.
+    build_install_plans();
+
     if (!_allow_data_download) {
     if (!_allow_data_download) {
       // Not authorized to start downloading yet; just report that
       // Not authorized to start downloading yet; just report that
       // we're ready.
       // we're ready.
       report_info_ready();
       report_info_ready();
     } else {
     } else {
       // We've already been authorized to start downloading, so do it.
       // We've already been authorized to start downloading, so do it.
-      begin_data_download();
+      follow_install_plans(true);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::clear_install_plans
+//       Access: Private
+//  Description:a Empties _install_plans cleanly.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+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::begin_data_download
+//     Function: P3DPackage::build_install_plans
 //       Access: Private
 //       Access: Private
-//  Description: Begins downloading and installing the package data
-//               itself, if needed.
+//  Description: Sets up _install_plans, a list of one or more "plans"
+//               to download and install the package.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
-begin_data_download() {
+build_install_plans() {
+  clear_install_plans();
+
   if (_instances.empty()) {
   if (_instances.empty()) {
     // Can't download without any instances.
     // Can't download without any instances.
     return;
     return;
@@ -596,249 +725,115 @@ begin_data_download() {
     return;
     return;
   }
   }
 
 
-  if (_active_download != NULL) {
-    // In the middle of downloading.
-    return;
-  }
-
-  if (!_allow_data_download) {
-    // Not authorized yet.
-    return;
-  }
-
-  if (_uncompressed_archive.quick_verify(_package_dir)) {
-    // We need to re-extract the archive.
-    extract_archive();
-
-  } else if (_compressed_archive.quick_verify(_package_dir)) {
-    // We need to uncompress the archive.
-    uncompress_archive();
-
-  } 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();
+  _install_plans.push_back(InstallPlan());
+  InstallPlan &plan = _install_plans.back();
 
 
-  start_download(DT_compressed_archive, urlbase, target_pathname, 
-                 _compressed_archive);
-}
+  InstallStep *step;
+  if (!_uncompressed_archive.quick_verify(_package_dir)) {
+    // The uncompressed archive is no good.
 
 
-////////////////////////////////////////////////////////////////////
-//     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);
-}
+    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);
+    }
 
 
-////////////////////////////////////////////////////////////////////
-//     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;
+    // Uncompress the compressed archive to generate the uncompressed
+    // archive.
+    step = new InstallStepUncompressFile(this, _compressed_archive, _uncompressed_archive);
+    plan.push_back(step);
   }
   }
 
 
-  // Go on to uncompress the archive.
-  uncompress_archive();
+  // Unpack the uncompressed archive.
+  step = new InstallStepUnpackArchive(this, _unpack_size);
+  plan.push_back(step);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: P3DPackage::uncompress_archive
+//     Function: P3DPackage::follow_install_plans
 //       Access: Private
 //       Access: Private
-//  Description: Uncompresses the archive file.
+//  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::
 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);
+follow_install_plans(bool download_finished) {
+  if (!_allow_data_download) {
+    // Not authorized yet.
     return;
     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];
+  while (!_install_plans.empty()) {
+    // Pull the next step off the current plan.
 
 
-  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";
+    InstallPlan &plan = _install_plans.front();
+    bool plan_failed = false;
 
 
-  bool eof = false;
-  int flush = 0;
+    _total_plan_size = 0.0;
+    InstallPlan::iterator si;
+    for (si = plan.begin(); si != plan.end(); ++si) {
+      _total_plan_size += (*si)->get_effort();
+    }
 
 
-  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_completed = 0.0;
+    _download_progress = 0.0;
 
 
-  int result = inflateInit(&z);
-  if (result < 0) {
-    nout << z.msg << "\n";
-    report_done(false);
-    return;
-  }
+    while (!plan.empty() && !plan_failed) {
+      InstallStep *step = plan.front();
+      _current_step_effort = step->get_effort();
 
 
-  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;
-    }
+      InstallToken token = step->do_step(download_finished);
+      switch (token) {
+      case IT_step_failed:
+        // This plan has failed.
+        plan_failed = true;
+        break;
 
 
-    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);
+      case IT_continue:
+        // A callback hook has been attached; we'll come back later.
         return;
         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;
       return;
     }
     }
-  }
-
-  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;
+    // That plan failed.  Go on to the next plan.
+    _install_plans.pop_front();
   }
   }
 
 
-  // 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
 //     Function: P3DPackage::report_progress
 //       Access: Private
 //       Access: Private
-//  Description: Reports the indicated install progress to all
+//  Description: Reports the current install progress to all
 //               interested instances.
 //               interested instances.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 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;
   Instances::iterator ii;
   for (ii = _instances.begin(); ii != _instances.end(); ++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
 //     Function: P3DPackage::start_download
 //       Access: Private
 //       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, 
 start_download(P3DPackage::DownloadType dtype, const string &urlbase, 
                const string &pathname, const FileSpec &file_spec) {
                const string &pathname, const FileSpec &file_spec) {
   // Only one download should be active at a time
   // Only one download should be active at a time
   assert(_active_download == NULL);
   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
 #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
 #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);
   Download *download = new Download(this, dtype, file_spec);
 
 
   // Fill up the _try_urls vector for URL's to try getting this file
   // Fill up the _try_urls vector for URL's to try getting this file
   // from, in reverse order.
   // 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);
   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<string> mirrors;
     vector<string> mirrors;
     _host->choose_random_mirrors(mirrors, 2);
     _host->choose_random_mirrors(mirrors, 2);
     for (vector<string>::iterator si = mirrors.begin();
     for (vector<string>::iterator si = mirrors.begin();
@@ -947,8 +954,8 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase,
 
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   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
     // true, we always go straight to the authoritative host, not even
     // to the super-mirror.
     // to the super-mirror.
 
 
@@ -972,6 +979,7 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase,
   assert(!_instances.empty());
   assert(!_instances.empty());
 
 
   _instances[0]->start_download(download);
   _instances[0]->start_download(download);
+  return download;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1034,8 +1042,8 @@ download_progress() {
   case DT_desc_file:
   case DT_desc_file:
     break;
     break;
 
 
-  case DT_compressed_archive:
-    _package->compressed_archive_download_progress(get_download_progress());
+  case DT_install_step:
+    _package->follow_install_plans(false);
     break;
     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()) {
   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();
     string url = _try_urls.back();
     _try_urls.pop_back();
     _try_urls.pop_back();
 
 
@@ -1083,12 +1116,280 @@ download_finished(bool success) {
     _package->contents_file_download_finished(success);
     _package->contents_file_download_finished(success);
     break;
     break;
 
 
+  case DT_redownload_contents_file:
+    _package->contents_file_redownload_finished(success);
+    break;
+
   case DT_desc_file:
   case DT_desc_file:
     _package->desc_file_download_finished(success);
     _package->desc_file_download_finished(success);
     break;
     break;
 
 
-  case DT_compressed_archive:
-    _package->compressed_archive_download_finished(success);
+  case DT_install_step:
+    _package->follow_install_plans(true);
     break;
     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;
+}
+

+ 88 - 13
direct/src/plugin/p3dPackage.h

@@ -19,6 +19,7 @@
 #include "p3dFileDownload.h"
 #include "p3dFileDownload.h"
 #include "fileSpec.h"
 #include "fileSpec.h"
 #include "get_tinyxml.h"
 #include "get_tinyxml.h"
+#include <deque>
 
 
 class P3DHost;
 class P3DHost;
 class P3DInstance;
 class P3DInstance;
@@ -61,6 +62,7 @@ public:
   inline const TiXmlElement *get_xconfig() const;
   inline const TiXmlElement *get_xconfig() const;
 
 
   inline const string &get_desc_file_pathname() const;
   inline const string &get_desc_file_pathname() const;
+  inline const string &get_desc_file_dirname() const;
   inline string get_archive_file_pathname() const;
   inline string get_archive_file_pathname() const;
 
 
   void add_instance(P3DInstance *inst);
   void add_instance(P3DInstance *inst);
@@ -69,10 +71,13 @@ public:
   TiXmlElement *make_xml();
   TiXmlElement *make_xml();
 
 
 private:
 private:
+  typedef vector<FileSpec> Extracts;
+
   enum DownloadType {
   enum DownloadType {
     DT_contents_file,
     DT_contents_file,
+    DT_redownload_contents_file,
     DT_desc_file,
     DT_desc_file,
-    DT_compressed_archive
+    DT_install_step,
   };
   };
 
 
   typedef vector<string> TryUrls;
   typedef vector<string> TryUrls;
@@ -87,6 +92,9 @@ private:
     virtual void download_progress();
     virtual void download_progress();
     virtual void download_finished(bool success);
     virtual void download_finished(bool success);
 
 
+  public:
+    void resume_download_finished(bool success);
+
   public:
   public:
     // URL's to try downloading from, in reverse order.
     // URL's to try downloading from, in reverse order.
     TryUrls _try_urls;
     TryUrls _try_urls;
@@ -99,33 +107,94 @@ private:
     FileSpec _file_spec;
     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<InstallStep *> InstallPlan;
+  typedef deque<InstallPlan> InstallPlans;
+  InstallPlans _install_plans;
+
+  double _total_plan_size;
+  double _total_plan_completed;
+  double _download_progress;
+  double _current_step_effort;
+
   void begin_info_download();
   void begin_info_download();
   void download_contents_file();
   void download_contents_file();
   void contents_file_download_finished(bool success);
   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 host_got_contents_file();
 
 
   void download_desc_file();
   void download_desc_file();
   void desc_file_download_finished(bool success);
   void desc_file_download_finished(bool success);
   void got_desc_file(TiXmlDocument *doc, bool freshly_downloaded);
   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 uncompress_archive();
-  void extract_archive();
+  void clear_install_plans();
+  void build_install_plans();
+  void follow_install_plans(bool download_finished);
 
 
-  void report_progress(double progress);
+  class InstallStep;
+  void report_progress(InstallStep *step);
   void report_info_ready();
   void report_info_ready();
   void report_done(bool success);
   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;
   bool is_extractable(const string &filename) const;
 
 
 private:
 private:
   P3DHost *_host;
   P3DHost *_host;
+  int _host_contents_seq;
 
 
   string _package_name;
   string _package_name;
   string _package_version;
   string _package_version;
@@ -151,6 +220,7 @@ private:
   bool _ready;
   bool _ready;
   bool _failed;
   bool _failed;
   Download *_active_download;
   Download *_active_download;
+  Download *_saved_download;
 
 
   typedef vector<P3DInstance *> Instances;
   typedef vector<P3DInstance *> Instances;
   Instances _instances;
   Instances _instances;
@@ -158,12 +228,17 @@ private:
   FileSpec _compressed_archive;
   FileSpec _compressed_archive;
   FileSpec _uncompressed_archive;
   FileSpec _uncompressed_archive;
 
 
-  typedef vector<FileSpec> Extracts;
+  size_t _unpack_size;
   Extracts _extracts;
   Extracts _extracts;
 
 
+  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 Download;
+  friend class InstallStep;
   friend class P3DMultifileReader;
   friend class P3DMultifileReader;
-
   friend class P3DHost;
   friend class P3DHost;
 };
 };