diff --git a/direct/src/p3d/Packager.py b/direct/src/p3d/Packager.py index 2a40cfea9e..a6e39558dd 100644 --- a/direct/src/p3d/Packager.py +++ b/direct/src/p3d/Packager.py @@ -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: diff --git a/direct/src/plugin/fileSpec.I b/direct/src/plugin/fileSpec.I index 88ade091d6..5d7ffe53e3 100755 --- a/direct/src/plugin/fileSpec.I +++ b/direct/src/plugin/fileSpec.I @@ -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 diff --git a/direct/src/plugin/fileSpec.cxx b/direct/src/plugin/fileSpec.cxx index c100784841..f542a52546 100755 --- a/direct/src/plugin/fileSpec.cxx +++ b/direct/src/plugin/fileSpec.cxx @@ -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; } diff --git a/direct/src/plugin/fileSpec.h b/direct/src/plugin/fileSpec.h index 09ecfe1913..2b6a94786f 100755 --- a/direct/src/plugin/fileSpec.h +++ b/direct/src/plugin/fileSpec.h @@ -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); diff --git a/direct/src/plugin/p3dHost.I b/direct/src/plugin/p3dHost.I index 14a1c10f2a..40bcaee2ca 100644 --- a/direct/src/plugin/p3dHost.I +++ b/direct/src/plugin/p3dHost.I @@ -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 { diff --git a/direct/src/plugin/p3dHost.cxx b/direct/src/plugin/p3dHost.cxx index 0a0b7dac51..c0c3ced5ae 100644 --- a/direct/src/plugin/p3dHost.cxx +++ b/direct/src/plugin/p3dHost.cxx @@ -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,7 +172,50 @@ read_contents_file(const string &contents_filename) { } _xcontents = (TiXmlElement *)xcontents->Clone(); ++_contents_seq; - _contents_spec.read_hash(contents_filename); + _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) { @@ -194,10 +257,16 @@ read_contents_file(const string &contents_filename) { mkdir_complete(_host_dir, nout); string standard_filename = _host_dir + "/contents.xml"; - if (standardize_filename(standard_filename) != - standardize_filename(contents_filename)) { - if (!copy_file(contents_filename, standard_filename)) { - nout << "Couldn't copy to " << standard_filename << "\n"; + 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"; + } } } @@ -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,25 +356,30 @@ 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 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; + 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 = - new P3DPackage(this, package_name, package_version, alt_host); - package_map[key] = 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; +} diff --git a/direct/src/plugin/p3dHost.h b/direct/src/plugin/p3dHost.h index ebddf4a2e1..33dea9acd3 100644 --- a/direct/src/plugin/p3dHost.h +++ b/direct/src/plugin/p3dHost.h @@ -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; diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index ab52b7c71a..dedba4e486 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -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,32 +2368,36 @@ 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"); - xcommand->SetAttribute("op", "get_panda_script_object"); - doc->LinkEndChild(xcommand); - TiXmlDocument *response = _session->command_and_response(doc); - - P3D_object *result = NULL; - if (response != NULL) { - TiXmlElement *xresponse = response->FirstChildElement("response"); - if (xresponse != NULL) { - TiXmlElement *xvalue = xresponse->FirstChildElement("value"); - if (xvalue != NULL) { - result = _session->xml_to_p3dobj(xvalue); - } + TiXmlDocument *doc = new TiXmlDocument; + TiXmlElement *xcommand = new TiXmlElement("command"); + xcommand->SetAttribute("cmd", "pyobj"); + xcommand->SetAttribute("op", "get_panda_script_object"); + doc->LinkEndChild(xcommand); + TiXmlDocument *response = _session->command_and_response(doc); + + P3D_object *result = NULL; + if (response != NULL) { + TiXmlElement *xresponse = response->FirstChildElement("response"); + if (xresponse != NULL) { + TiXmlElement *xvalue = xresponse->FirstChildElement("value"); + if (xvalue != NULL) { + result = _session->xml_to_p3dobj(xvalue); } - delete response; } - - if (result != NULL) { + delete response; + } + + 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; diff --git a/direct/src/plugin/p3dInstanceManager.cxx b/direct/src/plugin/p3dInstanceManager.cxx index 0bfcc1a6cf..672670ad72 100644 --- a/direct/src/plugin/p3dInstanceManager.cxx +++ b/direct/src/plugin/p3dInstanceManager.cxx @@ -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"; } } diff --git a/direct/src/plugin/p3dMainObject.cxx b/direct/src/plugin/p3dMainObject.cxx index 27f435bb55..b10375809a 100644 --- a/direct/src/plugin/p3dMainObject.cxx +++ b/direct/src/plugin/p3dMainObject.cxx @@ -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 diff --git a/direct/src/plugin/p3dMainObject.h b/direct/src/plugin/p3dMainObject.h index 53b713cdfb..c7cda1c015 100644 --- a/direct/src/plugin/p3dMainObject.h +++ b/direct/src/plugin/p3dMainObject.h @@ -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); diff --git a/direct/src/plugin/p3dObject.cxx b/direct/src/plugin/p3dObject.cxx index 6fab75e13f..612fcd0603 100644 --- a/direct/src/plugin/p3dObject.cxx +++ b/direct/src/plugin/p3dObject.cxx @@ -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 diff --git a/direct/src/plugin/p3dObject.h b/direct/src/plugin/p3dObject.h index f3957c25cd..56d5e100a3 100644 --- a/direct/src/plugin/p3dObject.h +++ b/direct/src/plugin/p3dObject.h @@ -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); diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 72429b78ea..b5a9b5c86e 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -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; } } diff --git a/direct/src/plugin/p3dPythonObject.cxx b/direct/src/plugin/p3dPythonObject.cxx index c22307ba5c..2195321b22 100644 --- a/direct/src/plugin/p3dPythonObject.cxx +++ b/direct/src/plugin/p3dPythonObject.cxx @@ -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 diff --git a/direct/src/plugin/p3dPythonObject.h b/direct/src/plugin/p3dPythonObject.h index 8c3aed4f4e..8a98e23f0f 100644 --- a/direct/src/plugin/p3dPythonObject.h +++ b/direct/src/plugin/p3dPythonObject.h @@ -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(); diff --git a/direct/src/plugin/p3d_plugin.h b/direct/src/plugin/p3d_plugin.h index c7d6bc614d..53b0e97c4f 100644 --- a/direct/src/plugin/p3d_plugin.h +++ b/direct/src/plugin/p3d_plugin.h @@ -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 diff --git a/direct/src/plugin_npapi/ppInstance.cxx b/direct/src/plugin_npapi/ppInstance.cxx index d132e43c77..990cc7c9a2 100644 --- a/direct/src/plugin_npapi/ppInstance.cxx +++ b/direct/src/plugin_npapi/ppInstance.cxx @@ -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,24 +172,40 @@ begin() { } #endif // __APPLE__ + string url = PANDA_PACKAGE_HOST_URL; + if (!url.empty() && url[url.length() - 1] != '/') { + url += '/'; + } + _download_url_prefix = url; + 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 += '/'; + // 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; + } } - _download_url_prefix = url; - ostringstream strm; - strm << url << "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 - // in seconds; that's unique enough. - strm << "?" << time(NULL); - url = strm.str(); - - PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file); - start_download(url, req); + if (!success) { + // Go download the latest contents.xml file. + ostringstream strm; + 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 + // in seconds; that's unique enough. + strm << "?" << time(NULL); + url = strm.str(); + + 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 entry; it might point us at a different // download URL, and it might mention some mirrors. find_host(xcontents); @@ -1157,19 +1216,33 @@ 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; } } - + xpackage = xpackage->NextSiblingElement("package"); } } - // Couldn't find the coreapi package description. - nout << "No coreapi package defined in contents file for " - << DTOOL_PLATFORM << "\n"; - return false; + 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; } //////////////////////////////////////////////////////////////////// @@ -1212,24 +1285,28 @@ 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); - - } else { - // Error reading the contents.xml file, or in loading the core - // API that it references. - nout << "Unable to read contents file " << filename << "\n"; - - // 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)) { - nout << "Unable to read contents file " << contents_filename << "\n"; - set_failed(); + { + // 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, 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 + // API that it references. + nout << "Unable to read contents file " << filename << "\n"; + + // 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, false)) { + get_core_api(); + } else { + nout << "Unable to read contents file " << contents_filename << "\n"; + set_failed(); + } } } break; @@ -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(); diff --git a/direct/src/plugin_npapi/ppInstance.h b/direct/src/plugin_npapi/ppInstance.h index 97f96c5bc5..2e8dd2cf36 100644 --- a/direct/src/plugin_npapi/ppInstance.h +++ b/direct/src/plugin_npapi/ppInstance.h @@ -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; diff --git a/direct/src/plugin_standalone/panda3d.cxx b/direct/src/plugin_standalone/panda3d.cxx index 81b8ae252b..45180d4858 100644 --- a/direct/src/plugin_standalone/panda3d.cxx +++ b/direct/src/plugin_standalone/panda3d.cxx @@ -353,99 +353,143 @@ 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)) { - // Got the file, and it's good. - return true; + if (read_contents_file(contents_filename, false)) { + if (!_verify_contents || time(NULL) < _contents_expiration) { + // Got the file, and it's good. + success = true; + } } - // Couldn't read it, so go get it. - HTTPClient *http = HTTPClient::get_global_ptr(); - - // Try the super_mirror first. - if (!_super_mirror_url_prefix.empty()) { - // We don't bother putting a uniquifying query string when we're - // downloading this file from the super_mirror. The super_mirror - // is by definition a cache, so it doesn't make sense to bust - // caches here. - string url = _super_mirror_url_prefix + "contents.xml"; - PT(HTTPChannel) channel = http->make_channel(false); - channel->get_document(url); - - Filename tempfile = Filename::temporary("", "p3d_"); - if (!channel->download_to_file(tempfile)) { - 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; + 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. + if (!_super_mirror_url_prefix.empty()) { + // We don't bother putting a uniquifying query string when we're + // downloading this file from the super_mirror. The super_mirror + // is by definition a cache, so it doesn't make sense to bust + // caches here. + string url = _super_mirror_url_prefix + "contents.xml"; + PT(HTTPChannel) channel = http->make_channel(false); + channel->get_document(url); + + Filename tempfile = Filename::temporary("", "p3d_"); + if (!channel->download_to_file(tempfile)) { + cerr << "Unable to download " << url << "\n"; + tempfile.unlink(); + } else { + // 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"; + // Append a uniquifying query string to the URL to force the + // download to go all the way through any caches. We use the time + // in seconds; that's unique enough. + strm << "?" << time(NULL); + string url = strm.str(); + + // We might as well explicitly request the cache to be disabled too, + // since we have an interface for that via HTTPChannel. + DocumentSpec request(url); + request.set_cache_control(DocumentSpec::CC_no_cache); + + 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"; + + // 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; read it and move it into place. + success = read_contents_file(tempfile, true); + } + + tempfile.unlink(); + } } - // Go download contents.xml from the actual host. - ostringstream strm; - strm << _host_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 - // in seconds; that's unique enough. - strm << "?" << time(NULL); - string url = strm.str(); - - // We might as well explicitly request the cache to be disabled too, - // since we have an interface for that via HTTPChannel. - DocumentSpec request(url); - request.set_cache_control(DocumentSpec::CC_no_cache); - - PT(HTTPChannel) channel = http->make_channel(false); - channel->get_document(request); - - // 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. - } else { - // Successfully downloaded; move the temporary file into place. - contents_filename.make_dir(); - contents_filename.unlink(); - tempfile.rename_to(contents_filename); + if (success) { + // Now that we've downloaded the contents file successfully, start + // the Core API. + success = get_core_api(); } - // Since we had to download some of it, might as well ask the core - // API to check all of it. - _verify_contents = true; - - return read_contents_file(contents_filename); + 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 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,10 +511,38 @@ read_contents_file(const Filename &contents_filename) { } } - // Couldn't find the coreapi package description. - nout << "No coreapi package defined in contents file for " - << _this_platform << "\n"; - return false; + 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; } //////////////////////////////////////////////////////////////////// @@ -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, diff --git a/direct/src/plugin_standalone/panda3d.h b/direct/src/plugin_standalone/panda3d.h index d92dc8db4d..92fe0ba75a 100755 --- a/direct/src/plugin_standalone/panda3d.h +++ b/direct/src/plugin_standalone/panda3d.h @@ -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(); diff --git a/direct/src/plugin_standalone/panda3dBase.cxx b/direct/src/plugin_standalone/panda3dBase.cxx index de6321ce6a..ee98785fa3 100644 --- a/direct/src/plugin_standalone/panda3dBase.cxx +++ b/direct/src/plugin_standalone/panda3dBase.cxx @@ -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. diff --git a/direct/src/plugin_standalone/panda3dBase.h b/direct/src/plugin_standalone/panda3dBase.h index 68e2a20cb4..dead59ee04 100755 --- a/direct/src/plugin_standalone/panda3dBase.h +++ b/direct/src/plugin_standalone/panda3dBase.h @@ -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;