Skip to content

Instantly share code, notes, and snippets.

@codebrainz
Created December 13, 2011 00:02
Show Gist options
  • Select an option

  • Save codebrainz/1469745 to your computer and use it in GitHub Desktop.

Select an option

Save codebrainz/1469745 to your computer and use it in GitHub Desktop.
GeanyDocMonitor
/*
* 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