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 // 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 <config.h>
27 #endif
28 
29 #include <simgear/simgear_config.h>
30 
31 #ifdef HAVE_WINDOWS_H
32 #include <windows.h>
33 #endif
34 
35 #ifdef HAVE_UNISTD_H
36 #include "unistd.h"
37 #endif
38 
39 #ifdef __MINGW32__
40 #   include <time.h>
41 #elif defined(_MSC_VER)
42 #   include <io.h>
43 #endif
44 
45 #include <stdlib.h>             // atoi() atof() abs() system()
46 #include <signal.h>             // signal()
47 
48 #include <simgear/compiler.h>
49 
50 #include <iostream>
51 #include <fstream>
52 #include <string>
53 
54 #include <simgear/io/raw_socket.hxx>
55 #include <simgear/scene/tsync/terrasync.hxx>
56 
57 #if defined(_MSC_VER) || defined(__MINGW32__)
58     typedef void (__cdecl * sighandler_t)(int);
59 #elif defined( __APPLE__ ) || defined (__FreeBSD__)
60     typedef sig_t sighandler_t;
61 #endif
62 
63 int termination_triggering_signals[] = {
64 #if defined(_MSC_VER) || defined(__MINGW32__)
65     SIGINT, SIGILL, SIGFPE, SIGSEGV, SIGTERM, SIGBREAK, SIGABRT,
66 #else
67     SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGTERM,
68 #endif
69     0};  // zero terminated
70 
71 using namespace std;
72 
73 const char* svn_base =
74     "http://terrascenery.googlecode.com/svn/trunk/data/Scenery";
75 const char* rsync_base = "scenery.flightgear.org::Scenery";
76 
77 bool terminating = false;
78 sighandler_t prior_signal_handlers[32];
79 
80 simgear::Socket theSocket;
81 simgear::SGTerraSync* pTerraSync = NULL;
82 
83 /** display usage information */
84 static void
usage(const string & prog)85 usage( const string& prog ) {
86     cout << "Usage:  terrasync [options]\n"
87             "Options:\n"
88             " -d <dest>       destination directory [required]\n"
89             " -R              transport using pipe to rsync\n"
90             " -S              transport using built-in svn\n"
91             " -e              transport using external svn client\n"
92             " -p <port>       listen on UDP port [default: 5501]\n"
93             " -s <source>     source base [default: '']\n"
94 #ifndef _MSC_VER
95             " -pid <pidfile>  write PID to file\n"
96 #endif
97             " -v              be more verbose\n";
98 
99 #ifndef _MSC_VER
100     cout << "\n"
101             "Example:\n"
102             "  pid=$(cat $pidfile 2>/dev/null)\n"
103             "  if test -n \"$pid\" && kill -0 $pid ; then\n"
104             "      echo \"terrasync already running: $pid\"\n"
105             "  else\n"
106             "      nice /games/sport/fgs/utils/TerraSync/terrasync         \\\n"
107             "        -v -pid $pidfile -S -p 5500 -d /games/orig/terrasync &\n"
108             "  fi\n";
109 #endif
110 }
111 
112 /** Signal handler for termination requests (Ctrl-C) */
113 void
terminate_request_handler(int param)114 terminate_request_handler(int param)
115 {
116     char msg[] = "\nReceived signal XX, intend to exit soon.\n"
117          "Repeat the signal to force immediate termination.\n";
118     msg[17] = '0' + param / 10;
119     msg[18] = '0' + param % 10;
120     if (write(1, msg, sizeof(msg) - 1) == -1)
121     {
122         // need to act write's return value to avoid GCC compiler warning
123         // "'write' declared with attribute warn_unused_result"
124         terminating = true; // dummy operation
125     }
126     terminating = true;
127     signal(param, prior_signal_handlers[param]);
128     theSocket.close();
129     if (pTerraSync)
130         pTerraSync->unbind();
131 }
132 
133 /** Parse command-line options. */
134 void
parseOptions(int argc,char ** argv,SGPropertyNode_ptr config,bool & testing,int & verbose,int & port,const char * & pidfn)135 parseOptions(int argc, char** argv, SGPropertyNode_ptr config,
136              bool& testing, int& verbose, int& port, const char* &pidfn)
137 {
138     // parse arguments
139     for (int i=1; i < argc; ++i )
140     {
141         string arg = argv[i];
142         if ( arg == "-p" ) {
143             ++i;
144             port = atoi( argv[i] );
145         } else if ( arg.find("-pid") == 0 ) {
146             ++i;
147             pidfn = argv[i];
148             cout << "pidfn: " << pidfn << endl;
149         } else if ( arg == "-s" ) {
150             ++i;
151             config->setStringValue("svn-server", argv[i]);
152         } else if ( arg == "-d" ) {
153             ++i;
154             config->setStringValue("scenery-dir", argv[i]);
155         } else if ( arg == "-R" ) {
156             config->setBoolValue("use-svn", false);
157         } else if ( arg == "-S" ) {
158             config->setBoolValue("use-built-in-svn", true);
159         } else if ( arg == "-e" ) {
160             config->setBoolValue("use-built-in-svn", false);
161         } else if ( arg == "-v" ) {
162             verbose++;
163         } else if ( arg == "-T" ) {
164             testing = true;
165         } else if ( arg == "-h" ) {
166             usage( argv[0] );
167             exit(0);
168         } else {
169             cerr << "Unrecognized command-line option '" << arg << "'" << endl;
170             usage( argv[0] );
171             exit(-1);
172         }
173     }
174 }
175 
176 /** parse a single NMEA-0183 position message */
177 static void
parse_message(int verbose,const string & msg,int * lat,int * lon)178 parse_message( int verbose, const string &msg, int *lat, int *lon )
179 {
180     double dlat, dlon;
181     string text = msg;
182 
183     if (verbose>=3)
184         cout << "message: '" << text << "'" << endl;
185 
186     // find GGA string and advance to start of lat (see NMEA-0183 for protocol specs)
187     string::size_type pos = text.find( "$GPGGA" );
188     if ( pos == string::npos )
189     {
190         *lat = simgear::NOWHERE;
191         *lon = simgear::NOWHERE;
192         return;
193     }
194     string tmp = text.substr( pos + 7 );
195     pos = tmp.find( "," );
196     tmp = tmp.substr( pos + 1 );
197     // cout << "-> " << tmp << endl;
198 
199     // find lat then advance to start of hemisphere
200     pos = tmp.find( "," );
201     string lats = tmp.substr( 0, pos );
202     dlat = atof( lats.c_str() ) / 100.0;
203     tmp = tmp.substr( pos + 1 );
204 
205     // find N/S hemisphere and advance to start of lon
206     if ( tmp.substr( 0, 1 ) == "S" ) {
207         dlat = -dlat;
208     }
209     pos = tmp.find( "," );
210     tmp = tmp.substr( pos + 1 );
211 
212     // find lon
213     pos = tmp.find( "," );
214     string lons = tmp.substr( 0, pos );
215     dlon = atof( lons.c_str() ) / 100.0;
216     tmp = tmp.substr( pos + 1 );
217 
218     // find E/W hemisphere and advance to start of lon
219     if ( tmp.substr( 0, 1 ) == "W" ) {
220         dlon = -dlon;
221     }
222 
223     if ( dlat < 0 ) {
224         *lat = (int)dlat - 1;
225     } else {
226         *lat = (int)dlat;
227     }
228 
229     if ( dlon < 0 ) {
230         *lon = (int)dlon - 1;
231     } else {
232         *lon = (int)dlon;
233     }
234 
235     if ((dlon == 0) && (dlat == 0)) {
236         *lon = simgear::NOWHERE;
237         *lat = simgear::NOWHERE;
238     }
239 }
240 
241 void
syncArea(int lat,int lon)242 syncArea( int lat, int lon )
243 {
244     if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
245         return;
246     char NS, EW;
247     int baselat, baselon;
248 
249     if ( lat < 0 ) {
250         int base = (int)(lat / 10);
251         if ( lat == base * 10 ) {
252             baselat = base * 10;
253         } else {
254             baselat = (base - 1) * 10;
255         }
256         NS = 's';
257     } else {
258         baselat = (int)(lat / 10) * 10;
259         NS = 'n';
260     }
261     if ( lon < 0 ) {
262         int base = (int)(lon / 10);
263         if ( lon == base * 10 ) {
264             baselon = base * 10;
265         } else {
266             baselon = (base - 1) * 10;
267         }
268         EW = 'w';
269     } else {
270         baselon = (int)(lon / 10) * 10;
271         EW = 'e';
272     }
273 
274     ostringstream dir;
275     dir << setfill('0')
276     << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
277     << EW << setw(3) << abs(lon)     << NS << setw(2) << abs(lat);
278 
279     pTerraSync->syncAreaByPath(dir.str());
280 }
281 
282 void
syncAreas(int lat,int lon,int lat_dir,int lon_dir)283 syncAreas( int lat, int lon, int lat_dir, int lon_dir )
284 {
285     if ( lat_dir == 0 && lon_dir == 0 ) {
286 
287         // do surrounding 8 1x1 degree areas.
288         for ( int i = lat - 1; i <= lat + 1; ++i ) {
289             for ( int j = lon - 1; j <= lon + 1; ++j ) {
290                 if ( i != lat || j != lon ) {
291                     syncArea( i, j );
292                 }
293             }
294         }
295     } else {
296         if ( lat_dir != 0 ) {
297             syncArea( lat + lat_dir, lon - 1 );
298             syncArea( lat + lat_dir, lon + 1 );
299             syncArea( lat + lat_dir, lon );
300         }
301         if ( lon_dir != 0 ) {
302             syncArea( lat - 1, lon + lon_dir );
303             syncArea( lat + 1, lon + lon_dir );
304             syncArea( lat, lon + lon_dir );
305         }
306     }
307 
308     // do current 1x1 degree area first
309     syncArea( lat, lon );
310 }
311 
312 bool
schedulePosition(int lat,int lon)313 schedulePosition(int lat, int lon)
314 {
315     bool Ok = false;
316     if ((lat == simgear::NOWHERE) || (lon == simgear::NOWHERE)) {
317         return Ok;
318     }
319 
320     static int last_lat = simgear::NOWHERE;
321     static int last_lon = simgear::NOWHERE;
322     if ((lat == last_lat) && (lon == last_lon)) {
323         Ok = true;
324         return Ok;
325     }
326 
327     int lat_dir=0;
328     int lon_dir=0;
329 
330     if ( last_lat != simgear::NOWHERE && last_lon != simgear::NOWHERE )
331     {
332         int dist = lat - last_lat;
333         if ( dist != 0 )
334         {
335             lat_dir = dist / abs(dist);
336         }
337         else
338         {
339             lat_dir = 0;
340         }
341         dist = lon - last_lon;
342         if ( dist != 0 )
343         {
344             lon_dir = dist / abs(dist);
345         } else
346         {
347             lon_dir = 0;
348         }
349     }
350 
351     cout << "Scenery update for " <<
352            "lat = " << lat << ", lon = " << lon <<
353            ", lat_dir = " << lat_dir << ",  " <<
354            "lon_dir = " << lon_dir << endl;
355 
356     syncAreas( lat, lon, lat_dir, lon_dir );
357     Ok = true;
358 
359     last_lat = lat;
360     last_lon = lon;
361 
362     return Ok;
363 }
364 
365 /** Monitor UDP socket and process NMEA position updates. */
366 int
processRequests(SGPropertyNode_ptr config,bool testing,int verbose,int port)367 processRequests(SGPropertyNode_ptr config, bool testing, int verbose, int port)
368 {
369     const char* host = "localhost";
370     char msg[256];
371     int len;
372     int lat, lon;
373     bool connected = false;
374 
375     // Must call this before any other net stuff
376     simgear::Socket::initSockets();
377 
378     // open UDP socket
379     if ( !theSocket.open( false ) )
380     {
381         cerr << "error opening socket" << endl;
382         return -1;
383     }
384 
385     if ( theSocket.bind( host, port ) == -1 )
386     {
387         cerr << "error binding to port " << port << endl;
388         return -1;
389     }
390 
391     theSocket.setBlocking(true);
392 
393     while ( (!terminating)&&
394             (!config->getBoolValue("stalled", false)) )
395     {
396         if (verbose >= 4)
397             cout << "main loop" << endl;
398         // main loop
399         bool recv_msg = false;
400         if ( testing )
401         {
402             // Testing without FGFS communication
403             lat = 37;
404             lon = -123;
405             recv_msg = true;
406         } else
407         {
408             if (verbose && pTerraSync->isIdle())
409             {
410                 cout << "Idle; waiting for FlightGear position data" << endl;
411             }
412             len = theSocket.recv(msg, sizeof(msg)-1, 0);
413             if (len >= 0)
414             {
415                 msg[len] = '\0';
416                 recv_msg = true;
417                 if (verbose>=2)
418                     cout << "recv length: " << len << endl;
419                 parse_message( verbose, msg, &lat, &lon );
420                 if ((!connected)&&
421                     (lat != simgear::NOWHERE)&&
422                     (lon != simgear::NOWHERE))
423                 {
424                     cout << "Valid position data received. Connected successfully." << endl;
425                     connected = true;
426                 }
427             }
428         }
429 
430         if ( recv_msg )
431         {
432             schedulePosition(lat, lon);
433         }
434 
435         if ( testing )
436         {
437             if (pTerraSync->isIdle())
438                 terminating = true;
439             else
440                 SGTimeStamp::sleepForMSec(1000);
441         }
442     } // while !terminating
443 
444     return 0;
445 }
446 
447 
main(int argc,char ** argv)448 int main( int argc, char **argv )
449 {
450     int port = 5501;
451     int verbose = 0;
452     int exit_code = 0;
453     bool testing = false;
454     const char* pidfn = "";
455 
456     // default configuration
457     sglog().setLogLevels( SG_ALL, SG_ALERT);
458     SGPropertyNode_ptr root = new SGPropertyNode();
459     SGPropertyNode_ptr config = root->getNode("/sim/terrasync", true);
460     config->setStringValue("scenery-dir", "terrasyncdir");
461     config->setStringValue("svn-server", svn_base);
462     config->setStringValue("rsync-server", rsync_base);
463     config->setBoolValue("use-built-in-svn", true);
464     config->setBoolValue("use-svn", true);
465     config->setBoolValue("enabled", true);
466     config->setIntValue("max-errors", -1); // -1 = infinite
467 
468     // parse command-line arguments
469     parseOptions(argc, argv, config, testing, verbose, port, pidfn);
470 
471     if (verbose)
472         sglog().setLogLevels( SG_ALL, SG_INFO);
473 
474 #ifndef _MSC_VER
475     // create PID file
476     if (*pidfn)
477     {
478         ofstream pidstream;
479         pidstream.open(pidfn);
480         if (!pidstream.good())
481         {
482             cerr << "Cannot open pid file '" << pidfn << "': ";
483             perror(0);
484             exit(2);
485         }
486         pidstream << getpid() << endl;
487         pidstream.close();
488     }
489 #endif
490 
491     // install signal handlers
492     for (int* sigp=termination_triggering_signals; *sigp; sigp++)
493     {
494         prior_signal_handlers[*sigp] =
495             signal(*sigp, *terminate_request_handler);
496         if (verbose>=2)
497             cout << "Intercepting signal " << *sigp << endl;
498     }
499 
500     {
501         pTerraSync = new simgear::SGTerraSync;
502         pTerraSync->setRoot(root);
503         pTerraSync->bind();
504         pTerraSync->init();
505 
506         // now monitor and process position updates
507         exit_code = processRequests(config, testing, verbose, port);
508 
509         pTerraSync->unbind();
510         delete pTerraSync;
511         pTerraSync = NULL;
512     }
513 
514     return exit_code;
515 }
516