diff options
Diffstat (limited to 'trunk/monitor/proxy/gproxyvolumemonitor.c')
-rw-r--r-- | trunk/monitor/proxy/gproxyvolumemonitor.c | 1354 |
1 files changed, 1354 insertions, 0 deletions
diff --git a/trunk/monitor/proxy/gproxyvolumemonitor.c b/trunk/monitor/proxy/gproxyvolumemonitor.c new file mode 100644 index 00000000..f836c19d --- /dev/null +++ b/trunk/monitor/proxy/gproxyvolumemonitor.c @@ -0,0 +1,1354 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* gvfs - extensions for gio + * + * Copyright (C) 2006-2009 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. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +/* TODO: handle force_rescan in g_mount_guess_content_type(); right now we + * just scan in the daemon first time the GMount is seen and + * cache that result forever. + */ + +#include <config.h> + +#include <limits.h> +#include <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gdbusutils.h> + +#include "gproxyvolumemonitor.h" +#include "gproxymount.h" +#include "gproxyvolume.h" +#include "gproxydrive.h" + +G_LOCK_DEFINE_STATIC(proxy_vm); + +static DBusConnection *the_session_bus = NULL; +static gboolean the_session_bus_is_integrated = FALSE; +static GHashTable *the_volume_monitors = NULL; + +struct _GProxyVolumeMonitor { + GNativeVolumeMonitor parent; + DBusConnection *session_bus; + + GHashTable *drives; + GHashTable *volumes; + GHashTable *mounts; + + /* The unique D-Bus name of the remote monitor or NULL if disconnected */ + gchar *unique_name; +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GProxyVolumeMonitor, + g_proxy_volume_monitor, + G_TYPE_NATIVE_VOLUME_MONITOR, + G_TYPE_FLAG_ABSTRACT, + {}) + +static void seed_monitor (GProxyVolumeMonitor *monitor); + +static DBusHandlerResult filter_function (DBusConnection *connection, DBusMessage *message, void *user_data); + +static void signal_emit_in_idle (gpointer object, const char *signal_name, gpointer other_object); + +static gboolean is_supported (GProxyVolumeMonitorClass *klass); + +/* The is_supported API is kinda lame and doesn't pass in the class, + so we work around this with this hack */ +typedef gboolean (*is_supported_func) (void); + +static GProxyVolumeMonitorClass *is_supported_classes[10] = { NULL }; +static gboolean is_supported_0 (void) { return is_supported (is_supported_classes[0]); }; +static gboolean is_supported_1 (void) { return is_supported (is_supported_classes[1]); }; +static gboolean is_supported_2 (void) { return is_supported (is_supported_classes[2]); }; +static gboolean is_supported_3 (void) { return is_supported (is_supported_classes[3]); }; +static gboolean is_supported_4 (void) { return is_supported (is_supported_classes[4]); }; +static gboolean is_supported_5 (void) { return is_supported (is_supported_classes[5]); }; +static gboolean is_supported_6 (void) { return is_supported (is_supported_classes[6]); }; +static gboolean is_supported_7 (void) { return is_supported (is_supported_classes[7]); }; +static gboolean is_supported_8 (void) { return is_supported (is_supported_classes[8]); }; +static gboolean is_supported_9 (void) { return is_supported (is_supported_classes[9]); }; +static is_supported_func is_supported_funcs[] = { + is_supported_0, is_supported_1, is_supported_2, is_supported_3, + is_supported_4, is_supported_5, is_supported_6, is_supported_7, + is_supported_8, is_supported_9, + NULL +}; + +static char * +get_match_rule_for_signals (GProxyVolumeMonitor *monitor) +{ + return g_strdup_printf ("type='signal'," + "interface='org.gtk.Private.RemoteVolumeMonitor'," + "sender='%s',", + g_proxy_volume_monitor_get_dbus_name (monitor)); +} + +static char * +get_match_rule_for_name_owner_changed (GProxyVolumeMonitor *monitor) +{ + return g_strdup_printf ("type='signal'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg0='%s'", + g_proxy_volume_monitor_get_dbus_name (monitor)); +} + +static void +g_proxy_volume_monitor_finalize (GObject *object) +{ + GProxyVolumeMonitor *monitor; + DBusError dbus_error; + char *match_rule; + GObjectClass *parent_class; + + /* since GProxyVolumeMonitor is a non-instantiatable type we're dealing with a + * sub-type here. So we need to look at the grandparent sub-type to get the + * parent class for GProxyVolumeMonitor */ + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent ( + g_type_class_peek_parent (G_OBJECT_GET_CLASS (object)))); + + monitor = G_PROXY_VOLUME_MONITOR (object); + + g_hash_table_unref (monitor->drives); + g_hash_table_unref (monitor->volumes); + g_hash_table_unref (monitor->mounts); + + g_free (monitor->unique_name); + + dbus_connection_remove_filter (monitor->session_bus, filter_function, monitor); + + match_rule = get_match_rule_for_signals (monitor); + dbus_error_init (&dbus_error); + dbus_bus_remove_match (monitor->session_bus, + match_rule, + &dbus_error); + if (dbus_error_is_set (&dbus_error)) { + g_warning ("cannot remove match rule '%s': %s: %s", match_rule, dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + } + g_free (match_rule); + + match_rule = get_match_rule_for_name_owner_changed (monitor); + dbus_error_init (&dbus_error); + dbus_bus_remove_match (monitor->session_bus, + match_rule, + &dbus_error); + if (dbus_error_is_set (&dbus_error)) { + g_warning ("cannot remove match rule '%s': %s: %s", match_rule, dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + } + g_free (match_rule); + + dbus_connection_unref (monitor->session_bus); + + if (parent_class->finalize) + parent_class->finalize (object); +} + +static void +g_proxy_volume_monitor_dispose (GObject *object) +{ + GProxyVolumeMonitor *monitor; + GObjectClass *parent_class; + + /* since GProxyVolumeMonitor is a non-instantiatable type we're dealing with a + * sub-type here. So we need to look at the grandparent sub-type to get the + * parent class for GProxyVolumeMonitor */ + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent ( + g_type_class_peek_parent (G_OBJECT_GET_CLASS (object)))); + + monitor = G_PROXY_VOLUME_MONITOR (object); + + /* Clear all objects to avoid circular dependencies keeping things alive. + * Note that atm we're keeping the union monitor alive, so this won't + * actually happen, but better safe than sorry in case we change this + * later */ + g_hash_table_remove_all (monitor->drives); + g_hash_table_remove_all (monitor->volumes); + g_hash_table_remove_all (monitor->mounts); + + if (parent_class->dispose) + parent_class->dispose (object); +} + + +static GList * +get_mounts (GVolumeMonitor *volume_monitor) +{ + GProxyVolumeMonitor *monitor; + GList *l; + GHashTableIter hash_iter; + GProxyMount *mount; + GProxyVolume *volume; + + monitor = G_PROXY_VOLUME_MONITOR (volume_monitor); + l = NULL; + + G_LOCK (proxy_vm); + + g_hash_table_iter_init (&hash_iter, monitor->mounts); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &mount)) + l = g_list_append (l, g_object_ref (mount)); + + /* also return shadow mounts */ + g_hash_table_iter_init (&hash_iter, monitor->volumes); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &volume)) + { + GProxyShadowMount *shadow_mount; + shadow_mount = g_proxy_volume_get_shadow_mount (volume); + if (shadow_mount != NULL) + l = g_list_append (l, shadow_mount); + } + + G_UNLOCK (proxy_vm); + + return l; +} + +static GList * +get_volumes (GVolumeMonitor *volume_monitor) +{ + GProxyVolumeMonitor *monitor; + GList *l; + GHashTableIter hash_iter; + GProxyVolume *volume; + + monitor = G_PROXY_VOLUME_MONITOR (volume_monitor); + l = NULL; + + G_LOCK (proxy_vm); + + g_hash_table_iter_init (&hash_iter, monitor->volumes); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &volume)) + l = g_list_append (l, g_object_ref (volume)); + + G_UNLOCK (proxy_vm); + + return l; +} + +static GList * +get_connected_drives (GVolumeMonitor *volume_monitor) +{ + GProxyVolumeMonitor *monitor; + GList *l; + GHashTableIter hash_iter; + GProxyDrive *drive; + + monitor = G_PROXY_VOLUME_MONITOR (volume_monitor); + l = NULL; + + G_LOCK (proxy_vm); + + g_hash_table_iter_init (&hash_iter, monitor->drives); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &drive)) + l = g_list_append (l, g_object_ref (drive)); + + G_UNLOCK (proxy_vm); + + return l; +} + +static GVolume * +get_volume_for_uuid (GVolumeMonitor *volume_monitor, const char *uuid) +{ + GProxyVolumeMonitor *monitor; + GHashTableIter hash_iter; + GVolume *found_volume; + GVolume *volume; + + monitor = G_PROXY_VOLUME_MONITOR (volume_monitor); + + G_LOCK (proxy_vm); + + found_volume = NULL; + g_hash_table_iter_init (&hash_iter, monitor->volumes); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &volume) && + found_volume != NULL) + { + char *_uuid; + _uuid = g_volume_get_uuid (volume); + if (_uuid != NULL) + { + if (strcmp (uuid, _uuid) == 0) + found_volume = g_object_ref (volume); + g_free (_uuid); + } + } + + G_UNLOCK (proxy_vm); + + return found_volume; +} + +static GMount * +get_mount_for_uuid (GVolumeMonitor *volume_monitor, const char *uuid) +{ + GProxyVolumeMonitor *monitor; + GHashTableIter hash_iter; + GMount *found_mount; + GMount *mount; + + monitor = G_PROXY_VOLUME_MONITOR (volume_monitor); + + G_LOCK (proxy_vm); + + found_mount = NULL; + g_hash_table_iter_init (&hash_iter, monitor->mounts); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &mount) && + found_mount != NULL) + { + char *_uuid; + _uuid = g_mount_get_uuid (mount); + if (_uuid != NULL) + { + if (strcmp (uuid, _uuid) == 0) + found_mount = g_object_ref (mount); + g_free (_uuid); + } + } + + G_UNLOCK (proxy_vm); + + return found_mount; +} + +static GMount * +get_mount_for_mount_path (const char *mount_path, + GCancellable *cancellable) +{ + GMount *mount; + GProxyVolumeMonitor *volume_monitor; + GProxyVolumeMonitorClass *klass; + GHashTableIter vm_hash_iter; + GHashTableIter vol_hash_iter; + GProxyMount *candidate_mount; + static GVolumeMonitor *union_monitor = NULL; + + /* There's a problem here insofar that this static method on GNativeVolumeMonitor can + * be called *before* any of our monitors are constructed. Since this method doesn't + * pass in the class structure we *know* which native remote monitor to use. + * + * To work around that, we get the singleton GVolumeMonitor... This will trigger + * construction of a GUnionVolumeMonitor in gio which will construct the *appropriate* + * remote volume monitors to use (it's up to gio to pick which one to use). + * + * Note that we will *hold* on to this reference effectively making us a resident + * module. And effectively keeping volume monitoring alive. + * + * The reason we hold on to the reference is that otherwise we'd be constructing/destructing + * *all* proxy volume monitors (which includes synchronous D-Bus calls to seed the monitor) + * every time this method is called. + * + * Note that *simple* GIO apps that a) don't use volume monitors; and b) don't use the + * g_file_find_enclosing_mount() method will never see any volume monitor overhead. + */ + + /* Note that g_volume_monitor_get() is thread safe. We don't want to call it while + * holding the proxy_vm lock since it might end up calling our constructor. + */ + if (union_monitor == NULL) + union_monitor = g_volume_monitor_get (); + + mount = NULL; + + G_LOCK (proxy_vm); + + /* First find the native volume monitor if one exists */ + g_hash_table_iter_init (&vm_hash_iter, the_volume_monitors); + while (g_hash_table_iter_next (&vm_hash_iter, NULL, (gpointer) &volume_monitor)) { + klass = G_PROXY_VOLUME_MONITOR_CLASS (G_OBJECT_GET_CLASS (volume_monitor)); + + if (klass->is_native) { + /* The see if we've got a mount */ + g_hash_table_iter_init (&vol_hash_iter, volume_monitor->mounts); + while (g_hash_table_iter_next (&vol_hash_iter, NULL, (gpointer) &candidate_mount)) { + if (g_proxy_mount_has_mount_path (candidate_mount, mount_path)) + { + mount = g_object_ref (candidate_mount); + goto out; + } + } + goto out; + } + } + + out: + G_UNLOCK (proxy_vm); + return mount; +} + +static void +volume_monitor_went_away (gpointer data, + GObject *where_the_object_was) +{ + GType type = (GType) data; + G_LOCK (proxy_vm); + g_hash_table_remove (the_volume_monitors, (gpointer) type); + G_UNLOCK (proxy_vm); +} + +static GObject * +g_proxy_volume_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + GProxyVolumeMonitor *monitor; + GProxyVolumeMonitorClass *klass; + GObjectClass *parent_class; + DBusError dbus_error; + char *match_rule; + + G_LOCK (proxy_vm); + + klass = G_PROXY_VOLUME_MONITOR_CLASS (g_type_class_peek (type)); + object = g_hash_table_lookup (the_volume_monitors, (gpointer) type); + if (object != NULL) + { + g_object_ref (object); + goto out; + } + + /* Invoke parent constructor. */ + klass = G_PROXY_VOLUME_MONITOR_CLASS (g_type_class_peek (G_TYPE_PROXY_VOLUME_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + object = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + monitor = G_PROXY_VOLUME_MONITOR (object); + + dbus_error_init (&dbus_error); + monitor->session_bus = dbus_connection_ref (the_session_bus); + monitor->drives = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + monitor->volumes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + monitor->mounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + dbus_connection_add_filter (monitor->session_bus, filter_function, monitor, NULL); + + /* listen to volume monitor signals */ + match_rule = get_match_rule_for_signals (monitor); + dbus_bus_add_match (monitor->session_bus, + match_rule, + &dbus_error); + if (dbus_error_is_set (&dbus_error)) { + g_warning ("cannot add match rule '%s': %s: %s", match_rule, dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + } + g_free (match_rule); + + /* listen to when the owner of the service appears/disappears */ + match_rule = get_match_rule_for_name_owner_changed (monitor); + dbus_bus_add_match (monitor->session_bus, + match_rule, + &dbus_error); + if (dbus_error_is_set (&dbus_error)) { + g_warning ("cannot add match rule '%s': %s: %s", match_rule, dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + } + g_free (match_rule); + + seed_monitor (monitor); + + g_hash_table_insert (the_volume_monitors, (gpointer) type, object); + g_object_weak_ref (G_OBJECT (object), volume_monitor_went_away, (gpointer) type); + + out: + G_UNLOCK (proxy_vm); + return object; +} + +typedef struct { + const char *signal_name; + GObject *object; + GObject *other_object; +} SignalEmitIdleData; + +static gboolean +signal_emit_in_idle_do (SignalEmitIdleData *data) +{ + if (data->other_object != NULL) + { + g_signal_emit_by_name (data->object, data->signal_name, data->other_object); + g_object_unref (data->other_object); + } + else + { + g_signal_emit_by_name (data->object, data->signal_name); + } + g_object_unref (data->object); + g_free (data); + + return FALSE; +} + +static void +signal_emit_in_idle (gpointer object, const char *signal_name, gpointer other_object) +{ + SignalEmitIdleData *data; + + data = g_new0 (SignalEmitIdleData, 1); + data->signal_name = signal_name; + data->object = g_object_ref (G_OBJECT (object)); + data->other_object = other_object != NULL ? g_object_ref (G_OBJECT (other_object)) : NULL; + g_idle_add ((GSourceFunc) signal_emit_in_idle_do, data); +} + + + +static DBusHandlerResult +filter_function (DBusConnection *connection, DBusMessage *message, void *user_data) +{ + GProxyVolumeMonitor *monitor = G_PROXY_VOLUME_MONITOR (user_data); + DBusMessageIter iter; + const char *id; + const char *the_dbus_name; + const char *member; + GProxyDrive *drive; + GProxyVolume *volume; + GProxyMount *mount; + GProxyVolumeMonitorClass *klass; + + G_LOCK (proxy_vm); + + klass = G_PROXY_VOLUME_MONITOR_CLASS (G_OBJECT_GET_CLASS (monitor)); + + member = dbus_message_get_member (message); + + if (dbus_message_is_signal (message, "org.freedesktop.DBus", "NameOwnerChanged")) + { + GHashTableIter hash_iter; + GProxyMount *mount; + GProxyVolume *volume; + GProxyDrive *drive; + const gchar *name; + const gchar *old_owner; + const gchar *new_owner; + + dbus_message_iter_init (message, &iter); + dbus_message_iter_get_basic (&iter, &name); + dbus_message_iter_next (&iter); + dbus_message_iter_get_basic (&iter, &old_owner); + dbus_message_iter_next (&iter); + dbus_message_iter_get_basic (&iter, &new_owner); + dbus_message_iter_next (&iter); + + if (strcmp (name, klass->dbus_name) != 0) + goto not_for_us; + + if (monitor->unique_name != NULL && g_strcmp0 (new_owner, monitor->unique_name) != 0) + { + g_warning ("Owner %s of volume monitor %s disconnected from the bus; removing drives/volumes/mounts", + monitor->unique_name, + klass->dbus_name); + + g_hash_table_iter_init (&hash_iter, monitor->mounts); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &mount)) + { + signal_emit_in_idle (mount, "unmounted", NULL); + signal_emit_in_idle (monitor, "mount-removed", mount); + } + g_hash_table_remove_all (monitor->mounts); + + g_hash_table_iter_init (&hash_iter, monitor->volumes); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &volume)) + { + signal_emit_in_idle (volume, "removed", NULL); + signal_emit_in_idle (monitor, "volume-removed", volume); + } + g_hash_table_remove_all (monitor->volumes); + + g_hash_table_iter_init (&hash_iter, monitor->drives); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &drive)) + { + signal_emit_in_idle (drive, "disconnected", NULL); + signal_emit_in_idle (monitor, "drive-disconnected", drive); + } + g_hash_table_remove_all (monitor->drives); + + g_free (monitor->unique_name); + monitor->unique_name = NULL; + + /* TODO: maybe try to relaunch the monitor? */ + + } + + if (strlen (new_owner) > 0 && monitor->unique_name == NULL) + { + g_warning ("New owner %s for volume monitor %s connected to the bus; seeding drives/volumes/mounts", + new_owner, + klass->dbus_name); + + seed_monitor (monitor); + + /* emit signals for all the drives/volumes/mounts "added" */ + g_hash_table_iter_init (&hash_iter, monitor->drives); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &drive)) + signal_emit_in_idle (monitor, "drive-connected", drive); + + g_hash_table_iter_init (&hash_iter, monitor->volumes); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &volume)) + signal_emit_in_idle (monitor, "volume-added", volume); + + g_hash_table_iter_init (&hash_iter, monitor->mounts); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &mount)) + signal_emit_in_idle (monitor, "mount-added", mount); + } + + } + else if (dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveChanged") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveConnected") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveDisconnected") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveEjectButton")) + { + + dbus_message_iter_init (message, &iter); + dbus_message_iter_get_basic (&iter, &the_dbus_name); + dbus_message_iter_next (&iter); + dbus_message_iter_get_basic (&iter, &id); + dbus_message_iter_next (&iter); + + if (strcmp (the_dbus_name, klass->dbus_name) != 0) + goto not_for_us; + + if (strcmp (member, "DriveChanged") == 0) + { + drive = g_hash_table_lookup (monitor->drives, id); + if (drive != NULL) + { + g_proxy_drive_update (drive, &iter); + signal_emit_in_idle (drive, "changed", NULL); + signal_emit_in_idle (monitor, "drive-changed", drive); + } + } + else if (strcmp (member, "DriveConnected") == 0) + { + drive = g_hash_table_lookup (monitor->drives, id); + if (drive == NULL) + { + drive = g_proxy_drive_new (monitor); + g_proxy_drive_update (drive, &iter); + g_hash_table_insert (monitor->drives, g_strdup (g_proxy_drive_get_id (drive)), drive); + signal_emit_in_idle (monitor, "drive-connected", drive); + } + } + else if (strcmp (member, "DriveDisconnected") == 0) + { + drive = g_hash_table_lookup (monitor->drives, id); + if (drive != NULL) + { + g_object_ref (drive); + g_hash_table_remove (monitor->drives, id); + signal_emit_in_idle (drive, "disconnected", NULL); + signal_emit_in_idle (monitor, "drive-disconnected", drive); + g_object_unref (drive); + } + } + else if (strcmp (member, "DriveEjectButton") == 0) + { + drive = g_hash_table_lookup (monitor->drives, id); + if (drive != NULL) + { + signal_emit_in_idle (drive, "eject-button", NULL); + signal_emit_in_idle (monitor, "drive-eject-button", drive); + } + } + + } + else if (dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "VolumeChanged") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "VolumeAdded") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "VolumeRemoved") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountOpAskPassword") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountOpAskQuestion") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountOpAborted")) + { + dbus_message_iter_init (message, &iter); + dbus_message_iter_get_basic (&iter, &the_dbus_name); + dbus_message_iter_next (&iter); + dbus_message_iter_get_basic (&iter, &id); + dbus_message_iter_next (&iter); + + if (strcmp (the_dbus_name, klass->dbus_name) != 0) + goto not_for_us; + + if (strcmp (member, "VolumeChanged") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume != NULL) + { + GProxyShadowMount *shadow_mount; + + g_proxy_volume_update (volume, &iter); + signal_emit_in_idle (volume, "changed", NULL); + signal_emit_in_idle (monitor, "volume-changed", volume); + + shadow_mount = g_proxy_volume_get_shadow_mount (volume); + if (shadow_mount != NULL) + { + signal_emit_in_idle (shadow_mount, "changed", NULL); + signal_emit_in_idle (monitor, "mount-changed", shadow_mount); + g_object_unref (shadow_mount); + } + } + } + else if (strcmp (member, "VolumeAdded") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume == NULL) + { + volume = g_proxy_volume_new (monitor); + g_proxy_volume_update (volume, &iter); + g_hash_table_insert (monitor->volumes, g_strdup (g_proxy_volume_get_id (volume)), volume); + signal_emit_in_idle (monitor, "volume-added", volume); + } + } + else if (strcmp (member, "VolumeRemoved") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume != NULL) + { + g_object_ref (volume); + g_hash_table_remove (monitor->volumes, id); + signal_emit_in_idle (volume, "removed", NULL); + signal_emit_in_idle (monitor, "volume-removed", volume); + g_object_unref (volume); + } + } + else if (strcmp (member, "MountOpAskPassword") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume != NULL) + g_proxy_volume_handle_mount_op_ask_password (volume, &iter); + } + else if (strcmp (member, "MountOpAskQuestion") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume != NULL) + g_proxy_volume_handle_mount_op_ask_question (volume, &iter); + } + else if (strcmp (member, "MountOpAborted") == 0) + { + volume = g_hash_table_lookup (monitor->volumes, id); + if (volume != NULL) + g_proxy_volume_handle_mount_op_aborted (volume, &iter); + } + + } + else if (dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountChanged") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountAdded") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountPreUnmount") || + dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "MountRemoved")) + { + + dbus_message_iter_init (message, &iter); + dbus_message_iter_get_basic (&iter, &the_dbus_name); + dbus_message_iter_next (&iter); + dbus_message_iter_get_basic (&iter, &id); + dbus_message_iter_next (&iter); + + if (strcmp (the_dbus_name, klass->dbus_name) != 0) + goto not_for_us; + + if (strcmp (member, "MountChanged") == 0) + { + mount = g_hash_table_lookup (monitor->mounts, id); + if (mount != NULL) + { + g_proxy_mount_update (mount, &iter); + signal_emit_in_idle (mount, "changed", NULL); + signal_emit_in_idle (monitor, "mount-changed", mount); + } + } + else if (strcmp (member, "MountAdded") == 0) + { + mount = g_hash_table_lookup (monitor->mounts, id); + if (mount == NULL) + { + mount = g_proxy_mount_new (monitor); + g_proxy_mount_update (mount, &iter); + g_hash_table_insert (monitor->mounts, g_strdup (g_proxy_mount_get_id (mount)), mount); + signal_emit_in_idle (monitor, "mount-added", mount); + } + } + else if (strcmp (member, "MountPreUnmount") == 0) + { + mount = g_hash_table_lookup (monitor->mounts, id); + if (mount != NULL) + { + signal_emit_in_idle (mount, "pre-unmount", NULL); + signal_emit_in_idle (monitor, "mount-pre-unmount", mount); + } + } + else if (strcmp (member, "MountRemoved") == 0) + { + mount = g_hash_table_lookup (monitor->mounts, id); + if (mount != NULL) + { + g_object_ref (mount); + g_hash_table_remove (monitor->mounts, id); + signal_emit_in_idle (mount, "unmounted", NULL); + signal_emit_in_idle (monitor, "mount-removed", mount); + g_object_unref (mount); + } + } + } + + not_for_us: + G_UNLOCK (proxy_vm); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void +g_proxy_volume_monitor_init (GProxyVolumeMonitor *monitor) +{ + g_proxy_volume_monitor_setup_session_bus_connection (TRUE); +} + +static void +g_proxy_volume_monitor_class_finalize (GProxyVolumeMonitorClass *klass) +{ + g_free (klass->dbus_name); +} + +typedef struct { + char *dbus_name; + gboolean is_native; + int is_supported_nr; +} ProxyClassData; + +static ProxyClassData * +proxy_class_data_new (const char *dbus_name, gboolean is_native) +{ + ProxyClassData *data; + static int is_supported_nr = 0; + + data = g_new0 (ProxyClassData, 1); + data->dbus_name = g_strdup (dbus_name); + data->is_native = is_native; + data->is_supported_nr = is_supported_nr++; + + g_assert (is_supported_funcs[data->is_supported_nr] != NULL); + + return data; +} + +static void +g_proxy_volume_monitor_class_intern_init_pre (GProxyVolumeMonitorClass *klass, gconstpointer class_data) +{ + ProxyClassData *data = (ProxyClassData *) class_data; + klass->dbus_name = g_strdup (data->dbus_name); + klass->is_native = data->is_native; + klass->is_supported_nr = data->is_supported_nr; + g_proxy_volume_monitor_class_intern_init (klass); +} + +static gboolean +is_remote_monitor_supported (const char *dbus_name) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError dbus_error; + dbus_bool_t is_supported; + + message = NULL; + reply = NULL; + is_supported = FALSE; + + message = dbus_message_new_method_call (dbus_name, + "/org/gtk/Private/RemoteVolumeMonitor", + "org.gtk.Private.RemoteVolumeMonitor", + "IsSupported"); + if (message == NULL) + { + g_warning ("Cannot allocate memory for DBusMessage"); + goto fail; + } + dbus_error_init (&dbus_error); + reply = dbus_connection_send_with_reply_and_block (the_session_bus, + message, + -1, + &dbus_error); + if (dbus_error_is_set (&dbus_error)) + { + g_warning ("invoking IsSupported() failed for remote volume monitor with dbus name %s: %s: %s", + dbus_name, + dbus_error.name, + dbus_error.message); + dbus_error_free (&dbus_error); + goto fail; + } + + if (!dbus_message_get_args (reply, &dbus_error, + DBUS_TYPE_BOOLEAN, &is_supported, + DBUS_TYPE_INVALID)) + { + g_warning ("Error parsing args in reply for IsSupported(): %s: %s", dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + goto fail; + } + + if (!is_supported) + g_warning ("remote volume monitor with dbus name %s is not supported", dbus_name); + + fail: + if (message != NULL) + dbus_message_unref (message); + if (reply != NULL) + dbus_message_unref (reply); + return is_supported; +} + +static gboolean +is_supported (GProxyVolumeMonitorClass *klass) +{ + gboolean res; + + G_LOCK (proxy_vm); + res = g_proxy_volume_monitor_setup_session_bus_connection (FALSE); + G_UNLOCK (proxy_vm); + + if (res) + res = is_remote_monitor_supported (klass->dbus_name); + + return res; +} + +static void +g_proxy_volume_monitor_class_init (GProxyVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + GNativeVolumeMonitorClass *native_class = G_NATIVE_VOLUME_MONITOR_CLASS (klass); + int i; + + gobject_class->constructor = g_proxy_volume_monitor_constructor; + gobject_class->finalize = g_proxy_volume_monitor_finalize; + gobject_class->dispose = g_proxy_volume_monitor_dispose; + + monitor_class->get_mounts = get_mounts; + monitor_class->get_volumes = get_volumes; + monitor_class->get_connected_drives = get_connected_drives; + monitor_class->get_volume_for_uuid = get_volume_for_uuid; + monitor_class->get_mount_for_uuid = get_mount_for_uuid; + + i = klass->is_supported_nr; + is_supported_classes[i] = klass; + monitor_class->is_supported = is_supported_funcs[i]; + + native_class->get_mount_for_mount_path = get_mount_for_mount_path; +} + +/* Call with proxy_vm lock held */ +static void +seed_monitor (GProxyVolumeMonitor *monitor) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError dbus_error; + DBusMessageIter iter_reply; + DBusMessageIter iter_array; + + message = dbus_message_new_method_call (g_proxy_volume_monitor_get_dbus_name (monitor), + "/org/gtk/Private/RemoteVolumeMonitor", + "org.gtk.Private.RemoteVolumeMonitor", + "List"); + if (message == NULL) + { + g_warning ("Cannot allocate memory for DBusMessage"); + goto fail; + } + dbus_error_init (&dbus_error); + reply = dbus_connection_send_with_reply_and_block (monitor->session_bus, + message, + -1, + &dbus_error); + dbus_message_unref (message); + if (dbus_error_is_set (&dbus_error)) + { + g_warning ("invoking List() failed for type %s: %s: %s", + G_OBJECT_TYPE_NAME (monitor), + dbus_error.name, + dbus_error.message); + dbus_error_free (&dbus_error); + goto fail; + } + + dbus_message_iter_init (reply, &iter_reply); + + /* TODO: verify signature */ + + /* drives */ + dbus_message_iter_recurse (&iter_reply, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + GProxyDrive *drive; + const char *id; + drive = g_proxy_drive_new (monitor); + g_proxy_drive_update (drive, &iter_array); + id = g_proxy_drive_get_id (drive); + g_hash_table_insert (monitor->drives, g_strdup (id), drive); + dbus_message_iter_next (&iter_array); + } + dbus_message_iter_next (&iter_reply); + + /* volumes */ + dbus_message_iter_recurse (&iter_reply, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + GProxyVolume *volume; + const char *id; + volume = g_proxy_volume_new (monitor); + g_proxy_volume_update (volume, &iter_array); + id = g_proxy_volume_get_id (volume); + g_hash_table_insert (monitor->volumes, g_strdup (id), volume); + dbus_message_iter_next (&iter_array); + } + dbus_message_iter_next (&iter_reply); + + /* mounts */ + dbus_message_iter_recurse (&iter_reply, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + GProxyMount *mount; + const char *id; + mount = g_proxy_mount_new (monitor); + g_proxy_mount_update (mount, &iter_array); + id = g_proxy_mount_get_id (mount); + g_hash_table_insert (monitor->mounts, g_strdup (id), mount); + dbus_message_iter_next (&iter_array); + } + dbus_message_iter_next (&iter_reply); + + monitor->unique_name = g_strdup (dbus_message_get_sender (reply)); + + dbus_message_unref (reply); + + fail: + ; +} + +GProxyDrive * +g_proxy_volume_monitor_get_drive_for_id (GProxyVolumeMonitor *volume_monitor, + const char *id) +{ + GProxyDrive *drive; + + G_LOCK (proxy_vm); + drive = g_hash_table_lookup (volume_monitor->drives, id); + if (drive != NULL) + g_object_ref (drive); + G_UNLOCK (proxy_vm); + + return drive; +} + +GProxyVolume * +g_proxy_volume_monitor_get_volume_for_id (GProxyVolumeMonitor *volume_monitor, + const char *id) +{ + GProxyVolume *volume; + + G_LOCK (proxy_vm); + volume = g_hash_table_lookup (volume_monitor->volumes, id); + if (volume != NULL) + g_object_ref (volume); + G_UNLOCK (proxy_vm); + + return volume; +} + +GProxyMount * +g_proxy_volume_monitor_get_mount_for_id (GProxyVolumeMonitor *volume_monitor, + const char *id) +{ + GProxyMount *mount; + + G_LOCK (proxy_vm); + mount = g_hash_table_lookup (volume_monitor->mounts, id); + if (mount != NULL) + g_object_ref (mount); + G_UNLOCK (proxy_vm); + + return mount; +} + + +GHashTable * +_get_identifiers (DBusMessageIter *iter) +{ + GHashTable *hash_table; + DBusMessageIter iter_array; + + hash_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_free); + + dbus_message_iter_recurse (iter, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + DBusMessageIter iter_dict_entry; + const char *key; + const char *value; + + dbus_message_iter_recurse (&iter_array, &iter_dict_entry); + dbus_message_iter_get_basic (&iter_dict_entry, &key); + dbus_message_iter_next (&iter_dict_entry); + dbus_message_iter_get_basic (&iter_dict_entry, &value); + + g_hash_table_insert (hash_table, g_strdup (key), g_strdup (value)); + + dbus_message_iter_next (&iter_array); + } + + return hash_table; +} + +DBusConnection * +g_proxy_volume_monitor_get_dbus_connection (GProxyVolumeMonitor *volume_monitor) +{ + return dbus_connection_ref (volume_monitor->session_bus); +} + +const char * +g_proxy_volume_monitor_get_dbus_name (GProxyVolumeMonitor *volume_monitor) +{ + GProxyVolumeMonitorClass *klass = G_PROXY_VOLUME_MONITOR_CLASS (G_OBJECT_GET_CLASS (volume_monitor)); + return klass->dbus_name; +} + +static void +register_volume_monitor (GTypeModule *type_module, + const char *type_name, + const char *dbus_name, + gboolean is_native, + int priority) +{ + GType type; + const GTypeInfo type_info = { + sizeof (GProxyVolumeMonitorClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) g_proxy_volume_monitor_class_intern_init_pre, + (GClassFinalizeFunc) g_proxy_volume_monitor_class_finalize, + (gconstpointer) proxy_class_data_new (dbus_name, is_native), /* class_data (leaked!) */ + sizeof (GProxyVolumeMonitor), + 0, /* n_preallocs */ + (GInstanceInitFunc) g_proxy_volume_monitor_init, + NULL /* value_table */ + }; + + type = g_type_module_register_type (type_module, + G_TYPE_PROXY_VOLUME_MONITOR, + type_name, + &type_info, + 0 /* type_flags */); + + g_io_extension_point_implement (is_native ? G_NATIVE_VOLUME_MONITOR_EXTENSION_POINT_NAME : + G_VOLUME_MONITOR_EXTENSION_POINT_NAME, + type, + type_name, + priority); +} + +/* Call with proxy_vm lock held */ +gboolean +g_proxy_volume_monitor_setup_session_bus_connection (gboolean need_integration) +{ + gboolean ret; + DBusError dbus_error; + + ret = FALSE; + + if (the_session_bus != NULL) + goto has_bus_already; + + /* This is so that system daemons can use gio + * without spawning private dbus instances. + * See bug 526454. + */ + if (g_getenv ("DBUS_SESSION_BUS_ADDRESS") == NULL) + goto out; + + dbus_error_init (&dbus_error); + the_session_bus = dbus_bus_get_private (DBUS_BUS_SESSION, &dbus_error); + if (dbus_error_is_set (&dbus_error)) + { + g_warning ("cannot connect to the session bus: %s: %s", dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + goto out; + } + + the_volume_monitors = g_hash_table_new (g_direct_hash, g_direct_equal); + + has_bus_already: + + if (need_integration && !the_session_bus_is_integrated) + { + _g_dbus_connection_integrate_with_main (the_session_bus); + the_session_bus_is_integrated = TRUE; + } + + ret = TRUE; + + out: + return ret; +} + +void +g_proxy_volume_monitor_teardown_session_bus_connection (void) +{ + G_LOCK (proxy_vm); + if (the_session_bus != NULL) + { + if (the_session_bus_is_integrated) + _g_dbus_connection_remove_from_main (the_session_bus); + the_session_bus_is_integrated = FALSE; + dbus_connection_close (the_session_bus); + the_session_bus = NULL; + + g_hash_table_unref (the_volume_monitors); + the_volume_monitors = NULL; + } + G_UNLOCK (proxy_vm); +} + +void +g_proxy_volume_monitor_register (GIOModule *module) +{ + GDir *dir; + GError *error; + + /* first register the abstract base type... */ + g_proxy_volume_monitor_register_type (G_TYPE_MODULE (module)); + + /* ... then register instantiable types for each remote volume + * monitor - each remote volume monitor is defined in a key-value + * file in $(datadir)/gvfs/remote-volume-monitors that must have + * the suffix .monitor. Each file specifies + * + * - the name of the volume monitor + * - the name of the D-Bus service + * - whether the volume monitor is native + * - and if so the priority + */ + + error = NULL; + dir = g_dir_open (REMOTE_VOLUME_MONITORS_DIR, 0, &error); + if (dir == NULL) + { + g_warning ("cannot open directory " REMOTE_VOLUME_MONITORS_DIR ": %s", error->message); + g_error_free (error); + } + else + { + const char *name; + + while ((name = g_dir_read_name (dir)) != NULL) + { + GKeyFile *key_file; + char *type_name; + char *path; + char *dbus_name; + gboolean is_native; + int native_priority; + + type_name = NULL; + key_file = NULL; + dbus_name = NULL; + path = NULL; + + if (!g_str_has_suffix (name, ".monitor")) + goto cont; + + path = g_build_filename (REMOTE_VOLUME_MONITORS_DIR, name, NULL); + + key_file = g_key_file_new (); + error = NULL; + if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error)) + { + g_warning ("error loading key-value file %s: %s", path, error->message); + g_error_free (error); + goto cont; + } + + type_name = g_key_file_get_string (key_file, "RemoteVolumeMonitor", "Name", &error); + if (error != NULL) + { + g_warning ("error extracting Name key from %s: %s", path, error->message); + g_error_free (error); + goto cont; + } + + dbus_name = g_key_file_get_string (key_file, "RemoteVolumeMonitor", "DBusName", &error); + if (error != NULL) + { + g_warning ("error extracting DBusName key from %s: %s", path, error->message); + g_error_free (error); + goto cont; + } + + is_native = g_key_file_get_boolean (key_file, "RemoteVolumeMonitor", "IsNative", &error); + if (error != NULL) + { + g_warning ("error extracting IsNative key from %s: %s", path, error->message); + g_error_free (error); + goto cont; + } + + if (is_native) + { + native_priority = g_key_file_get_integer (key_file, "RemoteVolumeMonitor", "NativePriority", &error); + if (error != NULL) + { + g_warning ("error extracting NativePriority key from %s: %s", path, error->message); + g_error_free (error); + goto cont; + } + } + else + { + native_priority = 0; + } + + register_volume_monitor (G_TYPE_MODULE (module), + type_name, + dbus_name, + is_native, + native_priority); + + cont: + + g_free (type_name); + g_free (dbus_name); + g_free (path); + if (key_file != NULL) + g_key_file_free (key_file); + } + g_dir_close (dir); + } +} |