1 // ----------------------------------------------------------------------------
2 // dxcc.cxx
3 //
4 // Copyright (C) 2008-2010
5 // Stelios Bounanos, M0GLD
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 <config.h>
24
25 #include <cstring>
26 #include <cctype>
27 #include <cstdlib>
28 #include <fstream>
29 #include <sstream>
30 #include <string>
31 #include <list>
32 #include <map>
33 #include <algorithm>
34
35 #include <FL/filename.H>
36 #include "fileselect.h"
37
38 #include "gettext.h"
39 #include "dxcc.h"
40 #include "debug.h"
41 #include "configuration.h"
42 #include "confdialog.h"
43 #include "main.h"
44
45 using namespace std;
46
47 #if HAVE_STD_HASH
48 # include <unordered_map>
49 using std::unordered_map;
50 #elif HAVE_STD_TR1_HASH
51 # include <tr1/unordered_map>
52 using tr1::unordered_map;
53 #else
54 # error "No std::hash or std::tr1::hash support"
55 #endif
56
dxcc(const char * cn,int cq,int itu,const char * ct,float lat,float lon,float tz)57 dxcc::dxcc(const char* cn, int cq, int itu, const char* ct, float lat, float lon, float tz)
58 : country(cn), cq_zone(cq), itu_zone(itu), latitude(lat), longitude(lon), gmt_offset(tz)
59 {
60 if (*ct) {
61 continent[0] = ct[0];
62 continent[1] = ct[1];
63 }
64 continent[2] = '\0';
65 }
66
67 typedef unordered_map<string, dxcc*> dxcc_map_t;
68 typedef vector<dxcc*> dxcc_list_t;
69 static dxcc_map_t* cmap = 0;
70 static dxcc_list_t* clist = 0;
71 static vector<string>* cnames = 0;
72
73 static list<string> lnames;
74 string cbolist;
75
76 static void add_prefix(string& prefix, dxcc* entry);
77
78 extern std::string s_ctydat;
79
80 // comparison, not case sensitive.
compare_nocase(const std::string & first,const std::string & second)81 static bool compare_nocase (const std::string& first, const std::string& second)
82 {
83 unsigned int i=0;
84 while ( (i<first.length()) && (i<second.length()) )
85 {
86 if (tolower(first[i]) < tolower(second[i])) return true;
87 else if (tolower(first[i]) > tolower(second[i])) return false;
88 ++i;
89 }
90 return ( first.length() < second.length() );
91 }
92
dxcc_internal_data()93 bool dxcc_internal_data()
94 {
95
96 std::string tempfname = HomeDir;
97 tempfname.append("/temp/ctydat.txt");
98 ofstream out(tempfname.c_str());
99 if (!out) {
100 LOG_INFO("Could not write temp file %s", tempfname.c_str());
101 return false;
102 }
103 out << s_ctydat;
104 out.close();
105
106 ifstream in(tempfname.c_str());
107 if (!in) {
108 LOG_INFO("Could not read temp file %s", tempfname.c_str());
109 return false;
110 }
111
112 LOG_INFO("Using internal cty.dat data");
113
114 cmap = new dxcc_map_t;
115 cnames = new vector<string>;
116
117 // this MUST be greater than the actual number of dcxx entities or
118 // the Windows gcc string library will move all of the strings and
119 // destroy the integrity of the cmap c_str() pointer to the country
120 // string in cnames
121
122 cnames->reserve(500); // approximate number of dxcc entities
123
124 clist = new dxcc_list_t;
125 clist->reserve(500);
126
127 dxcc* entry;
128 string record;
129
130 unsigned nrec = 0;
131 while (getline(in, record, ';')) {
132 istringstream is(record);
133 entry = new dxcc;
134 nrec++;
135
136 // read country name
137 cnames->resize(cnames->size() + 1);
138 clist->push_back(entry);
139 getline(is, cnames->back(), ':');
140 entry->country = cnames->back().c_str();
141
142 lnames.push_back(entry->country);
143
144 // cq zone
145 (is >> entry->cq_zone).ignore();
146 // itu zone
147 (is >> entry->itu_zone).ignore();
148 // continent
149 (is >> ws).get(entry->continent, 3).ignore();
150
151 // latitude
152 (is >> entry->latitude).ignore();
153 // longitude
154 (is >> entry->longitude).ignore();
155 // gmt offset
156 (is >> entry->gmt_offset).ignore(256, '\n');
157
158 // prefixes and exceptions
159 int c;
160 string prefix;
161 while ((c = is.peek()) == ' ' || c == '\n') {
162 is >> ws;
163
164 while (getline(is, prefix, ',')) {
165 add_prefix(prefix, entry);
166 if ((c = is.peek()) == '\n')
167 break;
168 }
169 }
170
171 if (cnames->back() == "United States") {
172 dxcc *usa_entry = new dxcc(
173 "USA",
174 entry->cq_zone,
175 entry->itu_zone,
176 entry->continent,
177 entry->latitude,
178 entry->longitude,
179 entry->gmt_offset );
180 nrec++;
181 cnames->resize(cnames->size() + 1);
182 clist->push_back(usa_entry);
183 lnames.push_back("USA");
184 }
185
186 in >> ws;
187
188 }
189 lnames.sort(compare_nocase);
190
191 cbolist.clear();
192 list<string>::iterator p = lnames.begin();
193 while (p != lnames.end()) {
194 cbolist.append(*p);
195 p++;
196 if (p != lnames.end()) cbolist.append("|");
197 }
198
199 stringstream info;
200 info << "\nLoaded " << cmap->size() << " prefixes for " << nrec
201 << " countries from internal cty.dat\n"
202 << "You should download the latest from http://www.country-files.com";
203 LOG_INFO("%s", info.str().c_str());
204
205 remove(tempfname.c_str());
206
207 return true;
208 }
209
dxcc_open(const char * filename)210 bool dxcc_open(const char* filename)
211 {
212 if (cmap) {
213 LOG_INFO("cty.dat already loaded");
214 return true;
215 }
216
217 ifstream in(filename);
218 if (!in) {
219 LOG_INFO("Could not read contest country file \"%s\"", filename);
220 return dxcc_internal_data();
221 }
222
223 cmap = new dxcc_map_t;
224 cnames = new vector<string>;
225
226 // this MUST be greater than the actual number of dcxx entities or
227 // the Windows gcc string library will move all of the strings and
228 // destroy the integrity of the cmap c_str() pointer to the country
229 // string in cnames
230
231 cnames->reserve(500); // approximate number of dxcc entities
232
233 clist = new dxcc_list_t;
234 clist->reserve(500);
235
236 dxcc* entry;
237 string record;
238
239 unsigned nrec = 0;
240 while (getline(in, record, ';')) {
241 istringstream is(record);
242 entry = new dxcc;
243 nrec++;
244
245 // read country name
246 cnames->resize(cnames->size() + 1);
247 clist->push_back(entry);
248 getline(is, cnames->back(), ':');
249 entry->country = cnames->back().c_str();
250
251 lnames.push_back(entry->country);
252
253 // cq zone
254 (is >> entry->cq_zone).ignore();
255 // itu zone
256 (is >> entry->itu_zone).ignore();
257 // continent
258 (is >> ws).get(entry->continent, 3).ignore();
259
260 // latitude
261 (is >> entry->latitude).ignore();
262 // longitude
263 (is >> entry->longitude).ignore();
264 // gmt offset
265 (is >> entry->gmt_offset).ignore(256, '\n');
266
267 // prefixes and exceptions
268 int c;
269 string prefix;
270 while ((c = is.peek()) == ' ' || c == '\r' || c == '\n') {
271 is >> ws;
272
273 while (getline(is, prefix, ',')) {
274 add_prefix(prefix, entry);
275 if ((c = is.peek()) == '\r' || c == '\n')
276 break;
277 }
278 }
279
280 if (cnames->back() == "United States") {
281 dxcc *usa_entry = new dxcc(
282 "USA",
283 entry->cq_zone,
284 entry->itu_zone,
285 entry->continent,
286 entry->latitude,
287 entry->longitude,
288 entry->gmt_offset );
289 nrec++;
290 cnames->resize(cnames->size() + 1);
291 clist->push_back(usa_entry);
292 lnames.push_back("USA");
293 }
294
295 in >> ws;
296
297 }
298 lnames.sort(compare_nocase);
299
300 cbolist.clear();
301 list<string>::iterator p = lnames.begin();
302 while (p != lnames.end()) {
303 cbolist.append(*p);
304 p++;
305 if (p != lnames.end()) cbolist.append("|");
306 }
307
308 stringstream info;
309 info << "Loaded " << cmap->size() << " prefixes for " << nrec << " countries";
310 LOG_VERBOSE("%s", info.str().c_str());
311
312 return true;
313 }
314
dxcc_is_open(void)315 bool dxcc_is_open(void)
316 {
317 return cmap;
318 }
319
dxcc_close(void)320 void dxcc_close(void)
321 {
322 if (!cmap)
323 return;
324 delete cnames;
325 cnames = 0;
326 map<dxcc*, bool> rm;
327 for (dxcc_map_t::iterator i = cmap->begin(); i != cmap->end(); ++i)
328 if (rm.insert(make_pair(i->second, true)).second)
329 delete i->second;
330 delete cmap;
331 cmap = 0;
332 delete clist;
333 clist = 0;
334 }
335
dxcc_entity_list(void)336 const vector<dxcc*>* dxcc_entity_list(void)
337 {
338 return clist;
339 }
340
dxcc_lookup(const char * callsign)341 const dxcc* dxcc_lookup(const char* callsign)
342 {
343 if (!cmap || !callsign || !*callsign)
344 return NULL;
345
346 string sstr;
347 sstr.resize(strlen(callsign) + 1);
348 transform(callsign, callsign + sstr.length() - 1, sstr.begin() + 1, static_cast<int (*)(int)>(toupper));
349
350 // first look for a full callsign (prefixed with '=')
351 sstr[0] = '=';
352 dxcc_map_t::const_iterator entry = cmap->find(sstr);
353 if (entry != cmap->end()) {
354 entry->second->print();
355 return entry->second;
356 }
357 // erase the '=' and do a longest prefix search
358 sstr.erase(0, 1);
359 size_t len = sstr.length();
360 // accomodate special case for KG4... calls
361 // all two letter suffix KG4 calls are Guantanamo
362 // all others are US non Guantanamo
363 if (sstr.find("KG4") != string::npos) {
364 if (len == 4 || len == 6) {
365 sstr = "K";
366 len = 1;
367 }
368 }
369 do {
370 sstr.resize(len--);
371 if ((entry = cmap->find(sstr)) != cmap->end()) {
372 entry->second->print();
373 return entry->second;
374 }
375 } while (len);
376
377 return NULL;
378 }
379
add_prefix(string & prefix,dxcc * entry)380 static void add_prefix(string& prefix, dxcc* entry)
381 {
382 string::size_type i = prefix.find_first_of("([<{");
383 if (likely(i == string::npos)) {
384 (*cmap)[prefix] = entry;
385 return;
386 }
387
388 string::size_type j = i, first = i;
389 do {
390 entry = new struct dxcc(*entry);
391 switch (prefix[i++]) { // increment i past opening bracket
392 case '(':
393 if ((j = prefix.find(')', i)) == string::npos) {
394 delete entry;
395 return;
396 }
397 prefix[j] = '\0';
398 entry->cq_zone = atoi(prefix.data() + i);
399 break;
400 case '[':
401 if ((j = prefix.find(']', i)) == string::npos) {
402 delete entry;
403 return;
404 }
405 prefix[j] = '\0';
406 entry->itu_zone = atoi(prefix.data() + i);
407 break;
408 case '<':
409 if ((j = prefix.find('/', i)) == string::npos) {
410 delete entry;
411 return;
412 }
413 prefix[j] = '\0';
414 entry->latitude = atof(prefix.data() + i);
415 if ((j = prefix.find('>', j)) == string::npos) {
416 delete entry;
417 return;
418 }
419 prefix[j] = '\0';
420 entry->longitude = atof(prefix.data() + i);
421 break;
422 case '{':
423 if ((j = prefix.find('}', i)) == string::npos) {
424 delete entry;
425 return;
426 }
427 memcpy(entry->continent, prefix.data() + i, 2);
428 break;
429 }
430 } while ((i = prefix.find_first_of("([<{", j)) != string::npos);
431
432 prefix.erase(first);
433 (*cmap)[prefix] = entry;
434 }
435
436 typedef unordered_map<string, unsigned char> qsl_map_t;
437 static qsl_map_t* qsl_calls;
438 static unsigned char qsl_open_;
439 const char* qsl_names[] = { "LoTW", "eQSL" };
440
qsl_open(const char * filename,qsl_t qsl_type)441 bool qsl_open(const char* filename, qsl_t qsl_type)
442 {
443 ifstream in(filename);
444 if (!in)
445 return false;
446 if (!qsl_calls)
447 qsl_calls = new qsl_map_t;
448
449 size_t n = qsl_calls->size();
450 string::size_type p;
451 string s;
452 s.reserve(32);
453 while (getline(in, s)) {
454 if ((p = s.rfind('\r')) != string::npos)
455 s.erase(p);
456 (*qsl_calls)[s] |= (1 << qsl_type);
457 }
458
459 stringstream info;
460 info << "Added " << qsl_calls->size() - n
461 << " " << qsl_names[qsl_type]
462 << " callsigns from \"" << filename << "\"";
463 LOG_VERBOSE("%s", info.str().c_str());
464
465 qsl_open_ |= (1 << qsl_type);
466 return true;
467 }
468
qsl_is_open(void)469 unsigned char qsl_is_open(void)
470 {
471 return qsl_open_;
472 }
473
qsl_close(void)474 void qsl_close(void)
475 {
476 delete qsl_calls;
477 qsl_calls = 0;
478 qsl_open_ = 0;
479 }
480
qsl_lookup(const char * callsign)481 unsigned char qsl_lookup(const char* callsign)
482 {
483 if (qsl_calls == 0)
484 return 0;
485
486 string str;
487 str.resize(strlen(callsign));
488 transform(callsign, callsign + str.length(), str.begin(), static_cast<int (*)(int)>(toupper));
489
490 qsl_map_t::const_iterator i = qsl_calls->find(str);
491 return i == qsl_calls->end() ? 0 : i->second;
492 }
493
reload_cty_dat()494 void reload_cty_dat()
495 {
496 dxcc_close();
497 dxcc_open(string(progdefaults.cty_dat_pathname).append("cty.dat").c_str());
498 qsl_close();
499 qsl_open(string(progdefaults.cty_dat_pathname).append("lotw1.txt").c_str(), QSL_LOTW);
500 if (!qsl_open(string(progdefaults.cty_dat_pathname).append("eqsl.txt").c_str(), QSL_EQSL))
501 qsl_open(string(progdefaults.cty_dat_pathname).append("AGMemberList.txt").c_str(), QSL_EQSL);
502 }
503
default_cty_dat_pathname()504 void default_cty_dat_pathname()
505 {
506 progdefaults.cty_dat_pathname = HomeDir;
507 txt_cty_dat_pathname->value(progdefaults.cty_dat_pathname.c_str());
508 }
509
select_cty_dat_pathname()510 void select_cty_dat_pathname()
511 {
512 string deffilename = progdefaults.cty_dat_pathname;
513 const char *p = FSEL::select(_("Locate cty.dat folder"), _("cty.dat\t*"), deffilename.c_str());
514 if (p) {
515 string nupath = p;
516 size_t ptr;
517 //crappy win32 again
518 ptr = nupath.find("\\");
519 while (ptr != string::npos) {
520 nupath[ptr] = '/';
521 ptr = nupath.find("\\");
522 }
523 size_t endslash = nupath.rfind("/");
524 if ((endslash != string::npos) && (endslash != (nupath.length()-1)))
525 nupath.erase(endslash + 1);
526 progdefaults.cty_dat_pathname = nupath;
527 progdefaults.changed = true;
528 txt_cty_dat_pathname->value(nupath.c_str());
529 }
530 }
531
532