]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tsync/terrasync.cxx
hla: Provide a directly property based api for property data element.
[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 #   ifndef HAVE_SVN_CLIENT_H
41 #       include <time.h>
42 #       include <process.h>
43 #   endif
44 #endif
45
46 #include <stdlib.h>             // atoi() atof() abs() system()
47 #include <signal.h>             // signal()
48
49 #include <iostream>
50 #include <fstream>
51 #include <string>
52 #include <map>
53
54 #include <simgear/compiler.h>
55
56 #include "terrasync.hxx"
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/scene/tgdb/TileCache.hxx>
62 #include <simgear/misc/sg_dir.hxx>
63 #include <OpenThreads/Thread>
64
65 #ifdef HAVE_SVN_CLIENT_H
66 #  ifdef HAVE_LIBSVN_CLIENT_1
67 #    include <svn_version.h>
68 #    include <svn_auth.h>
69 #    include <svn_client.h>
70 #    include <svn_cmdline.h>
71 #    include <svn_pools.h>
72 #  else
73 #    undef HAVE_SVN_CLIENT_H
74 #  endif
75 #endif
76
77 #ifdef HAVE_SVN_CLIENT_H
78     static const svn_version_checklist_t mysvn_checklist[] = {
79         { "svn_subr",   svn_subr_version },
80         { "svn_client", svn_client_version },
81         { NULL, NULL }
82     };
83     static const bool svn_built_in_available = true;
84 #else
85     static const bool svn_built_in_available = false;
86 #endif
87
88 using namespace simgear;
89
90 const char* rsync_cmd = 
91         "rsync --verbose --archive --delete --perms --owner --group";
92
93 const char* svn_options =
94         "checkout -q";
95
96 namespace UpdateInterval
97 {
98     // interval in seconds to allow an update to repeat after a successful update (=daily)
99     static const double SuccessfulAttempt = 24*60*60;
100     // interval in seconds to allow another update after a failed attempt (10 minutes)
101     static const double FailedAttempt     = 10*60;
102 };
103
104 typedef map<string,time_t> CompletedTiles;
105
106 ///////////////////////////////////////////////////////////////////////////////
107 // helper functions ///////////////////////////////////////////////////////////
108 ///////////////////////////////////////////////////////////////////////////////
109 string stripPath(string path)
110 {
111     // svn doesn't like trailing white-spaces or path separators - strip them!
112     path = simgear::strutils::strip(path);
113     int slen = path.length();
114     while ((slen>0)&&
115             ((path[slen-1]=='/')||(path[slen-1]=='\\')))
116     {
117         slen--;
118     }
119     return path.substr(0,slen);
120 }
121
122 bool hasWhitespace(string path)
123 {
124     return path.find(' ')!=string::npos;
125 }
126
127 ///////////////////////////////////////////////////////////////////////////////
128 // WaitingTile ////////////////////////////////////////////////////////////////
129 ///////////////////////////////////////////////////////////////////////////////
130 class  WaitingTile
131 {
132 public:
133     WaitingTile(string dir,bool refresh) :
134         _dir(dir), _refreshScenery(refresh) {}
135     string _dir;
136     bool _refreshScenery;
137 };
138
139 ///////////////////////////////////////////////////////////////////////////////
140 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
141 ///////////////////////////////////////////////////////////////////////////////
142 class SGTerraSync::SvnThread : public OpenThreads::Thread
143 {
144 public:
145    SvnThread();
146    virtual ~SvnThread( ) { stop(); }
147
148    void stop();
149    bool start();
150
151    bool isIdle() {return waitingTiles.empty();}
152    void request(const WaitingTile& dir) {waitingTiles.push_front(dir);}
153    bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
154    bool hasNewTiles() { return !_freshTiles.empty();}
155    WaitingTile getNewTile() { return _freshTiles.pop_front();}
156
157    void   setSvnServer(string server)       { _svn_server   = stripPath(server);}
158    void   setExtSvnUtility(string svn_util) { _svn_command  = simgear::strutils::strip(svn_util);}
159    void   setRsyncServer(string server)     { _rsync_server = simgear::strutils::strip(server);}
160    void   setLocalDir(string dir)           { _local_dir    = stripPath(dir);}
161    string getLocalDir()                     { return _local_dir;}
162    void   setUseSvn(bool use_svn)           { _use_svn = use_svn;}
163
164 #ifdef HAVE_SVN_CLIENT_H
165    void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
166 #endif
167
168    volatile bool _active;
169    volatile bool _running;
170    volatile bool _busy;
171    volatile bool _stalled;
172    volatile int  _fail_count;
173    volatile int  _updated_tile_count;
174    volatile int  _success_count;
175    volatile int  _consecutive_errors;
176
177 private:
178    virtual void run();
179    bool syncTree(const char* dir, bool& isNewDirectory);
180    bool syncTreeExternal(const char* dir);
181
182 #ifdef HAVE_SVN_CLIENT_H
183    static int svnClientSetup(void);
184    bool syncTreeInternal(const char* dir);
185
186    bool _use_built_in;
187
188    // Things we need for doing subversion checkout - often
189    static apr_pool_t *_svn_pool;
190    static svn_client_ctx_t *_svn_ctx;
191    static svn_opt_revision_t *_svn_rev;
192    static svn_opt_revision_t *_svn_rev_peg;
193 #endif
194
195    volatile bool _is_dirty;
196    volatile bool _stop;
197    SGBlockingDeque <WaitingTile> waitingTiles;
198    CompletedTiles _completedTiles;
199    SGBlockingDeque <WaitingTile> _freshTiles;
200    bool _use_svn;
201    string _svn_server;
202    string _svn_command;
203    string _rsync_server;
204    string _local_dir;
205 };
206
207 #ifdef HAVE_SVN_CLIENT_H
208     apr_pool_t* SGTerraSync::SvnThread::_svn_pool = NULL;
209     svn_client_ctx_t* SGTerraSync::SvnThread::_svn_ctx = NULL;
210     svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev = NULL;
211     svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev_peg = NULL;
212 #endif
213
214 SGTerraSync::SvnThread::SvnThread() :
215     _active(false),
216     _running(false),
217     _busy(false),
218     _stalled(false),
219     _fail_count(0),
220     _updated_tile_count(0),
221     _success_count(0),
222     _consecutive_errors(0),
223 #ifdef HAVE_SVN_CLIENT_H
224     _use_built_in(true),
225 #endif
226     _is_dirty(false),
227     _stop(false),
228     _use_svn(true)
229 {
230 #ifdef HAVE_SVN_CLIENT_H
231     int errCode = SGTerraSync::SvnThread::svnClientSetup();
232     if (errCode != EXIT_SUCCESS)
233     {
234         SG_LOG(SG_TERRAIN,SG_ALERT,
235                "Failed to initialize built-in SVN client, error = " << errCode);
236     }
237 #endif
238 }
239
240 void SGTerraSync::SvnThread::stop()
241 {
242     // drop any pending requests
243     waitingTiles.clear();
244
245     if (!_running)
246         return;
247
248     // set stop flag and wake up the thread with an empty request
249     _stop = true;
250     WaitingTile w("",false);
251     request(w);
252     join();
253     _running = false;
254 }
255
256 bool SGTerraSync::SvnThread::start()
257 {
258     if (_running)
259         return false;
260
261     if (_local_dir=="")
262     {
263         SG_LOG(SG_TERRAIN,SG_ALERT,
264                "Cannot start scenery download. Local cache directory is undefined.");
265         _fail_count++;
266         _stalled = true;
267         return false;
268     }
269
270     SGPath path(_local_dir);
271     if (!path.exists())
272     {
273         SG_LOG(SG_TERRAIN,SG_ALERT,
274                "Cannot start scenery download. Directory '" << _local_dir <<
275                "' does not exist. Set correct directory path or create directory folder.");
276         _fail_count++;
277         _stalled = true;
278         return false;
279     }
280
281     path.append("version");
282     if (path.exists())
283     {
284         SG_LOG(SG_TERRAIN,SG_ALERT,
285                "Cannot start scenery download. Directory '" << _local_dir <<
286                "' contains the base package. Use a separate directory.");
287         _fail_count++;
288         _stalled = true;
289         return false;
290     }
291
292 #ifdef HAVE_SVN_CLIENT_H
293     _use_svn |= _use_built_in;
294 #endif
295
296     if ((_use_svn)&&(_svn_server==""))
297     {
298         SG_LOG(SG_TERRAIN,SG_ALERT,
299                "Cannot start scenery download. Subversion scenery server is undefined.");
300         _fail_count++;
301         _stalled = true;
302         return false;
303     }
304     if ((!_use_svn)&&(_rsync_server==""))
305     {
306         SG_LOG(SG_TERRAIN,SG_ALERT,
307                "Cannot start scenery download. Rsync scenery server is undefined.");
308         _fail_count++;
309         _stalled = true;
310         return false;
311     }
312
313     _fail_count = 0;
314     _updated_tile_count = 0;
315     _success_count = 0;
316     _consecutive_errors = 0;
317     _stop = false;
318     _stalled = false;
319     _running = true;
320
321     string status;
322 #ifdef HAVE_SVN_CLIENT_H
323     if (_use_svn && _use_built_in)
324         status = "Using built-in SVN support. ";
325     else
326 #endif
327     if (_use_svn)
328     {
329         status = "Using external SVN utility '";
330         status += _svn_command;
331         status += "'. ";
332     }
333     else
334     {
335         status = "Using RSYNC. ";
336     }
337
338     // not really an alert - but we want to (always) see this message, so user is
339     // aware we're downloading scenery (and using bandwidth).
340     SG_LOG(SG_TERRAIN,SG_ALERT,
341            "Starting automatic scenery download/synchronization. "
342            << status
343            << "Directory: '" << _local_dir << "'.");
344
345     OpenThreads::Thread::start();
346     return true;
347 }
348
349 // sync one directory tree
350 bool SGTerraSync::SvnThread::syncTree(const char* dir, bool& isNewDirectory)
351 {
352     int rc;
353     SGPath path( _local_dir );
354
355     path.append( dir );
356     isNewDirectory = !path.exists();
357     if (isNewDirectory)
358     {
359         rc = path.create_dir( 0755 );
360         if (rc)
361         {
362             SG_LOG(SG_TERRAIN,SG_ALERT,
363                    "Cannot create directory '" << dir << "', return code = " << rc );
364             return false;
365         }
366     }
367
368 #ifdef HAVE_SVN_CLIENT_H
369     if (_use_built_in)
370         return syncTreeInternal(dir);
371     else
372 #endif
373     {
374         return syncTreeExternal(dir);
375     }
376 }
377
378
379 #ifdef HAVE_SVN_CLIENT_H
380 bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
381 {
382     SG_LOG(SG_TERRAIN,SG_DEBUG, "Synchronizing scenery directory " << dir);
383     if (!_svn_pool)
384     {
385         SG_LOG(SG_TERRAIN,SG_ALERT,
386                "Built-in SVN client failed to initialize.");
387         return false;
388     }
389
390     ostringstream command;
391     command << _svn_server << "/" << dir;
392
393     ostringstream dest_base_dir;
394     dest_base_dir << _local_dir << "/" << dir;
395
396     apr_pool_t *subpool = svn_pool_create(_svn_pool);
397
398     svn_error_t *err = NULL;
399 #if (SVN_VER_MINOR >= 5)
400     err = svn_client_checkout3(NULL,
401             command.str().c_str(),
402             dest_base_dir.str().c_str(),
403             _svn_rev_peg,
404             _svn_rev,
405             svn_depth_infinity,
406             0, // ignore-externals = false
407             0, // allow unver obstructions = false
408             _svn_ctx,
409             subpool);
410 #else
411     // version 1.4 API
412     err = svn_client_checkout2(NULL,
413             command.str().c_str(),
414             dest_base_dir.str().c_str(),
415             _svn_rev_peg,
416             _svn_rev,
417             1, // recurse=true - same as svn_depth_infinity for checkout3 above
418             0, // ignore externals = false
419             _svn_ctx,
420             subpool);
421 #endif
422
423     bool ReturnValue = true;
424     if (err)
425     {
426         // Report errors from the checkout attempt
427         if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
428         {
429             // ignore errors when remote path doesn't exist (no scenery data for ocean areas)
430         }
431         else
432         {
433             SG_LOG(SG_TERRAIN,SG_ALERT,
434                     "Failed to synchronize directory '" << dir << "', " <<
435                     err->message << " (code " << err->apr_err << ").");
436             svn_error_clear(err);
437             // try to clean up
438             err = svn_client_cleanup(dest_base_dir.str().c_str(),
439                     _svn_ctx,subpool);
440             if (!err)
441             {
442                 SG_LOG(SG_TERRAIN,SG_ALERT,
443                        "SVN repository cleanup successful for '" << dir << "'.");
444             }
445             ReturnValue = false;
446         }
447     } else
448     {
449         SG_LOG(SG_TERRAIN,SG_DEBUG, "Done with scenery directory " << dir);
450     }
451     svn_pool_destroy(subpool);
452     return ReturnValue;
453 }
454 #endif
455
456 bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
457 {
458     ostringstream buf;
459     SGPath localPath( _local_dir );
460     localPath.append( dir );
461
462     if (_use_svn)
463     {
464         buf << "\"" << _svn_command << "\" "
465             << svn_options << " "
466             << "\"" << _svn_server << "/" << dir << "\" "
467             << "\"" << localPath.str_native() << "\"";
468     } else {
469         buf << rsync_cmd << " "
470             << "\"" << _rsync_server << "/" << dir << "/\" "
471             << "\"" << localPath.str_native() << "/\"";
472     }
473
474     string command;
475 #ifdef SG_WINDOWS
476         // windows command line parsing is just lovely...
477         // to allow white spaces, the system call needs this:
478         // ""C:\Program Files\something.exe" somearg "some other arg""
479         // Note: whitespace strings quoted by a pair of "" _and_ the 
480         //       entire string needs to be wrapped by "" too.
481         // The svn url needs forward slashes (/) as a path separator while
482         // the local path needs windows-native backslash as a path separator.
483     command = "\"" + buf.str() + "\"";
484 #else
485     command = buf.str();
486 #endif
487     SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
488     int rc = system( command.c_str() );
489     if (rc)
490     {
491         SG_LOG(SG_TERRAIN,SG_ALERT,
492                "Failed to synchronize directory '" << dir << "', " <<
493                "error code= " << rc);
494         return false;
495     }
496     return true;
497 }
498
499 void SGTerraSync::SvnThread::run()
500 {
501     _active = true;
502     while (!_stop)
503     {
504         WaitingTile next = waitingTiles.pop_front();
505         if (_stop)
506            break;
507
508         CompletedTiles::iterator ii =
509             _completedTiles.find( next._dir );
510         time_t now = time(0);
511         if ((ii == _completedTiles.end())||
512             (ii->second < now ))
513         {
514             bool isNewDirectory = false;
515
516             _busy = true;
517             if (!syncTree(next._dir.c_str(),isNewDirectory))
518             {
519                 _consecutive_errors++;
520                 _fail_count++;
521                 _completedTiles[ next._dir ] = now + UpdateInterval::FailedAttempt;
522             }
523             else
524             {
525                 _consecutive_errors = 0;
526                 _success_count++;
527                 SG_LOG(SG_TERRAIN,SG_INFO,
528                        "Successfully synchronized directory '" << next._dir << "'");
529                 if (next._refreshScenery)
530                 {
531                     // updated a tile
532                     _updated_tile_count++;
533                     if (isNewDirectory)
534                     {
535                         // for now only report new directories to refresh display
536                         // (i.e. only when ocean needs to be replaced with actual data)
537                         _freshTiles.push_back(next);
538                         _is_dirty = true;
539                     }
540                 }
541                 _completedTiles[ next._dir ] = now + UpdateInterval::SuccessfulAttempt;
542             }
543             _busy = false;
544         }
545
546         if (_consecutive_errors >= 5)
547         {
548             _stalled = true;
549             _stop = true;
550         }
551     }
552
553     _active = false;
554     _running = false;
555     _is_dirty = true;
556 }
557
558 #ifdef HAVE_SVN_CLIENT_H
559 // Configure our subversion session
560 int SGTerraSync::SvnThread::svnClientSetup(void)
561 {
562     // Are we already prepared?
563     if (_svn_pool) return EXIT_SUCCESS;
564     // No, so initialize svn internals generally
565
566 #ifdef _MSC_VER
567     // there is a segfault when providing an error stream.
568     //  Apparently, calling setvbuf with a nul buffer is
569     //  not supported under msvc 7.1 ( code inside svn_cmdline_init )
570     if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS)
571         return EXIT_FAILURE;
572
573     // revert locale setting
574     setlocale(LC_ALL,"C");
575 #else
576     /* svn_cmdline_init configures the locale. Setup environment to ensure the
577      * default "C" locale remains active, since fgfs isn't locale aware - especially
578      * requires "." as decimal point in strings containing floating point varibales. */
579     setenv("LC_ALL", "C", 1);
580
581     if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS)
582         return EXIT_FAILURE;
583 #endif
584
585     apr_pool_t *pool;
586     apr_pool_create(&pool, NULL);
587     svn_error_t *err = NULL;
588     SVN_VERSION_DEFINE(_svn_version);
589     err = svn_ver_check_list(&_svn_version, mysvn_checklist);
590     if (err)
591         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
592     err = svn_ra_initialize(pool);
593     if (err)
594         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
595     char *config_dir = NULL;
596     err = svn_config_ensure(config_dir, pool);
597     if (err)
598         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
599     err = svn_client_create_context(&_svn_ctx, pool);
600     if (err)
601         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
602     err = svn_config_get_config(&(_svn_ctx->config),
603         config_dir, pool);
604     if (err)
605         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
606     svn_config_t *cfg;
607     cfg = ( svn_config_t*) apr_hash_get(
608         _svn_ctx->config,
609         SVN_CONFIG_CATEGORY_CONFIG,
610         APR_HASH_KEY_STRING);
611     if (err)
612         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
613
614     svn_auth_baton_t *ab=NULL;
615
616 #if (SVN_VER_MINOR >= 6)
617     err = svn_cmdline_create_auth_baton  (&ab,
618             TRUE, NULL, NULL, config_dir, TRUE, FALSE, cfg,
619             _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
620 #else
621     err = svn_cmdline_setup_auth_baton(&ab,
622         TRUE, NULL, NULL, config_dir, TRUE, cfg,
623         _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
624 #endif
625
626     if (err)
627         return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
628     
629     _svn_ctx->auth_baton = ab;
630 #if (SVN_VER_MINOR >= 5)
631     _svn_ctx->conflict_func = NULL;
632     _svn_ctx->conflict_baton = NULL;
633 #endif
634
635     // Now our magic revisions
636     _svn_rev = (svn_opt_revision_t*) apr_palloc(pool, 
637         sizeof(svn_opt_revision_t));
638     if (!_svn_rev)
639         return EXIT_FAILURE;
640     _svn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool, 
641         sizeof(svn_opt_revision_t));
642     if (!_svn_rev_peg)
643         return EXIT_FAILURE;
644     _svn_rev->kind = svn_opt_revision_head;
645     _svn_rev_peg->kind = svn_opt_revision_unspecified;
646     // Success if we got this far
647     _svn_pool = pool;
648     return EXIT_SUCCESS;
649 }
650 #endif
651
652 ///////////////////////////////////////////////////////////////////////////////
653 // SGTerraSync ////////////////////////////////////////////////////////////////
654 ///////////////////////////////////////////////////////////////////////////////
655 SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
656     _svnThread(NULL),
657     last_lat(NOWHERE),
658     last_lon(NOWHERE),
659     _terraRoot(root->getNode("/sim/terrasync",true)),
660     _tile_cache(NULL)
661 {
662     _svnThread = new SvnThread();
663 }
664
665 SGTerraSync::~SGTerraSync()
666 {
667     _tiedProperties.Untie();
668     delete _svnThread;
669     _svnThread = NULL;
670 }
671
672 void SGTerraSync::init()
673 {
674     _refresh_display = _terraRoot->getNode("refresh-display",true);
675     _terraRoot->getNode("built-in-svn-available",true)->setBoolValue(svn_built_in_available);
676     reinit();
677 }
678
679 void SGTerraSync::reinit()
680 {
681     // do not reinit when enabled and we're already up and running
682     if ((_terraRoot->getBoolValue("enabled",false))&&
683          (_svnThread->_active && _svnThread->_running))
684         return;
685
686     _svnThread->stop();
687
688     if (_terraRoot->getBoolValue("enabled",false))
689     {
690         _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
691         _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
692         _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
693
694     #ifdef HAVE_SVN_CLIENT_H
695         _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
696     #else
697         _terraRoot->getNode("use-built-in-svn",true)->setBoolValue(false);
698     #endif
699         _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
700         _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
701
702         if (_svnThread->start())
703             syncAirportsModels();
704     }
705
706     _stalled_node->setBoolValue(_svnThread->_stalled);
707     last_lat = NOWHERE;
708     last_lon = NOWHERE;
709 }
710
711 void SGTerraSync::bind()
712 {
713     _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
714     _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
715     _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
716     _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
717     _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
718     _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
719     _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
720     _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
721     _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
722     _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
723     _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
724     _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
725     // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
726     _stalled_node = _terraRoot->getNode("stalled", true);
727     _stalled_node->setBoolValue(_svnThread->_stalled);
728     _stalled_node->setAttribute(SGPropertyNode::PRESERVE,true);
729 }
730
731 void SGTerraSync::unbind()
732 {
733     _svnThread->stop();
734     _tiedProperties.Untie();
735 }
736
737 void SGTerraSync::update(double)
738 {
739     static SGBucket bucket;
740     if (_svnThread->isDirty())
741     {
742         if (!_svnThread->_active)
743         {
744             if (_svnThread->_stalled)
745             {
746                 SG_LOG(SG_TERRAIN,SG_ALERT,
747                        "Automatic scenery download/synchronization stalled. Too many errors.");
748             }
749             else
750             {
751                 // not really an alert - just always show this message
752                 SG_LOG(SG_TERRAIN,SG_ALERT,
753                         "Automatic scenery download/synchronization has stopped.");
754             }
755             _stalled_node->setBoolValue(_svnThread->_stalled);
756         }
757
758         if (!_refresh_display->getBoolValue())
759             return;
760
761         while (_svnThread->hasNewTiles())
762         {
763             WaitingTile next = _svnThread->getNewTile();
764             if (next._refreshScenery)
765             {
766                 refreshScenery(_svnThread->getLocalDir(),next._dir);
767             }
768         }
769     }
770 }
771
772 void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
773 {
774     // find tiles to be refreshed
775     if (_tile_cache)
776     {
777         path.append(relativeDir);
778         if (path.exists())
779         {
780             simgear::Dir dir(path);
781             //TODO need to be smarter here. only update tiles which actually
782             // changed recently. May also be possible to use information from the
783             // built-in SVN client directly (instead of checking directory contents).
784             PathList tileList = dir.children(simgear::Dir::TYPE_FILE, ".stg");
785             for (unsigned int i=0; i<tileList.size(); ++i)
786             {
787                 // reload scenery tile
788                 long index = atoi(tileList[i].file().c_str());
789                 _tile_cache->refresh_tile(index);
790             }
791         }
792     }
793 }
794
795 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
796
797 void SGTerraSync::setTileCache(TileCache* tile_cache)
798 {
799     _tile_cache = tile_cache;
800 }
801
802 void SGTerraSync::syncAirportsModels()
803 {
804     static const char bounds[] = "KZAJ";
805     for( unsigned i = 0; i < sizeof(bounds)/sizeof(bounds[0])/2; i+= 2 ) 
806     {
807         for ( char synced_other = bounds[i]; synced_other <= bounds[i+1]; synced_other++ )
808         {
809             ostringstream dir;
810             dir << "Airports/" << synced_other;
811             WaitingTile w(dir.str(),false);
812             _svnThread->request( w );
813         }
814     }
815     WaitingTile w("Models",false);
816     _svnThread->request( w );
817 }
818
819
820 void SGTerraSync::syncArea( int lat, int lon )
821 {
822     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
823         return;
824     char NS, EW;
825     int baselat, baselon;
826
827     if ( lat < 0 ) {
828         int base = (int)(lat / 10);
829         if ( lat == base * 10 ) {
830             baselat = base * 10;
831         } else {
832             baselat = (base - 1) * 10;
833         }
834         NS = 's';
835     } else {
836         baselat = (int)(lat / 10) * 10;
837         NS = 'n';
838     }
839     if ( lon < 0 ) {
840         int base = (int)(lon / 10);
841         if ( lon == base * 10 ) {
842             baselon = base * 10;
843         } else {
844             baselon = (base - 1) * 10;
845         }
846         EW = 'w';
847     } else {
848         baselon = (int)(lon / 10) * 10;
849         EW = 'e';
850     }
851
852     const char* terrainobjects[3] = { "Terrain", "Objects",  0 };
853     bool refresh=true;
854     for (const char** tree = &terrainobjects[0]; *tree; tree++)
855     {
856         ostringstream dir;
857         dir << *tree << "/" << setfill('0')
858             << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
859             << EW << setw(3) << abs(lon)     << NS << setw(2) << abs(lat);
860         WaitingTile w(dir.str(),refresh);
861         _svnThread->request( w );
862         refresh=false;
863     }
864 }
865
866
867 void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
868 {
869     if ( lat_dir == 0 && lon_dir == 0 ) {
870         // do surrounding 8 1x1 degree areas.
871         for ( int i = lat - 1; i <= lat + 1; ++i ) {
872             for ( int j = lon - 1; j <= lon + 1; ++j ) {
873                 if ( i != lat || j != lon ) {
874                     syncArea( i, j );
875                 }
876             }
877         }
878     } else {
879         if ( lat_dir != 0 ) {
880             syncArea( lat + lat_dir, lon - 1 );
881             syncArea( lat + lat_dir, lon + 1 );
882             syncArea( lat + lat_dir, lon );
883         }
884         if ( lon_dir != 0 ) {
885             syncArea( lat - 1, lon + lon_dir );
886             syncArea( lat + 1, lon + lon_dir );
887             syncArea( lat, lon + lon_dir );
888         }
889     }
890
891     // do current 1x1 degree area first
892     syncArea( lat, lon );
893 }
894
895
896 bool SGTerraSync::schedulePosition(int lat, int lon)
897 {
898     // Ignore messages where the location does not change
899     if ( lat != last_lat || lon != last_lon )
900     {
901         SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " << 
902                                     lat << "," << lon);
903         int lat_dir, lon_dir, dist;
904         if ( last_lat == NOWHERE || last_lon == NOWHERE )
905         {
906             lat_dir = lon_dir = 0;
907         } else
908         {
909             dist = lat - last_lat;
910             if ( dist != 0 )
911             {
912                 lat_dir = dist / abs(dist);
913             }
914             else
915             {
916                 lat_dir = 0;
917             }
918             dist = lon - last_lon;
919             if ( dist != 0 )
920             {
921                 lon_dir = dist / abs(dist);
922             } else
923             {
924                 lon_dir = 0;
925             }
926         }
927
928         SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " << 
929                "lat = " << lat << ", lon = " << lon <<
930                ", lat_dir = " << lat_dir << ",  " <<
931                "lon_dir = " << lon_dir);
932
933         syncAreas( lat, lon, lat_dir, lon_dir );
934
935         last_lat = lat;
936         last_lon = lon;
937         return true;
938     }
939     return false;
940 }