X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fscene%2Ftsync%2Fterrasync.cxx;h=e4ae8ff8ad0a7ce35f7ee6a395e9f72e12a67abb;hb=ae0b8eb3b3943690d0788dde316b1c152390f1fd;hp=939ff6fb6640c27ad26150406aa59ce2fa4c5c11;hpb=c782a32076016f2c3c01b4fd437b024dc77806e9;p=simgear.git diff --git a/simgear/scene/tsync/terrasync.cxx b/simgear/scene/tsync/terrasync.cxx index 939ff6fb..e4ae8ff8 100644 --- a/simgear/scene/tsync/terrasync.cxx +++ b/simgear/scene/tsync/terrasync.cxx @@ -45,6 +45,7 @@ #include // atoi() atof() abs() system() #include // signal() +#include #include #include @@ -56,10 +57,9 @@ #include "terrasync.hxx" #include #include +#include #include -#include #include -#include #ifdef HAVE_SVN_CLIENT_H # ifdef HAVE_LIBSVN_CLIENT_1 @@ -85,15 +85,45 @@ #endif using namespace simgear; +using namespace std; const char* rsync_cmd = "rsync --verbose --archive --delete --perms --owner --group"; -const char* svn_cmd = - "svn checkout -q"; +const char* svn_options = + "checkout -q"; + +namespace UpdateInterval +{ + // interval in seconds to allow an update to repeat after a successful update (=daily) + static const double SuccessfulAttempt = 24*60*60; + // interval in seconds to allow another update after a failed attempt (10 minutes) + static const double FailedAttempt = 10*60; +}; typedef map CompletedTiles; +/////////////////////////////////////////////////////////////////////////////// +// helper functions /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +string stripPath(string path) +{ + // svn doesn't like trailing white-spaces or path separators - strip them! + path = simgear::strutils::strip(path); + int slen = path.length(); + while ((slen>0)&& + ((path[slen-1]=='/')||(path[slen-1]=='\\'))) + { + slen--; + } + return path.substr(0,slen); +} + +bool hasWhitespace(string path) +{ + return path.find(' ')!=string::npos; +} + /////////////////////////////////////////////////////////////////////////////// // WaitingTile //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -109,7 +139,7 @@ public: /////////////////////////////////////////////////////////////////////////////// // SGTerraSync::SvnThread ///////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -class SGTerraSync::SvnThread : public OpenThreads::Thread +class SGTerraSync::SvnThread : public SGThread { public: SvnThread(); @@ -124,11 +154,13 @@ public: bool hasNewTiles() { return !_freshTiles.empty();} WaitingTile getNewTile() { return _freshTiles.pop_front();} - void setSvnServer(string server) { _svn_server = server;} - void setRsyncServer(string server) { _rsync_server = server;} - void setLocalDir(string dir) { _local_dir = dir;} - string getLocalDir() { return _local_dir;} - void setUseSvn(bool use_svn) { _use_svn = use_svn;} + void setSvnServer(string server) { _svn_server = stripPath(server);} + void setExtSvnUtility(string svn_util) { _svn_command = simgear::strutils::strip(svn_util);} + void setRsyncServer(string server) { _rsync_server = simgear::strutils::strip(server);} + void setLocalDir(string dir) { _local_dir = stripPath(dir);} + string getLocalDir() { return _local_dir;} + void setUseSvn(bool use_svn) { _use_svn = use_svn;} + void setAllowedErrorCount(int errors) {_allowed_errors = errors;} #ifdef HAVE_SVN_CLIENT_H void setUseBuiltin(bool built_in) { _use_built_in = built_in;} @@ -142,10 +174,11 @@ public: volatile int _updated_tile_count; volatile int _success_count; volatile int _consecutive_errors; + volatile int _allowed_errors; private: virtual void run(); - bool syncTree(const char* dir); + bool syncTree(const char* dir, bool& isNewDirectory); bool syncTreeExternal(const char* dir); #ifdef HAVE_SVN_CLIENT_H @@ -168,6 +201,7 @@ private: SGBlockingDeque _freshTiles; bool _use_svn; string _svn_server; + string _svn_command; string _rsync_server; string _local_dir; }; @@ -188,6 +222,7 @@ SGTerraSync::SvnThread::SvnThread() : _updated_tile_count(0), _success_count(0), _consecutive_errors(0), + _allowed_errors(6), #ifdef HAVE_SVN_CLIENT_H _use_built_in(true), #endif @@ -229,13 +264,23 @@ bool SGTerraSync::SvnThread::start() if (_local_dir=="") { SG_LOG(SG_TERRAIN,SG_ALERT, - "Cannot start scenery download. No local cache directory defined."); + "Cannot start scenery download. Local cache directory is undefined."); _fail_count++; _stalled = true; return false; } - + SGPath path(_local_dir); + if (!path.exists()) + { + SG_LOG(SG_TERRAIN,SG_ALERT, + "Cannot start scenery download. Directory '" << _local_dir << + "' does not exist. Set correct directory path or create directory folder."); + _fail_count++; + _stalled = true; + return false; + } + path.append("version"); if (path.exists()) { @@ -246,6 +291,7 @@ bool SGTerraSync::SvnThread::start() _stalled = true; return false; } + #ifdef HAVE_SVN_CLIENT_H _use_svn |= _use_built_in; #endif @@ -258,7 +304,6 @@ bool SGTerraSync::SvnThread::start() _stalled = true; return false; } - if ((!_use_svn)&&(_rsync_server=="")) { SG_LOG(SG_TERRAIN,SG_ALERT, @@ -276,28 +321,51 @@ bool SGTerraSync::SvnThread::start() _stalled = false; _running = true; + string status; +#ifdef HAVE_SVN_CLIENT_H + if (_use_svn && _use_built_in) + status = "Using built-in SVN support. "; + else +#endif + if (_use_svn) + { + status = "Using external SVN utility '"; + status += _svn_command; + status += "'. "; + } + else + { + status = "Using RSYNC. "; + } + // not really an alert - but we want to (always) see this message, so user is // aware we're downloading scenery (and using bandwidth). SG_LOG(SG_TERRAIN,SG_ALERT, - "Starting automatic scenery download/synchronization. Directory: '" << _local_dir << "'"); + "Starting automatic scenery download/synchronization. " + << status + << "Directory: '" << _local_dir << "'."); - OpenThreads::Thread::start(); + SGThread::start(); return true; } // sync one directory tree -bool SGTerraSync::SvnThread::syncTree(const char* dir) +bool SGTerraSync::SvnThread::syncTree(const char* dir, bool& isNewDirectory) { int rc; SGPath path( _local_dir ); path.append( dir ); - rc = path.create_dir( 0755 ); - if (rc) + isNewDirectory = !path.exists(); + if (isNewDirectory) { - SG_LOG(SG_TERRAIN,SG_ALERT, - "Cannot create directory '" << dir << "', return code = " << rc ); - return false; + rc = path.create_dir( 0755 ); + if (rc) + { + SG_LOG(SG_TERRAIN,SG_ALERT, + "Cannot create directory '" << dir << "', return code = " << rc ); + return false; + } } #ifdef HAVE_SVN_CLIENT_H @@ -322,21 +390,19 @@ bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir) return false; } - char command[512]; - char dest_base_dir[512]; - snprintf( command, 512, - "%s/%s", _svn_server.c_str(), dir); - snprintf( dest_base_dir, 512, - "%s/%s", _local_dir.c_str(), dir); - svn_error_t *err = NULL; + ostringstream command; + command << _svn_server << "/" << dir; + + ostringstream dest_base_dir; + dest_base_dir << _local_dir << "/" << dir; apr_pool_t *subpool = svn_pool_create(_svn_pool); - err=0; + svn_error_t *err = NULL; #if (SVN_VER_MINOR >= 5) err = svn_client_checkout3(NULL, - command, - dest_base_dir, + command.str().c_str(), + dest_base_dir.str().c_str(), _svn_rev_peg, _svn_rev, svn_depth_infinity, @@ -347,8 +413,8 @@ bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir) #else // version 1.4 API err = svn_client_checkout2(NULL, - command, - dest_base_dir, + command.str().c_str(), + dest_base_dir.str().c_str(), _svn_rev_peg, _svn_rev, 1, // recurse=true - same as svn_depth_infinity for checkout3 above @@ -361,19 +427,26 @@ bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir) if (err) { // Report errors from the checkout attempt - SG_LOG(SG_TERRAIN,SG_ALERT, - "Failed to synchronize directory '" << dir << "', " << - err->message); - svn_error_clear(err); - // try to clean up - err = svn_client_cleanup(dest_base_dir, - _svn_ctx,subpool); - if (!err) + if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL) + { + // ignore errors when remote path doesn't exist (no scenery data for ocean areas) + } + else { SG_LOG(SG_TERRAIN,SG_ALERT, - "SVN repository cleanup successful for '" << dir << "'."); + "Failed to synchronize directory '" << dir << "', " << + err->message << " (code " << err->apr_err << ")."); + svn_error_clear(err); + // try to clean up + err = svn_client_cleanup(dest_base_dir.str().c_str(), + _svn_ctx,subpool); + if (!err) + { + SG_LOG(SG_TERRAIN,SG_ALERT, + "SVN repository cleanup successful for '" << dir << "'."); + } + ReturnValue = false; } - ReturnValue = false; } else { SG_LOG(SG_TERRAIN,SG_DEBUG, "Done with scenery directory " << dir); @@ -385,22 +458,48 @@ bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir) bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir) { - int rc; - char command[512]; + ostringstream buf; + SGPath localPath( _local_dir ); + localPath.append( dir ); + if (_use_svn) { - snprintf( command, 512, - "%s %s/%s %s/%s", svn_cmd, - _svn_server.c_str(), dir, - _local_dir.c_str(), dir ); + buf << "\"" << _svn_command << "\" " + << svn_options << " " + << "\"" << _svn_server << "/" << dir << "\" " + << "\"" << localPath.str_native() << "\""; } else { - snprintf( command, 512, - "%s %s/%s/ %s/%s/", rsync_cmd, - _rsync_server.c_str(), dir, - _local_dir.c_str(), dir ); + buf << rsync_cmd << " " + << "\"" << _rsync_server << "/" << dir << "/\" " + << "\"" << localPath.str_native() << "/\""; } + + string command; +#ifdef SG_WINDOWS + // windows command line parsing is just lovely... + // to allow white spaces, the system call needs this: + // ""C:\Program Files\something.exe" somearg "some other arg"" + // Note: whitespace strings quoted by a pair of "" _and_ the + // entire string needs to be wrapped by "" too. + // The svn url needs forward slashes (/) as a path separator while + // the local path needs windows-native backslash as a path separator. + command = "\"" + buf.str() + "\""; +#else + command = buf.str(); +#endif SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'"); - rc = system( command ); + +#ifdef SG_WINDOWS + // tbd: does Windows support "popen"? + int rc = system( command.c_str() ); +#else + FILE* pipe = popen( command.c_str(), "r"); + int rc=-1; + // wait for external process to finish + if (pipe) + rc = pclose(pipe); +#endif + if (rc) { SG_LOG(SG_TERRAIN,SG_ALERT, @@ -424,13 +523,16 @@ void SGTerraSync::SvnThread::run() _completedTiles.find( next._dir ); time_t now = time(0); if ((ii == _completedTiles.end())|| - ((ii->second + 60*60*24) < now )) + (ii->second < now )) { + bool isNewDirectory = false; + _busy = true; - if (!syncTree(next._dir.c_str())) + if (!syncTree(next._dir.c_str(),isNewDirectory)) { _consecutive_errors++; _fail_count++; + _completedTiles[ next._dir ] = now + UpdateInterval::FailedAttempt; } else { @@ -442,15 +544,21 @@ void SGTerraSync::SvnThread::run() { // updated a tile _updated_tile_count++; - _freshTiles.push_back(next); - _is_dirty = true; + if (isNewDirectory) + { + // for now only report new directories to refresh display + // (i.e. only when ocean needs to be replaced with actual data) + _freshTiles.push_back(next); + _is_dirty = true; + } } + _completedTiles[ next._dir ] = now + UpdateInterval::SuccessfulAttempt; } _busy = false; - _completedTiles[ next._dir ] = now; } - if (_consecutive_errors >= 5) + if ((_allowed_errors >= 0)&& + (_consecutive_errors >= _allowed_errors)) { _stalled = true; _stop = true; @@ -476,16 +584,18 @@ int SGTerraSync::SvnThread::svnClientSetup(void) // not supported under msvc 7.1 ( code inside svn_cmdline_init ) if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS) return EXIT_FAILURE; + + // revert locale setting + setlocale(LC_ALL,"C"); #else + /* svn_cmdline_init configures the locale. Setup environment to ensure the + * default "C" locale remains active, since fgfs isn't locale aware - especially + * requires "." as decimal point in strings containing floating point varibales. */ + setenv("LC_ALL", "C", 1); + if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS) return EXIT_FAILURE; #endif - /* Oh no! svn_cmdline_init configures the locale - affecting numeric output - * formats (i.e. sprintf("%f", ...)). fgfs relies on "C" locale in many places - * (including assumptions on required sprintf buffer sizes). Things go horribly - * wrong when the locale is changed to anything else but "C". Might be enough to - * revert LC_NUMERIC locale - but we'll do a complete revert for now...*/ - setlocale(LC_ALL,"C"); apr_pool_t *pool; apr_pool_create(&pool, NULL); @@ -562,7 +672,8 @@ SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) : last_lat(NOWHERE), last_lon(NOWHERE), _terraRoot(root->getNode("/sim/terrasync",true)), - _tile_cache(NULL) + _refreshCb(NULL), + _userCbData(NULL) { _svnThread = new SvnThread(); } @@ -576,40 +687,51 @@ SGTerraSync::~SGTerraSync() void SGTerraSync::init() { - _refresh_display = _terraRoot->getNode("refresh-display",true); - _terraRoot->getNode("built-in-svn-available",true)->setBoolValue(svn_built_in_available); + _refreshDisplay = _terraRoot->getNode("refresh-display",true); + _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available); reinit(); } void SGTerraSync::reinit() { // do not reinit when enabled and we're already up and running - if ((_terraRoot->getNode("enabled",true)->getBoolValue())&& + if ((_terraRoot->getBoolValue("enabled",false))&& (_svnThread->_active && _svnThread->_running)) return; _svnThread->stop(); - if (_terraRoot->getNode("enabled",true)->getBoolValue()) + if (_terraRoot->getBoolValue("enabled",false)) { - _svnThread->setSvnServer(_terraRoot->getNode("svn-server",true)->getStringValue()); - _svnThread->setRsyncServer(_terraRoot->getNode("rsync-server",true)->getStringValue()); - _svnThread->setLocalDir(_terraRoot->getNode("scenery-dir",true)->getStringValue()); + _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server","")); + _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server","")); + _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir","")); + _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5)); #ifdef HAVE_SVN_CLIENT_H - _svnThread->setUseBuiltin(_terraRoot->getNode("use-built-in-svn",true)->getBoolValue()); + _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true)); #else - _terraRoot->getNode("use-built-in-svn",true)->setBoolValue(false); + _terraRoot->setBoolValue("use-built-in-svn",false); #endif - _svnThread->setUseSvn(_terraRoot->getNode("use-svn",true)->getBoolValue()); + _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true)); + _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn")); if (_svnThread->start()) + { syncAirportsModels(); + if (last_lat != NOWHERE && last_lon != NOWHERE) + { + // reschedule most recent position + int lat = last_lat; + int lon = last_lon; + last_lat = NOWHERE; + last_lon = NOWHERE; + schedulePosition(lat, lon); + } + } } - _stalled_node->setBoolValue(_svnThread->_stalled); - last_lat = NOWHERE; - last_lon = NOWHERE; + _stalledNode->setBoolValue(_svnThread->_stalled); } void SGTerraSync::bind() @@ -624,10 +746,12 @@ void SGTerraSync::bind() _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false); _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false); _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false); + _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups) - _stalled_node = _terraRoot->getNode("stalled", true); - _stalled_node->setBoolValue(_svnThread->_stalled); - _stalled_node->setAttribute(SGPropertyNode::PRESERVE,true); + _stalledNode = _terraRoot->getNode("stalled", true); + _stalledNode->setBoolValue(_svnThread->_stalled); + _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); } void SGTerraSync::unbind() @@ -654,10 +778,10 @@ void SGTerraSync::update(double) SG_LOG(SG_TERRAIN,SG_ALERT, "Automatic scenery download/synchronization has stopped."); } - _stalled_node->setBoolValue(_svnThread->_stalled); + _stalledNode->setBoolValue(_svnThread->_stalled); } - if (!_refresh_display->getBoolValue()) + if (!_refreshDisplay->getBoolValue()) return; while (_svnThread->hasNewTiles()) @@ -674,7 +798,7 @@ void SGTerraSync::update(double) void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir) { // find tiles to be refreshed - if (_tile_cache) + if (_refreshCb) { path.append(relativeDir); if (path.exists()) @@ -688,7 +812,7 @@ void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir) { // reload scenery tile long index = atoi(tileList[i].file().c_str()); - _tile_cache->refresh_tile(index); + _refreshCb(_userCbData, index); } } } @@ -696,27 +820,25 @@ void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir) bool SGTerraSync::isIdle() {return _svnThread->isIdle();} -void SGTerraSync::setTileCache(TileCache* tile_cache) +void SGTerraSync::setTileRefreshCb(SGTerraSyncCallback refreshCb, void* userCbData) { - _tile_cache = tile_cache; + _refreshCb = refreshCb; + _userCbData = userCbData; } void SGTerraSync::syncAirportsModels() { - char synced_other; - for ( synced_other = 'K'; synced_other <= 'Z'; synced_other++ ) - { - char dir[512]; - snprintf( dir, 512, "Airports/%c", synced_other ); - WaitingTile w(dir,false); - _svnThread->request( w ); - } - for ( synced_other = 'A'; synced_other <= 'J'; synced_other++ ) + static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z + // note "request" method uses LIFO order, i.e. processes most recent request first + for( unsigned i = 0; i < strlen(bounds)/2; i++ ) { - char dir[512]; - snprintf( dir, 512, "Airports/%c", synced_other ); - WaitingTile w(dir,false); - _svnThread->request( w ); + for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ ) + { + ostringstream dir; + dir << "Airports/" << synced_other; + WaitingTile w(dir.str(),false); + _svnThread->request( w ); + } } WaitingTile w("Models",false); _svnThread->request( w ); @@ -759,12 +881,11 @@ void SGTerraSync::syncArea( int lat, int lon ) bool refresh=true; for (const char** tree = &terrainobjects[0]; *tree; tree++) { - char dir[512]; - snprintf( dir, 512, "%s/%c%03d%c%02d/%c%03d%c%02d", - *tree, - EW, abs(baselon), NS, abs(baselat), - EW, abs(lon), NS, abs(lat) ); - WaitingTile w(dir,refresh); + ostringstream dir; + dir << *tree << "/" << setfill('0') + << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/" + << EW << setw(3) << abs(lon) << NS << setw(2) << abs(lat); + WaitingTile w(dir.str(),refresh); _svnThread->request( w ); refresh=false; } @@ -802,46 +923,49 @@ void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir ) bool SGTerraSync::schedulePosition(int lat, int lon) { + bool Ok = false; + // Ignore messages where the location does not change if ( lat != last_lat || lon != last_lon ) { - SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " << - lat << "," << lon); - int lat_dir, lon_dir, dist; - if ( last_lat == NOWHERE || last_lon == NOWHERE ) - { - lat_dir = lon_dir = 0; - } else + if (_svnThread->_running) { - dist = lat - last_lat; - if ( dist != 0 ) - { - lat_dir = dist / abs(dist); - } - else - { - lat_dir = 0; - } - dist = lon - last_lon; - if ( dist != 0 ) - { - lon_dir = dist / abs(dist); - } else + SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " << + lat << "," << lon); + int lat_dir=0; + int lon_dir=0; + if ( last_lat != NOWHERE && last_lon != NOWHERE ) { - lon_dir = 0; + int dist = lat - last_lat; + if ( dist != 0 ) + { + lat_dir = dist / abs(dist); + } + else + { + lat_dir = 0; + } + dist = lon - last_lon; + if ( dist != 0 ) + { + lon_dir = dist / abs(dist); + } else + { + lon_dir = 0; + } } - } - - SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " << - "lat = " << lat << ", lon = " << lon << - ", lat_dir = " << lat_dir << ", " << - "lon_dir = " << lon_dir); - syncAreas( lat, lon, lat_dir, lon_dir ); + SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " << + "lat = " << lat << ", lon = " << lon << + ", lat_dir = " << lat_dir << ", " << + "lon_dir = " << lon_dir); + syncAreas( lat, lon, lat_dir, lon_dir ); + Ok = true; + } last_lat = lat; last_lon = lon; - return true; } - return false; + + return Ok; }