diff -Nuarp libegg/ChangeLog libegg.filemonitor/ChangeLog --- libegg/ChangeLog 2006-05-04 11:58:42.000000000 +0100 +++ libegg.filemonitor/ChangeLog 2006-05-07 22:55:25.000000000 +0100 @@ -1,3 +1,22 @@ +2006-05-07 Emmanuele Bassi + + Add EggFileMonitor, a GSource based file monitoring + API for GLib (bug #340741). + + * libegg/filemonitor/README + * libegg/filemonitor/eggfilemonitor.h: + * libegg/filemonitor/eggfilemonitor.c: EggFileMonitor. + + * libegg/filemonitor/test-simple.c: Simple test case for + the default implementation. + + * libegg/filemonitor/test-vfs.c: Test case using gnome-vfs + to override the default implementation. + + * configure.in: + * libegg/Makefile.am: + * libegg/filemonitor/Makefile.am: Build glue for EggFileMonitor. + 2006-05-04 Emmanuele Bassi * libegg/bookmarkfile/README: Add a notice about the BookmarkFile diff -Nuarp libegg/libegg/filemonitor/eggfilemonitor.c libegg.filemonitor/libegg/filemonitor/eggfilemonitor.c --- libegg/libegg/filemonitor/eggfilemonitor.c 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/eggfilemonitor.c 2006-05-07 22:37:11.000000000 +0100 @@ -0,0 +1,467 @@ +/* eggfilemonitor.c: simple/pluggable file monitoring + * Copyright (C) 2006 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + */ + +#include "config.h" + +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include + +#ifdef G_OS_UNIX +#include +#include +#endif + +#include +#include + +#include "eggfilemonitor.h" + +typedef struct _EggFileMonitorSource EggFileMonitorSource; + +struct _EggFileMonitorSource +{ + GSource source; + + gchar *uri; + EggFileEvent last_event; + gint last_poll_delta; + time_t last_mtime; + + guint exists : 1; +}; + + +static gboolean egg_file_monitor_prepare (GSource *source, + gint *timeout); +static gboolean egg_file_monitor_check (GSource *source); +static gboolean egg_file_monitor_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static void egg_file_monitor_finalize (GSource *source); + +static GSourceFuncs egg_file_monitor_funcs = +{ + egg_file_monitor_prepare, + egg_file_monitor_check, + egg_file_monitor_dispatch, + egg_file_monitor_finalize +}; + +G_LOCK_DEFINE_STATIC (egg_file_monitor_hooks); +static EggFileMonitorHooks egg_file_monitor_hooks = +{ + NULL, + NULL, + NULL +}; + +static gpointer egg_file_monitor_hooks_data = NULL; + +static gboolean +check_for_file_changes (EggFileMonitorSource *fm_source) +{ + GError *err; + gchar *filename; + struct stat stat_buf; + int stat_res; + + err = NULL; + filename = g_filename_from_uri (fm_source->uri, NULL, &err); + if (err) + { + g_warning ("Unable to obtain the file name from URI '%s': %s", + fm_source->uri, + err->message); + g_error_free (err); + + fm_source->last_event = EGG_FILE_UNKNOWN_EVENT; + + return FALSE; + } + + stat_res = g_stat (filename, &stat_buf); + g_free (filename); + + if (stat_res < 0) + { + if ((errno == ENOENT) && fm_source->exists) + { + fm_source->last_event = EGG_FILE_REMOVED_EVENT; + fm_source->exists = FALSE; + fm_source->last_mtime = (time_t) -1; + + return TRUE; + } + else + return FALSE; + } + else + { + if (fm_source->last_mtime == stat_buf.st_mtime) + return FALSE; + + if (!fm_source->exists) + { + fm_source->last_event = EGG_FILE_CREATED_EVENT; + fm_source->last_mtime = stat_buf.st_mtime; + fm_source->exists = TRUE; + } + else + { + fm_source->last_event = EGG_FILE_CHANGED_EVENT; + fm_source->last_mtime = stat_buf.st_mtime; + } + + return TRUE; + } + + return FALSE; +} + +#define POLL_DELTA_MAX 500 +#define POLL_DELTA_QUANTUM 100 + +static gboolean +egg_file_monitor_prepare (GSource *source, + gint *timeout) +{ + EggFileMonitorSource *fm_source = (EggFileMonitorSource *) source; + gboolean was_changed; + EggFileEvent old_last_event = fm_source->last_event; + EggFileEvent new_event; + + if (!egg_file_monitor_hooks.check_uri) + { + was_changed = check_for_file_changes (fm_source); + + if (was_changed) + fm_source->last_poll_delta = 0; + else + { + /* simple quantum-based timeout delay logic to avoid stat() + * storms; we update the timeout depending on the last event: if + * the file was removed, set the poll time to the maximum + * delta available; otherwise, if it was last created or + * changed, add a quantum of time until the timeout reaches + * the maximum delta available. + */ + if (old_last_event == EGG_FILE_REMOVED_EVENT) + fm_source->last_poll_delta = POLL_DELTA_MAX; + else + { + gint new_delta; + + new_delta = fm_source->last_poll_delta + POLL_DELTA_QUANTUM; + fm_source->last_poll_delta = MIN (new_delta, POLL_DELTA_MAX); + } + } + } + else + { + was_changed = (egg_file_monitor_hooks.check_uri) (fm_source->uri, + &new_event, + egg_file_monitor_hooks_data); + if (was_changed) + { + fm_source->last_event = new_event; + fm_source->last_poll_delta = 0; + } + else + fm_source->last_poll_delta = POLL_DELTA_MAX; + } + + *timeout = fm_source->last_poll_delta; + + return (fm_source->last_poll_delta == 0); +} + +static gboolean +egg_file_monitor_check (GSource *source) +{ + EggFileMonitorSource *fm_source = (EggFileMonitorSource *) source; + + if (!egg_file_monitor_hooks.check_uri) + return check_for_file_changes (fm_source); + else + { + EggFileEvent event; + gboolean retval; + + retval = (egg_file_monitor_hooks.check_uri) (fm_source->uri, + &event, + egg_file_monitor_hooks_data); + + if (retval) + fm_source->last_event = event; + + return retval; + } + +} + +static gboolean +egg_file_monitor_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + EggFileMonitorSource *fm_source; + EggFileMonitorFunc fm_callback = (EggFileMonitorFunc) callback; + + fm_source = (EggFileMonitorSource *) source; + + if (!callback) + { + g_warning ("File monitor source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + if (fm_callback (fm_source->uri, fm_source->last_event, user_data)) + return TRUE; + else + { + if (egg_file_monitor_hooks.cancel_monitor) + (egg_file_monitor_hooks.cancel_monitor) (fm_source->uri, + egg_file_monitor_hooks_data); + + return FALSE; + } +} + +static void +egg_file_monitor_finalize (GSource *source) +{ + EggFileMonitorSource *fm_source; + + fm_source = (EggFileMonitorSource *) source; + + g_free (fm_source->uri); +} + +/** + * egg_file_monitor_source_new: + * @uri: a valid URI + * @events: the #EggFileEventMask mask to watch + * + * Creates a new file monitor source. + * + * The source will not initially be associated with any #GMainContext + * and must be added to one with g_source_attach() before it will be + * executed. Note that the default priority for file monitor sources + * is %G_PRIORITY_DEFAULT_IDLE, as compared to other sources which + * have a default priority of %G_PRIORITY_DEFAULT. + * + * Return value: the newly-created file monitor source + * + * Since: 2.12 + */ +GSource * +egg_file_monitor_source_new (const gchar *uri) +{ + GSource *source; + EggFileMonitorSource *fm_source; + GError *err; + gchar *filename; + struct stat stat_buf; + int stat_res; + gboolean exists = FALSE; + time_t last_mtime = (time_t) -1; + + g_return_val_if_fail (uri != NULL, NULL); + + err = NULL; + filename = g_filename_from_uri (uri, NULL, &err); + if (err) + { + g_warning ("Invalid URI '%s': %s", uri, err->message); + g_error_free (err); + + return NULL; + } + + stat_res = g_stat (filename, &stat_buf); + g_free (filename); + + if (stat_res < 0) + { + if (errno == ENOENT) + exists = FALSE; + else + { + g_warning ("g_stat() of %s failed: %s", uri, g_strerror (errno)); + + return NULL; + } + } + else + { + exists = TRUE; + last_mtime = stat_buf.st_mtime; + } + + source = g_source_new (&egg_file_monitor_funcs, sizeof (EggFileMonitorSource)); + g_source_set_priority (source, G_PRIORITY_DEFAULT_IDLE); + + fm_source = (EggFileMonitorSource *) source; + fm_source->uri = g_strdup (uri); + fm_source->last_event = EGG_FILE_UNKNOWN_EVENT; + fm_source->last_mtime = last_mtime; + fm_source->exists = exists; + + return source; +} + +/** + * egg_file_monitor_add_full: + * @priority: the priority of the file monitor source. Typically, this + * whill be in the range between #G_PRIORITY_DEFAULT_IDLE and + * #G_PRIORITY_HIGH_IDLE. + * @uri: a valid URI + * @function: function to be called + * @data: data to pass to @function + * @notify: function called when the file monitor is removed, or %NULL + * + * Sets a function to be called when the file pointed by @uri + * is created, changed or removed, with the given priority. + * + * If the function returns %FALSE it is automatically removed + * from the list of event sources and will not be called again. + * + * Note that the default implementation relies on a timed g_stat(), + * and should therefore be used only on local files (using a file:/ URI). + * If you want to control how the files are checked, you can override + * the default implementation using egg_set_file_monitor_hooks(). + * + * Return value: the ID (greater than 0) of the event source + * + * Since: 2.12 + */ +guint +egg_file_monitor_add_full (gint priority, + const gchar *uri, + EggFileMonitorFunc function, + gpointer data, + GDestroyNotify notify) +{ + GSource *source; + guint id; + + g_return_val_if_fail (function != NULL, 0); + + source = egg_file_monitor_source_new (uri); + + if (priority != G_PRIORITY_DEFAULT_IDLE) + g_source_set_priority (source, priority); + + g_source_set_callback (source, (GSourceFunc) function, data, notify); + + if (egg_file_monitor_hooks.add_monitor) + { + EggFileMonitorSource *fm_source = (EggFileMonitorSource *) source; + gboolean res; + + res = (egg_file_monitor_hooks.add_monitor) (fm_source->uri, + egg_file_monitor_hooks_data); + if (!res) + { + g_warning ("The add_monitor hook returned an error condition\n"); + g_source_unref (source); + + return 0; + } + } + + id = g_source_attach (source, NULL); + g_source_unref (source); + + return id; +} + +/** + * egg_file_monitor_add: + * @uri: a valid URI + * @function: function to be called + * @data: data to be passed to @function + * + * Sets a function to be called when the file pointed by @uri + * is created, changed or removed. + * + * The function is given the default idle priority, %G_PRIORITY_DEFAULT_IDLE. + * If the function returns %FALSE it is automatically removed + * from the list of event sources and will not be called again. + * + * Return value: the ID (greater than 0) of the event source + * + * Since: 2.12 + */ +guint +egg_file_monitor_add (const gchar *uri, + EggFileMonitorFunc function, + gpointer data) +{ + return egg_file_monitor_add_full (G_PRIORITY_DEFAULT_IDLE, uri, function, data, NULL); +} + +/** + * egg_set_file_monitor_hooks: + * @hooks: a #EggFileMonitorHooks + * @data: data to be passed to the hooks + * + * Sets the functions to be called when creating/removing a file monitor + * source and the function used to check the URI of a file monitor. + * + * You can use these functions to plug an external file monitoring system + * inside every file monitor source. + * + * You must call this function before adding any file monitor using + * g_file_monitor_add() or g_file_monitor_add_full(), and before + * attaching a #GSource created using g_file_monitor_source_new() to + * a #GMainContext. + * + * Since: 2.12 + */ +void +egg_set_file_monitor_hooks (EggFileMonitorHooks *hooks, + gpointer user_data) +{ + g_return_if_fail (hooks != NULL); + + G_LOCK (egg_file_monitor_hooks); + + if (hooks->add_monitor) + egg_file_monitor_hooks.add_monitor = hooks->add_monitor; + + if (hooks->cancel_monitor) + egg_file_monitor_hooks.cancel_monitor = hooks->cancel_monitor; + + if (hooks->check_uri) + egg_file_monitor_hooks.check_uri = hooks->check_uri; + + G_UNLOCK (egg_file_monitor_hooks); + + egg_file_monitor_hooks_data = user_data; +} diff -Nuarp libegg/libegg/filemonitor/eggfilemonitor.h libegg.filemonitor/libegg/filemonitor/eggfilemonitor.h --- libegg/libegg/filemonitor/eggfilemonitor.h 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/eggfilemonitor.h 2006-05-07 21:46:29.000000000 +0100 @@ -0,0 +1,68 @@ +/* eggfilemonitor.h: simple/pluggable file monitoring + * Copyright (C) 2006 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + */ + +#ifndef __EGG_FILE_MONITOR_H__ +#define __EGG_FILE_MONITOR_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef enum { + EGG_FILE_CREATED_EVENT, + EGG_FILE_CHANGED_EVENT, + EGG_FILE_REMOVED_EVENT, + EGG_FILE_UNKNOWN_EVENT +} EggFileEvent; + +typedef struct _EggFileMonitorHooks EggFileMonitorHooks; + +typedef gboolean (*EggFileMonitorFunc) (const gchar *uri, + EggFileEvent event, + gpointer user_data); + + +GSource *egg_file_monitor_source_new (const gchar *uri); + +guint egg_file_monitor_add_full (gint priority, + const gchar *uri, + EggFileMonitorFunc function, + gpointer data, + GDestroyNotify notify); +guint egg_file_monitor_add (const gchar *uri, + EggFileMonitorFunc func, + gpointer data); + +struct _EggFileMonitorHooks +{ + gboolean (*add_monitor) (const gchar *uri, + gpointer user_data); + gboolean (*cancel_monitor) (const gchar *uri, + gpointer user_data); + gboolean (*check_uri) (const gchar *uri, + EggFileEvent *event, + gpointer user_data); +}; + +void egg_set_file_monitor_hooks (EggFileMonitorHooks *hooks, + gpointer data); + +G_END_DECLS + +#endif /* __EGG_FILE_MONITOR_H__ */ diff -Nuarp libegg/libegg/filemonitor/Makefile.am libegg.filemonitor/libegg/filemonitor/Makefile.am --- libegg/libegg/filemonitor/Makefile.am 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/Makefile.am 2006-05-04 12:21:06.000000000 +0100 @@ -0,0 +1,32 @@ +NULL = + +INCLUDES = \ + -DG_LOG_DOMAIN="\"EggFileMonitor\"" \ + $(NULL) + +noinst_LTLIBRARIES = libeggfilemonitor.la + +libeggfilemonitor_la_CFLAGS = $(EGG_CFLAGS) +libeggfilemonitor_la_LIBADD = $(EGG_LIBS) + +libeggfilemonitor_la_SOURCES = \ + eggfilemonitor.c \ + $(NULL) + +noinst_HEADERS = \ + eggfilemonitor.h \ + $(NULL) + +noinst_PROGRAMS = test-simple test-vfs + +test_simple_SOURCES = test-simple.c +test_simple_CFLAGS = $(EGG_CFLAGS) +test_simple_LDFLAGS = $(EGG_LIBS) $(top_builddir)/libegg/filemonitor/libeggfilemonitor.la +test_simple_DEPENDENCIES = $(top_builddir)/libegg/filemonitor/libeggfilemonitor.la + +test_vfs_SOURCES = test-vfs.c +test_vfs_CFLAGS = $(EGG_CFLAGS) $(EGG_VFS_FS_CFLAGS) +test_vfs_LDFLAGS = $(EGG_LIBS) $(EGG_VFS_FS_LIBS) $(top_builddir)/libegg/filemonitor/libeggfilemonitor.la +test_vfs_DEPENDENCIES = $(top_builddir)/libegg/filemonitor/libeggfilemonitor.la + +EXTRA_DIST = README diff -Nuarp libegg/libegg/filemonitor/README libegg.filemonitor/libegg/filemonitor/README --- libegg/libegg/filemonitor/README 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/README 2006-05-07 22:49:46.000000000 +0100 @@ -0,0 +1,45 @@ +EggFileMonitor - Simple, pluggable file monitoring API for GLib + +* DESCRIPTION + +EggFileMonitor provides a simple API for attaching a file monitor +with notification of events inside GLib's main loop, using GSource, +like the idle and timeout API. Typically, you should use it like +this: + + gint id; + + id = egg_file_monitor_add ("file:///some/path/to/file.txt", + file_monitor_cb, + NULL); + +where file_monitor_cb is a function with this definition: + +gboolean +file_monitor_cb (const gchar *uri, + EggFileEvent event, + gpointer user_data); + +EggFileEvent contains the last event that was notified by the monitor; +if you return FALSE from within the callback, the source will be +removed. + +The bonus point about EggFileMonitor is that its internal implementation +can be overridden per-process by using hook functions. Inside this +directory you'll find a simple test case using the default implementation +and the same test case modified to use gnome-vfs to override the +default implementation. + +See bug #340741 for further reference. + +* TO DO + +[ ] Provide a default implementation using i-notify instead of + stat()-ing the file; this requires a i-notify GSource, or + hooking the i-notify fd polling inside EggFileMonitorSource. +[ ] Provide a win32 default implementation. + + ++++ + +$ Last Edited: 2006-05-07 22:49 (+0100), Emmanuele Bassi $ diff -Nuarp libegg/libegg/filemonitor/test-simple.c libegg.filemonitor/libegg/filemonitor/test-simple.c --- libegg/libegg/filemonitor/test-simple.c 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/test-simple.c 2006-05-07 14:43:42.000000000 +0100 @@ -0,0 +1,49 @@ +#include +#include +#include + +#include + +#include "eggfilemonitor.h" + +#define TEST_URI "file:///tmp/test.txt" + +static gboolean +file_monitor_cb (const gchar *uri, + EggFileEvent event, + gpointer user_data) +{ + switch (event) + { + case EGG_FILE_CREATED_EVENT: + g_print ("File '%s' has been created\n", uri); + break; + case EGG_FILE_CHANGED_EVENT: + g_print ("File '%s' has been changed\n", uri); + break; + case EGG_FILE_REMOVED_EVENT: + g_print ("File '%s' has been removed\n", uri); + break; + default: + g_assert_not_reached (); + return FALSE; + } + + return TRUE; +} + +int main (int argc, char *argv[]) +{ + GMainLoop *main_loop; + guint id; + + main_loop = g_main_loop_new (NULL, FALSE); + + id = egg_file_monitor_add (TEST_URI, file_monitor_cb, NULL); + + g_main_loop_run (main_loop); + + g_source_remove (id); + + return 0; +} diff -Nuarp libegg/libegg/filemonitor/test-vfs.c libegg.filemonitor/libegg/filemonitor/test-vfs.c --- libegg/libegg/filemonitor/test-vfs.c 1970-01-01 01:00:00.000000000 +0100 +++ libegg.filemonitor/libegg/filemonitor/test-vfs.c 2006-05-07 22:25:40.000000000 +0100 @@ -0,0 +1,170 @@ +#include +#include +#include + +#include + +#include +#include + +#include "eggfilemonitor.h" + +static gboolean +file_monitor_cb (const gchar *uri, + EggFileEvent event, + gpointer user_data) +{ + switch (event) + { + case EGG_FILE_CREATED_EVENT: + g_print ("File '%s' has been created\n", uri); + break; + case EGG_FILE_CHANGED_EVENT: + g_print ("File '%s' has been changed\n", uri); + break; + case EGG_FILE_REMOVED_EVENT: + g_print ("File '%s' has been removed\n", uri); + break; + case EGG_FILE_UNKNOWN_EVENT: + break; + default: + g_print ("Unknown event id %d\n", event); + return FALSE; + } + + return TRUE; +} + +typedef struct _FMData +{ + GnomeVFSMonitorHandle *handle; + + guint uri_changed : 1; + GnomeVFSMonitorEventType last_event; +} FMData; + +static void +fm_monitor_cb (GnomeVFSMonitorHandle *gnome_handle, + const gchar *monitor_uri, + const char *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + FMData *fm_data = user_data; + + g_print ("(in %s) event %d\n", G_STRFUNC, event_type); + + fm_data->uri_changed = TRUE; + fm_data->last_event = event_type; +} + +static gboolean +fm_add_monitor (const gchar *uri, + gpointer user_data) +{ + FMData *fm_data = user_data; + GnomeVFSResult res; + + g_print ("(in %s) adding monitor for %s\n", G_STRFUNC, uri); + + res = gnome_vfs_monitor_add (&(fm_data->handle), + uri, + GNOME_VFS_MONITOR_FILE, + fm_monitor_cb, + fm_data); + + return (res == GNOME_VFS_OK); +} + +static gboolean +fm_cancel_monitor (const gchar *uri, + gpointer user_data) +{ + FMData *fm_data = user_data; + GnomeVFSResult res; + + g_print ("(in %s) removing the monitor on %s\n", + G_STRFUNC, + uri); + + res = gnome_vfs_monitor_cancel (fm_data->handle); + + return (res == GNOME_VFS_OK); +} + +static gboolean +fm_check_uri (const gchar *uri, + EggFileEvent *event, + gpointer user_data) +{ + FMData *fm_data = user_data; + + if (fm_data->uri_changed) + { + switch (fm_data->last_event) + { + case GNOME_VFS_MONITOR_EVENT_CREATED: + *event = EGG_FILE_CREATED_EVENT; + break; + case GNOME_VFS_MONITOR_EVENT_CHANGED: + case GNOME_VFS_MONITOR_EVENT_METADATA_CHANGED: + *event = EGG_FILE_CHANGED_EVENT; + break; + case GNOME_VFS_MONITOR_EVENT_DELETED: + *event = EGG_FILE_REMOVED_EVENT; + break; + default: + *event = EGG_FILE_UNKNOWN_EVENT; + break; + } + + /* reset the guard */ + fm_data->uri_changed = FALSE; + fm_data->last_event = -1; + + return TRUE; + } + + *event = EGG_FILE_UNKNOWN_EVENT; + + return FALSE; +} + +static EggFileMonitorHooks file_monitor_hooks = +{ + fm_add_monitor, + fm_cancel_monitor, + fm_check_uri +}; + +int main (int argc, char *argv[]) +{ + FMData *data; + GMainLoop *main_loop; + guint id; + + if (argc < 2) + { + g_print ("Usage: test-vfs \n"); + + return EXIT_FAILURE; + } + + gnome_vfs_init (); + + main_loop = g_main_loop_new (NULL, FALSE); + + data = g_new0 (FMData, 1); + egg_set_file_monitor_hooks (&file_monitor_hooks, data); + + id = egg_file_monitor_add (argv[1], file_monitor_cb, NULL); + + g_main_loop_run (main_loop); + + g_source_remove (id); + g_free (data); + + gnome_vfs_shutdown (); + + return EXIT_SUCCESS; +}