open_toontown_panda3d/direct/src/plugin/p3dHost.cxx

586 lines
20 KiB
C++

// Filename: p3dHost.cxx
// Created by: drose (21Aug09)
//
////////////////////////////////////////////////////////////////////
//
// 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 "p3dHost.h"
#include "p3dInstanceManager.h"
#include "p3dPackage.h"
#include "openssl/md5.h"
#include <algorithm>
////////////////////////////////////////////////////////////////////
// Function: P3DHost::Constructor
// Access: Private
// Description: Use P3DInstanceManager::get_host() to construct a new
// P3DHost.
////////////////////////////////////////////////////////////////////
P3DHost::
P3DHost(const string &host_url) :
_host_url(host_url)
{
// Ensure that the download URL ends with a slash.
_host_url_prefix = _host_url;
if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.size() - 1] != '/') {
_host_url_prefix += "/";
}
_download_url_prefix = _host_url_prefix;
_xcontents = NULL;
_contents_seq = 0;
determine_host_dir();
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::Destructor
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
P3DHost::
~P3DHost() {
if (_xcontents != NULL) {
delete _xcontents;
}
Packages::iterator mi;
for (mi = _packages.begin(); mi != _packages.end(); ++mi) {
PackageMap &package_map = (*mi).second;
PackageMap::iterator pi;
for (pi = package_map.begin(); pi != package_map.end(); ++pi) {
delete (*pi).second;
}
}
_packages.clear();
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::get_alt_host
// Access: Public
// Description: Returns the pre-defined alternate host with the
// indicated token, if one is defined for this token, or
// the original host if there is no alternate host
// defined for this token.
//
// This is intended to implement test versions and the
// like, for instance in which a particular p3d file may
// reference a package on one particular host, but there
// is an alternate version to be tested on a different
// host. The HTML code that embeds the p3d file can
// choose to set the alt_host token to redirect the p3d
// file to the alternate host.
//
// The actual URL for the alternate host is embedded
// within the host's contents.xml as a security measure,
// to prevent people from tricking a p3d file into
// running untrusted code by redirecting it to an
// arbitrary URL.
////////////////////////////////////////////////////////////////////
P3DHost *P3DHost::
get_alt_host(const string &alt_host) {
assert(_xcontents != NULL);
AltHosts::iterator hi;
hi = _alt_hosts.find(alt_host);
if (hi != _alt_hosts.end() && (*hi).second != _host_url) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
return inst_mgr->get_host((*hi).second);
}
return this;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::read_contents_file
// Access: Public
// Description: Reads the contents.xml file in the standard
// filename, if possible.
//
// Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DHost::
read_contents_file() {
string standard_filename = _host_dir + "/contents.xml";
return read_contents_file(standard_filename);
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::read_contents_file
// Access: Public
// Description: Reads the contents.xml file in the indicated
// filename. On success, copies the contents.xml file
// into the standard location (if it's not there
// already).
//
// Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool P3DHost::
read_contents_file(const string &contents_filename) {
TiXmlDocument doc(contents_filename.c_str());
if (!doc.LoadFile()) {
return false;
}
TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents == NULL) {
return false;
}
if (_xcontents != NULL) {
delete _xcontents;
}
_xcontents = (TiXmlElement *)xcontents->Clone();
++_contents_seq;
_contents_spec.read_hash(contents_filename);
TiXmlElement *xhost = _xcontents->FirstChildElement("host");
if (xhost != NULL) {
const char *url = xhost->Attribute("url");
if (url != NULL && _host_url == string(url)) {
// We're the primary host. This is the normal case.
read_xhost(xhost);
// Build up the list of alternate hosts.
TiXmlElement *xalthost = xhost->FirstChildElement("alt_host");
while (xalthost != NULL) {
const char *keyword = xalthost->Attribute("keyword");
const char *url = xalthost->Attribute("url");
if (keyword != NULL && url != NULL) {
_alt_hosts[keyword] = url;
}
xalthost = xalthost->NextSiblingElement("alt_host");
}
} else {
// We're not the primary host; perhaps we're an alternate host.
TiXmlElement *xalthost = xhost->FirstChildElement("alt_host");
while (xalthost != NULL) {
const char *url = xalthost->Attribute("url");
if (url != NULL && _host_url == string(url)) {
// Yep, we're this alternate host.
read_xhost(xhost);
break;
}
xalthost = xalthost->NextSiblingElement("alt_host");
}
}
}
string standard_filename = _host_dir + "/contents.xml";
if (standardize_filename(standard_filename) !=
standardize_filename(contents_filename)) {
copy_file(contents_filename, standard_filename);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::get_package
// Access: Public
// Description: Returns a (possibly shared) pointer to the indicated
// package.
////////////////////////////////////////////////////////////////////
P3DPackage *P3DHost::
get_package(const string &package_name, const string &package_version,
const string &alt_host) {
if (!alt_host.empty()) {
if (_xcontents != NULL) {
// If we're asking for an alt host and we've already read our
// contents.xml file, then we already know all of our hosts, and
// we can start the package off with the correct host immediately.
P3DHost *new_host = get_alt_host(alt_host);
return new_host->get_package(package_name, package_version);
}
// If we haven't read contents.xml yet, we need to create the
// package first, then let it be responsible for downloading our
// contents.xml, and it can migrate to its alt_host after that.
}
PackageMap &package_map = _packages[alt_host];
string key = package_name + "_" + package_version;
PackageMap::iterator pi = package_map.find(key);
if (pi != package_map.end()) {
return (*pi).second;
}
P3DPackage *package =
new P3DPackage(this, package_name, package_version, alt_host);
bool inserted = package_map.insert(PackageMap::value_type(key, package)).second;
assert(inserted);
return package;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::get_package_desc_file
// Access: Public
// Description: Fills the indicated FileSpec with the hash
// information for the package's desc file, and also
// determines the package's platform. Returns true if
// successful, false if the package is unknown. This
// requires has_contents_file() to return true in order
// to be successful.
////////////////////////////////////////////////////////////////////
bool P3DHost::
get_package_desc_file(FileSpec &desc_file, // out
string &package_platform, // out
bool &package_solo, // out
const string &package_name, // in
const string &package_version) { // in
if (_xcontents == NULL) {
return false;
}
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
// Scan the contents data for the indicated package. First, we look
// for a platform-specific version.
TiXmlElement *xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
const char *platform = xpackage->Attribute("platform");
const char *version = xpackage->Attribute("version");
const char *solo = xpackage->Attribute("solo");
if (version == NULL) {
version = "";
}
if (name != NULL && platform != NULL &&
package_name == name &&
inst_mgr->get_platform() == platform &&
package_version == version) {
// Here's the matching package definition.
desc_file.load_xml(xpackage);
package_platform = platform;
package_solo = false;
if (solo != NULL) {
package_solo = (atoi(solo) != 0);
}
return true;
}
xpackage = xpackage->NextSiblingElement("package");
}
// Look again, this time looking for a non-platform-specific version.
xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
const char *platform = xpackage->Attribute("platform");
const char *version = xpackage->Attribute("version");
const char *solo = xpackage->Attribute("solo");
if (platform == NULL) {
platform = "";
}
if (version == NULL) {
version = "";
}
if (name != NULL &&
package_name == name &&
*platform == '\0' &&
package_version == version) {
// Here's the matching package definition.
desc_file.load_xml(xpackage);
package_platform = platform;
package_solo = false;
if (solo != NULL) {
package_solo = (atoi(solo) != 0);
}
return true;
}
xpackage = xpackage->NextSiblingElement("package");
}
// Couldn't find the named package.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::migrate_package
// Access: Public
// Description: This is called by P3DPackage when it migrates from
// this host to its final alt_host, after downloading
// the contents.xml file for this file and learning the
// true URL for its target alt_host.
////////////////////////////////////////////////////////////////////
void P3DHost::
migrate_package(P3DPackage *package, const string &alt_host, P3DHost *new_host) {
assert(new_host != this);
assert(new_host == get_alt_host(alt_host));
string key = package->get_package_name() + "_" + package->get_package_version();
PackageMap &package_map = _packages[alt_host];
PackageMap::iterator pi = package_map.find(key);
assert(pi != package_map.end());
package_map.erase(pi);
PackageMap &new_package_map = new_host->_packages[""];
bool inserted = new_package_map.insert(PackageMap::value_type(key, package)).second;
assert(inserted);
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::choose_random_mirrors
// Access: Public
// Description: Selects num_mirrors elements, chosen at random, from
// the _mirrors list. Adds the selected mirrors to
// result. If there are fewer than num_mirrors elements
// in the list, adds only as many mirrors as we can get.
////////////////////////////////////////////////////////////////////
void P3DHost::
choose_random_mirrors(vector<string> &result, int num_mirrors) {
vector<size_t> selected;
size_t num_to_select = min(_mirrors.size(), (size_t)num_mirrors);
while (num_to_select > 0) {
size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
while (find(selected.begin(), selected.end(), i) != selected.end()) {
// Already found this i, find a new one.
i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
}
selected.push_back(i);
result.push_back(_mirrors[i]);
--num_to_select;
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::add_mirror
// Access: Public
// Description: Adds a new URL to serve as a mirror for this host.
// The mirrors will be consulted first, before
// consulting the host directly.
////////////////////////////////////////////////////////////////////
void P3DHost::
add_mirror(string mirror_url) {
// Ensure the URL ends in a slash.
if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') {
mirror_url += '/';
}
// Add it to the _mirrors list, but only if it's not already
// there.
if (find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) {
_mirrors.push_back(mirror_url);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::determine_host_dir
// Access: Private
// Description: Hashes the host_url into a (mostly) unique directory
// string, which will be the root of the host's install
// tree. Stores the result in _host_dir.
//
// This code is duplicated in Python, in
// HostInfo.determineHostDir().
////////////////////////////////////////////////////////////////////
void P3DHost::
determine_host_dir() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
_host_dir = inst_mgr->get_root_dir();
_host_dir += "/";
string hostname;
// Look for a server name in the URL. Including this string in the
// directory name makes it friendlier for people browsing the
// directory.
// We can't use URLSpec here, because we don't link with Panda3D.
// We have to do it by hand.
size_t p = _host_url.find("://");
if (p != string::npos) {
size_t start = p + 3;
size_t end = _host_url.find("/", start);
// Now start .. end is something like "username@host:port".
size_t at = _host_url.find("@", start);
if (at < end) {
start = at + 1;
}
size_t colon = _host_url.find(":", start);
if (colon < end) {
end = colon;
}
// Now start .. end is just the hostname.
hostname = _host_url.substr(start, end - start);
}
// Now build a hash string of the whole URL. We'll use MD5 to get a
// pretty good hash, with a minimum chance of collision. Even if
// there is a hash collision, though, it's not the end of the world;
// it just means that both hosts will dump their packages into the
// same directory, and they'll fight over the toplevel contents.xml
// file. Assuming they use different version numbers (which should
// be safe since they have the same hostname), there will be minimal
// redownloading.
static const size_t hash_size = 16;
unsigned char md[hash_size];
size_t keep_hash = hash_size;
if (!hostname.empty()) {
_host_dir += hostname;
_host_dir += "_";
// If we successfully got a hostname, we don't really need the
// full hash. We'll keep half of it.
keep_hash = keep_hash / 2;
}
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, _host_url.data(), _host_url.size());
MD5_Final(md, &ctx);
for (size_t i = 0; i < keep_hash; ++i) {
int high = (md[i] >> 4) & 0xf;
int low = md[i] & 0xf;
_host_dir += P3DInstanceManager::encode_hexdigit(high);
_host_dir += P3DInstanceManager::encode_hexdigit(low);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::read_xhost
// Access: Private
// Description: Reads the host data from the <host> (or <alt_host>)
// entry in the contents.xml file.
////////////////////////////////////////////////////////////////////
void P3DHost::
read_xhost(TiXmlElement *xhost) {
const char *descriptive_name = xhost->Attribute("descriptive_name");
if (descriptive_name != NULL && _descriptive_name.empty()) {
_descriptive_name = descriptive_name;
}
// Get the "download" URL, which is the source from which we
// download everything other than the contents.xml file.
const char *download_url = xhost->Attribute("download_url");
if (download_url != NULL) {
_download_url_prefix = download_url;
}
if (!_download_url_prefix.empty()) {
if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') {
_download_url_prefix += "/";
}
} else {
_download_url_prefix = _host_url_prefix;
}
TiXmlElement *xmirror = xhost->FirstChildElement("mirror");
while (xmirror != NULL) {
const char *url = xmirror->Attribute("url");
if (url != NULL) {
add_mirror(url);
}
xmirror = xmirror->NextSiblingElement("mirror");
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::standardize_filename
// Access: Private, Static
// Description: Attempts to change the filename into some standard
// form for comparison with other filenames. On a
// case-insensitive filesystem, this converts the
// filename to lowercase. On Windows, it further
// replaces forward slashes with backslashes.
////////////////////////////////////////////////////////////////////
string P3DHost::
standardize_filename(const string &filename) {
#if defined(_WIN32) || defined(__APPLE__)
string new_filename;
for (string::const_iterator si = filename.begin();
si != filename.end();
++si) {
char ch = *si;
#ifdef _WIN32
if (ch == '/') {
ch = '\\';
}
#endif // _WIN32
new_filename += tolower(ch);
}
return new_filename;
#else // _WIN32 || __APPLE__
return filename;
#endif // _WIN32 || __APPLE__
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::copy_file
// Access: Private, Static
// Description: Copies the data in the file named by from_filename
// into the file named by to_filename.
////////////////////////////////////////////////////////////////////
bool P3DHost::
copy_file(const string &from_filename, const string &to_filename) {
ifstream in(from_filename.c_str(), ios::in | ios::binary);
// Copy to a temporary file first, in case (a) the filenames
// actually refer to the same file, or (b) in case we have different
// processes writing to the same file, and (c) to prevent
// partially overwriting the file should something go wrong.
ostringstream strm;
strm << to_filename << ".t";
#ifdef _WIN32
strm << GetCurrentProcessId() << "_" << GetCurrentThreadId();
#else
strm << getpid();
#endif
string temp_filename = strm.str();
ofstream out(temp_filename.c_str(), ios::out | ios::binary);
static const size_t buffer_size = 4096;
char buffer[buffer_size];
in.read(buffer, buffer_size);
size_t count = in.gcount();
while (count != 0) {
out.write(buffer, count);
if (out.fail()) {
unlink(temp_filename.c_str());
return false;
}
in.read(buffer, buffer_size);
count = in.gcount();
}
out.close();
if (!in.eof()) {
unlink(temp_filename.c_str());
return false;
}
if (rename(temp_filename.c_str(), to_filename.c_str()) == 0) {
return true;
}
unlink(to_filename.c_str());
if (rename(temp_filename.c_str(), to_filename.c_str()) == 0) {
return true;
}
unlink(temp_filename.c_str());
return false;
}