gnome-control-center/panels/user-accounts/um-user-manager.c
Stef Walter 2e228b9992 user-accounts: Cleaner interaction with UmAccountDialog actions
More clear scoping and interaction with running actions in
UmAccountDialog. In later 'enterprise login' patches we have long
running actions that's why this needs cleaning up.

In particular:

 * Show errors as children of the dialog.
 * Errors don't make the account dialog go away, user can correct
   problems.
 * Use more standard GAsyncResult style callbacks:
   um_account_dialog_perform() um_account_dialog_finish()
 * Disable controls while the operation is happening.
 * Allow the user to cancel long actions in UmAccountDialog by
   pressing the cancel button.

https://bugzilla.gnome.org/show_bug.cgi?id=677548
2012-06-12 12:03:27 +02:00

676 lines
23 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2009-2010 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif /* HAVE_PATHS_H */
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <glib-object.h>
#include <gio/gio.h>
#include "um-user-manager.h"
enum {
USERS_LOADED,
USER_ADDED,
USER_REMOVED,
USER_CHANGED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0, };
static void um_user_manager_class_init (UmUserManagerClass *klass);
static void um_user_manager_init (UmUserManager *user_manager);
static void um_user_manager_finalize (GObject *object);
static gpointer user_manager_object = NULL;
G_DEFINE_TYPE (UmUserManager, um_user_manager, G_TYPE_OBJECT)
static void
um_user_manager_class_init (UmUserManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = um_user_manager_finalize;
signals [USERS_LOADED] =
g_signal_new ("users-loaded",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (UmUserManagerClass, users_loaded),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals [USER_ADDED] =
g_signal_new ("user-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (UmUserManagerClass, user_added),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, UM_TYPE_USER);
signals [USER_REMOVED] =
g_signal_new ("user-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (UmUserManagerClass, user_removed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, UM_TYPE_USER);
signals [USER_CHANGED] =
g_signal_new ("user-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (UmUserManagerClass, user_changed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, UM_TYPE_USER);
}
/* We maintain a ring for each group of users with the same real name.
* We need this to pick the right display names.
*/
static void
remove_user_from_dupe_ring (UmUserManager *manager,
UmUser *user)
{
GList *dupes;
UmUser *dup;
dup = NULL;
dupes = g_object_get_data (G_OBJECT (user), "dupes");
if (!dupes) {
goto out;
}
if (dupes->next == dupes->prev) {
dup = dupes->next->data;
g_list_free_1 (dupes->next);
g_object_set_data (G_OBJECT (dup), "dupes", NULL);
}
else {
dupes->next->prev = dupes->prev;
dupes->prev->next = dupes->next;
}
g_list_free_1 (dupes);
g_object_set_data (G_OBJECT (user), "dupes", NULL);
out:
if (dup) {
um_user_show_short_display_name (dup);
g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
}
um_user_show_short_display_name (user);
g_signal_emit (manager, signals[USER_CHANGED], 0, user);
}
static gboolean
match_real_name_hrfunc (gpointer key,
gpointer value,
gpointer user)
{
return (value != user && g_strcmp0 (um_user_get_real_name (user), um_user_get_real_name (value)) == 0);
}
static void
add_user_to_dupe_ring (UmUserManager *manager,
UmUser *user)
{
UmUser *dup;
GList *dupes;
GList *l;
dup = g_hash_table_find (manager->user_by_object_path,
match_real_name_hrfunc, user);
if (!dup) {
return;
}
dupes = g_object_get_data (G_OBJECT (dup), "dupes");
if (!dupes) {
dupes = g_list_append (NULL, dup);
g_object_set_data (G_OBJECT (dup), "dupes", dupes);
dupes->next = dupes->prev = dupes;
}
else {
dup = NULL;
}
l = g_list_append (NULL, user);
g_object_set_data (G_OBJECT (user), "dupes", l);
l->prev = dupes->prev;
dupes->prev->next = l;
l->next = dupes;
dupes->prev = l;
if (dup) {
um_user_show_full_display_name (dup);
g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
}
um_user_show_full_display_name (user);
g_signal_emit (manager, signals[USER_CHANGED], 0, user);
}
static void
user_changed_handler (UmUser *user,
UmUserManager *manager)
{
remove_user_from_dupe_ring (manager, user);
add_user_to_dupe_ring (manager, user);
g_signal_emit (manager, signals[USER_CHANGED], 0, user);
}
static void
user_added_handler (UmUserManager *manager,
const char *object_path)
{
UmUser *user;
if (g_hash_table_lookup (manager->user_by_object_path, object_path))
return;
user = um_user_new_from_object_path (object_path);
if (!user)
return;
if (um_user_is_system_account (user)) {
g_object_unref (user);
return;
}
add_user_to_dupe_ring (manager, user);
g_signal_connect (user, "changed",
G_CALLBACK (user_changed_handler), manager);
g_hash_table_insert (manager->user_by_object_path, g_strdup (um_user_get_object_path (user)), g_object_ref (user));
g_hash_table_insert (manager->user_by_name, g_strdup (um_user_get_user_name (user)), g_object_ref (user));
g_signal_emit (manager, signals[USER_ADDED], 0, user);
g_object_unref (user);
}
static void
user_deleted_handler (UmUserManager *manager,
const char *object_path)
{
UmUser *user;
user = g_hash_table_lookup (manager->user_by_object_path, object_path);
if (!user)
return;
g_object_ref (user);
g_signal_handlers_disconnect_by_func (user, user_changed_handler, manager);
remove_user_from_dupe_ring (manager, user);
g_hash_table_remove (manager->user_by_object_path, um_user_get_object_path (user));
g_hash_table_remove (manager->user_by_name, um_user_get_user_name (user));
g_signal_emit (manager, signals[USER_REMOVED], 0, user);
g_object_unref (user);
}
static void
manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UmUserManager *manager)
{
if (strcmp (signal_name, "UserAdded") == 0) {
if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
gchar *object_path;
g_variant_get (parameters, "(&o)", &object_path);
user_added_handler (manager, object_path);
}
}
else if (strcmp (signal_name, "UserDeleted") == 0) {
if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
gchar *object_path;
g_variant_get (parameters, "(&o)", &object_path);
user_deleted_handler (manager, object_path);
}
}
}
static void
got_users (GObject *object,
GAsyncResult *res,
gpointer data)
{
UmUserManager *manager = data;
GVariant *result;
GError *error = NULL;
result = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error);
if (!result) {
manager->no_service = TRUE;
g_error_free (error);
goto done;
}
if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)"))) {
GVariantIter *iter;
gchar *object_path;
g_variant_get (result, "(ao)", &iter);
while (g_variant_iter_loop (iter, "&o", &object_path))
user_added_handler (manager, object_path);
g_variant_iter_free (iter);
}
g_variant_unref (result);
done:
g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0);
}
static void
get_users (UmUserManager *manager)
{
g_debug ("calling 'ListCachedUsers'");
g_dbus_proxy_call (manager->proxy,
"ListCachedUsers",
g_variant_new ("()"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
got_users,
manager);
}
static void
um_user_manager_init (UmUserManager *manager)
{
GError *error = NULL;
GDBusConnection *bus;
manager->user_by_object_path = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_object_unref);
manager->user_by_name = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_object_unref);
bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (bus == NULL) {
g_warning ("Couldn't connect to system bus: %s", error->message);
g_error_free (error);
return;
}
manager->proxy = g_dbus_proxy_new_sync (bus,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.Accounts",
"/org/freedesktop/Accounts",
"org.freedesktop.Accounts",
NULL,
&error);
if (manager->proxy == NULL) {
g_warning ("Couldn't get accounts proxy: %s", error->message);
g_error_free (error);
return;
}
g_signal_connect (manager->proxy, "g-signal", G_CALLBACK (manager_signal_cb), manager);
get_users (manager);
}
static void
clear_dup (gpointer key,
gpointer value,
gpointer data)
{
GList *dupes;
/* don't bother maintaining the ring, we're destroying the
* entire hash table anyway
*/
dupes = g_object_get_data (G_OBJECT (value), "dupes");
if (dupes) {
g_list_free_1 (dupes);
g_object_set_data (G_OBJECT (value), "dupes", NULL);
}
}
static void
um_user_manager_finalize (GObject *object)
{
UmUserManager *manager;
manager = UM_USER_MANAGER (object);
g_hash_table_foreach (manager->user_by_object_path, clear_dup, NULL);
g_hash_table_destroy (manager->user_by_object_path);
g_hash_table_destroy (manager->user_by_name);
g_object_unref (manager->proxy);
G_OBJECT_CLASS (um_user_manager_parent_class)->finalize (object);
}
UmUserManager *
um_user_manager_ref_default (void)
{
if (user_manager_object != NULL) {
g_object_ref (user_manager_object);
} else {
user_manager_object = g_object_new (UM_TYPE_USER_MANAGER, NULL);
g_object_add_weak_pointer (user_manager_object,
(gpointer *) &user_manager_object);
}
return UM_USER_MANAGER (user_manager_object);
}
typedef struct {
UmUserManager *manager;
gchar *user_name;
GAsyncReadyCallback callback;
gpointer data;
GDestroyNotify destroy;
} AsyncUserOpData;
static void
async_user_op_data_free (gpointer d)
{
AsyncUserOpData *data = d;
g_object_unref (data->manager);
g_free (data->user_name);
if (data->destroy)
data->destroy (data->data);
g_free (data);
}
static void
create_user_done (GObject *proxy,
GAsyncResult *r,
gpointer user_data)
{
AsyncUserOpData *data = user_data;
GSimpleAsyncResult *res;
GVariant *result;
GError *error = NULL;
gchar *remote;
res = g_simple_async_result_new (G_OBJECT (data->manager),
data->callback,
data->data,
um_user_manager_create_user);
result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), r, &error);
if (!result) {
/* dbus-glib fail:
* We have to translate the errors manually here, since
* calling dbus_g_error_has_name on the error returned in
* um_user_manager_create_user_finish doesn't work.
*/
remote = g_dbus_error_get_remote_error (error);
if (g_dbus_error_is_remote_error (error) &&
strcmp (remote, "org.freedesktop.Accounts.Error.PermissionDenied") == 0) {
g_simple_async_result_set_error (res,
UM_USER_MANAGER_ERROR,
UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
"Not authorized");
}
if (g_dbus_error_is_remote_error (error) &&
strcmp (remote, "org.freedesktop.Accounts.Error.UserExists") == 0) {
g_simple_async_result_set_error (res,
UM_USER_MANAGER_ERROR,
UM_USER_MANAGER_ERROR_USER_EXISTS,
_("A user with name '%s' already exists."),
data->user_name);
}
else {
g_simple_async_result_set_from_error (res, error);
}
g_error_free (error);
g_free (remote);
}
else {
if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(o)"))) {
gchar *path;
g_variant_get (result, "(o)", &path);
g_simple_async_result_set_op_res_gpointer (res, path, g_free);
}
else
g_simple_async_result_set_error (res,
UM_USER_MANAGER_ERROR,
UM_USER_MANAGER_ERROR_FAILED,
"Got invalid response from AccountsService");
g_variant_unref (result);
}
data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
async_user_op_data_free (data);
g_object_unref (res);
}
gboolean
um_user_manager_create_user_finish (UmUserManager *manager,
GAsyncResult *result,
UmUser **user,
GError **error)
{
gchar *path;
GSimpleAsyncResult *res;
res = G_SIMPLE_ASYNC_RESULT (result);
*user = NULL;
if (g_simple_async_result_propagate_error (res, error)) {
return FALSE;
}
path = g_simple_async_result_get_op_res_gpointer (res);
*user = g_hash_table_lookup (manager->user_by_object_path, path);
return TRUE;
}
void
um_user_manager_create_user (UmUserManager *manager,
const char *user_name,
const char *real_name,
gint account_type,
GCancellable *cancellable,
GAsyncReadyCallback done,
gpointer done_data,
GDestroyNotify destroy)
{
AsyncUserOpData *data;
data = g_new0 (AsyncUserOpData, 1);
data->manager = g_object_ref (manager);
data->user_name = g_strdup (user_name);
data->callback = done;
data->data = done_data;
data->destroy = destroy;
g_dbus_proxy_call (manager->proxy,
"CreateUser",
g_variant_new ("(ssi)", user_name, real_name, account_type),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
create_user_done,
data);
}
static void
delete_user_done (GObject *proxy,
GAsyncResult *r,
gpointer user_data)
{
AsyncUserOpData *data = user_data;
GSimpleAsyncResult *res;
GVariant *result;
GError *error = NULL;
res = g_simple_async_result_new (G_OBJECT (data->manager),
data->callback,
data->data,
um_user_manager_delete_user);
result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), r, &error);
if (!result) {
if (g_dbus_error_is_remote_error (error) &&
strcmp (g_dbus_error_get_remote_error(error), "org.freedesktop.Accounts.Error.PermissionDenied") == 0) {
g_simple_async_result_set_error (res,
UM_USER_MANAGER_ERROR,
UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
"Not authorized");
}
if (g_dbus_error_is_remote_error (error) &&
strcmp (g_dbus_error_get_remote_error(error), "org.freedesktop.Accounts.Error.UserExists") == 0) {
g_simple_async_result_set_error (res,
UM_USER_MANAGER_ERROR,
UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST,
_("This user does not exist."));
}
else {
g_simple_async_result_set_from_error (res, error);
g_error_free (error);
}
}
else
g_variant_unref (result);
data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
async_user_op_data_free (data);
g_object_unref (res);
}
gboolean
um_user_manager_delete_user_finish (UmUserManager *manager,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *res;
res = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (res, error)) {
return FALSE;
}
return TRUE;
}
void
um_user_manager_delete_user (UmUserManager *manager,
UmUser *user,
gboolean remove_files,
GAsyncReadyCallback done,
gpointer done_data,
GDestroyNotify destroy)
{
AsyncUserOpData *data;
data = g_new0 (AsyncUserOpData, 1);
data->manager = g_object_ref (manager);
data->callback = done;
data->data = done_data;
data->destroy = destroy;
g_dbus_proxy_call (manager->proxy,
"DeleteUser",
g_variant_new ("(xb)", (gint64) um_user_get_uid (user), remove_files),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
delete_user_done,
data);
}
GSList *
um_user_manager_list_users (UmUserManager *manager)
{
GSList *list = NULL;
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, manager->user_by_name);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
list = g_slist_prepend (list, value);
}
return g_slist_sort (list, (GCompareFunc) um_user_collate);
}
UmUser *
um_user_manager_get_user (UmUserManager *manager,
const gchar *name)
{
return g_hash_table_lookup (manager->user_by_name, name);
}
UmUser *
um_user_manager_get_user_by_id (UmUserManager *manager,
uid_t uid)
{
struct passwd *pwent;
pwent = getpwuid (uid);
if (!pwent) {
return NULL;
}
return um_user_manager_get_user (manager, pwent->pw_name);
}
gboolean
um_user_manager_no_service (UmUserManager *manager)
{
return manager->no_service;
}
GQuark
um_user_manager_error_quark (void)
{
return g_quark_from_static_string ("um-user-manager-error-quark");
}