/*
xlog - GTK+ logging program for amateur radio operators
Copyright (C) 2001 - 2008 Joop Stakenborg
This file is part of xlog.
Xlog is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Xlog is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with xlog. If not, see .
*/
/*
* log.c - assorted utilities for maintaining the logs
*/
#include
#include
#include
#include
#include
#include
#include
#ifndef G_OS_WIN32
#include
#endif
#include "callbacks_mainwindow_list.h"
#include "gui_utils.h"
#include "utils.h"
#include "cfg.h"
#include "log.h"
#include "support.h"
#include "main.h"
#include "dxcc.h"
extern GtkWidget *mainnotebook;
extern programstatetype programstate;
extern preferencestype preferences;
extern gchar **qso;
/* backup a log */
void
backuplog (gchar * filename, gchar * backupfilename)
{
gint ch;
gchar *msg;
FILE *in, *out;
in = g_fopen (filename, "r");
if (in)
{
out = g_fopen (backupfilename, "w");
if (out)
{
while ((ch = getc (in)) != EOF)
putc (ch, out);
fclose (out);
}
else
{
msg = g_strdup_printf (_("Backup to %s failed: %s"),
backupfilename, g_strerror (errno));
g_warning (msg);
g_free (msg);
}
fclose (in);
}
}
/* extract name of the log from filename, returned string should be free'd */
gchar *
logname (gchar * filename)
{
gchar *logname, *basen, **split;
basen = g_path_get_basename (filename);
split = g_strsplit (basen, ".", -1);
logname = g_strdup (split[0]);
g_free (basen);
g_strfreev (split);
return (logname);
}
/*
* Prepend qsos to the log, we convert from locale to UTF-8 here. If for some
* reason locale conversion fails we set a flag and replace the character
* with a dot. If that fails, the field is emptied. The flag is used to display
* a warning dialog. Only date, name, QTH, freefield1, freefield2 and the
* remarks field are checked for locales.
*/
gint
fillin_list (LOGDB * handle, qso_t q[], gpointer arg)
{
GtkTreeIter iter;
GtkListStore *model;
logtype *logw = (logtype *) arg;
gchar *date = NULL, *name = NULL, *qth = NULL, *u1 = NULL,
*u2 = NULL, *remarks = NULL;
GError *error;
gint i;
GtkTreePath *path;
programstate.qsos++;
logw->qsos++;
q[NR] = g_strdup_printf ("%d", logw->qsos);
model = GTK_LIST_STORE
(gtk_tree_view_get_model (GTK_TREE_VIEW (logw->treeview)));
gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
if (!q[DATE])
date = g_strdup ("");
else if (!g_utf8_validate (q[DATE], -1, NULL ))
{
error = NULL;
date = g_locale_to_utf8 (q[DATE], -1, NULL, NULL, &error);
if (!date)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[DATE], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
date = g_convert_with_fallback(q[DATE], strlen(q[DATE]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!date) date = g_strdup ("");
}
}
else date = g_strdup (q[DATE]);
if (!q[GMT])
q[GMT] = g_strdup ("");
if (!q[GMTEND])
q[GMTEND] = g_strdup ("");
if (!q[CALL])
q[CALL] = g_strdup ("");
if (!q[BAND])
q[BAND] = g_strdup ("");
if (!q[MODE])
q[MODE] = g_strdup ("");
if (!q[RST])
q[RST] = g_strdup ("");
if (!q[MYRST])
q[MYRST] = g_strdup ("");
if (!q[AWARDS])
q[AWARDS] = g_strdup ("");
if (!q[QSLOUT])
q[QSLOUT] = g_strdup ("");
if (!q[QSLIN])
q[QSLIN] = g_strdup ("");
if (!q[POWER])
q[POWER] = g_strdup ("");
if (!q[NAME])
name = g_strdup ("");
else if (!g_utf8_validate (q[NAME], -1, NULL ))
{
error = NULL;
name = g_locale_to_utf8 (q[NAME], -1, NULL, NULL, &error);
if (!name)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[NAME], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
name = g_convert_with_fallback(q[NAME], strlen(q[NAME]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!name) name = g_strdup ("");
}
}
else name = g_strdup (q[NAME]);
if (!q[QTH])
qth = g_strdup ("");
else if (!g_utf8_validate (q[QTH], -1, NULL ))
{
error = NULL;
qth = g_locale_to_utf8 (q[QTH], -1, NULL, NULL, &error);
if (!qth)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[QTH], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
qth = g_convert_with_fallback(q[QTH], strlen(q[QTH]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!qth) qth = g_strdup ("");
}
}
else qth = g_strdup (q[QTH]);
if (!q[LOCATOR])
q[LOCATOR] = g_strdup ("");
if (!q[U1])
u1 = g_strdup ("");
else if (!g_utf8_validate (q[U1], -1, NULL ))
{
error = NULL;
u1 = g_locale_to_utf8 (q[U1], -1, NULL, NULL, &error);
if (!u1)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[U1], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
u1 = g_convert_with_fallback(q[U1], strlen(q[U1]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!u1) u1 = g_strdup ("");
}
}
else u1 = g_strdup (q[U1]);
if (!q[U2])
u2 = g_strdup ("");
else if (!g_utf8_validate (q[U2], -1, NULL ))
{
error = NULL;
u2 = g_locale_to_utf8 (q[U2], -1, NULL, NULL, &error);
if (!u2)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[U2], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
u2 = g_convert_with_fallback(q[U2], strlen(q[U2]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!u2) u2 = g_strdup ("");
}
}
else u2 = g_strdup (q[U2]);
if (!q[REMARKS])
remarks = g_strdup ("");
else if (!g_utf8_validate (q[REMARKS], -1, NULL ))
{
error = NULL;
remarks = g_locale_to_utf8 (q[REMARKS], -1, NULL, NULL, &error);
if (!remarks)
{
g_warning (_("Unable to convert '%s' to UTF-8: %s"), q[REMARKS], error->message);
g_error_free (error);
programstate.utf8error = TRUE;
remarks = g_convert_with_fallback(q[REMARKS], strlen(q[REMARKS]), "UTF-8", "ISO-8859-1", ".", NULL, NULL, NULL);
if (!remarks) remarks = g_strdup ("");
}
}
else remarks = g_strdup (q[REMARKS]);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
NR, q[NR], DATE, date, GMT, q[GMT], GMTEND, q[GMTEND], CALL, q[CALL],
BAND, q[BAND], MODE, q[MODE], RST, q[RST], MYRST, q[MYRST], AWARDS, q[AWARDS],
QSLOUT, q[QSLOUT], QSLIN, q[QSLIN], POWER, q[POWER], NAME, name, QTH, qth,
LOCATOR, q[LOCATOR], U1, u1, U2, u2, REMARKS, remarks, -1);
if (!(logw->qsos % 1000))
{
while (gtk_events_pending ())
gtk_main_iteration ();
path = gtk_tree_path_new_from_string ("0");
gtk_tree_view_scroll_to_cell
(GTK_TREE_VIEW (logw->treeview), path, NULL, TRUE, 0.5, 0.0);
gtk_tree_path_free (path);
update_statusbar (_("Reading..."));
}
for (i = 0; i < QSO_FIELDS; i++)
g_free (q[i]);
if (date) g_free (date);
if (remarks) g_free (remarks);
if (name) g_free (name);
if (qth) g_free (qth);
if (u1) g_free (u1);
if (u2) g_free (u2);
return 0;
}
/* create a new struct for a log */
static logtype *
new_logwindow (void)
{
logtype *newlog;
gint i;
newlog = g_new0 (struct logtype, 1);
newlog->scrolledwindow = NULL;
newlog->treeview = NULL;
newlog->label = NULL;
newlog->logname = NULL;
newlog->filename = NULL;
newlog->logchanged = FALSE;
newlog->readonly = FALSE;
newlog->qsos = 0;
newlog->columns = 0;
for (i = 0; i < QSO_FIELDS; i++)
newlog->logfields[i] = 0;
return (newlog);
}
/* open a log and return a struct */
logtype *
openlogwindow (LOGDB * lp, gchar * name, gint page)
{
logtype *logwindow;
gint i, j;
gchar *logn, *labelname;
GtkCellRenderer *renderer, *brenderer;
GtkTreeViewColumn *column;
GObject *selection;
GtkListStore *model;
logwindow = new_logwindow ();
logwindow->scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_show (logwindow->scrolledwindow);
gtk_container_add (GTK_CONTAINER (mainnotebook), logwindow->scrolledwindow);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(logwindow->scrolledwindow),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
model = gtk_list_store_new (QSO_FIELDS,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING);
logwindow->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
gtk_tree_view_set_search_column (GTK_TREE_VIEW (logwindow->treeview), -1);
g_object_unref (G_OBJECT (model));
/* add callback for selecting a row */
selection = G_OBJECT (gtk_tree_view_get_selection(GTK_TREE_VIEW (logwindow->treeview)));
gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection), GTK_SELECTION_SINGLE);
g_signal_connect (selection, "changed", G_CALLBACK (on_log_select_row), NULL);
logwindow->columns = lp->column_nr;
/* save the active columns */
for (j = 0; j < logwindow->columns; j++)
logwindow->logfields[j] = lp->column_fields[j];
/* NR column is the first one */
renderer = gtk_cell_renderer_text_new ();
brenderer = gtk_cell_renderer_text_new ();
g_object_set (G_OBJECT (brenderer), "weight", "bold", NULL);
column = gtk_tree_view_column_new_with_attributes ("NR", renderer, "text", NR, NULL);
gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE);
gtk_tree_view_append_column (GTK_TREE_VIEW (logwindow->treeview), column);
if (preferences.logcwidths2[NR] == 0)
gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(column), FALSE);
else
gtk_tree_view_column_set_fixed_width (GTK_TREE_VIEW_COLUMN(column), preferences.logcwidths2[NR]);
/* see which fields are in the log and add a column or hide it */
for (j = 1; j < QSO_FIELDS; j++)
{
for (i = 0; i < lp->column_nr; i++)
{
if (j == lp->column_fields[i])
break;
}
renderer = gtk_cell_renderer_text_new ();
if (j == U1)
column = gtk_tree_view_column_new_with_attributes
(preferences.freefield1, renderer, "text", j, NULL);
else if (j == U2)
column = gtk_tree_view_column_new_with_attributes
(preferences.freefield2, renderer, "text", j, NULL);
else if (j == CALL)
column = gtk_tree_view_column_new_with_attributes
(strcolumn (j), brenderer, "text", j, NULL);
else
column = gtk_tree_view_column_new_with_attributes
(strcolumn (j), renderer, "text", j, NULL);
gtk_tree_view_column_set_sizing
(GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_resizable
(GTK_TREE_VIEW_COLUMN(column), TRUE);
gtk_tree_view_column_set_fixed_width
(GTK_TREE_VIEW_COLUMN(column), preferences.logcwidths2[j]);
gtk_tree_view_append_column (GTK_TREE_VIEW(logwindow->treeview), column);
if (i == lp->column_nr)
gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(column), FALSE);
}
g_object_set(G_OBJECT(logwindow->treeview), "fixed-height-mode", TRUE, NULL);
gtk_widget_show (logwindow->treeview);
gtk_container_add (GTK_CONTAINER (logwindow->scrolledwindow), logwindow->treeview);
/* change the page label */
logn = logname (name);
logwindow->label = gtk_label_new (NULL);
labelname = g_strdup_printf ("%s", logn);
gtk_label_set_markup (GTK_LABEL (logwindow->label), labelname);
gtk_widget_show (logwindow->label);
g_free (labelname);
gtk_notebook_set_tab_label (GTK_NOTEBOOK (mainnotebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK(mainnotebook), page), logwindow->label);
gtk_misc_set_padding (GTK_MISC (logwindow->label), 10, 0);
logwindow->logname = g_strdup (logn);
g_free (logn);
return (logwindow);
}
/* close child process when finished */
#ifndef G_OS_WIN32
static gboolean
childcheck (void)
{
gint status, childpid;
childpid = waitpid (-1, &status, WNOHANG);
return (WIFEXITED(status) == 0);
}
#endif
typedef gchar *item_t[QSO_FIELDS];
static int savelog_compar_groupbycall(const void *b, const void *a)
{
const gchar **item_a, **item_b;
item_a = (const gchar **)a;
item_b = (const gchar **)b;
return strcmp(item_a[CALL], item_b[CALL]);
}
extern GPtrArray *dxcc;
static int savelog_compar_sortbydxcc(const void *b, const void *a)
{
gint rescmp;
gchar **item_a, **item_b;
item_a = (gchar **)a;
item_b = (gchar **)b;
struct info info_a, info_b;
info_a = lookupcountry_by_callsign (item_a[CALL]);
info_b = lookupcountry_by_callsign (item_b[CALL]);
dxcc_data *d_a = g_ptr_array_index (dxcc, info_a.country);
dxcc_data *d_b = g_ptr_array_index (dxcc, info_b.country);
/* Sort by DXCC first, then group by call sign within same DXCC */
rescmp = strcmp(d_b->px, d_a->px);
if (rescmp != 0)
return rescmp;
return strcmp(item_a[CALL], item_b[CALL]);
}
/* saving of the log */
void
savelog (gpointer arg, gchar * logfile, gint type, gint first, gint last)
{
LOGDB *lp;
gint i, j, k, pid, exported;
G_CONST_RETURN gchar *label;
gchar *pathstr;
item_t *sorteditems;
gint fields[QSO_FIELDS], widths[QSO_FIELDS];
logtype *logw = (logtype *) arg;
GtkTreeViewColumn *column;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
/* how many columns do we have and what are the labels, skip first field */
/* NOTE: unknown fields have a fixed label */
for (i = 0; i < logw->columns; i++)
{
column = gtk_tree_view_get_column (GTK_TREE_VIEW (logw->treeview), logw->logfields[i]);
label = gtk_tree_view_column_get_title (column);
if (logw->logfields[i] == U1)
fields[i] = U1;
else if (logw->logfields[i] == U2)
fields[i] = U2;
else
fields[i] = parse_column_name (label);
widths[i] = parse_field_width (fields[i]);
}
#ifndef G_OS_WIN32
pid = fork (); /* use fork for log saving */
if (pid == -1)
g_warning(_("fork failed when saving log: %s"), g_strerror (errno));
#else
pid = 0;
#endif
if (pid == 0) { /* this is the child */
lp = log_file_create (logfile, type, logw->columns, fields, widths);
k = 0;
if (lp)
{
model = gtk_tree_view_get_model (GTK_TREE_VIEW(logw->treeview));
exported = last - first + 1;
/* create an array to be used for sorting */
sorteditems = g_new0 (item_t, exported);
/* go through the QSO's and store in the sort array */
for (i = logw->qsos - first; i >= logw->qsos - last; i--)
{
pathstr = g_strdup_printf ("%d", i);
path = gtk_tree_path_new_from_string (pathstr);
gtk_tree_model_get_iter (model, &iter, path);
for (j = 0; j < logw->columns; j++)
{
gtk_tree_model_get (model, &iter, logw->logfields[j], &sorteditems[k][fields[j]], -1);
if (fields[j] == DATE || fields[j] == NAME || fields[j] == QTH || fields[j] == U1 || fields[j] == U2 || fields[j] == REMARKS)
sorteditems[k][fields[j]] = g_locale_from_utf8 (sorteditems[k][fields[j]], -1, NULL, NULL, NULL);
}
k++;
gtk_tree_path_free (path);
g_free (pathstr);
}
if (type == TYPE_LABELS)
{
if (preferences.tsvsortbydxcc)
qsort(sorteditems, exported, sizeof(gchar*)*QSO_FIELDS, &savelog_compar_sortbydxcc);
else if (preferences.tsvgroupbycallsign > 1)
qsort(sorteditems, exported, sizeof(gchar*)*QSO_FIELDS, &savelog_compar_groupbycall);
}
/* QSO's have been sorted (or not), now save them */
for (i = 0; i <= exported -1; i++)
log_file_qso_append (lp, sorteditems[i]);
/* free the sortarray */
g_free(sorteditems);
log_file_close (lp);
#ifndef G_OS_WIN32
_exit (0);
#else
return;
#endif
} }
/* parent running */
#ifndef G_OS_WIN32
g_timeout_add (1000, (GSourceFunc) childcheck, NULL);
#endif
}
/* look for logs in dir */
GPtrArray*
getxlogs (gchar *path, gchar *patt)
{
GError *error = NULL;
GDir *dir = g_dir_open (path, 0, &error);
gchar *pattern = g_strdup_printf ("%s.xlog", patt);
GPtrArray *arr = g_ptr_array_new ();
if (!error)
{
const gchar *dirname = g_dir_read_name (dir);
while (dirname)
{
if (g_pattern_match_simple (pattern, dirname))
g_ptr_array_add (arr, g_strdup(dirname));
dirname = g_dir_read_name (dir);
}
g_dir_close (dir);
}
g_free (pattern);
return (arr);
}