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