]> git.mxchange.org Git - flightgear.git/blob - utils/TerraSync/terrasync.cxx
Merge downstream fix from Gentoo, to deal with libsvn versions.
[flightgear.git] / utils / TerraSync / terrasync.cxx
1 // terrasync.cxx -- "JIT" scenery fetcher
2 //
3 // Written by Curtis Olson, started 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 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #ifdef HAVE_WINDOWS_H
29 #include <windows.h>
30 #endif
31
32 #ifdef __MINGW32__
33 #include <time.h>
34 #include <unistd.h>
35 #elif defined(_MSC_VER)
36 #   include <io.h>
37 #   ifndef HAVE_SVN_CLIENT_H
38 #       include <time.h>
39 #       include <process.h>
40 #   endif
41 #endif
42
43 #include <stdlib.h>             // atoi() atof() abs() system()
44 #include <signal.h>             // signal()
45
46 #include <simgear/compiler.h>
47
48 #include <iostream>
49 #include <fstream>
50 #include <string>
51 #include <deque>
52 #include <map>
53
54 #include <simgear/io/raw_socket.hxx>
55 #include <simgear/bucket/newbucket.hxx>
56 #include <simgear/misc/sg_path.hxx>
57
58 #ifdef HAVE_SVN_CLIENT_H
59 #  ifdef HAVE_LIBSVN_CLIENT_1
60 #    include <svn_auth.h>
61 #    include <svn_client.h>
62 #    include <svn_cmdline.h>
63 #    include <svn_pools.h>
64 #    include <svn_version.h>
65 #  else
66 #    undef HAVE_SVN_CLIENT_H
67 #  endif
68 #endif
69
70 using namespace std;
71
72 const char* source_base = NULL;
73 const char* svn_base =
74   "http://terrascenery.googlecode.com/svn/trunk/data/Scenery";
75 const char* rsync_base = "scenery.flightgear.org::Scenery";
76 const char* dest_base = "terrasyncdir";
77 const char* rsync_cmd = 
78     "rsync --verbose --archive --delete --perms --owner --group";
79
80 #ifdef HAVE_SVN_CLIENT_H
81 bool use_svn = true;
82 #else
83 bool use_svn = false;
84 const char* svn_cmd = "svn checkout";
85 #endif
86
87 // display usage
88 static void usage( const string& prog ) {
89     cout << 
90 "Usage:  terrasync [options]\n"
91 "Options:  \n"
92 " -d <dest>       destination directory [required]\n"
93 " -R              transport using pipe to rsync\n"
94 " -S              transport using built-in svn\n"
95 " -p <port>       listen on UDP port [default: 5501]\n"
96 " -s <source>     source base [default: '']\n"
97 " -pid <pidfile>  write PID to file\n"
98 " -v              be more verbose\n"
99 ;
100
101 #ifdef HAVE_SVN_CLIENT_H
102     cout << "    (defaults to the built in subversion)" << endl;
103 #else
104     cout << "    (defaults to rsync, using external commands)" << endl;
105 #endif
106
107   cout << "\nExample:\n"
108 "pid=$(cat $pidfile 2>/dev/null)\n"
109 "if test -n \"$pid\" && kill -0 $pid ; then\n"
110 "    echo \"terrasync already running: $pid\"\n"
111 "else\n"
112 "    nice /games/sport/fgs/utils/TerraSync/terrasync         \\\n"
113 "      -v -pid $pidfile -S -p 5500 -d /games/orig/terrasync &\n"
114 "fi" << endl;
115
116 }
117
118 deque<string> waitingTiles;
119 typedef map<string,time_t> CompletedTiles;
120 CompletedTiles completedTiles;
121 simgear::Socket theSocket;
122
123 #ifdef HAVE_SVN_CLIENT_H
124
125 // Things we need for doing subversion checkout - often
126 apr_pool_t *mysvn_pool = NULL;
127 svn_client_ctx_t *mysvn_ctx = NULL;
128 svn_opt_revision_t *mysvn_rev = NULL;
129 svn_opt_revision_t *mysvn_rev_peg = NULL;
130
131 static const svn_version_checklist_t mysvn_checklist[] = {
132     { "svn_subr",   svn_subr_version },
133     { "svn_client", svn_client_version },
134     { NULL, NULL }
135 };
136
137 // Configure our subversion session
138 int mysvn_setup(void) {
139     // Are we already prepared?
140     if (mysvn_pool) return EXIT_SUCCESS;
141     // No, so initialize svn internals generally
142 #ifdef _MSC_VER
143     // there is a segfault when providing an error stream.
144     //  Apparently, calling setvbuf with a nul buffer is
145     //  not supported under msvc 7.1 ( code inside svn_cmdline_init )
146     if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS)
147         return EXIT_FAILURE;
148 #else
149     if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS)
150         return EXIT_FAILURE;
151 #endif
152     apr_pool_t *pool;
153     apr_pool_create(&pool, NULL);
154     svn_error_t *err = NULL;
155     SVN_VERSION_DEFINE(mysvn_version);
156     err = svn_ver_check_list(&mysvn_version, mysvn_checklist);
157     if (err)
158         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
159     err = svn_ra_initialize(pool);
160     if (err)
161         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
162     char *config_dir = NULL;
163     err = svn_config_ensure(config_dir, pool);
164     if (err)
165         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
166     err = svn_client_create_context(&mysvn_ctx, pool);
167     if (err)
168         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
169     err = svn_config_get_config(&(mysvn_ctx->config),
170         config_dir, pool);
171     if (err)
172         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
173     svn_config_t *cfg;
174     cfg = ( svn_config_t*) apr_hash_get(
175         mysvn_ctx->config,
176         SVN_CONFIG_CATEGORY_CONFIG,
177         APR_HASH_KEY_STRING);
178     if (err)
179         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
180     svn_auth_baton_t *ab;
181     err = svn_cmdline_setup_auth_baton(&ab,
182         TRUE, NULL, NULL, config_dir, TRUE, cfg,
183         mysvn_ctx->cancel_func, mysvn_ctx->cancel_baton, pool);
184     if (err)
185         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
186     mysvn_ctx->auth_baton = ab;
187 #if (SVN_VER_MINOR >= 5)
188     mysvn_ctx->conflict_func = NULL;
189     mysvn_ctx->conflict_baton = NULL;
190 #endif
191     // Now our magic revisions
192     mysvn_rev = (svn_opt_revision_t*) apr_palloc(pool, 
193         sizeof(svn_opt_revision_t));
194     if (!mysvn_rev)
195         return EXIT_FAILURE;
196     mysvn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool, 
197         sizeof(svn_opt_revision_t));
198     if (!mysvn_rev_peg)
199         return EXIT_FAILURE;
200     mysvn_rev->kind = svn_opt_revision_head;
201     mysvn_rev_peg->kind = svn_opt_revision_unspecified;
202     // Success if we got this far
203     mysvn_pool = pool;
204     return EXIT_SUCCESS;
205 }
206
207 #endif
208
209 // sync one directory tree
210 void sync_tree(const char* dir) {
211     int rc;
212     char command[512];
213     SGPath path( dest_base );
214
215     path.append( dir );
216     rc = path.create_dir( 0755 );
217     if (rc) {
218         cout << "Return code = " << rc << endl;
219         exit(1);
220     }
221
222     if (use_svn) {
223 #ifdef HAVE_SVN_CLIENT_H
224         cout << dir << " ... ";
225         cout.flush();
226         char dest_base_dir[512];
227         snprintf( command, 512,
228             "%s/%s", source_base, dir);
229         snprintf( dest_base_dir, 512,
230             "%s/%s", dest_base, dir);
231         svn_error_t *err = NULL;
232         if (mysvn_setup() != EXIT_SUCCESS)
233             exit(1);
234         apr_pool_t *subpool = svn_pool_create(mysvn_pool);
235         
236 #if (SVN_VER_MINOR >= 5)
237         err = svn_client_checkout3(NULL,
238             command,
239             dest_base_dir,
240             mysvn_rev_peg,
241             mysvn_rev,
242             svn_depth_infinity,
243             0, // ignore-externals = false
244             0, // allow unver obstructions = false
245             mysvn_ctx,
246             subpool);
247 #else
248     // version 1.4 API
249     err = svn_client_checkout2(NULL,
250             command,
251             dest_base_dir,
252             mysvn_rev_peg,
253             mysvn_rev,
254             1, // recurse=true - same as svn_depth_infinity for checkout3 above
255             0, // ignore externals = false
256             mysvn_ctx,
257             subpool);
258 #endif
259             
260         if (err) {
261             // Report errors from the checkout attempt
262             cout << "failed: " << endl
263                  << err->message << endl;
264             svn_error_clear(err);
265             return;
266         } else {
267             cout << "done" << endl;
268         }
269         svn_pool_destroy(subpool);
270         return;
271 #else
272
273         snprintf( command, 512,
274             "%s %s/%s %s/%s", svn_cmd,
275             source_base, dir,
276             dest_base, dir );
277 #endif
278     } else {
279         snprintf( command, 512,
280             "%s %s/%s/ %s/%s/", rsync_cmd,
281             source_base, dir,
282             dest_base, dir );
283     }
284     cout << command << endl;
285     rc = system( command );
286     if (rc) {
287         cout << "Return code = " << rc << endl;
288         if (rc == 5120) exit(1);
289     }
290 }
291
292 #if defined(_MSC_VER) || defined(__MINGW32__)
293 typedef void (__cdecl * sighandler_t)(int);
294 #elif defined( __APPLE__ ) || defined (__FreeBSD__)
295 typedef sig_t sighandler_t;
296 #endif
297
298 bool terminating = false;
299 sighandler_t prior_signal_handlers[32];
300 int termination_triggering_signals[] = {
301 #if defined(_MSC_VER) || defined(__MINGW32__)
302     SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, SIGBREAK, SIGABRT,
303 #else
304     SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGTERM,
305 #endif
306     0};  // zero terminated
307
308 void terminate_request_handler(int param) {
309     char msg[] = "\nReceived signal XX, intend to exit soon.\n"
310          "repeat the signal to force immediate termination.\n";
311     msg[17] = '0' + param / 10;
312     msg[18] = '0' + param % 10;
313     write(1, msg, sizeof(msg) - 1);
314     terminating = true;
315     signal(param, prior_signal_handlers[param]);
316     theSocket.close();
317 }
318
319
320 const int nowhere = -9999;
321
322
323 // parse message
324 static void parse_message( const string &msg, int *lat, int *lon ) {
325     double dlat, dlon;
326     string text = msg;
327
328     // find GGA string and advance to start of lat
329     string::size_type pos = text.find( "$GPGGA" );
330     if ( pos == string::npos )
331     {
332         *lat = nowhere;
333         *lon = nowhere;
334         return;
335     }
336     string tmp = text.substr( pos + 7 );
337     pos = tmp.find( "," );
338     tmp = tmp.substr( pos + 1 );
339     // cout << "-> " << tmp << endl;
340
341     // find lat then advance to start of hemisphere
342     pos = tmp.find( "," );
343     string lats = tmp.substr( 0, pos );
344     dlat = atof( lats.c_str() ) / 100.0;
345     tmp = tmp.substr( pos + 1 );
346
347     // find N/S hemisphere and advance to start of lon
348     if ( tmp.substr( 0, 1 ) == "S" ) {
349         dlat = -dlat;
350     }
351     pos = tmp.find( "," );
352     tmp = tmp.substr( pos + 1 );
353
354     // find lon
355     pos = tmp.find( "," );
356     string lons = tmp.substr( 0, pos );
357     dlon = atof( lons.c_str() ) / 100.0;
358     tmp = tmp.substr( pos + 1 );
359
360     // find E/W hemisphere and advance to start of lon
361     if ( tmp.substr( 0, 1 ) == "W" ) {
362         dlon = -dlon;
363     }
364
365     if ( dlat < 0 ) {
366         *lat = (int)dlat - 1;
367     } else {
368         *lat = (int)dlat;
369     }
370
371     if ( dlon < 0 ) {
372         *lon = (int)dlon - 1;
373     } else {
374         *lon = (int)dlon;
375     }
376
377     if ((dlon == 0) && (dlat == 0)) {
378       *lon = nowhere;
379       *lat = nowhere;
380     }
381 }
382
383
384 // sync area
385 static void sync_area( int lat, int lon ) {
386     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
387         return;
388     char NS, EW;
389     int baselat, baselon;
390
391     if ( lat < 0 ) {
392         int base = (int)(lat / 10);
393         if ( lat == base * 10 ) {
394             baselat = base * 10;
395         } else {
396             baselat = (base - 1) * 10;
397         }
398         NS = 's';
399     } else {
400         baselat = (int)(lat / 10) * 10;
401         NS = 'n';
402     }
403     if ( lon < 0 ) {
404         int base = (int)(lon / 10);
405         if ( lon == base * 10 ) {
406             baselon = base * 10;
407         } else {
408             baselon = (base - 1) * 10;
409         }
410         EW = 'w';
411     } else {
412         baselon = (int)(lon / 10) * 10;
413         EW = 'e';
414     }
415
416     const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
417     
418     for (const char** tree = &terrainobjects[0]; *tree; tree++) {
419         char dir[512];
420         snprintf( dir, 512, "%s/%c%03d%c%02d/%c%03d%c%02d",
421                 *tree,
422                 EW, abs(baselon), NS, abs(baselat),
423                 EW, abs(lon), NS, abs(lat) );
424         waitingTiles.push_back( dir );
425     }
426 }
427
428
429 // sync areas
430 static void sync_areas( int lat, int lon, int lat_dir, int lon_dir ) {
431     // do current 1x1 degree area first
432     sync_area( lat, lon );
433
434     if ( lat_dir == 0 && lon_dir == 0 ) {
435         // now do surrounding 8 1x1 degree areas.
436         for ( int i = lat - 1; i <= lat + 1; ++i ) {
437             for ( int j = lon - 1; j <= lon + 1; ++j ) {
438                 if ( i != lat || j != lon ) {
439                     sync_area( i, j );
440                 }
441             }
442         }
443     } else {
444         if ( lat_dir != 0 ) {
445             sync_area( lat + lat_dir, lon );
446             sync_area( lat + lat_dir, lon - 1 );
447             sync_area( lat + lat_dir, lon + 1 );
448         }
449         if ( lon_dir != 0 ) {
450             sync_area( lat, lon + lon_dir );
451             sync_area( lat - 1, lon + lon_dir );
452             sync_area( lat + 1, lon + lon_dir );
453         }
454     }
455 }
456
457 void getWaitingTile() {
458     while ( !waitingTiles.empty() ) {
459         CompletedTiles::iterator ii =
460             completedTiles.find( waitingTiles.front() );
461         time_t now = time(0);
462         if ( ii == completedTiles.end() || ii->second + 600 < now ) {
463             sync_tree(waitingTiles.front().c_str());
464             completedTiles[ waitingTiles.front() ] = now;
465             waitingTiles.pop_front();
466             break;
467         }
468         waitingTiles.pop_front();
469     }
470 }
471
472 int main( int argc, char **argv ) {
473     int port = 5501;
474     char host[256] = "localhost";
475     bool testing = false;
476     bool do_checkout(true);
477     int verbose(0);
478     const char* pidfn = "";
479
480     // parse arguments
481     int i = 1;
482     while ( i < argc ) {
483         if ( (string)argv[i] == "-p" ) {
484             ++i;
485             port = atoi( argv[i] );
486         } else if ( string(argv[i]).find("-pid") == 0 ) {
487             ++i;
488             pidfn = argv[i];
489             cout << "pidfn: " << pidfn << endl;
490         } else if ( (string)argv[i] == "-s" ) {
491             ++i;
492             source_base = argv[i];
493         } else if ( (string)argv[i] == "-d" ) {
494             ++i;
495             dest_base = argv[i];
496         } else if ( (string)argv[i] == "-R" ) {
497             use_svn = false;
498         } else if ( (string)argv[i] == "-S" ) {
499             use_svn = true;
500         } else if ( (string)argv[i] == "-v" ) {
501             verbose++;
502         } else if ( (string)argv[i] == "-T" ) {
503             testing = true;
504         } else if ( (string)argv[i] == "-h" ) {
505             usage( argv[0] );
506             exit(0);
507         } else {
508             cerr << "Unrecognized verbiage '" << argv[i] << "'" << endl;
509             usage( argv[0] );
510             exit(-1);        
511         }
512         ++i;
513     }
514
515     if (*pidfn) {
516       ofstream pidstream;
517       pidstream.open(pidfn);
518       if (!pidstream.good()) {
519         cerr << "Cannot open pid file '" << pidfn << "': ";
520         perror(0);
521         exit(2);
522       }
523       pidstream << getpid() << endl;
524       pidstream.close();
525     }
526
527     // Use the appropriate default for the "-s" flag
528     if (source_base == NULL) {
529         if (use_svn)
530             source_base = svn_base;
531         else
532             source_base = rsync_base;
533     }
534     
535     // Must call this before any other net stuff
536     simgear::Socket::initSockets();
537
538     if ( ! theSocket.open( false ) ) {  // open a UDP socket
539         printf("error opening socket\n");
540         return -1;
541     }
542
543     if ( theSocket.bind( host, port ) == -1 ) {
544         printf("error binding to port %d\n", port);
545         return -1;
546     }
547
548     char msg[256];
549     int maxlen = 256;
550     int len;
551     int lat, lon;
552     int last_lat = nowhere;
553     int last_lon = nowhere;
554     bool recv_msg = false;
555
556     char synced_other;
557     if (do_checkout) {
558         for ( synced_other = 'K'; synced_other <= 'Z'; synced_other++ ) {
559             char dir[512];
560             snprintf( dir, 512, "Airports/%c", synced_other );
561             waitingTiles.push_back( dir );
562         }
563         for ( synced_other = 'A'; synced_other <= 'J'; synced_other++ ) {
564             char dir[512];
565             snprintf( dir, 512, "Airports/%c", synced_other );
566             waitingTiles.push_back( dir );
567         }
568         if ( use_svn ) {
569             waitingTiles.push_back( "Models" );
570         }
571     }
572
573
574     for (int* sigp=termination_triggering_signals; *sigp; sigp++) {
575         prior_signal_handlers[*sigp] =
576             signal(*sigp, *terminate_request_handler);
577         if (verbose) cout << "Intercepted signal " << *sigp << endl;
578     }
579
580     while ( !terminating ) {
581         // main loop
582         recv_msg = false;
583         if ( testing ) {
584             // No FGFS communications
585             lat = 37;
586             lon = -123;
587             recv_msg = (lat != last_lat) || (lon != last_lon);
588         } else {
589             if (verbose && waitingTiles.empty()) {
590                 cout << "Idle; waiting for FlightGear position\n";
591             }
592             theSocket.setBlocking(waitingTiles.empty());
593             len = theSocket.recv(msg, maxlen, 0);
594             if (len >= 0) {
595                 msg[len] = '\0';
596                 recv_msg = true;
597                 if (verbose) cout << "recv length: " << len << endl;
598                 parse_message( msg, &lat, &lon );
599             }
600         }
601
602         if ( recv_msg ) {
603              // Ignore messages where the location does not change
604              if ( lat != last_lat || lon != last_lon ) {
605                 cout << "pos in msg = " << lat << "," << lon << endl;
606                 deque<string> oldRequests;
607                 oldRequests.swap( waitingTiles );
608                 int lat_dir, lon_dir, dist;
609                 if ( last_lat == nowhere || last_lon == nowhere ) {
610                     lat_dir = lon_dir = 0;
611                 } else {
612                     dist = lat - last_lat;
613                     if ( dist != 0 ) {
614                         lat_dir = dist / abs(dist);
615                     } else {
616                         lat_dir = 0;
617                     }
618                     dist = lon - last_lon;
619                     if ( dist != 0 ) {
620                         lon_dir = dist / abs(dist);
621                     } else {
622                         lon_dir = 0;
623                     }
624                 }
625                 cout << "lat = " << lat << " lon = " << lon << endl;
626                 cout << "lat_dir = " << lat_dir << "  "
627                      << "lon_dir = " << lon_dir << endl;
628                 sync_areas( lat, lon, lat_dir, lon_dir );
629                 while ( !oldRequests.empty() ) {
630                     waitingTiles.push_back( oldRequests.front() );
631                     oldRequests.pop_front();
632                 }
633                 last_lat = lat;
634                 last_lon = lon;
635             }
636         }
637
638         // No messages inbound, so process some pending work
639         else if ( !waitingTiles.empty() ) {
640             getWaitingTile();
641         }
642
643         else if ( testing ) {
644             terminating = true;
645         } else
646
647         #ifdef _WIN32
648                 Sleep(1000);
649 #else
650                 sleep(1);
651 #endif
652     } // while !terminating
653         
654     return 0;
655 }