1 /*
2 
3    xlog - GTK+ logging program for amateur radio operators
4    Copyright (C) 2012 - 2021 Andy Stewart <kb1oiq@arrl.net>
5    Copyright (C) 2001 - 2010 Joop Stakenborg <pg4i@amsat.org>
6 
7    This file is part of xlog.
8 
9    Xlog 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    Xlog 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 xlog.  If not, see <http://www.gnu.org/licenses/>.
21 
22 */
23 
24 /*
25  * cabrillo3.c - scanner for Cabrillo format - 3.0
26  * Reports to Andy Stewart KB1OIQ
27  * Specification:  https://wwrof.org/cabrillo/
28  */
29 
30 #ifndef _XOPEN_SOURCE
31 #define _XOPEN_SOURCE
32 #endif
33 #include <stdlib.h>
34 #include <string.h>
35 #include <locale.h>
36 #include <ctype.h>
37 #include <glib/gstdio.h>
38 #include <time.h>
39 #include <glib.h>
40 #include "logfile.h"
41 #include "../cfg.h"
42 #include "../utils.h"
43 #include "../main.h"
44 #include "../xlog_enum.h"
45 
46 #ifndef HAVE_STRPTIME
47 #include "strptime.h"
48 #define strptime(s,f,t) mystrptime(s,f,t)
49 #endif
50 
51 extern preferencestype preferences;
52 extern programstatetype programstate;
53 
54 /*
55  * fields to be stored in the cabrillo file
56  */
57 static const gint cabrillo_fields[] =
58 	{ BAND, MODE, DATE, GMT, RST, CALL, MYRST, REMARKS };
59 
60 static const gint cabrillo3_widths[] = { 5, 2, 10, 4, 10, 13, 10 };
61 static const gint cabrillo3_ss_widths[] = { 5, 2, 10, 4, 13, 10, 13 };
62 static const gint cabrillo3_na_widths[] = { 5, 2, 10, 4, 20, 10, 21 };
63 static const gint cabrillo3_iota_widths[] = { 5, 2, 10, 4, 15, 13, 15 };
64 static const gint cabrillo3_field_nr = 7;
65 
66 static gint cabrillo3_open (LOGDB *);
67 static void cabrillo3_close (LOGDB *);
68 static gint cabrillo3_create (LOGDB *);
69 static gint cabrillo3_qso_append (LOGDB *, const qso_t *);
70 static gint cabrillo3_qso_foreach (LOGDB *, gint (*fn) (LOGDB *, qso_t *, gpointer arg), gpointer arg);
71 
72 const struct log_ops cabrillo3_ops = {
73 	.open = cabrillo3_open,
74 	.close = cabrillo3_close,
75 	.create = cabrillo3_create,
76 	.qso_append = cabrillo3_qso_append,
77 	.qso_foreach = cabrillo3_qso_foreach,
78 	.type = TYPE_CABRILLO3,
79 	.name = "Cabrillo3",
80 	.extension = ".cbr",
81 };
82 
83 /*
84  * open for read
85  */
86 gint
cabrillo3_open(LOGDB * handle)87 cabrillo3_open (LOGDB * handle)
88 {
89 	FILE *fp;
90 	static const gint xlog_fields [] = {DATE, GMT, CALL, BAND, MODE, RST, MYRST, AWARDS, REMARKS};
91 
92     // printf("DEBUG: cabrillo3.c:  cabrillo3_open (): got here #1\n");
93 
94 	fp = g_fopen (handle->path, "r");
95 	if (!fp)
96 		return -1;
97 	handle->priv = (gpointer) fp;
98 
99  	/* set columns to be used in xlog */
100 	handle->column_nr = 8;
101 	memcpy (handle->column_fields, xlog_fields, sizeof (xlog_fields));
102 	/* TODO: set and use handle->column_widths */
103 
104 	return 0;
105 }
106 
107 /*
108  * open for write
109  */
110 gint
cabrillo3_create(LOGDB * handle)111 cabrillo3_create (LOGDB * handle)
112 {
113 	FILE *fp;
114 
115     // printf("DEBUG: cabrillo3.c:  cabrillo3_create (): got here #1\n");
116 
117 	fp = g_fopen (handle->path, "w");
118 	if (!fp)
119 		return -1;
120 	handle->priv = (gpointer) fp;
121 
122 	/* write header */
123 	fprintf (fp, "START-OF-LOG: 3.0\n"
124 		"CREATED-BY: " PACKAGE " Version " VERSION "\n"
125 		"CONTEST: \n"
126 		"LOCATION: \n"
127 		"GRID-LOCATOR: \n"
128 		"CALLSIGN: \n"
129 		"CATEGORY-ASSISTED: \n"
130 		"CATEGORY-BAND: \n"
131 		"CATEGORY-MODE: \n"
132 		"CATEGORY-OPERATOR: \n"
133 		"CATEGORY-POWER: \n"
134 		"CATEGORY-STATION: \n"
135 		"CATEGORY-TIME: \n"
136 		"CATEGORY-TRANSMITTER: \n"
137 		"CATEGORY-OVERLAY: \n"
138 		"CERTIFICATE: \n"
139 		"CLAIMED-SCORE: \n"
140 		"CLUB: \n"
141 		"NAME: \n"
142 		"ADDRESS: \n"
143 		"ADDRESS-CITY: \n"
144 		"ADDRESS-STATE-PROVINCE: \n"
145 		"ADDRESS-POSTALCODE: \n"
146 		"ADDRESS-COUNTRY: \n"
147 		"EMAIL: \n"
148 		"OPERATORS: \n"
149 		"OFFTIME: \n"
150 		"SOAPBOX: \n"
151 		"SOAPBOX: \n");
152 	return 0;
153 }
154 
155 void
cabrillo3_close(LOGDB * handle)156 cabrillo3_close (LOGDB * handle)
157 {
158 	FILE *fp = (FILE *) handle->priv;
159 
160     // printf("DEBUG: cabrillo3.c:  cabrillo3_close (): got here #1\n");
161 
162 	/* will fail silently if file was open read-only */
163 	fprintf (fp, "END-OF-LOG:\n");
164 
165 	fclose (fp);
166 }
167 
168 /*
169  * append a qso. NOTE: preferences.callsign contains the operator's call.
170  */
171 gint
cabrillo3_qso_append(LOGDB * handle,const qso_t * q)172 cabrillo3_qso_append (LOGDB * handle, const qso_t * q)
173 {
174 	FILE *fp = (FILE *) handle->priv;
175 	gchar rst[16], exch[16] = "", my_rst[16], my_exch[16] = "", date[16];
176 	const gchar *mode;
177 	gint rst_len;
178 	gchar *q_mode = q[MODE] ? q[MODE] : "SSB";
179 	gchar *freq;
180 	gchar *res = NULL;
181 	struct tm tm_cab;
182 
183 	/*
184 	 * there's no exchange fields in xlog. However, the exchange information
185 	 * may be piggybacked by the rst field. eg. "599ON".
186 	 */
187 
188 	if (!strcmp (q_mode, "SSB") ||
189 	    !strcmp (q_mode, "USB") ||
190 		!strcmp (q_mode, "LSB") ||
191 		!strcmp (q_mode, "FM")  ||
192 		!strcmp (q_mode, "AM"))
193 		rst_len = 2;
194 	else
195 		rst_len = 3;
196 
197     /* If the QSO frame has TX/RX RST hidden, just put in 59(9) */
198 
199     if (q[RST] == NULL) {
200 	  strncpy(rst, "599", rst_len);
201 	  rst[rst_len] = '\0';
202 	}  else {
203 	  strncpy (rst, q[RST], rst_len);
204 	  rst[rst_len] = '\0';
205       if (strlen (q[RST]) > rst_len)
206         strcpy (exch, q[RST] + rst_len);
207 	}
208 
209     if (q[MYRST] == NULL) {
210 	  strncpy(my_rst, "599", rst_len);
211 	  my_rst[rst_len] = '\0';
212 	}  else {
213 	  strncpy (my_rst, q[RST], rst_len);
214 	  my_rst[rst_len] = '\0';
215       if (strlen (q[MYRST]) > rst_len)
216 	    strcpy (my_exch, q[MYRST] + rst_len);
217 	}
218 
219 	gint bandenum = freq2enum (q[BAND]);
220 
221 	if (preferences.saveascabrillo == 1) {
222 	  freq = band_enum2cabrillochar (bandenum); /* Save just the band */
223 	} else {
224 	  /* Save the freq in khz for some bands */
225 	  /* punt for the other bands */
226 	  if ((bandenum >= BAND_160) && (bandenum <= BAND_10)) {
227 	    freq = freq2khz (q[BAND]);
228 	  } else {
229 	    freq = band_enum2cabrillochar (bandenum);
230 	  }
231 	}
232 
233 	/* convert "dd <month> yyyy" (month in any language) to dd-mm-yyyy */
234 	setlocale (LC_TIME, "");
235 	res = strptime (q[DATE], "%d %b %Y", &tm_cab);
236 	setlocale (LC_TIME, "C");
237 	if (res != NULL)
238 	  {
239 	    setlocale (LC_TIME, "");
240 	    strftime (date, sizeof(date), "%Y-%m-%d", &tm_cab);
241 	    setlocale (LC_TIME, "C");
242 	  }
243 	else
244 	  {
245 	    strcpy (date, q[DATE]);
246 	  }
247 
248 	/* translate mode, valid: PH, RY, FM, CW, DG */
249 	if (!strcmp (q_mode, "USB") ||
250 	    !strcmp (q_mode, "LSB") ||
251 		!strcmp (q_mode, "SSB") ||
252 		!strcmp (q_mode, "AM"))
253 		{
254 			mode = "PH";
255 		}
256 	else if (!strcmp (q_mode, "RTTY"))
257 		{
258 			mode = "RY";
259 		}
260 	else if (!strcmp (q_mode, "FM") || !strcmp (q_mode, "CW"))
261 		{
262 			mode = q_mode;
263 		}
264 
265 	// The PSK* modes are not present in the "official"(?) Cabrillo specification
266 	// http://www.kkn.net/~trey/cabrillo/qso-template.html
267 	// These translations are used in European contests (and maybe others???)
268 	// Added at the request of Alex (EW1LN) who showed descriptions of these
269 	// modes from 3 European contest sites.
270 
271 	// AndyS: Should all digital modes be represented as DG?
272     // How does that dovetail with this use of unsupported modes used
273     // in Europe?
274 
275     // Leave these 3 modes alone....all others are DG.
276 
277 	else if (!strcmp (q_mode, "PSK63"))
278 		{
279 			mode = "PM";
280 		}
281 	else if (!strcmp (q_mode, "PSK31"))
282 		{
283 			mode = "PS";
284 		}
285 	else if (!strcmp (q_mode, "PSK125"))
286 		{
287 			mode = "PO";
288 		}
289 	else
290 		mode = "DG";
291 
292 	fprintf (fp, "QSO: %5s %-3s%-11s%-5s%-14s%-4s%-7s%-14s%-4s%-7s\n",
293 		freq, mode, date, q[GMT],
294 		preferences.callsign, rst, exch, q[CALL], my_rst, my_exch);
295 
296 	g_free (freq);
297 	return 0;
298 }
299 
300 #define MAXROWLEN 120
301 
302 enum cbr_contest_type
303 {
304 	CBR_OTHER,
305 	CBR_SWEEPSTAKES,
306 	CBR_NA,
307 	CBR_IOTA
308 };
309 
310 gint
cabrillo3_qso_foreach(LOGDB * handle,gint (* fn)(LOGDB *,qso_t *,gpointer arg),gpointer arg)311 cabrillo3_qso_foreach (LOGDB * handle,
312 	gint (*fn) (LOGDB *, qso_t *, gpointer arg), gpointer arg)
313 {
314 	FILE *fp = (FILE *) handle->priv;
315 	gint i, ret;
316 	qso_t q[QSO_FIELDS];
317 	gchar *field, *end, buffer[MAXROWLEN], buf[20], *res = NULL;
318 	enum cbr_contest_type contest_type = CBR_OTHER;
319 	const gint *widths = cabrillo3_widths;
320 	gint sent_call_len = 14;
321 	struct tm tm_cab;
322 	gboolean phone;
323 	gint awards_was = 0;
324 
325     // printf("DEBUG: cabrillo3.c:  cabrillo3_qso_foreach (): got here #1\n");
326 
327 	while (!feof (fp))
328 	{
329 		if (!fgets (buffer, MAXROWLEN - 1, fp))
330 			break;
331 
332 		phone = FALSE;
333 			/* valid record starts with "QSO: " */
334 		if (strncmp (buffer, "QSO: ", 5))
335 		{
336 
337 			/* okay, this is not a QSO record. look at the Contest field
338 			* to deduce the log format
339 			*/
340 			if (!strncmp (buffer, "CONTEST: ", 9))
341 			{
342 				if (!strncmp (buffer + 9, "NA", 2))
343 				{
344 					contest_type = CBR_NA;
345 					widths = cabrillo3_na_widths;
346 					sent_call_len = 11;
347 					awards_was = 1;
348 				}
349 				else if (!strncmp (buffer + 9, "ARRL-SS", 7))
350 				{
351 					contest_type = CBR_SWEEPSTAKES;
352 					widths = cabrillo3_ss_widths;
353 					sent_call_len = 11;
354 					awards_was = 1;
355 				}
356 				else if (!strncmp (buffer + 9, "ARRL", 4))
357 				{
358 					awards_was = 1;
359 				}
360 				else if (!strncmp (buffer + 9, "RSGB-IOTA", 9))
361 				{
362 					contest_type = CBR_IOTA;
363 					widths = cabrillo3_iota_widths;
364 					sent_call_len = 14;
365 				}
366 			}
367 			else if (!strncmp (buffer, "END-OF-LOG:", 11))
368 			{
369 				break;
370 			}
371 			continue;
372 		}
373 
374 		memset (q, 0, sizeof (q));
375 		field = buffer + 5;
376 
377 		for (i = 0; i < cabrillo3_field_nr; i++)
378 		{
379 			/* hop the fifth field, this is the operator call sign */
380 			if (i == 4)
381 				field += sent_call_len;
382 
383 			end = field + widths[i];
384 			*end = '\0';
385 
386 			switch (i)
387 			{
388 			case 0:
389 				if (!strchr(field, 'G') && (
390 					!strncmp(field, " 1", 2) ||
391 					!strncmp(field, " 3", 2) ||
392 					!strncmp(field, " 7", 2) ||
393 					(!strncmp(field, "14", 2) && field[2]!='4') ||
394 					!strncmp(field, "21", 2) ||
395 					!strncmp(field, "28", 2)))
396 				{
397 					gint khz = atoi(field);
398 					if (khz%1000 == 0)
399 						q[cabrillo_fields[i]] =
400 							g_strdup_printf ("%d", khz/1000);
401 					else
402 						q[cabrillo_fields[i]] =
403 							g_strdup_printf ("%d.%03d", khz/1000, khz%1000);
404 				}
405 				else
406 				{
407 					q[cabrillo_fields[i]] = g_strdup (g_strstrip (field));
408 				}
409 				break;
410 
411 			case 1:
412 				/* check the different mode strings and store mode */
413 				if (!strcmp (field, "PH"))
414 				{
415 					phone = TRUE;
416 					strcpy (buf, "SSB");
417 				}
418 				else if (!strcmp (field, "RY"))
419 					strcpy (buf, "RTTY");
420 				else
421 					strcpy (buf, field);
422 				q[cabrillo_fields[i]] = g_strdup (buf);
423 				break;
424 
425 			case 2:
426 				/* reformat date 2007-01-23 -> 23 Jan 2007 */
427 				res = strptime (field, "%Y-%m-%d", &tm_cab);
428 				if (res != NULL)
429 				{
430 					setlocale (LC_TIME, "");
431 					strftime (buf, 20, "%d %b %Y", &tm_cab);
432 					setlocale (LC_TIME, "C");
433 				}
434 				else
435 					strcpy (buf, field);
436 				q[cabrillo_fields[i]] = g_strdup (buf);
437 				break;
438 
439 			case 4:
440 			case 6:
441 				/* cut IOTA ref */
442 				if (contest_type == CBR_IOTA)
443 					field[8] = '\0';
444 
445 				/* TODO: add prepending zero's if serial */
446 				if (phone)
447 				{
448 					strcpy (buf, field + 2);
449 					g_strstrip (buf);
450 					field[2] = '\0';
451 				}
452 				else
453 				{
454 					strcpy (buf, field + 3);
455 					g_strstrip (buf);
456 					field[3] = '\0';
457 				}
458 
459 				q[cabrillo_fields[i]] =
460 					g_strdup_printf ("%s%s", field, buf);
461 
462 				if (i == 6 && contest_type == CBR_IOTA &&
463 						strcmp(field+9, "------"))
464 				{
465 					field[9+6] = '\0';
466 					g_strstrip (field+9);
467 					q[AWARDS] = g_strdup_printf ("IOTA-%s", field+9);
468 				}
469 				else if (i == 6 && awards_was && isupper(field[4]) &&
470 						strcmp(field+4, "DX"))
471 				{
472 					g_strstrip (field+4);
473 					q[AWARDS] = g_strdup_printf ("WAS-%s", field+4);
474 				}
475 				/* TODO: WAZ for CONTEST:CQ-WW* ? */
476 
477 				/* add the remark field if present */
478 				if ((i == 6) && (strlen(programstate.importremark) > 0))
479 				{
480 					q[cabrillo_fields[i + 1]] =
481 						g_strdup (programstate.importremark);
482 				}
483 				break;
484 
485 			default:
486 				q[cabrillo_fields[i]] = g_strdup (g_strstrip (field));
487 			}
488 
489 			field = end + 1;
490 		}
491 
492 		ret = (*fn) (handle, q, arg);
493 		if (ret) return ret;
494 	}
495 	return 0;
496 }
497