From 0fd7bb8d3f7bcf62230d960a01cafab2a59ddf2e Mon Sep 17 00:00:00 2001 From: ThorstenB Date: Sat, 11 Jun 2011 23:22:26 +0200 Subject: [PATCH] Convert terrasync into a subsystem. Fixed scenery tile refresh method. Simgear automake makefiles with libsvn detection. Simgear CMake support for libsvn (may not work :) ) --- CMakeLists.txt | 12 + configure.ac | 34 + projects/VC90/SimGear.vcproj | 12 + simgear/scene/CMakeLists.txt | 1 + simgear/scene/tgdb/TileCache.cxx | 11 +- simgear/scene/tgdb/TileEntry.cxx | 17 + simgear/scene/tgdb/TileEntry.hxx | 6 +- simgear/scene/tsync/CMakeLists.txt | 11 + simgear/scene/tsync/Makefile.am | 15 + simgear/scene/tsync/terrasync.cxx | 1092 ++++++++++++++++------------ 10 files changed, 750 insertions(+), 461 deletions(-) create mode 100644 simgear/scene/tsync/CMakeLists.txt create mode 100644 simgear/scene/tsync/Makefile.am diff --git a/CMakeLists.txt b/CMakeLists.txt index 489be89c..9374a3ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}") option(SIMGEAR_SHARED "Set to ON to build SimGear as a shared library/framework" OFF) option(SIMGEAR_HEADLESS "Set to ON to build SimGear with GUI/graphics support" OFF) option(JPEG_FACTORY "Enable JPEG-factory support" OFF) +option(ENABLE_LIBSVN "Set to ON to build SimGear with libsvnclient support" OFF) + set(MSVC_3RDPARTY_ROOT NOT_FOUND CACHE PATH "Location where the third-party dependencies are extracted") if (MSVC AND MSVC_3RDPARTY_ROOT) @@ -67,6 +69,16 @@ if(JPEG_FACTORY) include_directories(${JPEG_INCLUDE_DIR}) endif() +if(ENABLE_LIBSVN) + find_package(SvnClient) + + if(LIBSVN_FOUND) + message(STATUS "libsvn found, enabling in SimGear") + set(HAVE_SVN_CLIENT_H 1) + set(HAVE_LIBSVN_CLIENT_1 1) + endif(LIBSVN_FOUND) +endif(ENABLE_LIBSVN) + check_include_file(sys/time.h HAVE_SYS_TIME_H) check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H) check_include_file(unistd.h HAVE_UNISTD_H) diff --git a/configure.ac b/configure.ac index 9a9b2bb5..28e846cd 100644 --- a/configure.ac +++ b/configure.ac @@ -540,6 +540,39 @@ if test "x$ac_cv_header_zlib_h" != "xyes"; then echo fi +dnl Check for Subversion library support +# libsvn support defaults to yes +save_LIBS=$LIBS +save_CPPFLAGS=$CPPFLAGS +AC_ARG_WITH(libsvn, [ --without-libsvn Do not use built-in subversion (libsvn) for simgear [default=no]], [], [with_libsvn=yes]) +if test "x$with_libsvn" = "xyes"; then + LIBS="`apr-1-config --link-ld`" + CPPFLAGS="-I/usr/include/subversion-1 `apr-1-config --includes --cppflags`" + AC_CHECK_HEADERS([svn_client.h]) + if test "x$ac_cv_header_svn_client_h" = "xyes"; then + echo "Using built-in subversion (libsvn) for scenery downloads." + AC_SEARCH_LIBS(svn_client_checkout, svn_client-1, + [AC_DEFINE([HAVE_LIBSVN_CLIENT_1], [1], [Define to 1 if you have libsvn_client-1])], + [AC_MSG_ERROR(svn_client-1 library not found.)],) + AC_SEARCH_LIBS(svn_cmdline_init, svn_subr-1, , [AC_MSG_ERROR(svn_subr-1 library not found.)],) + AC_SEARCH_LIBS(svn_ra_initialize, svn_ra-1, , [AC_MSG_ERROR(svn_ra-1 library not found.)],) + svn_LIBS=$LIBS + svn_CPPFLAGS=$CPPFLAGS + AC_SUBST(svn_LIBS) + AC_SUBST(svn_CPPFLAGS) + else + echo "Libsvn not found. Will use command line subversion for scenery downloads." + svn_LIBS="" + svn_CPPFLAGS="" + fi +else +echo "Libsvn explicitly disabled. Will use command line subversion for scenery downloads." + svn_LIBS="" + svn_CPPFLAGS="" +fi +LIBS=$save_LIBS +CPPFLAGS=$save_CPPFLAGS + dnl Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS( \ @@ -596,6 +629,7 @@ AC_CONFIG_FILES([ \ simgear/scene/sky/Makefile \ simgear/scene/tgdb/Makefile \ simgear/scene/util/Makefile \ + simgear/scene/tsync/Makefile \ simgear/screen/Makefile \ simgear/serial/Makefile \ simgear/sound/Makefile \ diff --git a/projects/VC90/SimGear.vcproj b/projects/VC90/SimGear.vcproj index 28eaa8b8..144b5123 100644 --- a/projects/VC90/SimGear.vcproj +++ b/projects/VC90/SimGear.vcproj @@ -1459,6 +1459,18 @@ RelativePath="..\..\simgear\scene\tgdb\userdata.hxx" > + + + + + + +// Copyright (C) 2011 Thorsten Brehm // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -22,9 +23,11 @@ // $Id$ #ifdef HAVE_CONFIG_H -#include +# include #endif +#include + #ifdef HAVE_WINDOWS_H #include #endif @@ -43,20 +46,24 @@ #include // atoi() atof() abs() system() #include // signal() -#include - #include #include #include -#include #include -#include +#include + +#include "terrasync.hxx" #include #include +#include +#include +#include +#include #ifdef HAVE_SVN_CLIENT_H # ifdef HAVE_LIBSVN_CLIENT_1 +# include # include # include # include @@ -66,78 +73,403 @@ # 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 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 destination directory [required]\n" -" -R transport using pipe to rsync\n" -" -S transport using built-in svn\n" -" -p listen on UDP port [default: 5501]\n" -" -s source base [default: '']\n" -" -pid 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 waitingTiles; + CompletedTiles _completedTiles; + SGBlockingDeque _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 waitingTiles; -typedef map 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 @@ -148,240 +480,246 @@ int mysvn_setup(void) { 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; irefresh_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; @@ -412,243 +750,93 @@ static void sync_area( int lat, int lon ) { 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 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; } -- 2.39.5