1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* Copyright (C) 1998 Cesar Miquel <miquel@df.uba.ar>
3  * Copyright (C) 2008 Cosimo Cecchi <cosimoc@gnome.org>
4  * Copyright (C) 2012-2021 MATE Developers
5  *
6  * This file is part of MATE Utils.
7  *
8  * MATE Utils 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 2 of the License, or
11  * (at your option) any later version.
12  *
13  * MATE Utils 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 MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gio/gio.h>
29 
30 #ifdef HAVE_ZLIB
31 #include <zlib.h>
32 #endif
33 
34 #include "logview-log.h"
35 #include "logview-utils.h"
36 
37 enum {
38   LOG_CHANGED,
39   LAST_SIGNAL
40 };
41 
42 static guint signals [LAST_SIGNAL] = { 0 };
43 
44 struct _LogviewLogPrivate {
45   /* file and monitor */
46   GFile *file;
47   GFileMonitor *mon;
48 
49   /* stats about the file */
50   time_t file_time;
51   goffset file_size;
52   char *display_name;
53   gboolean has_days;
54 
55   /* lines and relative days */
56   GSList *days;
57   GPtrArray *lines;
58   guint lines_no;
59 
60   /* stream poiting to the log */
61   GDataInputStream *stream;
62   gboolean has_new_lines;
63 };
64 
65 typedef struct {
66   LogviewLog *log;
67   GError *err;
68   LogviewCreateCallback callback;
69   gpointer user_data;
70 } LoadJob;
71 
72 typedef struct {
73   LogviewLog *log;
74   GError *err;
75   const char **lines;
76   GSList *new_days;
77   GCancellable *cancellable;
78   LogviewNewLinesCallback callback;
79   gpointer user_data;
80 } NewLinesJob;
81 
82 typedef struct {
83   GInputStream *parent_str;
84   guchar * buffer;
85   GFile *file;
86 
87   gboolean last_str_result;
88   int last_z_result;
89   z_stream zstream;
90 } GZHandle;
91 
92 G_DEFINE_TYPE_WITH_PRIVATE (LogviewLog, logview_log, G_TYPE_OBJECT);
93 
94 static void
do_finalize(GObject * obj)95 do_finalize (GObject *obj)
96 {
97   LogviewLog *log = LOGVIEW_LOG (obj);
98   char ** lines;
99 
100   if (log->priv->stream) {
101     g_object_unref (log->priv->stream);
102     log->priv->stream = NULL;
103   }
104 
105   if (log->priv->file) {
106     g_object_unref (log->priv->file);
107     log->priv->file = NULL;
108   }
109 
110   if (log->priv->mon) {
111     g_object_unref (log->priv->mon);
112     log->priv->mon = NULL;
113   }
114 
115   if (log->priv->days) {
116     g_slist_free_full (log->priv->days,
117                        (GDestroyNotify) logview_utils_day_free);
118     log->priv->days = NULL;
119   }
120 
121   if (log->priv->lines) {
122     lines = (char **) g_ptr_array_free (log->priv->lines, FALSE);
123     g_strfreev (lines);
124     log->priv->lines = NULL;
125   }
126 
127   G_OBJECT_CLASS (logview_log_parent_class)->finalize (obj);
128 }
129 
130 static void
logview_log_class_init(LogviewLogClass * klass)131 logview_log_class_init (LogviewLogClass *klass)
132 {
133   GObjectClass *object_class = G_OBJECT_CLASS (klass);
134 
135   object_class->finalize = do_finalize;
136 
137   signals[LOG_CHANGED] = g_signal_new ("log-changed",
138                                        G_OBJECT_CLASS_TYPE (object_class),
139                                        G_SIGNAL_RUN_LAST,
140                                        G_STRUCT_OFFSET (LogviewLogClass, log_changed),
141                                        NULL, NULL,
142                                        g_cclosure_marshal_VOID__VOID,
143                                        G_TYPE_NONE, 0);
144 }
145 
146 static void
logview_log_init(LogviewLog * self)147 logview_log_init (LogviewLog *self)
148 {
149   self->priv = logview_log_get_instance_private (self);
150 
151   self->priv->lines = NULL;
152   self->priv->lines_no = 0;
153   self->priv->days = NULL;
154   self->priv->file = NULL;
155   self->priv->mon = NULL;
156   self->priv->has_new_lines = FALSE;
157   self->priv->has_days = FALSE;
158 }
159 
160 static void
monitor_changed_cb(GFileMonitor * monitor,GFile * file,GFile * unused,GFileMonitorEvent event,gpointer user_data)161 monitor_changed_cb (GFileMonitor *monitor,
162                     GFile *file,
163                     GFile *unused,
164                     GFileMonitorEvent event,
165                     gpointer user_data)
166 {
167   LogviewLog *log = user_data;
168 
169   if (event == G_FILE_MONITOR_EVENT_CHANGED) {
170     log->priv->has_new_lines = TRUE;
171     g_signal_emit (log, signals[LOG_CHANGED], 0, NULL);
172   }
173   /* TODO: handle the case where the log is deleted? */
174 }
175 
176 static void
setup_file_monitor(LogviewLog * log)177 setup_file_monitor (LogviewLog *log)
178 {
179   GError *err = NULL;
180 
181   log->priv->mon = g_file_monitor (log->priv->file,
182                                    0, NULL, &err);
183   if (err) {
184     /* it'd be strange to get this error at this point but whatever */
185     g_warning ("Impossible to monitor the log file: the changes won't be notified");
186     g_error_free (err);
187     return;
188   }
189 
190   /* set the rate to 1sec, as I guess it's not unusual to have more than
191    * one line written consequently or in a short time, being a log file.
192    */
193   g_file_monitor_set_rate_limit (log->priv->mon, 1000);
194   g_signal_connect (log->priv->mon, "changed",
195                     G_CALLBACK (monitor_changed_cb), log);
196 }
197 
198 static GSList *
add_new_days_to_cache(LogviewLog * log,const char ** new_lines,guint lines_offset)199 add_new_days_to_cache (LogviewLog *log, const char **new_lines, guint lines_offset)
200 {
201   GSList *new_days, *l, *last_cached;
202   int res;
203   Day *day, *last;
204 
205   new_days = log_read_dates (new_lines, log->priv->file_time);
206 
207   /* the days are stored in chronological order, so we compare the last cached
208    * one with the new we got.
209    */
210   last_cached = g_slist_last (log->priv->days);
211 
212   if (!last_cached) {
213     /* this means the day list is empty (i.e. we're on the first read */
214     log->priv->days = logview_utils_day_list_copy (new_days);
215     return new_days;
216   }
217 
218   for (l = new_days; l; l = l->next) {
219     res = days_compare (l->data, last_cached->data);
220     day = l->data;
221 
222     if (res > 0) {
223       /* this day in the list is newer than the last one, append to
224        * the cache.
225        */
226       day->first_line += lines_offset;
227       day->last_line += lines_offset;
228       log->priv->days = g_slist_append (log->priv->days, logview_utils_day_copy (day));
229     } else if (res == 0) {
230       last = last_cached->data;
231 
232       /* update the lines number */
233       last->last_line += day->last_line;
234     }
235   }
236 
237   return new_days;
238 }
239 
240 static gboolean
new_lines_job_done(gpointer data)241 new_lines_job_done (gpointer data)
242 {
243   NewLinesJob *job = data;
244 
245   if (job->err) {
246     job->callback (job->log, NULL, NULL, job->err, job->user_data);
247     g_error_free (job->err);
248   } else {
249     job->callback (job->log, job->lines, job->new_days, job->err, job->user_data);
250   }
251 
252   g_clear_object (&job->cancellable);
253 
254   g_slist_free_full (job->new_days, (GDestroyNotify) logview_utils_day_free);
255 
256   /* drop the reference we acquired before */
257   g_object_unref (job->log);
258 
259   g_slice_free (NewLinesJob, job);
260 
261   return FALSE;
262 }
263 
264 static gboolean
do_read_new_lines(GIOSchedulerJob * io_job,GCancellable * cancellable,gpointer user_data)265 do_read_new_lines (GIOSchedulerJob *io_job,
266                    GCancellable *cancellable,
267                    gpointer user_data)
268 {
269   /* this runs in a separate thread */
270   NewLinesJob *job = user_data;
271   LogviewLog *log = job->log;
272   char *line;
273   GError *err = NULL;
274   GPtrArray *lines;
275 
276   g_assert (LOGVIEW_IS_LOG (log));
277   g_assert (log->priv->stream != NULL);
278 
279   if (!log->priv->lines) {
280     log->priv->lines = g_ptr_array_new ();
281     g_ptr_array_add (log->priv->lines, NULL);
282   }
283 
284   lines = log->priv->lines;
285 
286   /* remove the NULL-terminator */
287   g_ptr_array_remove_index (lines, lines->len - 1);
288 
289   while ((line = g_data_input_stream_read_line (log->priv->stream, NULL,
290                                                 job->cancellable, &err)) != NULL)
291   {
292     g_ptr_array_add (lines, (gpointer) line);
293   }
294 
295   /* NULL-terminate the array again */
296   g_ptr_array_add (lines, NULL);
297 
298   if (err) {
299     job->err = err;
300     goto out;
301   }
302 
303   log->priv->has_new_lines = FALSE;
304 
305   /* we'll return only the new lines in the callback */
306   job->lines = (const char **) lines->pdata + log->priv->lines_no;
307 
308   /* save the new number of days and lines */
309   job->new_days = add_new_days_to_cache (log, job->lines, log->priv->lines_no);
310   log->priv->lines_no = (lines->len - 1);
311 
312 out:
313   g_io_scheduler_job_send_to_mainloop_async (io_job,
314                                              new_lines_job_done,
315                                              job, NULL);
316   return FALSE;
317 }
318 
319 static gboolean
log_load_done(gpointer user_data)320 log_load_done (gpointer user_data)
321 {
322   LoadJob *job = user_data;
323 
324   if (job->err) {
325     /* the callback will have NULL as log, and the error set */
326     g_object_unref (job->log);
327     job->callback (NULL, job->err, job->user_data);
328     g_error_free (job->err);
329   } else {
330     job->callback (job->log, NULL, job->user_data);
331     setup_file_monitor (job->log);
332   }
333 
334   g_slice_free (LoadJob, job);
335 
336   return FALSE;
337 }
338 
339 #ifdef HAVE_ZLIB
340 
341 /* GZip functions adapted for GIO from mate-vfs/gzip-method.c */
342 
343 #define Z_BUFSIZE 16384
344 
345 #define GZIP_HEADER_SIZE 10
346 #define GZIP_MAGIC_1 0x1f
347 #define GZIP_MAGIC_2 0x8b
348 #define GZIP_FLAG_ASCII        0x01 /* bit 0 set: file probably ascii text */
349 #define GZIP_FLAG_HEAD_CRC     0x02 /* bit 1 set: header CRC present */
350 #define GZIP_FLAG_EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
351 #define GZIP_FLAG_ORIG_NAME    0x08 /* bit 3 set: original file name present */
352 #define GZIP_FLAG_COMMENT      0x10 /* bit 4 set: file comment present */
353 #define GZIP_FLAG_RESERVED     0xE0 /* bits 5..7: reserved */
354 
355 static gboolean
skip_string(GInputStream * is)356 skip_string (GInputStream *is)
357 {
358 	guchar c;
359 	gssize bytes_read;
360 
361 	do {
362 		bytes_read = g_input_stream_read (is, &c, 1, NULL, NULL);
363 
364 		if (bytes_read != 1) {
365 			return FALSE;
366     }
367 	} while (c != 0);
368 
369 	return TRUE;
370 }
371 
372 static gboolean
read_gzip_header(GInputStream * is,time_t * modification_time)373 read_gzip_header (GInputStream *is,
374                   time_t *modification_time)
375 {
376 	guchar buffer[GZIP_HEADER_SIZE];
377 	gssize bytes, to_skip;
378 	guint mode;
379 	guint flags;
380 
381 	bytes = g_input_stream_read (is, buffer, GZIP_HEADER_SIZE,
382                                NULL, NULL);
383   if (bytes == -1) {
384     return FALSE;
385   }
386 
387 	if (bytes != GZIP_HEADER_SIZE)
388 		return FALSE;
389 
390 	if (buffer[0] != GZIP_MAGIC_1 || buffer[1] != GZIP_MAGIC_2)
391 		return FALSE;
392 
393 	mode = buffer[2];
394 	if (mode != 8) /* Mode: deflate */
395 		return FALSE;
396 
397 	flags = buffer[3];
398 
399 	if (flags & GZIP_FLAG_RESERVED)
400 		return FALSE;
401 
402 	if (flags & GZIP_FLAG_EXTRA_FIELD) {
403 		guchar tmp[2];
404 
405     bytes = g_input_stream_read (is, tmp, 2, NULL, NULL);
406 
407     if (bytes != 2) {
408 			return FALSE;
409     }
410 
411     to_skip = tmp[0] | (tmp[0] << 8);
412     bytes = g_input_stream_skip (is, to_skip, NULL, NULL);
413     if (bytes != to_skip) {
414       return FALSE;
415     }
416 	}
417 
418 	if (flags & GZIP_FLAG_ORIG_NAME) {
419     if (!skip_string (is)) {
420       return FALSE;
421     }
422   }
423 
424 	if (flags & GZIP_FLAG_COMMENT) {
425     if (!skip_string (is)) {
426       return FALSE;
427     }
428   }
429 
430 	if (flags & GZIP_FLAG_HEAD_CRC) {
431     bytes = g_input_stream_skip (is, 2, NULL, NULL);
432 		if (bytes != 2) {
433       return FALSE;
434     }
435   }
436 
437   *modification_time = (buffer[4] | (buffer[5] << 8)
438                         | (buffer[6] << 16) | (buffer[7] << 24));
439 
440 	return TRUE;
441 }
442 
443 static GZHandle *
gz_handle_new(GFile * file,GInputStream * parent_stream)444 gz_handle_new (GFile *file,
445                GInputStream *parent_stream)
446 {
447   GZHandle *ret;
448 
449   ret = g_new (GZHandle, 1);
450   ret->parent_str = g_object_ref (parent_stream);
451   ret->file = g_object_ref (file);
452   ret->buffer = NULL;
453 
454   return ret;
455 }
456 
457 static gboolean
gz_handle_init(GZHandle * gz)458 gz_handle_init (GZHandle *gz)
459 {
460   gz->zstream.zalloc = NULL;
461   gz->zstream.zfree = NULL;
462   gz->zstream.opaque = NULL;
463 
464   g_free (gz->buffer);
465   gz->buffer = g_malloc (Z_BUFSIZE);
466   gz->zstream.next_in = gz->buffer;
467   gz->zstream.avail_in = 0;
468 
469   if (inflateInit2 (&gz->zstream, -MAX_WBITS) != Z_OK) {
470     return FALSE;
471   }
472 
473   gz->last_z_result = Z_OK;
474   gz->last_str_result = TRUE;
475 
476   return TRUE;
477 }
478 
479 static void
gz_handle_free(GZHandle * gz)480 gz_handle_free (GZHandle *gz)
481 {
482   g_object_unref (gz->parent_str);
483   g_object_unref (gz->file);
484   g_free (gz->buffer);
485   g_free (gz);
486 }
487 
488 static gboolean
fill_buffer(GZHandle * gz,gsize num_bytes)489 fill_buffer (GZHandle *gz,
490              gsize num_bytes)
491 {
492   gsize count;
493 
494   z_stream * zstream = &gz->zstream;
495 
496   if (zstream->avail_in > 0) {
497     return TRUE;
498   }
499 
500   count = g_input_stream_read (gz->parent_str,
501                                gz->buffer,
502                                Z_BUFSIZE,
503                                NULL, NULL);
504   if (count == -1) {
505     if (zstream->avail_out == num_bytes) {
506       return FALSE;
507     }
508     gz->last_str_result = FALSE;
509   } else {
510     zstream->next_in = gz->buffer;
511     zstream->avail_in = count;
512   }
513 
514   return TRUE;
515 }
516 
517 static gboolean
result_from_z_result(int z_result)518 result_from_z_result (int z_result)
519 {
520   switch (z_result) {
521     case Z_OK:
522     case Z_STREAM_END:
523         return TRUE;
524     case Z_DATA_ERROR:
525       return FALSE;
526     default:
527       return FALSE;
528   }
529 }
530 
531 static gboolean
gz_handle_read(GZHandle * gz,guchar * buffer,gsize num_bytes,gsize * bytes_read)532 gz_handle_read (GZHandle *gz,
533                 guchar *buffer,
534                 gsize num_bytes,
535                 gsize * bytes_read)
536 {
537   z_stream *zstream;
538   gboolean res;
539   int z_result;
540 
541   *bytes_read = 0;
542   zstream = &gz->zstream;
543 
544   if (gz->last_z_result != Z_OK) {
545     if (gz->last_z_result == Z_STREAM_END) {
546       *bytes_read = 0;
547       return TRUE;
548     } else {
549       return result_from_z_result (gz->last_z_result);
550     }
551   } else if (gz->last_str_result == FALSE) {
552     return FALSE;
553   }
554 
555   zstream->next_out = buffer;
556   zstream->avail_out = num_bytes;
557 
558   while (zstream->avail_out != 0) {
559     res = fill_buffer (gz, num_bytes);
560 
561     if (!res) {
562       return res;
563     }
564 
565     z_result = inflate (zstream, Z_NO_FLUSH);
566     if (z_result == Z_STREAM_END) {
567       gz->last_z_result = z_result;
568       break;
569     } else if (z_result != Z_OK) {
570       gz->last_z_result = z_result;
571     }
572 
573     if (gz->last_z_result != Z_OK && zstream->avail_out == num_bytes) {
574       return result_from_z_result (gz->last_z_result);
575     }
576   }
577 
578   *bytes_read = num_bytes - zstream->avail_out;
579 
580   return TRUE;
581 }
582 
583 static GError *
create_zlib_error(void)584 create_zlib_error (void)
585 {
586   GError *err;
587 
588   err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_ZLIB,
589                              _("Error while uncompressing the GZipped log. The file "
590                                "might be corrupt."));
591   return err;
592 }
593 
594 #endif /* HAVE_ZLIB */
595 
596 static gboolean
log_load(GIOSchedulerJob * io_job,GCancellable * cancellable,gpointer user_data)597 log_load (GIOSchedulerJob *io_job,
598           GCancellable *cancellable,
599           gpointer user_data)
600 {
601   /* this runs in a separate i/o thread */
602   LoadJob *job = user_data;
603   LogviewLog *log = job->log;
604   GFile *f = log->priv->file;
605   GFileInfo *info;
606   GInputStream *is;
607   const char *peeked_buffer;
608   const char * parse_data[2];
609   GSList *days;
610   const char *content_type;
611   GFileType type;
612   GError *err = NULL;
613   gboolean is_archive, can_read;
614 
615   info = g_file_query_info (f,
616                             G_FILE_ATTRIBUTE_ACCESS_CAN_READ ","
617                             G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
618                             G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
619                             G_FILE_ATTRIBUTE_STANDARD_TYPE ","
620                             G_FILE_ATTRIBUTE_STANDARD_SIZE ","
621                             G_FILE_ATTRIBUTE_TIME_MODIFIED ",",
622                             0, NULL, &err);
623   if (err) {
624     if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
625       /* TODO: PolicyKit integration */
626     }
627     goto out;
628   }
629 
630   can_read = g_file_info_get_attribute_boolean (info,
631                                                 G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
632   if (!can_read) {
633     /* TODO: PolicyKit integration */
634     err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_PERMISSION_DENIED,
635                                _("You don't have enough permissions to read the file."));
636     g_object_unref (info);
637 
638     goto out;
639   }
640 
641   type = g_file_info_get_file_type (info);
642   content_type = g_file_info_get_content_type (info);
643 
644   is_archive = g_content_type_equals (content_type, "application/x-gzip");
645 
646   if (type != (G_FILE_TYPE_REGULAR || G_FILE_TYPE_SYMBOLIC_LINK) ||
647       (!g_content_type_is_a (content_type, "text/plain") && !is_archive))
648   {
649     err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_A_LOG,
650                                _("The file is not a regular file or is not a text file."));
651     g_object_unref (info);
652 
653     goto out;
654   }
655 
656   log->priv->file_size = g_file_info_get_size (info);
657   #if GLIB_CHECK_VERSION(2,61,2)
658     GDateTime *file_dt;
659     gint64 t;
660     file_dt = g_file_info_get_modification_date_time (info);
661     t = g_date_time_to_unix (file_dt);
662     g_date_time_unref (file_dt);
663     log->priv->file_time = t;
664   #else
665     GTimeVal time_val;
666     g_file_info_get_modification_time (info, &time_val);
667     log->priv->file_time = time_val.tv_sec;
668   #endif
669 
670   log->priv->display_name = g_strdup (g_file_info_get_display_name (info));
671 
672   g_object_unref (info);
673 
674   /* initialize the stream */
675   is = G_INPUT_STREAM (g_file_read (f, NULL, &err));
676 
677   if (err) {
678     if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
679       /* TODO: PolicyKit integration */
680     }
681 
682     goto out;
683   }
684 
685   if (is_archive) {
686 #ifdef HAVE_ZLIB
687     GZHandle *gz;
688     gboolean res;
689     guchar * buffer;
690     gsize bytes_read;
691     GInputStream *real_is;
692     time_t mtime; /* seconds */
693 
694     /* this also skips the header from |is| */
695     res = read_gzip_header (is, &mtime);
696 
697     if (!res) {
698       g_object_unref (is);
699 
700       err = create_zlib_error ();
701       goto out;
702     }
703 
704     log->priv->file_time = mtime;
705 
706     gz = gz_handle_new (f, is);
707     res = gz_handle_init (gz);
708 
709     if (!res) {
710       g_object_unref (is);
711       gz_handle_free (gz);
712 
713       err = create_zlib_error ();
714       goto out;
715     }
716 
717     real_is = g_memory_input_stream_new ();
718 
719     do {
720       buffer = g_malloc (1024);
721       res = gz_handle_read (gz, buffer, 1024, &bytes_read);
722       g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (real_is),
723                                       buffer, bytes_read, g_free);
724     } while (res == TRUE && bytes_read > 0);
725 
726     if (!res) {
727       gz_handle_free (gz);
728       g_object_unref (real_is);
729       g_object_unref (is);
730 
731       err = create_zlib_error ();
732       goto out;
733     }
734 
735     g_object_unref (is);
736     is = real_is;
737 
738     gz_handle_free (gz);
739 #else /* HAVE_ZLIB */
740     g_object_unref (is);
741 
742     err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_SUPPORTED,
743                                _("This version of System Log does not support GZipped logs."));
744     goto out;
745 #endif /* HAVE_ZLIB */
746   }
747 
748   log->priv->stream = g_data_input_stream_new (is);
749 
750   /* sniff into the stream for a timestamped line */
751   g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (log->priv->stream),
752                                 (gssize) g_buffered_input_stream_get_buffer_size (G_BUFFERED_INPUT_STREAM (log->priv->stream)),
753                                 NULL, &err);
754   if (err == NULL) {
755     peeked_buffer = g_buffered_input_stream_peek_buffer
756         (G_BUFFERED_INPUT_STREAM (log->priv->stream), NULL);
757     parse_data[0] = peeked_buffer;
758     parse_data[1] = NULL;
759 
760     if ((days = log_read_dates (parse_data, time (NULL))) != NULL) {
761       log->priv->has_days = TRUE;
762       g_slist_free_full (days, (GDestroyNotify) logview_utils_day_free);
763     } else {
764       log->priv->has_days = FALSE;
765     }
766   } else {
767     log->priv->has_days = FALSE;
768     g_clear_error (&err);
769   }
770 
771   g_object_unref (is);
772 
773 out:
774   if (err) {
775     job->err = err;
776   }
777 
778   g_io_scheduler_job_send_to_mainloop_async (io_job,
779                                              log_load_done,
780                                              job, NULL);
781   return FALSE;
782 }
783 
784 static void
log_setup_load(LogviewLog * log,LogviewCreateCallback callback,gpointer user_data)785 log_setup_load (LogviewLog *log, LogviewCreateCallback callback,
786                 gpointer user_data)
787 {
788   LoadJob *job;
789 
790   job = g_slice_new0 (LoadJob);
791   job->callback = callback;
792   job->user_data = user_data;
793   job->log = log;
794   job->err = NULL;
795 
796   /* push the loading job into another thread */
797   g_io_scheduler_push_job (log_load,
798                            job,
799                            NULL, 0, NULL);
800 }
801 
802 /* public methods */
803 
804 void
logview_log_read_new_lines(LogviewLog * log,GCancellable * cancellable,LogviewNewLinesCallback callback,gpointer user_data)805 logview_log_read_new_lines (LogviewLog *log,
806                             GCancellable *cancellable,
807                             LogviewNewLinesCallback callback,
808                             gpointer user_data)
809 {
810   NewLinesJob *job;
811 
812   /* initialize the job struct with sensible values */
813   job = g_slice_new0 (NewLinesJob);
814   job->callback = callback;
815   job->user_data = user_data;
816   job->cancellable = (cancellable != NULL) ? g_object_ref (cancellable) : NULL;
817   job->log = g_object_ref (log);
818   job->err = NULL;
819   job->lines = NULL;
820   job->new_days = NULL;
821 
822   /* push the fetching job into another thread */
823   g_io_scheduler_push_job (do_read_new_lines,
824                            job,
825                            NULL, 0,
826                            job->cancellable);
827 }
828 
829 void
logview_log_create(const char * filename,LogviewCreateCallback callback,gpointer user_data)830 logview_log_create (const char *filename, LogviewCreateCallback callback,
831                     gpointer user_data)
832 {
833   LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
834 
835   log->priv->file = g_file_new_for_path (filename);
836 
837   log_setup_load (log, callback, user_data);
838 }
839 
840 void
logview_log_create_from_gfile(GFile * file,LogviewCreateCallback callback,gpointer user_data)841 logview_log_create_from_gfile (GFile *file, LogviewCreateCallback callback,
842                                gpointer user_data)
843 {
844   LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
845 
846   log->priv->file = g_object_ref (file);
847 
848   log_setup_load (log, callback, user_data);
849 }
850 
851 const char *
logview_log_get_display_name(LogviewLog * log)852 logview_log_get_display_name (LogviewLog *log)
853 {
854   g_assert (LOGVIEW_IS_LOG (log));
855 
856   return log->priv->display_name;
857 }
858 
859 time_t
logview_log_get_timestamp(LogviewLog * log)860 logview_log_get_timestamp (LogviewLog *log)
861 {
862   g_assert (LOGVIEW_IS_LOG (log));
863 
864   return log->priv->file_time;
865 }
866 
867 goffset
logview_log_get_file_size(LogviewLog * log)868 logview_log_get_file_size (LogviewLog *log)
869 {
870   g_assert (LOGVIEW_IS_LOG (log));
871 
872   return log->priv->file_size;
873 }
874 
875 guint
logview_log_get_cached_lines_number(LogviewLog * log)876 logview_log_get_cached_lines_number (LogviewLog *log)
877 {
878   g_assert (LOGVIEW_IS_LOG (log));
879 
880   return log->priv->lines_no;
881 }
882 
883 const char **
logview_log_get_cached_lines(LogviewLog * log)884 logview_log_get_cached_lines (LogviewLog *log)
885 {
886   const char ** lines = NULL;
887 
888   g_assert (LOGVIEW_IS_LOG (log));
889 
890   if (log->priv->lines) {
891     lines = (const char **) log->priv->lines->pdata;
892   }
893 
894   return lines;
895 }
896 
897 GSList *
logview_log_get_days_for_cached_lines(LogviewLog * log)898 logview_log_get_days_for_cached_lines (LogviewLog *log)
899 {
900   g_assert (LOGVIEW_IS_LOG (log));
901 
902   return log->priv->days;
903 }
904 
905 gboolean
logview_log_has_new_lines(LogviewLog * log)906 logview_log_has_new_lines (LogviewLog *log)
907 {
908   g_assert (LOGVIEW_IS_LOG (log));
909 
910   return log->priv->has_new_lines;
911 }
912 
913 char *
logview_log_get_uri(LogviewLog * log)914 logview_log_get_uri (LogviewLog *log)
915 {
916   g_assert (LOGVIEW_IS_LOG (log));
917 
918   return g_file_get_uri (log->priv->file);
919 }
920 
921 GFile *
logview_log_get_gfile(LogviewLog * log)922 logview_log_get_gfile (LogviewLog *log)
923 {
924   g_assert (LOGVIEW_IS_LOG (log));
925 
926   return g_object_ref (log->priv->file);
927 }
928 
929 gboolean
logview_log_get_has_days(LogviewLog * log)930 logview_log_get_has_days (LogviewLog *log)
931 {
932   g_assert (LOGVIEW_IS_LOG (log));
933 
934   return log->priv->has_days;
935 }
936 
937