-// terrasync.cxx -- "JIT" scenery fetcher
+// terrasync.cxx -- scenery fetcher
//
-// Written by Curtis Olson, started November 2002.
+// Started by Curtis Olson, November 2002.
//
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
// Copyright (C) 2008 Alexander R. Perry <alex.perry@ieee.org>
+// Copyright (C) 2011 Thorsten Brehm <brehmt@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// $Id$
#ifdef HAVE_CONFIG_H
-#include <config.h>
+# include <simgear_config.h>
#endif
+#include <simgear/compiler.h>
+
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include <stdlib.h> // atoi() atof() abs() system()
#include <signal.h> // signal()
-#include <simgear/compiler.h>
-
#include <iostream>
#include <fstream>
#include <string>
-#include <deque>
#include <map>
-#include <simgear/io/raw_socket.hxx>
+#include <simgear/compiler.h>
+
+#include "terrasync.hxx"
#include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.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
+# include <svn_version.h>
# include <svn_auth.h>
# include <svn_client.h>
# include <svn_cmdline.h>
# endif
#endif
-using namespace std;
+#ifdef HAVE_SVN_CLIENT_H
+ static const svn_version_checklist_t mysvn_checklist[] = {
+ { "svn_subr", svn_subr_version },
+ { "svn_client", svn_client_version },
+ { NULL, NULL }
+ };
+ static const bool svn_built_in_available = true;
+#else
+ static const bool svn_built_in_available = false;
+#endif
+
+using namespace simgear;
-const char* source_base = NULL;
-const char* svn_base =
- "http://terrascenery.googlecode.com/svn/trunk/data/Scenery";
-const char* rsync_base = "scenery.flightgear.org::Scenery";
-const char* dest_base = "terrasyncdir";
const char* rsync_cmd =
- "rsync --verbose --archive --delete --perms --owner --group";
+ "rsync --verbose --archive --delete --perms --owner --group";
+
+const char* svn_cmd =
+ "svn checkout -q";
+
+typedef map<string,time_t> CompletedTiles;
+
+///////////////////////////////////////////////////////////////////////////////
+// WaitingTile ////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+class WaitingTile
+{
+public:
+ WaitingTile(string dir,bool refresh) :
+ _dir(dir), _refreshScenery(refresh) {}
+ string _dir;
+ bool _refreshScenery;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SGTerraSync::SvnThread /////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+class SGTerraSync::SvnThread : public OpenThreads::Thread
+{
+public:
+ SvnThread();
+ virtual ~SvnThread( ) { stop(); }
+
+ void stop();
+ bool start();
+
+ bool isIdle() {return waitingTiles.empty();}
+ void request(const WaitingTile& dir) {waitingTiles.push_front(dir);}
+ bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
+ 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;}
#ifdef HAVE_SVN_CLIENT_H
-bool use_svn = true;
-#else
-bool use_svn = false;
-const char* svn_cmd = "svn checkout";
+ void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
#endif
-// display usage
-static void usage( const string& prog ) {
- cout <<
-"Usage: terrasync [options]\n"
-"Options: \n"
-" -d <dest> destination directory [required]\n"
-" -R transport using pipe to rsync\n"
-" -S transport using built-in svn\n"
-" -p <port> listen on UDP port [default: 5501]\n"
-" -s <source> source base [default: '']\n"
-" -pid <pidfile> write PID to file\n"
-" -v be more verbose\n"
-;
+ volatile bool _active;
+ volatile bool _running;
+ volatile bool _busy;
+ volatile bool _stalled;
+ volatile int _fail_count;
+ volatile int _updated_tile_count;
+ volatile int _success_count;
+ volatile int _consecutive_errors;
+
+private:
+ virtual void run();
+ bool syncTree(const char* dir);
+ bool syncTreeExternal(const char* dir);
#ifdef HAVE_SVN_CLIENT_H
- cout << " (defaults to the built in subversion)" << endl;
-#else
- cout << " (defaults to rsync, using external commands)" << endl;
+ static int svnClientSetup(void);
+ bool syncTreeInternal(const char* dir);
+
+ bool _use_built_in;
+
+ // Things we need for doing subversion checkout - often
+ static apr_pool_t *_svn_pool;
+ static svn_client_ctx_t *_svn_ctx;
+ static svn_opt_revision_t *_svn_rev;
+ static svn_opt_revision_t *_svn_rev_peg;
#endif
- cout << "\nExample:\n"
-"pid=$(cat $pidfile 2>/dev/null)\n"
-"if test -n \"$pid\" && kill -0 $pid ; then\n"
-" echo \"terrasync already running: $pid\"\n"
-"else\n"
-" nice /games/sport/fgs/utils/TerraSync/terrasync \\\n"
-" -v -pid $pidfile -S -p 5500 -d /games/orig/terrasync &\n"
-"fi" << endl;
+ volatile bool _is_dirty;
+ volatile bool _stop;
+ SGBlockingDeque <WaitingTile> waitingTiles;
+ CompletedTiles _completedTiles;
+ SGBlockingDeque <WaitingTile> _freshTiles;
+ bool _use_svn;
+ string _svn_server;
+ string _rsync_server;
+ string _local_dir;
+};
+
+#ifdef HAVE_SVN_CLIENT_H
+ apr_pool_t* SGTerraSync::SvnThread::_svn_pool = NULL;
+ svn_client_ctx_t* SGTerraSync::SvnThread::_svn_ctx = NULL;
+ svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev = NULL;
+ svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev_peg = NULL;
+#endif
+SGTerraSync::SvnThread::SvnThread() :
+ _active(false),
+ _running(false),
+ _busy(false),
+ _stalled(false),
+ _fail_count(0),
+ _updated_tile_count(0),
+ _success_count(0),
+ _consecutive_errors(0),
+#ifdef HAVE_SVN_CLIENT_H
+ _use_built_in(true),
+#endif
+ _is_dirty(false),
+ _stop(false),
+ _use_svn(true)
+{
+#ifdef HAVE_SVN_CLIENT_H
+ int errCode = SGTerraSync::SvnThread::svnClientSetup();
+ if (errCode != EXIT_SUCCESS)
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Failed to initialize built-in SVN client, error = " << errCode);
+ }
+#endif
}
-deque<string> waitingTiles;
-typedef map<string,time_t> CompletedTiles;
-CompletedTiles completedTiles;
-simgear::Socket theSocket;
+void SGTerraSync::SvnThread::stop()
+{
+ // drop any pending requests
+ waitingTiles.clear();
+
+ if (!_running)
+ return;
+
+ // set stop flag and wake up the thread with an empty request
+ _stop = true;
+ WaitingTile w("",false);
+ request(w);
+ join();
+ _running = false;
+}
+
+bool SGTerraSync::SvnThread::start()
+{
+ if (_running)
+ return false;
+ if (_local_dir=="")
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Cannot start scenery download. No local cache directory defined.");
+ _fail_count++;
+ _stalled = true;
+ return false;
+ }
+
+ SGPath path(_local_dir);
+ path.append("version");
+ if (path.exists())
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Cannot start scenery download. Directory '" << _local_dir <<
+ "' contains the base package. Use a separate directory.");
+ _fail_count++;
+ _stalled = true;
+ return false;
+ }
#ifdef HAVE_SVN_CLIENT_H
+ _use_svn |= _use_built_in;
+#endif
-// Things we need for doing subversion checkout - often
-apr_pool_t *mysvn_pool = NULL;
-svn_client_ctx_t *mysvn_ctx = NULL;
-svn_opt_revision_t *mysvn_rev = NULL;
-svn_opt_revision_t *mysvn_rev_peg = NULL;
+ if ((_use_svn)&&(_svn_server==""))
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Cannot start scenery download. Subversion scenery server is undefined.");
+ _fail_count++;
+ _stalled = true;
+ return false;
+ }
-static const svn_version_checklist_t mysvn_checklist[] = {
- { "svn_subr", svn_subr_version },
- { "svn_client", svn_client_version },
- { NULL, NULL }
-};
+ if ((!_use_svn)&&(_rsync_server==""))
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Cannot start scenery download. Rsync scenery server is undefined.");
+ _fail_count++;
+ _stalled = true;
+ return false;
+ }
+
+ _fail_count = 0;
+ _updated_tile_count = 0;
+ _success_count = 0;
+ _consecutive_errors = 0;
+ _stop = false;
+ _stalled = false;
+ _running = true;
+
+ // 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 << "'");
+
+ OpenThreads::Thread::start();
+ return true;
+}
+
+// sync one directory tree
+bool SGTerraSync::SvnThread::syncTree(const char* dir)
+{
+ int rc;
+ SGPath path( _local_dir );
+
+ path.append( dir );
+ 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
+ if (_use_built_in)
+ return syncTreeInternal(dir);
+ else
+#endif
+ {
+ return syncTreeExternal(dir);
+ }
+}
+
+
+#ifdef HAVE_SVN_CLIENT_H
+bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
+{
+ SG_LOG(SG_TERRAIN,SG_DEBUG, "Synchronizing scenery directory " << dir);
+ if (!_svn_pool)
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Built-in SVN client failed to initialize.");
+ 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;
+
+ apr_pool_t *subpool = svn_pool_create(_svn_pool);
+
+ err=0;
+#if (SVN_VER_MINOR >= 5)
+ err = svn_client_checkout3(NULL,
+ command,
+ dest_base_dir,
+ _svn_rev_peg,
+ _svn_rev,
+ svn_depth_infinity,
+ 0, // ignore-externals = false
+ 0, // allow unver obstructions = false
+ _svn_ctx,
+ subpool);
+#else
+ // version 1.4 API
+ err = svn_client_checkout2(NULL,
+ command,
+ dest_base_dir,
+ _svn_rev_peg,
+ _svn_rev,
+ 1, // recurse=true - same as svn_depth_infinity for checkout3 above
+ 0, // ignore externals = false
+ _svn_ctx,
+ subpool);
+#endif
+
+ bool ReturnValue = true;
+ 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)
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "SVN repository cleanup successful for '" << dir << "'.");
+ }
+ ReturnValue = false;
+ } else
+ {
+ SG_LOG(SG_TERRAIN,SG_DEBUG, "Done with scenery directory " << dir);
+ }
+ svn_pool_destroy(subpool);
+ return ReturnValue;
+}
+#endif
+
+bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
+{
+ int rc;
+ char command[512];
+ if (_use_svn)
+ {
+ snprintf( command, 512,
+ "%s %s/%s %s/%s", svn_cmd,
+ _svn_server.c_str(), dir,
+ _local_dir.c_str(), dir );
+ } else {
+ snprintf( command, 512,
+ "%s %s/%s/ %s/%s/", rsync_cmd,
+ _rsync_server.c_str(), dir,
+ _local_dir.c_str(), dir );
+ }
+ SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
+ rc = system( command );
+ if (rc)
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Failed to synchronize directory '" << dir << "', " <<
+ "error code= " << rc);
+ return false;
+ }
+ return true;
+}
+
+void SGTerraSync::SvnThread::run()
+{
+ _active = true;
+ while (!_stop)
+ {
+ WaitingTile next = waitingTiles.pop_front();
+ if (_stop)
+ break;
+
+ CompletedTiles::iterator ii =
+ _completedTiles.find( next._dir );
+ time_t now = time(0);
+ if ((ii == _completedTiles.end())||
+ ((ii->second + 60*60*24) < now ))
+ {
+ _busy = true;
+ if (!syncTree(next._dir.c_str()))
+ {
+ _consecutive_errors++;
+ _fail_count++;
+ }
+ else
+ {
+ _consecutive_errors = 0;
+ _success_count++;
+ SG_LOG(SG_TERRAIN,SG_INFO,
+ "Successfully synchronized directory '" << next._dir << "'");
+ if (next._refreshScenery)
+ {
+ // updated a tile
+ _updated_tile_count++;
+ _freshTiles.push_back(next);
+ _is_dirty = true;
+ }
+ }
+ _busy = false;
+ _completedTiles[ next._dir ] = now;
+ }
+
+ if (_consecutive_errors >= 5)
+ {
+ _stalled = true;
+ _stop = true;
+ }
+ }
+ _active = false;
+ _running = false;
+ _is_dirty = true;
+}
+
+#ifdef HAVE_SVN_CLIENT_H
// Configure our subversion session
-int mysvn_setup(void) {
+int SGTerraSync::SvnThread::svnClientSetup(void)
+{
// Are we already prepared?
- if (mysvn_pool) return EXIT_SUCCESS;
+ if (_svn_pool) return EXIT_SUCCESS;
// No, so initialize svn internals generally
+
#ifdef _MSC_VER
// there is a segfault when providing an error stream.
// Apparently, calling setvbuf with a nul buffer is
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);
svn_error_t *err = NULL;
- SVN_VERSION_DEFINE(mysvn_version);
- err = svn_ver_check_list(&mysvn_version, mysvn_checklist);
+ SVN_VERSION_DEFINE(_svn_version);
+ err = svn_ver_check_list(&_svn_version, mysvn_checklist);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
err = svn_ra_initialize(pool);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
char *config_dir = NULL;
err = svn_config_ensure(config_dir, pool);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
- err = svn_client_create_context(&mysvn_ctx, pool);
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
+ err = svn_client_create_context(&_svn_ctx, pool);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
- err = svn_config_get_config(&(mysvn_ctx->config),
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
+ err = svn_config_get_config(&(_svn_ctx->config),
config_dir, pool);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
svn_config_t *cfg;
cfg = ( svn_config_t*) apr_hash_get(
- mysvn_ctx->config,
- SVN_CONFIG_CATEGORY_CONFIG,
+ _svn_ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING);
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
- svn_auth_baton_t *ab;
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
+
+ svn_auth_baton_t *ab=NULL;
+
+#if (SVN_VER_MINOR >= 6)
+ err = svn_cmdline_create_auth_baton (&ab,
+ TRUE, NULL, NULL, config_dir, TRUE, FALSE, cfg,
+ _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
+#else
err = svn_cmdline_setup_auth_baton(&ab,
TRUE, NULL, NULL, config_dir, TRUE, cfg,
- mysvn_ctx->cancel_func, mysvn_ctx->cancel_baton, pool);
+ _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
+#endif
+
if (err)
- return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
- mysvn_ctx->auth_baton = ab;
+ return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
+
+ _svn_ctx->auth_baton = ab;
#if (SVN_VER_MINOR >= 5)
- mysvn_ctx->conflict_func = NULL;
- mysvn_ctx->conflict_baton = NULL;
+ _svn_ctx->conflict_func = NULL;
+ _svn_ctx->conflict_baton = NULL;
#endif
+
// Now our magic revisions
- mysvn_rev = (svn_opt_revision_t*) apr_palloc(pool,
+ _svn_rev = (svn_opt_revision_t*) apr_palloc(pool,
sizeof(svn_opt_revision_t));
- if (!mysvn_rev)
+ if (!_svn_rev)
return EXIT_FAILURE;
- mysvn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool,
+ _svn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool,
sizeof(svn_opt_revision_t));
- if (!mysvn_rev_peg)
+ if (!_svn_rev_peg)
return EXIT_FAILURE;
- mysvn_rev->kind = svn_opt_revision_head;
- mysvn_rev_peg->kind = svn_opt_revision_unspecified;
+ _svn_rev->kind = svn_opt_revision_head;
+ _svn_rev_peg->kind = svn_opt_revision_unspecified;
// Success if we got this far
- mysvn_pool = pool;
+ _svn_pool = pool;
return EXIT_SUCCESS;
}
-
#endif
-// sync one directory tree
-void sync_tree(const char* dir) {
- int rc;
- char command[512];
- SGPath path( dest_base );
+///////////////////////////////////////////////////////////////////////////////
+// SGTerraSync ////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
+ _svnThread(NULL),
+ last_lat(NOWHERE),
+ last_lon(NOWHERE),
+ _terraRoot(root->getNode("/sim/terrasync",true)),
+ _tile_cache(NULL)
+{
+ _svnThread = new SvnThread();
+}
- path.append( dir );
- rc = path.create_dir( 0755 );
- if (rc) {
- cout << "Return code = " << rc << endl;
- exit(1);
- }
+SGTerraSync::~SGTerraSync()
+{
+ _tiedProperties.Untie();
+ delete _svnThread;
+ _svnThread = NULL;
+}
- if (use_svn) {
-#ifdef HAVE_SVN_CLIENT_H
- cout << dir << " ... ";
- cout.flush();
- char dest_base_dir[512];
- snprintf( command, 512,
- "%s/%s", source_base, dir);
- snprintf( dest_base_dir, 512,
- "%s/%s", dest_base, dir);
- svn_error_t *err = NULL;
- if (mysvn_setup() != EXIT_SUCCESS)
- exit(1);
- apr_pool_t *subpool = svn_pool_create(mysvn_pool);
-
-#if (SVN_VER_MINOR >= 5)
- err = svn_client_checkout3(NULL,
- command,
- dest_base_dir,
- mysvn_rev_peg,
- mysvn_rev,
- svn_depth_infinity,
- 0, // ignore-externals = false
- 0, // allow unver obstructions = false
- mysvn_ctx,
- subpool);
-#else
- // version 1.4 API
- err = svn_client_checkout2(NULL,
- command,
- dest_base_dir,
- mysvn_rev_peg,
- mysvn_rev,
- 1, // recurse=true - same as svn_depth_infinity for checkout3 above
- 0, // ignore externals = false
- mysvn_ctx,
- subpool);
-#endif
-
- if (err) {
- // Report errors from the checkout attempt
- cout << "failed: " << endl
- << err->message << endl;
- svn_error_clear(err);
- return;
- } else {
- cout << "done" << endl;
- }
- svn_pool_destroy(subpool);
- return;
-#else
+void SGTerraSync::init()
+{
+ _refresh_display = _terraRoot->getNode("refresh-display",true);
+ _terraRoot->getNode("built-in-svn-available",true)->setBoolValue(svn_built_in_available);
+ reinit();
+}
- snprintf( command, 512,
- "%s %s/%s %s/%s", svn_cmd,
- source_base, dir,
- dest_base, dir );
-#endif
- } else {
- snprintf( command, 512,
- "%s %s/%s/ %s/%s/", rsync_cmd,
- source_base, dir,
- dest_base, dir );
- }
- cout << command << endl;
- rc = system( command );
- if (rc) {
- cout << "Return code = " << rc << endl;
- if (rc == 5120) exit(1);
+void SGTerraSync::reinit()
+{
+ // do not reinit when enabled and we're already up and running
+ if ((_terraRoot->getNode("enabled",true)->getBoolValue())&&
+ (_svnThread->_active && _svnThread->_running))
+ return;
+
+ _svnThread->stop();
+
+ if (_terraRoot->getNode("enabled",true)->getBoolValue())
+ {
+ _svnThread->setSvnServer(_terraRoot->getNode("svn-server",true)->getStringValue());
+ _svnThread->setRsyncServer(_terraRoot->getNode("rsync-server",true)->getStringValue());
+ _svnThread->setLocalDir(_terraRoot->getNode("scenery-dir",true)->getStringValue());
+
+ #ifdef HAVE_SVN_CLIENT_H
+ _svnThread->setUseBuiltin(_terraRoot->getNode("use-built-in-svn",true)->getBoolValue());
+ #else
+ _terraRoot->getNode("use-built-in-svn",true)->setBoolValue(false);
+ #endif
+ _svnThread->setUseSvn(_terraRoot->getNode("use-svn",true)->getBoolValue());
+
+ if (_svnThread->start())
+ syncAirportsModels();
}
-}
-#if defined(_MSC_VER) || defined(__MINGW32__)
-typedef void (__cdecl * sighandler_t)(int);
-#elif defined( __APPLE__ ) || defined (__FreeBSD__)
-typedef sig_t sighandler_t;
-#endif
+ _stalled_node->setBoolValue(_svnThread->_stalled);
+ last_lat = NOWHERE;
+ last_lon = NOWHERE;
+}
-bool terminating = false;
-sighandler_t prior_signal_handlers[32];
-int termination_triggering_signals[] = {
-#if defined(_MSC_VER) || defined(__MINGW32__)
- SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, SIGBREAK, SIGABRT,
-#else
- SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGTERM,
-#endif
- 0}; // zero terminated
-
-void terminate_request_handler(int param) {
- char msg[] = "\nReceived signal XX, intend to exit soon.\n"
- "repeat the signal to force immediate termination.\n";
- msg[17] = '0' + param / 10;
- msg[18] = '0' + param % 10;
- write(1, msg, sizeof(msg) - 1);
- terminating = true;
- signal(param, prior_signal_handlers[param]);
- theSocket.close();
+void SGTerraSync::bind()
+{
+ _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
+ _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
+ _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
+ _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
+ _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
+
+ // 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);
}
+void SGTerraSync::unbind()
+{
+ _svnThread->stop();
+ _tiedProperties.Untie();
+}
-const int nowhere = -9999;
+void SGTerraSync::update(double)
+{
+ static SGBucket bucket;
+ if (_svnThread->isDirty())
+ {
+ if (!_svnThread->_active)
+ {
+ if (_svnThread->_stalled)
+ {
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Automatic scenery download/synchronization stalled. Too many errors.");
+ }
+ else
+ {
+ // not really an alert - just always show this message
+ SG_LOG(SG_TERRAIN,SG_ALERT,
+ "Automatic scenery download/synchronization has stopped.");
+ }
+ _stalled_node->setBoolValue(_svnThread->_stalled);
+ }
+ if (!_refresh_display->getBoolValue())
+ return;
-// parse message
-static void parse_message( const string &msg, int *lat, int *lon ) {
- double dlat, dlon;
- string text = msg;
+ while (_svnThread->hasNewTiles())
+ {
+ WaitingTile next = _svnThread->getNewTile();
+ if (next._refreshScenery)
+ {
+ refreshScenery(_svnThread->getLocalDir(),next._dir);
+ }
+ }
+ }
+}
- // find GGA string and advance to start of lat
- string::size_type pos = text.find( "$GPGGA" );
- if ( pos == string::npos )
+void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
+{
+ // find tiles to be refreshed
+ if (_tile_cache)
{
- *lat = nowhere;
- *lon = nowhere;
- return;
- }
- string tmp = text.substr( pos + 7 );
- pos = tmp.find( "," );
- tmp = tmp.substr( pos + 1 );
- // cout << "-> " << tmp << endl;
-
- // find lat then advance to start of hemisphere
- pos = tmp.find( "," );
- string lats = tmp.substr( 0, pos );
- dlat = atof( lats.c_str() ) / 100.0;
- tmp = tmp.substr( pos + 1 );
-
- // find N/S hemisphere and advance to start of lon
- if ( tmp.substr( 0, 1 ) == "S" ) {
- dlat = -dlat;
- }
- pos = tmp.find( "," );
- tmp = tmp.substr( pos + 1 );
-
- // find lon
- pos = tmp.find( "," );
- string lons = tmp.substr( 0, pos );
- dlon = atof( lons.c_str() ) / 100.0;
- tmp = tmp.substr( pos + 1 );
-
- // find E/W hemisphere and advance to start of lon
- if ( tmp.substr( 0, 1 ) == "W" ) {
- dlon = -dlon;
+ path.append(relativeDir);
+ if (path.exists())
+ {
+ simgear::Dir dir(path);
+ //TODO need to be smarter here. only update tiles which actually
+ // changed recently. May also be possible to use information from the
+ // built-in SVN client directly (instead of checking directory contents).
+ PathList tileList = dir.children(simgear::Dir::TYPE_FILE, ".stg");
+ for (unsigned int i=0; i<tileList.size(); ++i)
+ {
+ // reload scenery tile
+ long index = atoi(tileList[i].file().c_str());
+ _tile_cache->refresh_tile(index);
+ }
+ }
}
+}
- if ( dlat < 0 ) {
- *lat = (int)dlat - 1;
- } else {
- *lat = (int)dlat;
- }
+bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
- if ( dlon < 0 ) {
- *lon = (int)dlon - 1;
- } else {
- *lon = (int)dlon;
- }
+void SGTerraSync::setTileCache(TileCache* tile_cache)
+{
+ _tile_cache = tile_cache;
+}
- if ((dlon == 0) && (dlat == 0)) {
- *lon = nowhere;
- *lat = nowhere;
+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++ )
+ {
+ char dir[512];
+ snprintf( dir, 512, "Airports/%c", synced_other );
+ WaitingTile w(dir,false);
+ _svnThread->request( w );
+ }
+ WaitingTile w("Models",false);
+ _svnThread->request( w );
}
-// sync area
-static void sync_area( int lat, int lon ) {
+void SGTerraSync::syncArea( int lat, int lon )
+{
if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
return;
char NS, EW;
EW = 'e';
}
- const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
-
- 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) );
- waitingTiles.push_back( dir );
+ const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
+ 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);
+ _svnThread->request( w );
+ refresh=false;
}
}
-// sync areas
-static void sync_areas( int lat, int lon, int lat_dir, int lon_dir ) {
- // do current 1x1 degree area first
- sync_area( lat, lon );
-
+void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
+{
if ( lat_dir == 0 && lon_dir == 0 ) {
- // now do surrounding 8 1x1 degree areas.
+ // do surrounding 8 1x1 degree areas.
for ( int i = lat - 1; i <= lat + 1; ++i ) {
for ( int j = lon - 1; j <= lon + 1; ++j ) {
if ( i != lat || j != lon ) {
- sync_area( i, j );
+ syncArea( i, j );
}
}
}
} else {
if ( lat_dir != 0 ) {
- sync_area( lat + lat_dir, lon );
- sync_area( lat + lat_dir, lon - 1 );
- sync_area( lat + lat_dir, lon + 1 );
+ syncArea( lat + lat_dir, lon - 1 );
+ syncArea( lat + lat_dir, lon + 1 );
+ syncArea( lat + lat_dir, lon );
}
if ( lon_dir != 0 ) {
- sync_area( lat, lon + lon_dir );
- sync_area( lat - 1, lon + lon_dir );
- sync_area( lat + 1, lon + lon_dir );
+ syncArea( lat - 1, lon + lon_dir );
+ syncArea( lat + 1, lon + lon_dir );
+ syncArea( lat, lon + lon_dir );
}
}
-}
-void getWaitingTile() {
- while ( !waitingTiles.empty() ) {
- CompletedTiles::iterator ii =
- completedTiles.find( waitingTiles.front() );
- time_t now = time(0);
- if ( ii == completedTiles.end() || ii->second + 600 < now ) {
- sync_tree(waitingTiles.front().c_str());
- completedTiles[ waitingTiles.front() ] = now;
- waitingTiles.pop_front();
- break;
- }
- waitingTiles.pop_front();
- }
+ // do current 1x1 degree area first
+ syncArea( lat, lon );
}
-int main( int argc, char **argv ) {
- int port = 5501;
- char host[256] = "localhost";
- bool testing = false;
- bool do_checkout(true);
- int verbose(0);
- const char* pidfn = "";
-
- // parse arguments
- int i = 1;
- while ( i < argc ) {
- if ( (string)argv[i] == "-p" ) {
- ++i;
- port = atoi( argv[i] );
- } else if ( string(argv[i]).find("-pid") == 0 ) {
- ++i;
- pidfn = argv[i];
- cout << "pidfn: " << pidfn << endl;
- } else if ( (string)argv[i] == "-s" ) {
- ++i;
- source_base = argv[i];
- } else if ( (string)argv[i] == "-d" ) {
- ++i;
- dest_base = argv[i];
- } else if ( (string)argv[i] == "-R" ) {
- use_svn = false;
- } else if ( (string)argv[i] == "-S" ) {
- use_svn = true;
- } else if ( (string)argv[i] == "-v" ) {
- verbose++;
- } else if ( (string)argv[i] == "-T" ) {
- testing = true;
- } else if ( (string)argv[i] == "-h" ) {
- usage( argv[0] );
- exit(0);
- } else {
- cerr << "Unrecognized verbiage '" << argv[i] << "'" << endl;
- usage( argv[0] );
- exit(-1);
- }
- ++i;
- }
-
- if (*pidfn) {
- ofstream pidstream;
- pidstream.open(pidfn);
- if (!pidstream.good()) {
- cerr << "Cannot open pid file '" << pidfn << "': ";
- perror(0);
- exit(2);
- }
- pidstream << getpid() << endl;
- pidstream.close();
- }
-
- // Use the appropriate default for the "-s" flag
- if (source_base == NULL) {
- if (use_svn)
- source_base = svn_base;
- else
- source_base = rsync_base;
- }
-
- // Must call this before any other net stuff
- simgear::Socket::initSockets();
-
- if ( ! theSocket.open( false ) ) { // open a UDP socket
- printf("error opening socket\n");
- return -1;
- }
-
- if ( theSocket.bind( host, port ) == -1 ) {
- printf("error binding to port %d\n", port);
- return -1;
- }
-
- char msg[256];
- int maxlen = 256;
- int len;
- int lat, lon;
- int last_lat = nowhere;
- int last_lon = nowhere;
- bool recv_msg = false;
-
- char synced_other;
- if (do_checkout) {
- for ( synced_other = 'K'; synced_other <= 'Z'; synced_other++ ) {
- char dir[512];
- snprintf( dir, 512, "Airports/%c", synced_other );
- waitingTiles.push_back( dir );
- }
- for ( synced_other = 'A'; synced_other <= 'J'; synced_other++ ) {
- char dir[512];
- snprintf( dir, 512, "Airports/%c", synced_other );
- waitingTiles.push_back( dir );
- }
- if ( use_svn ) {
- waitingTiles.push_back( "Models" );
- }
- }
-
- for (int* sigp=termination_triggering_signals; *sigp; sigp++) {
- prior_signal_handlers[*sigp] =
- signal(*sigp, *terminate_request_handler);
- if (verbose) cout << "Intercepted signal " << *sigp << endl;
- }
-
- while ( !terminating ) {
- // main loop
- recv_msg = false;
- if ( testing ) {
- // No FGFS communications
- lat = 37;
- lon = -123;
- recv_msg = (lat != last_lat) || (lon != last_lon);
- } else {
- if (verbose && waitingTiles.empty()) {
- cout << "Idle; waiting for FlightGear position\n";
+bool SGTerraSync::schedulePosition(int lat, int lon)
+{
+ // 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
+ {
+ dist = lat - last_lat;
+ if ( dist != 0 )
+ {
+ lat_dir = dist / abs(dist);
}
- theSocket.setBlocking(waitingTiles.empty());
- len = theSocket.recv(msg, maxlen, 0);
- if (len >= 0) {
- msg[len] = '\0';
- recv_msg = true;
- if (verbose) cout << "recv length: " << len << endl;
- parse_message( msg, &lat, &lon );
+ else
+ {
+ lat_dir = 0;
}
- }
-
- if ( recv_msg ) {
- // Ignore messages where the location does not change
- if ( lat != last_lat || lon != last_lon ) {
- cout << "pos in msg = " << lat << "," << lon << endl;
- deque<string> oldRequests;
- oldRequests.swap( waitingTiles );
- int lat_dir, lon_dir, dist;
- if ( last_lat == nowhere || last_lon == nowhere ) {
- lat_dir = lon_dir = 0;
- } else {
- 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;
- }
- }
- cout << "lat = " << lat << " lon = " << lon << endl;
- cout << "lat_dir = " << lat_dir << " "
- << "lon_dir = " << lon_dir << endl;
- sync_areas( lat, lon, lat_dir, lon_dir );
- while ( !oldRequests.empty() ) {
- waitingTiles.push_back( oldRequests.front() );
- oldRequests.pop_front();
- }
- last_lat = lat;
- last_lon = lon;
+ dist = lon - last_lon;
+ if ( dist != 0 )
+ {
+ lon_dir = dist / abs(dist);
+ } else
+ {
+ lon_dir = 0;
}
- }
-
- // No messages inbound, so process some pending work
- else if ( !waitingTiles.empty() ) {
- getWaitingTile();
}
- else if ( testing ) {
- terminating = true;
- } else
+ SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " <<
+ "lat = " << lat << ", lon = " << lon <<
+ ", lat_dir = " << lat_dir << ", " <<
+ "lon_dir = " << lon_dir);
- #ifdef _WIN32
- Sleep(1000);
-#else
- sleep(1);
-#endif
- } // while !terminating
-
- return 0;
+ syncAreas( lat, lon, lat_dir, lon_dir );
+
+ last_lat = lat;
+ last_lon = lon;
+ return true;
+ }
+ return false;
}