/ src / sat-log-browser.c
sat-log-browser.c
  1  /*
  2      Gpredict: Real-time satellite tracking and orbit prediction program
  3  
  4      Copyright (C)  2001-2017  Alexandru Csete, OZ9AEC.
  5  
  6      This program is free software; you can redistribute it and/or modify
  7      it under the terms of the GNU General Public License as published by
  8      the Free Software Foundation; either version 2 of the License, or
  9      (at your option) any later version.
 10  
 11      This program is distributed in the hope that it will be useful,
 12      but WITHOUT ANY WARRANTY; without even the implied warranty of
 13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14      GNU General Public License for more details.
 15  
 16      You should have received a copy of the GNU General Public License
 17      along with this program; if not, visit http://www.fsf.org/
 18  */
 19  #include <gtk/gtk.h>
 20  #include <glib/gi18n.h>
 21  
 22  #include "compat.h"
 23  #include "sat-log.h"
 24  #include "sat-log-browser.h"
 25  
 26  
 27  /* columns in the message list */
 28  typedef enum {
 29      MSG_LIST_COL_TIME = 0,
 30      MSG_LIST_COL_LEVEL,
 31      MSG_LIST_COL_MSG,
 32      MSG_LIST_COL_NUMBER
 33  } msg_list_col_t;
 34  
 35  /* data structure to hold one message */
 36  typedef struct {
 37      time_t          time;       /* time stamp */
 38      sat_log_level_t level;      /* debug level */
 39      gchar          *message;    /* the message itself */
 40  } message_t;
 41  
 42  /* Easy access to column titles */
 43  const gchar    *MSG_LIST_COL_TITLE[MSG_LIST_COL_NUMBER] = {
 44      N_("Time"),
 45      N_("Level"),
 46      N_("Message")
 47  };
 48  
 49  const gfloat    MSG_LIST_COL_TITLE_ALIGN[MSG_LIST_COL_NUMBER] = {
 50      0.5, 0.5, 0.0
 51  };
 52  
 53  const gchar    *DEBUG_STR[6] = {
 54      N_("NONE"),
 55      N_("ERROR"),
 56      N_("WARNING"),
 57      N_("INFO"),
 58      N_("DEBUG")
 59  };
 60  
 61  
 62  extern GtkWidget *app;
 63  static gboolean initialised = FALSE;    /* Is module initialised? */
 64  
 65  /* counters */
 66  static guint32  errors = 0;     /* Number of error messages */
 67  static guint32  warnings = 0;   /* Number of warning messages */
 68  static guint32  infos = 0;      /* Number of verbose messages */
 69  static guint32  debugs = 0;     /* Number of trace messages */
 70  
 71  /* summary labels; they need to be accessible at runtime */
 72  static GtkWidget *errorlabel, *warnlabel, *infolabel, *debuglabel, *sumlabel;
 73  
 74  /* The message window itself */
 75  static GtkWidget *window;
 76  
 77  /* the tree view model */
 78  GtkTreeModel   *model;
 79  
 80  
 81  /* load debug file related */
 82  //static void     load_debug_file(GtkWidget * parent);
 83  //static int      read_debug_file(const gchar * filename);
 84  //static void     clear_message_list(void);
 85  
 86  static void     add_debug_message(const gchar * datetime,
 87                                    sat_log_level_t debug_level,
 88                                    const char *message);
 89  
 90  /*
 91   * Clear the message list
 92   *
 93   * Besides clearing the message list, the function also resets
 94   * the counters and set the text of the corresponding widgets
 95   * to zero.
 96   */
 97  static void clear_message_list()
 98  {
 99      /* clear the meaase list */
100      gtk_list_store_clear(GTK_LIST_STORE(model));
101  
102      /* reset the counters and text widgets */
103      errors = 0;
104      warnings = 0;
105      debugs = 0;
106  
107      gtk_label_set_text(GTK_LABEL(errorlabel), "0");
108      gtk_label_set_text(GTK_LABEL(warnlabel), "0");
109      gtk_label_set_text(GTK_LABEL(infolabel), "0");
110      gtk_label_set_text(GTK_LABEL(debuglabel), "0");
111      gtk_label_set_markup(GTK_LABEL(sumlabel), "<b>0</b>");
112  }
113  
114  /* Read contents of debug file. */
115  static int read_debug_file(const gchar * filename)
116  {
117      GIOChannel     *logfile = NULL;     /* the log file */
118      GError         *error = NULL;       /* error structure */
119      gint            errorcode = 0;      /* error code returned by function */
120      gchar          *line;       /* line read from file */
121      gsize           length;     /* length of line read from file */
122      gchar         **buff;
123  
124  
125      /* check file and read contents */
126      if (g_file_test(filename, G_FILE_TEST_EXISTS))
127      {
128          /* open file */
129          logfile = g_io_channel_new_file(filename, "r", &error);
130  
131          if (logfile)
132          {
133              /* read the file line by line */
134              while (g_io_channel_read_line(logfile,
135                                            &line,
136                                            &length,
137                                            NULL, NULL) != G_IO_STATUS_EOF)
138              {
139                  /* trim line and split it */
140                  line = g_strdelimit(line, "\n", '\0');
141  
142                  buff = g_strsplit(line, SAT_LOG_MSG_SEPARATOR, MSG_LIST_COL_NUMBER + 1);        // +1 because we used to have a msg source (pre 1.4)
143  
144                  /* Gpredict 1.3 and earlier had 4 fields:
145                   *   buff[0]: Date and time
146                   *   buff[1]: Message source
147                   *   buff[2]: Message type
148                   *   buff[3]: Message
149                   * As of 1.4 we no longer have message source:
150                   *   buff[0]: Date and time
151                   *   buff[1]: Message type
152                   *   buff[2]: Message
153                   * 
154                   * Gpredicxt 1.4+ will be able to read logs generated by earlier versions
155                   * but not the other way around.
156                   */
157                  switch (g_strv_length(buff))
158                  {
159  
160                  case 1:
161                      add_debug_message("", SAT_LOG_LEVEL_ERROR, buff[0]);
162                      break;
163  
164                  case 3:
165                      /* v1.4 and later */
166                      add_debug_message(buff[0],
167                                        (guint) g_ascii_strtod(buff[1], NULL),
168                                        buff[2]);
169                      break;
170  
171                  case 4:
172                      /* v1.3 and earlier with message source */
173                      add_debug_message(buff[0],  /* buff[1], - used to be message src */
174                                        (guint) g_ascii_strtod(buff[2], NULL),
175                                        buff[3]);
176                      break;
177                  default:
178                      add_debug_message("", SAT_LOG_LEVEL_ERROR,
179                                        _("Log file is corrupt"));
180                      break;
181                  }
182  
183                  /* clean up */
184                  g_free(line);
185                  g_strfreev(buff);
186              }
187  
188              errorcode = 0;
189  
190              /* Close IO channel; don't care about status.
191                 Shutdown will flush the stream and close the channel
192                 as soon as the reference count is dropped. Order matters!
193               */
194              g_io_channel_shutdown(logfile, TRUE, NULL);
195              g_io_channel_unref(logfile);
196  
197          }
198          else
199          {
200              /* an error occurred */
201              sat_log_log(SAT_LOG_LEVEL_ERROR,
202                          _("%s:%d: Error open debug log (%s)"),
203                          __FILE__, __LINE__, error->message);
204  
205              g_clear_error(&error);
206              errorcode = 1;
207          }
208      }
209      else
210      {
211          errorcode = 1;
212      }
213  
214      return errorcode;
215  }
216  
217  /*
218   * Load debug file.
219   *
220   * This function creates the file chooser dialog, which can be used to select
221   * a file containing debug messages. When the dialog returns, the selected
222   * file is checked and, if the file exists, is read line by line.
223   */
224  static void load_debug_file(GtkWidget * parent)
225  {
226      gchar          *confdir;
227      gchar          *filename;
228      gchar          *title;
229      gint            error;      /* error code returned by by read_debug_file */
230  
231  
232      GtkWidget      *dialog;
233  
234      /* create file chooser dialog */
235      dialog = gtk_file_chooser_dialog_new(_("Select Log File"),
236                                           GTK_WINDOW(parent),
237                                           GTK_FILE_CHOOSER_ACTION_OPEN,
238                                           "_Cancel", GTK_RESPONSE_CANCEL,
239                                           "_Open", GTK_RESPONSE_ACCEPT, NULL);
240  
241      confdir = get_user_conf_dir();
242      filename = g_strconcat(confdir, G_DIR_SEPARATOR_S, "logs", NULL);
243      gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), filename);
244      g_free(filename);
245      g_free(confdir);
246  
247      if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
248      {
249          clear_message_list();
250  
251          filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
252  
253          /* sanity check of filename will be performed
254             in read_debug_file */
255          error = read_debug_file(filename);
256          if (error == 0)
257          {
258              /* Update title with filename */
259              title = g_strdup_printf(_("Log Browser: %s"), filename);
260              gtk_window_set_title(GTK_WINDOW(window), title);
261              g_free(title);
262          }
263          else
264          {
265              /* clear filename from title if unable to read file */
266              gtk_window_set_title(GTK_WINDOW(window), _("Log Browser"));
267          }
268  
269          g_free(filename);
270      }
271  
272      gtk_widget_destroy(dialog);
273  }
274  
275  /* callback function called when a dialog button is clicked */
276  static void message_window_response(GtkWidget * widget, gint response,
277                                      gpointer data)
278  {
279      (void)data;
280  
281      switch (response)
282      {
283      case GTK_RESPONSE_CLOSE:
284          gtk_widget_destroy(widget);
285          break;
286      case GTK_RESPONSE_YES:
287          load_debug_file(widget);
288          break;
289      case GTK_RESPONSE_NO:
290          clear_message_list();
291          break;
292  
293      default:
294          break;
295      }
296  }
297  
298  /* create summary */
299  static GtkWidget *create_message_summary()
300  {
301      GtkWidget      *vbox;
302      GtkWidget      *table;      /* table containing everything */
303      GtkWidget      *frame;      /* surrounding frame */
304      GtkWidget      *label;      /* dummy label */
305  
306      /* create labels */
307      errorlabel = gtk_label_new("0");
308      g_object_set(errorlabel, "xalign", 1.0f, "yalign", 0.5f, NULL);
309  
310      warnlabel = gtk_label_new("0");
311      g_object_set(warnlabel, "xalign", 1.0f, "yalign", 0.5f, NULL);
312  
313      infolabel = gtk_label_new("0");
314      g_object_set(infolabel, "xalign", 1.0f, "yalign", 0.5f, NULL);
315  
316      debuglabel = gtk_label_new("0");
317      g_object_set(debuglabel, "xalign", 1.0f, "yalign", 0.5f, NULL);
318  
319      sumlabel = gtk_label_new(NULL);
320      gtk_label_set_use_markup(GTK_LABEL(sumlabel), TRUE);
321      gtk_label_set_markup(GTK_LABEL(sumlabel), "<b>0</b>");
322      g_object_set(sumlabel, "xalign", 1.0f, "yalign", 0.5f, NULL);
323  
324      /* create table and add widgets */
325      table = gtk_grid_new();
326      gtk_grid_set_column_homogeneous(GTK_GRID(table), TRUE);
327      gtk_grid_set_row_homogeneous(GTK_GRID(table), TRUE);
328      gtk_container_set_border_width(GTK_CONTAINER(table), 10);
329  
330      label = gtk_label_new(_("Errors"));
331      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
332      gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
333  
334      label = gtk_label_new(_("Warnings"));
335      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
336      gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
337  
338      label = gtk_label_new(_("Info"));
339      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
340      gtk_grid_attach(GTK_GRID(table), label, 0, 2, 1, 1);
341  
342      label = gtk_label_new(_("Debug"));
343      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
344      gtk_grid_attach(GTK_GRID(table), label, 0, 3, 1, 1);
345  
346      gtk_grid_attach(GTK_GRID(table),
347                      gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 4, 2, 1);
348  
349      label = gtk_label_new(NULL);
350      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
351      gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
352      gtk_label_set_markup(GTK_LABEL(label), _("<b>Total</b>"));
353      gtk_grid_attach(GTK_GRID(table), label, 0, 5, 1, 1);
354  
355      gtk_grid_attach(GTK_GRID(table), errorlabel, 1, 0, 1, 1);
356      gtk_grid_attach(GTK_GRID(table), warnlabel, 1, 1, 1, 1);
357      gtk_grid_attach(GTK_GRID(table), infolabel, 1, 2, 1, 1);
358      gtk_grid_attach(GTK_GRID(table), debuglabel, 1, 3, 1, 1);
359      gtk_grid_attach(GTK_GRID(table), sumlabel, 1, 5, 1, 1);
360  
361      /* frame around the table */
362      frame = gtk_frame_new(_(" Summary "));
363      gtk_frame_set_label_align(GTK_FRAME(frame), 0.5, 0.5);
364      gtk_container_add(GTK_CONTAINER(frame), table);
365  
366      /* pack frame into vbox so that it doesn't gets stretched vertically */
367      vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
368      gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
369  
370      return vbox;
371  }
372  
373  /* create tree view model; we actually create a GtkListStore because we are
374     only interested in a flat list. A GtkListStore can be cast to a GtkTreeModel
375     without any problems.
376  */
377  static GtkTreeModel *create_list_model()
378  {
379      GtkListStore   *liststore;
380  
381      liststore = gtk_list_store_new(MSG_LIST_COL_NUMBER, G_TYPE_STRING,
382                                     G_TYPE_STRING, G_TYPE_STRING);
383  
384      /*** Fill existing data into the list here ***/
385  
386      return GTK_TREE_MODEL(liststore);
387  }
388  
389  static gint message_window_delete(GtkWidget * widget, GdkEvent * event,
390                                    gpointer data)
391  {
392      (void)widget;
393      (void)event;
394      (void)data;
395  
396      /* return FALSE to indicate that message window
397         should be destroyed */
398      return FALSE;
399  }
400  
401  /* callback function called when the dialog window is destroyed */
402  static void message_window_destroy(GtkWidget * widget, gpointer data)
403  {
404      (void)widget;
405      (void)data;
406  
407      /* clean up memory */
408      /* GSList, ... */
409  
410      initialised = FALSE;
411  }
412  
413  /* Create list view */
414  static GtkWidget *create_message_list()
415  {
416      GtkWidget      *treeview;   /* high level treev iew widget */
417      GtkWidget      *swin;       /* scrolled window containing the tree view */
418      GtkCellRenderer *renderer;  /* cell renderer used to create a column */
419      GtkTreeViewColumn *column;  /* place holder for a tree view column */
420  
421      guint           i;
422  
423      treeview = gtk_tree_view_new();
424  
425      for (i = 0; i < MSG_LIST_COL_NUMBER; i++)
426      {
427          renderer = gtk_cell_renderer_text_new();
428          column =
429              gtk_tree_view_column_new_with_attributes(_(MSG_LIST_COL_TITLE[i]),
430                                                       renderer, "text", i,
431                                                       NULL);
432          gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
433  
434          /* only aligns the headers? */
435          gtk_tree_view_column_set_alignment(column,
436                                             MSG_LIST_COL_TITLE_ALIGN[i]);
437      }
438  
439      /* create tree view model and finalise tree view */
440      model = create_list_model();
441      gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model);
442      g_object_unref(model);
443  
444      /* treeview is packed into a scroleld window */
445      swin = gtk_scrolled_window_new(NULL, NULL);
446      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
447                                     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
448      gtk_container_add(GTK_CONTAINER(swin), treeview);
449  
450      return swin;
451  }
452  
453  /* Initialise message window.
454   *
455   * This function creates the message window and allocates all the internal
456   * data structures. The function should be called when the main program
457   * is initialised.
458   */
459  void sat_log_browser_open()
460  {
461      GtkWidget      *hbox;
462      gchar          *fname;
463      gchar          *confdir;
464      gchar          *title;
465      gint            error;      /* error code returned by by read_debug_file */
466  
467      if (!initialised)
468      {
469          hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
470          gtk_box_pack_start(GTK_BOX(hbox), create_message_list(),
471                             TRUE, TRUE, 0);
472          gtk_box_pack_start(GTK_BOX(hbox), create_message_summary(),
473                             FALSE, TRUE, 0);
474  
475          /* create dialog window; we use "fake" stock responses to catch user
476             button clicks (save_as and pause)
477           */
478          window = gtk_dialog_new_with_buttons(_("Log Browser"),
479                                               GTK_WINDOW(app),
480                                               GTK_DIALOG_DESTROY_WITH_PARENT,
481                                               "_Open", GTK_RESPONSE_YES,
482                                               "Clear", GTK_RESPONSE_NO,
483                                               "_Close", GTK_RESPONSE_CLOSE,
484                                               NULL);
485  
486          gtk_window_set_default_size(GTK_WINDOW(window), 600, 300);
487  
488          gtk_box_pack_start(GTK_BOX
489                             (gtk_dialog_get_content_area(GTK_DIALOG(window))),
490                             hbox, TRUE, TRUE, 0);
491  
492          /* connect response signal */
493          g_signal_connect(G_OBJECT(window), "response",
494                           G_CALLBACK(message_window_response), NULL);
495  
496          /* connect delete and destroy signals */
497          g_signal_connect(G_OBJECT(window), "delete_event",
498                           G_CALLBACK(message_window_delete), NULL);
499          g_signal_connect(G_OBJECT(window), "destroy",
500                           G_CALLBACK(message_window_destroy), NULL);
501  
502          gtk_widget_show_all(window);
503  
504          /* read gpredict.log by default */
505          confdir = get_user_conf_dir();
506          fname = g_strconcat(confdir, G_DIR_SEPARATOR_S,
507                              "logs", G_DIR_SEPARATOR_S, "gpredict.log", NULL);
508  
509          error = read_debug_file(fname);
510  
511          if (error == 0)
512          {
513              /* update window title if file read cleanly */
514              title = g_strdup_printf(_("Log Browser: %s"), fname);
515              gtk_window_set_title(GTK_WINDOW(window), title);
516  
517              g_free(title);
518          }
519          else
520          {
521              /* Remove filename if file not read */
522              gtk_window_set_title(GTK_WINDOW(window), _("Log Browser"));
523          }
524  
525          g_free(fname);
526          g_free(confdir);
527  
528          initialised = TRUE;
529      }
530  }
531  
532  /* Add a message to message list */
533  static void add_debug_message(const gchar * datetime,
534                                sat_log_level_t debug_level, const char *message)
535  {
536      guint           total;      /* totalt number of messages */
537      gchar          *str;        /* string to show message count */
538      GtkTreeIter     item;       /* new item added to the list store */
539  
540      gtk_list_store_append(GTK_LIST_STORE(model), &item);
541      gtk_list_store_set(GTK_LIST_STORE(model), &item,
542                         MSG_LIST_COL_TIME, datetime,
543                         MSG_LIST_COL_LEVEL, _(DEBUG_STR[debug_level]),
544                         MSG_LIST_COL_MSG, message, -1);
545  
546      switch (debug_level)
547      {
548      case SAT_LOG_LEVEL_ERROR:
549          errors++;
550          str = g_strdup_printf("%d", errors);
551          gtk_label_set_text(GTK_LABEL(errorlabel), str);
552          g_free(str);
553          break;
554      case SAT_LOG_LEVEL_WARN:
555          warnings++;
556          str = g_strdup_printf("%d", warnings);
557          gtk_label_set_text(GTK_LABEL(warnlabel), str);
558          g_free(str);
559          break;
560      case SAT_LOG_LEVEL_INFO:
561          infos++;
562          str = g_strdup_printf("%d", infos);
563          gtk_label_set_text(GTK_LABEL(infolabel), str);
564          g_free(str);
565          break;
566      case SAT_LOG_LEVEL_DEBUG:
567          debugs++;
568          str = g_strdup_printf("%d", debugs);
569          gtk_label_set_text(GTK_LABEL(debuglabel), str);
570          g_free(str);
571          break;
572  
573      default:
574          break;
575      }
576  
577      /* the sum does not have to be updated for each line */
578      total = errors + warnings + infos + debugs;
579      str = g_strdup_printf("<b>%d</b>", total);
580      gtk_label_set_markup(GTK_LABEL(sumlabel), str);
581      g_free(str);
582  }