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

View File

@ -67,6 +67,17 @@ get_timestamp() const {
return _timestamp; 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 // Function: FileSpec::get_actual_file
// Access: Public // 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 // Function: FileSpec::quick_verify
// Access: Public // Access: Public
@ -272,6 +296,7 @@ check_hash(const string &pathname) const {
bool FileSpec:: bool FileSpec::
read_hash(const string &pathname) { read_hash(const string &pathname) {
memset(_hash, 0, hash_size); memset(_hash, 0, hash_size);
_got_hash = false;
ifstream stream(pathname.c_str(), ios::in | ios::binary); ifstream stream(pathname.c_str(), ios::in | ios::binary);
if (!stream) { if (!stream) {
@ -294,6 +319,7 @@ read_hash(const string &pathname) {
} }
MD5_Final(_hash, &ctx); MD5_Final(_hash, &ctx);
_got_hash = true;
return true; return true;
} }

View File

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

View File

@ -107,7 +107,8 @@ get_contents_seq() const {
// Function: P3DHost::check_contents_hash // Function: P3DHost::check_contents_hash
// Access: Public // Access: Public
// Description: Returns true if the indicated pathname has the same // 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:: inline bool P3DHost::
check_contents_hash(const string &pathname) const { check_contents_hash(const string &pathname) const {

View File

@ -40,6 +40,7 @@ P3DHost(const string &host_url) :
_descriptive_name = _host_url; _descriptive_name = _host_url;
_xcontents = NULL; _xcontents = NULL;
_contents_expiration = 0;
_contents_seq = 0; _contents_seq = 0;
} }
@ -106,6 +107,25 @@ get_alt_host(const string &alt_host) {
return this; 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 // Function: P3DHost::read_contents_file
// Access: Public // Access: Public
@ -122,21 +142,21 @@ read_contents_file() {
} }
string standard_filename = _host_dir + "/contents.xml"; 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 // Function: P3DHost::read_contents_file
// Access: Public // Access: Public
// Description: Reads the contents.xml file in the indicated // 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 // into the standard location (if it's not there
// already). // already).
// //
// Returns true on success, false on failure. // Returns true on success, false on failure.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool P3DHost:: 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()); TiXmlDocument doc(contents_filename.c_str());
if (!doc.LoadFile()) { if (!doc.LoadFile()) {
return false; return false;
@ -152,8 +172,51 @@ read_contents_file(const string &contents_filename) {
} }
_xcontents = (TiXmlElement *)xcontents->Clone(); _xcontents = (TiXmlElement *)xcontents->Clone();
++_contents_seq; ++_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); _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"); TiXmlElement *xhost = _xcontents->FirstChildElement("host");
if (xhost != NULL) { if (xhost != NULL) {
const char *url = xhost->Attribute("url"); const char *url = xhost->Attribute("url");
@ -194,12 +257,18 @@ read_contents_file(const string &contents_filename) {
mkdir_complete(_host_dir, nout); mkdir_complete(_host_dir, nout);
string standard_filename = _host_dir + "/contents.xml"; 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) != if (standardize_filename(standard_filename) !=
standardize_filename(contents_filename)) { standardize_filename(contents_filename)) {
if (!copy_file(contents_filename, standard_filename)) { if (!copy_file(contents_filename, standard_filename)) {
nout << "Couldn't copy to " << standard_filename << "\n"; nout << "Couldn't copy to " << standard_filename << "\n";
} }
} }
}
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (_host_url == inst_mgr->get_host_url()) { if (_host_url == inst_mgr->get_host_url()) {
@ -208,14 +277,13 @@ read_contents_file(const string &contents_filename) {
// iteration. // iteration.
string top_filename = inst_mgr->get_root_dir() + "/contents.xml"; string top_filename = inst_mgr->get_root_dir() + "/contents.xml";
if (standardize_filename(top_filename) != if (standardize_filename(top_filename) !=
standardize_filename(contents_filename)) { standardize_filename(standard_filename)) {
if (!copy_file(contents_filename, top_filename)) { if (!copy_file(standard_filename, top_filename)) {
nout << "Couldn't copy to " << top_filename << "\n"; nout << "Couldn't copy to " << top_filename << "\n";
} }
} }
} }
return true; return true;
} }
@ -288,24 +356,29 @@ get_package(const string &package_name, const string &package_version,
PackageMap &package_map = _packages[alt_host]; PackageMap &package_map = _packages[alt_host];
P3DPackage *package = NULL;
string key = package_name + "_" + package_version; string key = package_name + "_" + package_version;
PackageMap::iterator pi = package_map.find(key); PackageMap::iterator pi = package_map.find(key);
if (pi != package_map.end()) { if (pi != package_map.end()) {
P3DPackage *package = (*pi).second; // We've previously installed this package.
if (!package->get_failed()) { package = (*pi).second;
return package;
}
if (package->get_failed()) {
// If the package has previously failed, move it aside and try // If the package has previously failed, move it aside and try
// again (maybe it just failed because the user interrupted it). // again (maybe it just failed because the user interrupted it).
nout << "Package " << key << " has previously failed; trying again.\n"; nout << "Package " << key << " has previously failed; trying again.\n";
_failed_packages.push_back(package); _failed_packages.push_back(package);
(*pi).second = NULL; (*pi).second = NULL;
package = NULL;
}
} }
P3DPackage *package = if (package == NULL) {
package =
new P3DPackage(this, package_name, package_version, alt_host); new P3DPackage(this, package_name, package_version, alt_host);
package_map[key] = package; package_map[key] = package;
}
return package; return package;
} }
@ -694,3 +767,41 @@ copy_file(const string &from_filename, const string &to_filename) {
unlink(temp_filename.c_str()); unlink(temp_filename.c_str());
return false; 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); P3DHost *get_alt_host(const string &alt_host);
inline bool has_contents_file() const; inline bool has_contents_file() const;
bool has_current_contents_file(P3DInstanceManager *inst_mgr) const;
inline int get_contents_seq() const; inline int get_contents_seq() const;
inline bool check_contents_hash(const string &pathname) const; inline bool check_contents_hash(const string &pathname) const;
bool read_contents_file(); bool read_contents_file();
bool read_contents_file(const string &contents_filename); bool read_contents_file(const string &contents_filename, bool fresh_download);
void read_xhost(TiXmlElement *xhost); void read_xhost(TiXmlElement *xhost);
P3DPackage *get_package(const string &package_name, P3DPackage *get_package(const string &package_name,
@ -72,6 +73,7 @@ private:
static string standardize_filename(const string &filename); static string standardize_filename(const string &filename);
static bool copy_file(const string &from_filename, const string &to_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: private:
string _host_dir; string _host_dir;
@ -80,6 +82,7 @@ private:
string _download_url_prefix; string _download_url_prefix;
string _descriptive_name; string _descriptive_name;
TiXmlElement *_xcontents; TiXmlElement *_xcontents;
time_t _contents_expiration;
int _contents_seq; int _contents_seq;
FileSpec _contents_spec; FileSpec _contents_spec;

View File

@ -1141,6 +1141,11 @@ get_packages_info_ready() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool P3DInstance:: bool P3DInstance::
get_packages_ready() const { 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; Packages::const_iterator pi;
for (pi = _packages.begin(); pi != _packages.end(); ++pi) { for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
if (!(*pi)->get_ready()) { if (!(*pi)->get_ready()) {
@ -1384,6 +1389,7 @@ void P3DInstance::
splash_button_clicked_main_thread() { splash_button_clicked_main_thread() {
if (is_failed()) { if (is_failed()) {
// Can't click the button after we've failed. // Can't click the button after we've failed.
nout << "Ignoring click for failed instance\n";
return; return;
} }
@ -1391,6 +1397,8 @@ splash_button_clicked_main_thread() {
auth_button_clicked(); auth_button_clicked();
} else if (_session == NULL) { } else if (_session == NULL) {
play_button_clicked(); 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 // Once Python is up and running, we can get the actual main
// object from the Python side, and merge it with our own. // 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; TiXmlDocument *doc = new TiXmlDocument;
TiXmlElement *xcommand = new TiXmlElement("command"); TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "pyobj"); xcommand->SetAttribute("cmd", "pyobj");
@ -2383,9 +2388,16 @@ handle_notify_request(const string &message) {
} }
if (result != NULL) { 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); _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"); _panda_script_object->set_string_property("status", "starting");
@ -3006,7 +3018,6 @@ mark_download_complete() {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void P3DInstance:: void P3DInstance::
ready_to_start() { ready_to_start() {
nout << "_instance_started = " << _instance_started << "\n";
if (_instance_started || is_failed()) { if (_instance_started || is_failed()) {
// Already started--or never mind. // Already started--or never mind.
return; return;

View File

@ -261,12 +261,11 @@ initialize(int api_version, const string &contents_filename,
create_runtime_environment(); create_runtime_environment();
_is_initialized = true; _is_initialized = true;
if (!_verify_contents && if (!host_url.empty() && !contents_filename.empty()) {
!host_url.empty() && !contents_filename.empty()) {
// Attempt to pre-read the supplied contents.xml file, to avoid an // Attempt to pre-read the supplied contents.xml file, to avoid an
// unnecessary download later. // unnecessary download later.
P3DHost *host = get_host(host_url); 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"; 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 // Now that we have a pyobj, we have to transfer down all of the
// properties we'd set locally. // properties we'd set locally.
Properties::const_iterator pi; apply_properties(_pyobj);
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);
}
} }
} }
} }
@ -310,6 +305,41 @@ get_pyobj() const {
return _pyobj; 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 // Function: P3DMainObject::set_instance
// Access: Public // Access: Public

View File

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

View File

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

View File

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

View File

@ -181,6 +181,17 @@ void P3DPackage::
add_instance(P3DInstance *inst) { add_instance(P3DInstance *inst) {
_instances.push_back(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(); begin_info_download();
} }
@ -381,13 +392,13 @@ begin_info_download() {
void P3DPackage:: void P3DPackage::
download_contents_file() { download_contents_file() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (!_host->has_contents_file() && !inst_mgr->get_verify_contents()) { if (!_host->has_contents_file()) {
// If we're allowed to read a contents file without checking the // First, read whatever contents file is already on disk. Maybe
// server first, try it now. // it's current enough.
_host->read_contents_file(); _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 // We've already got a contents.xml file; go straight to the
// package desc file. // package desc file.
host_got_contents_file(); host_got_contents_file();
@ -414,14 +425,15 @@ download_contents_file() {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void P3DPackage:: void P3DPackage::
contents_file_download_finished(bool success) { contents_file_download_finished(bool success) {
if (!_host->has_contents_file()) { P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (!success || !_host->read_contents_file(_temp_contents_file->get_filename())) { 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"; nout << "Couldn't read " << *_temp_contents_file << "\n";
// Maybe we can read an already-downloaded contents.xml file. // Maybe we can read an already-downloaded contents.xml file.
string standard_filename = _host->get_host_dir() + "/contents.xml"; string standard_filename = _host->get_host_dir() + "/contents.xml";
if (_host->get_host_dir().empty() || 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. // Couldn't even read that. Fail.
report_done(false); report_done(false);
delete _temp_contents_file; delete _temp_contents_file;
@ -503,7 +515,7 @@ contents_file_redownload_finished(bool success) {
// from what we had before. // from what we had before.
if (!_host->check_contents_hash(_temp_contents_file->get_filename())) { if (!_host->check_contents_hash(_temp_contents_file->get_filename())) {
// It changed! Now see if we can read the new contents. // 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. // Huh, appears to have changed to something bad. Never mind.
nout << "Couldn't read " << *_temp_contents_file << "\n"; nout << "Couldn't read " << *_temp_contents_file << "\n";
@ -569,7 +581,8 @@ host_got_contents_file() {
// host. // host.
_alt_host.clear(); _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. // Now go back and get the contents.xml file for the new host.
download_contents_file(); download_contents_file();
return; return;
@ -1434,6 +1447,12 @@ download_finished(bool success) {
if (!_file_spec.full_verify(_package->_package_dir)) { if (!_file_spec.full_verify(_package->_package_dir)) {
nout << "After downloading " << get_url() nout << "After downloading " << get_url()
<< ", failed hash check\n"; << ", 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; success = false;
} }
} }

View File

@ -159,6 +159,19 @@ set_property(const string &property, bool needs_response, P3D_object *value) {
return false; 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; bool bresult = !needs_response;
P3D_object *params[2]; P3D_object *params[2];
@ -168,12 +181,12 @@ set_property(const string &property, bool needs_response, P3D_object *value) {
if (value == NULL) { if (value == NULL) {
// Delete an attribute. // Delete an attribute.
result = call("__del_property__", needs_response, params, 1); result = call_insecure("__del_property__", needs_response, params, 1);
} else { } else {
// Set a new attribute. // Set a new attribute.
params[1] = value; 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]); P3D_OBJECT_DECREF(params[0]);
@ -244,6 +257,19 @@ call(const string &method_name, bool needs_response,
return NULL; 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; TiXmlDocument *doc = new TiXmlDocument;
TiXmlElement *xcommand = new TiXmlElement("command"); TiXmlElement *xcommand = new TiXmlElement("command");
xcommand->SetAttribute("cmd", "pyobj"); xcommand->SetAttribute("cmd", "pyobj");
@ -331,6 +357,18 @@ fill_xml(TiXmlElement *xvalue, P3DSession *session) {
return false; 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 // Function: P3DPythonObject::get_session
// Access: Public // Access: Public

View File

@ -43,14 +43,20 @@ public:
virtual P3D_object *get_property(const string &property); virtual P3D_object *get_property(const string &property);
virtual bool set_property(const string &property, bool needs_response, P3D_object *value); 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 bool has_method(const string &method_name);
virtual P3D_object *call(const string &method_name, bool needs_response, virtual P3D_object *call(const string &method_name, bool needs_response,
P3D_object *params[], int num_params); 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 void output(ostream &out);
virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session); virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
virtual P3DPythonObject *as_python_object();
P3DSession *get_session(); P3DSession *get_session();
int get_object_id(); 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 */ #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 #ifdef __cplusplus
}; /* end of extern "C" */ }; /* end of extern "C" */
#endif #endif

View File

@ -58,6 +58,7 @@ PPInstance(NPMIMEType pluginType, NPP instance, uint16_t mode,
_window_handle_type = window_handle_type; _window_handle_type = window_handle_type;
_event_type = event_type; _event_type = event_type;
_script_object = NULL; _script_object = NULL;
_contents_expiration = 0;
_failed = false; _failed = false;
_started = false; _started = false;
@ -171,15 +172,30 @@ begin() {
} }
#endif // __APPLE__ #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; string url = PANDA_PACKAGE_HOST_URL;
if (!url.empty() && url[url.length() - 1] != '/') { if (!url.empty() && url[url.length() - 1] != '/') {
url += '/'; url += '/';
} }
_download_url_prefix = 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; ostringstream strm;
strm << url << "contents.xml"; strm << _download_url_prefix << "contents.xml";
// Append a uniquifying query string to the URL to force the // Append a uniquifying query string to the URL to force the
// download to go all the way through any caches. We use the time // 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); PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file);
start_download(url, req); start_download(url, req);
} }
}
handle_request_loop(); 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 // there's an outstanding contents.xml file on disk, try to
// load that one as a fallback. // load that one as a fallback.
string contents_filename = _root_dir + "/contents.xml"; 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"; nout << "Unable to read contents file " << contents_filename << "\n";
set_failed(); set_failed();
} }
@ -1134,18 +1153,58 @@ start_download(const string &url, PPDownloadRequest *req) {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: PPInstance::read_contents_file // Function: PPInstance::read_contents_file
// Access: Private // Access: Private
// Description: Reads the contents.xml file and starts the core API // Description: Attempts to open and read the contents.xml file on
// DLL downloading, if necessary. // disk. Copies the file to its standard location
// on success. Returns true on success, false on
// failure.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool PPInstance:: 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()); TiXmlDocument doc(contents_filename.c_str());
if (!doc.LoadFile()) { if (!doc.LoadFile()) {
return false; return false;
} }
bool found_core_package = false;
TiXmlElement *xcontents = doc.FirstChildElement("contents"); TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents != NULL) { 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 // Look for the <host> entry; it might point us at a different
// download URL, and it might mention some mirrors. // download URL, and it might mention some mirrors.
find_host(xcontents); find_host(xcontents);
@ -1157,8 +1216,9 @@ read_contents_file(const string &contents_filename) {
if (name != NULL && strcmp(name, "coreapi") == 0) { if (name != NULL && strcmp(name, "coreapi") == 0) {
const char *platform = xpackage->Attribute("platform"); const char *platform = xpackage->Attribute("platform");
if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) { if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) {
get_core_api(xpackage); _core_api_dll.load_xml(xpackage);
return true; found_core_package = true;
break;
} }
} }
@ -1166,10 +1226,23 @@ read_contents_file(const string &contents_filename) {
} }
} }
if (!found_core_package) {
// Couldn't find the coreapi package description. // Couldn't find the coreapi package description.
nout << "No coreapi package defined in contents file for " nout << "No coreapi package defined in contents file for "
<< DTOOL_PLATFORM << "\n"; << DTOOL_PLATFORM << "\n";
return false; 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;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -1212,12 +1285,13 @@ void PPInstance::
downloaded_file(PPDownloadRequest *req, const string &filename) { downloaded_file(PPDownloadRequest *req, const string &filename) {
switch (req->_rtype) { switch (req->_rtype) {
case PPDownloadRequest::RT_contents_file: case PPDownloadRequest::RT_contents_file:
{
// Now we have the contents.xml file. Read this to get the // Now we have the contents.xml file. Read this to get the
// filename and md5 hash of our core API DLL. // filename and md5 hash of our core API DLL.
if (read_contents_file(filename)) { if (read_contents_file(filename, true)) {
// Successfully read. Copy it into its normal place. // Successfully downloaded and read, and it has been written
string contents_filename = _root_dir + "/contents.xml"; // into its normal place.
copy_file(filename, contents_filename); get_core_api();
} else { } else {
// Error reading the contents.xml file, or in loading the core // 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 // If there's an outstanding contents.xml file on disk, try to
// load that one as a fallback. // load that one as a fallback.
string contents_filename = _root_dir + "/contents.xml"; 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"; nout << "Unable to read contents file " << contents_filename << "\n";
set_failed(); set_failed();
} }
} }
}
break; break;
case PPDownloadRequest::RT_core_dll: case PPDownloadRequest::RT_core_dll:
@ -1350,9 +1427,7 @@ send_p3d_temp_file_data() {
// if necessary. // if necessary.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void PPInstance:: void PPInstance::
get_core_api(TiXmlElement *xpackage) { get_core_api() {
_core_api_dll.load_xml(xpackage);
if (_core_api_dll.quick_verify(_root_dir)) { if (_core_api_dll.quick_verify(_root_dir)) {
// The DLL file is good. Just load it. // The DLL file is good. Just load it.
do_load_plugin(); do_load_plugin();
@ -1477,7 +1552,9 @@ do_load_plugin() {
#endif // P3D_PLUGIN_P3D_PLUGIN #endif // P3D_PLUGIN_P3D_PLUGIN
nout << "Attempting to load core API from " << pathname << "\n"; 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)) { _root_dir, nout)) {
nout << "Unable to launch core API in " << pathname << "\n"; nout << "Unable to launch core API in " << pathname << "\n";
set_failed(); set_failed();

View File

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

View File

@ -353,13 +353,19 @@ post_arg_processing() {
bool Panda3D:: bool Panda3D::
get_plugin() { get_plugin() {
// First, look for the existing contents.xml file. // First, look for the existing contents.xml file.
bool success = false;
Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml"); 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. // 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(); HTTPClient *http = HTTPClient::get_global_ptr();
// Try the super_mirror first. // Try the super_mirror first.
@ -377,19 +383,13 @@ get_plugin() {
cerr << "Unable to download " << url << "\n"; cerr << "Unable to download " << url << "\n";
tempfile.unlink(); tempfile.unlink();
} else { } else {
// Successfully downloaded from the super_mirror; move it into // Successfully downloaded from the super_mirror; try to read it.
// place and try to read it. success = read_contents_file(tempfile, true);
contents_filename.make_dir(); tempfile.unlink();
contents_filename.unlink();
tempfile.rename_to(contents_filename);
if (read_contents_file(contents_filename)) {
return true;
} }
} }
// Failed to download from the super_mirror. if (!success) {
}
// Go download contents.xml from the actual host. // Go download contents.xml from the actual host.
ostringstream strm; ostringstream strm;
strm << _host_url_prefix << "contents.xml"; strm << _host_url_prefix << "contents.xml";
@ -407,45 +407,89 @@ get_plugin() {
PT(HTTPChannel) channel = http->make_channel(false); PT(HTTPChannel) channel = http->make_channel(false);
channel->get_document(request); 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. // First, download it to a temporary file.
Filename tempfile = Filename::temporary("", "p3d_"); Filename tempfile = Filename::temporary("", "p3d_");
if (!channel->download_to_file(tempfile)) { if (!channel->download_to_file(tempfile)) {
cerr << "Unable to download " << url << "\n"; cerr << "Unable to download " << url << "\n";
tempfile.unlink();
// Couldn't download, but fall through and try to read the // Couldn't download, but try to read the existing contents.xml
// contents.xml file anyway. Maybe it's good enough. // file anyway. Maybe it's good enough.
success = read_contents_file(contents_filename, false);
} else { } else {
// Successfully downloaded; move the temporary file into place. // Successfully downloaded; read it and move it into place.
contents_filename.make_dir(); success = read_contents_file(tempfile, true);
contents_filename.unlink();
tempfile.rename_to(contents_filename);
} }
// Since we had to download some of it, might as well ask the core tempfile.unlink();
// API to check all of it. }
_verify_contents = true; }
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 // Function: Panda3D::read_contents_file
// Access: Protected // Access: Protected
// Description: Attempts to open and read the contents.xml file on // Description: Attempts to open and read the contents.xml file on
// disk, and uses that data to load the plugin, if // disk. Copies the file to its standard location
// possible. Returns true on success, false on failure. // on success. Returns true on success, false on
// failure.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool Panda3D:: 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(); string os_contents_filename = contents_filename.to_os_specific();
TiXmlDocument doc(os_contents_filename.c_str()); TiXmlDocument doc(os_contents_filename.c_str());
if (!doc.LoadFile()) { if (!doc.LoadFile()) {
return false; return false;
} }
bool found_core_package = false;
TiXmlElement *xcontents = doc.FirstChildElement("contents"); TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents != NULL) { 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 // Look for the <host> entry; it might point us at a different
// download URL, and it might mention some mirrors. // download URL, and it might mention some mirrors.
find_host(xcontents); find_host(xcontents);
@ -457,7 +501,9 @@ read_contents_file(const Filename &contents_filename) {
if (name != NULL && strcmp(name, "coreapi") == 0) { if (name != NULL && strcmp(name, "coreapi") == 0) {
const char *platform = xpackage->Attribute("platform"); const char *platform = xpackage->Attribute("platform");
if (platform != NULL && _this_platform == string(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,10 +511,38 @@ read_contents_file(const Filename &contents_filename) {
} }
} }
if (!found_core_package) {
// Couldn't find the coreapi package description. // Couldn't find the coreapi package description.
nout << "No coreapi package defined in contents file for " nout << "No coreapi package defined in contents file for "
<< _this_platform << "\n"; << _this_platform << "\n";
return false; 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;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -598,9 +672,7 @@ choose_random_mirrors(vector_string &result, int num_mirrors) {
// if necessary. // if necessary.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool Panda3D:: bool Panda3D::
get_core_api(const Filename &contents_filename, TiXmlElement *xpackage) { get_core_api() {
_core_api_dll.load_xml(xpackage);
if (!_core_api_dll.quick_verify(_root_dir)) { if (!_core_api_dll.quick_verify(_root_dir)) {
// The DLL file needs to be downloaded. Build up our list of // The DLL file needs to be downloaded. Build up our list of
// URL's to attempt to download it from, in reverse order. // 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; 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(), if (!load_plugin(pathname, contents_filename.to_os_specific(),
_host_url, _verify_contents, _this_platform, _log_dirname, _host_url, _verify_contents, _this_platform, _log_dirname,
_log_basename, trusted_environment, _console_environment, _log_basename, trusted_environment, _console_environment,

View File

@ -37,12 +37,12 @@ public:
protected: protected:
bool post_arg_processing(); bool post_arg_processing();
bool get_plugin(); 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 find_host(TiXmlElement *xcontents);
void read_xhost(TiXmlElement *xhost); void read_xhost(TiXmlElement *xhost);
void add_mirror(string mirror_url); void add_mirror(string mirror_url);
void choose_random_mirrors(vector_string &result, int num_mirrors); 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(); void usage();

View File

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

View File

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