/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. * * Authors: Alexander Larsson <alexl@redhat.com> * John McCutchan <john@johnmccutchan.com> * Sebastian Dröge <slomo@circular-chaos.org> */ #include "config.h" #include <fam.h> #include <gio/gfilemonitor.h> #include "gfile.h" #include "fam-helper.h" static FAMConnection* fam_connection = NULL; static gint fam_watch_id = 0; G_LOCK_DEFINE_STATIC(fam_connection); struct _fam_sub { gchar *pathname; gboolean directory; gpointer user_data; gboolean cancelled; FAMRequest request; }; /* This uses int as the argument type because the real type differs between implementations: gamin has "typedef enum FAMCodes {....} FAMCodes;" fam has "enum FAMCodes { ... }". */ static GFileMonitorEvent fam_event_to_file_monitor_event (int code) { switch (code) { case FAMChanged: return G_FILE_MONITOR_EVENT_CHANGED; break; case FAMDeleted: return G_FILE_MONITOR_EVENT_DELETED; break; case FAMCreated: return G_FILE_MONITOR_EVENT_CREATED; break; default: return -1; break; } } static gboolean fam_do_iter_unlocked (void) { while (fam_connection != NULL && FAMPending (fam_connection)) { FAMEvent ev; fam_sub* sub = NULL; gboolean cancelled; if (FAMNextEvent (fam_connection, &ev) != 1) { FAMClose (fam_connection); g_free (fam_connection); g_source_remove (fam_watch_id); fam_watch_id = 0; fam_connection = NULL; return FALSE; } sub = (fam_sub*)ev.userdata; cancelled = sub->cancelled; if (ev.code == FAMAcknowledge && cancelled) { _fam_sub_free (sub); continue; } if (cancelled) continue; if (sub->directory) { GFileMonitor* monitor = G_FILE_MONITOR (sub->user_data); GFileMonitorEvent eflags = fam_event_to_file_monitor_event (ev.code); gchar* path = NULL; GFile *child, *parent; /* unsupported event */ if (eflags == -1) continue; if (ev.filename[0] == '/') path = g_strdup (ev.filename); else path = g_strdup_printf ("%s/%s", sub->pathname, ev.filename); child = g_file_new_for_path (path); parent = g_file_get_parent (child); g_file_monitor_emit_event (monitor, child, NULL, eflags); g_free (path); g_object_unref (child); g_object_unref (parent); } else { GFile *child; GFileMonitor* monitor = G_FILE_MONITOR (sub->user_data); GFileMonitorEvent eflags = fam_event_to_file_monitor_event (ev.code); gchar* path = NULL; if (eflags == -1) continue; path = g_strdup (ev.filename); child = g_file_new_for_path (path); g_file_monitor_emit_event (monitor, child, NULL, eflags); g_free (path); g_object_unref (child); } } return TRUE; } static gboolean fam_callback (GIOChannel *source, GIOCondition condition, gpointer data) { gboolean res; G_LOCK (fam_connection); res = fam_do_iter_unlocked (); G_UNLOCK (fam_connection); return res; } gboolean _fam_sub_startup (void) { GIOChannel *ioc; G_LOCK (fam_connection); if (fam_connection == NULL) { fam_connection = g_new0 (FAMConnection, 1); if (FAMOpen2 (fam_connection, "gvfs user") != 0) { g_warning ("FAMOpen failed, FAMErrno=%d\n", FAMErrno); g_free (fam_connection); fam_connection = NULL; G_UNLOCK (fam_connection); return FALSE; } #ifdef HAVE_FAM_NO_EXISTS /* This is a gamin extension that avoids sending all the Exists event for dir monitors */ FAMNoExists (fam_connection); #endif ioc = g_io_channel_unix_new (FAMCONNECTION_GETFD(fam_connection)); fam_watch_id = g_io_add_watch (ioc, G_IO_IN | G_IO_HUP | G_IO_ERR, fam_callback, fam_connection); g_io_channel_unref (ioc); } G_UNLOCK (fam_connection); return TRUE; } void _fam_sub_shutdown (void) { G_LOCK (fam_connection); if (fam_connection != NULL) { FAMClose (fam_connection); g_free (fam_connection); g_source_remove (fam_watch_id); fam_watch_id = 0; fam_connection = NULL; } G_UNLOCK (fam_connection); } fam_sub* _fam_sub_add (const gchar *pathname, gboolean directory, gpointer user_data) { fam_sub *sub; if (!_fam_sub_startup ()) return NULL; G_LOCK (fam_connection); /* We need to queue up incoming messages to avoid blocking on write * if there are many monitors being canceled */ fam_do_iter_unlocked (); if (fam_connection == NULL) { G_UNLOCK (fam_connection); return NULL; } sub = g_new0 (fam_sub, 1); sub->pathname = g_strdup (pathname); sub->directory = directory; sub->user_data = user_data; if (directory) FAMMonitorDirectory (fam_connection, pathname, &sub->request, sub); else FAMMonitorFile (fam_connection, pathname, &sub->request, sub); G_UNLOCK (fam_connection); return sub; } gboolean _fam_sub_cancel (fam_sub* sub) { if (sub->cancelled) return TRUE; sub->cancelled = TRUE; G_LOCK (fam_connection); /* We need to queue up incoming messages to avoid blocking on write * if there are many monitors being canceled */ fam_do_iter_unlocked (); if (fam_connection == NULL) { G_UNLOCK (fam_connection); return FALSE; } FAMCancelMonitor (fam_connection, &sub->request); G_UNLOCK (fam_connection); return TRUE; } void _fam_sub_free (fam_sub* sub) { g_free (sub->pathname); g_free (sub); }