diff --git a/direct/src/plugin/p3dFileDownload.cxx b/direct/src/plugin/p3dFileDownload.cxx index 3444a46f37..b4390a11a0 100755 --- a/direct/src/plugin/p3dFileDownload.cxx +++ b/direct/src/plugin/p3dFileDownload.cxx @@ -59,11 +59,14 @@ set_filename(const string &filename) { bool P3DFileDownload:: open_file() { if (!mkfile_complete(_filename, nout)) { + nout << "Failed to create " << _filename << "\n"; return false; } - - _file.open(_filename.c_str(), ios::out | ios::ate | ios::binary); + + _file.clear(); + _file.open(_filename.c_str(), ios::out | ios::trunc | ios::binary); if (!_file) { + nout << "Failed to open " << _filename << " in write mode\n"; return false; } diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 8eb4aa27fe..5d09d668bf 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -91,13 +91,11 @@ P3DPackage:: // Cancel any pending download. if (_active_download != NULL) { _active_download->cancel(); - delete _active_download; - _active_download = NULL; + set_active_download(NULL); } if (_saved_download != NULL) { _saved_download->cancel(); - delete _saved_download; - _saved_download = NULL; + set_saved_download(NULL); } if (_temp_contents_file != NULL) { @@ -196,8 +194,7 @@ remove_instance(P3DInstance *inst) { // move to the next instance. if (_active_download != NULL) { _active_download->cancel(); - delete _active_download; - _active_download = NULL; + set_active_download(NULL); } } @@ -355,9 +352,8 @@ redownload_contents_file(P3DPackage::Download *download) { host_got_contents_file(); return; } - - _saved_download = download; - _saved_download->ref(); + + set_saved_download(download); // Download contents.xml to a temporary filename first. if (_temp_contents_file != NULL) { @@ -410,8 +406,7 @@ contents_file_redownload_finished(bool success) { if (contents_changed) { // OK, the contents.xml has changed; this means we have to restart // the whole download process from the beginning. - unref_delete(_saved_download); - _saved_download = NULL; + set_saved_download(NULL); host_got_contents_file(); } else { @@ -1045,13 +1040,51 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase, download->set_url(url); download->set_filename(pathname); - _active_download = download; + set_active_download(download); assert(!_instances.empty()); _instances[0]->start_download(download); return download; } +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::set_active_download +// Access: Private +// Description: Changes _active_download to point to the indicated +// object, respecting reference counts. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +set_active_download(Download *download) { + if (_active_download != download) { + if (_active_download != NULL) { + unref_delete(_active_download); + } + _active_download = download; + if (_active_download != NULL) { + _active_download->ref(); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::set_saved_download +// Access: Private +// Description: Changes _saved_download to point to the indicated +// object, respecting reference counts. +//////////////////////////////////////////////////////////////////// +void P3DPackage:: +set_saved_download(Download *download) { + if (_saved_download != download) { + if (_saved_download != NULL) { + unref_delete(_saved_download); + } + _saved_download = download; + if (_saved_download != NULL) { + _saved_download->ref(); + } + } +} + //////////////////////////////////////////////////////////////////// // Function: P3DPackage::is_extractable // Access: Private @@ -1129,7 +1162,15 @@ void P3DPackage::Download:: download_finished(bool success) { P3DFileDownload::download_finished(success); assert(_package->_active_download == this); - _package->_active_download = NULL; + if (get_ref_count() == 1) { + // No one cares anymore. + nout << "No one cares about " << get_url() << "\n"; + _package->set_active_download(NULL); + return; + } + + _package->set_active_download(NULL); + assert(get_ref_count() > 0); if (success && !_file_spec.get_filename().empty()) { // We think we downloaded it correctly. Check the hash to be @@ -1175,7 +1216,7 @@ resume_download_finished(bool success) { clear(); set_url(url); set_filename(get_filename()); - _package->_active_download = this; + _package->set_active_download(this); assert(!_package->_instances.empty()); _package->_instances[0]->start_download(this); diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index 359d5bf926..9ee16e1698 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -206,6 +206,8 @@ private: void report_done(bool success); Download *start_download(DownloadType dtype, const string &urlbase, const string &pathname, const FileSpec &file_spec); + void set_active_download(Download *download); + void set_saved_download(Download *download); bool is_extractable(FileSpec &file, const string &filename) const; diff --git a/direct/src/plugin/p3dReferenceCount.I b/direct/src/plugin/p3dReferenceCount.I index 51d6fdbe5d..f0ec37ef41 100644 --- a/direct/src/plugin/p3dReferenceCount.I +++ b/direct/src/plugin/p3dReferenceCount.I @@ -45,7 +45,7 @@ ref() const { //////////////////////////////////////////////////////////////////// // Function: P3DReferenceCount::unref -// Access: Published, Virtual +// Access: Public // Description: Explicitly decrements the reference count. Usually, // you should call unref_delete() instead. // @@ -57,6 +57,16 @@ unref() const { return --(((P3DReferenceCount *)this)->_ref_count) != 0; } +//////////////////////////////////////////////////////////////////// +// Function: P3DReferenceCount::get_ref_count +// Access: Public +// Description: Returns the current reference count. +//////////////////////////////////////////////////////////////////// +inline int P3DReferenceCount:: +get_ref_count() const { + return _ref_count; +} + //////////////////////////////////////////////////////////////////// // Function: unref_delete // Description: This global helper function will unref the given diff --git a/direct/src/plugin/p3dReferenceCount.h b/direct/src/plugin/p3dReferenceCount.h index 3c1d62c1a7..9cc5965159 100644 --- a/direct/src/plugin/p3dReferenceCount.h +++ b/direct/src/plugin/p3dReferenceCount.h @@ -31,6 +31,7 @@ public: inline void ref() const; inline bool unref() const; + inline int get_ref_count() const; private: int _ref_count; diff --git a/direct/src/plugin_activex/P3DActiveX.cpp b/direct/src/plugin_activex/P3DActiveX.cpp index a30bc1ab90..dcfe6ce8b3 100644 --- a/direct/src/plugin_activex/P3DActiveX.cpp +++ b/direct/src/plugin_activex/P3DActiveX.cpp @@ -167,6 +167,10 @@ BOOL CP3DActiveXApp::InitInstance() if (bInit) { // TODO: Add your own module initialization code here. + + // Seed the lame random number generator in rand(); we use it to + // select a mirror for downloading. + srand((unsigned int)time(NULL)); } return bInit; diff --git a/direct/src/plugin_activex/PPInstance.cpp b/direct/src/plugin_activex/PPInstance.cpp index 75f694318a..4345cd2266 100644 --- a/direct/src/plugin_activex/PPInstance.cpp +++ b/direct/src/plugin_activex/PPInstance.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,10 @@ #include "find_root_dir.h" #include "mkdir_complete.h" +// We can include this header file to get the DTOOL_PLATFORM +// definition, even though we don't link with dtool. +#include "dtool_platform.h" + #define P3D_CONTENTS_FILENAME "contents.xml" #define P3D_DEFAULT_PLUGIN_FILENAME "p3d_plugin.dll" @@ -101,11 +106,11 @@ PPInstance::~PPInstance( ) int PPInstance::DownloadFile( const std::string& from, const std::string& to ) { int error( 0 ); - PPDownloadRequest p3dContentsDownloadRequest( *this, to ); - PPDownloadCallback dcForContents( p3dContentsDownloadRequest ); + PPDownloadRequest p3dFileDownloadRequest( *this, to ); + PPDownloadCallback dcForFile( p3dFileDownloadRequest ); nout << "Downloading " << from << " into " << to << "\n"; - HRESULT hr = ::URLOpenStream( m_parentCtrl.GetControllingUnknown(), from.c_str(), 0, &dcForContents ); + HRESULT hr = ::URLOpenStream( m_parentCtrl.GetControllingUnknown(), from.c_str(), 0, &dcForFile ); if ( FAILED( hr ) ) { error = 1; @@ -140,35 +145,144 @@ int PPInstance::CopyFile( const std::string& from, const std::string& to ) return 0; } -int PPInstance::ReadContents( const std::string& contentsFilename, FileSpec& p3dDllFile ) -{ - int error(1); +//////////////////////////////////////////////////////////////////// +// Function: PPInstance::read_contents_file +// Access: Private +// Description: Reads the contents.xml file and starts the core API +// DLL downloading, if necessary. +//////////////////////////////////////////////////////////////////// +bool PPInstance:: +read_contents_file(const string &contents_filename) { + TiXmlDocument doc(contents_filename.c_str()); + if (!doc.LoadFile()) { + return false; + } - TiXmlDocument doc( contentsFilename.c_str( ) ); - if ( doc.LoadFile( ) ) - { - TiXmlElement *xcontents = doc.FirstChildElement( "contents" ); - if ( xcontents != NULL ) - { - TiXmlElement *xpackage = xcontents->FirstChildElement( "package" ); - while ( xpackage != NULL ) - { - const char *name = xpackage->Attribute( "name" ); - if ( name != NULL && strcmp( name, "coreapi" ) == 0 ) - { - const char *platform = xpackage->Attribute( "platform" ); - if ( platform != NULL && !strcmp(platform, "win32") ) - { - p3dDllFile.load_xml(xpackage); - error = 0; - break; - } - } - xpackage = xpackage->NextSiblingElement( "package" ); - } + TiXmlElement *xcontents = doc.FirstChildElement("contents"); + if (xcontents != NULL) { + // Look for the entry; it might point us at a different + // download URL, and it might mention some mirrors. + string host_url = PANDA_PACKAGE_HOST_URL; + TiXmlElement *xhost = xcontents->FirstChildElement("host"); + if (xhost != NULL) { + const char *url = xhost->Attribute("url"); + if (url != NULL && host_url == string(url)) { + // We're the primary host. This is the normal case. + read_xhost(xhost); + + } else { + // We're not the primary host; perhaps we're an alternate host. + TiXmlElement *xalthost = xhost->FirstChildElement("alt_host"); + while (xalthost != NULL) { + const char *url = xalthost->Attribute("url"); + if (url != NULL && host_url == string(url)) { + // Yep, we're this alternate host. + read_xhost(xhost); + break; + } + xalthost = xalthost->NextSiblingElement("alt_host"); } + } } - return error; + + // Now look for the core API package. + TiXmlElement *xpackage = xcontents->FirstChildElement("package"); + while (xpackage != NULL) { + const char *name = xpackage->Attribute("name"); + if (name != NULL && strcmp(name, "coreapi") == 0) { + const char *platform = xpackage->Attribute("platform"); + if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) { + _core_api_dll.load_xml(xpackage); + return true; + } + } + + 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; +} + +//////////////////////////////////////////////////////////////////// +// Function: PPInstance::read_xhost +// Access: Private +// Description: Reads the host data from the (or ) +// entry in the contents.xml file. +//////////////////////////////////////////////////////////////////// +void PPInstance:: +read_xhost(TiXmlElement *xhost) { + // Get the "download" URL, which is the source from which we + // download everything other than the contents.xml file. + const char *download_url = xhost->Attribute("download_url"); + if (download_url != NULL) { + _download_url_prefix = download_url; + } else { + _download_url_prefix = PANDA_PACKAGE_HOST_URL; + } + if (!_download_url_prefix.empty()) { + if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') { + _download_url_prefix += "/"; + } + } + + TiXmlElement *xmirror = xhost->FirstChildElement("mirror"); + while (xmirror != NULL) { + const char *url = xmirror->Attribute("url"); + if (url != NULL) { + add_mirror(url); + } + xmirror = xmirror->NextSiblingElement("mirror"); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PPInstance::add_mirror +// Access: Private +// Description: Adds a new URL to serve as a mirror for this host. +// The mirrors will be consulted first, before +// consulting the host directly. +//////////////////////////////////////////////////////////////////// +void PPInstance:: +add_mirror(std::string mirror_url) { + // Ensure the URL ends in a slash. + if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') { + mirror_url += '/'; + } + + // Add it to the _mirrors list, but only if it's not already + // there. + if (std::find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) { + _mirrors.push_back(mirror_url); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PPInstance::choose_random_mirrors +// Access: Public +// Description: Selects num_mirrors elements, chosen at random, from +// the _mirrors list. Adds the selected mirrors to +// result. If there are fewer than num_mirrors elements +// in the list, adds only as many mirrors as we can get. +//////////////////////////////////////////////////////////////////// +void PPInstance:: +choose_random_mirrors(std::vector &result, int num_mirrors) { + std::vector selected; + + size_t num_to_select = min(_mirrors.size(), (size_t)num_mirrors); + while (num_to_select > 0) { + size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size()); + while (std::find(selected.begin(), selected.end(), i) != selected.end()) { + // Already found this i, find a new one. + i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size()); + } + selected.push_back(i); + result.push_back(_mirrors[i]); + --num_to_select; + } } int PPInstance::DownloadP3DComponents( std::string& p3dDllFilename ) @@ -204,17 +318,18 @@ int PPInstance::DownloadP3DComponents( std::string& p3dDllFilename ) strm << hostUrl << P3D_CONTENTS_FILENAME << "?" << time(NULL); std::string remoteContentsUrl( strm.str() ); - FileSpec p3dDllFile; error = DownloadFile( remoteContentsUrl, localContentsFileName ); if ( !error ) { - error = ReadContents( localContentsFileName, p3dDllFile ); + if ( !read_contents_file( localContentsFileName ) ) + error = 1; } if ( error ) { // If we couldn't download or read the contents.xml file, check // to see if there's a good one on disk already, as a fallback. - error = ReadContents( finalContentsFileName, p3dDllFile ); + if ( !read_contents_file( finalContentsFileName ) ) + error = 1; } else { // If we have successfully read the downloaded version, @@ -226,34 +341,63 @@ int PPInstance::DownloadP3DComponents( std::string& p3dDllFilename ) // We don't need the temporary file any more. ::DeleteFile( localContentsFileName.c_str() ); - if ( !error ) - { - // OK, at this point we have successfully read contents.xml, - // and we have a good file spec in p3dDllFile. - if ( p3dDllFile.quick_verify( m_rootDir ) ) - { - // The DLL is already on-disk, and is good. - p3dDllFilename = p3dDllFile.get_pathname( m_rootDir ); + if (!error) { + // OK, at this point we have successfully read contents.xml, + // and we have a good file spec in _core_api_dll. + if (_core_api_dll.quick_verify(m_rootDir)) { + // The DLL is already on-disk, and is good. + p3dDllFilename = _core_api_dll.get_pathname(m_rootDir); + } else { + // The DLL is not already on-disk, or it's stale. Go get it. + std::string p3dLocalModuleFileName(_core_api_dll.get_pathname(m_rootDir)); + mkfile_complete(p3dLocalModuleFileName, nout); + + // Try one of the mirrors first. + std::vector mirrors; + choose_random_mirrors(mirrors, 2); + + error = 1; + for (std::vector::iterator si = mirrors.begin(); + si != mirrors.end() && error; + ++si) { + std::string url = (*si) + _core_api_dll.get_filename(); + error = DownloadFile(url, p3dLocalModuleFileName); + if (!error && !_core_api_dll.full_verify(m_rootDir)) { + // If it's not right after downloading, it's an error. + error = 1; + } } - else - { - // The DLL is not already on-disk, or it's stale. - std::string p3dLocalModuleFileName( p3dDllFile.get_pathname( m_rootDir ) ); - mkfile_complete( p3dLocalModuleFileName, nout ); - std::string p3dRemoteModuleUrl( hostUrl ); - p3dRemoteModuleUrl += p3dDllFile.get_filename(); - error = DownloadFile( p3dRemoteModuleUrl, p3dLocalModuleFileName ); - if ( !error ) - { - error = 1; - if ( p3dDllFile.full_verify( m_rootDir ) ) - { - // Downloaded successfully. - p3dDllFilename = p3dDllFile.get_pathname( m_rootDir ); - error = 0; - } - } + + // If that failed, go get it from the authoritative host. + if (error) { + std::string url = _download_url_prefix + _core_api_dll.get_filename(); + error = DownloadFile(url, p3dLocalModuleFileName); + if (!error && !_core_api_dll.full_verify(m_rootDir)) { + error = 1; + } } + + // If *that* failed, go get it again from the same URL, this + // time with a query prefix to bust through any caches. + if (error) { + std::ostringstream strm; + strm << _download_url_prefix << _core_api_dll.get_filename(); + strm << "?" << time(NULL); + + std::string url = strm.str(); + error = DownloadFile(url, p3dLocalModuleFileName); + if (!error && !_core_api_dll.full_verify(m_rootDir)) { + nout << "After download, " << _core_api_dll.get_filename() + << " is no good.\n"; + error = 1; + } + } + + if (!error) { + // Downloaded successfully. + p3dDllFilename = _core_api_dll.get_pathname(m_rootDir); + } + } } return error; @@ -438,22 +582,18 @@ void PPInstance::HandleRequestGetUrl( void* data ) PPDownloadRequest p3dObjectDownloadRequest( parent->m_instance, request ); PPDownloadCallback bsc( p3dObjectDownloadRequest ); HRESULT hr = ::URLOpenStream( parent->GetControllingUnknown(), url.c_str(), 0, &bsc ); + + P3D_result_code result_code = P3D_RC_done; if ( FAILED( hr ) ) { nout << "Error handling P3D_RT_get_url request" << " :" << hr << "\n"; - return; + result_code = P3D_RC_generic_error; } - //inet_InternetSession inet(parent->m_pythonEmbed.m_threadData.m_parent); - //std::string outdata; - //if ( !inet.getURLMemory( url, outdata, request ) ) - //{ - // handled = false; - //} P3D_instance_feed_url_stream( request->_instance, request->_request._get_url._unique_id, - P3D_RC_done, + result_code, 0, 0, (const void*)NULL, diff --git a/direct/src/plugin_activex/PPInstance.h b/direct/src/plugin_activex/PPInstance.h index e3e9ded3c3..37e6ffa819 100644 --- a/direct/src/plugin_activex/PPInstance.h +++ b/direct/src/plugin_activex/PPInstance.h @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include "afxmt.h" @@ -69,7 +70,11 @@ protected: int DownloadFile( const std::string& from, const std::string& to ); int CopyFile( const std::string& from, const std::string& to ); - int ReadContents( const std::string& contentsFilename, FileSpec& p3dDllFile ); + + bool read_contents_file(const std::string &contents_filename); + void read_xhost(TiXmlElement *xhost); + void add_mirror(std::string mirror_url); + void choose_random_mirrors(std::vector &result, int num_mirrors); void HandleRequest( P3D_request *request ); static void HandleRequestGetUrl( void *data ); @@ -82,5 +87,10 @@ protected: bool m_isInit; bool m_pluginLoaded; + std::string _download_url_prefix; + typedef std::vector Mirrors; + Mirrors _mirrors; + FileSpec _core_api_dll; + std::string m_rootDir; };