/* This is -*- C -*- */ /* $Id: guppi-stream.c,v 1.17 2001/09/08 05:50:00 trow Exp $ */ /* * guppi-stream.c * * Copyright (C) 1999 EMC Capital Management, Inc. * * Developed by Jon Trowbridge and * Havoc Pennington . * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include #include "guppi-stream.h" #include #include #include #include static GtkObjectClass *parent_class = NULL; enum { PRELOAD, FULLY_PRELOADED, CHANGED_CODES, LAST_SIGNAL }; static guint str_signals[LAST_SIGNAL] = { 0 }; static void guppi_stream_finalize (GtkObject *obj) { GuppiStream *gs = GUPPI_STREAM (obj); gint i; guppi_finalized (obj); if (gs->in) { guppi_file_close (gs->in); guppi_unref0 (gs->in); } if (gs->buffer) { for (i = 0; i <= gs->buffer_top; ++i) { guppi_free (gs->buffer[i]); guppi_free (gs->marked_buffer[i]); guppi_free (gs->sani_buffer[i]); } guppi_free (gs->buffer); guppi_free (gs->marked_buffer); guppi_free (gs->sani_buffer); } guppi_free (gs->current_line); guppi_free (gs->current_marked_line); guppi_free (gs->current_sani_line); guppi_free (gs->eol_comment); guppi_free (gs->ml_start_comment); guppi_free (gs->ml_end_comment); if (parent_class->finalize) parent_class->finalize (obj); } static void guppi_stream_class_init (GuppiStreamClass *klass) { GtkObjectClass *object_class = (GtkObjectClass *) klass; parent_class = gtk_type_class (GTK_TYPE_OBJECT); str_signals[PRELOAD] = gtk_signal_new ("preload", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GuppiStreamClass, preload), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); str_signals[FULLY_PRELOADED] = gtk_signal_new ("fully_preloaded", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GuppiStreamClass, fully_preloaded), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); str_signals[CHANGED_CODES] = gtk_signal_new ("changed_codes", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GuppiStreamClass, changed_codes), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, str_signals, LAST_SIGNAL); klass->preload = NULL; klass->fully_preloaded = NULL; klass->changed_codes = NULL; object_class->finalize = guppi_stream_finalize; } static void guppi_stream_init (GuppiStream *gs) { gs->in = NULL; gs->bad = FALSE; gs->buffering = TRUE; gs->eof = FALSE; gs->current_line_no = -1; gs->current_line = NULL; gs->current_marked_line = NULL; gs->current_sani_line = NULL; gs->last_visited_line_no = -1; gs->buffer_count = 0; gs->buffer_top = 0; gs->buffer = NULL; gs->marked_buffer = NULL; gs->sani_buffer = NULL; gs->total_size = -1; gs->buffered_size = 0; gs->in_ml_comment = FALSE; gs->eol_comment = guppi_strdup ("//"); gs->ml_start_comment = guppi_strdup ("/*"); gs->ml_end_comment = guppi_strdup ("*/"); gs->start_quote = '"'; gs->end_quote = '"'; gs->escape = '~'; } GtkType guppi_stream_get_type (void) { static GtkType guppi_stream_type = 0; if (!guppi_stream_type) { static const GtkTypeInfo guppi_stream_info = { "GuppiStream", sizeof (GuppiStream), sizeof (GuppiStreamClass), (GtkClassInitFunc) guppi_stream_class_init, (GtkObjectInitFunc) guppi_stream_init, NULL, NULL, (GtkClassInitFunc) NULL }; guppi_stream_type = gtk_type_unique (GTK_TYPE_OBJECT, &guppi_stream_info); } return guppi_stream_type; } GuppiStream * guppi_stream_new (GuppiFile *f) { GuppiStream *gs; struct stat stat; g_return_val_if_fail (f != NULL, NULL); gs = GUPPI_STREAM (guppi_type_new (guppi_stream_get_type ())); gs->in = f; guppi_ref (gs->in); if (guppi_file_stat (gs->in, &stat) == 0) { gs->total_size = stat.st_size; } return gs; } GuppiStream * guppi_stream_open_file (const gchar *filename) { GuppiFile *in; GuppiStream *gs; g_return_val_if_fail (filename != NULL, NULL); in = guppi_file_open (filename); if (in == NULL) return NULL; gs = guppi_stream_new (in); guppi_unref (in); return gs; } /**********************************************************************/ static gboolean guppi_stream_mark_line (GuppiStream *gs, const gchar *line, gchar *buffer, gint buffer_len) { gboolean injected_escape, in_comment, in_quote, punch_in; gint p = 0, i, j; gint eol_comment_len; gint ml_start_comment_len; gint ml_end_comment_len; g_return_val_if_fail (gs != NULL, FALSE); g_return_val_if_fail (line != NULL, FALSE); g_return_val_if_fail (buffer_len > 0, FALSE); injected_escape = FALSE; eol_comment_len = gs->eol_comment ? strlen (gs->eol_comment) : 0; ml_start_comment_len = gs->ml_start_comment ? strlen (gs->ml_start_comment) : 0; ml_end_comment_len = gs->ml_end_comment ? strlen (gs->ml_end_comment) : 0; if (gs->in_ml_comment) { buffer[0] = gs->escape; buffer[1] = 'C'; p += 2; injected_escape = TRUE; } in_comment = gs->in_ml_comment; in_quote = FALSE; i = 0; while (line[i]) { punch_in = TRUE; if (line[i] == gs->escape) { buffer[p] = gs->escape; ++p; injected_escape = TRUE; } else if (in_quote) { in_quote = line[i] != gs->end_quote; } else if (line[i] == gs->start_quote) { in_quote = TRUE; } else if (!in_quote && !in_comment && eol_comment_len && !strncmp (gs->eol_comment, line + i, eol_comment_len)) { /* We've hit an end-of-line-comment, so adjust accordingly */ buffer[p] = gs->escape; buffer[p + 1] = 'L'; p += 2; while (line[i]) buffer[p++] = line[i++]; injected_escape = TRUE; punch_in = FALSE; } else if (!in_quote && !in_comment && ml_start_comment_len && !strncmp (gs->ml_start_comment, line + i, ml_start_comment_len)) { buffer[p] = gs->escape; buffer[p + 1] = 'C'; p += 2; in_comment = TRUE; injected_escape = TRUE; } else if (in_comment && !in_quote && ml_end_comment_len && !strncmp (gs->ml_end_comment, line + i, ml_end_comment_len)) { for (j = 0; j < ml_end_comment_len; ++j) buffer[p++] = line[i++]; buffer[p] = gs->escape; buffer[p + 1] = 'c'; p += 2; in_comment = FALSE; injected_escape = TRUE; punch_in = FALSE; } if (punch_in) { buffer[p] = line[i]; ++p; ++i; } } gs->in_ml_comment = in_comment; buffer[p] = '\0'; /* Return whether or not we did anything */ return injected_escape; } static gboolean guppi_stream_sanitize_line (GuppiStream *gs, const gchar *line, gchar *sani, gint sani_len) { gint i = 0, j = 0; gboolean seen_escape; gboolean pending = FALSE, in_comment = FALSE, done = FALSE; g_return_val_if_fail (gs != NULL, FALSE); g_return_val_if_fail (line != NULL, FALSE); g_return_val_if_fail (sani != NULL, FALSE); g_return_val_if_fail (sani_len > 0, FALSE); seen_escape = FALSE; i = 0; while (line[i] && !seen_escape) { if (line[i] == gs->escape) seen_escape = TRUE; ++i; } if (!seen_escape) return FALSE; i = 0; while (line[i] && !done) { if (pending) { if (line[i] == gs->escape) { if (!in_comment) sani[j++] = gs->escape; } else if (line[i] == 'C') in_comment = TRUE; else if (line[i] == 'c') in_comment = FALSE; else if (line[i] == 'L') done = TRUE; else g_assert_not_reached (); pending = FALSE; } else { if (line[i] == gs->escape) pending = TRUE; else if (!in_comment) sani[j++] = line[i]; } ++i; } sani[j] = '\0'; return TRUE; } /**********************************************************************/ static void guppi_stream_changed (GuppiStream *gs) { const gint buflen = 1024; gchar mbuf[1024]; gchar sbuf[1024]; gboolean did_mark, did_sani; gint i; g_return_if_fail (gs != NULL); g_assert (gs->buffering); /* Reset state */ gs->in_ml_comment = FALSE; /* Regenerate buffered marked/sani lines and reset state */ for (i = 0; i <= gs->buffer_top; ++i) { guppi_free (gs->marked_buffer[i]); guppi_free (gs->sani_buffer[i]); did_mark = guppi_stream_mark_line (gs, gs->buffer[i], mbuf, buflen); did_sani = did_mark && guppi_stream_sanitize_line (gs, mbuf, sbuf, buflen); gs->marked_buffer[i] = did_mark ? guppi_strdup (mbuf) : NULL; gs->sani_buffer[i] = did_sani ? guppi_strdup (sbuf) : NULL; } gtk_signal_emit (GTK_OBJECT (gs), str_signals[CHANGED_CODES]); } gboolean guppi_stream_sequential_mode (GuppiStream *gs) { g_return_val_if_fail (gs != NULL, FALSE); return !gs->buffering; } void guppi_stream_set_sequential_mode (GuppiStream *gs) { g_return_if_fail (gs != NULL); g_return_if_fail (gs->buffering); gs->buffering = FALSE; } const gchar * guppi_stream_eol_comment (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, NULL); return gs->eol_comment; } void guppi_stream_set_eol_comment (GuppiStream *gs, const gchar *x) { g_return_if_fail (gs != NULL); if (x == NULL && gs->eol_comment == NULL) return; if ((x == NULL && gs->eol_comment != NULL) || (x != NULL && gs->eol_comment == NULL) || (strcmp (x, gs->eol_comment) != 0)) { guppi_free (gs->eol_comment); gs->eol_comment = guppi_strdup (x); guppi_stream_changed (gs); } } const gchar * guppi_stream_ml_comment_start (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, NULL); return gs->ml_start_comment; } const gchar * guppi_stream_ml_comment_end (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, NULL); return gs->ml_end_comment; } void guppi_stream_set_ml_comment_start (GuppiStream *gs, const gchar *x) { g_return_if_fail (gs != NULL); if (x == NULL && gs->ml_start_comment == NULL) return; if ((x == NULL && gs->ml_start_comment != NULL) || (x != NULL && gs->ml_start_comment == NULL) || (strcmp (x, gs->ml_start_comment) != 0)) { guppi_free (gs->ml_start_comment); gs->ml_start_comment = guppi_strdup (x); guppi_stream_changed (gs); } } void guppi_stream_set_ml_comment_end (GuppiStream *gs, const gchar *x) { g_return_if_fail (gs != NULL); if (x == NULL && gs->ml_end_comment == NULL) return; if ((x == NULL && gs->ml_end_comment != NULL) || (x != NULL && gs->ml_end_comment == NULL) || (strcmp (x, gs->ml_end_comment) != 0)) { guppi_free (gs->ml_end_comment); gs->ml_end_comment = guppi_strdup (x); guppi_stream_changed (gs); } } gchar guppi_stream_quote_start (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, '\0'); return gs->start_quote; } gchar guppi_stream_quote_end (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, '\0'); return gs->end_quote; } void guppi_stream_set_quote_start (GuppiStream *gs, gchar x) { g_return_if_fail (gs != NULL); gs->start_quote = x; guppi_stream_changed (gs); } void guppi_stream_set_quote_end (GuppiStream *gs, gchar x) { g_return_if_fail (gs != NULL); gs->end_quote = x; guppi_stream_changed (gs); } gchar guppi_stream_escape (const GuppiStream *gs) { g_return_val_if_fail (gs != NULL, '\0'); return gs->escape; } const gchar * guppi_stream_source (GuppiStream *gs) { g_return_val_if_fail (gs != NULL, NULL); return guppi_file_filename (gs->in); } gint guppi_stream_number_of_lines (GuppiStream *gs) { g_return_val_if_fail (gs != NULL, -1); return gs->buffering && gs->eof ? 1 + gs->current_line_no : -1; } gint guppi_stream_estimated_number_of_lines (GuppiStream *gs) { g_return_val_if_fail (gs != NULL, -1); if (gs->buffering && gs->eof) return 1 + gs->current_line_no; if (gs->total_size > 0) { if (gs->current_line > 0 && gs->buffered_size > 0) return (gint) (gs->total_size / (gs->buffered_size / (1.0 + gs->current_line_no))); else return gs->total_size / 60; /* totally arbitrary */ } return -1; } int guppi_stream_number_of_preloaded_lines (GuppiStream *gs) { g_return_val_if_fail (gs != NULL, -1); return gs->buffering ? gs->buffer_top : -1; } /* Ensure that the buffer is big enough to accomodate at least minimum_count lines. */ static void guppi_stream_grow_buffers (GuppiStream *gs, gint minimum_count) { gint new_count; gchar **new_buffer; g_return_if_fail (gs != NULL); g_return_if_fail (minimum_count > 0); if (gs->buffer_count >= minimum_count) return; /* Find the new size for our buffers */ new_count = gs->buffer_count ? gs->buffer_count : 500; while (new_count < minimum_count) new_count *= 2; /* Grow our three buffers (standard, marked, sanitized) */ new_buffer = guppi_new0 (gchar *, new_count); memcpy (new_buffer, gs->buffer, gs->buffer_count * sizeof (gchar *)); guppi_free (gs->buffer); gs->buffer = new_buffer; new_buffer = guppi_new0 (gchar *, new_count); memcpy (new_buffer, gs->marked_buffer, gs->buffer_count * sizeof (gchar *)); guppi_free (gs->marked_buffer); gs->marked_buffer = new_buffer; new_buffer = guppi_new0 (gchar *, new_count); memcpy (new_buffer, gs->sani_buffer, gs->buffer_count * sizeof (gchar *)); guppi_free (gs->sani_buffer); gs->sani_buffer = new_buffer; gs->buffer_count = new_count; } static void guppi_stream_add_to_buffer (GuppiStream *gs, const gchar *line, const gchar *marked_line, const gchar *sani_line) { gint i; g_return_if_fail (gs != NULL); g_return_if_fail (gs->buffering); i = gs->current_line_no; guppi_stream_grow_buffers (gs, 1 + i); gs->buffer[i] = guppi_strdup (line); gs->marked_buffer[i] = guppi_strdup (marked_line); gs->sani_buffer[i] = guppi_strdup (sani_line); gs->buffered_size += strlen (line) + 1; gs->buffer_top = gs->current_line_no; } /**********************************************************************/ static void guppi_stream_get_lines_through (GuppiStream *gs, gint line_no) { const gint buflen = 1024; gchar buf[1024]; gchar mbuf[1024]; gchar sbuf[1024]; gboolean did_mark, did_sani; gboolean did_something = FALSE; g_return_if_fail (gs != NULL); g_return_if_fail (line_no >= 0); g_return_if_fail (!gs->bad); if (gs->eof) return; while (!gs->eof && gs->current_line_no < line_no) { if (guppi_file_gets (gs->in, buf, buflen)) { g_strchomp (buf); did_something = TRUE; ++gs->current_line_no; did_mark = guppi_stream_mark_line (gs, buf, mbuf, buflen); did_sani = did_mark && guppi_stream_sanitize_line (gs, mbuf, sbuf, buflen); if (gs->buffering) guppi_stream_add_to_buffer (gs, buf, did_mark ? mbuf : NULL, did_sani ? sbuf : NULL); if (gs->current_line_no == line_no) { guppi_free (gs->current_line); gs->current_line = guppi_strdup (buf); guppi_free (gs->current_marked_line); gs->current_marked_line = did_mark ? guppi_strdup (mbuf) : NULL; guppi_free (gs->current_sani_line); gs->current_sani_line = did_sani ? guppi_strdup (sbuf) : NULL; } } else { gs->eof = TRUE; /* guppi_file_close(gs->in); gtk_object_unref(GTK_OBJECT(gs->in)); gs->in = NULL; */ if (gs->buffering) { gtk_signal_emit (GTK_OBJECT (gs), str_signals[FULLY_PRELOADED]); if (gs->total_size < 0) gs->total_size = gs->buffered_size; } } } if (did_something) gtk_signal_emit (GTK_OBJECT (gs), str_signals[PRELOAD]); } static void guppi_stream_line_get_triple (GuppiStream *gs, gint line_no, const gchar ** line, const gchar ** marked, const gchar ** sani) { g_return_if_fail (gs != NULL); g_return_if_fail (line_no >= 0); g_return_if_fail (!gs->bad); if (!gs->buffering) { if (line_no < gs->last_visited_line_no) { g_warning ("buffering violation: %d/%d", line_no, gs->current_line_no); return; } gs->last_visited_line_no = line_no; } if (line_no <= gs->buffer_top) { if (line) *line = gs->buffer[line_no]; if (marked) { *marked = gs->marked_buffer[line_no]; if (*marked == NULL) *marked = gs->buffer[line_no]; } if (sani) { *sani = gs->sani_buffer[line_no]; if (*sani == NULL) *sani = gs->buffer[line_no]; } return; } if (gs->current_line_no < line_no) guppi_stream_get_lines_through (gs, line_no); if (gs->current_line_no == line_no) { if (line) *line = gs->current_line; if (marked) { *marked = gs->current_marked_line; if (*marked == NULL) *marked = gs->current_line; } if (sani) { *sani = gs->current_sani_line; if (*sani == NULL) *sani = gs->current_line; } return; } if (line) *line = NULL; if (marked) *marked = NULL; if (sani) *sani = NULL; } const gchar * guppi_stream_get_line (GuppiStream *gs, gint line_no) { const gchar *x; guppi_stream_line_get_triple (gs, line_no, &x, NULL, NULL); return x; } const gchar * guppi_stream_get_marked_line (GuppiStream *gs, gint line_no) { const gchar *x; guppi_stream_line_get_triple (gs, line_no, NULL, &x, NULL); return x; } const gchar * guppi_stream_get_sanitized_line (GuppiStream *gs, gint line_no) { const gchar *x; guppi_stream_line_get_triple (gs, line_no, NULL, NULL, &x); return x; } /**********************************************************************/ void guppi_stream_load_some_lines (GuppiStream *gs) { g_return_if_fail (gs != NULL); if (gs->current_line_no < 0) guppi_stream_get_lines_through (gs, 250); } void guppi_stream_load_more_lines (GuppiStream *gs) { g_return_if_fail (gs != NULL); guppi_stream_get_lines_through (gs, gs->current_line_no + 250); } /* $Id: guppi-stream.c,v 1.17 2001/09/08 05:50:00 trow Exp $ */