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