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