diff options
Diffstat (limited to 'libmenu/menu-monitor.c')
-rw-r--r-- | libmenu/menu-monitor.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/libmenu/menu-monitor.c b/libmenu/menu-monitor.c new file mode 100644 index 0000000..ff36010 --- /dev/null +++ b/libmenu/menu-monitor.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2005 Red Hat, Inc. + * Copyright (C) 2006 Mark McLoughlin + * Copyright (C) 2007 Sebastian Dröge + * Copyright (C) 2008 Vincent Untz + * + * 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. + */ + +#include <config.h> + +#include "menu-monitor.h" + +#include <gio/gio.h> + +#include "menu-util.h" + +struct MenuMonitor { + char* path; + guint refcount; + + GSList* notifies; + + GFileMonitor* monitor; + + guint is_directory: 1; +}; + +typedef struct { + MenuMonitor* monitor; + MenuMonitorEvent event; + char* path; +} MenuMonitorEventInfo; + +typedef struct { + MenuMonitorNotifyFunc notify_func; + gpointer user_data; + guint refcount; +} MenuMonitorNotify; + +static MenuMonitorNotify* mate_menu_monitor_notify_ref(MenuMonitorNotify* notify); +static void mate_menu_monitor_notify_unref(MenuMonitorNotify* notify); + +static GHashTable* monitors_registry = NULL; +static guint events_idle_handler = 0; +static GSList* pending_events = NULL; + +static void invoke_notifies(MenuMonitor* monitor, MenuMonitorEvent event, const char* path) +{ + GSList *copy; + GSList *tmp; + + copy = g_slist_copy (monitor->notifies); + g_slist_foreach (copy, + (GFunc) mate_menu_monitor_notify_ref, + NULL); + + tmp = copy; + while (tmp != NULL) + { + MenuMonitorNotify *notify = tmp->data; + GSList *next = tmp->next; + + if (notify->notify_func) + { + notify->notify_func (monitor, event, path, notify->user_data); + } + + mate_menu_monitor_notify_unref(notify); + + tmp = next; + } + + g_slist_free (copy); +} + +static gboolean emit_events_in_idle(void) +{ + GSList *events_to_emit; + GSList *tmp; + + events_to_emit = pending_events; + + pending_events = NULL; + events_idle_handler = 0; + + tmp = events_to_emit; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + + mate_menu_monitor_ref(event_info->monitor); + + tmp = tmp->next; + } + + tmp = events_to_emit; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + + invoke_notifies (event_info->monitor, + event_info->event, + event_info->path); + + menu_monitor_unref (event_info->monitor); + event_info->monitor = NULL; + + g_free (event_info->path); + event_info->path = NULL; + + event_info->event = MENU_MONITOR_EVENT_INVALID; + + g_free (event_info); + + tmp = tmp->next; + } + + g_slist_free (events_to_emit); + + return FALSE; +} + +static void menu_monitor_queue_event(MenuMonitorEventInfo* event_info) +{ + pending_events = g_slist_append (pending_events, event_info); + + if (events_idle_handler == 0) + { + events_idle_handler = g_idle_add ((GSourceFunc) emit_events_in_idle, NULL); + } +} + +static inline char* get_registry_key(const char* path, gboolean is_directory) +{ + return g_strdup_printf ("%s:%s", + path, + is_directory ? "<dir>" : "<file>"); +} + +static gboolean monitor_callback (GFileMonitor* monitor, GFile* child, GFile* other_file, GFileMonitorEvent eflags, gpointer user_data) +{ + MenuMonitorEventInfo *event_info; + MenuMonitorEvent event; + MenuMonitor *menu_monitor = (MenuMonitor *) user_data; + + event = MENU_MONITOR_EVENT_INVALID; + switch (eflags) + { + case G_FILE_MONITOR_EVENT_CHANGED: + event = MENU_MONITOR_EVENT_CHANGED; + break; + case G_FILE_MONITOR_EVENT_CREATED: + event = MENU_MONITOR_EVENT_CREATED; + break; + case G_FILE_MONITOR_EVENT_DELETED: + event = MENU_MONITOR_EVENT_DELETED; + break; + default: + return TRUE; + } + + event_info = g_new0 (MenuMonitorEventInfo, 1); + + event_info->path = g_file_get_path (child); + event_info->event = event; + event_info->monitor = menu_monitor; + + menu_monitor_queue_event (event_info); + + return TRUE; +} + +static MenuMonitor* register_monitor(const char* path, gboolean is_directory) +{ + static gboolean initted = FALSE; + MenuMonitor *retval; + GFile *file; + + if (!initted) + { + /* This is the only place where we're using GObject and the app can't + * know we're using it, so we need to init the type system ourselves. */ + g_type_init (); + initted = TRUE; + } + + retval = g_new0 (MenuMonitor, 1); + + retval->path = g_strdup (path); + retval->refcount = 1; + retval->is_directory = is_directory != FALSE; + + file = g_file_new_for_path (retval->path); + + if (file == NULL) + { + menu_verbose ("Not adding monitor on '%s', failed to create GFile\n", + retval->path); + return retval; + } + + if (retval->is_directory) + retval->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, + NULL, NULL); + else + retval->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, + NULL, NULL); + + g_object_unref (G_OBJECT (file)); + + if (retval->monitor == NULL) + { + menu_verbose ("Not adding monitor on '%s', failed to create monitor\n", + retval->path); + return retval; + } + + g_signal_connect (retval->monitor, "changed", + G_CALLBACK (monitor_callback), retval); + + return retval; +} + +static MenuMonitor* lookup_monitor(const char* path, gboolean is_directory) +{ + MenuMonitor *retval; + char *registry_key; + + retval = NULL; + + registry_key = get_registry_key (path, is_directory); + + if (monitors_registry == NULL) + { + monitors_registry = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + } + else + { + retval = g_hash_table_lookup (monitors_registry, registry_key); + } + + if (retval == NULL) + { + retval = register_monitor (path, is_directory); + g_hash_table_insert (monitors_registry, registry_key, retval); + + return retval; + } + else + { + g_free (registry_key); + + return mate_menu_monitor_ref(retval); + } +} + +MenuMonitor* mate_menu_monitor_file_get(const char* path) +{ + g_return_val_if_fail(path != NULL, NULL); + + return lookup_monitor(path, FALSE); +} + +MenuMonitor* menu_get_directory_monitor(const char* path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return lookup_monitor (path, TRUE); +} + +MenuMonitor* mate_menu_monitor_ref(MenuMonitor* monitor) +{ + g_return_val_if_fail(monitor != NULL, NULL); + g_return_val_if_fail(monitor->refcount > 0, NULL); + + monitor->refcount++; + + return monitor; +} + +static void menu_monitor_clear_pending_events(MenuMonitor* monitor) +{ + GSList *tmp; + + tmp = pending_events; + while (tmp != NULL) + { + MenuMonitorEventInfo *event_info = tmp->data; + GSList *next = tmp->next; + + if (event_info->monitor == monitor) + { + pending_events = g_slist_delete_link (pending_events, tmp); + + g_free (event_info->path); + event_info->path = NULL; + + event_info->monitor = NULL; + event_info->event = MENU_MONITOR_EVENT_INVALID; + + g_free (event_info); + } + + tmp = next; + } +} + +void menu_monitor_unref(MenuMonitor* monitor) +{ + char *registry_key; + + g_return_if_fail (monitor != NULL); + g_return_if_fail (monitor->refcount > 0); + + if (--monitor->refcount > 0) + return; + + registry_key = get_registry_key (monitor->path, monitor->is_directory); + g_hash_table_remove (monitors_registry, registry_key); + g_free (registry_key); + + if (g_hash_table_size (monitors_registry) == 0) + { + g_hash_table_destroy (monitors_registry); + monitors_registry = NULL; + } + + if (monitor->monitor) + { + g_file_monitor_cancel (monitor->monitor); + g_object_unref (monitor->monitor); + monitor->monitor = NULL; + } + + g_slist_foreach (monitor->notifies, (GFunc) mate_menu_monitor_notify_unref, NULL); + g_slist_free (monitor->notifies); + monitor->notifies = NULL; + + menu_monitor_clear_pending_events (monitor); + + g_free (monitor->path); + monitor->path = NULL; + + g_free (monitor); +} + +static MenuMonitorNotify* mate_menu_monitor_notify_ref(MenuMonitorNotify* notify) +{ + g_return_val_if_fail(notify != NULL, NULL); + g_return_val_if_fail(notify->refcount > 0, NULL); + + notify->refcount++; + + return notify; +} + +static void mate_menu_monitor_notify_unref(MenuMonitorNotify* notify) +{ + g_return_if_fail(notify != NULL); + g_return_if_fail(notify->refcount > 0); + + if (--notify->refcount > 0) + { + return; + } + + g_free(notify); +} + +void menu_monitor_add_notify(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data) +{ + MenuMonitorNotify* notify; + + g_return_if_fail(monitor != NULL); + g_return_if_fail(notify_func != NULL); + + GSList* tmp = monitor->notifies; + + while (tmp != NULL) + { + notify = tmp->data; + + if (notify->notify_func == notify_func && notify->user_data == user_data) + { + break; + } + + tmp = tmp->next; + } + + if (tmp == NULL) + { + notify = g_new0(MenuMonitorNotify, 1); + notify->notify_func = notify_func; + notify->user_data = user_data; + notify->refcount = 1; + + monitor->notifies = g_slist_append(monitor->notifies, notify); + } +} + +void mate_menu_monitor_notify_remove(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data) +{ + GSList* tmp = monitor->notifies; + + while (tmp != NULL) + { + MenuMonitorNotify* notify = tmp->data; + GSList* next = tmp->next; + + if (notify->notify_func == notify_func && notify->user_data == user_data) + { + notify->notify_func = NULL; + notify->user_data = NULL; + + mate_menu_monitor_notify_unref(notify); + + monitor->notifies = g_slist_delete_link(monitor->notifies, tmp); + } + + tmp = next; + } +} |