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 &nbsp; &cent; &pound; &yen; &euro; &sect; &copy; &reg; &trade;
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 = "&quot;"; break;
149 			case '\''  : ptr = "&apos;"; break;
150 			case '&'   : ptr = "&amp;" ; break;
151 			case '<'   : ptr = "&lt;"  ; break;
152 			case '>'   : ptr = "&gt;"  ; 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