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