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