summaryrefslogtreecommitdiff
path: root/logview/src/logview-log.c
diff options
context:
space:
mode:
Diffstat (limited to 'logview/src/logview-log.c')
-rw-r--r--logview/src/logview-log.c936
1 files changed, 936 insertions, 0 deletions
diff --git a/logview/src/logview-log.c b/logview/src/logview-log.c
new file mode 100644
index 00000000..407f49c3
--- /dev/null
+++ b/logview/src/logview-log.c
@@ -0,0 +1,936 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-log.c - object representation of a logfile
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif
+
+#include "logview-log.h"
+#include "logview-utils.h"
+
+G_DEFINE_TYPE (LogviewLog, logview_log, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_LOG, LogviewLogPrivate))
+
+enum {
+ LOG_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+struct _LogviewLogPrivate {
+ /* file and monitor */
+ GFile *file;
+ GFileMonitor *mon;
+
+ /* stats about the file */
+ time_t file_time;
+ goffset file_size;
+ char *display_name;
+ gboolean has_days;
+
+ /* lines and relative days */
+ GSList *days;
+ GPtrArray *lines;
+ guint lines_no;
+
+ /* stream poiting to the log */
+ GDataInputStream *stream;
+ gboolean has_new_lines;
+};
+
+typedef struct {
+ LogviewLog *log;
+ GError *err;
+ LogviewCreateCallback callback;
+ gpointer user_data;
+} LoadJob;
+
+typedef struct {
+ LogviewLog *log;
+ GError *err;
+ const char **lines;
+ GSList *new_days;
+ GCancellable *cancellable;
+ LogviewNewLinesCallback callback;
+ gpointer user_data;
+} NewLinesJob;
+
+typedef struct {
+ GInputStream *parent_str;
+ guchar * buffer;
+ GFile *file;
+
+ gboolean last_str_result;
+ int last_z_result;
+ z_stream zstream;
+} GZHandle;
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewLog *log = LOGVIEW_LOG (obj);
+ char ** lines;
+
+ if (log->priv->stream) {
+ g_object_unref (log->priv->stream);
+ log->priv->stream = NULL;
+ }
+
+ if (log->priv->file) {
+ g_object_unref (log->priv->file);
+ log->priv->file = NULL;
+ }
+
+ if (log->priv->mon) {
+ g_object_unref (log->priv->mon);
+ log->priv->mon = NULL;
+ }
+
+ if (log->priv->days) {
+ g_slist_foreach (log->priv->days,
+ (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (log->priv->days);
+ log->priv->days = NULL;
+ }
+
+ if (log->priv->lines) {
+ lines = (char **) g_ptr_array_free (log->priv->lines, FALSE);
+ g_strfreev (lines);
+ log->priv->lines = NULL;
+ }
+
+ G_OBJECT_CLASS (logview_log_parent_class)->finalize (obj);
+}
+
+static void
+logview_log_class_init (LogviewLogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = do_finalize;
+
+ signals[LOG_CHANGED] = g_signal_new ("log-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewLogClass, log_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LogviewLogPrivate));
+}
+
+static void
+logview_log_init (LogviewLog *self)
+{
+ self->priv = GET_PRIVATE (self);
+
+ self->priv->lines = NULL;
+ self->priv->lines_no = 0;
+ self->priv->days = NULL;
+ self->priv->file = NULL;
+ self->priv->mon = NULL;
+ self->priv->has_new_lines = FALSE;
+ self->priv->has_days = FALSE;
+}
+
+static void
+monitor_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *unused,
+ GFileMonitorEvent event,
+ gpointer user_data)
+{
+ LogviewLog *log = user_data;
+
+ if (event == G_FILE_MONITOR_EVENT_CHANGED) {
+ log->priv->has_new_lines = TRUE;
+ g_signal_emit (log, signals[LOG_CHANGED], 0, NULL);
+ }
+ /* TODO: handle the case where the log is deleted? */
+}
+
+static void
+setup_file_monitor (LogviewLog *log)
+{
+ GError *err = NULL;
+
+ log->priv->mon = g_file_monitor (log->priv->file,
+ 0, NULL, &err);
+ if (err) {
+ /* it'd be strange to get this error at this point but whatever */
+ g_warning ("Impossible to monitor the log file: the changes won't be notified");
+ g_error_free (err);
+ return;
+ }
+
+ /* set the rate to 1sec, as I guess it's not unusual to have more than
+ * one line written consequently or in a short time, being a log file.
+ */
+ g_file_monitor_set_rate_limit (log->priv->mon, 1000);
+ g_signal_connect (log->priv->mon, "changed",
+ G_CALLBACK (monitor_changed_cb), log);
+}
+
+static GSList *
+add_new_days_to_cache (LogviewLog *log, const char **new_lines, guint lines_offset)
+{
+ GSList *new_days, *l, *last_cached;
+ int res;
+ Day *day, *last;
+
+ new_days = log_read_dates (new_lines, log->priv->file_time);
+
+ /* the days are stored in chronological order, so we compare the last cached
+ * one with the new we got.
+ */
+ last_cached = g_slist_last (log->priv->days);
+
+ if (!last_cached) {
+ /* this means the day list is empty (i.e. we're on the first read */
+ log->priv->days = logview_utils_day_list_copy (new_days);
+ return new_days;
+ }
+
+ for (l = new_days; l; l = l->next) {
+ res = days_compare (l->data, last_cached->data);
+ day = l->data;
+
+ if (res > 0) {
+ /* this day in the list is newer than the last one, append to
+ * the cache.
+ */
+ day->first_line += lines_offset;
+ day->last_line += lines_offset;
+ log->priv->days = g_slist_append (log->priv->days, logview_utils_day_copy (day));
+ } else if (res == 0) {
+ last = last_cached->data;
+
+ /* update the lines number */
+ last->last_line += day->last_line;
+ }
+ }
+
+ return new_days;
+}
+
+static gboolean
+new_lines_job_done (gpointer data)
+{
+ NewLinesJob *job = data;
+
+ if (job->err) {
+ job->callback (job->log, NULL, NULL, job->err, job->user_data);
+ g_error_free (job->err);
+ } else {
+ job->callback (job->log, job->lines, job->new_days, job->err, job->user_data);
+ }
+
+ g_clear_object (&job->cancellable);
+
+ g_slist_foreach (job->new_days, (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (job->new_days);
+
+ /* drop the reference we acquired before */
+ g_object_unref (job->log);
+
+ g_slice_free (NewLinesJob, job);
+
+ return FALSE;
+}
+
+static gboolean
+do_read_new_lines (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ /* this runs in a separate thread */
+ NewLinesJob *job = user_data;
+ LogviewLog *log = job->log;
+ char *line;
+ GError *err = NULL;
+ GPtrArray *lines;
+
+ g_assert (LOGVIEW_IS_LOG (log));
+ g_assert (log->priv->stream != NULL);
+
+ if (!log->priv->lines) {
+ log->priv->lines = g_ptr_array_new ();
+ g_ptr_array_add (log->priv->lines, NULL);
+ }
+
+ lines = log->priv->lines;
+
+ /* remove the NULL-terminator */
+ g_ptr_array_remove_index (lines, lines->len - 1);
+
+ while ((line = g_data_input_stream_read_line (log->priv->stream, NULL,
+ job->cancellable, &err)) != NULL)
+ {
+ g_ptr_array_add (lines, (gpointer) line);
+ }
+
+ /* NULL-terminate the array again */
+ g_ptr_array_add (lines, NULL);
+
+ if (err) {
+ job->err = err;
+ goto out;
+ }
+
+ log->priv->has_new_lines = FALSE;
+
+ /* we'll return only the new lines in the callback */
+ line = g_ptr_array_index (lines, log->priv->lines_no);
+ job->lines = (const char **) lines->pdata + log->priv->lines_no;
+
+ /* save the new number of days and lines */
+ job->new_days = add_new_days_to_cache (log, job->lines, log->priv->lines_no);
+ log->priv->lines_no = (lines->len - 1);
+
+out:
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ new_lines_job_done,
+ job, NULL);
+ return FALSE;
+}
+
+static gboolean
+log_load_done (gpointer user_data)
+{
+ LoadJob *job = user_data;
+
+ if (job->err) {
+ /* the callback will have NULL as log, and the error set */
+ g_object_unref (job->log);
+ job->callback (NULL, job->err, job->user_data);
+ g_error_free (job->err);
+ } else {
+ job->callback (job->log, NULL, job->user_data);
+ setup_file_monitor (job->log);
+ }
+
+ g_slice_free (LoadJob, job);
+
+ return FALSE;
+}
+
+#ifdef HAVE_ZLIB
+
+/* GZip functions adapted for GIO from mate-vfs/gzip-method.c */
+
+#define Z_BUFSIZE 16384
+
+#define GZIP_HEADER_SIZE 10
+#define GZIP_MAGIC_1 0x1f
+#define GZIP_MAGIC_2 0x8b
+#define GZIP_FLAG_ASCII 0x01 /* bit 0 set: file probably ascii text */
+#define GZIP_FLAG_HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define GZIP_FLAG_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define GZIP_FLAG_ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define GZIP_FLAG_COMMENT 0x10 /* bit 4 set: file comment present */
+#define GZIP_FLAG_RESERVED 0xE0 /* bits 5..7: reserved */
+
+static gboolean
+skip_string (GInputStream *is)
+{
+ guchar c;
+ gssize bytes_read;
+
+ do {
+ bytes_read = g_input_stream_read (is, &c, 1, NULL, NULL);
+
+ if (bytes_read != 1) {
+ return FALSE;
+ }
+ } while (c != 0);
+
+ return TRUE;
+}
+
+static gboolean
+read_gzip_header (GInputStream *is,
+ time_t *modification_time)
+{
+ gboolean res;
+ guchar buffer[GZIP_HEADER_SIZE];
+ gssize bytes, to_skip;
+ guint mode;
+ guint flags;
+
+ bytes = g_input_stream_read (is, buffer, GZIP_HEADER_SIZE,
+ NULL, NULL);
+ if (bytes == -1) {
+ return FALSE;
+ }
+
+ if (bytes != GZIP_HEADER_SIZE)
+ return FALSE;
+
+ if (buffer[0] != GZIP_MAGIC_1 || buffer[1] != GZIP_MAGIC_2)
+ return FALSE;
+
+ mode = buffer[2];
+ if (mode != 8) /* Mode: deflate */
+ return FALSE;
+
+ flags = buffer[3];
+
+ if (flags & GZIP_FLAG_RESERVED)
+ return FALSE;
+
+ if (flags & GZIP_FLAG_EXTRA_FIELD) {
+ guchar tmp[2];
+
+ bytes = g_input_stream_read (is, tmp, 2, NULL, NULL);
+
+ if (bytes != 2) {
+ return FALSE;
+ }
+
+ to_skip = tmp[0] | (tmp[0] << 8);
+ bytes = g_input_stream_skip (is, to_skip, NULL, NULL);
+ if (bytes != to_skip) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_ORIG_NAME) {
+ if (!skip_string (is)) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_COMMENT) {
+ if (!skip_string (is)) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_HEAD_CRC) {
+ bytes = g_input_stream_skip (is, 2, NULL, NULL);
+ if (bytes != 2) {
+ return FALSE;
+ }
+ }
+
+ *modification_time = (buffer[4] | (buffer[5] << 8)
+ | (buffer[6] << 16) | (buffer[7] << 24));
+
+ return TRUE;
+}
+
+static GZHandle *
+gz_handle_new (GFile *file,
+ GInputStream *parent_stream)
+{
+ GZHandle *ret;
+
+ ret = g_new (GZHandle, 1);
+ ret->parent_str = g_object_ref (parent_stream);
+ ret->file = g_object_ref (file);
+ ret->buffer = NULL;
+
+ return ret;
+}
+
+static gboolean
+gz_handle_init (GZHandle *gz)
+{
+ gz->zstream.zalloc = NULL;
+ gz->zstream.zfree = NULL;
+ gz->zstream.opaque = NULL;
+
+ g_free (gz->buffer);
+ gz->buffer = g_malloc (Z_BUFSIZE);
+ gz->zstream.next_in = gz->buffer;
+ gz->zstream.avail_in = 0;
+
+ if (inflateInit2 (&gz->zstream, -MAX_WBITS) != Z_OK) {
+ return FALSE;
+ }
+
+ gz->last_z_result = Z_OK;
+ gz->last_str_result = TRUE;
+
+ return TRUE;
+}
+
+static void
+gz_handle_free (GZHandle *gz)
+{
+ g_object_unref (gz->parent_str);
+ g_object_unref (gz->file);
+ g_free (gz->buffer);
+ g_free (gz);
+}
+
+static gboolean
+fill_buffer (GZHandle *gz,
+ gsize num_bytes)
+{
+ gboolean res;
+ gsize count;
+
+ z_stream * zstream = &gz->zstream;
+
+ if (zstream->avail_in > 0) {
+ return TRUE;
+ }
+
+ count = g_input_stream_read (gz->parent_str,
+ gz->buffer,
+ Z_BUFSIZE,
+ NULL, NULL);
+ if (count == -1) {
+ if (zstream->avail_out == num_bytes) {
+ return FALSE;
+ }
+ gz->last_str_result = FALSE;
+ } else {
+ zstream->next_in = gz->buffer;
+ zstream->avail_in = count;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+result_from_z_result (int z_result)
+{
+ switch (z_result) {
+ case Z_OK:
+ case Z_STREAM_END:
+ return TRUE;
+ case Z_DATA_ERROR:
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+gz_handle_read (GZHandle *gz,
+ guchar *buffer,
+ gsize num_bytes,
+ gsize * bytes_read)
+{
+ z_stream *zstream;
+ gboolean res;
+ int z_result;
+
+ *bytes_read = 0;
+ zstream = &gz->zstream;
+
+ if (gz->last_z_result != Z_OK) {
+ if (gz->last_z_result == Z_STREAM_END) {
+ *bytes_read = 0;
+ return TRUE;
+ } else {
+ return result_from_z_result (gz->last_z_result);
+ }
+ } else if (gz->last_str_result == FALSE) {
+ return FALSE;
+ }
+
+ zstream->next_out = buffer;
+ zstream->avail_out = num_bytes;
+
+ while (zstream->avail_out != 0) {
+ res = fill_buffer (gz, num_bytes);
+
+ if (!res) {
+ return res;
+ }
+
+ z_result = inflate (zstream, Z_NO_FLUSH);
+ if (z_result == Z_STREAM_END) {
+ gz->last_z_result = z_result;
+ break;
+ } else if (z_result != Z_OK) {
+ gz->last_z_result = z_result;
+ }
+
+ if (gz->last_z_result != Z_OK && zstream->avail_out == num_bytes) {
+ return result_from_z_result (gz->last_z_result);
+ }
+ }
+
+ *bytes_read = num_bytes - zstream->avail_out;
+
+ return TRUE;
+}
+
+static GError *
+create_zlib_error (void)
+{
+ GError *err;
+
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_ZLIB,
+ _("Error while uncompressing the GZipped log. The file "
+ "might be corrupt."));
+ return err;
+}
+
+#endif /* HAVE_ZLIB */
+
+static gboolean
+log_load (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ /* this runs in a separate i/o thread */
+ LoadJob *job = user_data;
+ LogviewLog *log = job->log;
+ GFile *f = log->priv->file;
+ GFileInfo *info;
+ GInputStream *is;
+ const char *peeked_buffer;
+ const char * parse_data[2];
+ GSList *days;
+ const char *content_type;
+ GFileType type;
+ GError *err = NULL;
+ GTimeVal timeval;
+ gboolean is_archive, can_read;
+
+ info = g_file_query_info (f,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_TIME_MODIFIED ",",
+ 0, NULL, &err);
+ if (err) {
+ if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
+ /* TODO: PolicyKit integration */
+ }
+ goto out;
+ }
+
+ can_read = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
+ if (!can_read) {
+ /* TODO: PolicyKit integration */
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_PERMISSION_DENIED,
+ _("You don't have enough permissions to read the file."));
+ g_object_unref (info);
+
+ goto out;
+ }
+
+ type = g_file_info_get_file_type (info);
+ content_type = g_file_info_get_content_type (info);
+
+ is_archive = g_content_type_equals (content_type, "application/x-gzip");
+
+ if (type != (G_FILE_TYPE_REGULAR || G_FILE_TYPE_SYMBOLIC_LINK) ||
+ (!g_content_type_is_a (content_type, "text/plain") && !is_archive))
+ {
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_A_LOG,
+ _("The file is not a regular file or is not a text file."));
+ g_object_unref (info);
+
+ goto out;
+ }
+
+ log->priv->file_size = g_file_info_get_size (info);
+ g_file_info_get_modification_time (info, &timeval);
+ log->priv->file_time = timeval.tv_sec;
+ log->priv->display_name = g_strdup (g_file_info_get_display_name (info));
+
+ g_object_unref (info);
+
+ /* initialize the stream */
+ is = G_INPUT_STREAM (g_file_read (f, NULL, &err));
+
+ if (err) {
+ if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
+ /* TODO: PolicyKit integration */
+ }
+
+ goto out;
+ }
+
+ if (is_archive) {
+#ifdef HAVE_ZLIB
+ GZHandle *gz;
+ gboolean res;
+ guchar * buffer;
+ gsize bytes_read;
+ GInputStream *real_is;
+ time_t mtime; /* seconds */
+
+ /* this also skips the header from |is| */
+ res = read_gzip_header (is, &mtime);
+
+ if (!res) {
+ g_object_unref (is);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ log->priv->file_time = mtime;
+
+ gz = gz_handle_new (f, is);
+ res = gz_handle_init (gz);
+
+ if (!res) {
+ g_object_unref (is);
+ gz_handle_free (gz);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ real_is = g_memory_input_stream_new ();
+
+ do {
+ buffer = g_malloc (1024);
+ res = gz_handle_read (gz, buffer, 1024, &bytes_read);
+ g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (real_is),
+ buffer, bytes_read, g_free);
+ } while (res == TRUE && bytes_read > 0);
+
+ if (!res) {
+ gz_handle_free (gz);
+ g_object_unref (real_is);
+ g_object_unref (is);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ g_object_unref (is);
+ is = real_is;
+
+ gz_handle_free (gz);
+#else /* HAVE_ZLIB */
+ g_object_unref (is);
+
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_SUPPORTED,
+ _("This version of System Log does not support GZipped logs."));
+ goto out;
+#endif /* HAVE_ZLIB */
+ }
+
+ log->priv->stream = g_data_input_stream_new (is);
+
+ /* sniff into the stream for a timestamped line */
+ g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (log->priv->stream),
+ (gssize) g_buffered_input_stream_get_buffer_size (G_BUFFERED_INPUT_STREAM (log->priv->stream)),
+ NULL, &err);
+ if (err == NULL) {
+ peeked_buffer = g_buffered_input_stream_peek_buffer
+ (G_BUFFERED_INPUT_STREAM (log->priv->stream), NULL);
+ parse_data[0] = peeked_buffer;
+ parse_data[1] = NULL;
+
+ if ((days = log_read_dates (parse_data, time (NULL))) != NULL) {
+ log->priv->has_days = TRUE;
+ g_slist_foreach (days, (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (days);
+ } else {
+ log->priv->has_days = FALSE;
+ }
+ } else {
+ log->priv->has_days = FALSE;
+ g_clear_error (&err);
+ }
+
+ g_object_unref (is);
+
+out:
+ if (err) {
+ job->err = err;
+ }
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ log_load_done,
+ job, NULL);
+ return FALSE;
+}
+
+static void
+log_setup_load (LogviewLog *log, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LoadJob *job;
+
+ job = g_slice_new0 (LoadJob);
+ job->callback = callback;
+ job->user_data = user_data;
+ job->log = log;
+ job->err = NULL;
+
+ /* push the loading job into another thread */
+ g_io_scheduler_push_job (log_load,
+ job,
+ NULL, 0, NULL);
+}
+
+/* public methods */
+
+void
+logview_log_read_new_lines (LogviewLog *log,
+ GCancellable *cancellable,
+ LogviewNewLinesCallback callback,
+ gpointer user_data)
+{
+ NewLinesJob *job;
+
+ /* initialize the job struct with sensible values */
+ job = g_slice_new0 (NewLinesJob);
+ job->callback = callback;
+ job->user_data = user_data;
+ job->cancellable = (cancellable != NULL) ? g_object_ref (cancellable) : NULL;
+ job->log = g_object_ref (log);
+ job->err = NULL;
+ job->lines = NULL;
+ job->new_days = NULL;
+
+ /* push the fetching job into another thread */
+ g_io_scheduler_push_job (do_read_new_lines,
+ job,
+ NULL, 0,
+ job->cancellable);
+}
+
+void
+logview_log_create (const char *filename, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
+
+ log->priv->file = g_file_new_for_path (filename);
+
+ log_setup_load (log, callback, user_data);
+}
+
+void
+logview_log_create_from_gfile (GFile *file, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
+
+ log->priv->file = g_object_ref (file);
+
+ log_setup_load (log, callback, user_data);
+}
+
+const char *
+logview_log_get_display_name (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->display_name;
+}
+
+time_t
+logview_log_get_timestamp (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->file_time;
+}
+
+goffset
+logview_log_get_file_size (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->file_size;
+}
+
+guint
+logview_log_get_cached_lines_number (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->lines_no;
+}
+
+const char **
+logview_log_get_cached_lines (LogviewLog *log)
+{
+ const char ** lines = NULL;
+
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ if (log->priv->lines) {
+ lines = (const char **) log->priv->lines->pdata;
+ }
+
+ return lines;
+}
+
+GSList *
+logview_log_get_days_for_cached_lines (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->days;
+}
+
+gboolean
+logview_log_has_new_lines (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->has_new_lines;
+}
+
+char *
+logview_log_get_uri (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return g_file_get_uri (log->priv->file);
+}
+
+GFile *
+logview_log_get_gfile (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return g_object_ref (log->priv->file);
+}
+
+gboolean
+logview_log_get_has_days (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->has_days;
+}
+