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.
25 #include <simgear_config.h>
26 #include <simgear/compiler.h>
35 #elif defined(_MSC_VER)
41 #include <stdlib.h> // atoi() atof() abs() system()
42 #include <signal.h> // signal()
50 #include <simgear/version.h>
52 #include "terrasync.hxx"
54 #include <simgear/bucket/newbucket.hxx>
55 #include <simgear/misc/sg_path.hxx>
56 #include <simgear/misc/strutils.hxx>
57 #include <simgear/threads/SGQueue.hxx>
58 #include <simgear/misc/sg_dir.hxx>
59 #include <simgear/debug/BufferedLogCallback.hxx>
60 #include <simgear/props/props_io.hxx>
61 #include <simgear/io/HTTPClient.hxx>
62 #include <simgear/io/SVNRepository.hxx>
63 #include <simgear/io/HTTPRepository.hxx>
64 #include <simgear/io/DNSClient.hxx>
65 #include <simgear/structure/exception.hxx>
66 #include <simgear/math/sg_random.h>
68 static const bool svn_built_in_available = true;
70 using namespace simgear;
73 const char* rsync_cmd =
74 "rsync --verbose --archive --delete --perms --owner --group";
76 const char* svn_options =
79 namespace UpdateInterval
81 // interval in seconds to allow an update to repeat after a successful update (=daily)
82 static const double SuccessfulAttempt = 24*60*60;
83 // interval in seconds to allow another update after a failed attempt (10 minutes)
84 static const double FailedAttempt = 10*60;
87 typedef map<string,time_t> TileAgeCache;
89 ///////////////////////////////////////////////////////////////////////////////
90 // helper functions ///////////////////////////////////////////////////////////
91 ///////////////////////////////////////////////////////////////////////////////
92 string stripPath(string path)
94 // svn doesn't like trailing white-spaces or path separators - strip them!
95 path = simgear::strutils::strip(path);
96 size_t slen = path.length();
98 ((path[slen-1]=='/')||(path[slen-1]=='\\')))
102 return path.substr(0,slen);
105 bool hasWhitespace(string path)
107 return path.find(' ')!=string::npos;
110 ///////////////////////////////////////////////////////////////////////////////
111 // SyncItem ////////////////////////////////////////////////////////////
112 ///////////////////////////////////////////////////////////////////////////////
118 Stop = 0, ///< special item indicating to stop the SVNThread
129 Cached, ///< using already cached result
142 SyncItem(string dir, Type ty) :
153 ///////////////////////////////////////////////////////////////////////////////
156 * @brief SyncSlot encapsulates a queue of sync items we will fetch
157 * serially. Multiple slots exist to sync different types of item in
164 isNewDirectory(false),
168 SyncItem currentItem;
170 std::queue<SyncItem> queue;
171 std::auto_ptr<AbstractRepository> repository;
173 bool busy; ///< is the slot working or idle
175 unsigned int nextWarnTimeout;
178 static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
179 static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
180 static const int SYNC_SLOT_AI_DATA = 2; /// AI traffic and models
181 static const unsigned int NUM_SYNC_SLOTS = 3;
184 * @brief translate a sync item type into one of the available slots.
185 * This provides the scheduling / balancing / prioritising between slots.
187 static unsigned int syncSlotForType(SyncItem::Type ty)
190 case SyncItem::Tile: return SYNC_SLOT_TILES;
191 case SyncItem::SharedModels:
192 case SyncItem::AirportData:
193 return SYNC_SLOT_SHARED_DATA;
194 case SyncItem::AIData:
195 return SYNC_SLOT_AI_DATA;
198 return SYNC_SLOT_SHARED_DATA;
202 ///////////////////////////////////////////////////////////////////////////////
204 ///////////////////////////////////////////////////////////////////////////////
206 class ServerSelectQuery : public HTTP::Request
209 ServerSelectQuery() :
210 HTTP::Request("http://scenery.flightgear.org/svn-server", "GET")
214 std::string svnUrl() const
216 std::string s = simgear::strutils::strip(m_url);
217 if (s.at(s.length() - 1) == '/') {
218 s = s.substr(0, s.length() - 1);
224 virtual void gotBodyData(const char* s, int n)
226 m_url.append(std::string(s, n));
229 virtual void onFail()
231 SG_LOG(SG_TERRASYNC, SG_ALERT, "Failed to query TerraSync SVN server");
232 HTTP::Request::onFail();
240 ///////////////////////////////////////////////////////////////////////////////
241 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
242 ///////////////////////////////////////////////////////////////////////////////
243 class SGTerraSync::SvnThread : public SGThread
247 virtual ~SvnThread( ) { stop(); }
252 bool isIdle() {return !_busy; }
253 void request(const SyncItem& dir) {waitingTiles.push_front(dir);}
254 bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
255 bool hasNewTiles() { return !_freshTiles.empty();}
256 SyncItem getNewTile() { return _freshTiles.pop_front();}
258 void setSvnServer(string server) { _svn_server = stripPath(server);}
259 void setSvnDataServer(string server) { _svn_data_server = stripPath(server);}
260 void setHTTPServer(const std::string& server)
262 _httpServer = stripPath(server);
265 void setExtSvnUtility(string svn_util) { _svn_command = simgear::strutils::strip(svn_util);}
266 void setRsyncServer(string server) { _rsync_server = simgear::strutils::strip(server);}
267 void setLocalDir(string dir) { _local_dir = stripPath(dir);}
268 string getLocalDir() { return _local_dir;}
269 void setUseSvn(bool use_svn) { _use_svn = use_svn;}
270 void setAllowedErrorCount(int errors) {_allowed_errors = errors;}
272 void setCachePath(const SGPath& p) {_persistentCachePath = p;}
273 void setCacheHits(unsigned int hits) {_cache_hits = hits;}
274 void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
276 volatile bool _active;
277 volatile bool _running;
279 volatile bool _stalled;
280 volatile int _fail_count;
281 volatile int _updated_tile_count;
282 volatile int _success_count;
283 volatile int _consecutive_errors;
284 volatile int _allowed_errors;
285 volatile int _cache_hits;
286 volatile int _transfer_rate;
287 // kbytes, not bytes, because bytes might overflow 2^31
288 volatile int _total_kb_downloaded;
293 // external model run and helpers
295 void syncPathExternal(const SyncItem& next);
296 bool runExternalSyncCommand(const char* dir);
298 // internal mode run and helpers
300 void updateSyncSlot(SyncSlot& slot);
302 // commond helpers between both internal and external models
304 SyncItem::Status isPathCached(const SyncItem& next) const;
305 void initCompletedTilesPersistentCache();
306 void writeCompletedTilesPersistentCache() const;
307 void updated(SyncItem item, bool isNewDirectory);
308 void fail(SyncItem failedItem);
309 void notFound(SyncItem notFoundItem);
313 SyncSlot _syncSlots[NUM_SYNC_SLOTS];
315 volatile bool _is_dirty;
317 SGBlockingDeque <SyncItem> waitingTiles;
319 TileAgeCache _completedTiles;
320 TileAgeCache _notFoundItems;
322 SGBlockingDeque <SyncItem> _freshTiles;
325 string _svn_data_server;
327 string _rsync_server;
329 SGPath _persistentCachePath;
333 SGTerraSync::SvnThread::SvnThread() :
339 _updated_tile_count(0),
341 _consecutive_errors(0),
345 _total_kb_downloaded(0),
351 _http.setUserAgent("terrascenery-" SG_STRINGIZE(SIMGEAR_VERSION));
354 void SGTerraSync::SvnThread::stop()
356 // drop any pending requests
357 waitingTiles.clear();
362 // set stop flag and wake up the thread with an empty request
364 SyncItem w(string(), SyncItem::Stop);
370 bool SGTerraSync::SvnThread::start()
377 SG_LOG(SG_TERRASYNC,SG_ALERT,
378 "Cannot start scenery download. Local cache directory is undefined.");
384 SGPath path(_local_dir);
387 SG_LOG(SG_TERRASYNC,SG_ALERT,
388 "Cannot start scenery download. Directory '" << _local_dir <<
389 "' does not exist. Set correct directory path or create directory folder.");
395 path.append("version");
398 SG_LOG(SG_TERRASYNC,SG_ALERT,
399 "Cannot start scenery download. Directory '" << _local_dir <<
400 "' contains the base package. Use a separate directory.");
406 _use_svn |= _use_built_in;
409 if ((!_use_svn)&&(_rsync_server==""))
411 SG_LOG(SG_TERRASYNC,SG_ALERT,
412 "Cannot start scenery download. Rsync scenery server is undefined.");
419 _updated_tile_count = 0;
421 _consecutive_errors = 0;
428 if (_use_svn && _use_built_in)
429 status = "Using built-in SVN support. ";
432 status = "Using external SVN utility '";
433 status += _svn_command;
438 status = "Using RSYNC. ";
441 // not really an alert - but we want to (always) see this message, so user is
442 // aware we're downloading scenery (and using bandwidth).
443 SG_LOG(SG_TERRASYNC,SG_ALERT,
444 "Starting automatic scenery download/synchronization. "
446 << "Directory: '" << _local_dir << "'.");
452 bool SGTerraSync::SvnThread::runExternalSyncCommand(const char* dir)
455 SGPath localPath( _local_dir );
456 localPath.append( dir );
460 buf << "\"" << _svn_command << "\" "
461 << svn_options << " "
462 << "\"" << _svn_server << "/" << dir << "\" "
463 << "\"" << localPath.str_native() << "\"";
465 buf << rsync_cmd << " "
466 << "\"" << _rsync_server << "/" << dir << "/\" "
467 << "\"" << localPath.str_native() << "/\"";
472 // windows command line parsing is just lovely...
473 // to allow white spaces, the system call needs this:
474 // ""C:\Program Files\something.exe" somearg "some other arg""
475 // Note: whitespace strings quoted by a pair of "" _and_ the
476 // entire string needs to be wrapped by "" too.
477 // The svn url needs forward slashes (/) as a path separator while
478 // the local path needs windows-native backslash as a path separator.
479 command = "\"" + buf.str() + "\"";
483 SG_LOG(SG_TERRASYNC,SG_DEBUG, "sync command '" << command << "'");
486 // tbd: does Windows support "popen"?
487 int rc = system( command.c_str() );
489 FILE* pipe = popen( command.c_str(), "r");
491 // wait for external process to finish
498 SG_LOG(SG_TERRASYNC,SG_ALERT,
499 "Failed to synchronize directory '" << dir << "', " <<
500 "error code= " << rc);
506 void SGTerraSync::SvnThread::run()
509 initCompletedTilesPersistentCache();
512 if (_httpServer == "automatic" ) {
514 //TODO: make DN and service settable from properties
515 DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest("terrasync.flightgear.org");
516 naptrRequest->qservice = "ws20";
518 naptrRequest->qflags = "U";
519 DNS::Request_ptr r(naptrRequest);
521 DNS::Client dnsClient;
522 dnsClient.makeRequest(r);
523 while( !r->isComplete() && !r->isTimeout() )
526 if( naptrRequest->entries.empty() ) {
527 SG_LOG(SG_TERRASYNC, SG_ALERT, "ERROR: automatic terrasync http-server requested, but no DNS entry found.");
530 // walk through responses, they are ordered by 1. order and 2. preference
531 // For now, only take entries with lowest order
532 // TODO: try all available servers in the order given by preferenc and order
533 int order = naptrRequest->entries[0]->order;
535 // get all servers with this order and the same (for now only lowest preference)
536 DNS::NAPTRRequest::NAPTR_list availableServers;
537 for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin();
538 it != naptrRequest->entries.end();
541 if( (*it)->order != order )
544 string regex = (*it)->regexp;
545 if( false == simgear::strutils::starts_with( (*it)->regexp, "!^.*$!" ) ) {
546 SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
550 if( false == simgear::strutils::ends_with( (*it)->regexp, "!" ) ) {
551 SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
555 // always use first entry
556 if( availableServers.empty() || (*it)->preference == availableServers[0]->preference) {
557 SG_LOG(SG_TERRASYNC,SG_DEBUG, "available server regexp: " << (*it)->regexp );
558 availableServers.push_back( *it );
562 // now pick a random entry from the available servers
563 DNS::NAPTRRequest::NAPTR_list::size_type idx = sg_random() * availableServers.size();
564 _httpServer = availableServers[idx]->regexp;
565 _httpServer = _httpServer.substr( 6, _httpServer.length()-7 ); // strip search pattern and separators
567 SG_LOG(SG_TERRASYNC,SG_INFO, "picking entry # " << idx << ", server is " << _httpServer );
571 if( _httpServer.empty() ) { // don't resolve SVN server is HTTP server is set
572 if ( _svn_server.empty()) {
573 SG_LOG(SG_TERRASYNC,SG_INFO, "Querying closest TerraSync server");
574 ServerSelectQuery* ssq = new ServerSelectQuery;
575 HTTP::Request_ptr req = ssq;
576 _http.makeRequest(req);
577 while (!req->isComplete()) {
581 if (req->readyState() == HTTP::Request::DONE) {
582 _svn_server = ssq->svnUrl();
583 SG_LOG(SG_TERRASYNC,SG_INFO, "Closest TerraSync server:" << _svn_server);
585 SG_LOG(SG_TERRASYNC,SG_WARN, "Failed to query closest TerraSync server");
588 SG_LOG(SG_TERRASYNC,SG_INFO, "Explicit: TerraSync server:" << _svn_server);
591 if (_svn_server.empty()) {
592 #if WE_HAVE_A_DEFAULT_SERVER_BUT_WE_DONT_THIS_URL_IS_NO_LONGER_VALID
594 _svn_server = "http://foxtrot.mgras.net:8080/terrascenery/trunk/data/Scenery";
610 void SGTerraSync::SvnThread::runExternal()
614 SyncItem next = waitingTiles.pop_front();
618 SyncItem::Status cacheStatus = isPathCached(next);
619 if (cacheStatus != SyncItem::Invalid) {
621 SG_LOG(SG_TERRASYNC, SG_DEBUG,
622 "Cache hit for: '" << next._dir << "'");
623 next._status = cacheStatus;
624 _freshTiles.push_back(next);
629 syncPathExternal(next);
631 if ((_allowed_errors >= 0)&&
632 (_consecutive_errors >= _allowed_errors))
637 } // of thread running loop
640 void SGTerraSync::SvnThread::syncPathExternal(const SyncItem& next)
643 SGPath path( _local_dir );
644 path.append( next._dir );
645 bool isNewDirectory = !path.exists();
648 if (isNewDirectory) {
649 int rc = path.create_dir( 0755 );
651 SG_LOG(SG_TERRASYNC,SG_ALERT,
652 "Cannot create directory '" << path << "', return code = " << rc );
653 throw sg_exception("Cannot create directory for terrasync", path.str());
657 if (!runExternalSyncCommand(next._dir.c_str())) {
658 throw sg_exception("Running external sync command failed");
660 } catch (sg_exception& e) {
666 updated(next, isNewDirectory);
670 void SGTerraSync::SvnThread::updateSyncSlot(SyncSlot &slot)
672 if (slot.repository.get()) {
673 if (slot.repository->isDoingSync()) {
675 if (slot.stamp.elapsedMSec() > (int)slot.nextWarnTimeout) {
676 SG_LOG(SG_TERRASYNC, SG_INFO, "sync taking a long time:" << slot.currentItem._dir << " taken " << slot.stamp.elapsedMSec());
677 SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP request count:" << _http.hasActiveRequests());
678 slot.nextWarnTimeout += 10000;
681 return; // easy, still working
685 SVNRepository::ResultCode res = slot.repository->failure();
686 if (res == AbstractRepository::REPO_ERROR_NOT_FOUND) {
687 notFound(slot.currentItem);
688 } else if (res != AbstractRepository::REPO_NO_ERROR) {
689 fail(slot.currentItem);
691 updated(slot.currentItem, slot.isNewDirectory);
692 SG_LOG(SG_TERRASYNC, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " finished ("
693 << slot.stamp.elapsedMSec() << " msec");
696 // whatever happened, we're done with this repository instance
698 slot.repository.reset();
701 // init and start sync of the next repository
702 if (!slot.queue.empty()) {
703 slot.currentItem = slot.queue.front();
706 SGPath path(_local_dir);
707 path.append(slot.currentItem._dir);
708 slot.isNewDirectory = !path.exists();
709 if (slot.isNewDirectory) {
710 int rc = path.create_dir( 0755 );
712 SG_LOG(SG_TERRASYNC,SG_ALERT,
713 "Cannot create directory '" << path << "', return code = " << rc );
714 fail(slot.currentItem);
717 } // of creating directory step
719 string serverUrl(_svn_server);
720 if (!_httpServer.empty()) {
721 slot.repository.reset(new HTTPRepository(path, &_http));
722 serverUrl = _httpServer;
724 if (slot.currentItem._type == SyncItem::AIData) {
725 serverUrl = _svn_data_server;
727 slot.repository.reset(new SVNRepository(path, &_http));
730 slot.repository->setBaseUrl(serverUrl + "/" + slot.currentItem._dir);
732 slot.repository->update();
733 } catch (sg_exception& e) {
734 SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " failed to start with error:"
735 << e.getFormattedMessage());
736 fail(slot.currentItem);
738 slot.repository.reset();
742 slot.nextWarnTimeout = 20000;
745 SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " started, queue size is " << slot.queue.size());
749 void SGTerraSync::SvnThread::runInternal()
754 } catch (sg_exception& e) {
755 SG_LOG(SG_TERRASYNC, SG_WARN, "failure doing HTTP update" << e.getFormattedMessage());
758 _transfer_rate = _http.transferRateBytesPerSec();
759 // convert from bytes to kbytes
760 _total_kb_downloaded = static_cast<int>(_http.totalBytesDownloaded() / 1024);
765 // drain the waiting tiles queue into the sync slot queues.
766 while (!waitingTiles.empty()) {
767 SyncItem next = waitingTiles.pop_front();
768 SyncItem::Status cacheStatus = isPathCached(next);
769 if (cacheStatus != SyncItem::Invalid) {
771 SG_LOG(SG_TERRASYNC, SG_DEBUG, "\nTerraSync Cache hit for: '" << next._dir << "'");
772 next._status = cacheStatus;
773 _freshTiles.push_back(next);
778 unsigned int slot = syncSlotForType(next._type);
779 _syncSlots[slot].queue.push(next);
782 bool anySlotBusy = false;
783 // update each sync slot in turn
784 for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
785 updateSyncSlot(_syncSlots[slot]);
786 anySlotBusy |= _syncSlots[slot].busy;
791 // wait on the blocking deque here, otherwise we spin
792 // the loop very fast, since _http::update with no connections
793 // active returns immediately.
794 waitingTiles.waitOnNotEmpty();
796 } // of thread running loop
799 SyncItem::Status SGTerraSync::SvnThread::isPathCached(const SyncItem& next) const
801 TileAgeCache::const_iterator ii = _completedTiles.find( next._dir );
802 if (ii == _completedTiles.end()) {
803 ii = _notFoundItems.find( next._dir );
804 // Invalid means 'not cached', otherwise we want to return to
805 // higher levels the cache status
806 return (ii == _notFoundItems.end()) ? SyncItem::Invalid : SyncItem::NotFound;
809 // check if the path still physically exists. This is needed to
810 // cope with the user manipulating our cache dir
811 SGPath p(_local_dir);
814 return SyncItem::Invalid;
817 time_t now = time(0);
818 return (ii->second > now) ? SyncItem::Cached : SyncItem::Invalid;
821 void SGTerraSync::SvnThread::fail(SyncItem failedItem)
823 time_t now = time(0);
824 _consecutive_errors++;
826 failedItem._status = SyncItem::Failed;
827 _freshTiles.push_back(failedItem);
828 SG_LOG(SG_TERRASYNC,SG_INFO,
829 "Failed to sync'" << failedItem._dir << "'");
830 _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
834 void SGTerraSync::SvnThread::notFound(SyncItem item)
836 // treat not found as authorative, so use the same cache expiry
837 // as succesful download. Important for MP models and similar so
838 // we don't spam the server with lookups for models that don't
841 time_t now = time(0);
842 item._status = SyncItem::NotFound;
843 _freshTiles.push_back(item);
845 _notFoundItems[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
846 writeCompletedTilesPersistentCache();
849 void SGTerraSync::SvnThread::updated(SyncItem item, bool isNewDirectory)
851 time_t now = time(0);
852 _consecutive_errors = 0;
854 SG_LOG(SG_TERRASYNC,SG_INFO,
855 "Successfully synchronized directory '" << item._dir << "'");
857 item._status = SyncItem::Updated;
858 if (item._type == SyncItem::Tile) {
859 _updated_tile_count++;
862 _freshTiles.push_back(item);
863 _completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
865 writeCompletedTilesPersistentCache();
868 void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
870 if (!_persistentCachePath.exists()) {
874 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
875 time_t now = time(0);
878 readProperties(_persistentCachePath.str(), cacheRoot);
879 } catch (sg_exception& e) {
880 SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding");
884 for (int i=0; i<cacheRoot->nChildren(); ++i) {
885 SGPropertyNode* entry = cacheRoot->getChild(i);
886 bool isNotFound = (strcmp(entry->getName(), "not-found") == 0);
887 string tileName = entry->getStringValue("path");
888 time_t stamp = entry->getIntValue("stamp");
894 _completedTiles[tileName] = stamp;
896 _notFoundItems[tileName] = stamp;
901 void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
904 if (_persistentCachePath.isNull()) {
908 std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
913 SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
914 TileAgeCache::const_iterator it = _completedTiles.begin();
915 for (; it != _completedTiles.end(); ++it) {
916 SGPropertyNode* entry = cacheRoot->addChild("entry");
917 entry->setStringValue("path", it->first);
918 entry->setIntValue("stamp", it->second);
921 it = _notFoundItems.begin();
922 for (; it != _notFoundItems.end(); ++it) {
923 SGPropertyNode* entry = cacheRoot->addChild("not-found");
924 entry->setStringValue("path", it->first);
925 entry->setIntValue("stamp", it->second);
928 writeProperties(f, cacheRoot, true /* write_all */);
932 ///////////////////////////////////////////////////////////////////////////////
933 // SGTerraSync ////////////////////////////////////////////////////////////////
934 ///////////////////////////////////////////////////////////////////////////////
935 SGTerraSync::SGTerraSync() :
940 _svnThread = new SvnThread();
941 _log = new BufferedLogCallback(SG_TERRASYNC, SG_INFO);
942 _log->truncateAt(255);
944 sglog().addCallback(_log);
947 SGTerraSync::~SGTerraSync()
951 sglog().removeCallback(_log);
953 _tiedProperties.Untie();
956 void SGTerraSync::setRoot(SGPropertyNode_ptr root)
958 _terraRoot = root->getNode("/sim/terrasync",true);
961 void SGTerraSync::init()
970 _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available);
975 void SGTerraSync::shutdown()
980 void SGTerraSync::reinit()
982 // do not reinit when enabled and we're already up and running
983 if ((_terraRoot->getBoolValue("enabled",false))&&
984 (_svnThread->_active && _svnThread->_running))
991 if (_terraRoot->getBoolValue("enabled",false))
993 _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
994 std::string httpServer(_terraRoot->getStringValue("http-server",""));
995 _svnThread->setHTTPServer(httpServer);
996 _svnThread->setSvnDataServer(_terraRoot->getStringValue("svn-data-server",""));
997 _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
998 _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
999 _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5));
1000 _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
1002 if (httpServer.empty()) {
1003 // HTTP doesn't benefit from using the persistent cache
1004 _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
1006 SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP repository selected, disabling persistent cache");
1009 _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
1010 _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
1011 _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
1013 if (_svnThread->start())
1015 syncAirportsModels();
1019 _stalledNode->setBoolValue(_svnThread->_stalled);
1022 void SGTerraSync::bind()
1029 _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
1030 _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
1031 _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
1032 _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
1033 _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
1034 _tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_svnThread->_cache_hits );
1035 _tiedProperties.Tie( _terraRoot->getNode("transfer-rate-bytes-sec", true), (int*) &_svnThread->_transfer_rate );
1037 // use kbytes here because propety doesn't support 64-bit and we might conceivably
1038 // download more than 2G in a single session
1039 _tiedProperties.Tie( _terraRoot->getNode("downloaded-kbytes", true), (int*) &_svnThread->_total_kb_downloaded );
1041 _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
1042 _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
1043 _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
1044 _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
1045 _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
1046 _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
1047 _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
1048 // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
1049 _stalledNode = _terraRoot->getNode("stalled", true);
1050 _stalledNode->setBoolValue(_svnThread->_stalled);
1051 _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
1054 void SGTerraSync::unbind()
1057 _tiedProperties.Untie();
1062 _stalledNode.clear();
1066 void SGTerraSync::update(double)
1068 static SGBucket bucket;
1069 if (_svnThread->isDirty())
1071 if (!_svnThread->_active)
1073 if (_svnThread->_stalled)
1075 SG_LOG(SG_TERRASYNC,SG_ALERT,
1076 "Automatic scenery download/synchronization stalled. Too many errors.");
1080 // not really an alert - just always show this message
1081 SG_LOG(SG_TERRASYNC,SG_ALERT,
1082 "Automatic scenery download/synchronization has stopped.");
1084 _stalledNode->setBoolValue(_svnThread->_stalled);
1087 while (_svnThread->hasNewTiles())
1089 SyncItem next = _svnThread->getNewTile();
1091 if ((next._type == SyncItem::Tile) || (next._type == SyncItem::AIData)) {
1092 _activeTileDirs.erase(next._dir);
1094 } // of freshly synced items
1098 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
1100 void SGTerraSync::syncAirportsModels()
1102 static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
1103 // note "request" method uses LIFO order, i.e. processes most recent request first
1104 for( unsigned i = 0; i < strlen(bounds)/2; i++ )
1106 for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
1109 dir << "Airports/" << synced_other;
1110 SyncItem w(dir.str(), SyncItem::AirportData);
1111 _svnThread->request( w );
1115 SyncItem w("Models", SyncItem::SharedModels);
1116 _svnThread->request( w );
1119 void SGTerraSync::syncAreaByPath(const std::string& aPath)
1121 const char* terrainobjects[3] = { "Terrain/", "Objects/", 0 };
1122 for (const char** tree = &terrainobjects[0]; *tree; tree++)
1124 std::string dir = string(*tree) + aPath;
1125 if (_activeTileDirs.find(dir) != _activeTileDirs.end()) {
1129 _activeTileDirs.insert(dir);
1130 SyncItem w(dir, SyncItem::Tile);
1131 _svnThread->request( w );
1135 bool SGTerraSync::scheduleTile(const SGBucket& bucket)
1137 std::string basePath = bucket.gen_base_path();
1138 syncAreaByPath(basePath);
1142 bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
1144 if (!_svnThread->_running) {
1148 const char* terrainobjects[3] = { "Terrain/", "Objects/", 0 };
1149 for (const char** tree = &terrainobjects[0]; *tree; tree++) {
1150 string s = *tree + sceneryDir;
1151 if (_activeTileDirs.find(s) != _activeTileDirs.end()) {
1159 void SGTerraSync::scheduleDataDir(const std::string& dataDir)
1161 if (_activeTileDirs.find(dataDir) != _activeTileDirs.end()) {
1165 _activeTileDirs.insert(dataDir);
1166 SyncItem w(dataDir, SyncItem::AIData);
1167 _svnThread->request( w );
1171 bool SGTerraSync::isDataDirPending(const std::string& dataDir) const
1173 if (!_svnThread->_running) {
1177 return (_activeTileDirs.find(dataDir) != _activeTileDirs.end());
1180 void SGTerraSync::reposition()