more towards self-downloading

This commit is contained in:
David Rose 2009-06-13 01:26:18 +00:00
parent 739ca67e1f
commit 652ca28703
13 changed files with 1244 additions and 16 deletions

View File

@ -1,9 +1,9 @@
// This directory is still experimental. Define HAVE_P3D_PLUGIN in
// your Config.pp to build it.
#define BUILD_DIRECTORY $[and $[HAVE_P3D_PLUGIN],$[HAVE_PYTHON],$[HAVE_TINYXML]]
#define BUILD_DIRECTORY $[and $[HAVE_P3D_PLUGIN],$[HAVE_PYTHON],$[HAVE_TINYXML],$[HAVE_OPENSSL],$[HAVE_ZLIB]]
#begin lib_target
#define USE_PACKAGES tinyxml
#define USE_PACKAGES tinyxml openssl zlib
#define TARGET p3d_plugin
#define COMBINED_SOURCES \
@ -18,6 +18,7 @@
p3dFileDownload.h p3dFileDownload.I \
p3dInstance.h p3dInstance.I \
p3dInstanceManager.h p3dInstanceManager.I \
p3dPackage.h p3dPackage.I \
p3dSession.h p3dSession.I
#define INCLUDED_SOURCES \
@ -26,6 +27,7 @@
p3dFileDownload.cxx \
p3dInstance.cxx \
p3dInstanceManager.cxx \
p3dPackage.cxx \
p3dSession.cxx
#define INSTALL_HEADERS \

190
direct/src/plugin/make_package.py Executable file
View File

@ -0,0 +1,190 @@
#! /bin/env python
"""
This command is used to build a downloadable package for the p3d
plugin to retrieve and install. It examines the files in the current
directory, assumes they are all intended to be part of the package,
and constructs the necessary package xml file and archive file for
hosting on the web serevr.
make_package.py [opts]
Options:
-d stage_dir
Specify the staging directory. This is a temporary directory on
the local machine that will be filled with the contents of the
package directory for the web server.
-p package_name
-v package_version
Specify the name and version of the package to build.
"""
import sys
import getopt
import os
import tarfile
import gzip
import stat
import md5
import direct
from pandac.PandaModules import *
class ArgumentError(AttributeError):
pass
class FileSpec:
def __init__(self, filename, pathname):
self.filename = filename
self.pathname = pathname
self.size = 0
self.timestamp = 0
self.hash = None
s = os.stat(self.pathname)
self.size = s[stat.ST_SIZE]
self.timestamp = s[stat.ST_MTIME]
m = md5.new()
f = open(self.pathname, 'rb')
data = f.read(4096)
while data:
m.update(data)
data = f.read(4096)
f.close()
self.hash = m.hexdigest()
def get_params(self):
return 'filename="%s" size=%s timestamp=%s hash="%s"' % (
self.filename, self.size, self.timestamp, self.hash)
class PackageMaker:
def __init__(self):
self.startDir = None
self.stageDir = None
self.packageName = None
self.packageVersion = None
def build(self):
if not self.startDir:
raise ArgumentError, "Start directory not specified."
if not self.stageDir:
raise ArgumentError, "Stage directory not specified."
if not self.packageName or not self.packageVersion:
raise ArgumentError, "Package name and version not specified."
self.packageFullname = '%s_%s' % (
self.packageName, self.packageVersion)
self.cleanDir(self.stageDir)
uncompressedArchiveBasename = '%s.tar' % (self.packageFullname)
uncompressedArchivePathname = os.path.join(self.stageDir, uncompressedArchiveBasename)
self.archive = tarfile.open(uncompressedArchivePathname, 'w')
self.components = []
self.addComponents()
self.archive.close()
uncompressedArchive = FileSpec(uncompressedArchiveBasename, uncompressedArchivePathname)
compressedArchiveBasename = '%s.tgz' % (self.packageFullname)
compressedArchivePathname = os.path.join(self.stageDir, compressedArchiveBasename)
print "\ncompressing"
f = open(uncompressedArchivePathname, 'rb')
gz = gzip.open(compressedArchivePathname, 'w', 9)
data = f.read(4096)
while data:
gz.write(data)
data = f.read(4096)
gz.close()
f.close()
compressedArchive = FileSpec(compressedArchiveBasename, compressedArchivePathname)
os.unlink(uncompressedArchivePathname)
descFileBasename = '%s.xml' % (self.packageFullname)
descFilePathname = os.path.join(self.stageDir, descFileBasename)
f = open(descFilePathname, 'w')
print >> f, '<?xml version="1.0" ?>'
print >> f, ''
print >> f, '<package name="%s" version="%s">' % (self.packageName, self.packageVersion)
print >> f, ' <uncompressed_archive %s />' % (uncompressedArchive.get_params())
print >> f, ' <compressed_archive %s />' % (compressedArchive.get_params())
for file in self.components:
print >> f, ' <component %s />' % (file.get_params())
print >> f, '</package>'
f.close()
def cleanDir(self, dirname):
""" Remove all the files in the named directory. Does not
operate recursively. """
for filename in os.listdir(dirname):
pathname = os.path.join(dirname, filename)
try:
os.unlink(pathname)
except OSError:
pass
def addComponents(self):
""" Walks through all the files in the start directory and
adds them to the archive. Recursively visits
sub-directories. """
startDir = self.startDir
if startDir.endswith(os.altsep) or startDir.endswith(os.sep):
startDir = startDir[:-1]
prefix = startDir + os.sep
for dirpath, dirnames, filenames in os.walk(startDir):
if dirpath == startDir:
localpath = ''
else:
assert dirpath.startswith(prefix)
localpath = dirpath[len(prefix):]
for basename in filenames:
file = FileSpec(os.path.join(localpath, basename),
os.path.join(startDir, basename))
print file.filename
self.components.append(file)
self.archive.add(file.pathname, file.filename, recursive = False)
def makePackage(args):
opts, args = getopt.getopt(args, 'd:p:v:h')
pm = PackageMaker()
pm.startDir = '.'
for option, value in opts:
if option == '-d':
pm.stageDir = Filename.fromOsSpecific(value).toOsSpecific()
elif option == '-p':
pm.packageName = value
elif option == '-v':
pm.packageVersion = value
elif option == '-h':
print __doc__
sys.exit(1)
pm.build()
if __name__ == '__main__':
try:
makePackage(sys.argv[1:])
except ArgumentError, e:
print e.args[0]
sys.exit(1)

View File

@ -25,7 +25,8 @@ P3DDownload() {
_http_status_code = 0;
_total_data = 0;
_total_expected_data = 0;
_canceled = false;
_download_id = 0;
}
@ -48,6 +49,21 @@ set_url(const string &url) {
_url = url;
}
////////////////////////////////////////////////////////////////////
// Function: P3DDownload::cancel
// Access: Public
// Description: Cancels a running download. download_finished() will
// not be called, but the P3DDownload object itself will
// eventually be deleted by its owning P3DInstance.
////////////////////////////////////////////////////////////////////
void P3DDownload::
cancel() {
_canceled = true;
if (_status == P3D_RC_in_progress) {
_status = P3D_RC_generic_error;
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DDownload::feed_url_stream
// Access: Public
@ -60,12 +76,16 @@ feed_url_stream(P3D_result_code result_code,
size_t total_expected_data,
const unsigned char *this_data,
size_t this_data_size) {
if (_canceled) {
return false;
}
_status = result_code;
_http_status_code = http_status_code;
if (this_data_size != 0) {
if (!receive_data(this_data, this_data_size)) {
// Failure writing to disk.
// Failure writing to disk or some such.
_status = P3D_RC_generic_error;
download_finished(false);
return false;

View File

@ -37,6 +37,8 @@ public:
inline bool get_download_finished() const;
inline bool get_download_success() const;
void cancel();
public:
// These are intended to be called only by P3DInstance.
inline void set_download_id(int download_id);
@ -62,6 +64,7 @@ protected:
size_t _total_expected_data;
private:
bool _canceled;
int _download_id;
string _url;
};

View File

@ -13,13 +13,51 @@
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_root_dir
// Access: Public
// Description: Returns the root directory into which all the P3D
// runtime files are downloaded and installed. This
// must be a writable directory or nothing will work.
////////////////////////////////////////////////////////////////////
inline const string &P3DInstanceManager::
get_root_dir() const {
return _root_dir;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_download_url
// Access: Public
// Description: Returns the URL of the download server. All
// downloadable files will be retrieved from various
// subdirectories of this URL root.
////////////////////////////////////////////////////////////////////
inline const string &P3DInstanceManager::
get_download_url() const {
return _download_url;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_command_instance
// Access: Public
// Description: Returns a global P3DInstance object that exists for
// the lifetime of the instance manager. This instance
// object has no reality onscreen, but can be used as a
// download host for downloading files that aren't
// specifically needed by any user-created instance.
////////////////////////////////////////////////////////////////////
inline P3DInstance *P3DInstanceManager::
get_command_instance() const {
return _command_instance;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_num_instances
// Access: Public
// Description: Returns the number of instances currently running
// within the world.
////////////////////////////////////////////////////////////////////
INLINE int P3DInstanceManager::
inline int P3DInstanceManager::
get_num_instances() const {
return _instances.size();
}

View File

@ -15,6 +15,7 @@
#include "p3dInstanceManager.h"
#include "p3dInstance.h"
#include "p3dSession.h"
#include "p3dPackage.h"
P3DInstanceManager *P3DInstanceManager::_global_ptr;
@ -35,6 +36,8 @@ P3DInstanceManager() {
INIT_LOCK(_request_ready_lock);
pthread_cond_init(&_request_ready_cvar, NULL);
#endif
_command_instance = NULL;
}
////////////////////////////////////////////////////////////////////
@ -44,7 +47,14 @@ P3DInstanceManager() {
////////////////////////////////////////////////////////////////////
P3DInstanceManager::
~P3DInstanceManager() {
// Actually, the destructor is never called.
// Actually, this destructor is never called, since this is a global
// object that never gets deleted.
assert(_instances.empty());
assert(_sessions.empty());
delete _command_instance;
#ifdef _WIN32
CloseHandle(_request_ready);
#else
@ -63,6 +73,8 @@ P3DInstanceManager::
////////////////////////////////////////////////////////////////////
bool P3DInstanceManager::
initialize() {
_root_dir = "c:/cygwin/home/drose/p3ddir";
_download_url = "http://10.196.143.118/~drose/";
return true;
}
@ -109,6 +121,8 @@ create_instance(P3D_request_ready_func *func,
////////////////////////////////////////////////////////////////////
void P3DInstanceManager::
finish_instance(P3DInstance *inst) {
assert(inst != _command_instance);
Instances::iterator ii;
ii = _instances.find(inst);
assert(ii != _instances.end());
@ -145,6 +159,10 @@ check_request() {
}
}
if (_command_instance->has_request()) {
return _command_instance;
}
return NULL;
}
@ -164,10 +182,10 @@ wait_request() {
int seq = _request_seq;
while (true) {
if (_instances.empty()) {
if (check_request() != (P3DInstance *)NULL) {
return;
}
if (check_request() != (P3DInstance *)NULL) {
if (_instances.empty()) {
return;
}
@ -187,6 +205,27 @@ wait_request() {
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_package
// Access: Public
// Description: Returns a (possibly shared) pointer to the indicated
// package.
////////////////////////////////////////////////////////////////////
P3DPackage *P3DInstanceManager::
get_package(const string &package_name, const string &package_version) {
string key = package_name + "_" + package_version;
Packages::iterator pi = _packages.find(key);
if (pi != _packages.end()) {
return (*pi).second;
}
P3DPackage *package = new P3DPackage(package_name, package_version);
bool inserted = _packages.insert(Packages::value_type(key, package)).second;
assert(inserted);
return package;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::get_unique_session_index
// Access: Public
@ -229,6 +268,22 @@ P3DInstanceManager *P3DInstanceManager::
get_global_ptr() {
if (_global_ptr == NULL) {
_global_ptr = new P3DInstanceManager;
_global_ptr->create_command_instance();
}
return _global_ptr;
}
////////////////////////////////////////////////////////////////////
// Function: P3DInstanceManager::create_command_instance;
// Access: Private
// Description: Create a command instance. This is used to handle
// requests that have nothing to do with any particular
// host-created instance.
////////////////////////////////////////////////////////////////////
void P3DInstanceManager::
create_command_instance() {
P3D_window_handle dummy_handle;
_command_instance =
new P3DInstance(NULL, "", P3D_WT_hidden, 0, 0, 0, 0,
dummy_handle, NULL, 0);
}

View File

@ -22,6 +22,7 @@
class P3DInstance;
class P3DSession;
class P3DPackage;
////////////////////////////////////////////////////////////////////
// Class : P3DInstanceManager
@ -36,6 +37,9 @@ private:
public:
bool initialize();
inline const string &get_root_dir() const;
inline const string &get_download_url() const;
P3DInstance *
create_instance(P3D_request_ready_func *func,
const string &p3d_filename,
@ -51,7 +55,11 @@ public:
P3DInstance *check_request();
void wait_request();
INLINE int get_num_instances() const;
P3DPackage *get_package(const string &package_name,
const string &package_version);
inline P3DInstance *get_command_instance() const;
inline int get_num_instances() const;
int get_unique_session_index();
void signal_request_ready();
@ -59,7 +67,13 @@ public:
static P3DInstanceManager *get_global_ptr();
private:
string _p3d_root_directory;
void create_command_instance();
private:
string _root_dir;
string _download_url;
P3DInstance *_command_instance;
typedef set<P3DInstance *> Instances;
Instances _instances;
@ -67,6 +81,9 @@ private:
typedef map<string, P3DSession *> Sessions;
Sessions _sessions;
typedef map<string, P3DPackage *> Packages;
Packages _packages;
int _unique_session_index;
// Implements a condition-var like behavior.

71
direct/src/plugin/p3dPackage.I Executable file
View File

@ -0,0 +1,71 @@
// Filename: p3dPackage.I
// 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."
//
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::get_ready
// Access: Public
// Description: Returns true if the package has been downloaded and
// verified and is ready to be used, false if it has
// not.
////////////////////////////////////////////////////////////////////
inline bool P3DPackage::
get_ready() const {
return _ready;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::get_failed
// Access: Public
// Description: Returns true if the package cannot be made ready, for
// instance because the download server is down.
////////////////////////////////////////////////////////////////////
inline bool P3DPackage::
get_failed() const {
return _failed;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::decode_hexdigit
// Access: Private
// Description: Returns the integer value corresponding to the
// indicated hex digit. Returns -1 if it is not a hex
// digit.
////////////////////////////////////////////////////////////////////
inline int P3DPackage::
decode_hexdigit(char c) {
if (isdigit(c)) {
return c - '0';
}
c = tolower(c);
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return -1;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::encode_hexdigit
// Access: Private
// Description: Returns the hex digit corresponding to the
// indicated integer value.
////////////////////////////////////////////////////////////////////
inline char P3DPackage::
encode_hexdigit(int c) {
if (c >= 10) {
return c - 10 + 'a';
}
return c + '0';
}

695
direct/src/plugin/p3dPackage.cxx Executable file
View File

@ -0,0 +1,695 @@
// 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 "openssl/md5.h"
#include "zlib.h"
#include <algorithm>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utime.h>
#ifdef _WIN32
#include <direct.h>
#define stat _stat
#define utime _utime
#define utimbuf _utimbuf
#endif
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::
P3DPackage(const string &package_name, const string &package_version) :
_package_name(package_name),
_package_version(package_version)
{
_package_fullname = _package_name + "_" + _package_version;
_ready = false;
_failed = false;
_active_download = NULL;
_partial_download = false;
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
// Ensure the package directory exists; create it if it does not.
_package_dir = inst_mgr->get_root_dir() + string("/") + _package_name;
#ifdef _WIN32
_mkdir(_package_dir.c_str());
#else
mkdir(_package_dir.c_str(), 0777);
#endif
_package_dir += string("/") + _package_version;
#ifdef _WIN32
_mkdir(_package_dir.c_str());
#else
mkdir(_package_dir.c_str(), 0777);
#endif
_desc_file_basename = _package_fullname + ".xml";
_desc_file_pathname = _package_dir + "/" + _desc_file_basename;
// TODO: we should check the desc file for updates with the server.
// Perhaps this should be done in a parent class.
// Load the desc file, if it exists.
TiXmlDocument doc(_desc_file_pathname.c_str());
if (!doc.LoadFile()) {
download_desc_file();
} else {
got_desc_file(&doc, false);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::
~P3DPackage() {
// Tell any pending callbacks that we're no good any more.
report_done(false);
// Cancel any pending download.
if (_active_download != NULL) {
_active_download->cancel();
delete _active_download;
_active_download = NULL;
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::set_callback
// Access: Public
// Description: Registers a callback on the package. The callback
// object will be notified when the package is ready for
// use (or when it has failed to download properly).
////////////////////////////////////////////////////////////////////
void P3DPackage::
set_callback(Callback *callback) {
if (_ready || _failed) {
// Actually, we're already done. Signal the callback immediately.
callback->package_ready(this, _ready);
} else {
// Bootstrap still in progress. Save the callback.
_callbacks.push_back(callback);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::cancel_callback
// Access: Public
// Description: Unregisters a particular callback object. This
// object will no longer be notified when the package is
// ready.
////////////////////////////////////////////////////////////////////
void P3DPackage::
cancel_callback(Callback *callback) {
Callbacks::iterator ci;
ci = find(_callbacks.begin(), _callbacks.end(), callback);
if (ci != _callbacks.end()) {
_callbacks.erase(ci);
} else {
cerr << "Canceling unknown callback on " << _package_fullname << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::download_desc_file
// Access: Private
// Description: Starts downloading the desc file for the package.
////////////////////////////////////////////////////////////////////
void P3DPackage::
download_desc_file() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
string url = inst_mgr->get_download_url();
url += _package_name + "/" + _package_version + "/" + _desc_file_basename;
start_download(DT_desc_file, url, _desc_file_pathname, false);
}
////////////////////////////////////////////////////////////////////
// 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;
}
TiXmlDocument doc(_desc_file_pathname.c_str());
if (!doc.LoadFile()) {
cerr << "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) {
cerr << "got desc file\n";
TiXmlElement *xpackage = doc->FirstChildElement("package");
TiXmlElement *uncompressed_archive = NULL;
TiXmlElement *compressed_archive = NULL;
if (xpackage != NULL) {
uncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
compressed_archive = xpackage->FirstChildElement("compressed_archive");
}
if (uncompressed_archive == NULL || compressed_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(uncompressed_archive);
_compressed_archive.load_xml(compressed_archive);
// Now get all the components.
_components.clear();
TiXmlElement *component = xpackage->FirstChildElement("component");
while (component != NULL) {
FileSpec file;
file.load_xml(component);
_components.push_back(file);
component = component->NextSiblingElement("component");
}
cerr << "got " << _components.size() << " components\n";
// Verify all of the components.
bool all_components_ok = true;
Components::iterator ci;
for (ci = _components.begin(); ci != _components.end(); ++ci) {
if (!(*ci).quick_verify(_package_dir)) {
all_components_ok = false;
break;
}
}
if (all_components_ok) {
// Great, we're ready to begin.
report_done(true);
} else 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(true);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::download_compressed_archive
// Access: Private
// Description: Starts downloading the archive file for the package.
////////////////////////////////////////////////////////////////////
void P3DPackage::
download_compressed_archive(bool allow_partial) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
string url = inst_mgr->get_download_url();
url += _package_name + "/" + _package_version + "/" + _compressed_archive._filename;
string target_pathname = _package_dir + "/" + _compressed_archive._filename;
start_download(DT_compressed_archive, url, target_pathname, allow_partial);
}
////////////////////////////////////////////////////////////////////
// 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;
}
if (_compressed_archive.full_verify(_package_dir)) {
// Go on to uncompress the archive.
uncompress_archive();
return;
}
// Oof, didn't download it correctly.
if (_partial_download) {
// Go back and get the whole file this time.
download_compressed_archive(false);
}
cerr << _compressed_archive._filename
<< " failed hash check after download\n";
report_done(false);
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::uncompress_archive
// Access: Private
// Description: Uncompresses the archive file.
////////////////////////////////////////////////////////////////////
void P3DPackage::
uncompress_archive() {
cerr << "uncompressing " << _compressed_archive._filename << "\n";
string source_pathname = _package_dir + "/" + _compressed_archive._filename;
string target_pathname = _package_dir + "/" + _uncompressed_archive._filename;
gzFile source = gzopen(source_pathname.c_str(), "rb");
if (source == NULL) {
cerr << "Couldn't open " << source_pathname << "\n";
report_done(false);
return;
}
ofstream target(target_pathname.c_str(), ios::out | ios::binary);
if (!target) {
cerr << "Couldn't write to " << target_pathname << "\n";
report_done(false);
}
static const int buffer_size = 1024;
char buffer[buffer_size];
int count = gzread(source, buffer, buffer_size);
while (count > 0) {
target.write(buffer, count);
count = gzread(source, buffer, buffer_size);
}
if (count < 0) {
cerr << "gzip error decompressing " << source_pathname << "\n";
int errnum;
cerr << gzerror(source, &errnum) << "\n";
gzclose(source);
report_done(false);
return;
}
gzclose(source);
if (!target) {
cerr << "Couldn't write entire file to " << target_pathname << "\n";
report_done(false);
return;
}
target.close();
if (!_uncompressed_archive.full_verify(_package_dir)) {
cerr << "after uncompressing " << target_pathname << ", failed hash check\n";
report_done(false);
return;
}
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() {
cerr << "extracting " << _uncompressed_archive._filename << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::report_done
// Access: Private
// Description: Transitions the package to "ready" or "failure"
// state, and reports this change to all the listening
// callbacks.
////////////////////////////////////////////////////////////////////
void P3DPackage::
report_done(bool success) {
if (success) {
_ready = true;
_failed = false;
} else {
_ready = false;
_failed = true;
}
Callbacks orig_callbacks;
orig_callbacks.swap(_callbacks);
Callbacks::iterator ci;
for (ci = orig_callbacks.begin(); ci != orig_callbacks.end(); ++ci) {
(*ci)->package_ready(this, _ready);
}
// We shouldn't have added any more callbacks during the above loop.
assert(_callbacks.empty());
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::start_download
// Access: Private
// Description: Initiates a download of the indicated file.
////////////////////////////////////////////////////////////////////
void P3DPackage::
start_download(P3DPackage::DownloadType dtype, const string &url,
const string &pathname, bool allow_partial) {
// Only one download should be active at a time
assert(_active_download == NULL);
Download *download = new Download(this, dtype);
download->set_url(url);
download->set_filename(pathname);
// TODO: implement partial file re-download.
allow_partial = false;
if (!allow_partial) {
unlink(pathname.c_str());
}
_active_download = download;
_partial_download = false;
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
inst_mgr->get_command_instance()->start_download(download);
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::decode_hex
// Access: Private, Static
// Description: Decodes the hex string in source into the character
// array in dest. dest must have has least size bytes;
// source must have size * 2 bytes.
//
// Returns true on success, false if there was a non-hex
// digit in the string.
////////////////////////////////////////////////////////////////////
bool P3DPackage::
decode_hex(unsigned char *dest, const char *source, size_t size) {
for (size_t i = 0; i < size; ++i) {
int high = decode_hexdigit(source[i * 2]);
int low = decode_hexdigit(source[i * 2 + 1]);
if (high < 0 || low < 0) {
return false;
}
dest[i] = (high << 4) | low;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::encode_hex
// Access: Private, Static
// Description: Encodes a character array into a hex string for
// output. dest must have at least size * 2 bytes;
// source must have size bytes. The result is not
// null-terminated.
////////////////////////////////////////////////////////////////////
void P3DPackage::
encode_hex(char *dest, const unsigned char *source, size_t size) {
for (size_t i = 0; i < size; ++i) {
int high = (source[i] >> 4) & 0xf;
int low = source[i] & 0xf;
dest[2 * i] = encode_hexdigit(high);
dest[2 * i + 1] = encode_hexdigit(low);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::stream_hex
// Access: Private, Static
// Description: Writes the indicated buffer as a string of hex
// characters to the given ostream.
////////////////////////////////////////////////////////////////////
void P3DPackage::
stream_hex(ostream &out, const unsigned char *source, size_t size) {
for (size_t i = 0; i < size; ++i) {
int high = (source[i] >> 4) & 0xf;
int low = source[i] & 0xf;
out.put(encode_hexdigit(high));
out.put(encode_hexdigit(low));
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::Download::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::Download::
Download(P3DPackage *package, DownloadType dtype) :
_package(package),
_dtype(dtype)
{
}
////////////////////////////////////////////////////////////////////
// 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);
_package->_active_download = NULL;
switch (_dtype) {
case DT_desc_file:
_package->desc_file_download_finished(success);
break;
case DT_compressed_archive:
_package->compressed_archive_download_finished(success);
break;
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::FileSpec::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::FileSpec::
FileSpec() {
_size = 0;
_timestamp = 0;
memset(_hash, 0, sizeof(_hash));
_got_hash = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::FileSpec::load_xml
// Access: Public
// Description: Reads the data from the indicated XML file.
////////////////////////////////////////////////////////////////////
void P3DPackage::FileSpec::
load_xml(TiXmlElement *element) {
const char *filename = element->Attribute("filename");
if (filename != NULL) {
_filename = filename;
}
const char *size = element->Attribute("size");
if (size != NULL) {
char *endptr;
_size = strtoul(size, &endptr, 10);
}
const char *timestamp = element->Attribute("timestamp");
if (timestamp != NULL) {
char *endptr;
_timestamp = strtoul(timestamp, &endptr, 10);
}
_got_hash = false;
const char *hash = element->Attribute("hash");
if (hash != NULL && strlen(hash) == (hash_size * 2)) {
// Decode the hex hash string.
_got_hash = decode_hex(_hash, hash, hash_size);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::FileSpec::quick_verify
// Access: Public
// Description: Performs a quick test to ensure the file has not been
// modified. This test is vulnerable to people
// maliciously attempting to fool the program (by
// setting datestamps etc.).
//
// Returns true if it is intact, false if it needs to be
// redownloaded.
////////////////////////////////////////////////////////////////////
bool P3DPackage::FileSpec::
quick_verify(const string &package_dir) const {
string pathname = package_dir + "/" + _filename;
struct stat st;
if (stat(pathname.c_str(), &st) != 0) {
cerr << "file not found: " << _filename << "\n";
return false;
}
if (st.st_size != _size) {
// If the size is wrong, the file fails.
cerr << "size wrong: " << _filename << "\n";
return false;
}
if (st.st_mtime == _timestamp) {
// If the size is right and the timestamp is right, the file passes.
return true;
}
cerr << "modification time wrong: " << _filename << "\n";
// If the size is right but the timestamp is wrong, the file
// soft-fails. We follow this up with a hash check.
if (!check_hash(pathname)) {
// Hard fail, the hash is wrong.
cerr << "hash check wrong: " << _filename << "\n";
return false;
}
cerr << "hash check ok: " << _filename << "\n";
// The hash is OK after all. Change the file's timestamp back to
// what we expect it to be, so we can quick-verify it successfully
// next time.
utimbuf utb;
utb.actime = st.st_atime;
utb.modtime = _timestamp;
utime(pathname.c_str(), &utb);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::FileSpec::quick_verify
// Access: Public
// Description: Performs a more thorough test to ensure the file has
// not been modified. This test is less vulnerable to
// malicious attacks, since it reads and verifies the
// entire file.
//
// Returns true if it is intact, false if it needs to be
// redownloaded.
////////////////////////////////////////////////////////////////////
bool P3DPackage::FileSpec::
full_verify(const string &package_dir) const {
string pathname = package_dir + "/" + _filename;
struct stat st;
if (stat(pathname.c_str(), &st) != 0) {
cerr << "file not found: " << _filename << "\n";
return false;
}
if (st.st_size != _size) {
// If the size is wrong, the file fails.
cerr << "size wrong: " << _filename << "\n";
return false;
}
// If the size is right but the timestamp is wrong, the file
// soft-fails. We follow this up with a hash check.
if (!check_hash(pathname)) {
// Hard fail, the hash is wrong.
cerr << "hash check wrong: " << _filename << "\n";
return false;
}
cerr << "hash check ok: " << _filename << "\n";
// The hash is OK. If the timestamp is wrong, change it back to
// what we expect it to be, so we can quick-verify it successfully
// next time.
if (st.st_mtime != _timestamp) {
utimbuf utb;
utb.actime = st.st_atime;
utb.modtime = _timestamp;
utime(pathname.c_str(), &utb);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::FileSpec::check_hash
// Access: Public
// Description: Returns true if the file has the expected md5 hash,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool P3DPackage::FileSpec::
check_hash(const string &pathname) const {
ifstream stream(pathname.c_str(), ios::in | ios::binary);
if (!stream) {
cerr << "unable to read " << pathname << "\n";
return false;
}
unsigned char md[hash_size];
MD5_CTX ctx;
MD5_Init(&ctx);
static const int buffer_size = 1024;
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(md, &ctx);
return (memcmp(md, _hash, hash_size) == 0);
}

135
direct/src/plugin/p3dPackage.h Executable file
View File

@ -0,0 +1,135 @@
// Filename: p3dPackage.h
// 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."
//
////////////////////////////////////////////////////////////////////
#ifndef P3DPACKAGE_H
#define P3DPACKAGE_H
#include "p3d_plugin_common.h"
#include "p3dFileDownload.h"
////////////////////////////////////////////////////////////////////
// Class : P3DPackage
// Description : This corresponds to a downloadable, patchable
// package, and all its constituent files. For
// instance, a particular version of the Panda3D
// runtime, which consists of a bunch of dll's
// downloaded in a single tar file, is a package.
//
// The plugin is responsible for managing these packages
// on disk, downloading new versions when needed, and
// removing stale versions to limit disk space waste.
////////////////////////////////////////////////////////////////////
class P3DPackage {
public:
P3DPackage(const string &package_name, const string &package_version);
~P3DPackage();
class Callback {
public:
virtual void package_ready(P3DPackage *package, bool success);
};
inline bool get_ready() const;
inline bool get_failed() const;
void set_callback(Callback *callback);
void cancel_callback(Callback *callback);
private:
enum DownloadType {
DT_desc_file,
DT_compressed_archive
};
class Download : public P3DFileDownload {
public:
Download(P3DPackage *package, DownloadType dtype);
protected:
virtual void download_finished(bool success);
private:
P3DPackage *_package;
DownloadType _dtype;
};
void download_desc_file();
void desc_file_download_finished(bool success);
void got_desc_file(TiXmlDocument *doc, bool freshly_downloaded);
void download_compressed_archive(bool allow_partial);
void compressed_archive_download_finished(bool success);
void uncompress_archive();
void extract_archive();
void report_done(bool success);
void start_download(DownloadType dtype, const string &url,
const string &pathname, bool allow_partial);
static inline int decode_hexdigit(char c);
static inline char encode_hexdigit(int c);
static bool decode_hex(unsigned char *dest, const char *source, size_t size);
static void encode_hex(char *dest, const unsigned char *source, size_t size);
static void stream_hex(ostream &out, const unsigned char *source, size_t size);
private:
string _package_name;
string _package_version;
string _package_fullname;
string _package_dir;
string _desc_file_basename;
string _desc_file_pathname;
bool _ready;
bool _failed;
Download *_active_download;
bool _partial_download;
typedef vector<Callback *> Callbacks;
Callbacks _callbacks;
enum { hash_size = 16 };
class FileSpec {
public:
FileSpec();
void load_xml(TiXmlElement *element);
bool quick_verify(const string &package_dir) const;
bool full_verify(const string &package_dir) const;
bool check_hash(const string &pathname) const;
string _filename;
size_t _size;
time_t _timestamp;
unsigned char _hash[hash_size];
bool _got_hash;
};
FileSpec _compressed_archive;
FileSpec _uncompressed_archive;
typedef vector<FileSpec> Components;
Components _components;
friend class Download;
};
#include "p3dPackage.I"
#endif

View File

@ -131,9 +131,9 @@ start_instance(P3DInstance *inst) {
send_command(doc);
if (_python_state == PS_init) {
download_p3dpython(inst);
}
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
P3DPackage *panda = inst_mgr->get_package("panda3d", "dev");
// start_p3dpython();
}
////////////////////////////////////////////////////////////////////
@ -209,7 +209,7 @@ download_p3dpython(P3DInstance *inst) {
inst->start_download(download);
*/
start_p3dpython();
// start_p3dpython();
}
////////////////////////////////////////////////////////////////////

View File

@ -3,4 +3,5 @@
#include "p3dFileDownload.cxx"
#include "p3dInstance.cxx"
#include "p3dInstanceManager.cxx"
#include "p3dPackage.cxx"
#include "p3dSession.cxx"

View File

@ -143,14 +143,15 @@ thread_main() {
} else {
status = P3D_RC_generic_error;
}
cerr << "Error getting URL " << _url << "\n";
} else {
cerr << "Done getting URL " << _url << ", got " << bytes_sent << " bytes\n";
}
P3D_instance_feed_url_stream
(_instance, _unique_id, status,
channel->get_status_code(),
bytes_sent, NULL, 0);
cerr << "Done getting URL " << _url << ", got " << bytes_sent << " bytes\n";
}
bool