[PATCH 5/6] nm-applet: monitor mounts/unmounts, ask for auto-mount
- From: Dominik Sommer <dominik sommer name>
- To: networkmanager-list gnome org
- Subject: [PATCH 5/6] nm-applet: monitor mounts/unmounts, ask for auto-mount
- Date: Mon, 16 Apr 2012 14:33:04 +0200
---
 src/applet.c                                 |  441 +++++++++++++++++++
 src/applet.h                                 |    6 +
diff --git a/src/applet.c b/src/applet.c
index e6a3e2e..b56516f 100644
--- a/src/applet.c
+++ b/src/applet.c
@@ -52,6 +52,7 @@
 #include <nm-device-wimax.h>
 #include <nm-utils.h>
 #include <nm-connection.h>
+#include <nm-remote-connection.h>
 #include <nm-vpn-connection.h>
 #include <nm-setting-connection.h>
 #include <nm-setting-wired.h>
@@ -61,6 +62,7 @@
 #include <nm-setting-cdma.h>
 #include <nm-setting-bluetooth.h>
 #include <nm-setting-vpn.h>
+#include <nm-setting-resources.h>
 #include <nm-active-connection.h>
 #include <nm-secret-agent.h>
@@ -3447,6 +3449,13 @@ static void finalize (GObject *object)
 {
     NMApplet *applet = NM_APPLET (object);
+    // Disconnect & free volume monitoring
+    g_signal_handler_disconnect(applet->volume_monitor,
+            applet->mount_removed_handler_id);
+    g_signal_handler_disconnect(applet->volume_monitor,
+            applet->mount_added_handler_id);
+    g_object_unref(applet->volume_monitor);
+
     g_slice_free (NMADeviceClass, applet->wired_class);
     g_slice_free (NMADeviceClass, applet->wifi_class);
     g_slice_free (NMADeviceClass, applet->gsm_class);
@@ -3512,6 +3521,424 @@ static void finalize (GObject *object)
     G_OBJECT_CLASS (nma_parent_class)->finalize (object);
 }
+/**
+ * @user_data the uri of the network drive
+ */
+static void
+commit_changes_cb (NMRemoteConnection *connection,
+                                              GError *error,
+                                              gpointer user_data)
+{
+    // Clear secrets from memory
+    nm_setting_clear_secrets ( NM_SETTING (
+            
nm_connection_get_setting_wireless_security(&connection->parent)));
+
+    if (error == NULL)
+    {
+        g_message("Changed auto-mounting for %s with connection %s",
+                (char*) user_data, 
nm_connection_get_id(&connection->parent));
+    }
+    else
+    {
+        g_warning("Failed to change auto-mounting for %s with 
connection %s: %s",
+                (char*) user_data, 
nm_connection_get_id(&connection->parent),
+                error->message);
+        g_error_free(error);
+    }
+
+    g_free(user_data);
+}
+
+/**
+ * @user_data the uri of the network drive
+ */
+static void
+get_secrets_cb (NMRemoteConnection *connection,
+                GHashTable *secrets,
+                GError *error,
+                gpointer user_data)
+{
+    GHashTable *settings, *settings_secrets;
+    GHashTableIter iter;
+    gpointer key, value;
+    NMSettingWirelessSecurity *sec;
+
+    if (error)
+    {
+        g_warning("Error getting secrets for connection %s while 
changing auto-mount %s for %s:",
+                nm_connection_get_id(&connection->parent),
+                (char *) user_data,
+                error->message);
+        g_free(user_data);
+        return;
+    }
+
+    // Merge wireless security settings with acquired secrets
+    settings = nm_connection_to_hash (&connection->parent,
+            NM_SETTING_HASH_FLAG_ALL);
+    settings = g_hash_table_lookup(settings, 
NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
+    settings_secrets = g_hash_table_lookup(secrets, 
NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
+    g_hash_table_iter_init (&iter, settings_secrets);
+    while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+        if (g_hash_table_lookup(settings, key) != NULL) continue;
+        g_hash_table_insert (settings, key, value);
+    }
+
+    // Re-add merged settings to connection
+    sec = NM_SETTING_WIRELESS_SECURITY 
(nm_setting_new_from_hash(NM_TYPE_SETTING_WIRELESS_SECURITY, settings));
+    nm_connection_add_setting(&connection->parent, NM_SETTING(sec));
+
+    // Commit settings
+    nm_remote_connection_commit_changes(NM_REMOTE_CONNECTION (connection),
+            commit_changes_cb, user_data);
+}
+
+static void
+autoconnect_cb (NotifyNotification *notify,
+                             gchar *id,
+                             gpointer user_data)
+{
+    NMConnection *connection = NM_CONNECTION(user_data);
+    NMSettingResources *setting =
+            nm_connection_get_setting_resources (connection);
+
+    // Assure connection has resources settings
+    if (!NM_IS_SETTING_RESOURCES(setting))
+    {
+        setting = NM_SETTING_RESOURCES (nm_setting_resources_new ());
+        nm_connection_add_setting (connection, NM_SETTING (setting));
+    }
+
+    // Add to connection's automount list
+    if (!nm_setting_resources_add_network_drive(setting, id))
+    {
+        g_error("Failed to add auto-mounting for %s with connection %s",
+                id, nm_connection_get_id(connection));
+        return;
+    }
+
+    // Secrets need to be acquired & explicitly re-added to the connection
+    // so they don't get lost
+    // The g_strdup(id) is required as id is free'd for some reason
+    // TODO: Not just wireless / only wireless if the connection is 
wireless
+    nm_remote_connection_get_secrets (
+            NM_REMOTE_CONNECTION (connection),
+            NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+            get_secrets_cb,
+            (gpointer) g_strdup(id));
+}
+
+static void
+mount_added_cb (GVolumeMonitor *volume_monitor,
+        GMount         *mount,
+        gpointer        user_data)
+{
+    NotifyNotification *notify;
+    GError *error = NULL;
+    char *escaped;
+    NMApplet *applet = NM_APPLET (user_data);
+    gboolean is_network_scheme = FALSE;
+    const char **allowed_schemes;
+    GSList *iter;
+    char *scheme, *uri, *tmp;
+    GFile *file;
+    GSList *active_networks = NULL;
+    const GPtrArray *active_list;
+    int i;
+
+    // Only continue if notification server allows actions,
+    // as the notification window is pointless otherwise
+    g_return_if_fail (applet != NULL);
+    g_return_if_fail (applet_notify_server_has_actions ());
+    g_return_if_fail (gtk_status_icon_is_embedded (applet->status_icon));
+
+    // Only continue with mounts that are auto-mountable
+    i = 0;
+    allowed_schemes = nm_setting_resources_get_allowed_schemes();
+    scheme = g_file_get_uri_scheme(g_mount_get_default_location(mount));
+    while ((!is_network_scheme) &&
+            (scheme != NULL) &&
+            (allowed_schemes[i] != NULL))
+    {
+        if (strcasecmp (allowed_schemes[i], scheme) == 0)
+            is_network_scheme = TRUE;
+        i++;
+    }
+    if (!is_network_scheme) return;
+
+    // Clean uri from trailing slash
+    file = g_mount_get_default_location(mount);
+    uri = g_file_get_uri(file);
+    if (g_str_has_suffix(uri, "/"))
+    {
+        tmp = g_strndup(uri, strlen(uri) - 1);
+        g_free(uri);
+        uri = tmp;
+    }
+    g_object_unref(file);
+
+    // Create list of active network connections to choose from in
+    // notification popup
+    // TODO: Check if this works with active VPN connections
+    active_list = nm_client_get_active_connections (applet->nm_client);
+    for (i = 0; active_list && (i < active_list->len); i++) {
+        NMActiveConnection *active =
+                NM_ACTIVE_CONNECTION (g_ptr_array_index (active_list, i));
+        NMConnection *connection =
+                applet_get_connection_for_active (applet, active);
+        NMSettingResources *settings =
+                nm_connection_get_setting_resources (connection);
+
+        // Only check supported connection types (e.g. configuring without
+        // messing secrets; supported in connection editor etc.)
+        if (!nm_connection_is_type (connection, 
NM_SETTING_WIRELESS_SETTING_NAME))
+            continue;
+
+        // Filter out if this mount is already configured for this 
connection
+        if (NM_IS_SETTING_RESOURCES (settings))
+        {
+            if (nm_setting_resources_has_network_drive (settings, uri))
+            {
+                g_debug("%s is already known on current active network 
%s; "
+                        "not asking for automount",
+                        uri, nm_connection_get_id (connection));
+                continue;
+            }
+        }
+        else
+        {
+            g_debug("Connection %s does not have any resources (for 
auto-mounting) configured",
+                    nm_connection_get_id (connection));
+        }
+
+        // TODO IF VPN IS SUPPORTED: Filter out if this mount is 
already on *any* connection
+
+        active_networks = g_slist_append (active_networks, connection);
+    }
+
+    if (!active_networks)
+    {
+        g_free(uri);
+        return;
+    }
+
+    // Display notification window & ask whether to always mount this share
+    // Following based on applet_do_notify, but enhanced for more than 
one action
+    applet_clear_notify (applet);
+    tmp = g_mount_get_name(mount);
+    if (g_slist_length(active_networks) == 1)
+        escaped = utils_escape_notify_message (g_strdup_printf(
+                _("Do you want to mount\n%s\nautomatically when 
connected to\n%s?"),
+                tmp,
+                nm_connection_get_id((NMConnection *) 
g_slist_nth_data(active_networks, 0))));
+    else
+        escaped = utils_escape_notify_message (g_strdup_printf(
+                _("Do you want to mount\n%s\nautomatically?"),
+                tmp));
+    notify = notify_notification_new (_("Mount automatically?"),
+                                      escaped,
+                                      "nm-device-wired"
+#if HAVE_LIBNOTIFY_07
+                                      );
+#else
+                                      , NULL);
+#endif
+    g_free (tmp);
+    g_free (escaped);
+    applet->notification = notify;
+
+#if HAVE_LIBNOTIFY_07
+    notify_notification_set_hint (notify, "transient", 
g_variant_new_boolean (TRUE));
+#else
+    notify_notification_attach_to_status_icon (notify, 
applet->status_icon);
+#endif
+    notify_notification_set_urgency (notify, NOTIFY_URGENCY_LOW);
+    notify_notification_set_timeout (notify, NOTIFY_EXPIRES_DEFAULT);
+
+    // Add active networks to choose from
+    for (iter = active_networks; iter; iter = g_slist_next (iter)) {
+        NMConnection *connection = NM_CONNECTION(iter->data);
+
+        notify_notification_add_action (
+                notify,
+                uri,
+                (g_slist_length(active_networks) == 1) ?
+                        _("Mount automatically") :
+                        g_strdup_printf (_("For %s"), 
nm_connection_get_id(connection)),
+                autoconnect_cb,
+                connection,
+                NULL);
+    }
+    g_slist_free(active_networks);
+
+    if (!notify_notification_show (notify, &error)) {
+        g_warning ("Failed to show notification: %s",
+                   error && error->message ? error->message : "(unknown)");
+        g_clear_error (&error);
+        g_free(uri);
+    }
+}
+
+
+static void
+remove_autoconnect_cb (NotifyNotification *notify,
+                             gchar *id,
+                             gpointer user_data)
+{
+    NMConnection *connection = NM_CONNECTION(user_data);
+    NMSettingResources *setting =
+            nm_connection_get_setting_resources (connection);
+
+    // Assure connection has resources settings
+    if (!NM_IS_SETTING_RESOURCES(setting))
+    {
+        setting = NM_SETTING_RESOURCES (nm_setting_resources_new ());
+        nm_connection_add_setting (connection, NM_SETTING (setting));
+    }
+
+    // Remove from connection's automount list
+    nm_setting_resources_remove_network_drive_by_uri(setting, id);
+
+    // Secrets need to be acquired & explicitly re-added to the connection
+    // so they don't get lost
+    // TODO: Not just wireless / only wireless if the connection is 
wireless
+    nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (connection),
+            NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+            get_secrets_cb,
+            (gpointer) g_strdup(id));
+}
+
+static void
+mount_removed_cb (GVolumeMonitor *volume_monitor,
+        GMount         *mount,
+        gpointer        user_data)
+{
+    NotifyNotification *notify;
+    GError *error = NULL;
+    char *escaped;
+    NMApplet *applet = NM_APPLET (user_data);
+    GSList *iter;
+    char *uri, *tmp;
+    GFile *file;
+    GSList *active_networks = NULL;
+    const GPtrArray *active_list;
+    int i;
+
+    // Only continue if notification server allows actions,
+    // as the notification window is pointless otherwise
+    g_return_if_fail (applet != NULL);
+    g_return_if_fail (applet_notify_server_has_actions ());
+    g_return_if_fail (gtk_status_icon_is_embedded (applet->status_icon));
+
+    // Clean uri
+    file = g_mount_get_default_location(mount);
+    uri = g_file_get_uri(file);
+    if (g_str_has_suffix(uri, "/"))
+    {
+        tmp = g_strndup(uri, strlen(uri) - 1);
+        g_free(uri);
+        uri = tmp;
+    }
+    g_object_unref(file);
+
+    // Only continue if unmount was caused by disconnection
+    if (applet->disconnecting)
+    {
+        g_debug("Network is disconnecting; not reacting to unmount of 
%s", uri);
+        g_free(uri);
+        return;
+    }
+
+    // Create list of active network connections that have this connection
+    // in their auto-mount lists
+    // TODO: Check if this works with active VPN connections
+    active_list = nm_client_get_active_connections (applet->nm_client);
+    for (i = 0; active_list && (i < active_list->len); i++) {
+        NMActiveConnection *active =
+                NM_ACTIVE_CONNECTION (g_ptr_array_index (active_list, i));
+        NMConnection *connection =
+                applet_get_connection_for_active (applet, active);
+        NMSettingResources *settings =
+                nm_connection_get_setting_resources (connection);
+
+        // Only check supported connection types (e.g. configuring without
+        // messing secrets; supported in connection editor etc.)
+        if (!nm_connection_is_type (connection, 
NM_SETTING_WIRELESS_SETTING_NAME))
+            continue;
+
+        // Filter out if this mount is already configured for this 
connection
+        if (NM_IS_SETTING_RESOURCES (settings))
+        {
+            if (nm_setting_resources_has_network_drive (settings, uri))
+            {
+                active_networks = g_slist_append (active_networks, 
connection);
+            }
+        }
+    }
+
+    if (!active_networks)
+    {
+        g_free(uri);
+        return;
+    }
+
+    // Display notification window & ask whether to always mount this share
+    // Following copy & paste from applet_do_notify, enhanced for more 
actions
+    applet_clear_notify (applet);
+    tmp = g_mount_get_name(mount);
+    if (g_slist_length(active_networks) == 1)
+        escaped = utils_escape_notify_message (g_strdup_printf(
+                _("You just unmounted\n%s\nDo you want to remove 
auto-mounting for\n%s?"),
+                tmp, nm_connection_get_id((NMConnection *) 
g_slist_nth_data(active_networks, 0))));
+    else
+        escaped = utils_escape_notify_message (g_strdup_printf(
+                _("You just unmounted\n%s\nDo you want to remove 
auto-mounting?"),
+                tmp));
+    notify = notify_notification_new (_("Also remove auto-mounting?"),
+                                      escaped,
+                                      "nm-device-wired"
+#if HAVE_LIBNOTIFY_07
+                                      );
+#else
+                                      , NULL);
+#endif
+    g_free (tmp);
+    g_free (escaped);
+    applet->notification = notify;
+
+#if HAVE_LIBNOTIFY_07
+    notify_notification_set_hint (notify, "transient", 
g_variant_new_boolean (TRUE));
+#else
+    notify_notification_attach_to_status_icon (notify, 
applet->status_icon);
+#endif
+    notify_notification_set_urgency (notify, NOTIFY_URGENCY_LOW);
+    notify_notification_set_timeout (notify, NOTIFY_EXPIRES_DEFAULT);
+
+    // Add active networks to choose from
+    for (iter = active_networks; iter; iter = g_slist_next (iter)) {
+        NMConnection *connection = NM_CONNECTION(iter->data);
+
+        notify_notification_add_action (
+                notify,
+                uri,
+                (g_slist_length(active_networks) == 1) ?
+                        _("Remove auto-mount") :
+                        g_strdup_printf (_("For %s"), 
nm_connection_get_id(connection)),
+                remove_autoconnect_cb,
+                connection,
+                NULL);
+    }
+    g_slist_free(active_networks);
+
+    if (!notify_notification_show (notify, &error)) {
+        g_warning ("Failed to show notification: %s",
+                   error && error->message ? error->message : "(unknown)");
+        g_clear_error (&error);
+        g_free(uri);
+    }
+}
+
 static void nma_init (NMApplet *applet)
 {
     applet->animation_id = 0;
@@ -3519,6 +3946,20 @@ static void nma_init (NMApplet *applet)
     applet->icon_theme = NULL;
     applet->notification = NULL;
     applet->icon_size = 16;
+
+    // Connect volume monitoring
+    applet->volume_monitor = g_volume_monitor_get();
+    applet->mount_added_handler_id = g_signal_connect_after (
+            applet->volume_monitor,
+            "mount-added",
+            G_CALLBACK (mount_added_cb),
+            applet);
+    applet->mount_removed_handler_id = g_signal_connect_after (
+            applet->volume_monitor,
+            "mount-removed",
+            G_CALLBACK (mount_removed_cb),
+            applet);
+    applet->disconnecting = FALSE;
 }
 enum {
diff --git a/src/applet.h b/src/applet.h
index ab21ece..e39a643 100644
--- a/src/applet.h
+++ b/src/applet.h
@@ -175,6 +175,12 @@ typedef struct
     /* Tracker objects for secrets requests */
     GSList *        secrets_reqs;
+
+    // Mount-watching for auto-mount configuration dialogs
+    GVolumeMonitor* volume_monitor;
+    gulong            mount_added_handler_id;
+    gulong            mount_removed_handler_id;
+    gboolean        disconnecting;
 } NMApplet;
 typedef void (*AppletNewAutoConnectionCallback) (NMConnection *connection,
[
Date Prev][
Date Next]   [
Thread Prev][
Thread Next]   
[
Thread Index]
[
Date Index]
[
Author Index]