1 /* EasyTAG - Tag editor for audio files
2  * Copyright (C) 2014-2015  David King <amigadave@amigadave.com>
3  * Copyright (C) 2000-2007  Jerome Couderc <easytag@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #include <glib/gi18n.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 
26 #include "log.h"
27 #include "application_window.h"
28 #include "easytag.h"
29 #include "setting.h"
30 #include "charset.h"
31 
32 #include "win32/win32dep.h"
33 
34 typedef struct
35 {
36     GtkWidget *log_view;
37     GtkListStore *log_model;
38 
39     /* Popup menu. */
40     GtkWidget *menu;
41 } EtLogAreaPrivate;
42 
43 G_DEFINE_TYPE_WITH_PRIVATE (EtLogArea, et_log_area, GTK_TYPE_BIN)
44 
45 enum
46 {
47     LOG_ICON_NAME,
48     LOG_TIME_TEXT,
49     LOG_TEXT,
50     LOG_COLUMN_COUNT
51 };
52 
53 /* File for log. */
54 static const gchar LOG_FILE[] = "easytag.log";
55 
56 /**************
57  * Prototypes *
58  **************/
59 static void Log_List_Set_Row_Visible (EtLogArea *self, GtkTreeIter *rowIter);
60 static gchar *Log_Format_Date (void);
61 
62 
63 
64 /*************
65  * Functions *
66  *************/
67 
68 static void
do_popup_menu(EtLogArea * self,GdkEventButton * event)69 do_popup_menu (EtLogArea *self,
70                GdkEventButton *event)
71 {
72     EtLogAreaPrivate *priv;
73     gint button;
74     gint event_time;
75 
76     priv = et_log_area_get_instance_private (self);
77 
78     if (event)
79     {
80         button = event->button;
81         event_time = event->time;
82     }
83     else
84     {
85         button = 0;
86         event_time = gtk_get_current_event_time ();
87     }
88 
89     /* TODO: Add popup positioning function. */
90     gtk_menu_popup (GTK_MENU (priv->menu), NULL, NULL, NULL, NULL, button,
91                     event_time);
92 }
93 
94 static gboolean
on_popup_menu(GtkWidget * treeview,EtLogArea * self)95 on_popup_menu (GtkWidget *treeview,
96                EtLogArea *self)
97 {
98     do_popup_menu (self, NULL);
99 
100     return GDK_EVENT_STOP;
101 }
102 
103 /*
104  * Log_Popup_Menu_Handler : displays the corresponding menu
105  */
106 static gboolean
on_button_press_event(GtkWidget * treeview,GdkEventButton * event,EtLogArea * self)107 on_button_press_event (GtkWidget *treeview,
108                        GdkEventButton *event,
109                        EtLogArea *self)
110 {
111     if (gdk_event_triggers_context_menu ((GdkEvent *)event))
112     {
113         do_popup_menu (self, event);
114 
115         return GDK_EVENT_STOP;
116     }
117 
118     return GDK_EVENT_PROPAGATE;
119 }
120 
121 
122 static void
et_log_area_class_init(EtLogAreaClass * klass)123 et_log_area_class_init (EtLogAreaClass *klass)
124 {
125     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
126 
127     gtk_widget_class_set_template_from_resource (widget_class,
128                                                  "/org/gnome/EasyTAG/log_area.ui");
129     gtk_widget_class_bind_template_child_private (widget_class, EtLogArea,
130                                                   log_view);
131     gtk_widget_class_bind_template_child_private (widget_class, EtLogArea,
132                                                   log_model);
133 }
134 
135 static void
et_log_area_init(EtLogArea * self)136 et_log_area_init (EtLogArea *self)
137 {
138     EtLogAreaPrivate *priv;
139     GtkBuilder *builder;
140     GMenuModel *menu_model;
141 
142     priv = et_log_area_get_instance_private (self);
143 
144     gtk_widget_init_template (GTK_WIDGET (self));
145 
146     /* Create popup menu. */
147     builder = gtk_builder_new_from_resource ("/org/gnome/EasyTAG/menus.ui");
148 
149     menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "log-menu"));
150     priv->menu = gtk_menu_new_from_model (menu_model);
151     gtk_menu_attach_to_widget (GTK_MENU (priv->menu), priv->log_view, NULL);
152 
153     g_object_unref (builder);
154 
155     g_signal_connect (priv->log_view, "popup-menu", G_CALLBACK (on_popup_menu),
156                       self);
157     g_signal_connect (priv->log_view, "button-press-event",
158                       G_CALLBACK (on_button_press_event), self);
159 }
160 
161 
162 GtkWidget *
et_log_area_new(void)163 et_log_area_new (void)
164 {
165     return g_object_new (ET_TYPE_LOG_AREA, NULL);
166 }
167 
168 /*
169  * Set a row visible in the log list (by scrolling the list)
170  */
171 static void
Log_List_Set_Row_Visible(EtLogArea * self,GtkTreeIter * rowIter)172 Log_List_Set_Row_Visible (EtLogArea *self,
173                           GtkTreeIter *rowIter)
174 {
175     EtLogAreaPrivate *priv;
176     GtkTreePath *start;
177     GtkTreePath *end;
178 
179     priv = et_log_area_get_instance_private (self);
180 
181     if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (priv->log_view),
182                                          &start, &end))
183     {
184         GtkTreePath *rowPath;
185 
186         rowPath = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->log_model),
187                                            rowIter);
188 
189         if ((gtk_tree_path_compare (rowPath, start) < 0)
190             || (gtk_tree_path_compare (rowPath, end) > 0))
191         {
192             /* rowPath is not is the visible range. */
193             gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->log_view),
194                                           rowPath, NULL, FALSE, 0, 0);
195         }
196 
197         gtk_tree_path_free (start);
198         gtk_tree_path_free (end);
199         gtk_tree_path_free (rowPath);
200     }
201 }
202 
203 
204 /*
205  * Remove all lines in the log list
206  */
207 void
et_log_area_clear(EtLogArea * self)208 et_log_area_clear (EtLogArea *self)
209 {
210     EtLogAreaPrivate *priv;
211 
212     g_return_if_fail (ET_LOG_AREA (self));
213 
214     priv = et_log_area_get_instance_private (self);
215 
216     if (priv->log_model)
217     {
218         gtk_list_store_clear (priv->log_model);
219     }
220 }
221 
222 
223 /*
224  * Return time in allocated data
225  */
226 static gchar *
Log_Format_Date(void)227 Log_Format_Date (void)
228 {
229     GDateTime *dt;
230     gchar *time;
231 
232     dt = g_date_time_new_now_local ();
233     /* Time without date in current locale. */
234     time = g_date_time_format (dt, "%X");
235 
236     g_date_time_unref (dt);
237 
238     return time;
239 }
240 
241 static const gchar *
get_icon_name_from_error_kind(EtLogAreaKind error_kind)242 get_icon_name_from_error_kind (EtLogAreaKind error_kind)
243 {
244     switch (error_kind)
245     {
246         /* Same icon for information and OK messages. */
247         case LOG_OK:
248         case LOG_INFO:
249             return "dialog-information";
250             break;
251         case LOG_WARNING:
252             return "dialog-warning";
253             break;
254         case LOG_ERROR:
255             return "dialog-error";
256             break;
257         case LOG_UNKNOWN:
258             return NULL;
259             break;
260         default:
261             g_assert_not_reached ();
262     }
263 }
264 
265 /*
266  * Function to use anywhere in the application to send a message to the LogList
267  */
268 void
Log_Print(EtLogAreaKind error_type,const gchar * const format,...)269 Log_Print (EtLogAreaKind error_type, const gchar * const format, ...)
270 {
271     EtLogArea *self;
272     EtLogAreaPrivate *priv;
273     va_list args;
274     gchar *string;
275     gchar *time;
276     GtkTreeIter iter;
277     static gboolean first_time = TRUE;
278     static gchar *file_path = NULL;
279     GFile *file;
280     GFileOutputStream *file_ostream;
281     GError *error = NULL;
282 
283     self = ET_LOG_AREA (et_application_window_get_log_area (ET_APPLICATION_WINDOW (MainWindow)));
284 
285     g_return_if_fail (self != NULL);
286 
287     priv = et_log_area_get_instance_private (self);
288 
289     va_start (args, format);
290     string = g_strdup_vprintf (format, args);
291     va_end (args);
292 
293     time = Log_Format_Date ();
294 
295     gtk_list_store_insert_with_values (priv->log_model, &iter, G_MAXINT,
296                                        LOG_ICON_NAME,
297                                        get_icon_name_from_error_kind (error_type),
298                                        LOG_TIME_TEXT, time, LOG_TEXT,
299                                        string, -1);
300     Log_List_Set_Row_Visible (self, &iter);
301     g_free (time);
302 
303     // Store also the messages in the log file.
304     if (!file_path)
305     {
306         gchar *cache_path = g_build_filename (g_get_user_cache_dir (),
307                                               PACKAGE_TARNAME, NULL);
308 
309         if (!g_file_test (cache_path, G_FILE_TEST_IS_DIR))
310         {
311             gint result = g_mkdir_with_parents (cache_path, S_IRWXU);
312 
313             if (result == -1)
314             {
315                 g_printerr ("%s", "Unable to create cache directory");
316                 g_free (cache_path);
317                 g_free (string);
318 
319                 return;
320             }
321         }
322 
323         file_path = g_build_filename (cache_path, LOG_FILE, NULL);
324         g_free (cache_path);
325     }
326 
327     file = g_file_new_for_path (file_path);
328 
329     /* On startup, the log is cleared. The log is then appended to for the
330      * remainder of the application lifetime. */
331     if (first_time)
332     {
333         file_ostream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
334                                        NULL, &error);
335     }
336     else
337     {
338         file_ostream = g_file_append_to (file, G_FILE_CREATE_NONE, NULL,
339                                          &error);
340     }
341 
342     if (file_ostream)
343     {
344         GString *data;
345         gsize bytes_written;
346 
347         time = Log_Format_Date ();
348         data = g_string_new (time);
349         g_free (time);
350 
351         data = g_string_append_c (data, ' ');
352         data = g_string_append (data, string);
353         g_free (string);
354 
355         data = g_string_append_c (data, '\n');
356 
357         if (!g_output_stream_write_all (G_OUTPUT_STREAM (file_ostream),
358                                         data->str, data->len, &bytes_written,
359                                         NULL, &error))
360         {
361             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
362                      "bytes of data were written", bytes_written, data->len);
363 
364             /* To avoid recursion of Log_Print. */
365             g_warning ("Error writing to the log file '%s' ('%s')", file_path,
366                        error->message);
367 
368             g_error_free (error);
369 
370             g_string_free (data, TRUE);
371             g_object_unref (file_ostream);
372             g_object_unref (file);
373 
374             return;
375         }
376 
377         first_time = FALSE;
378 
379         g_string_free (data, TRUE);
380     }
381     else
382     {
383         g_warning ("Error opening output stream of file '%s' ('%s')",
384                    file_path, error->message);
385         g_error_free (error);
386     }
387 
388     g_object_unref (file_ostream);
389     g_object_unref (file);
390 }
391