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