contents.xml max_age; internal cache busting

This commit is contained in:
David Rose 2010-06-08 23:56:04 +00:00
parent 9d14c7867b
commit 0b80f17c68
23 changed files with 650 additions and 202 deletions

View File

@ -1864,6 +1864,10 @@ class Packager:
self.host = PandaSystem.getPackageHostUrl()
self.addHost(self.host)
# The maximum amount of time a client should cache the
# contents.xml before re-querying the server, in seconds.
self.maxAge = 0
# A search list for previously-built local packages.
# We use a bit of caution to read the Filenames out of the
@ -1918,10 +1922,10 @@ class Packager:
# client and is therefore readily available to any hacker.
# Not only is this feature useless, but using it also
# increases the size of your patchfiles, since encrypted files
# don't patch as tightly as unencrypted files. But it's here
# if you really want it.
self.encryptExtensions = ['ptf', 'dna', 'txt', 'dc']
self.encryptFiles = []
# can't really be patched. But it's here if you really want
# it. ** Note: Actually, this isn't implemented yet.
#self.encryptExtensions = []
#self.encryptFiles = []
# This is the list of DC import suffixes that should be
# available to the client. Other suffixes, like AI and UD,
@ -2220,6 +2224,8 @@ class Packager:
# Set up the namespace dictionary for exec.
globals = {}
globals['__name__'] = packageDef.getBasenameWoExtension()
globals['__dir__'] = Filename(packageDef.getDirname()).toOsSpecific()
globals['packageDef'] = packageDef
globals['platform'] = self.platform
globals['packager'] = self
@ -3156,6 +3162,7 @@ class Packager:
# sure that our own host at least is added to the map.
self.addHost(self.host)
self.maxAge = 0
self.contents = {}
self.contentsChanged = False
@ -3171,6 +3178,10 @@ class Packager:
xcontents = doc.FirstChildElement('contents')
if xcontents:
maxAge = xcontents.Attribute('max_age')
if maxAge:
self.maxAge = int(maxAge)
xhost = xcontents.FirstChildElement('host')
if xhost:
he = self.HostEntry()
@ -3199,6 +3210,9 @@ class Packager:
doc.InsertEndChild(decl)
xcontents = TiXmlElement('contents')
if self.maxAge:
xcontents.SetAttribute('max_age', str(self.maxAge))
if self.host:
he = self.hosts.get(self.host, None)
if he:

View File

@ -67,6 +67,17 @@ get_timestamp() const {
return _timestamp;
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::has_hash
// Access: Public
// Description: Returns true if we have successfully read a hash
// value, false otherwise.
////////////////////////////////////////////////////////////////////
inline bool FileSpec::
has_hash() const {
return _got_hash;
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::get_actual_file
// Access: Public

View File

@ -123,6 +123,30 @@ load_xml(TiXmlElement *xelement) {
}
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::store_xml
// Access: Public
// Description: Stores the data to the indicated XML file.
////////////////////////////////////////////////////////////////////
void FileSpec::
store_xml(TiXmlElement *xelement) {
if (!_filename.empty()) {
xelement->SetAttribute("filename", _filename);
}
if (_size != 0) {
xelement->SetAttribute("size", _size);
}
if (_timestamp != 0) {
xelement->SetAttribute("timestamp", _timestamp);
}
if (_got_hash) {
char hash[hash_size * 2 + 1];
encode_hex(hash, _hash, hash_size);
hash[hash_size * 2] = '\0';
xelement->SetAttribute("hash", hash);
}
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::quick_verify
// Access: Public
@ -272,6 +296,7 @@ check_hash(const string &pathname) const {
bool FileSpec::
read_hash(const string &pathname) {
memset(_hash, 0, hash_size);
_got_hash = false;
ifstream stream(pathname.c_str(), ios::in | ios::binary);
if (!stream) {
@ -294,6 +319,7 @@ read_hash(const string &pathname) {
}
MD5_Final(_hash, &ctx);
_got_hash = true;
return true;
}

View File

@ -34,12 +34,14 @@ public:
~FileSpec();
void load_xml(TiXmlElement *xelement);
void store_xml(TiXmlElement *xelement);
inline const string &get_filename() const;
inline void set_filename(const string &filename);
inline string get_pathname(const string &package_dir) const;
inline size_t get_size() const;
inline time_t get_timestamp() const;
inline bool has_hash() const;
bool quick_verify(const string &package_dir);
bool quick_verify_pathname(const string &pathname);

View File

@ -107,7 +107,8 @@ get_contents_seq() const {
// Function: P3DHost::check_contents_hash
// Access: Public
// Description: Returns true if the indicated pathname has the same
// md5 hash as the contents.xml file, false otherwise.
// md5 hash as the contents.xml file (as provided by the
// server), false otherwise.
////////////////////////////////////////////////////////////////////
inline bool P3DHost::
check_contents_hash(const string &pathname) const {

View File

@ -40,6 +40,7 @@ P3DHost(const string &host_url) :
_descriptive_name = _host_url;
_xcontents = NULL;
_contents_expiration = 0;
_contents_seq = 0;
}
@ -106,6 +107,25 @@ get_alt_host(const string &alt_host) {
return this;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::has_current_contents_file
// Access: Public
// Description: Returns true if a contents.xml file has been
// successfully read for this host and is still current,
// false otherwise.
////////////////////////////////////////////////////////////////////
bool P3DHost::
has_current_contents_file(P3DInstanceManager *inst_mgr) const {
if (!inst_mgr->get_verify_contents()) {
// If we're not asking to verify contents, then contents.xml files
// never expire.
return has_contents_file();
}
time_t now = time(NULL);
return now < _contents_expiration && (_xcontents != NULL);
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::read_contents_file
// Access: Public
@ -122,21 +142,21 @@ read_contents_file() {
}
string standard_filename = _host_dir + "/contents.xml";
return read_contents_file(standard_filename);
return read_contents_file(standard_filename, false);
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::read_contents_file
// Access: Public
// Description: Reads the contents.xml file in the indicated
// filename. On success, copies the contents.xml file
// filename. On success, writes 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) {
read_contents_file(const string &contents_filename, bool fresh_download) {
TiXmlDocument doc(contents_filename.c_str());
if (!doc.LoadFile()) {
return false;
@ -152,8 +172,51 @@ read_contents_file(const string &contents_filename) {
}
_xcontents = (TiXmlElement *)xcontents->Clone();
++_contents_seq;
_contents_spec = FileSpec();
int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE;
xcontents->Attribute("max_age", &max_age);
// Get the latest possible expiration time, based on the max_age
// indication. Any expiration time later than this is in error.
time_t now = time(NULL);
_contents_expiration = now + (time_t)max_age;
if (fresh_download) {
_contents_spec.read_hash(contents_filename);
// Update the XML with the new download information.
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
while (xorig != NULL) {
xcontents->RemoveChild(xorig);
xorig = xcontents->FirstChildElement("orig");
}
xorig = new TiXmlElement("orig");
xcontents->LinkEndChild(xorig);
_contents_spec.store_xml(xorig);
xorig->SetAttribute("expiration", (int)_contents_expiration);
} else {
// Read the download hash and expiration time from the XML.
int expiration = 0;
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
if (xorig != NULL) {
_contents_spec.load_xml(xorig);
xorig->Attribute("expiration", &expiration);
}
if (!_contents_spec.has_hash()) {
_contents_spec.read_hash(contents_filename);
}
_contents_expiration = min(_contents_expiration, (time_t)expiration);
}
nout << "read contents.xml, max_age = " << max_age
<< ", expires in " << max(_contents_expiration, now) - now
<< " s\n";
TiXmlElement *xhost = _xcontents->FirstChildElement("host");
if (xhost != NULL) {
const char *url = xhost->Attribute("url");
@ -194,12 +257,18 @@ read_contents_file(const string &contents_filename) {
mkdir_complete(_host_dir, nout);
string standard_filename = _host_dir + "/contents.xml";
if (fresh_download) {
if (!save_xml_file(&doc, standard_filename)) {
nout << "Couldn't save to " << standard_filename << "\n";
}
} else {
if (standardize_filename(standard_filename) !=
standardize_filename(contents_filename)) {
if (!copy_file(contents_filename, standard_filename)) {
nout << "Couldn't copy to " << standard_filename << "\n";
}
}
}
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (_host_url == inst_mgr->get_host_url()) {
@ -208,14 +277,13 @@ read_contents_file(const string &contents_filename) {
// iteration.
string top_filename = inst_mgr->get_root_dir() + "/contents.xml";
if (standardize_filename(top_filename) !=
standardize_filename(contents_filename)) {
if (!copy_file(contents_filename, top_filename)) {
standardize_filename(standard_filename)) {
if (!copy_file(standard_filename, top_filename)) {
nout << "Couldn't copy to " << top_filename << "\n";
}
}
}
return true;
}
@ -288,24 +356,29 @@ get_package(const string &package_name, const string &package_version,
PackageMap &package_map = _packages[alt_host];
P3DPackage *package = NULL;
string key = package_name + "_" + package_version;
PackageMap::iterator pi = package_map.find(key);
if (pi != package_map.end()) {
P3DPackage *package = (*pi).second;
if (!package->get_failed()) {
return package;
}
// We've previously installed this package.
package = (*pi).second;
if (package->get_failed()) {
// If the package has previously failed, move it aside and try
// again (maybe it just failed because the user interrupted it).
nout << "Package " << key << " has previously failed; trying again.\n";
_failed_packages.push_back(package);
(*pi).second = NULL;
package = NULL;
}
}
P3DPackage *package =
if (package == NULL) {
package =
new P3DPackage(this, package_name, package_version, alt_host);
package_map[key] = package;
}
return package;
}
@ -694,3 +767,41 @@ copy_file(const string &from_filename, const string &to_filename) {
unlink(temp_filename.c_str());
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DHost::save_xml_file
// Access: Private, Static
// Description: Stores the XML document to the file named by
// to_filename, safely.
////////////////////////////////////////////////////////////////////
bool P3DHost::
save_xml_file(TiXmlDocument *doc, const string &to_filename) {
// Save to a temporary file first, in case (a) we have different
// processes writing to the same file, and (b) 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();
if (!doc->SaveFile(temp_filename.c_str())) {
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;
}

View File

@ -43,11 +43,12 @@ public:
P3DHost *get_alt_host(const string &alt_host);
inline bool has_contents_file() const;
bool has_current_contents_file(P3DInstanceManager *inst_mgr) const;
inline int get_contents_seq() const;
inline bool check_contents_hash(const string &pathname) const;
bool read_contents_file();
bool read_contents_file(const string &contents_filename);
bool read_contents_file(const string &contents_filename, bool fresh_download);
void read_xhost(TiXmlElement *xhost);
P3DPackage *get_package(const string &package_name,
@ -72,6 +73,7 @@ private:
static string standardize_filename(const string &filename);
static bool copy_file(const string &from_filename, const string &to_filename);
static bool save_xml_file(TiXmlDocument *doc, const string &to_filename);
private:
string _host_dir;
@ -80,6 +82,7 @@ private:
string _download_url_prefix;
string _descriptive_name;
TiXmlElement *_xcontents;
time_t _contents_expiration;
int _contents_seq;
FileSpec _contents_spec;

View File

@ -1141,6 +1141,11 @@ get_packages_info_ready() const {
////////////////////////////////////////////////////////////////////
bool P3DInstance::
get_packages_ready() const {
if (!_packages_specified) {
// We haven't even specified the full set of required packages yet.
return false;
}
Packages::const_iterator pi;
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
if (!(*pi)->get_ready()) {
@ -1384,6 +1389,7 @@ void P3DInstance::
splash_button_clicked_main_thread() {
if (is_failed()) {
// Can't click the button after we've failed.
nout << "Ignoring click for failed instance\n";
return;
}
@ -1391,6 +1397,8 @@ splash_button_clicked_main_thread() {
auth_button_clicked();
} else if (_session == NULL) {
play_button_clicked();
} else {
nout << "Ignoring click for already-started instance\n";
}
}
@ -2360,9 +2368,6 @@ handle_notify_request(const string &message) {
// Once Python is up and running, we can get the actual main
// object from the Python side, and merge it with our own.
// But only if this web page is allowed to call our scripting
// functions.
if (_matches_script_origin) {
TiXmlDocument *doc = new TiXmlDocument;
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "pyobj");
@ -2383,9 +2388,16 @@ handle_notify_request(const string &message) {
}
if (result != NULL) {
if (_matches_script_origin) {
// We only actually merge the objects if this web page is
// allowed to call our scripting functions.
_panda_script_object->set_pyobj(result);
P3D_OBJECT_DECREF(result);
} else {
// Otherwise, we just do a one-time application of the
// toplevel properties down to Python.
_panda_script_object->apply_properties(result);
}
P3D_OBJECT_DECREF(result);
}
_panda_script_object->set_string_property("status", "starting");
@ -3006,7 +3018,6 @@ mark_download_complete() {
////////////////////////////////////////////////////////////////////
void P3DInstance::
ready_to_start() {
nout << "_instance_started = " << _instance_started << "\n";
if (_instance_started || is_failed()) {
// Already started--or never mind.
return;

View File

@ -261,12 +261,11 @@ initialize(int api_version, const string &contents_filename,
create_runtime_environment();
_is_initialized = true;
if (!_verify_contents &&
!host_url.empty() && !contents_filename.empty()) {
if (!host_url.empty() && !contents_filename.empty()) {
// Attempt to pre-read the supplied contents.xml file, to avoid an
// unnecessary download later.
P3DHost *host = get_host(host_url);
if (!host->read_contents_file(contents_filename)) {
if (!host->read_contents_file(contents_filename, false)) {
nout << "Couldn't read " << contents_filename << "\n";
}
}

View File

@ -289,12 +289,7 @@ set_pyobj(P3D_object *pyobj) {
// Now that we have a pyobj, we have to transfer down all of the
// properties we'd set locally.
Properties::const_iterator pi;
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
const string &property_name = (*pi).first;
P3D_object *value = (*pi).second;
P3D_OBJECT_SET_PROPERTY(_pyobj, property_name.c_str(), false, value);
}
apply_properties(_pyobj);
}
}
}
@ -310,6 +305,41 @@ get_pyobj() const {
return _pyobj;
}
////////////////////////////////////////////////////////////////////
// Function: P3DMainObject::apply_properties
// Access: Public
// Description: Applies the locally-set properties onto the indicated
// Python object, but does not store the object. This
// is a one-time copy of the locally-set properties
// (like "coreapiHostUrl" and the like) onto the
// indicated Python object.
////////////////////////////////////////////////////////////////////
void P3DMainObject::
apply_properties(P3D_object *pyobj) {
P3DPythonObject *p3dpyobj = NULL;
if (pyobj->_class == &P3DObject::_object_class) {
p3dpyobj = ((P3DObject *)pyobj)->as_python_object();
}
Properties::const_iterator pi;
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
const string &property_name = (*pi).first;
P3D_object *value = (*pi).second;
if (p3dpyobj != NULL && P3D_OBJECT_GET_TYPE(value) != P3D_OT_object) {
// If we know we have an actual P3DPythonObject (we really
// expect this), then we can call set_property_insecure()
// directly, because we want to allow setting the initial
// properties even if Javascript has no permissions to write
// into Python. But we don't allow setting objects this way in
// any event.
p3dpyobj->set_property_insecure(property_name, false, value);
} else {
// Otherwise, we go through the generic interface.
P3D_OBJECT_SET_PROPERTY(pyobj, property_name.c_str(), false, value);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DMainObject::set_instance
// Access: Public

View File

@ -65,6 +65,7 @@ public:
void set_pyobj(P3D_object *pyobj);
P3D_object *get_pyobj() const;
void apply_properties(P3D_object *pyobj);
void set_instance(P3DInstance *inst);

View File

@ -352,7 +352,7 @@ fill_xml(TiXmlElement *xvalue, P3DSession *session) {
////////////////////////////////////////////////////////////////////
// Function: P3DObject::get_object_array
// Access: Public
// Access: Public, Virtual
// Description: Returns a pointer to the array of objects represented
// by this object, if any, or NULL if the object does
// not represent an array of objects. This may also
@ -366,7 +366,7 @@ get_object_array() {
////////////////////////////////////////////////////////////////////
// Function: P3DObject::get_object_array_size
// Access: Public
// Access: Public, Virtual
// Description: Returns the number of elements in the array returned
// by get_object_array(), or -1 if this object does not
// representan array of objects.
@ -376,6 +376,18 @@ get_object_array_size() {
return -1;
}
////////////////////////////////////////////////////////////////////
// Function: P3DObject::as_python_object
// Access: Public, Virtual
// Description: Returns this object, downcast to a P3DPythonObject,
// if it is in fact an object of that type; or NULL if
// it is not.
////////////////////////////////////////////////////////////////////
P3DPythonObject *P3DObject::
as_python_object() {
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: P3DObject::get_bool_property
// Access: Public

View File

@ -17,6 +17,8 @@
#include "p3d_plugin_common.h"
class P3DPythonObject;
////////////////////////////////////////////////////////////////////
// Class : P3DObject
// Description : The C++ implementation of P3D_value, corresponding
@ -56,6 +58,8 @@ public:
virtual P3D_object **get_object_array();
virtual int get_object_array_size();
virtual P3DPythonObject *as_python_object();
// Convenience functions.
bool get_bool_property(const string &property);
void set_bool_property(const string &property, bool value);

View File

@ -181,6 +181,17 @@ void P3DPackage::
add_instance(P3DInstance *inst) {
_instances.push_back(inst);
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (!_host->has_current_contents_file(inst_mgr)) {
// If the host needs to update its contents file, we're no longer
// sure that we're current.
_info_ready = false;
_ready = false;
_failed = false;
_allow_data_download = false;
nout << "No longer current: " << get_package_name() << "\n";
}
begin_info_download();
}
@ -381,13 +392,13 @@ begin_info_download() {
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.
if (!_host->has_contents_file()) {
// First, read whatever contents file is already on disk. Maybe
// it's current enough.
_host->read_contents_file();
}
if (_host->has_contents_file()) {
if (_host->has_current_contents_file(inst_mgr)) {
// We've already got a contents.xml file; go straight to the
// package desc file.
host_got_contents_file();
@ -414,14 +425,15 @@ download_contents_file() {
////////////////////////////////////////////////////////////////////
void P3DPackage::
contents_file_download_finished(bool success) {
if (!_host->has_contents_file()) {
if (!success || !_host->read_contents_file(_temp_contents_file->get_filename())) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (!_host->has_current_contents_file(inst_mgr)) {
if (!success || !_host->read_contents_file(_temp_contents_file->get_filename(), true)) {
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)) {
!_host->read_contents_file(standard_filename, false)) {
// Couldn't even read that. Fail.
report_done(false);
delete _temp_contents_file;
@ -503,7 +515,7 @@ contents_file_redownload_finished(bool success) {
// 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())) {
if (!_host->read_contents_file(_temp_contents_file->get_filename(), true)) {
// Huh, appears to have changed to something bad. Never mind.
nout << "Couldn't read " << *_temp_contents_file << "\n";
@ -569,7 +581,8 @@ host_got_contents_file() {
// host.
_alt_host.clear();
if (!_host->has_contents_file()) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (!_host->has_current_contents_file(inst_mgr)) {
// Now go back and get the contents.xml file for the new host.
download_contents_file();
return;
@ -1434,6 +1447,12 @@ download_finished(bool success) {
if (!_file_spec.full_verify(_package->_package_dir)) {
nout << "After downloading " << get_url()
<< ", failed hash check\n";
nout << "expected: ";
_file_spec.output_hash(nout);
nout << "\n got: ";
_file_spec.get_actual_file()->output_hash(nout);
nout << "\n";
success = false;
}
}

View File

@ -159,6 +159,19 @@ set_property(const string &property, bool needs_response, P3D_object *value) {
return false;
}
return set_property_insecure(property, needs_response, value);
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonObject::set_property_insecure
// Access: Public
// Description: Works as set_property(), but does not check the
// matches_script_origin flag. Intended to be called
// internally only, never to be called from Javascript.
////////////////////////////////////////////////////////////////////
bool P3DPythonObject::
set_property_insecure(const string &property, bool needs_response,
P3D_object *value) {
bool bresult = !needs_response;
P3D_object *params[2];
@ -168,12 +181,12 @@ set_property(const string &property, bool needs_response, P3D_object *value) {
if (value == NULL) {
// Delete an attribute.
result = call("__del_property__", needs_response, params, 1);
result = call_insecure("__del_property__", needs_response, params, 1);
} else {
// Set a new attribute.
params[1] = value;
result = call("__set_property__", needs_response, params, 2);
result = call_insecure("__set_property__", needs_response, params, 2);
}
P3D_OBJECT_DECREF(params[0]);
@ -244,6 +257,19 @@ call(const string &method_name, bool needs_response,
return NULL;
}
return call_insecure(method_name, needs_response, params, num_params);
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonObject::call_insecure
// Access: Public
// Description: Works as call(), but does not check the
// matches_script_origin flag. Intended to be called
// internally only, never to be called from Javascript.
////////////////////////////////////////////////////////////////////
P3D_object *P3DPythonObject::
call_insecure(const string &method_name, bool needs_response,
P3D_object *params[], int num_params) {
TiXmlDocument *doc = new TiXmlDocument;
TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "pyobj");
@ -331,6 +357,18 @@ fill_xml(TiXmlElement *xvalue, P3DSession *session) {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonObject::as_python_object
// Access: Public, Virtual
// Description: Returns this object, downcast to a P3DPythonObject,
// if it is in fact an object of that type; or NULL if
// it is not.
////////////////////////////////////////////////////////////////////
P3DPythonObject *P3DPythonObject::
as_python_object() {
return this;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPythonObject::get_session
// Access: Public

View File

@ -43,14 +43,20 @@ public:
virtual P3D_object *get_property(const string &property);
virtual bool set_property(const string &property, bool needs_response, P3D_object *value);
bool set_property_insecure(const string &property, bool needs_response,
P3D_object *value);
virtual bool has_method(const string &method_name);
virtual P3D_object *call(const string &method_name, bool needs_response,
P3D_object *params[], int num_params);
P3D_object *call_insecure(const string &method_name, bool needs_response,
P3D_object *params[], int num_params);
virtual void output(ostream &out);
virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
virtual P3DPythonObject *as_python_object();
P3DSession *get_session();
int get_object_id();

View File

@ -1096,6 +1096,12 @@ EXPCL_P3D_PLUGIN P3D_instance_handle_event_func P3D_instance_handle_event;
#endif /* P3D_FUNCTION_PROTOTYPES */
// The default max_age, if none is specified in a particular
// contents.xml, is 5 seconds. This gives us enough time to start a
// few packages downloading, without re-querying the host for a new
// contents.xml at each operation.
#define P3D_CONTENTS_DEFAULT_MAX_AGE 5
#ifdef __cplusplus
}; /* end of extern "C" */
#endif

View File

@ -58,6 +58,7 @@ PPInstance(NPMIMEType pluginType, NPP instance, uint16_t mode,
_window_handle_type = window_handle_type;
_event_type = event_type;
_script_object = NULL;
_contents_expiration = 0;
_failed = false;
_started = false;
@ -171,15 +172,30 @@ begin() {
}
#endif // __APPLE__
if (!is_plugin_loaded() && !_failed) {
// Go download the contents file, so we can download the core DLL.
string url = PANDA_PACKAGE_HOST_URL;
if (!url.empty() && url[url.length() - 1] != '/') {
url += '/';
}
_download_url_prefix = url;
if (!is_plugin_loaded() && !_failed) {
// We need to read the contents.xml file. First, check to see if
// the version on disk is already current enough.
bool success = false;
string contents_filename = _root_dir + "/contents.xml";
if (read_contents_file(contents_filename, false)) {
if (time(NULL) < _contents_expiration) {
// Got the file, and it's good.
get_core_api();
success = true;
}
}
if (!success) {
// Go download the latest contents.xml file.
ostringstream strm;
strm << url << "contents.xml";
strm << _download_url_prefix << "contents.xml";
// Append a uniquifying query string to the URL to force the
// download to go all the way through any caches. We use the time
@ -190,6 +206,7 @@ begin() {
PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file);
start_download(url, req);
}
}
handle_request_loop();
}
@ -562,7 +579,9 @@ url_notify(const char *url, NPReason reason, void *notifyData) {
// there's an outstanding contents.xml file on disk, try to
// load that one as a fallback.
string contents_filename = _root_dir + "/contents.xml";
if (!read_contents_file(contents_filename)) {
if (read_contents_file(contents_filename, false)) {
get_core_api();
} else {
nout << "Unable to read contents file " << contents_filename << "\n";
set_failed();
}
@ -1134,18 +1153,58 @@ start_download(const string &url, PPDownloadRequest *req) {
////////////////////////////////////////////////////////////////////
// Function: PPInstance::read_contents_file
// Access: Private
// Description: Reads the contents.xml file and starts the core API
// DLL downloading, if necessary.
// Description: Attempts to open and read the contents.xml file on
// disk. Copies the file to its standard location
// on success. Returns true on success, false on
// failure.
////////////////////////////////////////////////////////////////////
bool PPInstance::
read_contents_file(const string &contents_filename) {
read_contents_file(const string &contents_filename, bool fresh_download) {
TiXmlDocument doc(contents_filename.c_str());
if (!doc.LoadFile()) {
return false;
}
bool found_core_package = false;
TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents != NULL) {
int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE;
xcontents->Attribute("max_age", &max_age);
// Get the latest possible expiration time, based on the max_age
// indication. Any expiration time later than this is in error.
time_t now = time(NULL);
_contents_expiration = now + (time_t)max_age;
if (fresh_download) {
// Update the XML with the new download information.
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
while (xorig != NULL) {
xcontents->RemoveChild(xorig);
xorig = xcontents->FirstChildElement("orig");
}
xorig = new TiXmlElement("orig");
xcontents->LinkEndChild(xorig);
xorig->SetAttribute("expiration", (int)_contents_expiration);
} else {
// Read the expiration time from the XML.
int expiration = 0;
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
if (xorig != NULL) {
xorig->Attribute("expiration", &expiration);
}
_contents_expiration = min(_contents_expiration, (time_t)expiration);
}
nout << "read contents.xml, max_age = " << max_age
<< ", expires in " << max(_contents_expiration, now) - now
<< " s\n";
// Look for the <host> entry; it might point us at a different
// download URL, and it might mention some mirrors.
find_host(xcontents);
@ -1157,8 +1216,9 @@ read_contents_file(const string &contents_filename) {
if (name != NULL && strcmp(name, "coreapi") == 0) {
const char *platform = xpackage->Attribute("platform");
if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) {
get_core_api(xpackage);
return true;
_core_api_dll.load_xml(xpackage);
found_core_package = true;
break;
}
}
@ -1166,12 +1226,25 @@ read_contents_file(const string &contents_filename) {
}
}
if (!found_core_package) {
// Couldn't find the coreapi package description.
nout << "No coreapi package defined in contents file for "
<< DTOOL_PLATFORM << "\n";
return false;
}
// Success. Now save the file in its proper place.
string standard_filename = _root_dir + "/contents.xml";
mkfile_complete(standard_filename, nout);
if (!doc.SaveFile(standard_filename.c_str())) {
nout << "Couldn't rewrite " << standard_filename << "\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: PPInstance::get_filename_from_url
// Access: Private, Static
@ -1212,12 +1285,13 @@ void PPInstance::
downloaded_file(PPDownloadRequest *req, const string &filename) {
switch (req->_rtype) {
case PPDownloadRequest::RT_contents_file:
{
// Now we have the contents.xml file. Read this to get the
// filename and md5 hash of our core API DLL.
if (read_contents_file(filename)) {
// Successfully read. Copy it into its normal place.
string contents_filename = _root_dir + "/contents.xml";
copy_file(filename, contents_filename);
if (read_contents_file(filename, true)) {
// Successfully downloaded and read, and it has been written
// into its normal place.
get_core_api();
} else {
// Error reading the contents.xml file, or in loading the core
@ -1227,11 +1301,14 @@ downloaded_file(PPDownloadRequest *req, const string &filename) {
// If there's an outstanding contents.xml file on disk, try to
// load that one as a fallback.
string contents_filename = _root_dir + "/contents.xml";
if (!read_contents_file(contents_filename)) {
if (read_contents_file(contents_filename, false)) {
get_core_api();
} else {
nout << "Unable to read contents file " << contents_filename << "\n";
set_failed();
}
}
}
break;
case PPDownloadRequest::RT_core_dll:
@ -1350,9 +1427,7 @@ send_p3d_temp_file_data() {
// if necessary.
////////////////////////////////////////////////////////////////////
void PPInstance::
get_core_api(TiXmlElement *xpackage) {
_core_api_dll.load_xml(xpackage);
get_core_api() {
if (_core_api_dll.quick_verify(_root_dir)) {
// The DLL file is good. Just load it.
do_load_plugin();
@ -1477,7 +1552,9 @@ do_load_plugin() {
#endif // P3D_PLUGIN_P3D_PLUGIN
nout << "Attempting to load core API from " << pathname << "\n";
if (!load_plugin(pathname, "", "", true, "", "", "", false, false,
string contents_filename = _root_dir + "/contents.xml";
if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
true, "", "", "", false, false,
_root_dir, nout)) {
nout << "Unable to launch core API in " << pathname << "\n";
set_failed();

View File

@ -84,8 +84,8 @@ private:
void open_p3d_temp_file();
void send_p3d_temp_file_data();
bool read_contents_file(const string &contents_filename);
void get_core_api(TiXmlElement *xpackage);
bool read_contents_file(const string &contents_filename, bool fresh_download);
void get_core_api();
void downloaded_plugin(const string &filename);
void do_load_plugin();
@ -140,6 +140,7 @@ private:
CoreUrls _core_urls;
FileSpec _core_api_dll;
time_t _contents_expiration;
bool _failed;
bool _started;

View File

@ -353,13 +353,19 @@ post_arg_processing() {
bool Panda3D::
get_plugin() {
// First, look for the existing contents.xml file.
bool success = false;
Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (!_verify_contents && read_contents_file(contents_filename)) {
if (read_contents_file(contents_filename, false)) {
if (!_verify_contents || time(NULL) < _contents_expiration) {
// Got the file, and it's good.
return true;
success = true;
}
}
// Couldn't read it, so go get it.
if (!success) {
// Couldn't read it (or it wasn't current enough), so go get a new
// one.
HTTPClient *http = HTTPClient::get_global_ptr();
// Try the super_mirror first.
@ -377,19 +383,13 @@ get_plugin() {
cerr << "Unable to download " << url << "\n";
tempfile.unlink();
} else {
// Successfully downloaded from the super_mirror; move it into
// place and try to read it.
contents_filename.make_dir();
contents_filename.unlink();
tempfile.rename_to(contents_filename);
if (read_contents_file(contents_filename)) {
return true;
// Successfully downloaded from the super_mirror; try to read it.
success = read_contents_file(tempfile, true);
tempfile.unlink();
}
}
// Failed to download from the super_mirror.
}
if (!success) {
// Go download contents.xml from the actual host.
ostringstream strm;
strm << _host_url_prefix << "contents.xml";
@ -407,45 +407,89 @@ get_plugin() {
PT(HTTPChannel) channel = http->make_channel(false);
channel->get_document(request);
// Since we have to download some of it, might as well ask the core
// API to check all of it.
_verify_contents = true;
// First, download it to a temporary file.
Filename tempfile = Filename::temporary("", "p3d_");
if (!channel->download_to_file(tempfile)) {
cerr << "Unable to download " << url << "\n";
tempfile.unlink();
// Couldn't download, but fall through and try to read the
// contents.xml file anyway. Maybe it's good enough.
// Couldn't download, but try to read the existing contents.xml
// file anyway. Maybe it's good enough.
success = read_contents_file(contents_filename, false);
} else {
// Successfully downloaded; move the temporary file into place.
contents_filename.make_dir();
contents_filename.unlink();
tempfile.rename_to(contents_filename);
// Successfully downloaded; read it and move it into place.
success = read_contents_file(tempfile, true);
}
// Since we had to download some of it, might as well ask the core
// API to check all of it.
_verify_contents = true;
tempfile.unlink();
}
}
return read_contents_file(contents_filename);
if (success) {
// Now that we've downloaded the contents file successfully, start
// the Core API.
success = get_core_api();
}
return success;
}
////////////////////////////////////////////////////////////////////
// Function: Panda3D::read_contents_file
// Access: Protected
// Description: Attempts to open and read the contents.xml file on
// disk, and uses that data to load the plugin, if
// possible. Returns true on success, false on failure.
// disk. Copies the file to its standard location
// on success. Returns true on success, false on
// failure.
////////////////////////////////////////////////////////////////////
bool Panda3D::
read_contents_file(const Filename &contents_filename) {
read_contents_file(const Filename &contents_filename, bool fresh_download) {
string os_contents_filename = contents_filename.to_os_specific();
TiXmlDocument doc(os_contents_filename.c_str());
if (!doc.LoadFile()) {
return false;
}
bool found_core_package = false;
TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents != NULL) {
int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE;
xcontents->Attribute("max_age", &max_age);
// Get the latest possible expiration time, based on the max_age
// indication. Any expiration time later than this is in error.
time_t now = time(NULL);
_contents_expiration = now + (time_t)max_age;
if (fresh_download) {
// Update the XML with the new download information.
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
while (xorig != NULL) {
xcontents->RemoveChild(xorig);
xorig = xcontents->FirstChildElement("orig");
}
xorig = new TiXmlElement("orig");
xcontents->LinkEndChild(xorig);
xorig->SetAttribute("expiration", (int)_contents_expiration);
} else {
// Read the expiration time from the XML.
int expiration = 0;
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
if (xorig != NULL) {
xorig->Attribute("expiration", &expiration);
}
_contents_expiration = min(_contents_expiration, (time_t)expiration);
}
// Look for the <host> entry; it might point us at a different
// download URL, and it might mention some mirrors.
find_host(xcontents);
@ -457,7 +501,9 @@ read_contents_file(const Filename &contents_filename) {
if (name != NULL && strcmp(name, "coreapi") == 0) {
const char *platform = xpackage->Attribute("platform");
if (platform != NULL && _this_platform == string(platform)) {
return get_core_api(contents_filename, xpackage);
_core_api_dll.load_xml(xpackage);
found_core_package = true;
break;
}
}
@ -465,12 +511,40 @@ read_contents_file(const Filename &contents_filename) {
}
}
if (!found_core_package) {
// Couldn't find the coreapi package description.
nout << "No coreapi package defined in contents file for "
<< _this_platform << "\n";
return false;
}
// Success. Now copy the file into place.
Filename standard_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (fresh_download) {
Filename tempfile = Filename::temporary("", "p3d_");
string os_specific = tempfile.to_os_specific();
if (!doc.SaveFile(os_specific.c_str())) {
nout << "Couldn't write to " << tempfile << "\n";
tempfile.unlink();
return false;
}
tempfile.rename_to(standard_filename);
nout << "rewrote " << standard_filename << "\n";
} else {
if (contents_filename != standard_filename) {
if (!contents_filename.rename_to(standard_filename)) {
nout << "Couldn't move contents.xml to " << standard_filename << "\n";
contents_filename.unlink();
return false;
}
nout << "moved to " << standard_filename << "\n";
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: Panda3D::find_host
// Access: Protected
@ -598,9 +672,7 @@ choose_random_mirrors(vector_string &result, int num_mirrors) {
// if necessary.
////////////////////////////////////////////////////////////////////
bool Panda3D::
get_core_api(const Filename &contents_filename, TiXmlElement *xpackage) {
_core_api_dll.load_xml(xpackage);
get_core_api() {
if (!_core_api_dll.quick_verify(_root_dir)) {
// The DLL file needs to be downloaded. Build up our list of
// URL's to attempt to download it from, in reverse order.
@ -690,6 +762,7 @@ get_core_api(const Filename &contents_filename, TiXmlElement *xpackage) {
bool trusted_environment = !_enable_security;
Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (!load_plugin(pathname, contents_filename.to_os_specific(),
_host_url, _verify_contents, _this_platform, _log_dirname,
_log_basename, trusted_environment, _console_environment,

View File

@ -37,12 +37,12 @@ public:
protected:
bool post_arg_processing();
bool get_plugin();
bool read_contents_file(const Filename &contents_filename);
bool read_contents_file(const Filename &contents_filename, bool fresh_download);
void find_host(TiXmlElement *xcontents);
void read_xhost(TiXmlElement *xhost);
void add_mirror(string mirror_url);
void choose_random_mirrors(vector_string &result, int num_mirrors);
bool get_core_api(const Filename &contents_filename, TiXmlElement *xplugin);
bool get_core_api();
void usage();

View File

@ -65,6 +65,7 @@ Panda3DBase(bool console_environment) {
_host_url = PANDA_PACKAGE_HOST_URL;
_this_platform = DTOOL_PLATFORM;
_verify_contents = false;
_contents_expiration = 0;
// Seed the lame random number generator in rand(); we use it to
// select a mirror for downloading.

View File

@ -74,6 +74,8 @@ protected:
string _log_basename;
string _this_platform;
bool _verify_contents;
time_t _contents_expiration;
P3D_window_type _window_type;
P3D_window_handle _parent_window;
int _win_x, _win_y;