]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tsync/terrasync.cxx
Rename WaitingSyncItem -> SyncItem.
[simgear.git] / simgear / scene / tsync / terrasync.cxx
1 // terrasync.cxx -- scenery fetcher
2 //
3 // Started by Curtis Olson, November 2002.
4 //
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>
8 //
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.
13 //
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.
18 //
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.
22 //
23 // $Id$
24
25 #ifdef HAVE_CONFIG_H
26 #  include <simgear_config.h>
27 #endif
28
29 #include <simgear/compiler.h>
30
31 #ifdef HAVE_WINDOWS_H
32 #include <windows.h>
33 #endif
34
35 #ifdef __MINGW32__
36 #  include <time.h>
37 #  include <unistd.h>
38 #elif defined(_MSC_VER)
39 #   include <io.h>
40 #   include <time.h>
41 #   include <process.h>
42 #endif
43
44 #include <stdlib.h>             // atoi() atof() abs() system()
45 #include <signal.h>             // signal()
46 #include <string.h>
47
48 #include <iostream>
49 #include <fstream>
50 #include <string>
51 #include <map>
52
53 #include <simgear/compiler.h>
54
55 #include "terrasync.hxx"
56
57 #include <simgear/bucket/newbucket.hxx>
58 #include <simgear/misc/sg_path.hxx>
59 #include <simgear/misc/strutils.hxx>
60 #include <simgear/threads/SGQueue.hxx>
61 #include <simgear/misc/sg_dir.hxx>
62 #include <simgear/debug/BufferedLogCallback.hxx>
63 #include <simgear/props/props_io.hxx>
64 #include <simgear/io/HTTPClient.hxx>
65 #include <simgear/io/SVNRepository.hxx>
66 #include <simgear/structure/exception.hxx>
67
68 static const bool svn_built_in_available = true;
69
70 using namespace simgear;
71 using namespace std;
72
73 const char* rsync_cmd = 
74         "rsync --verbose --archive --delete --perms --owner --group";
75
76 const char* svn_options =
77         "checkout -q";
78
79 namespace UpdateInterval
80 {
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;
85 }
86
87 typedef map<string,time_t> CompletedTiles;
88
89 ///////////////////////////////////////////////////////////////////////////////
90 // helper functions ///////////////////////////////////////////////////////////
91 ///////////////////////////////////////////////////////////////////////////////
92 string stripPath(string path)
93 {
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();
97     while ((slen>0)&&
98             ((path[slen-1]=='/')||(path[slen-1]=='\\')))
99     {
100         slen--;
101     }
102     return path.substr(0,slen);
103 }
104
105 bool hasWhitespace(string path)
106 {
107     return path.find(' ')!=string::npos;
108 }
109
110 ///////////////////////////////////////////////////////////////////////////////
111 // SyncItem ////////////////////////////////////////////////////////////
112 ///////////////////////////////////////////////////////////////////////////////
113 class SyncItem
114 {
115 public:
116     enum Type
117     {
118         Stop = 0,   ///< special item indicating to stop the SVNThread
119         Tile,
120         AirportData,
121         SharedModels
122     };
123     
124     enum Status
125     {
126         Invalid = 0,
127         Waiting,
128         Cached, ///< using already cached result
129         Updated,
130         NotFound, 
131         Failed
132     };
133     
134     SyncItem() :
135         _dir(),
136         _type(Stop),
137         _status(Invalid)
138     {
139     }
140     
141     SyncItem(string dir, Type ty) :
142         _dir(dir),
143         _type(ty),
144         _status(Waiting)
145     {}
146         
147     string _dir;
148     Type _type;
149     Status _status;
150 };
151
152 ///////////////////////////////////////////////////////////////////////////////
153
154 /**
155  * @brief SyncSlot encapsulates a queue of sync items we will fetch
156  * serially. Multiple slots exist to sync different types of item in
157  * parallel.
158  */
159 class SyncSlot
160 {
161 public:
162     SyncSlot() :
163         isNewDirectory(false),
164         busy(false)
165     {}
166     
167     SyncItem currentItem;
168     bool isNewDirectory;
169     std::queue<SyncItem> queue;
170     std::auto_ptr<SVNRepository> repository;
171     SGTimeStamp stamp;
172     bool busy; ///< is the slot working or idle
173     
174     unsigned int nextWarnTimeout;
175 };
176
177 static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
178 static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
179 static const unsigned int NUM_SYNC_SLOTS = 2;
180
181 /**
182  * @brief translate a sync item type into one of the available slots.
183  * This provides the scheduling / balancing / prioritising between slots.
184  */
185 static unsigned int syncSlotForType(SyncItem::Type ty)
186 {
187     switch (ty) {
188     case SyncItem::Tile: return SYNC_SLOT_TILES;
189     case SyncItem::SharedModels:
190     case SyncItem::AirportData:
191         return SYNC_SLOT_SHARED_DATA;
192     default:
193         return SYNC_SLOT_SHARED_DATA;
194     }
195 }
196
197
198 ///////////////////////////////////////////////////////////////////////////////
199 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
200 ///////////////////////////////////////////////////////////////////////////////
201 class SGTerraSync::SvnThread : public SGThread
202 {
203 public:
204    SvnThread();
205    virtual ~SvnThread( ) { stop(); }
206
207    void stop();
208    bool start();
209
210     bool isIdle() {return !_busy; }
211    void request(const SyncItem& dir) {waitingTiles.push_front(dir);}
212    bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
213    bool hasNewTiles() { return !_freshTiles.empty();}
214    SyncItem getNewTile() { return _freshTiles.pop_front();}
215
216    void   setSvnServer(string server)       { _svn_server   = stripPath(server);}
217    void   setExtSvnUtility(string svn_util) { _svn_command  = simgear::strutils::strip(svn_util);}
218    void   setRsyncServer(string server)     { _rsync_server = simgear::strutils::strip(server);}
219    void   setLocalDir(string dir)           { _local_dir    = stripPath(dir);}
220    string getLocalDir()                     { return _local_dir;}
221    void   setUseSvn(bool use_svn)           { _use_svn = use_svn;}
222    void   setAllowedErrorCount(int errors)  {_allowed_errors = errors;}
223
224    void   setCachePath(const SGPath& p)     {_persistentCachePath = p;}
225    void   setCacheHits(unsigned int hits)   {_cache_hits = hits;}
226    void   setUseBuiltin(bool built_in) { _use_built_in = built_in;}
227
228    volatile bool _active;
229    volatile bool _running;
230    volatile bool _busy;
231    volatile bool _stalled;
232    volatile int  _fail_count;
233    volatile int  _updated_tile_count;
234    volatile int  _success_count;
235    volatile int  _consecutive_errors;
236    volatile int  _allowed_errors;
237    volatile int  _cache_hits;
238    volatile int _transfer_rate;
239 private:
240    virtual void run();
241     
242     // external model run and helpers
243     void runExternal();
244     void syncPathExternal(const SyncItem& next);
245     bool runExternalSyncCommand(const char* dir);
246
247     // internal mode run and helpers
248     void runInternal();
249     void updateSyncSlot(SyncSlot& slot);
250
251     // commond helpers between both internal and external models
252     
253     bool isPathCached(const SyncItem& next) const;
254     void initCompletedTilesPersistentCache();
255     void writeCompletedTilesPersistentCache() const;
256     void updated(SyncItem item, bool isNewDirectory);
257     void fail(SyncItem failedItem);
258     
259     bool _use_built_in;
260     HTTP::Client _http;
261     SyncSlot _syncSlots[NUM_SYNC_SLOTS];
262
263    volatile bool _is_dirty;
264    volatile bool _stop;
265    SGBlockingDeque <SyncItem> waitingTiles;
266    CompletedTiles _completedTiles;
267    SGBlockingDeque <SyncItem> _freshTiles;
268    bool _use_svn;
269    string _svn_server;
270    string _svn_command;
271    string _rsync_server;
272    string _local_dir;
273    SGPath _persistentCachePath;
274 };
275
276 SGTerraSync::SvnThread::SvnThread() :
277     _active(false),
278     _running(false),
279     _busy(false),
280     _stalled(false),
281     _fail_count(0),
282     _updated_tile_count(0),
283     _success_count(0),
284     _consecutive_errors(0),
285     _allowed_errors(6),
286     _cache_hits(0),
287     _transfer_rate(0),
288     _use_built_in(true),
289     _is_dirty(false),
290     _stop(false),
291     _use_svn(true)
292 {
293 }
294
295 void SGTerraSync::SvnThread::stop()
296 {
297     // drop any pending requests
298     waitingTiles.clear();
299
300     if (!_running)
301         return;
302
303     // set stop flag and wake up the thread with an empty request
304     _stop = true;
305     SyncItem w(string(), SyncItem::Stop);
306     request(w);
307     join();
308     _running = false;
309 }
310
311 bool SGTerraSync::SvnThread::start()
312 {
313     if (_running)
314         return false;
315
316     if (_local_dir=="")
317     {
318         SG_LOG(SG_TERRAIN,SG_ALERT,
319                "Cannot start scenery download. Local cache directory is undefined.");
320         _fail_count++;
321         _stalled = true;
322         return false;
323     }
324
325     SGPath path(_local_dir);
326     if (!path.exists())
327     {
328         SG_LOG(SG_TERRAIN,SG_ALERT,
329                "Cannot start scenery download. Directory '" << _local_dir <<
330                "' does not exist. Set correct directory path or create directory folder.");
331         _fail_count++;
332         _stalled = true;
333         return false;
334     }
335
336     path.append("version");
337     if (path.exists())
338     {
339         SG_LOG(SG_TERRAIN,SG_ALERT,
340                "Cannot start scenery download. Directory '" << _local_dir <<
341                "' contains the base package. Use a separate directory.");
342         _fail_count++;
343         _stalled = true;
344         return false;
345     }
346
347     _use_svn |= _use_built_in;
348
349     if ((_use_svn)&&(_svn_server==""))
350     {
351         SG_LOG(SG_TERRAIN,SG_ALERT,
352                "Cannot start scenery download. Subversion scenery server is undefined.");
353         _fail_count++;
354         _stalled = true;
355         return false;
356     }
357     if ((!_use_svn)&&(_rsync_server==""))
358     {
359         SG_LOG(SG_TERRAIN,SG_ALERT,
360                "Cannot start scenery download. Rsync scenery server is undefined.");
361         _fail_count++;
362         _stalled = true;
363         return false;
364     }
365
366     _fail_count = 0;
367     _updated_tile_count = 0;
368     _success_count = 0;
369     _consecutive_errors = 0;
370     _stop = false;
371     _stalled = false;
372     _running = true;
373
374     string status;
375
376     if (_use_svn && _use_built_in)
377         status = "Using built-in SVN support. ";
378     else if (_use_svn)
379     {
380         status = "Using external SVN utility '";
381         status += _svn_command;
382         status += "'. ";
383     }
384     else
385     {
386         status = "Using RSYNC. ";
387     }
388
389     // not really an alert - but we want to (always) see this message, so user is
390     // aware we're downloading scenery (and using bandwidth).
391     SG_LOG(SG_TERRAIN,SG_ALERT,
392            "Starting automatic scenery download/synchronization. "
393            << status
394            << "Directory: '" << _local_dir << "'.");
395
396     SGThread::start();
397     return true;
398 }
399
400 bool SGTerraSync::SvnThread::runExternalSyncCommand(const char* dir)
401 {
402     ostringstream buf;
403     SGPath localPath( _local_dir );
404     localPath.append( dir );
405
406     if (_use_svn)
407     {
408         buf << "\"" << _svn_command << "\" "
409             << svn_options << " "
410             << "\"" << _svn_server << "/" << dir << "\" "
411             << "\"" << localPath.str_native() << "\"";
412     } else {
413         buf << rsync_cmd << " "
414             << "\"" << _rsync_server << "/" << dir << "/\" "
415             << "\"" << localPath.str_native() << "/\"";
416     }
417
418     string command;
419 #ifdef SG_WINDOWS
420         // windows command line parsing is just lovely...
421         // to allow white spaces, the system call needs this:
422         // ""C:\Program Files\something.exe" somearg "some other arg""
423         // Note: whitespace strings quoted by a pair of "" _and_ the 
424         //       entire string needs to be wrapped by "" too.
425         // The svn url needs forward slashes (/) as a path separator while
426         // the local path needs windows-native backslash as a path separator.
427     command = "\"" + buf.str() + "\"";
428 #else
429     command = buf.str();
430 #endif
431     SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
432
433 #ifdef SG_WINDOWS
434     // tbd: does Windows support "popen"?
435     int rc = system( command.c_str() );
436 #else
437     FILE* pipe = popen( command.c_str(), "r");
438     int rc=-1;
439     // wait for external process to finish
440     if (pipe)
441         rc = pclose(pipe);
442 #endif
443
444     if (rc)
445     {
446         SG_LOG(SG_TERRAIN,SG_ALERT,
447                "Failed to synchronize directory '" << dir << "', " <<
448                "error code= " << rc);
449         return false;
450     }
451     return true;
452 }
453
454 void SGTerraSync::SvnThread::run()
455 {
456     _active = true;
457     initCompletedTilesPersistentCache();
458
459     
460     if (_use_built_in) {
461         runInternal();
462     } else {
463         runExternal();
464     }
465     
466     _active = false;
467     _running = false;
468     _is_dirty = true;
469 }
470
471 void SGTerraSync::SvnThread::runExternal()
472 {
473     while (!_stop)
474     {
475         SyncItem next = waitingTiles.pop_front();
476         if (_stop)
477            break;
478
479         if (isPathCached(next)) {
480             _cache_hits++;
481             SG_LOG(SG_TERRAIN, SG_DEBUG,
482                    "Cache hit for: '" << next._dir << "'");
483             next._status = SyncItem::Cached;
484             _freshTiles.push_back(next);
485             _is_dirty = true;
486             continue;
487         }
488         
489         syncPathExternal(next);
490
491         if ((_allowed_errors >= 0)&&
492             (_consecutive_errors >= _allowed_errors))
493         {
494             _stalled = true;
495             _stop = true;
496         }
497     } // of thread running loop
498 }
499
500 void SGTerraSync::SvnThread::syncPathExternal(const SyncItem& next)
501 {
502     _busy = true;
503     SGPath path( _local_dir );
504     path.append( next._dir );
505     bool isNewDirectory = !path.exists();
506     
507     try {
508         if (isNewDirectory) {
509             int rc = path.create_dir( 0755 );
510             if (rc) {
511                 SG_LOG(SG_TERRAIN,SG_ALERT,
512                        "Cannot create directory '" << path << "', return code = " << rc );
513                 throw sg_exception("Cannot create directory for terrasync", path.str());
514             }
515         }
516         
517         if (!runExternalSyncCommand(next._dir.c_str())) {
518             throw sg_exception("Running external sync command failed");
519         }
520     } catch (sg_exception& e) {
521         fail(next);
522         _busy = false;
523         return;
524     }
525     
526     updated(next, isNewDirectory);
527     _busy = false;
528 }
529
530 void SGTerraSync::SvnThread::updateSyncSlot(SyncSlot &slot)
531 {
532     if (slot.repository.get()) {
533         if (slot.repository->isDoingSync()) {
534 #if 0
535             if (slot.stamp.elapsedMSec() > slot.nextWarnTimeout) {
536                 SG_LOG(SG_TERRAIN, SG_INFO, "sync taking a long time:" << slot.currentItem._dir << " taken " << slot.stamp.elapsedMSec());
537                 SG_LOG(SG_TERRAIN, SG_INFO, "HTTP status:" << _http.hasActiveRequests());
538                 slot.nextWarnTimeout += 10000;
539             }
540 #endif
541             return; // easy, still working
542         }
543         
544         // check result
545         SVNRepository::ResultCode res = slot.repository->failure();
546         if (res == SVNRepository::SVN_ERROR_NOT_FOUND) {
547             // this is fine, but maybe we should use a different return code
548             // in the future to higher layers can distinguish this case
549             slot.currentItem._status = SyncItem::NotFound;
550             _freshTiles.push_back(slot.currentItem);
551             _is_dirty = true;
552         } else if (res != SVNRepository::SVN_NO_ERROR) {
553             fail(slot.currentItem);
554         } else {
555             updated(slot.currentItem, slot.isNewDirectory);
556             SG_LOG(SG_TERRAIN, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " finished ("
557                    << slot.stamp.elapsedMSec() << " msec");
558         }
559
560         // whatever happened, we're done with this repository instance
561         slot.busy = false;
562         slot.repository.reset();
563     }
564     
565     // init and start sync of the next repository
566     if (!slot.queue.empty()) {
567         slot.currentItem = slot.queue.front();
568         slot.queue.pop();
569         
570         SGPath path(_local_dir);
571         path.append(slot.currentItem._dir);
572         slot.isNewDirectory = !path.exists();
573         if (slot.isNewDirectory) {
574             int rc = path.create_dir( 0755 );
575             if (rc) {
576                 SG_LOG(SG_TERRAIN,SG_ALERT,
577                        "Cannot create directory '" << path << "', return code = " << rc );
578                 fail(slot.currentItem);
579                 return;
580             }
581         } // of creating directory step
582         
583         slot.repository.reset(new SVNRepository(path, &_http));
584         slot.repository->setBaseUrl(_svn_server + "/" + slot.currentItem._dir);
585         slot.repository->update();
586         
587         slot.nextWarnTimeout = 20000;
588         slot.stamp.stamp();
589         slot.busy = true;
590         SG_LOG(SG_TERRAIN, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " started, queue size is " << slot.queue.size());
591     }
592 }
593
594 void SGTerraSync::SvnThread::runInternal()
595 {
596     while (!_stop) {
597         _http.update(100);
598         _transfer_rate = _http.transferRateBytesPerSec();
599         if (_stop)
600             break;
601  
602         // drain the waiting tiles queue into the sync slot queues.
603         while (!waitingTiles.empty()) {
604             SyncItem next = waitingTiles.pop_front();
605             if (isPathCached(next)) {
606                 _cache_hits++;
607                 SG_LOG(SG_TERRAIN, SG_DEBUG, "TerraSync Cache hit for: '" << next._dir << "'");
608                 next._status = SyncItem::Cached;
609                 _freshTiles.push_back(next);
610                 _is_dirty = true;
611                 continue;
612             }
613
614             unsigned int slot = syncSlotForType(next._type);
615             _syncSlots[slot].queue.push(next);
616         }
617        
618         bool anySlotBusy = false;
619         // update each sync slot in turn
620         for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
621             updateSyncSlot(_syncSlots[slot]);
622             anySlotBusy |= _syncSlots[slot].busy;
623         }
624
625         _busy = anySlotBusy;
626     } // of thread running loop
627 }
628
629 bool SGTerraSync::SvnThread::isPathCached(const SyncItem& next) const
630 {
631     CompletedTiles::const_iterator ii = _completedTiles.find( next._dir );
632     if (ii == _completedTiles.end()) {
633         return false;
634     }
635     
636     // check if the path still physically exists
637     SGPath p(_local_dir);
638     p.append(next._dir);
639     if (!p.exists()) {
640         return false;
641     }
642     
643     time_t now = time(0);
644     return (ii->second > now);
645 }
646
647 void SGTerraSync::SvnThread::fail(SyncItem failedItem)
648 {
649     time_t now = time(0);
650     _consecutive_errors++;
651     _fail_count++;
652     failedItem._status = SyncItem::Failed;
653     _freshTiles.push_back(failedItem);
654     _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
655     _is_dirty = true;
656 }
657
658 void SGTerraSync::SvnThread::updated(SyncItem item, bool isNewDirectory)
659 {
660     time_t now = time(0);
661     _consecutive_errors = 0;
662     _success_count++;
663     SG_LOG(SG_TERRAIN,SG_INFO,
664            "Successfully synchronized directory '" << item._dir << "'");
665     
666     item._status = SyncItem::Updated;
667     if (item._type == SyncItem::Tile) {
668         _updated_tile_count++;
669     }
670
671     _freshTiles.push_back(item);
672     _completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
673     _is_dirty = true;
674     writeCompletedTilesPersistentCache();
675 }
676
677 void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
678 {
679     if (!_persistentCachePath.exists()) {
680         return;
681     }
682     
683     SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
684     time_t now = time(0);
685     
686     try {
687         readProperties(_persistentCachePath.str(), cacheRoot);
688     } catch (sg_exception& e) {
689         SG_LOG(SG_TERRAIN, SG_INFO, "corrupted persistent cache, discarding");
690         return;
691     }
692     
693     for (int i=0; i<cacheRoot->nChildren(); ++i) {
694         SGPropertyNode* entry = cacheRoot->getChild(i);
695         string tileName = entry->getStringValue("path");
696         time_t stamp = entry->getIntValue("stamp");
697         if (stamp < now) {
698             continue;
699         }
700         
701         _completedTiles[tileName] = stamp;
702     }
703 }
704
705 void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
706 {
707     // cache is disabled
708     if (_persistentCachePath.isNull()) {
709         return;
710     }
711     
712     std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
713     if (!f.is_open()) {
714         return;
715     }
716     
717     SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
718     CompletedTiles::const_iterator it = _completedTiles.begin();
719     for (; it != _completedTiles.end(); ++it) {
720         SGPropertyNode* entry = cacheRoot->addChild("entry");
721         entry->setStringValue("path", it->first);
722         entry->setIntValue("stamp", it->second);
723     }
724     
725     writeProperties(f, cacheRoot, true /* write_all */);
726     f.close();
727 }
728
729 ///////////////////////////////////////////////////////////////////////////////
730 // SGTerraSync ////////////////////////////////////////////////////////////////
731 ///////////////////////////////////////////////////////////////////////////////
732 SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
733     _svnThread(NULL),
734     last_lat(NOWHERE),
735     last_lon(NOWHERE),
736     _terraRoot(root->getNode("/sim/terrasync",true)),
737     _bound(false),
738     _inited(false)
739 {
740     _svnThread = new SvnThread();
741     _log = new BufferedLogCallback(SG_TERRAIN, SG_INFO);
742     _log->truncateAt(255);
743     
744     sglog().addCallback(_log);
745 }
746
747 SGTerraSync::~SGTerraSync()
748 {
749     _tiedProperties.Untie();
750     delete _svnThread;
751     _svnThread = NULL;
752     sglog().removeCallback(_log);
753     delete _log;
754 }
755
756 void SGTerraSync::init()
757 {
758     if (_inited) {
759         return;
760     }
761     
762     _inited = true;
763     _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available);
764         
765     reinit();
766 }
767
768 void SGTerraSync::reinit()
769 {
770     // do not reinit when enabled and we're already up and running
771     if ((_terraRoot->getBoolValue("enabled",false))&&
772          (_svnThread->_active && _svnThread->_running))
773     {
774         return;
775     }
776     
777     _svnThread->stop();
778
779     if (_terraRoot->getBoolValue("enabled",false))
780     {
781         _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
782         _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
783         _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
784         _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5));
785         _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
786         _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
787         _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
788         _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
789         _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
790
791         if (_svnThread->start())
792         {
793             syncAirportsModels();
794             if (last_lat != NOWHERE && last_lon != NOWHERE)
795             {
796                 // reschedule most recent position
797                 int lat = last_lat;
798                 int lon = last_lon;
799                 last_lat = NOWHERE;
800                 last_lon = NOWHERE;
801                 schedulePosition(lat, lon);
802             }
803         }
804     }
805
806     _stalledNode->setBoolValue(_svnThread->_stalled);
807 }
808
809 void SGTerraSync::bind()
810 {
811     if (_bound) {
812         return;
813     }
814     
815     _bound = true;
816     _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
817     _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
818     _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
819     _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
820     _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
821     _tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_svnThread->_cache_hits );
822     _tiedProperties.Tie( _terraRoot->getNode("transfer-rate-bytes-sec", true), (int*) &_svnThread->_transfer_rate );
823     
824     _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
825     _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
826     _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
827     _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
828     _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
829     _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
830     _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
831     // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
832     _stalledNode = _terraRoot->getNode("stalled", true);
833     _stalledNode->setBoolValue(_svnThread->_stalled);
834     _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
835 }
836
837 void SGTerraSync::unbind()
838 {
839     _svnThread->stop();
840     _tiedProperties.Untie();
841     _bound = false;
842     _inited = false;
843 }
844
845 void SGTerraSync::update(double)
846 {
847     static SGBucket bucket;
848     if (_svnThread->isDirty())
849     {
850         if (!_svnThread->_active)
851         {
852             if (_svnThread->_stalled)
853             {
854                 SG_LOG(SG_TERRAIN,SG_ALERT,
855                        "Automatic scenery download/synchronization stalled. Too many errors.");
856             }
857             else
858             {
859                 // not really an alert - just always show this message
860                 SG_LOG(SG_TERRAIN,SG_ALERT,
861                         "Automatic scenery download/synchronization has stopped.");
862             }
863             _stalledNode->setBoolValue(_svnThread->_stalled);
864         }
865
866         while (_svnThread->hasNewTiles())
867         {
868             SyncItem next = _svnThread->getNewTile();
869             
870             if (next._type == SyncItem::Tile) {
871                 if (_activeTileDirs.find(next._dir) == _activeTileDirs.end()) {
872                     SG_LOG(SG_TERRAIN, SG_INFO, "TTTTTTTT: finished tile not found in active set!: " << next._dir);
873                 }
874                 
875                 _activeTileDirs.erase(next._dir);
876             }
877         } // of freshly synced items
878     }
879 }
880
881 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
882
883 void SGTerraSync::syncAirportsModels()
884 {
885     static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
886     // note "request" method uses LIFO order, i.e. processes most recent request first
887     for( unsigned i = 0; i < strlen(bounds)/2; i++ )
888     {
889         for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
890         {
891             ostringstream dir;
892             dir << "Airports/" << synced_other;
893             SyncItem w(dir.str(), SyncItem::AirportData);
894             _svnThread->request( w );
895         }
896     }
897     
898     SyncItem w("Models", SyncItem::SharedModels);
899     _svnThread->request( w );
900 }
901
902
903 void SGTerraSync::syncArea( int lat, int lon )
904 {
905     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
906         return;
907     char NS, EW;
908     int baselat, baselon;
909
910     if ( lat < 0 ) {
911         int base = (int)(lat / 10);
912         if ( lat == base * 10 ) {
913             baselat = base * 10;
914         } else {
915             baselat = (base - 1) * 10;
916         }
917         NS = 's';
918     } else {
919         baselat = (int)(lat / 10) * 10;
920         NS = 'n';
921     }
922     if ( lon < 0 ) {
923         int base = (int)(lon / 10);
924         if ( lon == base * 10 ) {
925             baselon = base * 10;
926         } else {
927             baselon = (base - 1) * 10;
928         }
929         EW = 'w';
930     } else {
931         baselon = (int)(lon / 10) * 10;
932         EW = 'e';
933     }
934
935     ostringstream dir;
936     dir << setfill('0')
937     << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
938     << EW << setw(3) << abs(lon)     << NS << setw(2) << abs(lat);
939     
940     syncAreaByPath(dir.str());
941 }
942
943 void SGTerraSync::syncAreaByPath(const std::string& aPath)
944 {
945     const char* terrainobjects[3] = { "Terrain/", "Objects/",  0 };
946     for (const char** tree = &terrainobjects[0]; *tree; tree++)
947     {
948         std::string dir = string(*tree) + aPath;
949         if (_activeTileDirs.find(dir) != _activeTileDirs.end()) {
950             continue;
951         }
952                 
953         _activeTileDirs.insert(dir);
954         SyncItem w(dir, SyncItem::Tile);
955         _svnThread->request( w );
956     }
957 }
958
959
960 void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
961 {
962     if ( lat_dir == 0 && lon_dir == 0 ) {
963         
964         // do surrounding 8 1x1 degree areas.
965         for ( int i = lat - 1; i <= lat + 1; ++i ) {
966             for ( int j = lon - 1; j <= lon + 1; ++j ) {
967                 if ( i != lat || j != lon ) {
968                     syncArea( i, j );
969                 }
970             }
971         }
972     } else {
973         if ( lat_dir != 0 ) {
974             syncArea( lat + lat_dir, lon - 1 );
975             syncArea( lat + lat_dir, lon + 1 );
976             syncArea( lat + lat_dir, lon );
977         }
978         if ( lon_dir != 0 ) {
979             syncArea( lat - 1, lon + lon_dir );
980             syncArea( lat + 1, lon + lon_dir );
981             syncArea( lat, lon + lon_dir );
982         }
983     }
984
985     // do current 1x1 degree area first
986     syncArea( lat, lon );
987 }
988
989 bool SGTerraSync::scheduleTile(const SGBucket& bucket)
990 {
991     std::string basePath = bucket.gen_base_path();
992     syncAreaByPath(basePath);
993     return true;
994 }
995
996 bool SGTerraSync::schedulePosition(int lat, int lon)
997 {
998     bool Ok = false;
999
1000     // Ignore messages where the location does not change
1001     if ( lat != last_lat || lon != last_lon )
1002     {
1003         if (_svnThread->_running)
1004         {
1005             int lat_dir=0;
1006             int lon_dir=0;
1007             if ( last_lat != NOWHERE && last_lon != NOWHERE )
1008             {
1009                 int dist = lat - last_lat;
1010                 if ( dist != 0 )
1011                 {
1012                     lat_dir = dist / abs(dist);
1013                 }
1014                 else
1015                 {
1016                     lat_dir = 0;
1017                 }
1018                 dist = lon - last_lon;
1019                 if ( dist != 0 )
1020                 {
1021                     lon_dir = dist / abs(dist);
1022                 } else
1023                 {
1024                     lon_dir = 0;
1025                 }
1026             }
1027
1028             SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " <<
1029                    "lat = " << lat << ", lon = " << lon <<
1030                    ", lat_dir = " << lat_dir << ",  " <<
1031                    "lon_dir = " << lon_dir);
1032
1033             syncAreas( lat, lon, lat_dir, lon_dir );
1034             Ok = true;
1035         }
1036         last_lat = lat;
1037         last_lon = lon;
1038     }
1039
1040     return Ok;
1041 }
1042
1043 void SGTerraSync::reposition()
1044 {
1045     last_lat = last_lon = NOWHERE;
1046 }
1047
1048 bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
1049 {
1050     if (!_svnThread->_running) {
1051         return false;
1052     }
1053     
1054     const char* terrainobjects[3] = { "Terrain/", "Objects/",  0 };
1055     for (const char** tree = &terrainobjects[0]; *tree; tree++) {
1056         string s = *tree + sceneryDir;
1057         if (_activeTileDirs.find(s) != _activeTileDirs.end()) {
1058             return true;
1059         }
1060     }
1061
1062     return false;
1063 }
1064