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