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