]> git.mxchange.org Git - flightgear.git/blob - utils/TerraSync/terrasync.cxx
4024a3120dfd113dc55a20bdd3f1a60161f54691
[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
33 #include <stdlib.h>             // atoi() atof() abs() system()
34
35 #include <simgear/compiler.h>
36
37 #include <iostream>
38 #include <string>
39 #include <deque>
40 #include <map>
41
42 #include <plib/netSocket.h>
43 #include <plib/ul.h>
44
45 #include <simgear/bucket/newbucket.hxx>
46 #include <simgear/misc/sg_path.hxx>
47
48 #ifdef HAVE_SVN_CLIENT_H
49 #  ifdef HAVE_LIBSVN_CLIENT_1
50 #    include <svn_auth.h>
51 #    include <svn_client.h>
52 #    include <svn_cmdline.h>
53 #    include <svn_pools.h>
54 #  else
55 #    undef HAVE_SVN_CLIENT_H
56 #  endif
57 #endif
58
59 using std::string;
60 using std::cout;
61 using std::endl;
62
63 const char* source_base = NULL;
64 const char* svn_base =
65   "http://terrascenery.googlecode.com/svn/trunk/data/Scenery";
66 const char* rsync_base = "scenery.flightgear.org::Scenery";
67 const char* dest_base = "terrasyncdir";
68 const char* rsync_cmd = 
69     "rsync --verbose --archive --delete --perms --owner --group";
70
71 #ifdef HAVE_SVN_CLIENT_H
72 bool use_svn = true;
73 #else
74 bool use_svn = false;
75 const char* svn_cmd = "svn checkout";
76 #endif
77
78 // display usage
79 static void usage( const string& prog ) {
80     cout << "Usage: " << endl
81          << prog << " -p <port> "
82          << "-R [ -s <rsync_source> ] -d <dest>" << endl
83          << prog << " -p <port> "
84          << "-S [ -s <svn_source> ] -d <dest>" << endl;
85 #ifdef HAVE_SVN_CLIENT_H
86     cout << "    (defaults to the built in subversion)" << endl;
87 #else
88     cout << "    (defaults to rsync, using external commands)" << endl;
89 #endif
90 }
91
92 std::deque<std::string> waitingTiles;
93 typedef std::map<std::string,time_t> CompletedTiles;
94 CompletedTiles completedTiles;
95
96 #ifdef HAVE_SVN_CLIENT_H
97
98 // Things we need for doing subversion checkout - often
99 apr_pool_t *mysvn_pool = NULL;
100 svn_client_ctx_t *mysvn_ctx = NULL;
101 svn_opt_revision_t *mysvn_rev = NULL;
102 svn_opt_revision_t *mysvn_rev_peg = NULL;
103
104 static const svn_version_checklist_t mysvn_checklist[] = {
105     { "svn_subr",   svn_subr_version },
106     { "svn_client", svn_client_version },
107     { "svn_wc",     svn_wc_version },
108     { "svn_ra",     svn_ra_version },
109     { "svn_delta",  svn_delta_version },
110     { "svn_diff",   svn_diff_version },
111     { NULL, NULL }
112 };
113
114 // Configure our subversion session
115 int mysvn_setup(void) {
116     // Are we already prepared?
117     if (mysvn_pool) return EXIT_SUCCESS;
118     // No, so initialize svn internals generally
119 #ifdef _MSC_VER
120     // there is a segfault when providing an error stream.
121     //  Apparently, calling setvbuf with a nul buffer is
122     //  not supported under msvc 7.1 ( code inside svn_cmdline_init )
123     if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS)
124         return EXIT_FAILURE;
125 #else
126     if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS)
127         return EXIT_FAILURE;
128 #endif
129     apr_pool_t *pool;
130     apr_pool_create(&pool, NULL);
131     svn_error_t *err = NULL;
132     SVN_VERSION_DEFINE(mysvn_version);
133     err = svn_ver_check_list(&mysvn_version, mysvn_checklist);
134     if (err)
135         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
136     err = svn_ra_initialize(pool);
137     if (err)
138         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
139     char *config_dir = NULL;
140     err = svn_config_ensure(config_dir, pool);
141     if (err)
142         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
143     err = svn_client_create_context(&mysvn_ctx, pool);
144     if (err)
145         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
146     err = svn_config_get_config(&(mysvn_ctx->config),
147         config_dir, pool);
148     if (err)
149         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
150     svn_config_t *cfg;
151     cfg = ( svn_config_t*) apr_hash_get(
152         mysvn_ctx->config,
153         SVN_CONFIG_CATEGORY_CONFIG,
154         APR_HASH_KEY_STRING);
155     if (err)
156         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
157     svn_auth_baton_t *ab;
158     err = svn_cmdline_setup_auth_baton(&ab,
159         TRUE, NULL, NULL, config_dir, TRUE, cfg,
160         mysvn_ctx->cancel_func, mysvn_ctx->cancel_baton, pool);
161     if (err)
162         return svn_cmdline_handle_exit_error(err, pool, "terrasync: ");
163     mysvn_ctx->auth_baton = ab;
164     mysvn_ctx->conflict_func = NULL;
165     mysvn_ctx->conflict_baton = NULL;
166     // Now our magic revisions
167     mysvn_rev = (svn_opt_revision_t*) apr_palloc(pool, 
168         sizeof(svn_opt_revision_t));
169     if (!mysvn_rev)
170         return EXIT_FAILURE;
171     mysvn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool, 
172         sizeof(svn_opt_revision_t));
173     if (!mysvn_rev_peg)
174         return EXIT_FAILURE;
175     mysvn_rev->kind = svn_opt_revision_head;
176     mysvn_rev_peg->kind = svn_opt_revision_unspecified;
177     // Success if we got this far
178     mysvn_pool = pool;
179     return EXIT_SUCCESS;
180 }
181
182 #endif
183
184 // sync one directory tree
185 void sync_tree(const char* dir) {
186     int rc;
187     char command[512];
188     SGPath path( dest_base );
189
190     path.append( dir );
191     rc = path.create_dir( 0755 );
192     if (rc) {
193         cout << "Return code = " << rc << endl;
194         exit(1);
195     }
196
197     if (use_svn) {
198 #ifdef HAVE_SVN_CLIENT_H
199         cout << dir << " ... ";
200         cout.flush();
201         char dest_base_dir[512];
202         snprintf( command, 512,
203             "%s/%s", source_base, dir);
204         snprintf( dest_base_dir, 512,
205             "%s/%s", dest_base, dir);
206         svn_error_t *err = NULL;
207         if (mysvn_setup() != EXIT_SUCCESS)
208             exit(1);
209         apr_pool_t *subpool = svn_pool_create(mysvn_pool);
210         err = svn_client_checkout3(NULL,
211             command,
212             dest_base_dir,
213             mysvn_rev_peg,
214             mysvn_rev,
215             svn_depth_infinity,
216             0,
217             0,
218             mysvn_ctx,
219             subpool);
220         if (err) {
221             // Report errors from the checkout attempt
222             cout << "failed: " << endl
223                  << err->message << endl;
224             svn_error_clear(err);
225             return;
226         } else {
227             cout << "done" << endl;
228         }
229         svn_pool_destroy(subpool);
230         return;
231 #else
232
233         snprintf( command, 512,
234             "%s %s/%s %s/%s", svn_cmd,
235             source_base, dir,
236             dest_base, dir );
237 #endif
238     } else {
239         snprintf( command, 512,
240             "%s %s/%s/ %s/%s/", rsync_cmd,
241             source_base, dir,
242             dest_base, dir );
243     }
244     cout << command << endl;
245     rc = system( command );
246     if (rc) {
247         cout << "Return code = " << rc << endl;
248         if (rc == 5120) exit(1);
249     }
250 }
251
252
253 const int nowhere = -9999;
254
255 // parse message
256 static void parse_message( const string &msg, int *lat, int *lon ) {
257     double dlat, dlon;
258     string text = msg;
259
260     // find GGA string and advance to start of lat
261     string::size_type pos = text.find( "$GPGGA" );
262     if ( pos == string::npos )
263     {
264         *lat = -9999.0;
265         *lon = -9999.0;
266         return;
267     }
268     string tmp = text.substr( pos + 7 );
269     pos = tmp.find( "," );
270     tmp = tmp.substr( pos + 1 );
271     // cout << "-> " << tmp << endl;
272
273     // find lat then advance to start of hemisphere
274     pos = tmp.find( "," );
275     string lats = tmp.substr( 0, pos );
276     dlat = atof( lats.c_str() ) / 100.0;
277     tmp = tmp.substr( pos + 1 );
278
279     // find N/S hemisphere and advance to start of lon
280     if ( tmp.substr( 0, 1 ) == "S" ) {
281         dlat = -dlat;
282     }
283     pos = tmp.find( "," );
284     tmp = tmp.substr( pos + 1 );
285
286     // find lon
287     pos = tmp.find( "," );
288     string lons = tmp.substr( 0, pos );
289     dlon = atof( lons.c_str() ) / 100.0;
290     tmp = tmp.substr( pos + 1 );
291
292     // find E/W hemisphere and advance to start of lon
293     if ( tmp.substr( 0, 1 ) == "W" ) {
294         dlon = -dlon;
295     }
296
297     if ( dlat < 0 ) {
298         *lat = (int)dlat - 1;
299     } else {
300         *lat = (int)dlat;
301     }
302
303     if ( dlon < 0 ) {
304         *lon = (int)dlon - 1;
305     } else {
306         *lon = (int)dlon;
307     }
308
309     if ((dlon == 0) && (dlat == 0)) {
310       *lon = nowhere;
311       *lat = nowhere;
312     }
313 }
314
315
316 // sync area
317 static void sync_area( int lat, int lon ) {
318     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
319         return;
320     char NS, EW;
321     int baselat, baselon;
322
323     if ( lat < 0 ) {
324         int base = (int)(lat / 10);
325         if ( lat == base * 10 ) {
326             baselat = base * 10;
327         } else {
328             baselat = (base - 1) * 10;
329         }
330         NS = 's';
331     } else {
332         baselat = (int)(lat / 10) * 10;
333         NS = 'n';
334     }
335     if ( lon < 0 ) {
336         int base = (int)(lon / 10);
337         if ( lon == base * 10 ) {
338             baselon = base * 10;
339         } else {
340             baselon = (base - 1) * 10;
341         }
342         EW = 'w';
343     } else {
344         baselon = (int)(lon / 10) * 10;
345         EW = 'e';
346     }
347
348     const char* terrainobjects[3] = { "Terrain", "Objects", 0 };
349     
350     for (const char** tree = &terrainobjects[0]; *tree; tree++) {
351         char dir[512];
352         snprintf( dir, 512, "%s/%c%03d%c%02d/%c%03d%c%02d",
353                 *tree,
354                 EW, abs(baselon), NS, abs(baselat),
355                 EW, abs(lon), NS, abs(lat) );
356         waitingTiles.push_back( dir );
357     }
358 }
359
360
361 // sync areas
362 static void sync_areas( int lat, int lon, int lat_dir, int lon_dir ) {
363     // do current 1x1 degree area first
364     sync_area( lat, lon );
365
366     if ( lat_dir == 0 && lon_dir == 0 ) {
367         // now do surrounding 8 1x1 degree areas.
368         for ( int i = lat - 1; i <= lat + 1; ++i ) {
369             for ( int j = lon - 1; j <= lon + 1; ++j ) {
370                 if ( i != lat || j != lon ) {
371                     sync_area( i, j );
372                 }
373             }
374         }
375     } else {
376         if ( lat_dir != 0 ) {
377             sync_area( lat + lat_dir, lon );
378             sync_area( lat + lat_dir, lon - 1 );
379             sync_area( lat + lat_dir, lon + 1 );
380         }
381         if ( lon_dir != 0 ) {
382             sync_area( lat, lon + lon_dir );
383             sync_area( lat - 1, lon + lon_dir );
384             sync_area( lat + 1, lon + lon_dir );
385         }
386     }
387 }
388
389 void getWaitingTile() {
390     while ( !waitingTiles.empty() ) {
391         CompletedTiles::iterator ii = completedTiles.find( waitingTiles.front() );
392         time_t now = time(0);
393         if ( ii == completedTiles.end() || ii->second + 600 < now ) {
394             sync_tree(waitingTiles.front().c_str());
395             completedTiles[ waitingTiles.front() ] = now;
396             waitingTiles.pop_front();
397             break;
398         }
399         waitingTiles.pop_front();
400     }
401 }
402
403 int main( int argc, char **argv ) {
404     int port = 5501;
405     char host[256] = "";        // accept messages from anyone
406     bool testing = false;
407
408     // parse arguments
409     int i = 1;
410     while ( i < argc ) {
411         if ( (string)argv[i] == "-p" ) {
412             ++i;
413             port = atoi( argv[i] );
414         } else if ( (string)argv[i] == "-s" ) {
415             ++i;
416             source_base = argv[i];
417         } else if ( (string)argv[i] == "-d" ) {
418             ++i;
419             dest_base = argv[i];
420         } else if ( (string)argv[i] == "-R" ) {
421             use_svn = false;
422         } else if ( (string)argv[i] == "-S" ) {
423             use_svn = true;
424         } else if ( (string)argv[i] == "-T" ) {
425             testing = true;
426         } else {
427             usage( argv[0] );
428             exit(-1);        
429         }
430         ++i;
431     }
432
433     // Use the appropriate default for the "-s" flag
434     if (source_base == NULL) {
435         if (use_svn)
436             source_base = svn_base;
437         else
438             source_base = rsync_base;
439     }
440     
441     // Must call this before any other net stuff
442     netInit( &argc,argv );
443
444     netSocket s;
445
446     if ( ! s.open( false ) ) {  // open a UDP socket
447         printf("error opening socket\n");
448         return -1;
449     }
450
451     s.setBlocking( false );
452
453     if ( s.bind( host, port ) == -1 ) {
454         printf("error binding to port %d\n", port);
455         return -1;
456     }
457
458     char msg[256];
459     int maxlen = 256;
460     int len;
461     int lat, lon;
462     int last_lat = nowhere;
463     int last_lon = nowhere;
464     bool recv_msg = false;
465
466     char synced_other;
467     for ( synced_other = 'K'; synced_other <= 'Z'; synced_other++ ) {
468         char dir[512];
469         snprintf( dir, 512, "Airports/%c", synced_other );
470         waitingTiles.push_back( dir );
471     }
472     for ( synced_other = 'A'; synced_other <= 'J'; synced_other++ ) {
473         char dir[512];
474         snprintf( dir, 512, "Airports/%c", synced_other );
475         waitingTiles.push_back( dir );
476     }
477     if ( use_svn ) {
478         waitingTiles.push_back( "Models" );
479     }
480
481     while ( true ) {
482         recv_msg = false;
483         if ( testing ) {
484             // No FGFS communications
485             lat = 37;
486             lon = -123;
487             recv_msg = (lat != last_lat) || (lon != last_lon);
488         }
489         while ( (len = s.recv(msg, maxlen, 0)) >= 0 ) {
490             msg[len] = '\0';
491             recv_msg = true;
492
493             parse_message( msg, &lat, &lon );
494         }
495
496         if ( recv_msg ) {
497              // Ignore messages where the location does not change
498              if ( lat != last_lat || lon != last_lon ) {
499                 cout << "pos in msg = " << lat << "," << lon << endl;
500                 std::deque<std::string> oldRequests;
501                 oldRequests.swap( waitingTiles );
502                 int lat_dir, lon_dir, dist;
503                 if ( last_lat == nowhere || last_lon == nowhere ) {
504                     lat_dir = lon_dir = 0;
505                 } else {
506                     dist = lat - last_lat;
507                     if ( dist != 0 ) {
508                         lat_dir = dist / abs(dist);
509                     } else {
510                         lat_dir = 0;
511                     }
512                     dist = lon - last_lon;
513                     if ( dist != 0 ) {
514                         lon_dir = dist / abs(dist);
515                     } else {
516                         lon_dir = 0;
517                     }
518                 }
519                 cout << "lat = " << lat << " lon = " << lon << endl;
520                 cout << "lat_dir = " << lat_dir << "  "
521                      << "lon_dir = " << lon_dir << endl;
522                 sync_areas( lat, lon, lat_dir, lon_dir );
523                 while ( !oldRequests.empty() ) {
524                     waitingTiles.push_back( oldRequests.front() );
525                     oldRequests.pop_front();
526                 }
527                 last_lat = lat;
528                 last_lon = lon;
529             }
530         } else
531
532         // No messages inbound, so process some pending work
533         if ( !waitingTiles.empty() ) {
534             getWaitingTile();
535         } else
536
537         if ( testing ) {
538             exit( 0 );
539         } else
540
541         if ( last_lat == nowhere || last_lon == nowhere ) {
542             cout << "FlightGear is not running, exiting." << endl;
543             exit( 1 );
544         } else
545
546         ulSleep( 1 );
547     } // while true
548         
549     return 0;
550 }