1743 lines
56 KiB
C++
Executable File
1743 lines
56 KiB
C++
Executable File
// Filename: p3dPackage.cxx
|
|
// Created by: drose (12Jun09)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "p3dPackage.h"
|
|
#include "p3dInstanceManager.h"
|
|
#include "p3dInstance.h"
|
|
#include "p3dMultifileReader.h"
|
|
#include "p3dTemporaryFile.h"
|
|
#include "p3dPatchFinder.h"
|
|
#include "mkdir_complete.h"
|
|
|
|
#include "zlib.h"
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h> // chmod()
|
|
#endif
|
|
|
|
// 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
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::
|
|
P3DPackage(P3DHost *host, const string &package_name,
|
|
const string &package_version, const string &alt_host) :
|
|
_host(host),
|
|
_package_name(package_name),
|
|
_package_version(package_version),
|
|
_alt_host(alt_host)
|
|
{
|
|
_package_fullname = _package_name;
|
|
if (!_package_version.empty()) {
|
|
_package_fullname += string(".") + _package_version;
|
|
}
|
|
_patch_version = 0;
|
|
|
|
// This is set true if the package is a "solo", i.e. a single
|
|
// file, instead of an xml file and a multifile to unpack.
|
|
_package_solo = false;
|
|
|
|
_host_contents_seq = 0;
|
|
|
|
_xconfig = NULL;
|
|
_temp_contents_file = NULL;
|
|
|
|
_info_ready = false;
|
|
_download_size = 0;
|
|
_allow_data_download = false;
|
|
_ready = false;
|
|
_failed = false;
|
|
_active_download = NULL;
|
|
_saved_download = NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::
|
|
~P3DPackage() {
|
|
// Tell any pending callbacks that we're no good any more.
|
|
report_done(false);
|
|
|
|
// Ditto the outstanding instances.
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
(*ii)->remove_package(this);
|
|
}
|
|
_instances.clear();
|
|
|
|
if (_xconfig != NULL) {
|
|
delete _xconfig;
|
|
_xconfig = NULL;
|
|
}
|
|
|
|
// Cancel any pending download.
|
|
if (_active_download != NULL) {
|
|
_active_download->cancel();
|
|
set_active_download(NULL);
|
|
}
|
|
if (_saved_download != NULL) {
|
|
_saved_download->cancel();
|
|
set_saved_download(NULL);
|
|
}
|
|
|
|
if (_temp_contents_file != NULL) {
|
|
delete _temp_contents_file;
|
|
_temp_contents_file = NULL;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::activate_download
|
|
// Access: Public
|
|
// Description: Authorizes the package to begin downloading and
|
|
// unpacking the meat of its data. Until this is
|
|
// called, the package will download its file
|
|
// information only, and then wait.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
activate_download() {
|
|
_allow_data_download = true;
|
|
|
|
if (_ready) {
|
|
// If we've already been downloaded, we can report that now.
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
(*ii)->report_package_done(this, true);
|
|
}
|
|
|
|
} else {
|
|
// Otherwise, if we've already got the desc file, then start the
|
|
// download.
|
|
if (_info_ready) {
|
|
follow_install_plans(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::get_formatted_name
|
|
// Access: Public
|
|
// Description: Returns the name of this package, for output to the
|
|
// user. This will be the "public" name of the package,
|
|
// as formatted for user consumption; it will include
|
|
// capital letters and spaces where appropriate.
|
|
////////////////////////////////////////////////////////////////////
|
|
string P3DPackage::
|
|
get_formatted_name() const {
|
|
ostringstream strm;
|
|
|
|
if (!_package_display_name.empty()) {
|
|
strm << _package_display_name;
|
|
} else {
|
|
strm << _package_name;
|
|
if (!_package_version.empty()) {
|
|
strm << " " << _package_version;
|
|
}
|
|
}
|
|
|
|
if (_patch_version != 0) {
|
|
strm << " rev " << _patch_version;
|
|
}
|
|
|
|
return strm.str();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::add_instance
|
|
// Access: Public
|
|
// Description: Specifies an instance that that will be using this
|
|
// package, and may be responsible for downloading it.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
add_instance(P3DInstance *inst) {
|
|
_instances.push_back(inst);
|
|
|
|
begin_info_download();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::remove_instance
|
|
// Access: Public
|
|
// Description: Indicates that the given instance is no longer
|
|
// interested in this package and will not be
|
|
// responsible for downloading it.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
remove_instance(P3DInstance *inst) {
|
|
assert(!_instances.empty());
|
|
|
|
if (inst == _instances[0]) {
|
|
// This was the primary instance. Cancel any pending download and
|
|
// move to the next instance.
|
|
if (_active_download != NULL) {
|
|
_active_download->cancel();
|
|
set_active_download(NULL);
|
|
}
|
|
}
|
|
|
|
Instances::iterator ii = find(_instances.begin(), _instances.end(), inst);
|
|
assert(ii != _instances.end());
|
|
_instances.erase(ii);
|
|
|
|
begin_info_download();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::uninstall
|
|
// Access: Public
|
|
// Description: Removes the package directory and all its contents
|
|
// from the user's hard disk.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
uninstall() {
|
|
if (_package_dir.empty()) {
|
|
nout << "Cannot uninstall " << _package_name << ": package directory not yet known.\n";
|
|
return;
|
|
}
|
|
|
|
nout << "Uninstalling package " << _package_name << " from " << _package_dir << "\n";
|
|
|
|
// First, make sure that all instances that are sharing this package
|
|
// are stopped, so there will be no access conflicts preventing us
|
|
// from removing the files. This is particularly important on
|
|
// Windows.
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
P3DInstance *inst = (*ii);
|
|
P3DSession *session = inst->get_session();
|
|
if (session != NULL) {
|
|
nout << "Stopping session " << session << "\n";
|
|
session->shutdown();
|
|
}
|
|
inst->set_failed();
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->delete_directory_recursively(_package_dir);
|
|
|
|
_info_ready = false;
|
|
_ready = false;
|
|
_failed = false;
|
|
_allow_data_download = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::make_xml
|
|
// Access: Public
|
|
// Description: Returns a newly-allocated XML structure that
|
|
// corresponds to the package data within this
|
|
// instance.
|
|
////////////////////////////////////////////////////////////////////
|
|
TiXmlElement *P3DPackage::
|
|
make_xml() {
|
|
TiXmlElement *xpackage = new TiXmlElement("package");
|
|
|
|
xpackage->SetAttribute("name", _package_name);
|
|
if (!_package_platform.empty()) {
|
|
xpackage->SetAttribute("platform", _package_platform);
|
|
}
|
|
if (!_package_version.empty()) {
|
|
xpackage->SetAttribute("version", _package_version);
|
|
}
|
|
xpackage->SetAttribute("host", _host->get_host_url());
|
|
xpackage->SetAttribute("host_dir", _host->get_host_dir());
|
|
|
|
return xpackage;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::begin_info_download
|
|
// Access: Private
|
|
// Description: Begins downloading and installing the information
|
|
// about the package, including its file size and
|
|
// download source and such, if needed. This is
|
|
// generally a very small download.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
begin_info_download() {
|
|
if (_instances.empty()) {
|
|
// Can't download without any instances.
|
|
return;
|
|
}
|
|
|
|
if (_info_ready) {
|
|
// Already downloaded.
|
|
report_info_ready();
|
|
return;
|
|
}
|
|
|
|
if (_active_download != NULL) {
|
|
// In the middle of downloading.
|
|
return;
|
|
}
|
|
|
|
download_contents_file();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::download_contents_file
|
|
// Access: Private
|
|
// Description: Starts downloading the root-level contents.xml file.
|
|
// 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() {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (!_host->has_contents_file() && !inst_mgr->get_verify_contents()) {
|
|
// If we're allowed to read a contents file without checking the
|
|
// server first, try it now.
|
|
_host->read_contents_file();
|
|
}
|
|
|
|
if (_host->has_contents_file()) {
|
|
// We've already got a contents.xml file; go straight to the
|
|
// package desc file.
|
|
host_got_contents_file();
|
|
return;
|
|
}
|
|
|
|
// Download contents.xml to a temporary filename first, in case
|
|
// multiple packages are downloading it simultaneously.
|
|
if (_temp_contents_file != NULL) {
|
|
delete _temp_contents_file;
|
|
_temp_contents_file = NULL;
|
|
}
|
|
_temp_contents_file = new P3DTemporaryFile(".xml");
|
|
|
|
start_download(DT_contents_file, "contents.xml",
|
|
_temp_contents_file->get_filename(), FileSpec());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::contents_file_download_finished
|
|
// Access: Private
|
|
// Description: Called when the contents.xml file has been fully
|
|
// downloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
contents_file_download_finished(bool success) {
|
|
if (!_host->has_contents_file()) {
|
|
if (!success || !_host->read_contents_file(_temp_contents_file->get_filename())) {
|
|
nout << "Couldn't read " << *_temp_contents_file << "\n";
|
|
|
|
// Maybe we can read an already-downloaded contents.xml file.
|
|
string standard_filename = _host->get_host_dir() + "/contents.xml";
|
|
if (_host->get_host_dir().empty() ||
|
|
!_host->read_contents_file(standard_filename)) {
|
|
// Couldn't even read that. Fail.
|
|
report_done(false);
|
|
delete _temp_contents_file;
|
|
_temp_contents_file = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The file is correctly installed by now; we can remove the
|
|
// temporary file.
|
|
delete _temp_contents_file;
|
|
_temp_contents_file = NULL;
|
|
|
|
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_desc_file(), or from
|
|
// Download::download_finished(). If the former, the
|
|
// download pointer will be NULL.
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
set_saved_download(download);
|
|
|
|
// 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;
|
|
|
|
if (contents_changed) {
|
|
// OK, the contents.xml has changed; this means we have to restart
|
|
// the whole download process from the beginning.
|
|
nout << "Redownloading contents.xml made a difference.\n";
|
|
set_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.
|
|
nout << "Redownloading contents.xml didn't help.\n";
|
|
Download *download = _saved_download;
|
|
_saved_download = NULL;
|
|
if (download == NULL) {
|
|
// But, if _saved_download was NULL (meaning NULL was passed to
|
|
// redownload_contents_file(), above), it means that we were
|
|
// called from download_desc_file(), and there's nothing more to
|
|
// do. We're just hosed.
|
|
report_done(false);
|
|
|
|
} else {
|
|
download->resume_download_finished(false);
|
|
p3d_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. This begins the
|
|
// rest of the download process.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
host_got_contents_file() {
|
|
if (!_alt_host.empty()) {
|
|
// If we have an alt host specification, maybe we need to change
|
|
// the host now.
|
|
P3DHost *new_host = _host->get_alt_host(_alt_host);
|
|
nout << "Migrating " << get_package_name() << " to alt_host "
|
|
<< _alt_host << ": " << new_host->get_host_url() << "\n";
|
|
if (new_host != _host) {
|
|
_host->migrate_package(this, _alt_host, new_host);
|
|
_host = new_host;
|
|
}
|
|
|
|
// Clear the alt_host string now that we're migrated to our final
|
|
// host.
|
|
_alt_host.clear();
|
|
|
|
if (!_host->has_contents_file()) {
|
|
// Now go back and get the contents.xml file for the new host.
|
|
download_contents_file();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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()) {
|
|
_package_dir += string("/") + _package_version;
|
|
}
|
|
|
|
// Ensure the package directory exists; create it if it does not.
|
|
mkdir_complete(_package_dir, nout);
|
|
download_desc_file();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::download_desc_file
|
|
// Access: Private
|
|
// Description: Starts downloading the desc file for the package, if
|
|
// it's needed; or read the local version if it's fresh
|
|
// enough.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
download_desc_file() {
|
|
assert(!_package_dir.empty());
|
|
|
|
// Attempt to check the desc file for freshness. If it already
|
|
// exists, and is consistent with the server contents file, we don't
|
|
// need to re-download it.
|
|
if (!_host->get_package_desc_file(_desc_file, _package_platform,
|
|
_package_solo,
|
|
_package_name, _package_version)) {
|
|
nout << "Couldn't find package " << _package_fullname
|
|
<< " in contents file.\n";
|
|
redownload_contents_file(NULL);
|
|
return;
|
|
}
|
|
|
|
string url_filename = _desc_file.get_filename();
|
|
|
|
_desc_file_dirname = "";
|
|
_desc_file_basename = url_filename;
|
|
size_t slash = _desc_file_basename.rfind('/');
|
|
if (slash != string::npos) {
|
|
_desc_file_dirname = _desc_file_basename.substr(0, slash);
|
|
_desc_file_basename = _desc_file_basename.substr(slash + 1);
|
|
}
|
|
|
|
// The desc file might have a different path on the host server than
|
|
// it has locally, because we strip out the platform directory
|
|
// locally.
|
|
FileSpec local_desc_file = _desc_file;
|
|
local_desc_file.set_filename(_desc_file_basename);
|
|
_desc_file_pathname = local_desc_file.get_pathname(_package_dir);
|
|
|
|
if (!local_desc_file.full_verify(_package_dir)) {
|
|
nout << _desc_file_pathname << " is stale.\n";
|
|
|
|
} else {
|
|
// The desc file is current. Attempt to read it.
|
|
if (_package_solo) {
|
|
// No need to load it: the desc file *is* the package.
|
|
report_done(true);
|
|
return;
|
|
} else {
|
|
TiXmlDocument doc(_desc_file_pathname.c_str());
|
|
if (doc.LoadFile()) {
|
|
got_desc_file(&doc, false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The desc file is not current. Go download it.
|
|
start_download(DT_desc_file, _desc_file.get_filename(),
|
|
_desc_file_pathname, local_desc_file);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::desc_file_download_finished
|
|
// Access: Private
|
|
// Description: Called when the desc file has been fully downloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
desc_file_download_finished(bool success) {
|
|
if (!success) {
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
// Now that we've downloaded the desc file, make it read-only.
|
|
chmod(_desc_file_pathname.c_str(), 0444);
|
|
|
|
if (_package_solo) {
|
|
// No need to load it: the desc file *is* the package.
|
|
report_done(true);
|
|
return;
|
|
|
|
} else {
|
|
TiXmlDocument doc(_desc_file_pathname.c_str());
|
|
if (!doc.LoadFile()) {
|
|
nout << "Couldn't read " << _desc_file_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
got_desc_file(&doc, true);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::got_desc_file
|
|
// Access: Private
|
|
// Description: Reads the desc file and begins verifying the files.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
|
|
TiXmlElement *xpackage = doc->FirstChildElement("package");
|
|
if (xpackage == NULL) {
|
|
nout << _package_name << " desc file contains no <package>\n";
|
|
if (!freshly_downloaded) {
|
|
download_desc_file();
|
|
return;
|
|
}
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
xpackage->Attribute("patch_version", &_patch_version);
|
|
|
|
TiXmlElement *xconfig = xpackage->FirstChildElement("config");
|
|
if (xconfig != NULL) {
|
|
const char *display_name_cstr = xconfig->Attribute("display_name");
|
|
if (display_name_cstr != NULL) {
|
|
_package_display_name = display_name_cstr;
|
|
}
|
|
|
|
// Save the config entry within this class for others to query.
|
|
_xconfig = (TiXmlElement *)xconfig->Clone();
|
|
}
|
|
|
|
TiXmlElement *xuncompressed_archive =
|
|
xpackage->FirstChildElement("uncompressed_archive");
|
|
TiXmlElement *xcompressed_archive =
|
|
xpackage->FirstChildElement("compressed_archive");
|
|
|
|
if (xuncompressed_archive == NULL || xcompressed_archive == NULL) {
|
|
// The desc file didn't include the archive file itself, weird.
|
|
if (!freshly_downloaded) {
|
|
download_desc_file();
|
|
return;
|
|
}
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
_uncompressed_archive.load_xml(xuncompressed_archive);
|
|
_compressed_archive.load_xml(xcompressed_archive);
|
|
|
|
// Now get all the extractable components.
|
|
_unpack_size = 0;
|
|
_extracts.clear();
|
|
TiXmlElement *xextract = xpackage->FirstChildElement("extract");
|
|
while (xextract != NULL) {
|
|
FileSpec file;
|
|
file.load_xml(xextract);
|
|
_extracts.push_back(file);
|
|
_unpack_size += file.get_size();
|
|
xextract = xextract->NextSiblingElement("extract");
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
// Get the required packages.
|
|
_requires.clear();
|
|
TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
|
|
while (xrequires != NULL) {
|
|
const char *package_name = xrequires->Attribute("name");
|
|
const char *version = xrequires->Attribute("version");
|
|
const char *host_url = xrequires->Attribute("host");
|
|
if (package_name != NULL && host_url != NULL) {
|
|
P3DHost *host = inst_mgr->get_host(host_url);
|
|
if (version == NULL) {
|
|
version = "";
|
|
}
|
|
_requires.push_back(RequiredPackage(package_name, version, host));
|
|
}
|
|
|
|
xrequires = xrequires->NextSiblingElement("requires");
|
|
}
|
|
|
|
// Get a list of all of the files in the directory, so we can remove
|
|
// files that don't belong.
|
|
vector<string> contents, dirname_contents;
|
|
inst_mgr->scan_directory_recursively(_package_dir, contents, dirname_contents);
|
|
|
|
inst_mgr->remove_file_from_list(contents, _desc_file_basename);
|
|
inst_mgr->remove_file_from_list(contents, _uncompressed_archive.get_filename());
|
|
Extracts::iterator ei;
|
|
for (ei = _extracts.begin(); ei != _extracts.end(); ++ei) {
|
|
inst_mgr->remove_file_from_list(contents, (*ei).get_filename());
|
|
}
|
|
|
|
// Now, any files that are still in the contents list don't belong.
|
|
// It's important to remove these files before we start verifying
|
|
// the files that we expect to find here, in case there is a problem
|
|
// with ambiguous filenames or something (e.g. case insensitivity).
|
|
vector<string>::iterator ci;
|
|
for (ci = contents.begin(); ci != contents.end(); ++ci) {
|
|
string filename = (*ci);
|
|
nout << "Removing " << filename << "\n";
|
|
string pathname = _package_dir + "/" + filename;
|
|
|
|
#ifdef _WIN32
|
|
// Windows can't delete a file if it's read-only.
|
|
chmod(pathname.c_str(), 0644);
|
|
#endif
|
|
unlink(pathname.c_str());
|
|
}
|
|
|
|
// Verify the uncompressed archive.
|
|
bool all_extracts_ok = true;
|
|
if (!_uncompressed_archive.quick_verify(_package_dir)) {
|
|
nout << "File is incorrect: " << _uncompressed_archive.get_filename() << "\n";
|
|
all_extracts_ok = false;
|
|
}
|
|
|
|
// Verify all of the extracts.
|
|
for (ei = _extracts.begin(); ei != _extracts.end() && all_extracts_ok; ++ei) {
|
|
if (!(*ei).quick_verify(_package_dir)) {
|
|
nout << "File is incorrect: " << (*ei).get_filename() << "\n";
|
|
all_extracts_ok = false;
|
|
}
|
|
|
|
// Make sure all extracts are still marked executable.
|
|
chmod((*ei).get_pathname(_package_dir).c_str(), 0555);
|
|
}
|
|
|
|
if (all_extracts_ok) {
|
|
// Great, we're ready to begin.
|
|
nout << "All " << _extracts.size() << " extracts of " << _package_name
|
|
<< " seem good.\n";
|
|
|
|
report_done(true);
|
|
|
|
} else {
|
|
// We need to get the file data still, but at least we know all
|
|
// about it by this point.
|
|
build_install_plans(doc);
|
|
|
|
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.
|
|
follow_install_plans(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::clear_install_plans
|
|
// Access: Private
|
|
// Description: 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::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(TiXmlDocument *doc) {
|
|
clear_install_plans();
|
|
|
|
if (_instances.empty()) {
|
|
// Can't download without any instances.
|
|
return;
|
|
}
|
|
|
|
if (_ready) {
|
|
// Already downloaded.
|
|
return;
|
|
}
|
|
|
|
_install_plans.push_front(InstallPlan());
|
|
InstallPlan &plan = _install_plans.front();
|
|
|
|
bool needs_redownload = false;
|
|
|
|
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.
|
|
needs_redownload = true;
|
|
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, true);
|
|
plan.push_back(step);
|
|
}
|
|
|
|
// Unpack the uncompressed archive.
|
|
step = new InstallStepUnpackArchive(this, _unpack_size);
|
|
plan.push_back(step);
|
|
|
|
if (needs_redownload) {
|
|
// Since we need to do some downloading, try to build a plan that
|
|
// involves downloading patches instead of downloading the whole
|
|
// file. This will be our first choice, plan A, if we can do it.
|
|
|
|
// We'll need the md5 hash of the uncompressed archive currently
|
|
// on disk.
|
|
|
|
// Maybe we've already read the md5 hash and we have it stored here.
|
|
const FileSpec *on_disk_ptr = _uncompressed_archive.get_actual_file();
|
|
FileSpec on_disk;
|
|
if (on_disk_ptr == NULL) {
|
|
// If not, we have to go read it now.
|
|
if (on_disk.read_hash(_uncompressed_archive.get_pathname(_package_dir))) {
|
|
on_disk_ptr = &on_disk;
|
|
}
|
|
}
|
|
|
|
if (on_disk_ptr != NULL) {
|
|
P3DPatchFinder patch_finder;
|
|
P3DPatchFinder::Patchfiles chain;
|
|
if (patch_finder.get_patch_chain_to_current(chain, doc, *on_disk_ptr)) {
|
|
nout << "Got patch chain of length " << chain.size() << "\n";
|
|
|
|
// OK, we can create a plan to download and apply the patches.
|
|
_install_plans.push_front(InstallPlan());
|
|
InstallPlan &plan = _install_plans.front();
|
|
|
|
P3DPatchFinder::Patchfiles::iterator pi;
|
|
for (pi = chain.begin(); pi != chain.end(); ++pi) {
|
|
P3DPatchFinder::Patchfile *patchfile = (*pi);
|
|
|
|
// Download the patchfile
|
|
step = new InstallStepDownloadFile(this, patchfile->_file);
|
|
plan.push_back(step);
|
|
|
|
// Uncompress it
|
|
FileSpec new_file = patchfile->_file;
|
|
string new_filename = new_file.get_filename();
|
|
size_t dot = new_filename.rfind('.');
|
|
assert(new_filename.substr(dot) == ".pz");
|
|
new_filename = new_filename.substr(0, dot);
|
|
new_file.set_filename(new_filename);
|
|
step = new InstallStepUncompressFile
|
|
(this, patchfile->_file, new_file, false);
|
|
plan.push_back(step);
|
|
|
|
// And apply it
|
|
FileSpec source_file = patchfile->_source_file;
|
|
FileSpec target_file = patchfile->_target_file;
|
|
source_file.set_filename(_uncompressed_archive.get_filename());
|
|
target_file.set_filename(_uncompressed_archive.get_filename());
|
|
step = new InstallStepApplyPatch
|
|
(this, new_file, source_file, target_file);
|
|
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;
|
|
}
|
|
|
|
while (!_install_plans.empty()) {
|
|
// Pull the next step off the current plan.
|
|
|
|
InstallPlan &plan = _install_plans.front();
|
|
bool plan_failed = false;
|
|
|
|
_total_plan_size = 0.0;
|
|
InstallPlan::iterator si;
|
|
for (si = plan.begin(); si != plan.end(); ++si) {
|
|
_total_plan_size += (*si)->get_effort();
|
|
}
|
|
|
|
_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_terminate:
|
|
// All plans have failed.
|
|
_install_plans.clear();
|
|
report_done(false);
|
|
return;
|
|
|
|
case IT_continue:
|
|
// A callback hook has been attached; we'll come back later.
|
|
return;
|
|
|
|
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 (!plan_failed) {
|
|
// We've finished the plan successfully.
|
|
clear_install_plans();
|
|
report_done(true);
|
|
return;
|
|
}
|
|
|
|
// That plan failed. Go on to the next plan.
|
|
nout << "Plan failed.\n";
|
|
_install_plans.pop_front();
|
|
}
|
|
|
|
// All plans failed. Too bad for us.
|
|
report_done(false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::report_progress
|
|
// Access: Private
|
|
// Description: Reports the current install progress to all
|
|
// interested instances.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
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, _download_progress);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::report_info_ready
|
|
// Access: Private
|
|
// Description: Called when the package information has been
|
|
// successfully downloaded but activate_download() has
|
|
// not yet been called, and the package is now idle,
|
|
// waiting for activate_download() to be called.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
report_info_ready() {
|
|
_info_ready = true;
|
|
_download_size = _compressed_archive.get_size();
|
|
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
(*ii)->report_package_info_ready(this);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::report_done
|
|
// Access: Private
|
|
// Description: Transitions the package to "ready" or "failure"
|
|
// state, and reports this change to all the interested
|
|
// instances.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
report_done(bool success) {
|
|
if (success) {
|
|
_info_ready = true;
|
|
_ready = true;
|
|
_failed = false;
|
|
} else {
|
|
_ready = false;
|
|
_failed = true;
|
|
}
|
|
|
|
if (!_allow_data_download && success) {
|
|
// If we haven't been authorized to start downloading yet, just
|
|
// report that we're ready to start, but that we don't have to
|
|
// download anything.
|
|
_download_size = 0;
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
(*ii)->report_package_info_ready(this);
|
|
}
|
|
|
|
} else {
|
|
// Otherwise, we can report that we're fully downloaded.
|
|
Instances::iterator ii;
|
|
for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
|
|
(*ii)->report_package_done(this, success);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::start_download
|
|
// Access: Private
|
|
// Description: Initiates a download of the indicated file. Returns
|
|
// the new Download object.
|
|
////////////////////////////////////////////////////////////////////
|
|
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);
|
|
|
|
// 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);
|
|
#endif
|
|
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, 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);
|
|
|
|
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;
|
|
_host->choose_random_mirrors(mirrors, 2);
|
|
for (vector<string>::iterator si = mirrors.begin();
|
|
si != mirrors.end();
|
|
++si) {
|
|
url = (*si) + urlbase;
|
|
download->_try_urls.push_back(url);
|
|
}
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
if (dtype == DT_redownload_contents_file) {
|
|
// When we're redownloading the contents file after a download
|
|
// error, we always go straight to the authoritative host, not
|
|
// even to the super-mirror.
|
|
|
|
} else {
|
|
// In other cases, if the "super mirror" is enabled, we try that
|
|
// first.
|
|
if (!inst_mgr->get_super_mirror().empty()) {
|
|
string url = inst_mgr->get_super_mirror() + urlbase;
|
|
download->_try_urls.push_back(url);
|
|
}
|
|
}
|
|
|
|
// OK, start the download.
|
|
assert(!download->_try_urls.empty());
|
|
url = download->_try_urls.back();
|
|
download->_try_urls.pop_back();
|
|
download->set_url(url);
|
|
download->set_filename(pathname);
|
|
|
|
set_active_download(download);
|
|
assert(!_instances.empty());
|
|
|
|
_instances[0]->start_download(download);
|
|
return download;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::set_active_download
|
|
// Access: Private
|
|
// Description: Changes _active_download to point to the indicated
|
|
// object, respecting reference counts.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
set_active_download(Download *download) {
|
|
if (_active_download != download) {
|
|
if (_active_download != NULL) {
|
|
p3d_unref_delete(_active_download);
|
|
}
|
|
_active_download = download;
|
|
if (_active_download != NULL) {
|
|
_active_download->ref();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::set_saved_download
|
|
// Access: Private
|
|
// Description: Changes _saved_download to point to the indicated
|
|
// object, respecting reference counts.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
set_saved_download(Download *download) {
|
|
if (_saved_download != download) {
|
|
if (_saved_download != NULL) {
|
|
p3d_unref_delete(_saved_download);
|
|
}
|
|
_saved_download = download;
|
|
if (_saved_download != NULL) {
|
|
_saved_download->ref();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::is_extractable
|
|
// Access: Private
|
|
// Description: Returns true if the name file is on the extract list,
|
|
// false otherwise. If true, fills in the FileSpec with
|
|
// the file's information.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::
|
|
is_extractable(FileSpec &file, const string &filename) const {
|
|
Extracts::const_iterator ei;
|
|
for (ei = _extracts.begin(); ei != _extracts.end(); ++ei) {
|
|
if ((*ei).get_filename() == filename) {
|
|
file = (*ei);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::instance_terminating
|
|
// Access: Private
|
|
// Description: Called when P3D_RC_shutdown is received by any
|
|
// Download object, which indicates that the instance
|
|
// owning this download object is terminating and we
|
|
// should either find a new instance or abort the
|
|
// download.
|
|
//
|
|
// The return value is true if a new instance is
|
|
// available, or false if not.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::
|
|
instance_terminating(P3DInstance *instance) {
|
|
if (_instances.empty() ||
|
|
(_instances.size() == 1 && instance == _instances[0])) {
|
|
// No other instances.
|
|
return false;
|
|
}
|
|
|
|
// There are more instances available to continue this download;
|
|
// pick one of them. Move this one to the end of the list.
|
|
Instances::iterator ii = find(_instances.begin(), _instances.end(), instance);
|
|
if (ii != _instances.end()) {
|
|
_instances.erase(ii);
|
|
_instances.push_back(instance);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::Download::
|
|
Download(P3DPackage *package, DownloadType dtype, const FileSpec &file_spec) :
|
|
_package(package),
|
|
_dtype(dtype),
|
|
_file_spec(file_spec)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::Copy Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::Download::
|
|
Download(const P3DPackage::Download ©) :
|
|
P3DFileDownload(copy),
|
|
_try_urls(copy._try_urls),
|
|
_package(copy._package),
|
|
_dtype(copy._dtype),
|
|
_file_spec(copy._file_spec)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::download_progress
|
|
// Access: Protected, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::Download::
|
|
download_progress() {
|
|
P3DFileDownload::download_progress();
|
|
assert(_package->_active_download == this);
|
|
|
|
switch (_dtype) {
|
|
case DT_desc_file:
|
|
break;
|
|
|
|
case DT_install_step:
|
|
_package->follow_install_plans(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::download_finished
|
|
// Access: Protected, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::Download::
|
|
download_finished(bool success) {
|
|
P3DFileDownload::download_finished(success);
|
|
assert(_package->_active_download == this);
|
|
if (get_ref_count() == 1) {
|
|
// No one cares anymore.
|
|
nout << "No one cares about " << get_url() << "\n";
|
|
_package->set_active_download(NULL);
|
|
return;
|
|
}
|
|
|
|
_package->set_active_download(NULL);
|
|
assert(get_ref_count() > 0);
|
|
|
|
if (success && !_file_spec.get_filename().empty()) {
|
|
// We think we downloaded it correctly. Check the hash to be
|
|
// sure.
|
|
if (!_file_spec.full_verify(_package->_package_dir)) {
|
|
nout << "After downloading " << get_url()
|
|
<< ", failed hash check\n";
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
close_file();
|
|
|
|
if (!success) {
|
|
if (get_download_terminated()) {
|
|
// Short-circuit the exit.
|
|
_try_urls.clear();
|
|
resume_download_finished(false);
|
|
return;
|
|
}
|
|
|
|
// 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()) {
|
|
// Try the next mirror.
|
|
string url = _try_urls.back();
|
|
_try_urls.pop_back();
|
|
|
|
clear();
|
|
set_url(url);
|
|
set_filename(get_filename());
|
|
_package->set_active_download(this);
|
|
|
|
assert(!_package->_instances.empty());
|
|
_package->_instances[0]->start_download(this);
|
|
return;
|
|
}
|
|
|
|
switch (_dtype) {
|
|
case DT_contents_file:
|
|
_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_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) {
|
|
p3d_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 if (_download->get_download_terminated()) {
|
|
// The download was interrupted because its instance is shutting
|
|
// down. Don't try any other plans, unless we have some more
|
|
// instances.
|
|
P3DInstance *instance = _download->get_instance();
|
|
if (!_package->instance_terminating(instance)) {
|
|
// That was the only instance referencing this package, so stop
|
|
// the download.
|
|
nout << "Terminating download of " << _urlbase << "\n";
|
|
return IT_terminate;
|
|
}
|
|
nout << "Restarting download of " << _urlbase << " on new instance\n";
|
|
|
|
p3d_unref_delete(_download);
|
|
_download = NULL;
|
|
return IT_continue;
|
|
|
|
} else {
|
|
// The Download object has already tried all of the mirrors, and
|
|
// they all failed.
|
|
return IT_step_failed;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepDownloadFile::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::InstallStepDownloadFile::
|
|
output(ostream &out) {
|
|
out << "InstallStepDownloadFile(" << _package->get_package_name()
|
|
<< ", " << _file.get_filename() << ")";
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepUncompressFile::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::InstallStepUncompressFile::
|
|
InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
|
|
const FileSpec &target, bool verify_target) :
|
|
InstallStep(package, target.get_size(), _uncompress_factor),
|
|
_source(source),
|
|
_target(target),
|
|
_verify_target(verify_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 (_verify_target && !_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::InstallStepUncompressFile::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::InstallStepUncompressFile::
|
|
output(ostream &out) {
|
|
out << "InstallStepUncompressFile(" << _package->get_package_name()
|
|
<< ", " << _source.get_filename() << ", " << _target.get_filename()
|
|
<< ", " << _verify_target << ")";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepUnpackArchive::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::InstallStepUnpackArchive::
|
|
output(ostream &out) {
|
|
out << "InstallStepUnpackArchive(" << _package->get_package_name() << ")";
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepApplyPatch::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::InstallStepApplyPatch::
|
|
InstallStepApplyPatch(P3DPackage *package, const FileSpec &patchfile,
|
|
const FileSpec &source, const FileSpec &target) :
|
|
InstallStep(package, target.get_size(), _patch_factor),
|
|
_reader(package->get_package_dir(), patchfile, source, target)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepApplyPatch::do_step
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::InstallToken P3DPackage::InstallStepApplyPatch::
|
|
do_step(bool download_finished) {
|
|
// Open the patchfile
|
|
if (!_reader.open_read()) {
|
|
_reader.close();
|
|
return IT_step_failed;
|
|
}
|
|
|
|
// Apply the patch.
|
|
while (_reader.step()) {
|
|
_bytes_done = _reader.get_bytes_written();
|
|
report_step_progress();
|
|
}
|
|
|
|
// Close and verify.
|
|
_reader.close();
|
|
|
|
if (!_reader.get_success()) {
|
|
nout << "Patching failed\n";
|
|
return IT_step_failed;
|
|
}
|
|
|
|
return IT_step_complete;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::InstallStepApplyPatch::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::InstallStepApplyPatch::
|
|
output(ostream &out) {
|
|
out << "InstallStepApplyPatch(" << _package->get_package_name() << ")";
|
|
}
|