1 // terrasync.cxx -- scenery fetcher
3 // Started by Curtis Olson, November 2002.
5 // Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
6 // Copyright (C) 2008 Alexander R. Perry <alex.perry@ieee.org>
7 // Copyright (C) 2011 Thorsten Brehm <brehmt@gmail.com>
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 # include <simgear_config.h>
29 #include <simgear/compiler.h>
38 #elif defined(_MSC_VER)
40 # ifndef HAVE_SVN_CLIENT_H
46 #include <stdlib.h> // atoi() atof() abs() system()
47 #include <signal.h> // signal()
55 #include <simgear/compiler.h>
57 #include "terrasync.hxx"
59 #include <simgear/bucket/newbucket.hxx>
60 #include <simgear/misc/sg_path.hxx>
61 #include <simgear/misc/strutils.hxx>
62 #include <simgear/threads/SGQueue.hxx>
63 #include <simgear/misc/sg_dir.hxx>
64 #include <simgear/debug/BufferedLogCallback.hxx>
65 #include <simgear/props/props_io.hxx>
68 # include <simgear/io/HTTPClient.hxx>
69 # include <simgear/io/SVNRepository.hxx>
72 #ifdef HAVE_SVN_CLIENT_H
73 # ifdef HAVE_LIBSVN_CLIENT_1
74 # include <svn_version.h>
75 # include <svn_auth.h>
76 # include <svn_client.h>
77 # include <svn_cmdline.h>
78 # include <svn_pools.h>
80 # undef HAVE_SVN_CLIENT_H
84 #ifdef HAVE_SVN_CLIENT_H
85 static const svn_version_checklist_t mysvn_checklist[] = {
86 { "svn_subr", svn_subr_version },
87 { "svn_client", svn_client_version },
92 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
93 static const bool svn_built_in_available = true;
95 static const bool svn_built_in_available = false;
98 using namespace simgear;
101 const char* rsync_cmd =
102 "rsync --verbose --archive --delete --perms --owner --group";
104 const char* svn_options =
107 namespace UpdateInterval
109 // interval in seconds to allow an update to repeat after a successful update (=daily)
110 static const double SuccessfulAttempt = 24*60*60;
111 // interval in seconds to allow another update after a failed attempt (10 minutes)
112 static const double FailedAttempt = 10*60;
115 typedef map<string,time_t> CompletedTiles;
117 ///////////////////////////////////////////////////////////////////////////////
118 // helper functions ///////////////////////////////////////////////////////////
119 ///////////////////////////////////////////////////////////////////////////////
120 string stripPath(string path)
122 // svn doesn't like trailing white-spaces or path separators - strip them!
123 path = simgear::strutils::strip(path);
124 size_t slen = path.length();
126 ((path[slen-1]=='/')||(path[slen-1]=='\\')))
130 return path.substr(0,slen);
133 bool hasWhitespace(string path)
135 return path.find(' ')!=string::npos;
138 ///////////////////////////////////////////////////////////////////////////////
139 // WaitingTile ////////////////////////////////////////////////////////////////
140 ///////////////////////////////////////////////////////////////////////////////
144 WaitingTile(string dir,bool refresh) :
145 _dir(dir), _refreshScenery(refresh) {}
147 bool _refreshScenery;
150 ///////////////////////////////////////////////////////////////////////////////
151 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
152 ///////////////////////////////////////////////////////////////////////////////
153 class SGTerraSync::SvnThread : public SGThread
157 virtual ~SvnThread( ) { stop(); }
162 bool isIdle() {return waitingTiles.empty();}
163 void request(const WaitingTile& dir) {waitingTiles.push_front(dir);}
164 bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
165 bool hasNewTiles() { return !_freshTiles.empty();}
166 WaitingTile getNewTile() { return _freshTiles.pop_front();}
168 void setSvnServer(string server) { _svn_server = stripPath(server);}
169 void setExtSvnUtility(string svn_util) { _svn_command = simgear::strutils::strip(svn_util);}
170 void setRsyncServer(string server) { _rsync_server = simgear::strutils::strip(server);}
171 void setLocalDir(string dir) { _local_dir = stripPath(dir);}
172 string getLocalDir() { return _local_dir;}
173 void setUseSvn(bool use_svn) { _use_svn = use_svn;}
174 void setAllowedErrorCount(int errors) {_allowed_errors = errors;}
176 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
178 void setCachePath(const SGPath& p) {_persistentCachePath = p;}
179 void setCacheHits(unsigned int hits) {_cache_hits = hits;}
180 void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
183 volatile bool _active;
184 volatile bool _running;
186 volatile bool _stalled;
187 volatile int _fail_count;
188 volatile int _updated_tile_count;
189 volatile int _success_count;
190 volatile int _consecutive_errors;
191 volatile int _allowed_errors;
192 volatile int _cache_hits;
195 bool syncTree(const char* dir, bool& isNewDirectory);
196 bool syncTreeExternal(const char* dir);
198 bool isPathCached(const WaitingTile& next) const;
199 void syncPath(const WaitingTile& next);
201 void initCompletedTilesPersistentCache();
202 void writeCompletedTilesPersistentCache() const;
204 #if defined(SG_SVN_CLIENT)
205 bool syncTreeInternal(const char* dir);
208 std::auto_ptr<SVNRepository> _repository;
209 #elif defined(HAVE_SVN_CLIENT_H)
210 static int svnClientSetup(void);
211 bool syncTreeInternal(const char* dir);
215 // Things we need for doing subversion checkout - often
216 static apr_pool_t *_svn_pool;
217 static svn_client_ctx_t *_svn_ctx;
218 static svn_opt_revision_t *_svn_rev;
219 static svn_opt_revision_t *_svn_rev_peg;
222 volatile bool _is_dirty;
224 SGBlockingDeque <WaitingTile> waitingTiles;
225 CompletedTiles _completedTiles;
226 SGBlockingDeque <WaitingTile> _freshTiles;
230 string _rsync_server;
232 SGPath _persistentCachePath;
235 #ifdef HAVE_SVN_CLIENT_H
236 apr_pool_t* SGTerraSync::SvnThread::_svn_pool = NULL;
237 svn_client_ctx_t* SGTerraSync::SvnThread::_svn_ctx = NULL;
238 svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev = NULL;
239 svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev_peg = NULL;
242 SGTerraSync::SvnThread::SvnThread() :
248 _updated_tile_count(0),
250 _consecutive_errors(0),
253 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
260 #ifdef HAVE_SVN_CLIENT_H
261 int errCode = SGTerraSync::SvnThread::svnClientSetup();
262 if (errCode != EXIT_SUCCESS)
264 SG_LOG(SG_TERRAIN,SG_ALERT,
265 "Failed to initialize built-in SVN client, error = " << errCode);
270 void SGTerraSync::SvnThread::stop()
272 // drop any pending requests
273 waitingTiles.clear();
278 // set stop flag and wake up the thread with an empty request
280 WaitingTile w("",false);
286 bool SGTerraSync::SvnThread::start()
293 SG_LOG(SG_TERRAIN,SG_ALERT,
294 "Cannot start scenery download. Local cache directory is undefined.");
300 SGPath path(_local_dir);
303 SG_LOG(SG_TERRAIN,SG_ALERT,
304 "Cannot start scenery download. Directory '" << _local_dir <<
305 "' does not exist. Set correct directory path or create directory folder.");
311 path.append("version");
314 SG_LOG(SG_TERRAIN,SG_ALERT,
315 "Cannot start scenery download. Directory '" << _local_dir <<
316 "' contains the base package. Use a separate directory.");
322 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
323 _use_svn |= _use_built_in;
326 if ((_use_svn)&&(_svn_server==""))
328 SG_LOG(SG_TERRAIN,SG_ALERT,
329 "Cannot start scenery download. Subversion scenery server is undefined.");
334 if ((!_use_svn)&&(_rsync_server==""))
336 SG_LOG(SG_TERRAIN,SG_ALERT,
337 "Cannot start scenery download. Rsync scenery server is undefined.");
344 _updated_tile_count = 0;
346 _consecutive_errors = 0;
352 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
353 if (_use_svn && _use_built_in)
354 status = "Using built-in SVN support. ";
359 status = "Using external SVN utility '";
360 status += _svn_command;
365 status = "Using RSYNC. ";
368 // not really an alert - but we want to (always) see this message, so user is
369 // aware we're downloading scenery (and using bandwidth).
370 SG_LOG(SG_TERRAIN,SG_ALERT,
371 "Starting automatic scenery download/synchronization. "
373 << "Directory: '" << _local_dir << "'.");
379 // sync one directory tree
380 bool SGTerraSync::SvnThread::syncTree(const char* dir, bool& isNewDirectory)
383 SGPath path( _local_dir );
386 isNewDirectory = !path.exists();
389 rc = path.create_dir( 0755 );
392 SG_LOG(SG_TERRAIN,SG_ALERT,
393 "Cannot create directory '" << dir << "', return code = " << rc );
398 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
400 return syncTreeInternal(dir);
404 return syncTreeExternal(dir);
408 #if defined(SG_SVN_CLIENT)
410 bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
412 ostringstream command;
413 command << _svn_server << "/" << dir;
415 SGPath path(_local_dir);
417 _repository.reset(new SVNRepository(path, &_http));
418 _repository->setBaseUrl(command.str());
422 SG_LOG(SG_IO, SG_DEBUG, "terrasync: will sync " << command.str());
423 _repository->update();
426 while (!_stop && _repository->isDoingSync()) {
430 if (_repository->failure() == SVNRepository::SVN_ERROR_NOT_FOUND) {
431 // this is fine, but maybe we should use a different return code
432 // in the future to higher layers can distuinguish this case
433 } else if (_repository->failure() != SVNRepository::SVN_NO_ERROR) {
436 SG_LOG(SG_IO, SG_DEBUG, "sync of " << command.str() << " finished ("
437 << st.elapsedMSec() << " msec");
444 #elif defined(HAVE_SVN_CLIENT_H)
446 bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
448 SG_LOG(SG_TERRAIN,SG_DEBUG, "Synchronizing scenery directory " << dir);
451 SG_LOG(SG_TERRAIN,SG_ALERT,
452 "Built-in SVN client failed to initialize.");
456 ostringstream command;
457 command << _svn_server << "/" << dir;
459 ostringstream dest_base_dir;
460 dest_base_dir << _local_dir << "/" << dir;
462 apr_pool_t *subpool = svn_pool_create(_svn_pool);
464 svn_error_t *err = NULL;
465 #if (SVN_VER_MINOR >= 5)
466 err = svn_client_checkout3(NULL,
467 command.str().c_str(),
468 dest_base_dir.str().c_str(),
472 0, // ignore-externals = false
473 0, // allow unver obstructions = false
478 err = svn_client_checkout2(NULL,
479 command.str().c_str(),
480 dest_base_dir.str().c_str(),
483 1, // recurse=true - same as svn_depth_infinity for checkout3 above
484 0, // ignore externals = false
489 bool ReturnValue = true;
492 // Report errors from the checkout attempt
493 if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
495 // ignore errors when remote path doesn't exist (no scenery data for ocean areas)
499 SG_LOG(SG_TERRAIN,SG_ALERT,
500 "Failed to synchronize directory '" << dir << "', " <<
501 err->message << " (code " << err->apr_err << ").");
502 svn_error_clear(err);
504 err = svn_client_cleanup(dest_base_dir.str().c_str(),
508 SG_LOG(SG_TERRAIN,SG_ALERT,
509 "SVN repository cleanup successful for '" << dir << "'.");
515 SG_LOG(SG_TERRAIN,SG_DEBUG, "Done with scenery directory " << dir);
517 svn_pool_destroy(subpool);
520 #endif // of HAVE_SVN_CLIENT_H
522 bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
525 SGPath localPath( _local_dir );
526 localPath.append( dir );
530 buf << "\"" << _svn_command << "\" "
531 << svn_options << " "
532 << "\"" << _svn_server << "/" << dir << "\" "
533 << "\"" << localPath.str_native() << "\"";
535 buf << rsync_cmd << " "
536 << "\"" << _rsync_server << "/" << dir << "/\" "
537 << "\"" << localPath.str_native() << "/\"";
542 // windows command line parsing is just lovely...
543 // to allow white spaces, the system call needs this:
544 // ""C:\Program Files\something.exe" somearg "some other arg""
545 // Note: whitespace strings quoted by a pair of "" _and_ the
546 // entire string needs to be wrapped by "" too.
547 // The svn url needs forward slashes (/) as a path separator while
548 // the local path needs windows-native backslash as a path separator.
549 command = "\"" + buf.str() + "\"";
553 SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
556 // tbd: does Windows support "popen"?
557 int rc = system( command.c_str() );
559 FILE* pipe = popen( command.c_str(), "r");
561 // wait for external process to finish
568 SG_LOG(SG_TERRAIN,SG_ALERT,
569 "Failed to synchronize directory '" << dir << "', " <<
570 "error code= " << rc);
576 void SGTerraSync::SvnThread::run()
580 initCompletedTilesPersistentCache();
584 WaitingTile next = waitingTiles.pop_front();
588 if (isPathCached(next)) {
590 SG_LOG(SG_TERRAIN, SG_DEBUG,
591 "Cache hit for: '" << next._dir << "'");
597 if ((_allowed_errors >= 0)&&
598 (_consecutive_errors >= _allowed_errors))
610 bool SGTerraSync::SvnThread::isPathCached(const WaitingTile& next) const
612 CompletedTiles::const_iterator ii = _completedTiles.find( next._dir );
613 if (ii == _completedTiles.end()) {
617 // check if the path still physically exists
618 SGPath p(_local_dir);
624 time_t now = time(0);
625 return (ii->second > now);
628 void SGTerraSync::SvnThread::syncPath(const WaitingTile& next)
630 bool isNewDirectory = false;
631 time_t now = time(0);
634 if (!syncTree(next._dir.c_str(),isNewDirectory))
636 _consecutive_errors++;
638 _completedTiles[ next._dir ] = now + UpdateInterval::FailedAttempt;
642 _consecutive_errors = 0;
644 SG_LOG(SG_TERRAIN,SG_INFO,
645 "Successfully synchronized directory '" << next._dir << "'");
646 if (next._refreshScenery)
649 _updated_tile_count++;
652 // for now only report new directories to refresh display
653 // (i.e. only when ocean needs to be replaced with actual data)
654 _freshTiles.push_back(next);
659 _completedTiles[ next._dir ] = now + UpdateInterval::SuccessfulAttempt;
660 writeCompletedTilesPersistentCache();
665 #if defined(HAVE_SVN_CLIENT_H)
666 // Configure our subversion session
667 int SGTerraSync::SvnThread::svnClientSetup(void)
669 // Are we already prepared?
670 if (_svn_pool) return EXIT_SUCCESS;
671 // No, so initialize svn internals generally
674 // there is a segfault when providing an error stream.
675 // Apparently, calling setvbuf with a nul buffer is
676 // not supported under msvc 7.1 ( code inside svn_cmdline_init )
677 if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS)
680 // revert locale setting
681 setlocale(LC_ALL,"C");
683 /* svn_cmdline_init configures the locale. Setup environment to ensure the
684 * default "C" locale remains active, since fgfs isn't locale aware - especially
685 * requires "." as decimal point in strings containing floating point varibales. */
686 setenv("LC_ALL", "C", 1);
688 if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS)
692 apr_pool_t *pool = NULL;
694 apr_allocator_t* allocator = NULL;
695 int aprErr = apr_allocator_create(&allocator);
696 if (aprErr != APR_SUCCESS)
699 apr_pool_create_ex(&pool, NULL /* parent pool */, NULL /* abort func */, allocator);
701 svn_error_t *err = NULL;
702 SVN_VERSION_DEFINE(_svn_version);
703 err = svn_ver_check_list(&_svn_version, mysvn_checklist);
705 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
706 err = svn_ra_initialize(pool);
708 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
709 char *config_dir = NULL;
710 err = svn_config_ensure(config_dir, pool);
712 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
713 err = svn_client_create_context(&_svn_ctx, pool);
715 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
716 err = svn_config_get_config(&(_svn_ctx->config),
719 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
721 cfg = ( svn_config_t*) apr_hash_get(
723 SVN_CONFIG_CATEGORY_CONFIG,
724 APR_HASH_KEY_STRING);
726 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
728 svn_auth_baton_t *ab=NULL;
730 #if (SVN_VER_MINOR >= 6)
731 err = svn_cmdline_create_auth_baton (&ab,
732 TRUE, NULL, NULL, config_dir, TRUE, FALSE, cfg,
733 _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
735 err = svn_cmdline_setup_auth_baton(&ab,
736 TRUE, NULL, NULL, config_dir, TRUE, cfg,
737 _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
741 return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
743 _svn_ctx->auth_baton = ab;
744 #if (SVN_VER_MINOR >= 5)
745 _svn_ctx->conflict_func = NULL;
746 _svn_ctx->conflict_baton = NULL;
749 // Now our magic revisions
750 _svn_rev = (svn_opt_revision_t*) apr_palloc(pool,
751 sizeof(svn_opt_revision_t));
754 _svn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool,
755 sizeof(svn_opt_revision_t));
758 _svn_rev->kind = svn_opt_revision_head;
759 _svn_rev_peg->kind = svn_opt_revision_unspecified;
760 // Success if we got this far
764 #endif // of defined(HAVE_SVN_CLIENT_H)
766 void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
768 if (!_persistentCachePath.exists()) {
772 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
773 time_t now = time(0);
775 readProperties(_persistentCachePath.str(), cacheRoot);
776 for (int i=0; i<cacheRoot->nChildren(); ++i) {
777 SGPropertyNode* entry = cacheRoot->getChild(i);
778 string tileName = entry->getStringValue("path");
779 time_t stamp = entry->getIntValue("stamp");
784 _completedTiles[tileName] = stamp;
788 void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
791 if (_persistentCachePath.isNull()) {
795 std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
800 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
801 CompletedTiles::const_iterator it = _completedTiles.begin();
802 for (; it != _completedTiles.end(); ++it) {
803 SGPropertyNode* entry = cacheRoot->addChild("entry");
804 entry->setStringValue("path", it->first);
805 entry->setIntValue("stamp", it->second);
808 writeProperties(f, cacheRoot, true /* write_all */);
812 ///////////////////////////////////////////////////////////////////////////////
813 // SGTerraSync ////////////////////////////////////////////////////////////////
814 ///////////////////////////////////////////////////////////////////////////////
815 SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
819 _terraRoot(root->getNode("/sim/terrasync",true)),
823 _svnThread = new SvnThread();
824 _log = new BufferedLogCallback(SG_TERRAIN, SG_INFO);
825 _log->truncateAt(255);
827 sglog().addCallback(_log);
830 SGTerraSync::~SGTerraSync()
832 _tiedProperties.Untie();
835 sglog().removeCallback(_log);
839 void SGTerraSync::init()
841 _refreshDisplay = _terraRoot->getNode("refresh-display",true);
842 _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available);
846 void SGTerraSync::reinit()
848 // do not reinit when enabled and we're already up and running
849 if ((_terraRoot->getBoolValue("enabled",false))&&
850 (_svnThread->_active && _svnThread->_running))
855 if (_terraRoot->getBoolValue("enabled",false))
857 _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
858 _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
859 _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
860 _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5));
861 _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
862 _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
864 #if defined(HAVE_SVN_CLIENT_H) || defined(SG_SVN_CLIENT)
865 _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
867 _terraRoot->setBoolValue("use-built-in-svn",false);
869 _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
870 _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
872 if (_svnThread->start())
874 syncAirportsModels();
875 if (last_lat != NOWHERE && last_lon != NOWHERE)
877 // reschedule most recent position
882 schedulePosition(lat, lon);
887 _stalledNode->setBoolValue(_svnThread->_stalled);
890 void SGTerraSync::bind()
892 _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
893 _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
894 _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
895 _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
896 _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
897 _tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_svnThread->_cache_hits );
899 _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
900 _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
901 _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
902 _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
903 _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
904 _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
905 _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
906 // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
907 _stalledNode = _terraRoot->getNode("stalled", true);
908 _stalledNode->setBoolValue(_svnThread->_stalled);
909 _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
912 void SGTerraSync::unbind()
915 _tiedProperties.Untie();
918 void SGTerraSync::update(double)
920 static SGBucket bucket;
921 if (_svnThread->isDirty())
923 if (!_svnThread->_active)
925 if (_svnThread->_stalled)
927 SG_LOG(SG_TERRAIN,SG_ALERT,
928 "Automatic scenery download/synchronization stalled. Too many errors.");
932 // not really an alert - just always show this message
933 SG_LOG(SG_TERRAIN,SG_ALERT,
934 "Automatic scenery download/synchronization has stopped.");
936 _stalledNode->setBoolValue(_svnThread->_stalled);
939 if (!_refreshDisplay->getBoolValue())
942 while (_svnThread->hasNewTiles())
944 WaitingTile next = _svnThread->getNewTile();
945 if (next._refreshScenery)
947 refreshScenery(_svnThread->getLocalDir(),next._dir);
953 void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
955 // find tiles to be refreshed
958 path.append(relativeDir);
961 simgear::Dir dir(path);
962 //TODO need to be smarter here. only update tiles which actually
963 // changed recently. May also be possible to use information from the
964 // built-in SVN client directly (instead of checking directory contents).
965 PathList tileList = dir.children(simgear::Dir::TYPE_FILE, ".stg");
966 for (unsigned int i=0; i<tileList.size(); ++i)
968 // reload scenery tile
969 long index = atoi(tileList[i].file().c_str());
970 _refreshCb(_userCbData, index);
976 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
978 void SGTerraSync::setTileRefreshCb(SGTerraSyncCallback refreshCb, void* userCbData)
980 _refreshCb = refreshCb;
981 _userCbData = userCbData;
984 void SGTerraSync::syncAirportsModels()
986 static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
987 // note "request" method uses LIFO order, i.e. processes most recent request first
988 for( unsigned i = 0; i < strlen(bounds)/2; i++ )
990 for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
993 dir << "Airports/" << synced_other;
994 WaitingTile w(dir.str(),false);
995 _svnThread->request( w );
998 WaitingTile w("Models",false);
999 _svnThread->request( w );
1003 void SGTerraSync::syncArea( int lat, int lon )
1005 if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
1008 int baselat, baselon;
1011 int base = (int)(lat / 10);
1012 if ( lat == base * 10 ) {
1013 baselat = base * 10;
1015 baselat = (base - 1) * 10;
1019 baselat = (int)(lat / 10) * 10;
1023 int base = (int)(lon / 10);
1024 if ( lon == base * 10 ) {
1025 baselon = base * 10;
1027 baselon = (base - 1) * 10;
1031 baselon = (int)(lon / 10) * 10;
1035 const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
1037 for (const char** tree = &terrainobjects[0]; *tree; tree++)
1040 dir << *tree << "/" << setfill('0')
1041 << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
1042 << EW << setw(3) << abs(lon) << NS << setw(2) << abs(lat);
1043 WaitingTile w(dir.str(),refresh);
1044 _svnThread->request( w );
1050 void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
1052 if ( lat_dir == 0 && lon_dir == 0 ) {
1053 // do surrounding 8 1x1 degree areas.
1054 for ( int i = lat - 1; i <= lat + 1; ++i ) {
1055 for ( int j = lon - 1; j <= lon + 1; ++j ) {
1056 if ( i != lat || j != lon ) {
1062 if ( lat_dir != 0 ) {
1063 syncArea( lat + lat_dir, lon - 1 );
1064 syncArea( lat + lat_dir, lon + 1 );
1065 syncArea( lat + lat_dir, lon );
1067 if ( lon_dir != 0 ) {
1068 syncArea( lat - 1, lon + lon_dir );
1069 syncArea( lat + 1, lon + lon_dir );
1070 syncArea( lat, lon + lon_dir );
1074 // do current 1x1 degree area first
1075 syncArea( lat, lon );
1079 bool SGTerraSync::schedulePosition(int lat, int lon)
1083 // Ignore messages where the location does not change
1084 if ( lat != last_lat || lon != last_lon )
1086 if (_svnThread->_running)
1088 SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " <<
1092 if ( last_lat != NOWHERE && last_lon != NOWHERE )
1094 int dist = lat - last_lat;
1097 lat_dir = dist / abs(dist);
1103 dist = lon - last_lon;
1106 lon_dir = dist / abs(dist);
1113 SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " <<
1114 "lat = " << lat << ", lon = " << lon <<
1115 ", lat_dir = " << lat_dir << ", " <<
1116 "lon_dir = " << lon_dir);
1118 syncAreas( lat, lon, lat_dir, lon_dir );