1 // ----------------------------------------------------------------------------
2 // kmlserver.cxx -- KML Server
3 //
4 // Copyright (C) 2012
5 // Remi Chateauneu, F4ECW
6 //
7 // This file is part of fldigi.
8 //
9 // Fldigi is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // Fldigi is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
21 // ----------------------------------------------------------------------------
22
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <math.h>
28 #include <assert.h>
29 #include <sys/stat.h>
30
31 #include <string>
32 #include <set>
33 #include <map>
34 #include <vector>
35 #include <memory>
36 #include <list>
37 #include <stdexcept>
38 #include <fstream>
39 #include <sstream>
40
41 #include "config.h"
42 #include "kmlserver.h"
43 #include "gettext.h"
44 #include "debug.h"
45 #include "threads.h"
46 #include "strutil.h"
47 #include "configuration.h"
48 #include "fl_digi.h"
49
50 #include "irrXML.h"
51
52 #include "timeops.h"
53
54 LOG_FILE_SOURCE(debug::LOG_KML);
55
56 /** Some platforms have problems with condition variables apparently.
57 * When cancelling a thread which waits in pthread_cond_timedwait,
58 * the thread is stuck.
59 * We replace it by an unconditional wait, and a test on a boolean
60 * which indicates if data was saved in the internal buffers.
61 * The consequence is that data are not immediately saved in KML files,
62 * and the user has to wait until the end of the delay.
63 */
64
65 #if !defined(__APPLE__)
66 # define FLDIGI_KML_CONDITION_VARIABLE 1
67 #endif
68
69 // ----------------------------------------------------------------------------
70
71 static const char * KmlSrvUnique = "Permanent";
72
73 /// This must follow a specific ISO format so it can be serialized in KML files.
KmlTimestamp(std::ostream & ostrm,time_t tim)74 static void KmlTimestamp( std::ostream &ostrm, time_t tim ) {
75 if(tim == KmlServer::UniqueEvent) {
76 ostrm << KmlSrvUnique;
77 return;
78 }
79 tm objTm = *gmtime(&tim);
80 char bufTm[40];
81 // See http://www.w3.org/TR/xmlschema-2/#isoformats
82 snprintf( bufTm, sizeof(bufTm), "%4d-%02d-%02dT%02d:%02dZ",
83 objTm.tm_year + 1900,
84 objTm.tm_mon + 1,
85 objTm.tm_mday,
86 objTm.tm_hour,
87 objTm.tm_min );
88 ostrm << bufTm ;
89 }
90
91 /// For debugging purpose.
KmlTimestamp(time_t tim)92 static std::string KmlTimestamp( time_t tim ) {
93 std::stringstream strm ;
94 KmlTimestamp( strm, tim );
95 return strm.str();
96 }
97
98 /// Deserialize a timestamp, inverse of KmlTimestamp.
KmlFromTimestamp(const char * ts)99 static time_t KmlFromTimestamp( const char * ts ) {
100 if(ts == NULL ) throw std::runtime_error("Null timestamp");
101
102 if( 0 == strcmp( ts, KmlSrvUnique ) ) return KmlServer::UniqueEvent ;
103
104 /// So all fields are initialised with correct default values.
105 time_t timNow = time(NULL);
106 tm objTm = *gmtime( &timNow );
107
108 int r = sscanf( ts, "%4d-%02d-%02dT%02d:%02dZ",
109 &objTm.tm_year,
110 &objTm.tm_mon,
111 &objTm.tm_mday,
112 &objTm.tm_hour,
113 &objTm.tm_min );
114 if( r != 5 ) throw std::runtime_error("Cannot read timestamp from " + std::string(ts) );
115 objTm.tm_year -= 1900;
116 objTm.tm_mon -= 1;
117 objTm.tm_sec = 0;
118
119 time_t res = mktime( &objTm );
120 if( res < 0 ) throw std::runtime_error("Cannot make timestamp from " + std::string(ts) );
121 return res;
122 }
123
124 // ----------------------------------------------------------------------------
125
126 /// Some chars are forbidden in HTML documents. This replaces them by HTML entities.
127 // We do not need to create a temporary copy of the transformed string.
128 // See Html entities here: http://www.w3schools.com/tags/ref_entities.asp
StripHtmlTags(std::ostream & ostrm,const char * beg,bool newLines=false)129 static void StripHtmlTags( std::ostream & ostrm, const char * beg, bool newLines = false )
130 {
131 const char * ptr = NULL;
132 // TODO: Consider ¢ £ ¥ € § © ® ™
133 for( const char * it = beg ; ; ++it )
134 {
135 /** Other characters are filtered:
136 * U+0009, U+000A, U+000D: these are the only C0 controls accepted in XML 1.0;
137 * U+0020–U+D7FF, U+E000–U+FFFD: */
138 char ch = *it ;
139 switch( ch )
140 {
141 case 0x01 ... 0x08 :
142 // case 0x09 :
143 // case 0x0A :
144 case 0x0B ... 0x0C :
145 // case 0x0D :
146 case 0x0E ... 0x0F :
147 ptr = " "; break;
148 case '"' : ptr = """; break;
149 case '\'' : ptr = "'"; break;
150 case '&' : ptr = "&" ; break;
151 case '<' : ptr = "<" ; break;
152 case '>' : ptr = ">" ; break;
153 case '\0' : break ;
154 case '\n' : if(newLines) // Should we replace new lines by "<BR>" ?
155 {
156 ptr = "<BR>" ;
157 break;
158 }
159 // Otherwise we print the newline char like the other chars.
160 default : continue ;
161 }
162 if( it != beg ) {
163 ostrm.write( beg, it - beg );
164 }
165
166 if( ch == '\0' ) break ;
167 assert(ptr);
168 ostrm << ptr ;
169 beg = it + 1 ;
170 }
171 }
172
173 /// Some values such as Navtex messages may contain newline chars.
StripHtmlTagsNl(std::ostream & ostrm,const std::string & beg)174 static void StripHtmlTagsNl( std::ostream & ostrm, const std::string & beg ) {
175 StripHtmlTags( ostrm, beg.c_str(), true );
176 }
177
StripHtmlTags(std::ostream & ostrm,const std::string & beg)178 static void StripHtmlTags( std::ostream & ostrm, const std::string & beg ) {
179 StripHtmlTags( ostrm, beg.c_str() );
180 }
181
182 // ----------------------------------------------------------------------------
183
184 /// Also used when reloading a KML file.
Push(const char * k,const std::string & v)185 void KmlServer::CustomDataT::Push( const char * k, const std::string & v ) {
186 push_back( value_type( k, v ) );
187 }
188
189 // ----------------------------------------------------------------------------
190
191 /// Different sorts of KML datas. This list is hardcoded but they are processed identically.
192 static const char * categories[] = {
193 "User",
194 "Synop",
195 "Navtex"
196 };
197 static const size_t nb_categories = sizeof(categories) / sizeof(*categories);
198
199 /// Written at the beginning of each KML document.
KmlHeader(std::ostream & ostrm,const std::string & title)200 static void KmlHeader( std::ostream & ostrm, const std::string & title ) {
201 ostrm <<
202 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
203 "<kml xmlns=\"http://earth.google.com/kml/2.1\">\n"
204 "<Document>\n"
205 "<name>" << title << "</name>\n" ;
206 }
207
208 /// Appended at the end of each KML document.
209 static const char KmlFooter[] =
210 "</Document>\n"
211 "</kml>\n" ;
212
213 /// Contains for example GIF images, and all the styles. Can be customised by the user.
214 static const std::string namStyles = "styles.kml";
215
216 /** Used to code the data for reloading. The description tag is too complicated
217 * to parse and its serialization might not be reversible.
218 * We use preprocessor constants instead of char arrays so they can be concatenated at compile-time.
219 * The tags must be as short as possible but the values easy to deserialize.
220 * */
221 #define FLDIGI_TAG_ITM "fldigi:itm"
222 #define FLDIGI_TAG_EVT "fldigi:evt"
223 #define FLDIGI_TAG_KEY "k"
224 #define FLDIGI_TAG_VAL "v"
225 #define FLDIGI_TIMESTAMP "ts"
226
227 /// Global singleton of all KML-related things.
228 class KmlSrvImpl : public KmlServer {
229 volatile bool m_loaded ; /// Set when ready for operation.
230 std::string m_kml_dir; /// Where kml files are saved.
231 std::string m_command; /// Started each time KML files are saved.
232 int m_pid_command; /// Process id of the running commend for KML files.
233 double m_merge_dist; /// Below this, placemark with same name are merged.
234 int m_retention_delay; /// Purge old data.
235 int m_refresh_interval; /// In seconds, written in KML files.
236 int m_balloon_style; /// Display style in KML balloons: Lines, matrices, plain text.
237 pthread_t m_writer_thread ; /// Periodically woken up to write things to the KML file.
238
239 /// Models a KML placemark. It contains events indexed my a timestamp.
240 /// We need an ordered container in order to remove old data in one erase.
241 class PlacemarkT : public std::multimap< time_t, CustomDataT > {
242 CoordinateT::Pair m_coord;
243 double m_altitude;
244 std::string m_styleNam; // The icon.
245 std::string m_kmlId ; // Unique KML id for the placemark.
246 std::string m_descrTxt ;// KML snippet.
247
248 /// Serialize the internal data to XML so they can be easily read.
SerializeForReading(std::ostream & ostrm) const249 void SerializeForReading( std::ostream & ostrm ) const {
250
251 /// Custom data elements for reading the content when restarting.
252 ostrm << "<ExtendedData xmlns:fldigi=\"http://www.w1hkj.com\">\n";
253
254 /// Print from the most recent event, which is at the end.
255 for( const_iterator itEvt = begin(), enEvt = end(); itEvt != enEvt; ++itEvt )
256 {
257 ostrm << "<" FLDIGI_TAG_EVT " " FLDIGI_TIMESTAMP "=\"";
258 KmlTimestamp(ostrm,itEvt->first);
259 ostrm << "\">\n" ;
260 const CustomDataT & refCust = itEvt->second;
261 for( CustomDataT::const_iterator it = refCust.begin(), en = refCust.end(); it != en; ++it )
262 {
263 ostrm << "<" FLDIGI_TAG_ITM " " FLDIGI_TAG_KEY "=\"" << it->first
264 << "\" " FLDIGI_TAG_VAL "=\"";
265 StripHtmlTags(ostrm,it->second);
266 ostrm << "\" />";
267 }
268 ostrm << "</" FLDIGI_TAG_EVT ">\n" ;
269 }
270 /// Dumps all events in natural order
271 ostrm << "</ExtendedData>\n";
272 }
273 public:
274 /// Constructor called by "Broadcast", not from reading a KML file.
PlacemarkT(const CoordinateT::Pair & refCoo,double altitude,const std::string & styleNam,const std::string & kmlNam)275 PlacemarkT(
276 const CoordinateT::Pair & refCoo,
277 double altitude,
278 const std::string & styleNam,
279 const std::string & kmlNam )
280 : m_coord( refCoo )
281 , m_altitude( altitude )
282 , m_styleNam( styleNam ) {
283 /// The unique key is indeed the name and the time,
284 /// because an object such as a ship might move and come back to the same place.
285 /// We add a counter because during tests, the timestamps are too close.
286 static int dummyCnt = 0 ;
287 std::stringstream strm ;
288 /// No need to store the kml name because it is the multimap key.
289 StripHtmlTags(strm,kmlNam);
290 strm << ':' << Tm2Time() << ':' << ++dummyCnt ;
291 m_kmlId = strm.str();
292 }
293
294 /// Constructor for deserialization. Strings comes from the KML file.
PlacemarkT()295 PlacemarkT() : m_altitude(0.0) {}
296
Clear()297 void Clear() {
298 m_styleNam.clear();
299 m_kmlId.clear();
300 m_descrTxt.clear();
301 clear();
302 }
303
304 /// Used when reading a KML file. Read coordinates and altitude from a string when reloading a KML file.
SetCoordinates(const char * str)305 void SetCoordinates( const char * str ) {
306 double lon, lat ;
307 if( str == NULL ) {
308 throw std::runtime_error("Null coordinates text");
309 }
310 int r = sscanf( str, "%lf,%lf,%lf", &lon, &lat, &m_altitude );
311 if( r != 3 ) {
312 static const std::string msg("Cannot read coordinates and altitude:");
313 throw std::runtime_error(msg+str);
314 }
315 m_coord = CoordinateT::Pair( lon, lat );
316 }
317
318 /// Used when reading a KML file. HTML entities are already removed. "+1" is for the "#".
SetStyle(const char * str)319 void SetStyle( const char * str ) {
320 if(str == NULL ) throw std::runtime_error("Null input style");
321 size_t sz = namStyles.size();
322 // If the strings are equal, then strlen(str) >= sz, and str[sz] == '\0' if equality.
323 if( ( 0 == strncmp( str, namStyles.c_str(), sz ) ) && ( str[sz] == '#' ) ) {
324 m_styleNam = str + sz + 1;
325 } else {
326 LOG_INFO("Inconsistent URL style:%s",str );
327 m_styleNam = str ;
328 }
329 }
330
331 /// Used when reading a KML file.
SetKmlId(const char * str)332 void SetKmlId( const char * str ) {
333 std::stringstream strm ;
334 // The KML deserializer irrXML transforms the HTML entities
335 // into normal chars. We must do the reverse transformation.
336 StripHtmlTags(strm,str);
337 m_kmlId = strm.str();
338 }
339
340 /// Just add the events without suppressing duplicate information.
AppendEvent(time_t evtTim,const std::string & descrTxt,const CustomDataT & custDat)341 void AppendEvent(
342 time_t evtTim,
343 const std::string & descrTxt,
344 const CustomDataT & custDat )
345 {
346 if( m_descrTxt.empty() )
347 m_descrTxt = descrTxt ;
348 else
349 if( ! descrTxt.empty() )
350 {
351 // We want to ensure that it is not a miscommunication.
352 if( NULL == strstr( m_descrTxt.c_str(), descrTxt.c_str() ) ) {
353 m_descrTxt += "," + descrTxt ;
354 }
355 }
356
357 /// Default time is now. evtTim might have another special value.
358 if(evtTim == 0) {
359 evtTim = time(NULL);
360 }
361 insert( value_type( evtTim, custDat ) );
362 }
363
coordinates() const364 const CoordinateT::Pair & coordinates() const { return m_coord;}
altitude(void) const365 double altitude(void) const { return m_altitude;}
style(void) const366 const std::string & style(void) const { return m_styleNam; }
KmlId(void) const367 const std::string & KmlId(void) const { return m_kmlId; }
368
369 /// When writing the style to the KML file.
styleToKml(std::ostream & ostrm) const370 void styleToKml(std::ostream & ostrm) const {
371 ostrm << namStyles << '#';
372 StripHtmlTags(ostrm,m_styleNam);
373 }
374
375 /// Used when several PlacemarkT with the same kmlNam but different styles. Keep the first only.
style(const std::string & styl)376 void style(const std::string & styl) { m_styleNam = styl; }
377
378 /// This is NOT the Euclidian distance but tries to reflect a position change on a 3D map.
distance_to(const PlacemarkT & refOther) const379 double distance_to( const PlacemarkT & refOther ) const {
380 double delta_altitude_km = fabs( m_altitude - refOther.m_altitude ) * 0.001 ;
381 // Big coefficient for the altitude, to show on the map what happens.
382 double horiz_dist = m_coord.distance( refOther.m_coord );
383 return horiz_dist + 10 * delta_altitude_km ;
384 }
385
386 /// Adds the events of another placemark. Manages events enforced to be unique.
concatenate(const PlacemarkT & refOther)387 void concatenate( const PlacemarkT & refOther ) {
388 if( refOther.empty() ) return ;
389
390 time_t firstTm = refOther.begin()->first ;
391 if( firstTm == KmlServer::UniqueEvent ) {
392 clear();
393 /// We keep this special time_t value to enforce unicity of CustomDataT.
394 insert( *refOther.begin() );
395 } else {
396 insert( refOther.begin(), refOther.end() );
397 }
398 } // PlacemarkT::concatenate
399
400 /// This transforms our coordinates into KML ones.
WriteCooSub(std::ostream & ostrm) const401 void WriteCooSub( std::ostream & ostrm ) const
402 {
403 ostrm << m_coord.longitude().angle() << ','
404 << m_coord.latitude().angle() << ','
405 << m_altitude ;
406 }
407
408 /// Writes the placemark to a KML stream.
Serialize(std::ostream & ostrm,const std::string & kmlNam,int balloon_style) const409 void Serialize( std::ostream & ostrm, const std::string & kmlNam, int balloon_style ) const
410 {
411 // Range of events which occured at this place.
412 const_reverse_iterator beEvt = rbegin(), enEvt = rend();
413
414 // There should be at least one event.
415 if( beEvt == enEvt ) {
416 LOG_WARN("Inconsistency: No event kmlId=%s",m_kmlId.c_str() );
417 return ;
418 }
419
420 // The unique key is indeed the name and the time,
421 // because an object such as a ship might move and come back to the same place.
422 // We add a counter because during tests, the timestamps are too close.
423 ostrm << "<Placemark id=\"" << m_kmlId << "\">\n";
424
425 // Beware of the sign of longitude.
426 ostrm << "<Point><coordinates>" ;
427 WriteCooSub( ostrm );
428 ostrm << "</coordinates></Point>\n";
429
430 if( kmlNam.empty() )
431 ostrm << "<name>No name</name>\n";
432 else {
433 // Looks like there is a bug in KML when displaying a placemark ID containing an hyphen.
434 ostrm << "<name>";
435 StripHtmlTags(ostrm,kmlNam);
436 ostrm << "</name>\n";
437 }
438
439 ostrm << "<styleUrl>";
440 styleToKml(ostrm);
441 ostrm << "</styleUrl>\n";
442
443 // More information here: http://freegeographytools.com/2007/putting-time-data-into-a-kml-file
444 // 1944-06-06T06:00:00 See http://www.w3.org/TR/xmlschema-2/#isoformats
445 // We do not add the timestamps because it is not very ergonomic. Should be added to the linestrings too.
446 static const bool withTimestamp = false ;
447 if( withTimestamp ) {
448 // Last update time is last argument.
449 ostrm << "<Timestamp><when>";
450 KmlTimestamp(ostrm,enEvt->first);
451 ostrm << "</when></Timestamp>\n";
452 }
453
454 /// Whats is displayed on the margin. Must be short.
455 ostrm << "<Snippet maxLines=\"1\">";
456 StripHtmlTags(ostrm,m_descrTxt);
457 ostrm << "</Snippet>\n";
458
459
460 /**
461 * Unfortunately it is not possible to use CSS in Google-maps, due to "content scrubbing":
462 * http://stackoverflow.com/questions/8421260/styling-kml-with-css-in-google-maps-v3
463 * Scrubbing the contents is a security measure to prevent malicious code from executing.
464 * It removes JavaScript, CSS, iframe, embed and object tags.
465 */
466 static const char * colorTime = "#0099CC";
467 static const char * colorKey = "#FF9933";
468
469 ostrm << "<description>";
470
471 /// Data can be displayed into one big matrix, or several tables, one per event.
472 switch( balloon_style )
473 {
474 case 0: /// Plain text, for example for GPX conversion.
475 for( const_reverse_iterator itEvt = beEvt; itEvt != enEvt; ++itEvt )
476 {
477 ostrm << "Timestamp:" << Tm2Time(itEvt->first) << "\n" ;
478 const CustomDataT & refCust = itEvt->second;
479 for( CustomDataT::const_iterator it = refCust.begin(), en = refCust.end(); it != en; ++it )
480 {
481 StripHtmlTags(ostrm,it->first);
482 ostrm << ":";
483 /// Do not insert <br> tags.
484 StripHtmlTags(ostrm,it->second);
485 ostrm << "\n" ;
486 }
487 ostrm << "\n" ;
488 }
489 break;
490 case 1: // One distinct HTML table per event.
491 ostrm << "<![CDATA[<table border=\"1\">";
492 // Print from the most recent event, which is at the end.
493 for( const_reverse_iterator itEvt = beEvt; itEvt != enEvt; ++itEvt )
494 {
495 ostrm << "<tr>";
496 ostrm << "<td bgcolor=" << colorTime << " colspan=\"2\">" << Tm2Time(itEvt->first) << "</td>"
497 "</tr>" ;
498 const CustomDataT & refCust = itEvt->second;
499 for( CustomDataT::const_iterator it = refCust.begin(), en = refCust.end(); it != en; ++it )
500 {
501 ostrm << "<tr>" "<td>";
502 StripHtmlTags(ostrm,it->first);
503 ostrm << "</td>" "<td>";
504 StripHtmlTagsNl(ostrm,it->second);
505 ostrm << "</td>" "</tr>" ;
506 }
507 }
508 ostrm << "</table>]]>\n";
509 break;
510 case 2 :
511 {
512 /// Transposition of the html matrix.
513 typedef std::vector<time_t> TitleT ;
514 TitleT titles ;
515 typedef std::vector< std::string > RowT ;
516
517 const_iterator beCol = begin(), enCol = end();
518 for( const_iterator itCol = beCol; itCol != enCol; ++itCol )
519 {
520 titles.push_back( itCol->first );
521 }
522 size_t nbCols = titles.size();
523
524 typedef std::map< std::string, RowT > MtxT;
525 MtxT mtx ;
526
527 size_t iCols = 0;
528 for( const_iterator itCol = beCol; itCol != enCol; ++itCol, ++iCols )
529 {
530 const CustomDataT & refCust = itCol->second;
531 for( CustomDataT::const_iterator it = refCust.begin(), en = refCust.end(); it != en; ++it )
532 {
533 MtxT::iterator itMtx = mtx.find( it->first );
534 if( itMtx == mtx.end() ) {
535 itMtx = mtx.insert( mtx.end(), MtxT::value_type( it->first, RowT() ) );
536 itMtx->second.resize(nbCols);
537 }
538 itMtx->second[iCols] = it->second ;
539 }
540 }
541
542 ostrm << "<![CDATA[<table border=\"1\">";
543 ostrm << "<tr><td></td>";
544 for( size_t iCols = 0; iCols < nbCols; ++iCols ) {
545 ostrm << "<td bgcolor=" << colorTime << ">" << Tm2Time(titles[iCols]) << "</td>";
546 }
547 ostrm << "</tr>" ;
548
549 for( MtxT::const_iterator itMtx = mtx.begin(), enMtx = mtx.end(); itMtx != enMtx; ++itMtx )
550 {
551 ostrm << "<tr>";
552 ostrm << "<td bgcolor=" << colorKey << ">";
553 StripHtmlTags(ostrm,itMtx->first);
554 ostrm << "</td>";
555
556 // TODO: Do not write twice the same value if it is not numeric (Starting with a digit)
557 // or longer than N characters.
558 for(
559 RowT::const_iterator itRow = itMtx->second.begin(), enRow = itMtx->second.end();
560 itRow != enRow;
561 ++itRow )
562 {
563 ostrm << "<td>";
564 StripHtmlTags(ostrm,*itRow);
565 ostrm << "</td>";
566 }
567 ostrm << "</tr>" ;
568 }
569 ostrm << "</table>]]>\n";
570 break;
571 }
572 }
573 ostrm << "</description>\n";
574 // TODO: Other dsplay style: All elements on a one single table. Removal of duplicate text values etc...
575
576
577 SerializeForReading( ostrm );
578
579 ostrm << "</Placemark>\n";
580 } // PlacemarkT::Serialize
581 }; // PlacemarkT
582
583 /** The placemark name is unique wrt to the application.
584 It might map to several PlaceMark, each having distinct coordinates.
585 It is not really possible to sort the different locations by coordinates,
586 because it is a list created by the object trajectory.
587 class PlacesMapT : public std::multimap< std::string, PlacemarkT >
588 */
589 class PlacesMapT : public std::multimap< std::string, PlacemarkT >
590 {
591 /// Written to by the main thread with lock protection, read (and emptied)
592 /// by the sub thread which is later in charge of writing things to disk.
593 typedef std::list< value_type > PlacemarkListT ;
594
595 /// A separate queue helps for performance because the insertion in the main container
596 /// might take time and the main thread may lose data.
597 PlacemarkListT m_queue_to_insert ;
598
599 /// This is not set when inserting at load time, but when emptying
600 /// the queue, and when data are ready for writing to disk.
601 mutable bool m_must_save ;
602
603 public:
PlacesMapT()604 PlacesMapT() : m_must_save(false) {}
605
606 /// Finds an object with the same name and close enough.
607 /// If an object with the same name exists but is distant, creates a new one,
608 /// plus a path between the two.
609 /// Called by the main thread at startup when loading the previous KML files. Then later
610 /// called by the subthread in charge of flushing PlacemarkT to the KML file.
DirectInsert(const value_type & refVL,double merge_dist)611 void DirectInsert( const value_type & refVL, double merge_dist )
612 {
613 /// Not needed to use equal_range because we need the last placemark matching
614 /// this key, and this iterator is forward_iterator only.
615 iterator it = find( refVL.first ), en = end() ;
616 if( it == en ) {
617 LOG_INFO("Cannot find '%s'", refVL.first.c_str() );
618 it = insert( end(), refVL );
619 return;
620 }
621
622 /// Searches for the last element with the same key.
623 iterator last = it, next = it ;
624 ++next ;
625 while( next != en && next->first == refVL.first ) {
626 last = next;
627 ++next ;
628 }
629
630 double dist = last->second.distance_to( refVL.second );
631
632 /// We can reuse the last element because it is not too far from our coordinates.
633 if( 1000 * dist < merge_dist ) {
634 /// LOG_INFO("Reusing '%s' merge_dist=%lf", refVL.first.c_str(), dist );
635
636 /** There will be one event only if adding a new received event,
637 otherwise several if reloading from a file.
638 The new events will be inserted based on their timestamp.
639 */
640 // last->second.insert( refVL.second.begin(), refVL.second.end() );
641 last->second.concatenate( refVL.second );
642 return ;
643 }
644
645 LOG_INFO("Inserted '%s' merge_dist=%lf", refVL.first.c_str(), dist );
646
647 /// The object is inserted at the end of all elements with the same key.
648 iterator ret = insert( next, refVL );
649
650 /// Runtime check of an assumption.
651 {
652 iterator tst = last ;
653 ++tst ;
654 if( tst != ret ) {
655 LOG_WARN("Iterators assumption is wrong (1): %s", refVL.first.c_str() );
656 }
657 ++tst ;
658 if( tst != next ) {
659 LOG_WARN("Iterators assumption is wrong (2): %s", refVL.first.c_str() );
660 }
661 }
662
663 /// They must have the same style otherwise they will be in different folders.
664 if( refVL.second.style() != last->second.style() ) {
665 LOG_WARN("Correcting style discrepancy %s: %s != %s",
666 refVL.first.c_str(),
667 refVL.second.style().c_str(),
668 last->second.style().c_str() );
669 ret->second.style( last->second.style() );
670 }
671 } // DirectInsert
672
673 /// Enqueues a new placemark for insertion by the subthread. Called by the main thread
674 /// each time a Broadcast of a new PlacemarkT is done.
Enqueue(const std::string & kmlNam,const PlacemarkT & refPM)675 void Enqueue( const std::string & kmlNam, const PlacemarkT & refPM ) {
676 /// So we will save to a file, because something changed.
677 m_queue_to_insert.push_back( value_type( kmlNam, refPM ) );
678 }
679
680 /// Called by the subthread. It can merge data of placemarks with the same name
681 /// and different positions due to a move. This has to be very fast because under lock protection.
FlushQueue(double merge_dist)682 void FlushQueue(double merge_dist) {
683 LOG_INFO("FlushQueue nbelts %d sz=%d",
684 static_cast<int>(m_queue_to_insert.size()),
685 static_cast<int>(size()) );
686
687 if( m_queue_to_insert.empty() ) return ;
688
689 for( PlacemarkListT::iterator itPL = m_queue_to_insert.begin(), enPL = m_queue_to_insert.end(); itPL != enPL; ++ itPL )
690 {
691 DirectInsert( *itPL, merge_dist );
692 }
693 LOG_INFO("Flushed into sz=%d", static_cast<int>(size()) );
694
695 // TODO: If lock contention problems, we might swap this list with another one owned by this
696 // objet. This would later be merged into the container before saving data to disk.
697 m_queue_to_insert.clear();
698 m_must_save = true ;
699 }
700
701 /// Removes obsolete data for one category only.
PruneKmlFile(int retention_delay)702 void PruneKmlFile( int retention_delay )
703 {
704 /// By convention, it means keeping all data.
705 if( retention_delay <= 0 ) return ;
706
707 /// Called only once per hour, instead of at every call. Saves CPU.
708 static time_t prev_call_tm = 0 ;
709 time_t now = time(NULL);
710
711 static const int seconds_per_hour = 60 * 60 ;
712
713 /// First call of this function, always do the processing.
714 if( prev_call_tm != 0 ) {
715 /// If this was called for less than one hour, then return.
716 if( prev_call_tm > now - seconds_per_hour ) return ;
717 }
718 prev_call_tm = now ;
719
720 /// Cleanup all data older than this.
721 time_t limit_time = now - retention_delay * seconds_per_hour ;
722
723 LOG_INFO("sz=%d retention=%d hours now=%s limit=%s",
724 (int)size(), retention_delay,
725 KmlTimestamp(now).c_str(), KmlTimestamp(limit_time).c_str() );
726
727 size_t nbFullErased = 0 ;
728 size_t nbPartErased = 0 ;
729 for( iterator itMap = begin(), nxtMap = itMap, enMap = end() ; itMap != enMap; itMap = nxtMap ) {
730 PlacemarkT & refP = itMap->second ;
731 ++nxtMap ;
732
733 /// Erases all elements older than the limit, or the whole element.
734 PlacemarkT::iterator itP = refP.upper_bound( limit_time );
735 if( itP == refP.end() ) {
736 erase( itMap );
737 ++nbFullErased ;
738 } else if( itP != refP.begin() ) {
739 refP.erase( refP.begin(), itP );
740 ++nbPartErased ;
741 }
742 }
743
744 // Maybe the container only lost data because of data expiration, so it must be saved.
745 bool must_save_now = m_must_save || ( nbFullErased > 0 ) || ( nbPartErased > 0 ) ;
746 LOG_INFO("Sz=%d FullyErased=%d PartialErased=%d m_must_save=%d must_save_now=%d",
747 (int)size(), (int)nbFullErased, (int)nbPartErased, m_must_save, must_save_now );
748 m_must_save = must_save_now ;
749 }
750
751 /// This is not efficient because we reopen the file at each access, but it ensures
752 /// that the file is consistent and accessible at any moment.
RewriteKmlFileOneCategory(const std::string & category,const std::string & kmlFilNam,int balloon_style) const753 bool RewriteKmlFileOneCategory(
754 const std::string & category,
755 const std::string & kmlFilNam,
756 int balloon_style ) const {
757 // Normally, it is stable when we insert an element with a duplicate key.
758 typedef std::multiset< PlacesMapT::const_iterator, PlacesMapIterSortT > PlacesMapItrSetT ;
759
760 PlacesMapItrSetT plcMapIterSet ;
761
762 /// If there is nothing to save, not needed to create a file.
763 if( false == m_must_save ) return false ;
764
765 /// For safety purpose, we do not empty the file. It might be an error.
766 if( empty() ) {
767 LOG_INFO("Should empty KML file %s. Grace period.", kmlFilNam.c_str() );
768 return false ;
769 }
770
771 m_must_save = false ;
772
773 for( const_iterator itPlcMap = begin(), en = end(); itPlcMap != en ; ++itPlcMap )
774 {
775 plcMapIterSet.insert( itPlcMap );
776 }
777 int nbPlacemarks = plcMapIterSet.size();
778
779 // This file must be atomic because it is periodically read.
780 // TODO: Checks if another process has locked the ".tmp" file.
781 // If so , we should reread the KML file and merge our data with it.
782 // This would allow to have several fldigi sessions simultaneously running
783 // on the same KML files. On the other hand, it should already work if these
784 // processes are updating different categories, which is more probable.
785 AtomicRenamer ar( kmlFilNam );
786
787 KmlHeader(ar, category) ;
788
789 // This will easily allow hierarchical folders.
790 std::string lastStyle ;
791 for( PlacesMapItrSetT::const_iterator itStyl = plcMapIterSet.begin(), enStyl = plcMapIterSet.end(); itStyl != enStyl; )
792 {
793 ar << "<Folder>";
794
795 // Hyphen: No idea why, but the string "10-meter discus buoy:W GULF 207 NM " just displays as "10-"
796 ar << "<name>";
797 StripHtmlTags( ar,(*itStyl)->second.style() );
798 ar << "</name>";
799
800 // Several placemarks with the same name: A single object has moved.
801 PlacesMapItrSetT::const_iterator itStylNext = itStyl ;
802 for(;;) {
803 // TODO: Objects with the same name and different coordinates must be signaled so.
804 (*itStylNext)->second.Serialize( ar, (*itStylNext)->first, balloon_style);
805 ++itStylNext ;
806 if( itStylNext == enStyl ) break ;
807 if( (*itStyl)->second.style() != (*itStylNext)->second.style() ) break ;
808 }
809
810 // Now, in the same loop, we draw the polylines between placemarks with the same name.
811 for( PlacesMapItrSetT::const_iterator itNamBeg = itStyl, itNamLast = itNamBeg; itNamLast != itStylNext ; )
812 {
813 PlacesMapItrSetT::const_iterator itNamNxt = itNamLast;
814 ++itNamNxt ;
815 if( ( itNamNxt == itStylNext ) || ( (*itNamNxt)->first != (*itNamLast)->first ) ) {
816 // No point tracing a line with one point only.
817 if( *itNamBeg != *itNamLast ) {
818 DrawPolyline( ar, category, *itNamBeg, *itNamLast );
819 }
820 itNamBeg = itNamNxt ;
821 }
822 itNamLast = itNamNxt ;
823 }
824
825 itStyl = itStylNext ;
826 ar << "</Folder>";
827 }
828
829 ar << KmlFooter ;
830
831 LOG_INFO("Saved %s: %d placemarks to %s",
832 category.c_str(), nbPlacemarks, kmlFilNam.c_str() );
833 return true ;
834 } // KmlSrvImpl::PlacesMapT::RewriteKmlFileOneCategory
835
836 }; // KmlSrvImpl::PlacesMapT
837
838 /// There is a very small number of categories: Synop, Navtex etc...
839 struct PlacemarksCacheT : public std::map< std::string, PlacesMapT > {
FindCategoryKmlSrvImpl::PlacemarksCacheT840 const PlacesMapT * FindCategory( const std::string & category ) const {
841 const_iterator it = find( category );
842 if( it == end() ) return NULL ;
843 return &it->second;
844 }
FindCategoryKmlSrvImpl::PlacemarksCacheT845 PlacesMapT * FindCategory( const std::string & category ) {
846 iterator it = find( category );
847 if( it == end() )
848 it = insert( end(), value_type( category, PlacesMapT() ) );
849 return &it->second ;
850 }
851 };
852
853 /// At startup, should be reloaded from the various KML files.
854 PlacemarksCacheT m_placemarks ;
855
856 /// Used for signaling errors.
close_throw(FILE * ofil,const std::string & msg) const857 void close_throw( FILE * ofil, const std::string & msg ) const {
858 if( ofil ) fclose( ofil );
859 throw std::runtime_error( strformat( "%s:%s", msg.c_str(), strerror(errno) ) );
860 }
861
862 /// This points to the specific KML file of the category.
CategoryNetworkLink(std::ostream & strm,const std::string & category) const863 void CategoryNetworkLink( std::ostream & strm, const std::string & category ) const
864 {
865 strm <<
866 "<NetworkLink>\n"
867 " <name>" << category << "</name>\n"
868 " <Link>\n"
869 " <href>" << category << ".kml</href>\n"
870 " <refreshMode>onInterval</refreshMode>\n"
871 " <refreshInterval>" << m_refresh_interval << "</refreshInterval>\n"
872 " </Link>\n"
873 "</NetworkLink>\n";
874 } // KmlSrvImpl::CategoryNetworkLink
875
876 /// This file copy does not need to be atomic because it happens once only.
CopyStyleFileIfNotExists(void)877 void CopyStyleFileIfNotExists(void) {
878 /// Where the installed file is stored and never moved from. Used as default.
879 std::string namSrc = PKGDATADIR "/kml/" + namStyles ;
880
881 /// The use might customize its styles file: It will not be altered.
882 std::string namDst = m_kml_dir + namStyles ;
883
884 /// Used to copy the master file to the user copy if needed.
885 FILE * filSrc = NULL;
886 FILE * filDst = fl_fopen( namDst.c_str(), "r" );
887
888 /// If the file is there, leave as it is because it is maybe customize.
889 if( filDst ) {
890 LOG_INFO("Style file %s not altered", namDst.c_str() );
891 goto close_and_quit ;
892 }
893 filDst = fl_fopen( namDst.c_str(), "w" );
894 if( filDst == NULL ) {
895 LOG_INFO("Cannot open destination style file %s", namDst.c_str() );
896 goto close_and_quit ;
897 }
898 filSrc = fl_fopen( namSrc.c_str(), "r" );
899 if( filSrc == NULL ) {
900 LOG_INFO("Cannot open source style file %s", namSrc.c_str() );
901 goto close_and_quit ;
902 }
903
904 /// Transient buffer to copy the file.
905 char buffer[BUFSIZ];
906 size_t n;
907
908 while ((n = fread(buffer, sizeof(char), sizeof(buffer), filSrc)) > 0)
909 {
910 if (fwrite(buffer, sizeof(char), n, filDst) != n) {
911 LOG_WARN("Error %s copying style file %s to %s", strerror(errno), namSrc.c_str(), namDst.c_str() );
912 goto close_and_quit ;
913 }
914 }
915 LOG_INFO("Style file %s copied to %s", namSrc.c_str(), namDst.c_str() );
916 close_and_quit:
917 if( filDst ) fclose(filDst);
918 if( filSrc ) fclose(filSrc);
919 }
920
921 /// This creates a KML file with links to the categories such as Synop and Navtex.
CreateMainKmlFile(void)922 void CreateMainKmlFile(void) {
923 // This is the file, that the user must click on.
924 std::string baseFil = m_kml_dir + "fldigi.kml" ;
925
926 LOG_INFO("Creating baseFil=%s", baseFil.c_str() );
927
928 /// We do not need to make this file atomic because it is read once only.
929 AtomicRenamer ar( baseFil );
930
931 KmlHeader( ar, "Fldigi");
932
933 for( size_t i = 0; i < nb_categories; ++i )
934 CategoryNetworkLink( ar, categories[i] );
935
936 ar << KmlFooter ;
937 }
938
939 // TODO: Consider hierarchical categories: "Synop/buoy/Inmarsat"
940
941 /// A specific name is chosen so we can later update this line.
DrawPolyline(std::ostream & ostrm,const std::string & category,PlacesMapT::const_iterator beg,PlacesMapT::const_iterator last)942 static void DrawPolyline(
943 std::ostream & ostrm,
944 const std::string & category,
945 PlacesMapT::const_iterator beg,
946 PlacesMapT::const_iterator last ) {
947
948 /// The polyline gets an id based on the beginning of the path, which will never change.
949 ostrm
950 << "<Placemark id=\"" << beg->second.KmlId() << ":Path\">"
951 "<name>" << beg->second.KmlId() << "</name>"
952 "<LineString>"
953 "<altitudeMode>clampToGround</altitudeMode><tessellate>1</tessellate>\n"
954 "<coordinates>\n";
955 double dist = 0.0 ;
956 int nbStops = 0 ;
957 // "135.2, 35.4, 0.
958 for(;;) {
959 beg->second.WriteCooSub( ostrm );
960 ostrm << "\n";
961 if( beg == last ) break ;
962 PlacesMapT::const_iterator next = beg ;
963 ++next ;
964 ++nbStops;
965 dist += beg->second.coordinates().distance( next->second.coordinates() );
966 beg = next ;
967 };
968
969 ostrm << "</coordinates>"
970 "</LineString>"
971 "<Snippet>" << dist << " " << _("kilometers") << " in " << nbStops << " " << _("stops") << "</Snippet>"
972 "<Style>"
973 "<LineStyle><color>#ff0000ff</color></LineStyle> "
974 "</Style>"
975 "</Placemark>\n";
976 } // DrawPolyline
977
978 /// Similar to a std::ofstream but atomically updated by renaming a temp file.
979 struct AtomicRenamer : public std::ofstream {
980 /// Target file name.
981 std::string m_filnam ;
982 /// Temporary file name. Renamed to the target when closed.
983 std::string m_filtmp ;
984 public:
985 /// This opens a temporary file when all the writing is done.
AtomicRenamerKmlSrvImpl::AtomicRenamer986 AtomicRenamer( const std::string & filnam )
987 : m_filnam( filnam )
988 , m_filtmp( filnam + ".tmp" )
989 {
990 LOG_INFO("AtomicRenamer opening tmp %s", filnam.c_str() );
991 open( m_filtmp.c_str() );
992 if( bad() ) {
993 LOG_WARN("Cannot open %s", m_filtmp.c_str() );
994 }
995 }
996
997 /// Atomic because rename is an atomic too, and very fast if in same directory.
~AtomicRenamerKmlSrvImpl::AtomicRenamer998 ~AtomicRenamer() {
999 close();
1000 /// This is needed on Windows.
1001 int ret_rm = remove( m_filnam.c_str() );
1002 if( ( ret_rm != 0 ) && ( errno != ENOENT ) ) {
1003 LOG_WARN("Cannot remove %s: %s", m_filnam.c_str(), strerror(errno) );
1004 }
1005 int ret_mv = rename( m_filtmp.c_str(), m_filnam.c_str() );
1006 if( ret_mv ) {
1007 LOG_WARN("Cannot rename %s to %s:%s", m_filtmp.c_str(), m_filnam.c_str(), strerror(errno) );
1008 }
1009 }
1010 };
1011
1012 /// The KML filename associated to a category.
CategFile(const std::string & category) const1013 std::string CategFile( const std::string & category ) const {
1014 return m_kml_dir + category + ".kml";
1015 }
1016
1017 /// Resets the files. Called from the test program and the GUI.
CreateNewKmlFile(const std::string & category) const1018 void CreateNewKmlFile( const std::string & category ) const {
1019 // This file must be atomic because it is periodically read.
1020 AtomicRenamer ar( CategFile( category ) );
1021 KmlHeader( ar, category);
1022 ar << KmlFooter ;
1023 }
1024
1025 /// Template parameters should not be local types.
1026 struct PlacesMapIterSortT {
1027 // This sort iterators on placemarks, based on the style name then the placemark name.
operator ()KmlSrvImpl::PlacesMapIterSortT1028 bool operator()( const PlacesMapT::const_iterator & it1, const PlacesMapT::const_iterator & it2 ) const {
1029 int res = it1->second.style().compare( it2->second.style() );
1030 if( res == 0 )
1031 res = it1->first.compare( it2->first );
1032 return res < 0 ;
1033 }
1034 };
1035
1036 /// Various states of the KML reader.
1037 #define KMLRD_NONE 1
1038 #define KMLRD_FOLDER 2
1039 #define KMLRD_FOLDER_NAME 3
1040 #define KMLRD_FOLDER_PLACEMARK 4
1041 #define KMLRD_FOLDER_PLACEMARK_NAME 5
1042 #define KMLRD_FOLDER_PLACEMARK_POINT 6
1043 #define KMLRD_FOLDER_PLACEMARK_POINT_COORDINATES 7
1044 #define KMLRD_FOLDER_PLACEMARK_STYLEURL 8
1045 #define KMLRD_FOLDER_PLACEMARK_SNIPPET 9
1046 #define KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA 10
1047 #define KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT 11
1048 #define KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT_FLDIGIITEM 12
1049 #define KMLRD_FOLDER_PLACEMARK_UNDEFINED 13
1050 #define KMLRD_FOLDER_UNDEFINED 14
1051
1052 /// Debugging purpose only.
KmlRdToStr(int kmlRd)1053 static const char * KmlRdToStr( int kmlRd ) {
1054 #define KMLRD_CASE(k) case k : return #k ;
1055 switch(kmlRd) {
1056 KMLRD_CASE(KMLRD_NONE)
1057 KMLRD_CASE(KMLRD_FOLDER)
1058 KMLRD_CASE(KMLRD_FOLDER_NAME)
1059 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK)
1060 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_NAME)
1061 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_POINT)
1062 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_POINT_COORDINATES)
1063 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_STYLEURL)
1064 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_SNIPPET)
1065 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA)
1066 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT)
1067 KMLRD_CASE(KMLRD_FOLDER_PLACEMARK_UNDEFINED)
1068 KMLRD_CASE(KMLRD_FOLDER_UNDEFINED)
1069 default : return "Unknown KMLRD code";
1070 }
1071 #undef KMLRD_CASE
1072 }
1073
1074 /// Loads a file of a previous session. The mutex should be locked at this moment.
ReloadSingleKmlFile(const std::string & category)1075 void ReloadSingleKmlFile( const std::string & category ) {
1076 std::string kmlFilNam = CategFile( category );
1077
1078 LOG_INFO("kmlFilNam=%s m_merge_dist=%lf", kmlFilNam.c_str(), m_merge_dist );
1079
1080 PlacesMapT *ptrMap = m_placemarks.FindCategory( category );
1081
1082 FILE * filKml = fl_fopen( kmlFilNam.c_str(), "r" );
1083 if( filKml == NULL ) {
1084 LOG_ERROR("Could not open %s. Creating one.", kmlFilNam.c_str() );
1085 CreateNewKmlFile( category );
1086 return ;
1087 }
1088 /// The destructor ensures the file will be closed if an exception is thrown.
1089 struct FilCloserT {
1090 FILE * m_file ;
1091 ~FilCloserT() { fclose(m_file); }
1092 } Closer = { filKml };
1093
1094 irr::io::IrrXMLReader * xml = irr::io::createIrrXMLReader( Closer.m_file );
1095 if( xml == NULL ) {
1096 LOG_ERROR("Could not parse %s", kmlFilNam.c_str() );
1097 return ;
1098 }
1099
1100 using namespace irr::io ;
1101
1102 int currState = KMLRD_NONE ;
1103
1104 std::string currFolderName ;
1105 std::string currPlcmrkName ;
1106 time_t currTimestamp = 0;
1107 std::string currPlacemarkDescr ;
1108 CustomDataT currCustData ;
1109 PlacemarkT currPM;
1110 bool currIsPoint = false ;
1111 std::string avoidNode ;
1112
1113 /// Stores the unique nodes which are misplaced.
1114 typedef std::set< std::string > UnexpectedNodesT ;
1115
1116 UnexpectedNodesT unexpectedNodes ;
1117
1118 // <Folder><name>ship</name><Placemark id="Ship:AUP06:2012-10-11 04:28:9">
1119 // <Point><coordinates>146.8,-19.2,0</coordinates></Point>
1120 // <name>Ship:AUP06</name>
1121 // <styleUrl>styles.kml#ship</styleUrl>
1122 // <description><![CDATA[<table border="1"><tr><td bgcolor=#00FF00 colspan="2">2012-09-24 12:00</td></tr><tr
1123 while(xml->read())
1124 {
1125 switch(xml->getNodeType())
1126 {
1127 case EXN_TEXT: {
1128 if( ! avoidNode.empty() ) break ;
1129
1130 const char * msgTxt = xml->getNodeData();
1131 LOG_DEBUG( "getNodeData=%s currState=%s", msgTxt, KmlRdToStr(currState) );
1132 switch(currState) {
1133 case KMLRD_FOLDER_NAME :
1134 currFolderName = msgTxt ? msgTxt : "NullFolder";
1135 break;
1136 case KMLRD_FOLDER_PLACEMARK_POINT_COORDINATES :
1137 currPM.SetCoordinates(msgTxt);
1138 break;
1139 case KMLRD_FOLDER_PLACEMARK_NAME :
1140 currPlcmrkName = msgTxt ? msgTxt : "NullPlacemarkName";
1141 break;
1142 case KMLRD_FOLDER_PLACEMARK_SNIPPET :
1143 currPlacemarkDescr = msgTxt ? msgTxt : "NullSnippet";
1144 break;
1145 case KMLRD_FOLDER_PLACEMARK_STYLEURL :
1146 currPM.SetStyle(msgTxt);
1147 break;
1148 default:
1149 break;
1150 }
1151 break;
1152 }
1153 case EXN_ELEMENT: {
1154 if( ! avoidNode.empty() ) break ;
1155
1156 const char *nodeName = xml->getNodeName();
1157 LOG_INFO( "getNodeName=%s currState=%s", nodeName, KmlRdToStr(currState) );
1158 // TODO: Have a hashmap for each case.
1159 switch(currState) {
1160 case KMLRD_NONE :
1161 if (!strcmp("Folder", nodeName)) {
1162 currState = KMLRD_FOLDER ;
1163 } else {
1164 /// These tags are not meaningful for us.
1165 if( strcmp( "kml", nodeName ) &&
1166 strcmp( "Document", nodeName ) &&
1167 strcmp( "name", nodeName ) )
1168 LOG_INFO("Unexpected %s in document %s. currState=%s",
1169 nodeName, category.c_str(), KmlRdToStr(currState) );
1170 }
1171 break;
1172 case KMLRD_FOLDER :
1173 if (!strcmp("name", nodeName)) {
1174 currState = KMLRD_FOLDER_NAME ;
1175 }
1176 else if (!strcmp("Placemark", nodeName)) {
1177 currState = KMLRD_FOLDER_PLACEMARK ;
1178 currPM.Clear();
1179 const char * strId = xml->getAttributeValue("id");
1180 currPM.SetKmlId( strId );
1181 }
1182 else {
1183 avoidNode = nodeName ;
1184 currState = KMLRD_FOLDER_UNDEFINED ;
1185 LOG_INFO("Unexpected %s in folder %s. currState=%s",
1186 nodeName, currFolderName.c_str(), KmlRdToStr(currState) );
1187 }
1188 break;
1189 case KMLRD_FOLDER_PLACEMARK :
1190 // There are different sorts of Placemark such as Point or LineString.
1191 if (!strcmp("Point", nodeName)) {
1192 currState = KMLRD_FOLDER_PLACEMARK_POINT ;
1193 currIsPoint = true ;
1194 }
1195 else if (!strcmp("name", nodeName)) {
1196 currState = KMLRD_FOLDER_PLACEMARK_NAME ;
1197 }
1198 else if (!strcmp("Snippet", nodeName)) {
1199 currState = KMLRD_FOLDER_PLACEMARK_SNIPPET ;
1200 }
1201 else if (!strcmp("styleUrl", nodeName)) {
1202 currState = KMLRD_FOLDER_PLACEMARK_STYLEURL ;
1203 }
1204 else if (!strcmp("ExtendedData", nodeName)) {
1205 currState = KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA ;
1206 }
1207 else if (!strcmp("description", nodeName)) {
1208 // We do not care about this tag, but it is always here.
1209 currState = KMLRD_FOLDER_PLACEMARK_UNDEFINED ;
1210 }
1211 else {
1212 avoidNode = nodeName ;
1213 currState = KMLRD_FOLDER_PLACEMARK_UNDEFINED ;
1214
1215 /// At the same time, it is detected and inserted.
1216 std::pair< UnexpectedNodesT::iterator, bool >
1217 pr = unexpectedNodes.insert( nodeName );
1218
1219 // Other occurences of the same nodes will not be signaled.
1220 if( pr.second ) {
1221 LOG_INFO("Unexpected %s in placemark id=%s name=%s. currState=%s",
1222 nodeName, currPM.KmlId().c_str(),
1223 currPlcmrkName.c_str(), KmlRdToStr(currState) );
1224 }
1225 }
1226 break;
1227 case KMLRD_FOLDER_PLACEMARK_POINT :
1228 if (!strcmp("coordinates", nodeName)) {
1229 currState = KMLRD_FOLDER_PLACEMARK_POINT_COORDINATES ;
1230 }
1231 else {
1232 LOG_INFO("Unexpected %s in coordinates. currState=%s",
1233 nodeName, KmlRdToStr(currState) );
1234 }
1235 break;
1236 case KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA:
1237 if (!strcmp(FLDIGI_TAG_EVT, nodeName)) {
1238 currState = KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT ;
1239 currTimestamp = KmlFromTimestamp( xml->getAttributeValue(FLDIGI_TIMESTAMP) );
1240 currCustData.clear();
1241 }
1242 else
1243 LOG_INFO("Unexpected %s in extended data. currState=%s",
1244 nodeName, KmlRdToStr(currState) );
1245 break;
1246 case KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT:
1247 // http://irrlicht.sourceforge.net/forum/viewtopic.php?t=10532
1248 // The Parser irrXml will not call "EXN_ELEMENT_END" because the tag <fldigi:item/>
1249 // has a trailing slash. Therefore we stay in the same state.
1250 // We could call irr::io::IIrrXMLReader<...>::isEmptyElement() to check that.
1251 if (!strcmp(FLDIGI_TAG_ITM, nodeName)) {
1252 assert( xml->isEmptyElement() );
1253 const char * strKey = xml->getAttributeValue(FLDIGI_TAG_KEY);
1254 if(strKey == NULL ) {
1255 LOG_INFO("Null item key");
1256 break ;
1257 }
1258 const char * strVal = xml->getAttributeValue(FLDIGI_TAG_VAL);
1259 if(strVal == NULL ) {
1260 LOG_INFO("Null item value");
1261 break ;
1262 }
1263 currCustData.Push( strKey, strVal );
1264 }
1265 else
1266 LOG_INFO("Unexpected %s in event. currState=%s",
1267 nodeName, KmlRdToStr(currState) );
1268 break;
1269 default:
1270 break;
1271 }
1272 break;
1273 }
1274 case EXN_ELEMENT_END: {
1275 if( ! avoidNode.empty() ) {
1276 const char * msgTxt = xml->getNodeData();
1277 if( avoidNode == msgTxt ) {
1278 LOG_INFO("Leaving forbidden element %s. currState=%s", avoidNode.c_str(), KmlRdToStr(currState) );
1279 // We can leave the quarantine.
1280 avoidNode.clear();
1281 } else {
1282 LOG_INFO("Still in forbidden element %s, leaving %s. currState=%s",
1283 avoidNode.c_str(), msgTxt, KmlRdToStr(currState) );
1284 break ;
1285 }
1286 }
1287
1288 // We should check that this string matches wuth the state expects, but this is much
1289 // faster to use only integers.
1290 LOG_INFO("End of %s currState=%s",
1291 xml->getNodeData(), KmlRdToStr(currState) );
1292 switch(currState) {
1293 case KMLRD_FOLDER :
1294 currState = KMLRD_NONE ;
1295 break;
1296 case KMLRD_FOLDER_PLACEMARK :
1297 // Loads only "Point" placemarks. Do not load "Linestring".
1298 if( ( ! currPlcmrkName.empty() ) && currIsPoint ) {
1299 ptrMap->DirectInsert( PlacesMapT::value_type( currPlcmrkName, currPM ), m_merge_dist );
1300 }
1301
1302 currTimestamp = -1 ;
1303 currPlacemarkDescr.clear();
1304 currPlcmrkName.clear();
1305 currIsPoint = false ;
1306 currState = KMLRD_FOLDER ;
1307 break;
1308 case KMLRD_FOLDER_NAME :
1309 case KMLRD_FOLDER_UNDEFINED :
1310 currState = KMLRD_FOLDER ;
1311 break;
1312 case KMLRD_FOLDER_PLACEMARK_NAME :
1313 case KMLRD_FOLDER_PLACEMARK_POINT :
1314 case KMLRD_FOLDER_PLACEMARK_STYLEURL :
1315 case KMLRD_FOLDER_PLACEMARK_SNIPPET :
1316 case KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA :
1317 case KMLRD_FOLDER_PLACEMARK_UNDEFINED :
1318 currState = KMLRD_FOLDER_PLACEMARK ;
1319 break;
1320 case KMLRD_FOLDER_PLACEMARK_POINT_COORDINATES :
1321 currState = KMLRD_FOLDER_PLACEMARK_POINT ;
1322 break;
1323 case KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA_FLDIGIEVENT :
1324 currState = KMLRD_FOLDER_PLACEMARK_EXTENDEDDATA ;
1325 // TODO: As soon as possible, use std::move for performance.
1326 currPM.AppendEvent( currTimestamp, currPlacemarkDescr, currCustData );
1327 currCustData.clear();
1328 break;
1329 case KMLRD_NONE:
1330 break;
1331 default:
1332 LOG_ERROR("Should not happen %s", KmlRdToStr(currState));
1333 break;
1334 }
1335 LOG_INFO("currState=%s", KmlRdToStr(currState) );
1336 break;
1337 }
1338 case EXN_NONE:
1339 case EXN_COMMENT:
1340 case EXN_CDATA:
1341 case EXN_UNKNOWN:
1342 break;
1343 default:
1344 // LOG_INFO( "Default NodeType=%d", xml->getNodeType());
1345 break;
1346 }
1347 }
1348
1349 // And of course delete it
1350
1351 delete xml;
1352
1353 LOG_INFO("kmlFilNam=%s loaded sz=%d",
1354 kmlFilNam.c_str(), (int)ptrMap->size() );
1355 } // KmlSrvImpl::ReloadSingleKmlFile
1356
1357 /// Rewrites only the categories which have changed.
RewriteKmlFileFull(void)1358 bool RewriteKmlFileFull(void) {
1359 bool wasSaved = false ;
1360 LOG_INFO("nb_categories=%d", static_cast<int>(nb_categories) );
1361 for( size_t i = 0; i < nb_categories; ++i ) {
1362 const char * category = categories[i];
1363 PlacesMapT *ptrMap = m_placemarks.FindCategory( category );
1364 if( ptrMap == NULL ) {
1365 LOG_INFO("Category %s undefined", category );
1366 continue;
1367 }
1368 ptrMap->PruneKmlFile( m_retention_delay );
1369 wasSaved |= ptrMap->RewriteKmlFileOneCategory( category, CategFile( category ), m_balloon_style );
1370 }
1371 return wasSaved ;
1372 } // KmlSrvImpl::RewriteKmlFileFull
1373
1374 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1375 /// This is signaled when geographic data is broadcasted.
1376 pthread_cond_t m_cond_queue ;
1377 #else
1378 /// This is set to true when geographic data is broadcasted.
1379 bool m_bool_queue ;
1380
1381 /// This tells that the subthread must leave at the first opportunity.
1382 bool m_kml_must_leave;
1383 #endif
1384 pthread_mutex_t m_mutex_write ;
1385
1386 typedef std::list< PlacemarkT > PlacemarkListT ;
1387
1388 PlacemarkListT m_queues[ nb_categories ];
1389
1390 /// Called in a subthread. Woken up by a timed condition to save content to a KML file.
ThreadFunc(void)1391 void * ThreadFunc(void) {
1392 MilliSleep(2000); // Give time enough to load data.
1393 int r ;
1394
1395 // This is normally the default behaviour.
1396 r = pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL );
1397 if( r != 0 ) {
1398 LOG_ERROR("pthread_setcancelstate %s", strerror(errno) );
1399 return NULL;
1400 }
1401 r = pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, NULL );
1402 if( r != 0 ) {
1403 LOG_ERROR("pthread_setcanceltype %s", strerror(errno) );
1404 return NULL;
1405 }
1406 int refresh_delay = m_refresh_interval ;
1407
1408 // Endless loop until end of program, which cancels this subthread.
1409 for(;;)
1410 {
1411 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1412 struct timespec tmp_tim;
1413 {
1414 guard_lock myGuard( &m_mutex_write );
1415
1416 // It does not need to be very accurate.
1417 tmp_tim.tv_sec = time(NULL) + refresh_delay;
1418 tmp_tim.tv_nsec = 0 ;
1419
1420 //LOG_INFO("About to wait %d seconds", refresh );
1421 r = pthread_cond_timedwait( &m_cond_queue, &m_mutex_write, &tmp_tim );
1422 if( ( r != ETIMEDOUT ) && ( r != 0 ) ) {
1423 LOG_ERROR("pthread_cond_timedwait %s d=%d", strerror(errno), m_refresh_interval );
1424 return (void *)"Error in pthread_cond_timed_wait";
1425 }
1426 #else
1427 /// On the platforms where pthread_cond_timedwait has problems, everything behaves
1428 // as if there was a timeout when data is saved. refresh_delay is never changed.
1429 for( int i = 0; i < refresh_delay; ++i )
1430 {
1431 MilliSleep( 1000 );
1432 if( m_kml_must_leave )
1433 {
1434 LOG_INFO("Exit flag detected. Leaving");
1435 return (void *)"Exit flag detected";
1436 }
1437 }
1438 {
1439 guard_lock myGuard( &m_mutex_write );
1440 r = m_bool_queue ? ETIMEDOUT : 0 ;
1441 m_bool_queue = false;
1442 #endif
1443
1444 // Except if extremely slow init, the object should be ready now: Files loaded etc...
1445 if( ! m_loaded ) {
1446 static int nb_retries = 3 ;
1447 if( nb_retries == 0 ) {
1448 static const char * error_message = "KML server could not start. Leaving";
1449 LOG_ERROR("%s", error_message );
1450 return (void *)error_message ;
1451 }
1452 --nb_retries ;
1453 LOG_INFO("KML server not ready yet. Cnt=%d. Restarting.",nb_retries);
1454 MilliSleep(1000); // Give time to load data.
1455 continue ;
1456 }
1457
1458 // We might have missed the condition signal so a quick check will not do any harm.
1459 for( size_t i = 0; i < nb_categories; ++i )
1460 {
1461 PlacesMapT *ptrMap = m_placemarks.FindCategory( categories[i] );
1462 if( ptrMap == NULL ) {
1463 LOG_INFO("Category %s undefined", categories[i] );
1464 continue;
1465 }
1466 // TODO: If there are contention problems, internally swap the queue
1467 // with a fresh empty one.
1468 ptrMap->FlushQueue( m_merge_dist );
1469 }
1470 LOG_INFO("Releasing lock" );
1471 }
1472 if( r == ETIMEDOUT )
1473 {
1474 //LOG_INFO("Saving after wait=%d", refresh );
1475 bool wasSaved = RewriteKmlFileFull();
1476
1477 // Maybe a user process must be created to process these KML files.
1478 if(wasSaved) {
1479 SpawnCommand();
1480 }
1481
1482 // Reset the interval to the initial value.
1483 refresh_delay = m_refresh_interval ;
1484 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1485 } else {
1486 refresh_delay = tmp_tim.tv_sec - time(NULL);
1487 if( refresh_delay <= 0 ) refresh_delay = 1 ;
1488 //LOG_INFO("Interrupted when waiting. Restart with wait=%d", refresh );
1489 #endif
1490 }
1491 } // Endless loop.
1492 return NULL ;
1493 } // ThreadFunc
1494
1495 /// The C-style function called by pthread.
1496 static void * ThreadFunc( void * ptr ) {
1497 return static_cast< KmlSrvImpl *>(ptr)->ThreadFunc();
1498 };
1499
1500 public:
1501 /// When setting or changing core parameters.
1502 void InitParams(
1503 const std::string & kml_command,
1504 const std::string & kml_dir,
1505 double kml_merge_distance,
1506 int kml_retention_delay,
1507 int kml_refresh_interval,
1508 int kml_balloon_style)
1509 try
1510 {
1511 /// The thread should NOT access anything at this moment.
1512 guard_lock myGuard( &m_mutex_write );
1513
1514 /// If the string is empty, no command is executed.
1515 m_command = kml_command;
1516 strtrim( m_command );
1517
1518 m_merge_dist = kml_merge_distance ;
1519 m_retention_delay = kml_retention_delay ;
1520
1521 static const int min_refresh = 10 ;
1522 if( kml_refresh_interval < min_refresh ) {
1523 LOG_WARN("Refresh interval too small %d minimum is %d", kml_refresh_interval, min_refresh );
1524 kml_refresh_interval = min_refresh ;
1525 }
1526 m_refresh_interval = kml_refresh_interval ;
1527 m_balloon_style = kml_balloon_style ;
1528
1529 LOG_INFO("dir=%s merge_distance=%lf retention_delay=%d refresh_interval=%d balloon_style=%d",
1530 kml_dir.c_str(), kml_merge_distance,
1531 kml_retention_delay, kml_refresh_interval, kml_balloon_style );
1532
1533 m_kml_dir = kml_dir ;
1534
1535 /// This enforces that the directory name always ends with a slash.
1536 if( m_kml_dir[ m_kml_dir.size() - 1 ] != '/' ) m_kml_dir += '/' ;
1537
1538 const char * resdir = create_directory( m_kml_dir.c_str() );
1539 if ( resdir ) {
1540 throw std::runtime_error( strformat( "Cannot create %s:%s", m_kml_dir.c_str(), resdir ) );
1541 }
1542
1543 CreateMainKmlFile();
1544 CopyStyleFileIfNotExists();
1545 } catch (const std::exception & exc) {
1546 // We assume that the calling program has no try/catch handler.
1547 LOG_ERROR("Caught exception:%s", exc.what() );
1548 } catch (...) {
1549 LOG_ERROR("Caught unknown exception");
1550 }
1551
1552 virtual void ReloadKmlFiles()
1553 {
1554 /// The thread should NOT access anything at this moment.
1555 guard_lock myGuard( &m_mutex_write );
1556
1557 // This loads placemarks without the subthread, because it is simpler.
1558 for( size_t i = 0; i < nb_categories; ++i ) {
1559 try {
1560 ReloadSingleKmlFile( categories[i] );
1561 } catch( const std::exception & exc ) {
1562 LOG_ERROR("Category %s. Caught %s", categories[i], exc.what() );
1563 }
1564 }
1565
1566 // Now the object is usable. Theoretically should be protected by a mutex.
1567 LOG_DEBUG("Object ready");
1568
1569 /// Even if an exception was thrown when loading the previous file, it does not
1570 /// prevent to overwrite the old files with new and clean ones.
1571 m_loaded = true ;
1572 }
1573
1574 /// Invalid values everywhere, intentionnaly.
1575 KmlSrvImpl()
1576 : m_loaded(false)
1577 , m_pid_command(-1)
1578 , m_merge_dist(-1.0)
1579 , m_retention_delay(-1)
1580 , m_refresh_interval(-1)
1581 , m_balloon_style(0)
1582 {
1583 LOG_DEBUG("Creation");
1584 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1585 pthread_cond_init( &m_cond_queue, NULL );
1586 #else
1587 m_bool_queue = false;
1588 m_kml_must_leave = false;
1589 #endif
1590 pthread_mutex_init( &m_mutex_write, NULL );
1591
1592 /// TODO: Add this thread to the other fldigi threads stored in cbq[].
1593 if( pthread_create( &m_writer_thread, NULL, ThreadFunc, this ) ) {
1594 /// It is not urgent because this does not interact with the main thread.
1595 LOG_ERROR("pthread_create %s", strerror(errno) );
1596 }
1597 }
1598
1599 /** We cannot use KML updates with a local file:
1600 * "I want to know if it exists any solution to put relative paths in a
1601 * targetHref to load upload files in a directory in my computer simply
1602 * without any server running."
1603 * "Unfortunately, no, the security restrictions around <Update> prevent
1604 * this explicitly."
1605 */
1606
1607 /// iconNam: wmo automated fixed other dart buoy oilrig tao
1608 void Broadcast(
1609 const std::string & category,
1610 time_t evtTim,
1611 const CoordinateT::Pair & refCoo,
1612 double altitude,
1613 const std::string & kmlNam,
1614 const std::string & styleNam,
1615 const std::string & descrTxt,
1616 const CustomDataT & custDat )
1617 {
1618 /// Hyphen: No idea why, but the string "10-meter discus buoy:W GULF 207 NM " just displays as "10-"
1619 std::string tmpKmlNam = strreplace( kmlNam, "-", " ");
1620 PlacemarkT currPM( refCoo, altitude, styleNam, tmpKmlNam );
1621 currPM.AppendEvent( evtTim, descrTxt, custDat );
1622
1623 guard_lock myGuard( &m_mutex_write );
1624
1625 ++KmlServer::m_nb_broadcasts;
1626 PlacesMapT *ptrMap = m_placemarks.FindCategory( category );
1627 if(ptrMap == NULL ) {
1628 LOG_ERROR("Category %s undefined", category.c_str());
1629 }
1630 ptrMap->Enqueue( tmpKmlNam, currPM );
1631 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1632 pthread_cond_signal( &m_cond_queue );
1633 #else
1634 m_bool_queue = true;
1635 #endif
1636 LOG_INFO("'%s' sz=%d time=%s nb_broad=%d m_merge_dist=%lf",
1637 descrTxt.c_str(), (int)ptrMap->size(),
1638 KmlTimestamp(evtTim).c_str(),
1639 KmlServer::m_nb_broadcasts,m_merge_dist );
1640 }
1641
1642 /// It flushes the content to disk.
1643 ~KmlSrvImpl() {
1644 {
1645 /// This will not be killed in the middle of the writing.
1646 LOG_INFO("Cancelling writer thread");
1647 guard_lock myGuard( &m_mutex_write );
1648
1649 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1650 LOG_INFO("Cancelling subthread");
1651 int r = pthread_cancel( m_writer_thread );
1652 if( r ) {
1653 LOG_ERROR("pthread_cancel %s", strerror(errno) );
1654 return ;
1655 }
1656 #else
1657 LOG_INFO("Setting exit flag.");
1658 m_kml_must_leave = true ;
1659 #endif
1660 }
1661 LOG_INFO("Joining subthread");
1662 void * retPtr;
1663 int r = pthread_join( m_writer_thread, &retPtr );
1664 if( r ) {
1665 LOG_ERROR("pthread_join %s", strerror(errno) );
1666 return ;
1667 }
1668 const char * msg =
1669 (retPtr == NULL)
1670 ? "Null"
1671 : (retPtr == PTHREAD_CANCELED)
1672 ? "Canceled thread"
1673 : static_cast<const char *>(retPtr);
1674 LOG_INFO("Thread stopped. Message:%s", msg );
1675
1676 /// Here we are sure that the subthread is stopped. The subprocess is not called.
1677 RewriteKmlFileFull();
1678
1679 #ifdef FLDIGI_KML_CONDITION_VARIABLE
1680 pthread_cond_destroy( &m_cond_queue );
1681 #endif
1682 pthread_mutex_destroy( &m_mutex_write );
1683 }
1684
1685 /// Empties the generated files.
1686 void Reset(void) {
1687 for( size_t i = 0; i < nb_categories; ++i ) {
1688 std::cout << "reset: " << categories[i] << std::endl;
1689 CreateNewKmlFile(categories[i]);
1690 }
1691 ResetCounter();
1692 }
1693
1694 /// This is called when KML files are saved, or on demand from the configuration tab.
1695 void SpawnCommand() {
1696 if( m_command.empty() ) return ;
1697
1698 /** This stores the process id so the command is not restarted if still running.
1699 This allows to start for example Google Earth only once. But this allows
1700 also to run GPS_Babel for each new set of files, as soon as they are created.
1701 */
1702 int is_proc_still_running = test_process( m_pid_command );
1703 if( is_proc_still_running == -1 ) return ;
1704 if( ( m_pid_command <= 0 ) || ( is_proc_still_running == 0 ) ) {
1705 m_pid_command = fork_process( m_command.c_str() );
1706 LOG_INFO("%s: Pid=%d Command=%s",
1707 __FUNCTION__, m_pid_command, m_command.c_str() );
1708 }
1709 }
1710
1711 }; // KmlSrvImpl
1712
1713 /// Singleton. It must be destroyed at the end.
1714 static KmlServer * g_inst = NULL;
1715
Pointer()1716 static KmlSrvImpl * Pointer() {
1717 if( NULL == g_inst ) {
1718 g_inst = new KmlSrvImpl();
1719 }
1720 KmlSrvImpl * p = dynamic_cast< KmlSrvImpl * >( g_inst );
1721 if( p == NULL ) {
1722 LOG_ERROR("Null pointer");
1723 throw std::runtime_error("KmlServer not initialised");
1724 }
1725 return p ;
1726 }
1727
Tm2Time(time_t tim)1728 std::string KmlServer::Tm2Time( time_t tim ) {
1729 char bufTm[40];
1730 tm tmpTm;
1731 gmtime_r( &tim, & tmpTm );
1732
1733 snprintf( bufTm, sizeof(bufTm), "%4d-%02d-%02d %02d:%02d",
1734 tmpTm.tm_year + 1900,
1735 tmpTm.tm_mon + 1,
1736 tmpTm.tm_mday,
1737 tmpTm.tm_hour,
1738 tmpTm.tm_min );
1739 return bufTm;
1740 }
1741
1742 /// Returns current time.
Tm2Time()1743 std::string KmlServer::Tm2Time( ) {
1744 return Tm2Time( time(NULL) );
1745 }
1746
1747 /// One singleton for everyone.
GetInstance(void)1748 KmlServer * KmlServer::GetInstance(void)
1749 {
1750 return Pointer();
1751 }
1752
1753 /// This creates a process running the user command.
SpawnProcess()1754 void KmlServer::SpawnProcess() {
1755 Pointer()->SpawnCommand();
1756 }
1757
1758 /// Called by thr main program, clean exit.
Exit(void)1759 void KmlServer::Exit(void) {
1760 // We assume that the calling program has no try/catch handler.
1761 LOG_INFO("Exiting");
1762 try {
1763 KmlServer * pKml = Pointer();
1764 if( pKml ) {
1765 delete pKml;
1766 }
1767 } catch (const std::exception & exc) {
1768 LOG_ERROR("Caught exception:%s",exc.what() );
1769 } catch (...) {
1770 LOG_ERROR("Caught unknown exception");
1771 }
1772 }
1773