1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* logview-log.c - object representation of a logfile
4 * Copyright (C) 1998 Cesar Miquel <miquel@df.uba.ar>
5 * Copyright (C) 2008 Cosimo Cecchi <cosimoc@gnome.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
26 #include <glib/gi18n.h>
33 #include "logview-log.h"
34 #include "logview-utils.h"
36 G_DEFINE_TYPE (LogviewLog
, logview_log
, G_TYPE_OBJECT
);
38 #define GET_PRIVATE(o) \
39 (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_LOG, LogviewLogPrivate))
46 static guint signals
[LAST_SIGNAL
] = { 0 };
48 struct _LogviewLogPrivate
{
49 /* file and monitor */
53 /* stats about the file */
59 /* lines and relative days */
64 /* stream poiting to the log */
65 GDataInputStream
*stream
;
66 gboolean has_new_lines
;
72 LogviewCreateCallback callback
;
81 LogviewNewLinesCallback callback
;
86 GInputStream
*parent_str
;
90 gboolean last_str_result
;
96 do_finalize (GObject
*obj
)
98 LogviewLog
*log
= LOGVIEW_LOG (obj
);
101 if (log
->priv
->stream
) {
102 g_object_unref (log
->priv
->stream
);
103 log
->priv
->stream
= NULL
;
106 if (log
->priv
->file
) {
107 g_object_unref (log
->priv
->file
);
108 log
->priv
->file
= NULL
;
111 if (log
->priv
->mon
) {
112 g_object_unref (log
->priv
->mon
);
113 log
->priv
->mon
= NULL
;
116 if (log
->priv
->days
) {
117 g_slist_foreach (log
->priv
->days
,
118 (GFunc
) logview_utils_day_free
, NULL
);
119 g_slist_free (log
->priv
->days
);
120 log
->priv
->days
= NULL
;
123 if (log
->priv
->lines
) {
124 lines
= (char **) g_ptr_array_free (log
->priv
->lines
, FALSE
);
126 log
->priv
->lines
= NULL
;
129 G_OBJECT_CLASS (logview_log_parent_class
)->finalize (obj
);
133 logview_log_class_init (LogviewLogClass
*klass
)
135 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
137 object_class
->finalize
= do_finalize
;
139 signals
[LOG_CHANGED
] = g_signal_new ("log-changed",
140 G_OBJECT_CLASS_TYPE (object_class
),
142 G_STRUCT_OFFSET (LogviewLogClass
, log_changed
),
144 g_cclosure_marshal_VOID__VOID
,
147 g_type_class_add_private (klass
, sizeof (LogviewLogPrivate
));
151 logview_log_init (LogviewLog
*self
)
153 self
->priv
= GET_PRIVATE (self
);
155 self
->priv
->lines
= NULL
;
156 self
->priv
->lines_no
= 0;
157 self
->priv
->days
= NULL
;
158 self
->priv
->file
= NULL
;
159 self
->priv
->mon
= NULL
;
160 self
->priv
->has_new_lines
= FALSE
;
161 self
->priv
->has_days
= FALSE
;
165 monitor_changed_cb (GFileMonitor
*monitor
,
168 GFileMonitorEvent event
,
171 LogviewLog
*log
= user_data
;
173 if (event
== G_FILE_MONITOR_EVENT_CHANGED
) {
174 log
->priv
->has_new_lines
= TRUE
;
175 g_signal_emit (log
, signals
[LOG_CHANGED
], 0, NULL
);
177 /* TODO: handle the case where the log is deleted? */
181 setup_file_monitor (LogviewLog
*log
)
185 log
->priv
->mon
= g_file_monitor (log
->priv
->file
,
188 /* it'd be strange to get this error at this point but whatever */
189 g_warning ("Impossible to monitor the log file: the changes won't be notified");
194 /* set the rate to 1sec, as I guess it's not unusual to have more than
195 * one line written consequently or in a short time, being a log file.
197 g_file_monitor_set_rate_limit (log
->priv
->mon
, 1000);
198 g_signal_connect (log
->priv
->mon
, "changed",
199 G_CALLBACK (monitor_changed_cb
), log
);
203 add_new_days_to_cache (LogviewLog
*log
, const char **new_lines
, guint lines_offset
)
205 GSList
*new_days
, *l
, *last_cached
;
209 new_days
= log_read_dates (new_lines
, log
->priv
->file_time
);
211 /* the days are stored in chronological order, so we compare the last cached
212 * one with the new we got.
214 last_cached
= g_slist_last (log
->priv
->days
);
217 /* this means the day list is empty (i.e. we're on the first read */
218 log
->priv
->days
= logview_utils_day_list_copy (new_days
);
222 for (l
= new_days
; l
; l
= l
->next
) {
223 res
= days_compare (l
->data
, last_cached
->data
);
227 /* this day in the list is newer than the last one, append to
230 day
->first_line
+= lines_offset
;
231 day
->last_line
+= lines_offset
;
232 log
->priv
->days
= g_slist_append (log
->priv
->days
, logview_utils_day_copy (day
));
233 } else if (res
== 0) {
234 last
= last_cached
->data
;
236 /* update the lines number */
237 last
->last_line
+= day
->last_line
;
245 new_lines_job_done (gpointer data
)
247 NewLinesJob
*job
= data
;
250 job
->callback (job
->log
, NULL
, NULL
, job
->err
, job
->user_data
);
251 g_error_free (job
->err
);
253 job
->callback (job
->log
, job
->lines
, job
->new_days
, job
->err
, job
->user_data
);
256 g_slist_foreach (job
->new_days
, (GFunc
) logview_utils_day_free
, NULL
);
257 g_slist_free (job
->new_days
);
259 /* drop the reference we acquired before */
260 g_object_unref (job
->log
);
262 g_slice_free (NewLinesJob
, job
);
268 do_read_new_lines (GIOSchedulerJob
*io_job
,
269 GCancellable
*cancellable
,
272 /* this runs in a separate thread */
273 NewLinesJob
*job
= user_data
;
274 LogviewLog
*log
= job
->log
;
279 g_assert (LOGVIEW_IS_LOG (log
));
280 g_assert (log
->priv
->stream
!= NULL
);
282 if (!log
->priv
->lines
) {
283 log
->priv
->lines
= g_ptr_array_new ();
284 g_ptr_array_add (log
->priv
->lines
, NULL
);
287 lines
= log
->priv
->lines
;
289 /* remove the NULL-terminator */
290 g_ptr_array_remove_index (lines
, lines
->len
- 1);
292 while ((line
= g_data_input_stream_read_line (log
->priv
->stream
, NULL
,
293 NULL
, &err
)) != NULL
)
295 g_ptr_array_add (lines
, (gpointer
) line
);
303 /* NULL-terminate the array again */
304 g_ptr_array_add (lines
, NULL
);
306 log
->priv
->has_new_lines
= FALSE
;
308 /* we'll return only the new lines in the callback */
309 line
= g_ptr_array_index (lines
, log
->priv
->lines_no
);
310 job
->lines
= (const char **) lines
->pdata
+ log
->priv
->lines_no
;
312 /* save the new number of days and lines */
313 job
->new_days
= add_new_days_to_cache (log
, job
->lines
, log
->priv
->lines_no
);
314 log
->priv
->lines_no
= (lines
->len
- 1);
317 g_io_scheduler_job_send_to_mainloop_async (io_job
,
324 log_load_done (gpointer user_data
)
326 LoadJob
*job
= user_data
;
329 /* the callback will have NULL as log, and the error set */
330 g_object_unref (job
->log
);
331 job
->callback (NULL
, job
->err
, job
->user_data
);
332 g_error_free (job
->err
);
334 job
->callback (job
->log
, NULL
, job
->user_data
);
335 setup_file_monitor (job
->log
);
338 g_slice_free (LoadJob
, job
);
345 /* GZip functions adapted for GIO from gnome-vfs/gzip-method.c */
347 #define Z_BUFSIZE 16384
349 #define GZIP_HEADER_SIZE 10
350 #define GZIP_MAGIC_1 0x1f
351 #define GZIP_MAGIC_2 0x8b
352 #define GZIP_FLAG_ASCII 0x01 /* bit 0 set: file probably ascii text */
353 #define GZIP_FLAG_HEAD_CRC 0x02 /* bit 1 set: header CRC present */
354 #define GZIP_FLAG_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
355 #define GZIP_FLAG_ORIG_NAME 0x08 /* bit 3 set: original file name present */
356 #define GZIP_FLAG_COMMENT 0x10 /* bit 4 set: file comment present */
357 #define GZIP_FLAG_RESERVED 0xE0 /* bits 5..7: reserved */
360 skip_string (GInputStream
*is
)
366 bytes_read
= g_input_stream_read (is
, &c
, 1, NULL
, NULL
);
368 if (bytes_read
!= 1) {
377 read_gzip_header (GInputStream
*is
,
378 time_t *modification_time
)
381 guchar buffer
[GZIP_HEADER_SIZE
];
382 gssize bytes
, to_skip
;
386 bytes
= g_input_stream_read (is
, buffer
, GZIP_HEADER_SIZE
,
392 if (bytes
!= GZIP_HEADER_SIZE
)
395 if (buffer
[0] != GZIP_MAGIC_1
|| buffer
[1] != GZIP_MAGIC_2
)
399 if (mode
!= 8) /* Mode: deflate */
404 if (flags
& GZIP_FLAG_RESERVED
)
407 if (flags
& GZIP_FLAG_EXTRA_FIELD
) {
410 bytes
= g_input_stream_read (is
, tmp
, 2, NULL
, NULL
);
416 to_skip
= tmp
[0] | (tmp
[0] << 8);
417 bytes
= g_input_stream_skip (is
, to_skip
, NULL
, NULL
);
418 if (bytes
!= to_skip
) {
423 if (flags
& GZIP_FLAG_ORIG_NAME
) {
424 if (!skip_string (is
)) {
429 if (flags
& GZIP_FLAG_COMMENT
) {
430 if (!skip_string (is
)) {
435 if (flags
& GZIP_FLAG_HEAD_CRC
) {
436 bytes
= g_input_stream_skip (is
, 2, NULL
, NULL
);
442 *modification_time
= (buffer
[4] | (buffer
[5] << 8)
443 | (buffer
[6] << 16) | (buffer
[7] << 24));
449 gz_handle_new (GFile
*file
,
450 GInputStream
*parent_stream
)
454 ret
= g_new (GZHandle
, 1);
455 ret
->parent_str
= g_object_ref (parent_stream
);
456 ret
->file
= g_object_ref (file
);
463 gz_handle_init (GZHandle
*gz
)
465 gz
->zstream
.zalloc
= NULL
;
466 gz
->zstream
.zfree
= NULL
;
467 gz
->zstream
.opaque
= NULL
;
470 gz
->buffer
= g_malloc (Z_BUFSIZE
);
471 gz
->zstream
.next_in
= gz
->buffer
;
472 gz
->zstream
.avail_in
= 0;
474 if (inflateInit2 (&gz
->zstream
, -MAX_WBITS
) != Z_OK
) {
478 gz
->last_z_result
= Z_OK
;
479 gz
->last_str_result
= TRUE
;
485 gz_handle_free (GZHandle
*gz
)
487 g_object_unref (gz
->parent_str
);
488 g_object_unref (gz
->file
);
494 fill_buffer (GZHandle
*gz
,
500 z_stream
* zstream
= &gz
->zstream
;
502 if (zstream
->avail_in
> 0) {
506 count
= g_input_stream_read (gz
->parent_str
,
511 if (zstream
->avail_out
== num_bytes
) {
514 gz
->last_str_result
= FALSE
;
516 zstream
->next_in
= gz
->buffer
;
517 zstream
->avail_in
= count
;
524 result_from_z_result (int z_result
)
538 gz_handle_read (GZHandle
*gz
,
548 zstream
= &gz
->zstream
;
550 if (gz
->last_z_result
!= Z_OK
) {
551 if (gz
->last_z_result
== Z_STREAM_END
) {
555 return result_from_z_result (gz
->last_z_result
);
557 } else if (gz
->last_str_result
== FALSE
) {
561 zstream
->next_out
= buffer
;
562 zstream
->avail_out
= num_bytes
;
564 while (zstream
->avail_out
!= 0) {
565 res
= fill_buffer (gz
, num_bytes
);
571 z_result
= inflate (zstream
, Z_NO_FLUSH
);
572 if (z_result
== Z_STREAM_END
) {
573 gz
->last_z_result
= z_result
;
575 } else if (z_result
!= Z_OK
) {
576 gz
->last_z_result
= z_result
;
579 if (gz
->last_z_result
!= Z_OK
&& zstream
->avail_out
== num_bytes
) {
580 return result_from_z_result (gz
->last_z_result
);
584 *bytes_read
= num_bytes
- zstream
->avail_out
;
590 create_zlib_error (void)
594 err
= g_error_new_literal (LOGVIEW_ERROR_QUARK
, LOGVIEW_ERROR_ZLIB
,
595 _("Error while uncompressing the GZipped log. The file "
596 "might be corrupt."));
600 #endif /* HAVE_ZLIB */
603 log_load (GIOSchedulerJob
*io_job
,
604 GCancellable
*cancellable
,
607 /* this runs in a separate i/o thread */
608 LoadJob
*job
= user_data
;
609 LogviewLog
*log
= job
->log
;
610 GFile
*f
= log
->priv
->file
;
613 const char *peeked_buffer
;
614 const char * parse_data
[2];
616 const char *content_type
;
620 gboolean is_archive
, can_read
;
622 info
= g_file_query_info (f
,
623 G_FILE_ATTRIBUTE_ACCESS_CAN_READ
","
624 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
","
625 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
","
626 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
627 G_FILE_ATTRIBUTE_STANDARD_SIZE
","
628 G_FILE_ATTRIBUTE_TIME_MODIFIED
",",
631 if (err
->code
== G_IO_ERROR_PERMISSION_DENIED
) {
632 /* TODO: PolicyKit integration */
637 can_read
= g_file_info_get_attribute_boolean (info
,
638 G_FILE_ATTRIBUTE_ACCESS_CAN_READ
);
640 /* TODO: PolicyKit integration */
641 err
= g_error_new_literal (LOGVIEW_ERROR_QUARK
, LOGVIEW_ERROR_PERMISSION_DENIED
,
642 _("You don't have enough permissions to read the file."));
643 g_object_unref (info
);
648 type
= g_file_info_get_file_type (info
);
649 content_type
= g_file_info_get_content_type (info
);
651 is_archive
= g_content_type_equals (content_type
, "application/x-gzip");
653 if (type
!= (G_FILE_TYPE_REGULAR
|| G_FILE_TYPE_SYMBOLIC_LINK
) ||
654 (!g_content_type_is_a (content_type
, "text/plain") && !is_archive
))
656 err
= g_error_new_literal (LOGVIEW_ERROR_QUARK
, LOGVIEW_ERROR_NOT_A_LOG
,
657 _("The file is not a regular file or is not a text file."));
658 g_object_unref (info
);
663 log
->priv
->file_size
= g_file_info_get_size (info
);
664 g_file_info_get_modification_time (info
, &timeval
);
665 log
->priv
->file_time
= timeval
.tv_sec
;
666 log
->priv
->display_name
= g_strdup (g_file_info_get_display_name (info
));
668 g_object_unref (info
);
670 /* initialize the stream */
671 is
= G_INPUT_STREAM (g_file_read (f
, NULL
, &err
));
674 if (err
->code
== G_IO_ERROR_PERMISSION_DENIED
) {
675 /* TODO: PolicyKit integration */
687 GInputStream
*real_is
;
688 time_t mtime
; /* seconds */
690 /* this also skips the header from |is| */
691 res
= read_gzip_header (is
, &mtime
);
696 err
= create_zlib_error ();
700 log
->priv
->file_time
= mtime
;
702 gz
= gz_handle_new (f
, is
);
703 res
= gz_handle_init (gz
);
709 err
= create_zlib_error ();
713 real_is
= g_memory_input_stream_new ();
716 buffer
= g_malloc (1024);
717 res
= gz_handle_read (gz
, buffer
, 1024, &bytes_read
);
718 g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (real_is
),
719 buffer
, bytes_read
, g_free
);
720 } while (res
== TRUE
&& bytes_read
> 0);
724 g_object_unref (real_is
);
727 err
= create_zlib_error ();
735 #else /* HAVE_ZLIB */
738 err
= g_error_new_literal (LOGVIEW_ERROR_QUARK
, LOGVIEW_ERROR_NOT_SUPPORTED
,
739 _("This version of System Log does not support GZipped logs."));
741 #endif /* HAVE_ZLIB */
744 log
->priv
->stream
= g_data_input_stream_new (is
);
746 /* sniff into the stream for a timestamped line */
747 g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (log
->priv
->stream
),
748 (gssize
) g_buffered_input_stream_get_buffer_size (G_BUFFERED_INPUT_STREAM (log
->priv
->stream
)),
751 peeked_buffer
= g_buffered_input_stream_peek_buffer
752 (G_BUFFERED_INPUT_STREAM (log
->priv
->stream
), NULL
);
753 parse_data
[0] = peeked_buffer
;
754 parse_data
[1] = NULL
;
756 if ((days
= log_read_dates (parse_data
, time (NULL
))) != NULL
) {
757 log
->priv
->has_days
= TRUE
;
758 g_slist_foreach (days
, (GFunc
) logview_utils_day_free
, NULL
);
761 log
->priv
->has_days
= FALSE
;
764 log
->priv
->has_days
= FALSE
;
765 g_clear_error (&err
);
775 g_io_scheduler_job_send_to_mainloop_async (io_job
,
782 log_setup_load (LogviewLog
*log
, LogviewCreateCallback callback
,
787 job
= g_slice_new0 (LoadJob
);
788 job
->callback
= callback
;
789 job
->user_data
= user_data
;
793 /* push the loading job into another thread */
794 g_io_scheduler_push_job (log_load
,
802 logview_log_read_new_lines (LogviewLog
*log
,
803 LogviewNewLinesCallback callback
,
808 /* initialize the job struct with sensible values */
809 job
= g_slice_new0 (NewLinesJob
);
810 job
->callback
= callback
;
811 job
->user_data
= user_data
;
812 job
->log
= g_object_ref (log
);
815 job
->new_days
= NULL
;
817 /* push the fetching job into another thread */
818 g_io_scheduler_push_job (do_read_new_lines
,
824 logview_log_create (const char *filename
, LogviewCreateCallback callback
,
827 LogviewLog
*log
= g_object_new (LOGVIEW_TYPE_LOG
, NULL
);
829 log
->priv
->file
= g_file_new_for_path (filename
);
831 log_setup_load (log
, callback
, user_data
);
835 logview_log_create_from_gfile (GFile
*file
, LogviewCreateCallback callback
,
838 LogviewLog
*log
= g_object_new (LOGVIEW_TYPE_LOG
, NULL
);
840 log
->priv
->file
= g_object_ref (file
);
842 log_setup_load (log
, callback
, user_data
);
846 logview_log_get_display_name (LogviewLog
*log
)
848 g_assert (LOGVIEW_IS_LOG (log
));
850 return log
->priv
->display_name
;
854 logview_log_get_timestamp (LogviewLog
*log
)
856 g_assert (LOGVIEW_IS_LOG (log
));
858 return log
->priv
->file_time
;
862 logview_log_get_file_size (LogviewLog
*log
)
864 g_assert (LOGVIEW_IS_LOG (log
));
866 return log
->priv
->file_size
;
870 logview_log_get_cached_lines_number (LogviewLog
*log
)
872 g_assert (LOGVIEW_IS_LOG (log
));
874 return log
->priv
->lines_no
;
878 logview_log_get_cached_lines (LogviewLog
*log
)
880 const char ** lines
= NULL
;
882 g_assert (LOGVIEW_IS_LOG (log
));
884 if (log
->priv
->lines
) {
885 lines
= (const char **) log
->priv
->lines
->pdata
;
892 logview_log_get_days_for_cached_lines (LogviewLog
*log
)
894 g_assert (LOGVIEW_IS_LOG (log
));
896 return log
->priv
->days
;
900 logview_log_has_new_lines (LogviewLog
*log
)
902 g_assert (LOGVIEW_IS_LOG (log
));
904 return log
->priv
->has_new_lines
;
908 logview_log_get_uri (LogviewLog
*log
)
910 g_assert (LOGVIEW_IS_LOG (log
));
912 return g_file_get_uri (log
->priv
->file
);
916 logview_log_get_gfile (LogviewLog
*log
)
918 g_assert (LOGVIEW_IS_LOG (log
));
920 return g_object_ref (log
->priv
->file
);
924 logview_log_get_has_days (LogviewLog
*log
)
926 g_assert (LOGVIEW_IS_LOG (log
));
928 return log
->priv
->has_days
;