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