1 // ----------------------------------------------------------------------------
2 // lookupcall.cxx -- a part of fldigi
3 //
4 // Copyright (C) 2006-2009
5 // Dave Freese, W1HKJ
6 // Copyright (C) 2006-2007
7 // Leigh Klotz, WA5ZNU
8 // Copyright (C) 2008-2009
9 // Stelios Bounanos, M0GLD
10 //
11 // This file is part of fldigi.
12 //
13 // Fldigi is free software: you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation, either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // Fldigi is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
25 // ----------------------------------------------------------------------------
26
27 #include <config.h>
28
29 #ifdef __MINGW32__
30 # include "compat.h"
31 #endif
32
33 #include <sys/time.h>
34 #include "signal.h"
35 #include <string>
36 #include <iostream>
37 #include <cstring>
38 #include <cmath>
39 #include <cctype>
40
41 #include "threads.h"
42
43 #include "misc.h"
44 #include "configuration.h"
45
46 #include "lookupcall.h"
47 #include "logsupport.h"
48 #include "main.h"
49 #include "confdialog.h"
50 #include "fl_digi.h"
51 #include "flmisc.h"
52 #include "qrzlib.h"
53 #include "trx.h"
54
55 #include "xmlreader.h"
56
57 #include "qrunner.h"
58 #include "debug.h"
59 #include "network.h"
60 #include "locator.h"
61
62 using namespace std;
63
64
65 string qrzhost = "xmldata.qrz.com";
66 string qrzSessionKey;
67 string qrzalert;
68 string qrzerror;
69
70 string callsign;
71
72 string lookup_name;
73 string lookup_addr1;
74 string lookup_addr2;
75 string lookup_state;
76 string lookup_province;
77 string lookup_zip;
78 string lookup_country;
79 string lookup_born;
80 string lookup_fname;
81 string lookup_qth;
82 string lookup_grid;
83 string lookup_latd;
84 string lookup_lond;
85 string lookup_notes;
86
87 qrz_xmlquery_t DB_XML_query = QRZXMLNONE;
88 qrz_webquery_t DB_WEB_query = QRZWEBNONE;
89
90 enum TAG {
91 QRZ_IGNORE, QRZ_KEY, QRZ_ALERT, QRZ_ERROR, QRZ_CALL,
92 QRZ_FNAME, QRZ_NAME, QRZ_ADDR1, QRZ_ADDR2, QRZ_STATE,
93 QRZ_ZIP, QRZ_COUNTRY, QRZ_LATD, QRZ_LOND, QRZ_GRID,
94 QRZ_DOB
95 };
96
97 pthread_t* QRZ_thread = 0;
98 pthread_mutex_t qrz_mutex = PTHREAD_MUTEX_INITIALIZER;
99 pthread_cond_t qrz_cond = PTHREAD_COND_INITIALIZER;
100
101 static void *LOOKUP_loop(void *args);
102
103 bool parseSessionKey();
104 bool parse_xml();
105
106 bool getSessionKey(string& sessionpage);
107 bool QRZGetXML(string& xmlpage);
108 int bearing(const char *, const char *);
109 void qra(const char *, double &, double &);
110 void QRZ_disp_result();
111 void QRZ_CD_query();
112 void Lookup_init(void);
113 void QRZclose(void);
114 void qthappend(string &qth, string &datum);
115 void QRZAlert();
116 bool QRZLogin(string& sessionpage);
117 void QRZquery();
118 void parse_html(const string& htmlpage);
119 bool HAMCALLget(string& htmlpage);
120 void HAMCALLquery();
121
122 void QRZ_DETAILS_query();
123
124 QRZ *qCall = 0;
125
126 static notify_dialog *announce = 0;
127
print_query(const string & name,const string & s)128 void print_query(const string &name, const string &s)
129 {
130 // LOG_WARN("%s query:\n%s", name.c_str(), s.c_str());
131 LOG_VERBOSE("%s query:\n%s", name.c_str(), s.c_str());
132 }
133
print_data(const string & name,const string & s)134 void print_data(const string &name, const string &s) {
135 // LOG_WARN("%s data:\n%s", name.c_str(), s.c_str());
136 LOG_VERBOSE("%s data:\n%s", name.c_str(), s.c_str());
137 }
138
clear_Lookup()139 void clear_Lookup()
140 {
141 lookup_name.clear();
142 lookup_addr1.clear();
143 lookup_addr2.clear();
144 lookup_state.clear();
145 lookup_province.clear();
146 lookup_zip.clear();
147 lookup_born.clear();
148 lookup_fname.clear();
149 lookup_qth.clear();
150 lookup_grid.clear();
151 lookup_latd.clear();
152 lookup_lond.clear();
153 lookup_notes.clear();
154 lookup_country.clear();
155 }
156
157 // ----------------------------------------------------------------------------
158 // QRZ subscription query
159 // ----------------------------------------------------------------------------
160
parseSessionKey(const string & sessionpage)161 bool parseSessionKey(const string& sessionpage)
162 {
163 if (sessionpage.find("Bad Request") != string::npos) {
164 return false;
165 }
166
167 IrrXMLReader* xml = createIrrXMLReader(new IIrrXMLStringReader(sessionpage));
168 TAG tag=QRZ_IGNORE;
169 while(xml && xml->read()) {
170 switch(xml->getNodeType())
171 {
172 case EXN_TEXT:
173 case EXN_CDATA:
174 switch (tag)
175 {
176 default:
177 break;
178 case QRZ_KEY:
179 qrzSessionKey = xml->getNodeData();
180 break;
181 case QRZ_ALERT:
182 qrzalert = xml->getNodeData();
183 break;
184 case QRZ_ERROR:
185 qrzerror = xml->getNodeData();
186 break;
187 }
188 break;
189
190 case EXN_ELEMENT_END:
191 tag=QRZ_IGNORE;
192 break;
193
194 case EXN_ELEMENT:
195 {
196 const char *nodeName = xml->getNodeName();
197 if (!strcmp("Key", nodeName)) tag=QRZ_KEY;
198 else if (!strcmp("Alert", nodeName)) tag=QRZ_ALERT;
199 else if (!strcmp("Error", nodeName)) tag=QRZ_ERROR;
200 else tag=QRZ_IGNORE;
201 break;
202 }
203
204 case EXN_NONE:
205 case EXN_COMMENT:
206 case EXN_UNKNOWN:
207 break;
208 }
209 }
210 delete xml;
211 return true;
212 }
213
214
parse_xml(const string & xmlpage)215 bool parse_xml(const string& xmlpage)
216 {
217 print_data("xmldata.qrz.com", xmlpage);
218
219 if (xmlpage.find("<Error>Not found") != std::string::npos) {
220 if (!announce) announce = new notify_dialog;
221 announce->notify("Not found", 2.0);
222 REQ(show_notifier, announce);
223 return false;
224 }
225
226 IrrXMLReader* xml = createIrrXMLReader(new IIrrXMLStringReader(xmlpage));
227
228 // If we got any result back, clear the session key so that it will be
229 // refreshed by this response, or if not present, will be removed and we'll
230 // know to log in next time.
231 if (xml) {
232 qrzSessionKey.clear();
233 qrzalert.clear();
234 qrzerror.clear();
235 clear_Lookup();
236 }
237
238 TAG tag = QRZ_IGNORE;
239
240 // parse the file until end reached
241 while(xml && xml->read()) {
242 switch(xml->getNodeType()) {
243 case EXN_TEXT:
244 case EXN_CDATA:
245 switch (tag) {
246 default:
247 case QRZ_IGNORE:
248 break;
249 case QRZ_CALL:
250 break;
251 case QRZ_FNAME:
252 lookup_fname = xml->getNodeData();
253 break;
254 case QRZ_NAME:
255 lookup_name = xml->getNodeData();
256 break;
257 case QRZ_ADDR1:
258 {
259 lookup_addr1 = xml->getNodeData();
260 size_t apt = lookup_addr1.find("#");
261 if (apt != string::npos)
262 lookup_addr1.erase(apt, lookup_addr1.length() - apt);
263 break;
264 }
265 case QRZ_ADDR2:
266 lookup_addr2 = xml->getNodeData();
267 break;
268 case QRZ_STATE:
269 lookup_state = xml->getNodeData();
270 break;
271 case QRZ_ZIP:
272 lookup_zip = xml->getNodeData();
273 break;
274 case QRZ_COUNTRY:
275 lookup_country = xml->getNodeData();
276 break;
277 case QRZ_LATD:
278 lookup_latd = xml->getNodeData();
279 break;
280 case QRZ_LOND:
281 lookup_lond = xml->getNodeData();
282 break;
283 case QRZ_GRID:
284 lookup_grid = xml->getNodeData();
285 break;
286 case QRZ_ALERT:
287 qrzalert = xml->getNodeData();
288 break;
289 case QRZ_ERROR:
290 qrzerror = xml->getNodeData();
291 break;
292 case QRZ_KEY:
293 qrzSessionKey = xml->getNodeData();
294 break;
295 }
296 break;
297
298 case EXN_ELEMENT_END:
299 tag=QRZ_IGNORE;
300 break;
301
302 case EXN_ELEMENT:
303 {
304 const char *nodeName = xml->getNodeName();
305 if (!strcmp("call", nodeName)) tag = QRZ_CALL;
306 else if (!strcmp("fname", nodeName)) tag = QRZ_FNAME;
307 else if (!strcmp("name", nodeName)) tag = QRZ_NAME;
308 else if (!strcmp("addr1", nodeName)) tag = QRZ_ADDR1;
309 else if (!strcmp("addr2", nodeName)) tag = QRZ_ADDR2;
310 else if (!strcmp("state", nodeName)) tag = QRZ_STATE;
311 else if (!strcmp("zip", nodeName)) tag = QRZ_ZIP;
312 else if (!strcmp("country", nodeName)) tag = QRZ_COUNTRY;
313 else if (!strcmp("lat", nodeName)) tag = QRZ_LATD;
314 else if (!strcmp("lon", nodeName)) tag = QRZ_LOND;
315 else if (!strcmp("grid", nodeName)) tag = QRZ_GRID;
316 else if (!strcmp("dob", nodeName)) tag = QRZ_DOB;
317 else if (!strcmp("Alert", nodeName)) tag = QRZ_ALERT;
318 else if (!strcmp("Error", nodeName)) tag = QRZ_ERROR;
319 else if (!strcmp("Key", nodeName)) tag = QRZ_KEY;
320 else tag = QRZ_IGNORE;
321 }
322 break;
323
324 case EXN_NONE:
325 case EXN_COMMENT:
326 case EXN_UNKNOWN:
327 break;
328 }
329 }
330
331 // delete the xml parser after usage
332 delete xml;
333 return true;
334 }
335
getSessionKey(string & sessionpage)336 bool getSessionKey(string& sessionpage)
337 {
338 string html = "http://";
339 html.append(qrzhost);
340 html.append(" /xml/current/?username=");
341 html.append(progdefaults.QRZusername);
342 html.append(";password=");
343 html.append(progdefaults.QRZuserpassword);
344 html.append(";agent=");
345 html.append(PACKAGE_NAME);
346 html.append("-");
347 html.append(PACKAGE_VERSION);
348 return get_http(html, sessionpage, 5.0);
349 }
350
QRZGetXML(string & xmlpage)351 bool QRZGetXML(string& xmlpage)
352 {
353 string html;
354 html.assign("http://").append(qrzhost);
355 html.append(" /bin/xml?s=");
356 html.append(qrzSessionKey);
357 html.append(";callsign=");
358 html.append(callsign);
359 return get_http(html, xmlpage, 5.0);
360 }
361
camel_case(string & s)362 void camel_case(string &s)
363 {
364 bool first_letter = true;
365 for (size_t n = 0; n < s.length(); n++) {
366 if (s[n] == ' ') first_letter = true;
367 else if (first_letter) {
368 s[n] = toupper(s[n]);
369 first_letter = false;
370 } else s[n] = tolower(s[n]);
371 }
372 }
373
QRZ_disp_result()374 void QRZ_disp_result()
375 {
376 ENSURE_THREAD(FLMAIN_TID);
377
378 if (lookup_fname.length() > 0) {
379 camel_case(lookup_fname);
380 string::size_type spacePos = lookup_fname.find(" ");
381 // if fname is "ABC" then display "ABC"
382 // or if fname is "A BCD" then display "A BCD"
383 if (spacePos == string::npos || (spacePos == 1)) {
384 inpName->value(lookup_fname.c_str());
385 }
386 // if fname is "ABC Y" then display "ABC"
387 else if (spacePos > 2) {
388 string fname;
389 fname.assign(lookup_fname, 0, spacePos);
390 inpName->value(fname.c_str());
391 }
392 // fname must be "ABC DEF" so display "ABC DEF"
393 else {
394 inpName->value(lookup_fname.c_str());
395 }
396 } else if (lookup_name.length() > 0) {
397 // only name is set; don't know first/last, so just show all
398 inpName->value(lookup_name.c_str());
399 }
400
401 inpQth->value(lookup_qth.c_str());
402 inpQth->position (0);
403
404 inpState->value(lookup_state.c_str());
405 inpState->position (0);
406
407 inpVEprov->value(lookup_province.c_str());
408 inpVEprov->position (0);
409
410 inpLoc->value(lookup_grid.c_str());
411 inpLoc->position (0);
412
413 if (!lookup_country.empty()) {
414 cboCountry->value(lookup_country.c_str());
415 }
416
417 if (!progdefaults.myLocator.empty() && !lookup_grid.empty()) {
418 char buf[10];
419 buf[0] = '\0';
420 double distance, azimuth, lon[2], lat[2];
421 if (QRB::locator2longlat(&lon[0], &lat[0], progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
422 QRB::locator2longlat(&lon[1], &lat[1], lookup_grid.c_str()) == QRB::QRB_OK &&
423 QRB::qrb(lon[0], lat[0], lon[1], lat[1], &distance, &azimuth) == QRB::QRB_OK)
424 snprintf(buf, sizeof(buf), "%03.0f", round(azimuth));
425 inpAZ->value(buf);
426 inpAZ->position (0);
427 }
428 inpNotes->value(lookup_notes.c_str());
429 inpNotes->position (0);
430
431 }
432
QRZ_CD_query()433 void QRZ_CD_query()
434 {
435 ENSURE_THREAD(QRZ_TID);
436
437 char srch[20];
438 size_t snip;
439
440 memset( srch, 0, sizeof(srch) );
441 strncpy( srch, callsign.c_str(), 6 );
442 for (size_t i = 0; i < strlen(srch); i ++ )
443 srch[i] = toupper(srch[i]);
444
445 string notes;
446 if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
447 else notes.clear();
448
449 if( qCall->FindRecord( srch ) == 1) {
450 lookup_fname = qCall->GetFname();
451 camel_case(lookup_fname);
452 snip = lookup_fname.find(' ');
453 if (snip != string::npos)
454 lookup_fname.erase(snip, lookup_fname.length() - snip);
455 lookup_qth = qCall->GetCity();
456 lookup_state = qCall->GetState();
457 lookup_grid.clear();
458 if (progdefaults.notes_address) {
459 if (!notes.empty()) notes.append("\n");
460 notes.append(lookup_fname).append(" ").append(lookup_name).append("\n");
461 notes.append(lookup_addr1).append("\n");
462 notes.append(lookup_addr2);
463 if (!lookup_state.empty())
464 notes.append(", ").append(lookup_state).append(" ").append(lookup_zip);
465 else if (!lookup_province.empty())
466 notes.append(", ").append(lookup_province).append(" ").append(lookup_zip);
467 else
468 notes.append(" ").append(lookup_country);
469 }
470 } else {
471 lookup_fname.clear();
472 lookup_qth.clear();
473 lookup_grid.clear();
474 lookup_born.clear();
475 lookup_notes.append("Not found in CD database");
476 }
477 REQ(QRZ_disp_result);
478 }
479
Lookup_init(void)480 void Lookup_init(void)
481 {
482 ENSURE_THREAD(FLMAIN_TID);
483
484 if (QRZ_thread)
485 return;
486 QRZ_thread = new pthread_t;
487 if (pthread_create(QRZ_thread, NULL, LOOKUP_loop, NULL) != 0) {
488 LOG_PERROR("pthread_create");
489 return;
490 }
491 MilliSleep(10);
492 }
493
QRZclose(void)494 void QRZclose(void)
495 {
496 ENSURE_THREAD(FLMAIN_TID);
497
498 if (!QRZ_thread)
499 return;
500
501 CANCEL_THREAD(*QRZ_thread);
502
503 DB_XML_query = QRZXML_EXIT;
504 DB_WEB_query = QRZWEB_EXIT;
505
506 pthread_mutex_lock(&qrz_mutex);
507 pthread_cond_signal(&qrz_cond);
508 pthread_mutex_unlock(&qrz_mutex);
509
510 pthread_join(*QRZ_thread, NULL);
511 delete QRZ_thread;
512 QRZ_thread = 0;
513 }
514
qthappend(string & qth,string & datum)515 void qthappend(string &qth, string &datum) {
516 if (datum.empty()) return;
517 if (!qth.empty()) qth += ", ";
518 qth += datum;
519 }
520
QRZAlert()521 void QRZAlert()
522 {
523 ENSURE_THREAD(FLMAIN_TID);
524
525 string qrznote;
526 if (!qrzalert.empty()) {
527 qrznote.append("QRZ alert:\n");
528 qrznote.append(qrzalert);
529 qrznote.append("\n");
530 qrzalert.clear();
531 }
532 if (!qrzerror.empty()) {
533 qrznote.append("QRZ error:\n");
534 qrznote.append(qrzerror);
535 qrzerror.clear();
536 }
537 string notes;
538 if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
539 else notes.clear();
540
541 if (!qrznote.empty()) {
542 if (!notes.empty()) notes.append("\n");
543 notes.append(qrznote);
544 inpNotes->value(notes.c_str());
545 }
546 }
547
QRZLogin(string & sessionpage)548 bool QRZLogin(string& sessionpage)
549 {
550 bool ok = true;
551 if (qrzSessionKey.empty()) {
552 ok = getSessionKey(sessionpage);
553 if (ok) ok = parseSessionKey(sessionpage);
554 }
555 if (!ok) {
556 LOG_VERBOSE("failed");
557 REQ(QRZAlert);
558 }
559
560 return ok;
561 }
562
QRZquery()563 void QRZquery()
564 {
565 ENSURE_THREAD(QRZ_TID);
566
567 bool ok = true;
568
569 string qrzpage;
570
571 if (qrzSessionKey.empty())
572 ok = QRZLogin(qrzpage);
573 if (ok)
574 ok = QRZGetXML(qrzpage);
575 if (ok) {
576 parse_xml(qrzpage);
577 if (!qrzalert.empty() || !qrzerror.empty())
578 REQ(QRZAlert);
579 else {
580 lookup_qth = lookup_addr2;
581 if (lookup_country.find("Canada") != string::npos) {
582 lookup_province = lookup_state;
583 lookup_state.clear();
584 }
585
586 string notes;
587 if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
588 else notes.clear();
589
590 if (progdefaults.notes_address) {
591 if (!notes.empty()) notes.append("\n");
592 notes.append(lookup_fname).append(" ").append(lookup_name).append("\n");
593 notes.append(lookup_addr1).append("\n");
594 notes.append(lookup_addr2);
595 if (!lookup_state.empty())
596 notes.append(", ").append(lookup_state).append(" ").append(lookup_zip);
597 else if (!lookup_province.empty())
598 notes.append(", ").append(lookup_province).append(" ").append(lookup_zip);
599 else
600 notes.append(" ").append(lookup_country);
601 }
602 lookup_notes = notes;
603 REQ(QRZ_disp_result);
604 }
605 }
606 else {
607 qrzerror = qrzpage;
608 REQ(QRZAlert);
609 }
610 }
611
612 // ---------------------------------------------------------------------
613 // HTTP:://callook.info queries
614 // ---------------------------------------------------------------------
615
node_data(const string & xmlpage,const string nodename)616 string node_data(const string &xmlpage, const string nodename)
617 {
618 size_t pos1, pos2;
619 string test;
620 test.assign("<").append(nodename).append(">");
621 pos1 = xmlpage.find(test);
622 if (pos1 == string::npos) return "";
623 pos1 += test.length();
624 test.assign("</").append(nodename).append(">");
625 pos2 = xmlpage.find(test);
626 if (pos2 == string::npos) return "";
627 return xmlpage.substr(pos1, pos2 - pos1);
628 }
629
parse_callook(string & xmlpage)630 void parse_callook(string& xmlpage)
631 {
632 print_data("Callook info", xmlpage);
633
634 if (xmlpage.find("INVALID") != std::string::npos) {
635 if (!announce) announce = new notify_dialog;
636 announce->notify("Call not found", 2.0);
637 REQ(show_notifier, announce);
638 return;
639 }
640
641 string nodestr = node_data(xmlpage, "current");
642
643 if (nodestr.empty()) {
644 if (!announce) announce = new notify_dialog;
645 announce->notify("no data from callook.info", 2.0);
646 REQ(show_notifier, announce);
647 return;
648 }
649
650 size_t start_pos = xmlpage.find("</trustee>");
651 if (start_pos == string::npos) return;
652
653 start_pos += 10;
654 xmlpage = xmlpage.substr(start_pos);
655 lookup_name = node_data(xmlpage, "name");
656 camel_case(lookup_name);
657 lookup_fname = lookup_name;
658
659 nodestr = node_data(xmlpage, "address");
660 if (!nodestr.empty()) {
661 lookup_addr1 = node_data(nodestr, "line1");
662 lookup_addr2 = node_data(nodestr, "line2");
663 }
664
665 nodestr = node_data(xmlpage, "location");
666 if (!nodestr.empty()) {
667 lookup_lond = node_data(nodestr, "longitude");
668 lookup_latd = node_data(nodestr, "latitude");
669 lookup_grid = node_data(nodestr, "gridsquare");
670 }
671
672 string notes;
673 if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
674 else notes.clear();
675
676 if (progdefaults.notes_address) {
677 if (!notes.empty()) notes.append("\n");
678 notes.append(lookup_name).append("\n");
679 notes.append(lookup_addr1).append("\n");
680 notes.append(lookup_addr2);
681 }
682 lookup_notes = notes;
683
684 size_t p = lookup_addr2.find(",");
685 if (p != string::npos) {
686 lookup_qth = lookup_addr2.substr(0, p);
687 lookup_addr2.erase(0, p+2);
688 p = lookup_addr2.find(" ");
689 if (p != string::npos)
690 lookup_state = lookup_addr2.substr(0, p);
691 }
692
693 }
694
CALLOOKGetXML(string & xmlpage)695 bool CALLOOKGetXML(string& xmlpage)
696 {
697 string url = progdefaults.callookurl;
698 size_t p = 0;
699 if ((p = url.find("https")) != std::string::npos)
700 url.erase(p+4,1);
701 url.append(callsign).append("/xml");
702 bool res = get_http(url, xmlpage, 5.0);
703 LOG_VERBOSE("result = %d", res);
704 return res;
705 }
706
CALLOOKquery()707 void CALLOOKquery()
708 {
709 ENSURE_THREAD(QRZ_TID);
710
711 // bool ok = true;
712
713 string CALLOOKpage;
714
715 clear_Lookup();
716 // ok =
717 CALLOOKGetXML(CALLOOKpage);
718 // if (ok)
719 parse_callook(CALLOOKpage);
720 REQ(QRZ_disp_result);
721 }
722
723 // ---------------------------------------------------------------------
724 // Hamcall specific functions
725 // ---------------------------------------------------------------------
726
727 #define HAMCALL_CALL (char)181
728 #define HAMCALL_FIRST (char)184
729 #define HAMCALL_CITY (char)191
730 #define HAMCALL_STATE (char)192
731 #define HAMCALL_GRID (char)202
732 #define HAMCALL_DOB (char)194
733
parse_html(const string & htmlpage)734 void parse_html(const string& htmlpage)
735 {
736 print_data("Hamcall data", htmlpage);
737
738 size_t p;
739
740 clear_Lookup();
741 if ((p = htmlpage.find(HAMCALL_FIRST)) != string::npos) {
742 p++;
743 while ((uchar)htmlpage[p] < 128 && p < htmlpage.length() )
744 lookup_fname.append(1, htmlpage[p++]);
745 camel_case(lookup_fname);
746 }
747 if ((p = htmlpage.find(HAMCALL_CITY)) != string::npos) {
748 p++;
749 while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
750 lookup_qth.append(1, htmlpage[p++]);
751 }
752 if ((p = htmlpage.find(HAMCALL_STATE)) != string::npos) {
753 p++;
754 while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
755 lookup_state.append(1, htmlpage[p++]);
756 }
757 if ((p = htmlpage.find(HAMCALL_GRID)) != string::npos) {
758 p++;
759 while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
760 lookup_grid.append(1, htmlpage[p++]);
761 }
762 if ((p = htmlpage.find(HAMCALL_DOB)) != string::npos) {
763 p++;
764 lookup_notes.append("DOB: ");
765 while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
766 lookup_notes.append(1, htmlpage[p++]);
767 }
768 }
769
HAMCALLget(string & htmlpage)770 bool HAMCALLget(string& htmlpage)
771 {
772 string html;
773 size_t p1;
774
775 html.assign(progdefaults.hamcallurl);
776 if ((p1 = html.find("https")) != std::string::npos)
777 html.erase(p1+4,1);
778 if (html[html.length()-1] != '/') html += '/';
779 html.append("/call?username=");
780 html.append(progdefaults.QRZusername);
781 html.append("&password=");
782 html.append(progdefaults.QRZuserpassword);
783 html.append("&rawlookup=1&callsign=");
784 html.append(callsign);
785 html.append("&program=fldigi-");
786 html.append(VERSION);
787 return get_http(html, htmlpage, 5.0);
788
789 // print_query("hamcall", url_html);
790
791 // string url = progdefaults.hamcallurl;
792 // size_t p = url.find("//");
793 // string service = url.substr(0, p);
794 // url.erase(0, p+2);
795 // size_t len = url.length();
796 // if (url[len-1]=='/') url.erase(len-1, 1);
797 // return network_query(url, service, url_html, htmlpage, 5.0);
798 }
799
HAMCALLquery()800 void HAMCALLquery()
801 {
802 ENSURE_THREAD(QRZ_TID);
803
804 string htmlpage;
805
806 if (HAMCALLget(htmlpage))
807 parse_html(htmlpage);
808 else
809 lookup_notes = htmlpage;
810 REQ(QRZ_disp_result);
811 }
812
813 // ---------------------------------------------------------------------
814 // Hamcall specific functions
815 // ---------------------------------------------------------------------
816
817 static string HAMQTH_session_id = "";
818 static string HAMQTH_reply = "";
819
820 #define HAMQTH_DEBUG 1
821 #undef HAMQTH_DEBUG
822 /*
823 * send:
824 https://www.hamqth.com/xml.php?u=username&p=password
825 * response:
826 <?xml version="1.0"?>
827 <HamQTH version="2.7" xmlns="https://www.hamqth.com">
828 <session>
829 <session_id>09b0ae90050be03c452ad235a1f2915ad684393c</session_id>
830 </session>
831 </HamQTH>
832 *
833 * send:
834 https://www.hamqth.com/xml.php?id=09b0ae90050be03c452ad235a1f2915ad684393c\
835 &callsign=ok7an&prg=YOUR_PROGRAM_NAME
836 * response:
837 <?xml version="1.0"?>
838 <HamQTH version="2.7" xmlns="https://www.hamqth.com">
839 <search>
840 <callsign>ok7an</callsign>
841 <nick>Petr</nick>
842 <qth>Neratovice</qth>
843 <country>Czech Republic</country>
844 <adif>503</adif>
845 <itu>28</itu>
846 <cq>15</cq>
847 <grid>jo70gg</grid>
848 <adr_name>Petr Hlozek</adr_name>
849 <adr_street1>17. listopadu 1065</adr_street1>
850 <adr_city>Neratovice</adr_city>
851 <adr_zip>27711</adr_zip>
852 <adr_country>Czech Republic</adr_country>
853 <adr_adif>503</adr_adif>
854 <district>GZL</district>
855 <lotw>Y</lotw>
856 <qsl>Y</qsl>
857 <qsldirect>Y</qsldirect>
858 <eqsl>Y</eqsl>
859 <email>petr@ok7an.com</email>
860 <jabber>petr@ok7an.com</jabber>
861 <skype>PetrHH</skype>
862 <birth_year>1982</birth_year>
863 <lic_year>1998</lic_year>
864 <web>https://www.ok7an.com</web>
865 <latitude>50.07</latitude>
866 <longitude>14.42</longitude>
867 <continent>EU</continent>
868 <utc_offset>-1</utc_offset>
869 <picture>https://www.hamqth.com/userfiles/o/ok/ok7an/_profile/ok7an_nove.jpg</picture>
870 </search>
871 </HamQTH>
872 */
HAMQTH_get_session_id()873 bool HAMQTH_get_session_id()
874 {
875 string url;
876 string retstr = "";
877 size_t p1 = string::npos;
878 size_t p2 = string::npos;
879
880 url.assign(progdefaults.hamqthurl);
881 if ((p1 = url.find("https")) != std::string::npos)
882 url.erase(p1+4,1);
883 if (url[url.length()-1] != '/') url += '/';
884 url.append("xml.php?u=").append(progdefaults.QRZusername);
885 url.append("&p=").append(progdefaults.QRZuserpassword);
886
887 HAMQTH_session_id.clear();
888
889 int ret = get_http(url, retstr, 5.0);
890
891 if (ret == 0 ) {
892 LOG_ERROR("get_http( %s, retstr, 5.0) failed\n", url.c_str());
893 return false;
894 }
895
896 LOG_VERBOSE("url: %s", url.c_str());
897 LOG_VERBOSE("reply: %s\n", retstr.c_str());
898 p1 = retstr.find("<error>");
899 if (p1 != string::npos) {
900 p2 = retstr.find("</error>");
901 if (p2 != string::npos) {
902 p1 += 7;
903 lookup_notes = retstr.substr(p1, p2 - p1);
904 }
905 return false;
906 }
907 p1 = retstr.find("<session_id>");
908 if (p1 == string::npos) {
909 lookup_notes = "HamQTH not available";
910 return false;
911 }
912 p2 = retstr.find("</session_id>");
913 HAMQTH_session_id = retstr.substr(p1 + 12, p2 - p1 - 12);
914 print_data("HamQTH session id", HAMQTH_session_id);
915 return true;
916 }
917
parse_HAMQTH_html(const string & htmlpage)918 void parse_HAMQTH_html(const string& htmlpage)
919 {
920 print_data("HamQth html", htmlpage);
921
922 size_t p = string::npos;
923 size_t p1 = string::npos;
924 string tempstr;
925
926 clear_Lookup();
927
928 lookup_fname.clear();
929 lookup_qth.clear();
930 lookup_state.clear();
931 lookup_grid.clear();
932 lookup_notes.clear();
933 lookup_country.clear();
934
935 if ((p = htmlpage.find("<error>")) != string::npos) {
936 p += 7;
937 p1 = htmlpage.find("</error>", p);
938 if (p1 != string::npos) {
939 std::string errstr = htmlpage.substr(p, p1 - p);
940 if (!announce) announce = new notify_dialog;
941 announce->notify(errstr.c_str(), 2.0);
942 REQ(show_notifier, announce);
943 }
944 return;
945 }
946 if ((p = htmlpage.find("<nick>")) != string::npos) {
947 p += 6;
948 p1 = htmlpage.find("</nick>", p);
949 if (p1 != string::npos) {
950 lookup_fname = htmlpage.substr(p, p1 - p);
951 camel_case(lookup_fname);
952 }
953 }
954 if ((p = htmlpage.find("<qth>")) != string::npos) {
955 p += 5;
956 p1 = htmlpage.find("</qth>", p);
957 if (p1 != string::npos)
958 lookup_qth = htmlpage.substr(p, p1 - p);
959 }
960 if ((p = htmlpage.find("<country>")) != string::npos) {
961 p += 9;
962 p1 = htmlpage.find("</country>", p);
963 if (p1 != string::npos) {
964 lookup_country = htmlpage.substr(p, p1 - p);
965 if (lookup_country == "Canada") {
966 p = htmlpage.find("<district>");
967 if (p != string::npos) {
968 p += 10;
969 p1 = htmlpage.find("</district>", p);
970 if (p1 != string::npos)
971 lookup_province = htmlpage.substr(p, p1 - p);
972 }
973 }
974 }
975 }
976 if ((p = htmlpage.find("<us_state>")) != string::npos) {
977 p += 10;
978 p1 = htmlpage.find("</us_state>", p);
979 if (p1 != string::npos)
980 lookup_state = htmlpage.substr(p, p1 - p);
981 }
982 if ((p = htmlpage.find("<grid>")) != string::npos) {
983 p += 6;
984 p1 = htmlpage.find("</grid>", p);
985 if (p1 != string::npos)
986 lookup_grid = htmlpage.substr(p, p1 - p);
987 }
988 if ((p = htmlpage.find("<qsl_via>")) != string::npos) {
989 p += 9;
990 p1 = htmlpage.find("</qsl_via>", p);
991 if (p1 != string::npos) {
992 tempstr.assign(htmlpage.substr(p, p1 - p));
993 if (!tempstr.empty())
994 lookup_notes.append("QSL via: ").append(tempstr).append("\n");
995 }
996 }
997 if (progdefaults.notes_address) {
998 if ((p = htmlpage.find("<adr_name>")) != string::npos) {
999 p += 10;
1000 p1 = htmlpage.find("</adr_name>", p);
1001 if (p1 != string::npos) {
1002 tempstr.assign(htmlpage.substr(p, p1 - p));
1003 if (!tempstr.empty())
1004 lookup_notes.append(tempstr).append("\n");
1005 }
1006 }
1007 if ((p = htmlpage.find("<adr_street1>")) != string::npos) {
1008 p += 13;
1009 p1 = htmlpage.find("</adr_street1>", p);
1010 if (p1 != string::npos) {
1011 tempstr.assign(htmlpage.substr(p, p1 - p));
1012 if (!tempstr.empty())
1013 lookup_notes.append(tempstr).append("\n");
1014 }
1015 }
1016 if ((p = htmlpage.find("<adr_city>")) != string::npos) {
1017 p += 10;
1018 p1 = htmlpage.find("</adr_city>", p);
1019 if (p1 != string::npos) {
1020 tempstr.assign(htmlpage.substr(p, p1 - p));
1021 if (!tempstr.empty())
1022 lookup_notes.append(tempstr);
1023 if (!lookup_state.empty())
1024 lookup_notes.append(", ").append(lookup_state);
1025 else if (!lookup_province.empty())
1026 lookup_notes.append(", ").append(lookup_province);
1027 }
1028 }
1029 if ((p = htmlpage.find("<adr_zip>")) != string::npos) {
1030 p += 9;
1031 p1 = htmlpage.find("</adr_zip>", p);
1032 if (p1 != string::npos) {
1033 tempstr.assign(htmlpage.substr(p, p1 - p));
1034 if (!tempstr.empty())
1035 lookup_notes.append(" ").append(tempstr);
1036 }
1037 }
1038 }
1039 }
1040
HAMQTHget(string & htmlpage)1041 bool HAMQTHget(string& htmlpage)
1042 {
1043 string url;
1044 bool ret;
1045
1046 if (HAMQTH_session_id.empty()) {
1047 if (!HAMQTH_get_session_id()) {
1048 LOG_WARN("HAMQTH session id failed!");
1049 lookup_notes = "Get session id failed!\n";
1050 return false;
1051 }
1052 }
1053
1054 size_t p1;
1055 url.assign(progdefaults.hamqthurl);
1056 if ((p1 = url.find("https")) != std::string::npos)
1057 url.erase(p1+4,1);
1058 if (url[url.length()-1] != '/') url += '/';
1059 url.append("xml.php?id=").append(HAMQTH_session_id);
1060 url.append("&callsign=").append(callsign);
1061 url.append("&prg=FLDIGI");
1062
1063 print_query("HamQTH", url);
1064
1065 ret = get_http(url, htmlpage, 5.0);
1066
1067 size_t p = htmlpage.find("Session does not exist or expired");
1068 if (p != string::npos) {
1069 htmlpage.clear();
1070 LOG_WARN("HAMQTH session id expired!");
1071 HAMQTH_session_id.clear();
1072 if (!HAMQTH_get_session_id()) {
1073 LOG_WARN("HAMQTH get session id failed!");
1074 lookup_notes = "Get session id failed!\n";
1075 htmlpage.clear();
1076 return false;
1077 }
1078 ret = get_http(url, htmlpage, 5.0);
1079 }
1080 #ifdef HAMQTH_DEBUG
1081 FILE *fetchit = fopen("fetchit.txt", "a");
1082 fprintf(fetchit, "%s\n", htmlpage.c_str());
1083 fclose(fetchit);
1084 #endif
1085 return ret;
1086 }
1087
HAMQTHquery()1088 void HAMQTHquery()
1089 {
1090 ENSURE_THREAD(QRZ_TID);
1091
1092 string htmlpage;
1093
1094 if (!HAMQTHget(htmlpage)) return;
1095
1096 parse_HAMQTH_html(htmlpage);
1097 REQ(QRZ_disp_result);
1098
1099 }
1100
1101 // ----------------------------------------------------------------------------
1102
QRZ_DETAILS_query()1103 void QRZ_DETAILS_query()
1104 {
1105 string qrz = progdefaults.qrzurl;
1106 qrz.append("db/").append(callsign);
1107 cb_mnuVisitURL(0, (void*)qrz.c_str());
1108 }
1109
HAMCALL_DETAILS_query()1110 void HAMCALL_DETAILS_query()
1111 {
1112 string hamcall = progdefaults.hamcallurl;
1113 hamcall.append("call?callsign=").append(callsign);
1114 cb_mnuVisitURL(0, (void*)hamcall.c_str());
1115 }
1116
HAMQTH_DETAILS_query()1117 void HAMQTH_DETAILS_query()
1118 {
1119 string hamqth = progdefaults.hamqthurl;
1120 hamqth.append(callsign);
1121 cb_mnuVisitURL(0, (void*)hamqth.c_str());
1122 }
1123
CALLOOK_DETAILS_query()1124 void CALLOOK_DETAILS_query()
1125 {
1126 string hamcall = progdefaults.callookurl;
1127 hamcall.append(callsign);
1128 cb_mnuVisitURL(0, (void *)hamcall.c_str());
1129 }
1130
1131 // ----------------------------------------------------------------------------
1132
LOOKUP_loop(void * args)1133 static void *LOOKUP_loop(void *args)
1134 {
1135 SET_THREAD_ID(QRZ_TID);
1136
1137 SET_THREAD_CANCEL();
1138
1139 for (;;) {
1140 TEST_THREAD_CANCEL();
1141 pthread_mutex_lock(&qrz_mutex);
1142 pthread_cond_wait(&qrz_cond, &qrz_mutex);
1143 pthread_mutex_unlock(&qrz_mutex);
1144
1145 switch (DB_XML_query) {
1146 case QRZCD :
1147 QRZ_CD_query();
1148 break;
1149 case QRZNET :
1150 QRZquery();
1151 break;
1152 case HAMCALLNET :
1153 HAMCALLquery();
1154 break;
1155 case CALLOOK:
1156 CALLOOKquery();
1157 break;
1158 case HAMQTH:
1159 HAMQTHquery();
1160 break;
1161 case QRZXML_EXIT:
1162 return NULL;
1163 default:
1164 break;
1165 }
1166
1167 switch (DB_WEB_query) {
1168 case QRZHTML :
1169 QRZ_DETAILS_query();
1170 break;
1171 case HAMCALLHTML :
1172 HAMCALL_DETAILS_query();
1173 break;
1174 case HAMQTHHTML :
1175 HAMQTH_DETAILS_query();
1176 break;
1177 case CALLOOKHTML :
1178 CALLOOK_DETAILS_query();
1179 break;
1180 case QRZWEB_EXIT:
1181 return NULL;
1182 default:
1183 break;
1184 }
1185 }
1186
1187 return NULL;
1188 }
1189
CALLSIGNquery()1190 void CALLSIGNquery()
1191 {
1192 ENSURE_THREAD(FLMAIN_TID);
1193
1194 if (!QRZ_thread)
1195 Lookup_init();
1196
1197 // Filter callsign for nonsense characters (remove all but [A-Za-z0-9/])
1198 callsign.clear();
1199 for (const char* p = inpCall->value(); *p; p++)
1200 if (isalnum(*p) || *p == '/')
1201 callsign += *p;
1202 if (callsign.empty())
1203 return;
1204 if (callsign != inpCall->value())
1205 inpCall->value(callsign.c_str());
1206
1207 size_t slash;
1208 while ((slash = callsign.rfind('/')) != string::npos) {
1209 if (((slash+1) * 2) < callsign.length())
1210 callsign.erase(0, slash + 1);
1211 else
1212 callsign.erase(slash);
1213 }
1214
1215 switch (DB_XML_query = static_cast<qrz_xmlquery_t>(progdefaults.QRZXML)) {
1216 case QRZNET:
1217 LOG_INFO("%s","Request sent to qrz.com...");
1218 break;
1219 case HAMCALLNET:
1220 LOG_INFO("%s","Request sent to www.hamcall.net...");
1221 break;
1222 case QRZCD:
1223 if (!qCall)
1224 qCall = new QRZ( "callbkc" );
1225 if (progdefaults.QRZchanged) {
1226 qCall->NewDBpath("callbkc");
1227 progdefaults.QRZchanged = false;
1228 }
1229 if (!qCall->getQRZvalid()) {
1230 LOG_ERROR("%s","QRZ DB error");
1231 DB_XML_query = QRZXMLNONE;
1232 return;
1233 }
1234 break;
1235 case CALLOOK:
1236 LOG_INFO("Request sent to %s", progdefaults.callookurl.c_str());
1237 break;
1238 case HAMQTH:
1239 LOG_INFO("Request sent to %s", progdefaults.hamqthurl.c_str());
1240 break;
1241 case QRZXMLNONE:
1242 break;
1243 default:
1244 LOG_ERROR("Bad query type %d", DB_XML_query);
1245 return;
1246 }
1247
1248 DB_WEB_query = static_cast<qrz_webquery_t>(progdefaults.QRZWEB);
1249
1250 pthread_mutex_lock(&qrz_mutex);
1251 pthread_cond_signal(&qrz_cond);
1252 pthread_mutex_unlock(&qrz_mutex);
1253 }
1254
1255
1256 /// With this constructor, no need to declare the array mode_info[] in the calling program.
QsoHelper(int the_mode)1257 QsoHelper::QsoHelper(int the_mode)
1258 : qso_rec( new cQsoRec )
1259 {
1260 qso_rec->putField(ADIF_MODE, mode_info[the_mode].adif_name );
1261 }
1262
1263 /// This saves the new record to a file and must be run in the main thread.
QsoHelperSave(cQsoRec * qso_rec_ptr)1264 static void QsoHelperSave(cQsoRec * qso_rec_ptr)
1265 {
1266 qsodb.qsoNewRec (qso_rec_ptr);
1267 qsodb.isdirty(0);
1268 qsodb.isdirty(0);
1269
1270 loadBrowser(true);
1271
1272 /// It is mandatory to do this in the main thread. TODO: Crash suspected.
1273 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
1274
1275 /// Beware that this object is created in a thread and deleted in the main one.
1276 delete qso_rec_ptr ;
1277 }
1278
1279 /// This must be called from the main thread, according to writeLog().
~QsoHelper()1280 QsoHelper::~QsoHelper()
1281 {
1282 qso_rec->setDateTime(true);
1283 qso_rec->setDateTime(false);
1284 qso_rec->setFrequency( wf->rfcarrier() );
1285
1286 REQ( QsoHelperSave, qso_rec );
1287
1288 // It will be deleted in the main thread.
1289 qso_rec = NULL ;
1290 }
1291
1292 /// Adds one key-value pair to display in the ADIF file.
Push(ADIF_FIELD_POS pos,const std::string & value)1293 void QsoHelper::Push( ADIF_FIELD_POS pos, const std::string & value )
1294 {
1295 qso_rec->putField( pos, value.c_str() );
1296 }
1297
1298