1 /*
2
3 xlog - GTK+ logging program for amateur radio operators
4 Copyright (C) 2001 - 2008 Joop Stakenborg <pg4i@amsat.org>
5
6 This file is part of xlog.
7
8 Xlog is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Xlog is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with xlog. If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 /*
24 * log.c - assorted utilities for maintaining the logs
25 */
26
27 #include <gtk/gtk.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <glib/gstdio.h>
34
35 #ifndef G_OS_WIN32
36 #include <sys/wait.h>
37 #endif
38
39 #include "callbacks_mainwindow_list.h"
40 #include "gui_utils.h"
41 #include "utils.h"
42 #include "cfg.h"
43 #include "log.h"
44 #include "support.h"
45 #include "main.h"
46 #include "dxcc.h"
47
48 extern GtkWidget *mainnotebook;
49 extern programstatetype programstate;
50 extern preferencestype preferences;
51 extern gchar **qso;
52
53 /* backup a log */
54 void
backuplog(gchar * filename,gchar * backupfilename)55 backuplog (gchar * filename, gchar * backupfilename)
56 {
57 gint ch;
58 gchar *msg;
59 FILE *in, *out;
60
61 in = g_fopen (filename, "r");
62 if (in)
63 {
64 out = g_fopen (backupfilename, "w");
65 if (out)
66 {
67 while ((ch = getc (in)) != EOF)
68 putc (ch, out);
69 fclose (out);
70 }
71 else
72 {
73 msg = g_strdup_printf (_("Backup to %s failed: %s"),
74 backupfilename, g_strerror (errno));
75 g_warning ("%s", msg);
76 g_free (msg);
77 }
78 fclose (in);
79 }
80 }
81
82 /* extract name of the log from filename, returned string should be free'd */
83 gchar *
logname(gchar * filename)84 logname (gchar * filename)
85 {
86 gchar *logname, *basen, **split;
87
88 basen = g_path_get_basename (filename);
89 split = g_strsplit (basen, ".", -1);
90 logname = g_strdup (split[0]);
91 g_free (basen);
92 g_strfreev (split);
93 return (logname);
94 }
95
96 /*
97 * Prepend qsos to the log, we convert from locale to UTF-8 here. If for some
98 * reason locale conversion fails we set a flag and replace the character
99 * with a dot. If that fails, the field is emptied. The flag is used to display
100 * a warning dialog. Only date, name, QTH, freefield1, freefield2 and the
101 * remarks field are checked for locales.
102 */
103 gint
fillin_list(LOGDB * handle,qso_t q[],gpointer arg)104 fillin_list (LOGDB * handle, qso_t q[], gpointer arg)
105 {
106 GtkTreeIter iter;
107 GtkListStore *model;
108 logtype *logw = (logtype *) arg;
109 gchar *date = NULL, *name = NULL, *qth = NULL, *u1 = NULL,
110 *u2 = NULL, *remarks = NULL;
111 GError *error;
112 gint i;
113 GtkTreePath *path;
114
115 programstate.qsos++;
116 logw->qsos++;
117 q[NR] = g_strdup_printf ("%d", logw->qsos);
118
119 model = GTK_LIST_STORE
120 (gtk_tree_view_get_model (GTK_TREE_VIEW (logw->treeview)));
121 gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
122
123 if (!q[DATE])
124 date = g_strdup ("");
125 else if (!g_utf8_validate (q[DATE], -1, NULL ))
126 {
127 error = NULL;
128 date = g_locale_to_utf8 (q[DATE], -1, NULL, NULL, &error);
129 if (!date)
130 {
131 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[DATE], error->message);
132 g_error_free (error);
133 programstate.utf8error = TRUE;
134 date = g_convert_with_fallback(q[DATE], strlen(q[DATE]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
135 if (!date) date = g_strdup ("");
136 }
137 }
138 else date = g_strdup (q[DATE]);
139
140 if (!q[GMT])
141 q[GMT] = g_strdup ("");
142 if (!q[GMTEND])
143 q[GMTEND] = g_strdup ("");
144 if (!q[CALL])
145 q[CALL] = g_strdup ("");
146 if (!q[BAND])
147 q[BAND] = g_strdup ("");
148 if (!q[MODE])
149 q[MODE] = g_strdup ("");
150 if (!q[RST])
151 q[RST] = g_strdup ("");
152 if (!q[MYRST])
153 q[MYRST] = g_strdup ("");
154 if (!q[AWARDS])
155 q[AWARDS] = g_strdup ("");
156 if (!q[QSLOUT])
157 q[QSLOUT] = g_strdup ("");
158 if (!q[QSLIN])
159 q[QSLIN] = g_strdup ("");
160 if (!q[POWER])
161 q[POWER] = g_strdup ("");
162
163 if (!q[NAME])
164 name = g_strdup ("");
165 else if (!g_utf8_validate (q[NAME], -1, NULL ))
166 {
167 error = NULL;
168 name = g_locale_to_utf8 (q[NAME], -1, NULL, NULL, &error);
169 if (!name)
170 {
171 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[NAME], error->message);
172 g_error_free (error);
173 programstate.utf8error = TRUE;
174 name = g_convert_with_fallback(q[NAME], strlen(q[NAME]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
175 if (!name) name = g_strdup ("");
176 }
177 }
178 else name = g_strdup (q[NAME]);
179
180 if (!q[QTH])
181 qth = g_strdup ("");
182 else if (!g_utf8_validate (q[QTH], -1, NULL ))
183 {
184 error = NULL;
185 qth = g_locale_to_utf8 (q[QTH], -1, NULL, NULL, &error);
186 if (!qth)
187 {
188 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[QTH], error->message);
189 g_error_free (error);
190 programstate.utf8error = TRUE;
191 qth = g_convert_with_fallback(q[QTH], strlen(q[QTH]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
192 if (!qth) qth = g_strdup ("");
193 }
194 }
195 else qth = g_strdup (q[QTH]);
196
197 if (!q[LOCATOR])
198 q[LOCATOR] = g_strdup ("");
199
200 if (!q[U1])
201 u1 = g_strdup ("");
202 else if (!g_utf8_validate (q[U1], -1, NULL ))
203 {
204 error = NULL;
205 u1 = g_locale_to_utf8 (q[U1], -1, NULL, NULL, &error);
206 if (!u1)
207 {
208 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[U1], error->message);
209 g_error_free (error);
210 programstate.utf8error = TRUE;
211 u1 = g_convert_with_fallback(q[U1], strlen(q[U1]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
212 if (!u1) u1 = g_strdup ("");
213 }
214 }
215 else u1 = g_strdup (q[U1]);
216
217 if (!q[U2])
218 u2 = g_strdup ("");
219 else if (!g_utf8_validate (q[U2], -1, NULL ))
220 {
221 error = NULL;
222 u2 = g_locale_to_utf8 (q[U2], -1, NULL, NULL, &error);
223 if (!u2)
224 {
225 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[U2], error->message);
226 g_error_free (error);
227 programstate.utf8error = TRUE;
228 u2 = g_convert_with_fallback(q[U2], strlen(q[U2]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
229 if (!u2) u2 = g_strdup ("");
230 }
231 }
232 else u2 = g_strdup (q[U2]);
233
234 if (!q[REMARKS])
235 remarks = g_strdup ("");
236 else if (!g_utf8_validate (q[REMARKS], -1, NULL ))
237 {
238 error = NULL;
239 remarks = g_locale_to_utf8 (q[REMARKS], -1, NULL, NULL, &error);
240 if (!remarks)
241 {
242 g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[REMARKS], error->message);
243 g_error_free (error);
244 programstate.utf8error = TRUE;
245 remarks = g_convert_with_fallback(q[REMARKS], strlen(q[REMARKS]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
246 if (!remarks) remarks = g_strdup ("");
247 }
248 }
249 else remarks = g_strdup (q[REMARKS]);
250
251 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
252 NR, q[NR], DATE, date, GMT, q[GMT], GMTEND, q[GMTEND], CALL, q[CALL],
253 BAND, q[BAND], MODE, q[MODE], RST, q[RST], MYRST, q[MYRST], AWARDS, q[AWARDS],
254 QSLOUT, q[QSLOUT], QSLIN, q[QSLIN], POWER, q[POWER], NAME, name, QTH, qth,
255 LOCATOR, q[LOCATOR], U1, u1, U2, u2, REMARKS, remarks, -1);
256 if (!(logw->qsos % 1000))
257 {
258 while (gtk_events_pending ())
259 gtk_main_iteration ();
260
261 path = gtk_tree_path_new_from_string ("0");
262 gtk_tree_view_scroll_to_cell
263 (GTK_TREE_VIEW (logw->treeview), path, NULL, TRUE, 0.5, 0.0);
264 gtk_tree_path_free (path);
265
266 update_statusbar (_("Reading..."));
267 }
268
269 for (i = 0; i < QSO_FIELDS; i++)
270 g_free (q[i]);
271
272 if (date) g_free (date);
273 if (remarks) g_free (remarks);
274 if (name) g_free (name);
275 if (qth) g_free (qth);
276 if (u1) g_free (u1);
277 if (u2) g_free (u2);
278 return 0;
279 }
280
281 /* create a new struct for a log */
282 static logtype *
new_logwindow(void)283 new_logwindow (void)
284 {
285 logtype *newlog;
286 gint i;
287
288 newlog = g_new0 (struct logtype, 1);
289 newlog->scrolledwindow = NULL;
290 newlog->treeview = NULL;
291 newlog->label = NULL;
292 newlog->logname = NULL;
293 newlog->filename = NULL;
294 newlog->logchanged = FALSE;
295 newlog->readonly = FALSE;
296 newlog->qsos = 0;
297 newlog->columns = 0;
298 for (i = 0; i < QSO_FIELDS; i++)
299 newlog->logfields[i] = 0;
300 return (newlog);
301 }
302
303
304 /* open a log and return a struct */
305 logtype *
openlogwindow(LOGDB * lp,gchar * name,gint page)306 openlogwindow (LOGDB * lp, gchar * name, gint page)
307 {
308 logtype *logwindow;
309 gint i, j;
310 gchar *logn, *labelname;
311 GtkCellRenderer *renderer, *brenderer;
312 GtkTreeViewColumn *column;
313 GObject *selection;
314 GtkListStore *model;
315
316 logwindow = new_logwindow ();
317 logwindow->scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
318 gtk_widget_show (logwindow->scrolledwindow);
319 gtk_container_add (GTK_CONTAINER (mainnotebook), logwindow->scrolledwindow);
320 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(logwindow->scrolledwindow),
321 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
322
323 model = gtk_list_store_new (QSO_FIELDS,
324 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
325 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
326 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
327 G_TYPE_STRING);
328 logwindow->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
329 gtk_tree_view_set_search_column (GTK_TREE_VIEW (logwindow->treeview), -1);
330 g_object_unref (G_OBJECT (model));
331
332 /* add callback for selecting a row */
333 selection = G_OBJECT (gtk_tree_view_get_selection(GTK_TREE_VIEW (logwindow->treeview)));
334 gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection), GTK_SELECTION_SINGLE);
335 g_signal_connect (selection, "changed", G_CALLBACK (on_log_select_row), NULL);
336
337 logwindow->columns = lp->column_nr;
338 /* save the active columns */
339 for (j = 0; j < logwindow->columns; j++)
340 logwindow->logfields[j] = lp->column_fields[j];
341
342 /* NR column is the first one */
343 renderer = gtk_cell_renderer_text_new ();
344 brenderer = gtk_cell_renderer_text_new ();
345 // AndyS: comment out this line to fix GLib-GObject-WARNING message when xlog starts
346 // g_object_set (G_OBJECT (brenderer), "weight", "bold", NULL);
347 column = gtk_tree_view_column_new_with_attributes ("NR", renderer, "text", NR, NULL);
348 gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_FIXED);
349 gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE);
350 gtk_tree_view_append_column (GTK_TREE_VIEW (logwindow->treeview), column);
351 if (preferences.logcwidths2[NR] == 0)
352 gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(column), FALSE);
353 else
354 gtk_tree_view_column_set_fixed_width (GTK_TREE_VIEW_COLUMN(column), preferences.logcwidths2[NR]);
355
356 /* see which fields are in the log and add a column or hide it */
357 for (j = 1; j < QSO_FIELDS; j++)
358 {
359 for (i = 0; i < lp->column_nr; i++)
360 {
361 if (j == lp->column_fields[i])
362 break;
363 }
364 renderer = gtk_cell_renderer_text_new ();
365 if (j == U1)
366 column = gtk_tree_view_column_new_with_attributes
367 (preferences.freefield1, renderer, "text", j, NULL);
368 else if (j == U2)
369 column = gtk_tree_view_column_new_with_attributes
370 (preferences.freefield2, renderer, "text", j, NULL);
371 else if (j == CALL)
372 column = gtk_tree_view_column_new_with_attributes
373 (strcolumn (j), brenderer, "text", j, NULL);
374 else
375 column = gtk_tree_view_column_new_with_attributes
376 (strcolumn (j), renderer, "text", j, NULL);
377
378 gtk_tree_view_column_set_sizing
379 (GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED);
380 gtk_tree_view_column_set_resizable
381 (GTK_TREE_VIEW_COLUMN(column), TRUE);
382 gtk_tree_view_column_set_fixed_width
383 (GTK_TREE_VIEW_COLUMN(column), preferences.logcwidths2[j]);
384 gtk_tree_view_append_column (GTK_TREE_VIEW(logwindow->treeview), column);
385
386 if (i == lp->column_nr)
387 gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(column), FALSE);
388 }
389
390 g_object_set(G_OBJECT(logwindow->treeview), "fixed-height-mode", TRUE, NULL);
391 gtk_widget_show (logwindow->treeview);
392 gtk_container_add (GTK_CONTAINER (logwindow->scrolledwindow), logwindow->treeview);
393
394 /* change the page label */
395 logn = logname (name);
396 logwindow->label = gtk_label_new (NULL);
397 labelname = g_strdup_printf ("<b>%s</b>", logn);
398 gtk_label_set_markup (GTK_LABEL (logwindow->label), labelname);
399 gtk_widget_show (logwindow->label);
400 g_free (labelname);
401 gtk_notebook_set_tab_label (GTK_NOTEBOOK (mainnotebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK(mainnotebook), page), logwindow->label);
402 gtk_misc_set_padding (GTK_MISC (logwindow->label), 10, 0);
403 logwindow->logname = g_strdup (logn);
404 g_free (logn);
405
406 return (logwindow);
407 }
408
409 /* close child process when finished */
410 #ifndef G_OS_WIN32
411 static gboolean
childcheck(void)412 childcheck (void)
413 {
414 gint status, childpid;
415
416 childpid = waitpid (-1, &status, WNOHANG);
417 return (WIFEXITED(status) == 0);
418 }
419 #endif
420
421 typedef gchar *item_t[QSO_FIELDS];
savelog_compar_groupbycall(const void * b,const void * a)422 static int savelog_compar_groupbycall(const void *b, const void *a)
423 {
424 const gchar **item_a, **item_b;
425 item_a = (const gchar **)a;
426 item_b = (const gchar **)b;
427
428 return strcmp(item_a[CALL], item_b[CALL]);
429 }
430
431
432 extern GPtrArray *dxcc;
savelog_compar_sortbydxcc(const void * b,const void * a)433 static int savelog_compar_sortbydxcc(const void *b, const void *a)
434 {
435 gint rescmp;
436 gchar **item_a, **item_b;
437 item_a = (gchar **)a;
438 item_b = (gchar **)b;
439
440 struct info info_a, info_b;
441 info_a = lookupcountry_by_callsign (item_a[CALL]);
442 info_b = lookupcountry_by_callsign (item_b[CALL]);
443 dxcc_data *d_a = g_ptr_array_index (dxcc, info_a.country);
444 dxcc_data *d_b = g_ptr_array_index (dxcc, info_b.country);
445
446 /* Sort by DXCC first, then group by call sign within same DXCC */
447 rescmp = strcmp(d_b->px, d_a->px);
448 if (rescmp != 0)
449 return rescmp;
450 return strcmp(item_a[CALL], item_b[CALL]);
451 }
452
453 /* saving of the log */
454 void
savelog(gpointer arg,gchar * logfile,gint type,gint first,gint last)455 savelog (gpointer arg, gchar * logfile, gint type, gint first, gint last)
456 {
457 LOGDB *lp;
458 gint i, j, k, pid, exported;
459 const gchar *label;
460 gchar *pathstr;
461 item_t *sorteditems;
462 gint fields[QSO_FIELDS], widths[QSO_FIELDS];
463 logtype *logw = (logtype *) arg;
464 GtkTreeViewColumn *column;
465 GtkTreeModel *model;
466 GtkTreePath *path;
467 GtkTreeIter iter;
468
469 /* how many columns do we have and what are the labels, skip first field */
470 /* NOTE: unknown fields have a fixed label */
471 for (i = 0; i < logw->columns; i++)
472 {
473 column = gtk_tree_view_get_column (GTK_TREE_VIEW (logw->treeview), logw->logfields[i]);
474 label = gtk_tree_view_column_get_title (column);
475 if (logw->logfields[i] == U1)
476 fields[i] = U1;
477 else if (logw->logfields[i] == U2)
478 fields[i] = U2;
479 else
480 fields[i] = parse_column_name (label);
481 widths[i] = parse_field_width (fields[i]);
482 }
483
484 #ifndef G_OS_WIN32
485 pid = fork (); /* use fork for log saving */
486 if (pid == -1)
487 g_warning(_("fork failed when saving log: %s"), g_strerror (errno));
488 #else
489 pid = 0;
490 #endif
491
492 if (pid == 0)
493 { /* this is the child */
494 exported = last - first + 1;
495 save_num_qsos_to_export(exported); /* saved for EDI export, nobody else needs it for now */
496 lp = log_file_create (logfile, type, logw->columns, fields, widths);
497 k = 0;
498 if (lp)
499 {
500 model = gtk_tree_view_get_model (GTK_TREE_VIEW(logw->treeview));
501 /* create an array to be used for sorting */
502 sorteditems = g_new0 (item_t, exported);
503 /* go through the QSO's and store in the sort array */
504 for (i = logw->qsos - first; i >= logw->qsos - last; i--)
505 {
506 pathstr = g_strdup_printf ("%d", i);
507 path = gtk_tree_path_new_from_string (pathstr);
508 gtk_tree_model_get_iter (model, &iter, path);
509 for (j = 0; j < logw->columns; j++)
510 {
511 gtk_tree_model_get (model, &iter, logw->logfields[j], &sorteditems[k][fields[j]], -1);
512 if (fields[j] == DATE || fields[j] == NAME || fields[j] == QTH || fields[j] == U1 || fields[j] == U2 || fields[j] == REMARKS)
513 sorteditems[k][fields[j]] = g_locale_from_utf8 (sorteditems[k][fields[j]], -1, NULL, NULL, NULL);
514 }
515 k++;
516 gtk_tree_path_free (path);
517 g_free (pathstr);
518 }
519
520 if (type == TYPE_LABELS)
521 {
522 if (preferences.tsvsortbydxcc)
523 qsort(sorteditems, exported, sizeof(gchar*)*QSO_FIELDS, &savelog_compar_sortbydxcc);
524 else if (preferences.tsvgroupbycallsign > 1)
525 qsort(sorteditems, exported, sizeof(gchar*)*QSO_FIELDS, &savelog_compar_groupbycall);
526 }
527
528 /* QSO's have been sorted (or not), now save them */
529
530 for (i = 0; i <= exported -1; i++)
531 log_file_qso_append (lp, sorteditems[i]);
532 /* free the sortarray */
533 g_free(sorteditems);
534
535 log_file_close (lp);
536 #ifndef G_OS_WIN32
537 _exit (0);
538 #else
539 return;
540 #endif
541 }
542 }
543 /* parent running */
544 #ifndef G_OS_WIN32
545 g_timeout_add (1000, (GSourceFunc) childcheck, NULL);
546 #endif
547 }
548
549
550 /* look for logs in dir */
551 GPtrArray*
getxlogs(gchar * path,gchar * patt)552 getxlogs (gchar *path, gchar *patt)
553 {
554 GError *error = NULL;
555
556 GDir *dir = g_dir_open (path, 0, &error);
557 gchar *pattern = g_strdup_printf ("%s.xlog", patt);
558 GPtrArray *arr = g_ptr_array_new ();
559 if (!error)
560 {
561 const gchar *dirname = g_dir_read_name (dir);
562 while (dirname)
563 {
564 if (g_pattern_match_simple (pattern, dirname))
565 g_ptr_array_add (arr, g_strdup(dirname));
566 dirname = g_dir_read_name (dir);
567 }
568 g_dir_close (dir);
569 }
570 g_free (pattern);
571 return (arr);
572 }
573