#include <stdlib.h> // atoi() atof() abs() system()
#include <signal.h> // signal()
+#include <string.h>
#include <iostream>
#include <fstream>
#include "terrasync.hxx"
#include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/strutils.hxx>
#include <simgear/threads/SGQueue.hxx>
-#include <simgear/scene/tgdb/TileCache.hxx>
#include <simgear/misc/sg_dir.hxx>
-#include <OpenThreads/Thread>
#ifdef HAVE_SVN_CLIENT_H
# ifdef HAVE_LIBSVN_CLIENT_1
#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<string,time_t> 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 ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// SGTerraSync::SvnThread /////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
-class SGTerraSync::SvnThread : public OpenThreads::Thread
+class SGTerraSync::SvnThread : public SGThread
{
public:
SvnThread();
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;}
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
SGBlockingDeque <WaitingTile> _freshTiles;
bool _use_svn;
string _svn_server;
+ string _svn_command;
string _rsync_server;
string _local_dir;
};
_updated_tile_count(0),
_success_count(0),
_consecutive_errors(0),
+ _allowed_errors(6),
#ifdef HAVE_SVN_CLIENT_H
_use_built_in(true),
#endif
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())
{
_stalled = true;
return false;
}
+
#ifdef HAVE_SVN_CLIENT_H
_use_svn |= _use_built_in;
#endif
_stalled = true;
return false;
}
-
if ((!_use_svn)&&(_rsync_server==""))
{
SG_LOG(SG_TERRAIN,SG_ALERT,
_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
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,
#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
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);
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,
_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
{
{
// 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;
// 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);
last_lat(NOWHERE),
last_lon(NOWHERE),
_terraRoot(root->getNode("/sim/terrasync",true)),
- _tile_cache(NULL)
+ _refreshCb(NULL),
+ _userCbData(NULL)
{
_svnThread = new SvnThread();
}
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()
_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()
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())
void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
{
// find tiles to be refreshed
- if (_tile_cache)
+ if (_refreshCb)
{
path.append(relativeDir);
if (path.exists())
{
// reload scenery tile
long index = atoi(tileList[i].file().c_str());
- _tile_cache->refresh_tile(index);
+ _refreshCb(_userCbData, index);
}
}
}
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 );
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;
}
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;
}