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