Created
December 13, 2011 00:02
-
-
Save codebrainz/1469745 to your computer and use it in GitHub Desktop.
GeanyDocMonitor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * geanydocmonitor.c | |
| * | |
| * Copyright 2011 Matthew Brush <matt@geany.org> | |
| * | |
| * 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., 51 Franklin Street, Fifth Floor, Boston, | |
| * MA 02110-1301, USA. | |
| */ | |
| #include <glib/gstdio.h> | |
| #include <gtk/gtk.h> | |
| #include <gio/gio.h> /* for GFile and GFileMonitor */ | |
| #include "geany.h" /* for GeanyDocument */ | |
| #include "support.h" /* for _ macro */ | |
| #include "document.h" /* for document functions */ | |
| #include "utils.h" /* for utils_get_real_time() */ | |
| #include "ui_utils.h" /* for ui_set_statusbar() */ | |
| #include "geanydocmessage.h" | |
| #include "geanydocmonitor.h" | |
| #define GEANY_DOC_MONITOR_DEFAULT_RATE_LIMIT 800 | |
| /* Comment out this line to disable monitoring of remote files. */ | |
| #define GEANY_DOC_MONITOR_HANDLE_REMOTE_FILES 1 | |
| enum | |
| { | |
| PROP_0, | |
| PROP_ENABLED, | |
| PROP_RATE_LIMIT, | |
| PROP_LAST | |
| }; | |
| enum | |
| { | |
| SIGNAL_CHANGED, | |
| SIGNAL_DELETED, | |
| SIGNAL_MOVED, | |
| SIGNAL_LAST | |
| }; | |
| struct _GeanyDocMonitorPrivate | |
| { | |
| gchar *filename; | |
| GFile *file; | |
| GFileMonitor *mon; | |
| gboolean enabled; | |
| gboolean rate_set; | |
| guint before_save_id; | |
| guint save_id; | |
| }; | |
| static guint geany_doc_monitor_signals[SIGNAL_LAST]; | |
| static void geany_doc_monitor_finalize(GObject *object); | |
| static void on_doc_monitor_monitor_changed(GFileMonitor *monitor, | |
| GFile *file, GFile *other_file, GFileMonitorEvent event_type, | |
| GeanyDocMonitor *self); | |
| static void geany_doc_monitor_set_property(GObject *obj, guint property_id, | |
| const GValue *value, GParamSpec *pspec); | |
| static void geany_doc_monitor_get_property(GObject *obj, guint property_id, | |
| GValue *value, GParamSpec *pspec); | |
| static gint geany_doc_monitor_guess_rate_limit(GeanyDocMonitor *self); | |
| static void on_geany_doc_before_save(GeanyDocMonitor *self, GeanyDocument *doc, | |
| G_GNUC_UNUSED GObject *obj); | |
| static void on_geany_doc_save(GeanyDocMonitor *self, GeanyDocument *doc, | |
| G_GNUC_UNUSED GObject *obj); | |
| G_DEFINE_TYPE(GeanyDocMonitor, geany_doc_monitor, GEANY_TYPE_DOC_MESSAGE) | |
| static GObject *geany_doc_monitor_constructor(GType gtype, guint n_properties, | |
| GObjectConstructParam *properties) | |
| { | |
| GObject *obj; | |
| GObjectClass *klass = G_OBJECT_CLASS(geany_doc_monitor_parent_class); | |
| obj = klass->constructor(gtype, n_properties, properties); | |
| geany_doc_message_set_message_type(GEANY_DOC_MESSAGE(obj), GTK_MESSAGE_ERROR); | |
| return obj; | |
| } | |
| static void geany_doc_monitor_class_init(GeanyDocMonitorClass *klass) | |
| { | |
| GObjectClass *g_object_class; | |
| g_object_class = G_OBJECT_CLASS(klass); | |
| g_object_class->constructor = geany_doc_monitor_constructor; | |
| g_object_class->finalize = geany_doc_monitor_finalize; | |
| g_object_class->set_property = geany_doc_monitor_set_property; | |
| g_object_class->get_property = geany_doc_monitor_get_property; | |
| g_object_class_install_property(g_object_class, PROP_ENABLED, | |
| g_param_spec_boolean("enabled", "Enabled", "Monitoring enabled", | |
| FALSE, G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); | |
| g_object_class_install_property(g_object_class, PROP_RATE_LIMIT, | |
| g_param_spec_uint("rate-limit", "RateLimit", "Limit monitoring rate", | |
| 0, G_MAXINT, GEANY_DOC_MONITOR_DEFAULT_RATE_LIMIT, | |
| G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); | |
| geany_doc_monitor_signals[SIGNAL_CHANGED] = | |
| g_signal_newv("changed", G_TYPE_FROM_CLASS(klass), | |
| G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, | |
| NULL, NULL, NULL, | |
| g_cclosure_marshal_VOID__VOID, | |
| G_TYPE_NONE, 0, NULL); | |
| geany_doc_monitor_signals[SIGNAL_DELETED] = | |
| g_signal_newv("deleted", G_TYPE_FROM_CLASS(klass), | |
| G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, | |
| NULL, NULL, NULL, | |
| g_cclosure_marshal_VOID__VOID, | |
| G_TYPE_NONE, 0, NULL); | |
| geany_doc_monitor_signals[SIGNAL_MOVED] = | |
| g_signal_newv("moved", G_TYPE_FROM_CLASS(klass), | |
| G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, | |
| NULL, NULL, NULL, | |
| g_cclosure_marshal_VOID__STRING, | |
| G_TYPE_NONE, 0, NULL); | |
| g_type_class_add_private((gpointer)klass, sizeof(GeanyDocMonitorPrivate)); | |
| } | |
| static void geany_doc_monitor_finalize(GObject *object) | |
| { | |
| GeanyDocMonitor *self; | |
| g_return_if_fail (GEANY_IS_DOC_MONITOR(object)); | |
| self = GEANY_DOC_MONITOR(object); | |
| g_free(self->priv->filename); | |
| if (G_IS_FILE_MONITOR(self->priv->mon)) | |
| g_object_unref(self->priv->mon); | |
| if (G_IS_FILE(self->priv->mon)) | |
| g_object_unref(self->priv->file); | |
| G_OBJECT_CLASS(geany_doc_monitor_parent_class)->finalize(object); | |
| } | |
| static void geany_doc_monitor_init (GeanyDocMonitor *self) | |
| { | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GEANY_TYPE_DOC_MONITOR, GeanyDocMonitorPrivate); | |
| self->priv->enabled = FALSE; | |
| self->priv->rate_set = FALSE; | |
| self->priv->before_save_id = 0; | |
| self->priv->save_id = 0; | |
| } | |
| GtkWidget *geany_doc_monitor_new(GeanyDocument *doc) | |
| { | |
| return g_object_new(GEANY_TYPE_DOC_MONITOR, "document", (gpointer) doc, NULL); | |
| } | |
| static void on_doc_monitor_monitor_changed(GFileMonitor *monitor, | |
| GFile *file, GFile *other_file, GFileMonitorEvent event_type, | |
| GeanyDocMonitor *self) | |
| { | |
| GeanyDocument *doc; | |
| gchar *display_name; | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| if (!geany_doc_monitor_get_enabled(self)) | |
| return; | |
| doc = geany_doc_message_get_document(GEANY_DOC_MESSAGE(self)); | |
| switch (event_type) | |
| { | |
| case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: | |
| display_name = document_get_basename_for_display(doc, 50); | |
| ui_set_statusbar(TRUE, _("File '%s' was changed on disk"), display_name); | |
| g_free(display_name); | |
| document_set_text_changed(doc, TRUE); | |
| gtk_widget_show_all(GTK_WIDGET(self)); | |
| g_signal_emit_by_name(self, "changed", NULL); | |
| break; | |
| case G_FILE_MONITOR_EVENT_DELETED: | |
| display_name = document_get_basename_for_display(doc, 50); | |
| ui_set_statusbar(TRUE, _("File '%s' was not found on disk"), display_name); | |
| g_free(display_name); | |
| document_set_text_changed(doc, TRUE); | |
| gtk_widget_show_all(GTK_WIDGET(self)); | |
| g_signal_emit_by_name(self, "deleted", NULL); | |
| break; | |
| #if 0 | |
| case G_FILE_MONITOR_EVENT_MOVED: | |
| { | |
| gchar *newfn = g_file_get_path(other_file); | |
| display_name = document_get_basename_for_display(doc, 50); | |
| ui_set_statusbar(TRUE, "File '%s' was moved to '%s'", | |
| display_name, newfn); | |
| g_free(display_name); | |
| document_set_text_changed(doc, TRUE); | |
| gtk_widget_show_all(GTK_WIDGET(self)); | |
| g_signal_emit_by_name(self, "moved", newfn, NULL); | |
| g_free(newfn); | |
| break; | |
| } | |
| #endif | |
| default: | |
| break; | |
| } | |
| } | |
| static void on_geany_doc_before_save(GeanyDocMonitor *self, GeanyDocument *doc, | |
| G_GNUC_UNUSED GObject *obj) | |
| { | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| geany_doc_monitor_set_enabled(self, FALSE); | |
| } | |
| static void on_geany_doc_save(GeanyDocMonitor *self, GeanyDocument *doc, | |
| G_GNUC_UNUSED GObject *obj) | |
| { | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| gtk_widget_hide(GTK_WIDGET(self)); | |
| geany_doc_monitor_set_enabled(self, TRUE); | |
| } | |
| static gboolean on_idle_set_rate_limit(GeanyDocMonitor *self) | |
| { | |
| gint rate_limit; | |
| g_return_val_if_fail(GEANY_IS_DOC_MONITOR(self), TRUE); | |
| rate_limit = geany_doc_monitor_guess_rate_limit(self); | |
| if (rate_limit >= 0) | |
| geany_doc_monitor_set_rate_limit(self, (guint) rate_limit); | |
| self->priv->rate_set = 1; | |
| return FALSE; | |
| } | |
| void geany_doc_monitor_set_enabled(GeanyDocMonitor *self, gboolean enabled) | |
| { | |
| gboolean changed; | |
| GeanyDocument *doc; | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| doc = geany_doc_message_get_document(GEANY_DOC_MESSAGE(self)); | |
| #ifndef GEANY_DOC_MONITOR_HANDLE_REMOTE_FILES | |
| if (document_get_is_remote(doc)) | |
| return; | |
| #endif | |
| changed = (enabled != self->priv->enabled); | |
| if (changed && enabled) | |
| { | |
| g_return_if_fail(doc != NULL); | |
| if (doc->file_name != NULL) | |
| { | |
| if (self->priv->filename == NULL) | |
| self->priv->filename = g_strdup(doc->file_name); | |
| if (self->priv->file == NULL) | |
| self->priv->file = g_file_new_for_path(self->priv->filename); | |
| if (G_IS_FILE(self->priv->file) && self->priv->mon == NULL) | |
| { | |
| #if 0 | |
| self->priv->mon = g_file_monitor_file(self->priv->file, | |
| G_FILE_MONITOR_SEND_MOVED, NULL, NULL); | |
| #else | |
| self->priv->mon = g_file_monitor_file(self->priv->file, | |
| 0, NULL, NULL); | |
| #endif | |
| if (G_IS_FILE_MONITOR(self->priv->mon)) | |
| { | |
| self->priv->enabled = TRUE; | |
| g_signal_connect(self->priv->mon, "changed", | |
| G_CALLBACK(on_doc_monitor_monitor_changed), self); | |
| if (self->priv->before_save_id < 1) | |
| { | |
| self->priv->before_save_id = g_signal_connect_swapped(geany_object, | |
| "document-before-save", G_CALLBACK(on_geany_doc_before_save), self); | |
| } | |
| if (self->priv->save_id < 1) | |
| { | |
| self->priv->save_id = g_signal_connect_swapped(geany_object, | |
| "document-save", G_CALLBACK(on_geany_doc_save), self); | |
| } | |
| self->priv->rate_set = FALSE; | |
| g_idle_add((GSourceFunc) on_idle_set_rate_limit, self); | |
| } | |
| else | |
| { | |
| g_object_unref(self->priv->file); | |
| g_free(self->priv->filename); | |
| self->priv->file = NULL; | |
| self->priv->filename = NULL; | |
| self->priv->enabled = FALSE; | |
| changed = FALSE; | |
| } | |
| } | |
| } | |
| else | |
| self->priv->enabled = FALSE; | |
| } | |
| else if (changed && !enabled) | |
| { | |
| g_free(self->priv->filename); | |
| if (G_IS_FILE_MONITOR(self->priv->mon)) | |
| g_object_unref(self->priv->mon); | |
| if (G_IS_FILE(self->priv->file)) | |
| g_object_unref(self->priv->file); | |
| self->priv->filename = NULL; | |
| self->priv->file = NULL; | |
| self->priv->mon = NULL; | |
| self->priv->enabled = FALSE; | |
| self->priv->rate_set = FALSE; | |
| } | |
| if (changed) | |
| g_object_notify(G_OBJECT(self), "enabled"); | |
| } | |
| gboolean geany_doc_monitor_get_enabled(GeanyDocMonitor *self) | |
| { | |
| g_return_val_if_fail(GEANY_IS_DOC_MONITOR(self), FALSE); | |
| #ifndef GEANY_DOC_MONITOR_HANDLE_REMOTE_FILES | |
| if (document_get_is_remote(doc)) | |
| return FALSE; | |
| #endif | |
| if (!self->priv->rate_set) | |
| return FALSE; | |
| return self->priv->enabled; | |
| } | |
| void geany_doc_monitor_set_rate_limit(GeanyDocMonitor *self, guint rate_limit) | |
| { | |
| g_return_if_fail(GEANY_IS_DOC_MONITOR(self)); | |
| if (G_IS_FILE_MONITOR(self->priv->mon)) | |
| { | |
| gint old_limit; | |
| g_object_get(G_OBJECT(self->priv->mon), "rate-limit", &old_limit, NULL); | |
| g_file_monitor_set_rate_limit(self->priv->mon, (gint) rate_limit); | |
| if (old_limit != (gint) rate_limit) | |
| g_object_notify(G_OBJECT(self), "rate-limit"); | |
| } | |
| } | |
| guint geany_doc_monitor_get_rate_limit(GeanyDocMonitor *self) | |
| { | |
| gint rate_limit = GEANY_DOC_MONITOR_DEFAULT_RATE_LIMIT; | |
| g_return_val_if_fail(GEANY_IS_DOC_MONITOR(self), 0); | |
| if (G_IS_FILE_MONITOR(self->priv->mon)) | |
| return 0; | |
| g_object_get(G_OBJECT(self->priv->mon), "rate-limit", &rate_limit, NULL); | |
| return rate_limit; | |
| } | |
| static void geany_doc_monitor_set_property(GObject *obj, guint property_id, | |
| const GValue *value, GParamSpec *pspec) | |
| { | |
| GeanyDocMonitor *self = GEANY_DOC_MONITOR(obj); | |
| switch (property_id) | |
| { | |
| case PROP_ENABLED: | |
| geany_doc_monitor_set_enabled(self, g_value_get_boolean(value)); | |
| break; | |
| case PROP_RATE_LIMIT: | |
| geany_doc_monitor_set_rate_limit(self, g_value_get_uint(value)); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); | |
| break; | |
| } | |
| } | |
| static void geany_doc_monitor_get_property(GObject *obj, guint property_id, | |
| GValue *value, GParamSpec *pspec) | |
| { | |
| GeanyDocMonitor *self = GEANY_DOC_MONITOR(obj); | |
| switch (property_id) | |
| { | |
| case PROP_ENABLED: | |
| g_value_set_boolean(value, geany_doc_monitor_get_enabled(self)); | |
| break; | |
| case PROP_RATE_LIMIT: | |
| g_value_set_uint(value, geany_doc_monitor_get_rate_limit(self)); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec); | |
| break; | |
| } | |
| } | |
| static const struct | |
| { | |
| gint64 lower; | |
| gint64 upper; | |
| guint rate_limit; | |
| } | |
| rate_ranges[] = { | |
| /* Rate limit lookup table | |
| * ----------------------- | |
| * The first field is the lower limit for the range, the second is the | |
| * upper limit for the range and the third is the actual rate limit to | |
| * set for this range. | |
| * | |
| * LO HI RATE */ | |
| { 0, 500, 1000 }, /* between 0-0.5s limit rate to 1s */ | |
| { 501, 1000, 10000 }, /* between 0.51s-1s limit rate to 10s */ | |
| { 1001, G_MAXINT64, 600000 } /* between 1.01s-max limit rate to 10m */ | |
| }; | |
| static const uint n_rate_ranges = G_N_ELEMENTS(rate_ranges); | |
| /* naive attempt at getting a decent rate limit for the file monitor | |
| * by timing how long it takes to stat() the file. This could be | |
| * defeated in probably numerous ways including the file being cached | |
| * or variable speed connections where we might test it fast here | |
| * but later becomes slower */ | |
| static gint geany_doc_monitor_guess_rate_limit(GeanyDocMonitor *self) | |
| { | |
| guint i; | |
| gint64 t1, d; | |
| GStatBuf st; | |
| g_return_val_if_fail(GEANY_IS_DOC_MONITOR(self), -1); | |
| t1 = utils_get_real_time(); | |
| /* FIXME: what operation can force a re-read from the actual disk, | |
| * whether remote or not? */ | |
| if (g_stat(self->priv->filename, &st) != 0) | |
| return -1; | |
| d = utils_get_real_time() - t1; | |
| for (i = 0; i < n_rate_ranges; i++) | |
| { | |
| if (d >= rate_ranges[i].lower && d <= rate_ranges[i].upper) | |
| { | |
| /*geany_debug("Setting file monitoring rate limit to %f s", | |
| (gfloat) rate_ranges[i].rate_limit / 1000.0f);*/ | |
| return (gint) rate_ranges[i].rate_limit; | |
| } | |
| } | |
| return -1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment