1 // ----------------------------------------------------------------------------
2 // Copyright (C) 2014
3 //              David Freese, W1HKJ
4 //
5 // This file is part of fldigi
6 //
7 // fldigi is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // fldigi is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 // ----------------------------------------------------------------------------
20 
21 #include <FL/Fl.H>
22 #include <FL/filename.H>
23 #include <FL/fl_ask.H>
24 
25 #include <cstring>
26 #include <cstdlib>
27 #include <string>
28 
29 #include "fl_digi.h"
30 
31 #include "signal.h"
32 #include "threads.h"
33 #include "adif_io.h"
34 #include "config.h"
35 #include "configuration.h"
36 #include "lgbook.h"
37 #include "icons.h"
38 #include "gettext.h"
39 #include "debug.h"
40 #include "util.h"
41 #include "date.h"
42 #include "logsupport.h"
43 #include "qrunner.h"
44 #include "timeops.h"
45 
46 using namespace std;
47 
48 static pthread_mutex_t logfile_mutex = PTHREAD_MUTEX_INITIALIZER;
49 
50 size_t ptr, ptr2;
51 string sbuff;
52 
53 #ifdef __WOE32__
54 static const char *szEOL = "\r\n";
55 #else
56 static const char *szEOL = "\n";
57 #endif
58 static const char *szEOR = "<EOR>";
59 
60 // These ADIF fields define the ADIF database
61 FIELD fields[] = {
62 //  TYPE,          FSIZE,  NAME,              WIDGET
63 	{FREQ,         12,    "FREQ",             &btnSelectFreq},      // QSO frequency in Mhz
64 	{CALL,         30,    "CALL",             &btnSelectCall},      // contacted stations CALLSIGN
65 	{ADIF_MODE,    20,    "MODE",             &btnSelectMode},      // QSO mode
66 	{SUBMODE,      20,    "SUBMODE",          NULL},                // QSO submode
67 	{NAME,         80,    "NAME",             &btnSelectName},      // contacted operators NAME
68 	{QSO_DATE,     8,     "QSO_DATE",         &btnSelectQSOdateOn}, // QSO data
69 	{QSO_DATE_OFF, 8,     "QSO_DATE_OFF",     &btnSelectQSOdateOff},// QSO data OFF, according to ADIF 2.2.6
70 	{TIME_OFF,     6,     "TIME_OFF",         &btnSelectTimeOFF},   // HHMM or HHMMSS in UTC
71 	{TIME_ON,      6,     "TIME_ON",          &btnSelectTimeON},    // HHMM or HHMMSS in UTC
72 	{QTH,          100,   "QTH",              &btnSelectQth},       // contacted stations city
73 	{RST_RCVD,     3,     "RST_RCVD",         &btnSelectRSTrcvd},   // received signal report
74 	{RST_SENT,     3,     "RST_SENT",         &btnSelectRSTsent},   // sent signal report
75 	{STATE,        20,    "STATE",            &btnSelectState},     // contacted stations STATE
76 	{VE_PROV,      20,    "VE_PROV",          &btnSelectProvince},  // 2 letter abbreviation for Canadian Province
77 	{NOTES,        512,   "NOTES",            &btnSelectNotes},     // QSO notes
78 
79 	{QSLRDATE,     8,     "QSLRDATE",         &btnSelectQSLrcvd},   // QSL received date
80 	{QSLSDATE,     8,     "QSLSDATE",         &btnSelectQSLsent},   // QSL sent date
81 
82 	{EQSLRDATE,    8,     "EQSLRDATE",        &btnSelecteQSLrcvd},  // EQSL received date
83 	{EQSLSDATE,    8,     "EQSLSDATE",        &btnSelecteQSLsent},  // EQSL sent date
84 
85 	{LOTWRDATE,    8,     "LOTWRDATE",        &btnSelectLOTWrcvd},  // LOTW received date
86 	{LOTWSDATE,    8,     "LOTWSDATE",        &btnSelectLOTWsent},  // LOTW sent date
87 
88 	{GRIDSQUARE,   8,     "GRIDSQUARE",       &btnSelectLOC},       // contacted stations Maidenhead Grid Square
89 	{BAND,         8,     "BAND",             &btnSelectBand},      // QSO band
90 	{CNTY,         60,    "CNTY",             &btnSelectCNTY},      // secondary political subdivision, ie: county
91 	{COUNTRY,      60,    "COUNTRY",          &btnSelectCountry},   // contacted stations DXCC entity name
92 	{CQZ,          8,     "CQZ",              &btnSelectCQZ},       // contacted stations CQ Zone
93 	{DXCC,         8,     "DXCC",             &btnSelectDXCC},      // contacted stations Country Code
94 	{QSL_VIA,      256,   "QSL_VIA",          &btnSelectQSL_VIA},   // contacted stations path
95 	{IOTA,         20,    "IOTA",             &btnSelectIOTA},      // Islands on the air
96 	{ITUZ,         20,    "ITUZ",             &btnSelectITUZ},      // ITU zone
97 	{CONT,         60,    "CONT",             &btnSelectCONT},      // contacted stations continent
98 
99 	{SRX,          50,    "SRX",              &btnSelectSerialIN},  // received serial number for a contest QSO
100 	{STX,          50,    "STX",              &btnSelectSerialOUT}, // QSO transmitted serial number
101 
102 	{XCHG1,        100,   "SRX_STRING",       &btnSelectXchgIn},    // contest exchange #1 / free1 in xlog
103 	{MYXCHG,       100,   "STX_STRING",       &btnSelectMyXchg},    // contest exchange sent
104 
105 	{CLASS,        20,    "CLASS",            &btnSelectClass},     // Field Day / School RR class received
106 	{ARRL_SECT,    20,    "ARRL_SECT",        &btnSelectSection},   // ARRL section received
107 
108 	{TX_PWR,       8,     "TX_PWR",           &btnSelectTX_pwr},    // power transmitted by this station
109 
110 	{OP_CALL,     30,     "OPERATOR",         &btnSelectOperator},  // Callsign of person logging the QSO
111 	{STA_CALL,    30,     "STATION_CALLSIGN", &btnSelectStaCall},   // Callsign of transmitting station
112 	{MY_GRID,      8,     "MY_GRIDSQUARE",    &btnSelectStaGrid},   // Xmt station locator
113 	{MY_CITY,     60,     "MY_CITY",          &btnSelectStaCity},   // Xmt station location
114 
115 	{SS_SEC,       20,    "CWSS_SECTION",     &btnSelect_cwss_section},   // CW sweepstakes
116 	{SS_SERNO,     20,    "CWSS_SERNO",       &btnSelect_cwss_serno},
117 	{SS_PREC,      20,    "CWSS_PREC",        &btnSelect_cwss_prec},
118 	{SS_CHK,       20,    "CWSS_CHK",         &btnSelect_cwss_check},
119 
120 	{AGE,          2,     "AGE",              &btnSelectAge},       // contacted operators age in years
121 	{TEN_TEN,      10,    "TEN_TEN",          &btnSelect_1010},     // ten ten # of other station
122 	{CHECK,        10,    "CHECK",            &btnSelectCheck},     // contest identifier
123 
124 	{FD_CLASS,     20,    "FD_CLASS",         NULL},                // Field Day Rcvd
125 	{FD_SECTION,   20,    "FD_SECTION",       NULL},                // FD section received
126 
127 	{TROOPS,       20,    "TROOPS",           NULL},                // JOTA troop number sent
128 	{TROOPR,       20,    "TROOPR",           NULL},                // JOTA troop number received
129 	{SCOUTS,       20,    "SCOUTS",           NULL},
130 	{SCOUTR,       20,    "SCOUTR",           NULL},
131 
132 	{NUMFIELDS,    0,     "",             NULL}
133 };
134 
135 // This ADIF fields is in the fldigi QSO database, but not saved in the ADIF file
136 /*
137 	{EXPORT,       0,     "EXPORT",       NULL},                // used to indicate record is to be exported
138 */
139 
140 // These ADIF fields are not in the fldigi QSO database
141 /*
142 	{COMMENT,      256,   "COMMENT",      NULL},                // comment field for QSO
143 	{ADDRESS,      256,   "ADDRESS",      NULL},                // contacted stations mailing address
144 	{PFX,          20,    "PFX",          NULL},                // WPA prefix
145 	{PROP_MODE,    100,   "PROP_MODE",    NULL},                // propogation mode
146 	{QSL_MSG,      256,   "QSL_MSG",      NULL},                // personal message to appear on qsl card
147 	{QSL_RCVD,     4,     "QSL_RCVD",     NULL},                // QSL received status
148 	{QSL_SENT,     4,     "QSL_SENT",     NULL},                // QSL sent status
149 	{QSL_VIA,      20,    "QSL_VIA",      NULL},                // QSL via this person
150 	{RX_PWR,       8,     "RX_PWR",       NULL},                // power of other station in watts
151 	{SAT_MODE,     20,    "SAT_MODE",     NULL},                // satellite mode
152 	{SAT_NAME,     20,    "SAT_NAME",     NULL},                // satellite name
153 };
154 */
155 
156 static string read_errors;
157 static int    num_read_errors;
158 
write_rxtext(const char * s)159 static void write_rxtext(const char *s)
160 {
161 	ReceiveText->addstr(s);
162 }
163 
164 static char *fastlookup = 0;
165 
166 static unsigned int maxlen = 0;
167 
initfields()168 static void initfields()
169 {
170 	if (fastlookup) return; // may have multiple instances using common code
171 	int i = 0;
172 	while (fields[i].type != NUMFIELDS) {
173 		if (strlen(fields[i].name) > maxlen) maxlen = strlen(fields[i].name);
174 		i++;
175 	}
176 	maxlen++;
177 	fastlookup = new char[maxlen * i + 1];
178 	fastlookup[0] = 0;
179 	i = 0;
180 	while (fields[i].type != NUMFIELDS) {
181 		strcat(fastlookup, fields[i].name);
182 		unsigned int n = maxlen - strlen(fastlookup) % maxlen;
183 		if (n > 0 && n < maxlen) for (unsigned int j = 0; j < n; j++) strcat(fastlookup, " ");
184 		i++;
185 	}
186 }
187 
findfield(char * p)188 static inline int findfield( char *p )
189 {
190 	if (strncasecmp (p, "EOR>", 4) == 0 || !maxlen)
191 		return -1;
192 
193 	char *pos;
194 	char *p1 = strchr(p, ':');
195 	char *p2 = strchr(p, '>');
196 	if (p1 && p2) {
197 		if (p1 < p2) {
198 			pos = p;
199 			do { *pos = toupper(*pos); } while (++pos < p1);
200 			*p1 = 0;
201 			pos = strcasestr(fastlookup, p);
202 			*p1 = ':';
203 			if (pos) {
204 				return fields[(pos - fastlookup) / maxlen].type;
205 			}
206 		}
207 	}
208 	return -2;		//search key not found
209 }
210 
211 int cAdifIO::instances = 0;
212 
cAdifIO()213 cAdifIO::cAdifIO ()
214 {
215 	initfields();
216 	instances++;
217 }
218 
~cAdifIO()219 cAdifIO::~cAdifIO()
220 {
221 	if (--instances == 0) {
222 		delete [] fastlookup;
223 		fastlookup = 0;
224 	}
225 }
226 
fillfield(int recnbr,int fieldnum,char * buff)227 char * cAdifIO::fillfield (int recnbr, int fieldnum, char *buff)
228 {
229 	char *p1 = strchr(buff, ':');
230 	char *p2 = strchr(buff, '>');
231 	if (!p1 || !p2 || p2 < p1) {
232 		return 0; // bad ADIF specifier ---> no ':' after field name
233 	}
234 
235 	p1++;
236 	int fldsize = 0;
237 	while (p1 != p2) {
238 		if (*p1 >= '0' && *p1 <= '9') {
239 			fldsize = fldsize * 10 + *p1 - '0';
240 		}
241 		p1++;
242 	}
243 
244 	string tmp = "";
245 	tmp.assign(p2+1, fldsize);
246 
247 // added to disallow very large corrupted adif fields
248 	if (fldsize > fields[fieldnum].fsize) {
249 		string bfr = buff;
250 		tmp.erase(fields[fieldnum].fsize);
251 		static char szmsg[1000];
252 		snprintf(szmsg, sizeof(szmsg),
253 			"In record # %d, <%s, too large, saving first %d characters\n",
254 			recnbr+1,
255 			bfr.substr(0, (int)(p2+1 - buff)).c_str(),
256 			fields[fieldnum].fsize );
257 		read_errors.append(szmsg);
258 		num_read_errors++;
259 	}
260 
261 	if ((fieldnum == TIME_ON || fieldnum == TIME_OFF) && fldsize < 6)
262 		while (tmp.length() < 6) tmp += '0';
263 
264 	adifqso->putField( fieldnum, tmp.c_str(), tmp.length() );
265 
266 	return p2 + fldsize + 1;
267 }
268 
do_readfile(const char * fname,cQsoDb * db)269 void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
270 {
271 	guard_lock lock(&logfile_mutex);
272 
273 	int found;
274 	static char szmsg[500];
275 
276 	read_errors.clear();
277 	num_read_errors = 0;
278 
279 // open the adif file
280 	FILE *adiFile = fl_fopen (fname, "rb");
281 
282 	if (adiFile == NULL) {
283 		LOG_ERROR("Could not open %s", fname);
284 		return;
285 	}
286 /*
287 	struct timespec t0, t1, t2;
288 #ifdef _POSIX_MONOTONIC_CLOCK
289 	clock_gettime(CLOCK_MONOTONIC, &t0);
290 #else
291 	clock_gettime(CLOCK_REALTIME, &t0);
292 #endif
293 */
294 	char buff[16384];
295 	sbuff.clear();
296 	memset(buff, 0, 16384);
297 	int retnbr = fread(buff, 1, 16384, adiFile);
298 	while (retnbr) {
299 		sbuff.append(buff, retnbr);
300 		retnbr = fread(buff, 1, 16384, adiFile);
301 	}
302 	fclose(adiFile);
303 
304 	size_t p;//, ptr, ptr2;
305 
306 	p = sbuff.find("<EOH>");
307 	if (p == std::string::npos) p = sbuff.find("<eoh>");
308 	if (p == std::string::npos) {
309 		LOG_ERROR("Could not find <EOH> in %s", fname);
310 		return;
311 	}
312 	if ((sbuff.find("<EOR>") == std::string::npos) &&
313 		(sbuff.find("<eor>") == std::string::npos)) {
314 			LOG_ERROR("Empty log file %s", fname);
315 			return;
316 	}
317 
318 size_t recend;
319 
320 	int recnbr = 0;
321 
322 	p = sbuff.find('<', p + 1);
323 
324 	while (p != std::string::npos) {
325 		recend = sbuff.find("<EOR>", p);
326 		if (recend == string::npos) recend = sbuff.find("<eor>", p);
327 		if (recend == string::npos)
328 			break;
329 
330 		ptr = p;
331 		adifqso = 0;
332 		while (ptr != std::string::npos) {
333 			ptr2 = sbuff.find('<', ptr + 1);
334 			if (ptr2 == string::npos)
335 				break;
336 			found = findfield( &sbuff[ptr + 1] );
337 			if (found > -1) {
338 				if (!adifqso) adifqso = db->newrec(); // need new record in db
339 				fillfield (recnbr, found, &sbuff[ptr + 1]);
340 			} else if (found == -1) { // <eor> reached;
341 				break;
342 			}
343 			ptr = ptr2;
344 			if (ptr == std::string::npos)
345 				break; // corrupt record
346 		}
347 		recnbr++;
348 		p = sbuff.find('<', recend + 1);
349 	}
350 /*
351 #ifdef _POSIX_MONOTONIC_CLOCK
352 	clock_gettime(CLOCK_MONOTONIC, &t2);
353 #else
354 	clock_gettime(CLOCK_REALTIME, &t2);
355 #endif
356 
357 	float t = t1.tv_sec - t0.tv_sec + (t1.tv_nsec - t0.tv_nsec)/1e9;
358 	float tp = t2.tv_sec - t1.tv_sec + (t2.tv_nsec - t1.tv_nsec)/1e9;
359 
360 	snprintf(szmsg, sizeof(szmsg), "\n\
361 ================================================\n\
362 Read Logbook: %s\n\
363   read %d records in %4.1f seconds\n\
364   parsed in %4.1f seconds\n\
365 ================================================\n",
366 fname, db->nbrRecs(), t, tp);
367 */
368 	snprintf(szmsg, sizeof(szmsg), "\n\
369 ================================================\n\
370 Read Logbook: %s\n\
371   %d records\n\
372 ================================================\n",
373 fname, db->nbrRecs());
374 
375 	if (progdefaults.DisplayLogbookRead && (db == &qsodb))
376 		REQ(write_rxtext, szmsg);
377 
378 	LOG_INFO("%s", szmsg);
379 
380 	if (num_read_errors) {
381 		if (!read_errors.empty()) {
382 			read_errors.append("\n");
383 			read_errors.append(szmsg);
384 		} else
385 			read_errors.assign(szmsg);
386 		snprintf(szmsg, sizeof(szmsg),
387 			"Corrected %d errors.  Save logbook and then reload\n",
388 			num_read_errors);
389 		read_errors.append("\n\
390 ================================================\n").append(szmsg);
391 			read_errors.append("\
392 ================================================\n");
393 		REQ(write_rxtext, read_errors.c_str());
394 	}
395 
396 	if (db == &qsodb)
397 		REQ(adif_read_OK);
398 }
399 
400 static const char *adifmt = "<%s:%d>";
401 
402 // write ALL or SELECTED records to the designated file
403 
writeFile(const char * fname,cQsoDb * db)404 int cAdifIO::writeFile (const char *fname, cQsoDb *db)
405 {
406 	guard_lock lock(&logfile_mutex);
407 
408 	string ADIFHEADER;
409 	ADIFHEADER = "File: %s";
410 	ADIFHEADER.append(szEOL);
411 	ADIFHEADER.append("<ADIF_VER:%d>%s");
412 	ADIFHEADER.append(szEOL);
413 	ADIFHEADER.append("<PROGRAMID:%d>%s");
414 	ADIFHEADER.append(szEOL);
415 	ADIFHEADER.append("<PROGRAMVERSION:%d>%s");
416 	ADIFHEADER.append(szEOL);
417 	ADIFHEADER.append("<EOH>");
418 	ADIFHEADER.append(szEOL);
419 // open the adif file
420 	cQsoRec *rec;
421 	string sFld;
422 	adiFile = fl_fopen (fname, "wb");
423 	if (!adiFile)
424 		return 1;
425 
426 	fprintf (adiFile, ADIFHEADER.c_str(),
427 			 fl_filename_name(fname),
428 			 strlen(ADIF_VERS), ADIF_VERS,
429 			 strlen(PACKAGE_NAME), PACKAGE_NAME,
430 			 strlen(PACKAGE_VERSION), PACKAGE_VERSION);
431 
432 	string sName;
433 	int field_type;
434 	for (int i = 0; i < db->nbrRecs(); i++) {
435 		rec = db->getRec(i);
436 		if (rec->getField(EXPORT)[0] == 'E') {
437 			int j = 0;
438 			while (fields[j].type != NUMFIELDS) {
439 				if (strcmp(fields[j].name,"MYXCHG") == 0) { j++; continue; }
440 				if (strcmp(fields[j].name,"XCHG1") == 0) { j++; continue; }
441 				if (fields[j].btn != NULL) {
442 					if ((*fields[j].btn)->value()) {
443 						field_type = fields[j].type;
444 						sFld = rec->getField(field_type);
445 						sName = fields[j].name;
446 						if (field_type == ADIF_MODE  && !sFld.empty()) {
447 							fprintf(adiFile, adifmt,
448 								"MODE",
449 								adif2export(sFld).length());
450 							fprintf(adiFile, "%s", adif2export(sFld).c_str());
451 							if (!adif2submode(sFld).empty()) {
452 								fprintf(adiFile, adifmt,
453 									"SUBMODE",
454 									adif2submode(sFld).length());
455 								fprintf(adiFile, "%s", adif2submode(sFld).c_str());
456 							}
457 						} else {
458 							if (!sFld.empty()) {
459 								fprintf(adiFile, adifmt,
460 									sName.c_str(),
461 									sFld.length());
462 
463                                 //Exchange commas by dots in frequency for ADIF-conformity
464                                 if (strcmp(fields[j].name,"FREQ") == 0) {
465                                     char sfreq[20];
466                                     char* comma_position;
467                                     memset(sfreq, 0, 20);
468                                     strncpy (sfreq, sFld.c_str(), sizeof(sfreq) - 1);
469                                     comma_position = strchr(sfreq,',');
470                                     if (comma_position != NULL) {
471                                         *comma_position = '.';
472                                     }
473 
474                                     fprintf(adiFile, "%s", sfreq);
475                                 } else {
476                                     fprintf(adiFile, "%s", sFld.c_str());
477                                 }
478 							}
479 						}
480 					}
481 				}
482 				j++;
483 			}
484 			rec->putField(EXPORT,"");
485 			db->qsoUpdRec(i, rec);
486 			fprintf(adiFile, "%s", szEOR);
487 			fprintf(adiFile, "%s", szEOL);
488 		}
489 	}
490 	fclose (adiFile);
491 
492 	return 0;
493 }
494 
495 // write ALL records to the common log
496 
497 //======================================================================
498 // thread support writing database
499 //======================================================================
500 
501 pthread_t* ADIF_RW_thread = 0;
502 pthread_mutex_t ADIF_RW_mutex = PTHREAD_MUTEX_INITIALIZER;
503 pthread_cond_t ADIF_RW_cond = PTHREAD_COND_INITIALIZER;
504 static void ADIF_RW_init();
505 
506 static string adif_file_image;
507 static string adif_file_name;
508 static string records;
509 static string record;
510 static char recfield[200];
511 
512 static bool ADIF_READ = false;
513 static bool ADIF_WRITE = false;
514 
515 static cQsoDb *adif_db;
516 
517 static cAdifIO *adifIO = 0;
518 
readFile(const char * fname,cQsoDb * db)519 void cAdifIO::readFile (const char *fname, cQsoDb *db)
520 {
521 	ENSURE_THREAD(FLMAIN_TID);
522 
523 	if (!ADIF_RW_thread)
524 		ADIF_RW_init();
525 
526 	pthread_mutex_lock(&ADIF_RW_mutex);
527 
528 	adif_file_name = fname;
529 	adif_db = db;
530 	adifIO = this;
531 	ADIF_READ = true;
532 
533 	pthread_cond_signal(&ADIF_RW_cond);
534 	pthread_mutex_unlock(&ADIF_RW_mutex);
535 }
536 
537 static cQsoDb *adifdb = 0;
538 static cQsoDb *wrdb = 0;
539 
540 static struct timespec t0, t1;
541 
adif_record(cQsoRec * rec)542 std::string cAdifIO::adif_record(cQsoRec *rec)
543 {
544 	static std::string record;
545 	static std::string sFld;
546 	record.clear();
547 	for (int j = 0; fields[j].type != NUMFIELDS; j++) {
548 		if (strcmp(fields[j].name,"MYXCHG") == 0) continue;
549 		if (strcmp(fields[j].name,"XCHG1") == 0) continue;
550 		sFld = rec->getField(fields[j].type);
551 		if (!sFld.empty()) {
552 			snprintf(recfield, sizeof(recfield),
553 				adifmt,
554 				fields[j].name,
555 				sFld.length());
556 			record.append(recfield).append(sFld);
557 		}
558 	}
559 	record.append(szEOR);
560 	record.append(szEOL);
561 	return record;
562 }
563 
writeAdifRec(cQsoRec * rec,const char * fname)564 int cAdifIO::writeAdifRec (cQsoRec *rec, const char *fname)
565 {
566 	std::string strRecord = adif_record(rec);
567 
568 	FILE *adiFile = fl_fopen (fname, "ab");
569 
570 	if (!adiFile) {
571 		LOG_ERROR("Cannot write to %s", fname);
572 		return 1;
573 	}
574 	LOG_INFO("Write record to %s", fname);
575 
576 	fprintf (adiFile, "%s", strRecord.c_str());
577 
578 	fclose (adiFile);
579 
580 	return 0;
581 }
582 
writeLog(const char * fname,cQsoDb * db,bool immediate)583 int cAdifIO::writeLog (const char *fname, cQsoDb *db, bool immediate) {
584 	ENSURE_THREAD(FLMAIN_TID);
585 
586 	if (!ADIF_RW_thread)
587 		ADIF_RW_init();
588 
589 #ifdef _POSIX_MONOTONIC_CLOCK
590 	clock_gettime(CLOCK_MONOTONIC, &t0);
591 #else
592 	clock_gettime(CLOCK_REALTIME, &t0);
593 #endif
594 
595 	if (!immediate) {
596 		pthread_mutex_lock(&ADIF_RW_mutex);
597 		adif_file_name = fname;
598 		adifIO = this;
599 		ADIF_WRITE = true;
600 		if (wrdb) delete wrdb;
601 		wrdb = new cQsoDb(db);
602 		adifdb = wrdb;
603 		pthread_cond_signal(&ADIF_RW_cond);
604 		pthread_mutex_unlock(&ADIF_RW_mutex);
605 	} else {
606 		adif_file_name = fname;
607 		adifdb = db;
608 		do_writelog();
609 	}
610 
611 	return 1;
612 }
613 
do_writelog()614 void cAdifIO::do_writelog()
615 {
616 	guard_lock lock(&logfile_mutex);
617 
618 	string ADIFHEADER;
619 	ADIFHEADER = "File: %s";
620 	ADIFHEADER.append(szEOL);
621 	ADIFHEADER.append("<ADIF_VER:%d>%s");
622 	ADIFHEADER.append(szEOL);
623 	ADIFHEADER.append("<PROGRAMID:%d>%s");
624 	ADIFHEADER.append(szEOL);
625 	ADIFHEADER.append("<PROGRAMVERSION:%d>%s");
626 	ADIFHEADER.append(szEOL);
627 	ADIFHEADER.append("<EOH>");
628 	ADIFHEADER.append(szEOL);
629 
630 	adiFile = fl_fopen (adif_file_name.c_str(), "wb");
631 
632 	if (!adiFile) {
633 		LOG_ERROR("Cannot write to %s", adif_file_name.c_str());
634 		if (wrdb) delete wrdb;
635 		return;
636 	}
637 	LOG_INFO("Writing %s", adif_file_name.c_str());
638 
639 	cQsoRec *rec;
640 
641 	fprintf ( adiFile, ADIFHEADER.c_str(),
642 		 fl_filename_name(adif_file_name.c_str()),
643 		 strlen(ADIF_VERS), ADIF_VERS,
644 		 strlen(PACKAGE_NAME), PACKAGE_NAME,
645 		 strlen(PACKAGE_VERSION), PACKAGE_VERSION );
646 
647 	for (int i = 0; i < adifdb->nbrRecs(); i++) {
648 		rec = adifdb->getRec(i);
649 		fprintf (adiFile, "%s", adif_record(rec).c_str());
650 		if (wrdb) adifdb->qsoUpdRec(i, rec);
651 	}
652 
653 	fflush (adiFile);
654 	fclose (adiFile);
655 
656 	if (wrdb) delete wrdb;
657 
658 #ifdef _POSIX_MONOTONIC_CLOCK
659 	clock_gettime(CLOCK_MONOTONIC, &t1);
660 #else
661 	clock_gettime(CLOCK_REALTIME, &t1);
662 #endif
663 
664 	t0 = t1 - t0;
665 	float t = (t0.tv_sec + t0.tv_nsec/1e9);
666 
667 	static char szmsg[50];
668 	snprintf(szmsg, sizeof(szmsg), "%d records in %4.2f seconds", adifdb->nbrRecs(), t);
669 	LOG_INFO("%s", szmsg);
670 
671 	snprintf(szmsg, sizeof(szmsg), "Wrote log %d recs", adifdb->nbrRecs());
672 	put_status(szmsg, 5.0);
673 
674 	return;
675 }
676 
677 //======================================================================
678 // thread to support writing database in a separate thread
679 //======================================================================
680 
681 static void *ADIF_RW_loop(void *args);
682 static bool ADIF_RW_EXIT = false;
683 
ADIF_RW_loop(void * args)684 static void *ADIF_RW_loop(void *args)
685 {
686 	SET_THREAD_ID(ADIF_RW_TID);
687 
688 	for (;;) {
689 		pthread_mutex_lock(&ADIF_RW_mutex);
690 		pthread_cond_wait(&ADIF_RW_cond, &ADIF_RW_mutex);
691 		pthread_mutex_unlock(&ADIF_RW_mutex);
692 
693 		if (ADIF_RW_EXIT)
694 			return NULL;
695 		if (ADIF_WRITE && adifIO) {
696 LOG_INFO("ADIF_WRITE: adifIO->do_writelog()");
697 			adifIO->do_writelog();
698 			ADIF_WRITE = false;
699 		} else if (ADIF_READ && adifIO) {
700 LOG_INFO("ADIF_READ: adifIO->do_readfile(%s)", adif_file_name.c_str());
701 			adifIO->do_readfile(adif_file_name.c_str(), adif_db);
702 			ADIF_READ = false;
703 		}
704 	}
705 	return NULL;
706 }
707 
ADIF_RW_close(void)708 void ADIF_RW_close(void)
709 {
710 	ENSURE_THREAD(FLMAIN_TID);
711 
712 	if (!ADIF_RW_thread)
713 		return;
714 
715 	pthread_mutex_lock(&ADIF_RW_mutex);
716 	ADIF_RW_EXIT = true;
717 	LOG_INFO("%s", "Exiting ADIF_RW_thread");
718 	pthread_cond_signal(&ADIF_RW_cond);
719 	pthread_mutex_unlock(&ADIF_RW_mutex);
720 
721 	pthread_join(*ADIF_RW_thread, NULL);
722 	delete ADIF_RW_thread;
723 	ADIF_RW_thread = 0;
724 	LOG_INFO("%s", "ADIF_RW_thread closed");
725 }
726 
ADIF_RW_init()727 static void ADIF_RW_init()
728 {
729 	ENSURE_THREAD(FLMAIN_TID);
730 
731 	if (ADIF_RW_thread)
732 		return;
733 	ADIF_RW_thread = new pthread_t;
734 	ADIF_RW_EXIT = false;
735 	if (pthread_create(ADIF_RW_thread, NULL, ADIF_RW_loop, NULL) != 0) {
736 		LOG_PERROR("pthread_create");
737 		return;
738 	}
739 #ifdef __WIN32__
740 	MilliSleep(100);
741 #else
742 	MilliSleep(10);
743 #endif
744 }
745