/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
/* Copyright (C) 2005 Carlos Garnacho
*
* This program 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 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.
*
* Authors: Carlos Garnacho Parro <carlosg@gnome.org>
*/
#include <dbus/dbus.h>
#include <glib-object.h>
#include "oobs-object.h"
#include "oobs-object-private.h"
#include "oobs-session.h"
#include "oobs-session-private.h"
#define OOBS_OBJECT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), OOBS_TYPE_OBJECT, OobsObjectPrivate))
typedef struct _OobsObjectPrivate OobsObjectPrivate;
typedef struct _OobsObjectAsyncCallbackData OobsObjectAsyncCallbackData;
struct _OobsObjectPrivate
{
OobsSession *session;
DBusError dbus_error;
gchar *remote_object;
gchar *path;
gchar *method;
GList *pending_calls;
};
struct _OobsObjectAsyncCallbackData
{
OobsObject *object;
gboolean update;
OobsObjectAsyncFunc func;
gpointer data;
};
static void oobs_object_class_init (OobsObjectClass *class);
static void oobs_object_init (OobsObject *object);
static void oobs_object_finalize (GObject *object);
static void oobs_object_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void oobs_object_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
enum
{
CHANGED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_SESSION,
PROP_REMOTE_OBJECT
};
static GQuark dbus_connection_quark;
static guint object_signals [LAST_SIGNAL] = { 0 };
G_DEFINE_ABSTRACT_TYPE (OobsObject, oobs_object, G_TYPE_OBJECT);
static void
oobs_object_class_init (OobsObjectClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->get_property = oobs_object_get_property;
object_class->set_property = oobs_object_set_property;
object_class->finalize = oobs_object_finalize;
class->commit = NULL;
class->update = NULL;
class->changed = NULL;
dbus_connection_quark = g_quark_from_static_string ("oobs-dbus-connection");
g_object_class_install_property (object_class,
PROP_SESSION,
g_param_spec_object ("session",
"Session to connect to",
"Holds the OobsSession that the object will use",
OOBS_TYPE_SESSION,
G_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_REMOTE_OBJECT,
g_param_spec_string ("remote-object",
"Remote object to deal with",
"Name of the remote object at the other side of the connection",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
object_signals [CHANGED] = g_signal_new ("changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (OobsObjectClass, changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
g_type_class_add_private (object_class,
sizeof (OobsObjectPrivate));
}
static void
oobs_object_init (OobsObject *object)
{
OobsObjectPrivate *priv;
g_return_if_fail (OOBS_IS_OBJECT (object));
priv = OOBS_OBJECT_GET_PRIVATE (object);
priv->session = NULL;
priv->remote_object = NULL;
dbus_error_init (&priv->dbus_error);
object->_priv = priv;
}
static void
oobs_object_finalize (GObject *object)
{
OobsObject *obj;
OobsObjectPrivate *priv;
g_return_if_fail (OOBS_IS_OBJECT (object));
obj = OOBS_OBJECT (object);
priv = OOBS_OBJECT (object)->_priv;
/* Cancel all pending calls, they're going to be orphaned soon */
g_list_foreach (priv->pending_calls, (GFunc) dbus_pending_call_cancel, NULL);
g_list_foreach (priv->pending_calls, (GFunc) dbus_pending_call_unref, NULL);
g_list_free (priv->pending_calls);
_oobs_session_unregister_object (priv->session, obj);
g_free (priv->remote_object);
g_free (priv->path);
g_free (priv->method);
if (G_OBJECT_CLASS (oobs_object_parent_class)->finalize)
(* G_OBJECT_CLASS (oobs_object_parent_class)->finalize) (object);
}
static DBusHandlerResult
changed_signal_filter (DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
OobsObject *object;
OobsObjectPrivate *priv;
object = OOBS_OBJECT (user_data);
priv = OOBS_OBJECT (object)->_priv;
if (dbus_message_is_signal (message, priv->method, "changed") &&
dbus_message_has_path (message, priv->path))
g_signal_emit (object, object_signals [CHANGED], 0);
/* we want the rest of the objects of
* the same type to get the signal too
*/
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void
connect_object_to_session (OobsObject *object)
{
OobsObjectPrivate *priv;
DBusConnection *connection;
gchar *rule;
priv = OOBS_OBJECT (object)->_priv;
connection = _oobs_session_get_connection_bus (priv->session);
if (!connection)
{
g_warning ("OobsSession object hasn't connected to the bus, cannot register OobsObject");
return;
}
_oobs_session_register_object (priv->session, object);
dbus_connection_add_filter (connection, changed_signal_filter, object, NULL);
rule = g_strdup_printf ("type='signal',interface='%s',path='%s'",
priv->method, priv->path);
dbus_bus_add_match (connection, rule, &priv->dbus_error);
if (dbus_error_is_set (&priv->dbus_error))
{
g_critical ("There was an error adding the match function: %s", priv->dbus_error.message);
dbus_error_free (&priv->dbus_error);
}
g_free (rule);
}
static void
oobs_object_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
OobsObject *obj;
OobsObjectPrivate *priv;
g_return_if_fail (OOBS_IS_OBJECT (object));
obj = OOBS_OBJECT (object);
priv = obj->_priv;
switch (prop_id)
{
case PROP_SESSION:
if (priv->session)
_oobs_session_unregister_object (priv->session, obj);
priv->session = g_value_get_object (value);
if (priv->session)
connect_object_to_session (obj);
break;
case PROP_REMOTE_OBJECT:
priv->remote_object = g_value_dup_string (value);
priv->path = g_strconcat (OOBS_DBUS_PATH_PREFIX, "/", priv->remote_object, NULL);
priv->method = g_strdup (OOBS_DBUS_METHOD_PREFIX);
break;
}
}
static void
oobs_object_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
OobsObject *obj;
OobsObjectPrivate *priv;
g_return_if_fail (OOBS_IS_OBJECT (object));
obj = OOBS_OBJECT (object);
priv = obj->_priv;
switch (prop_id)
{
case PROP_SESSION:
g_value_set_object (value, G_OBJECT (priv->session));
break;
}
}
DBusMessage*
_oobs_object_get_dbus_message (OobsObject *object)
{
return (DBusMessage *) g_object_get_qdata (G_OBJECT (object), dbus_connection_quark);
}
void
_oobs_object_set_dbus_message (OobsObject *object, DBusMessage *message)
{
g_object_set_qdata_full (G_OBJECT (object), dbus_connection_quark,
message, (GDestroyNotify) dbus_message_unref);
}
static OobsResult
update_object_from_message (OobsObject *object,
DBusMessage *message)
{
OobsObjectClass *class;
class = OOBS_OBJECT_GET_CLASS (object);
if (!class->update)
{
g_critical ("There is no update() implementation for this object");
return OOBS_RESULT_MALFORMED_DATA;
}
g_object_set_qdata (G_OBJECT (object), dbus_connection_quark, message);
class->update (object);
g_object_steal_qdata (G_OBJECT (object), dbus_connection_quark);
return OOBS_RESULT_OK;
}
static DBusMessage*
run_message (OobsObject *object,
DBusMessage *message,
OobsResult *result)
{
OobsObjectPrivate *priv;
DBusConnection *connection;
DBusMessage *reply;
priv = object->_priv;
if (!oobs_session_get_connected (priv->session))
{
g_warning ("could send message, OobsSession hasn't connected to the bus");
return NULL;
}
connection = _oobs_session_get_connection_bus (priv->session);
reply = dbus_connection_send_with_reply_and_block (connection, message, -1, &priv->dbus_error);
if (dbus_error_is_set (&priv->dbus_error))
{
if (dbus_error_has_name (&priv->dbus_error, DBUS_ERROR_ACCESS_DENIED))
*result = OOBS_RESULT_ACCESS_DENIED;
else
g_warning ("There was an unknown error communicating with the backends: %s", priv->dbus_error.message);
dbus_error_free (&priv->dbus_error);
return NULL;
}
*result = OOBS_RESULT_OK;
return reply;
}
static void
async_message_cb (DBusPendingCall *pending_call, gpointer data)
{
OobsObjectPrivate *priv;
OobsObjectAsyncCallbackData *async_data;
OobsResult result = OOBS_RESULT_MALFORMED_DATA;
DBusMessage *reply;
DBusError error;
dbus_error_init (&error);
async_data = (OobsObjectAsyncCallbackData*) data;
reply = dbus_pending_call_steal_reply (pending_call);
if (dbus_set_error_from_message (&error, reply))
{
if (dbus_error_has_name (&error, DBUS_ERROR_ACCESS_DENIED))
result = OOBS_RESULT_ACCESS_DENIED;
else
{
/* FIXME: process error */
result = OOBS_RESULT_MALFORMED_DATA;
}
dbus_error_free (&error);
}
else
{
if (async_data->update)
result = update_object_from_message (OOBS_OBJECT (async_data->object), reply);
else
result = OOBS_RESULT_OK;
}
priv = async_data->object->_priv;
priv->pending_calls = g_list_remove (priv->pending_calls, pending_call);
if (async_data->func)
(* async_data->func) (OOBS_OBJECT (async_data->object), result, async_data->data);
dbus_message_unref (reply);
g_object_unref (async_data->object);
dbus_pending_call_unref (pending_call);
}
static void
run_message_async (OobsObject *object,
DBusMessage *message,
gboolean update,
OobsObjectAsyncFunc func,
gpointer data)
{
OobsObjectPrivate *priv;
DBusPendingCall *call;
OobsObjectAsyncCallbackData *async_data;
DBusConnection *connection;
priv = object->_priv;
if (!oobs_session_get_connected (priv->session))
{
g_warning ("could not send message, OobsSession hasn't connected to the bus");
return;
}
connection = _oobs_session_get_connection_bus (priv->session);
dbus_connection_send_with_reply (connection, message, &call, -1);
async_data = g_new0 (OobsObjectAsyncCallbackData, 1);
async_data->object = g_object_ref (object);
async_data->update = update;
async_data->func = func;
async_data->data = data;
dbus_pending_call_set_notify (call, async_message_cb, async_data, g_free);
priv->pending_calls = g_list_prepend (priv->pending_calls, call);
}
static DBusMessage*
get_commit_message (OobsObject *object)
{
OobsObjectClass *class;
OobsObjectPrivate *priv;
DBusMessage *message;
priv = object->_priv;
class = OOBS_OBJECT_GET_CLASS (object);
if (!priv->session)
{
g_critical ("Trying to commit changes after the session has terminated, "
"this reflects a bug in the application");
return NULL;
}
if (!class->commit)
{
g_critical ("There is no commit() implementation for this object");
return NULL;
}
message = dbus_message_new_method_call (OOBS_DBUS_DESTINATION, priv->path, priv->method, "set");
/* Let the commit() implementation fill the message */
_oobs_object_set_dbus_message (object, message);
class->commit (object);
message = g_object_steal_qdata (G_OBJECT (object), dbus_connection_quark);
if (!message)
{
/* a NULL message means malformed configuration */
g_critical ("Not committing due to inconsistencies in the "
"configuration, this reflects a bug in the application\n");
}
return message;
}
static DBusMessage*
get_update_message (OobsObject *object)
{
OobsObjectPrivate *priv;
priv = object->_priv;
if (!priv->session)
{
g_critical ("Trying to update the object after the session has terminated, "
"this reflects a bug in the application");
return NULL;
}
return dbus_message_new_method_call (OOBS_DBUS_DESTINATION, priv->path, priv->method, "get");
}
/**
* oobs_object_commit:
* @object: an #OobsObject
*
* Commits to the system all the changes done
* to the configuration held by an #OobsObject.
*
* Return value: an #OobsResult enum with the error code.
**/
OobsResult
oobs_object_commit (OobsObject *object)
{
DBusMessage *message;
OobsResult result;
g_return_val_if_fail (OOBS_IS_OBJECT (object), OOBS_RESULT_MALFORMED_DATA);
message = get_commit_message (object);
if (!message)
return OOBS_RESULT_MALFORMED_DATA;
run_message (object, message, &result);
dbus_message_unref (message);
return result;
}
/**
* oobs_object_commit_async:
* @object: An #OobsObject.
* @func: An #OobsObjectAsyncFunc that will be called when the asynchronous operation has ended.
* @data: Aditional data to pass to @func.
*
* Commits to the system all the changes done to the configuration held by an #OobsObject.
* This change will be asynchronous, being run the function @func when the change has been done.
*
* Return value: an #OobsResult enum with the error code. Due to the asynchronous nature
* of the function, only OOBS_RESULT_MALFORMED and OOBS_RESULT_OK can be returned.
**/
OobsResult
oobs_object_commit_async (OobsObject *object,
OobsObjectAsyncFunc func,
gpointer data)
{
DBusMessage *message;
g_return_val_if_fail (OOBS_IS_OBJECT (object), OOBS_RESULT_MALFORMED_DATA);
message = get_commit_message (object);
if (!message)
return OOBS_RESULT_MALFORMED_DATA;
run_message_async (object, message, FALSE, func, data);
dbus_message_unref (message);
return OOBS_RESULT_OK;
}
/**
* oobs_object_update:
* @object: an #OobsObject
*
* Synchronizes the configuration held by the #OobsObject
* with the actual system configuration. All the changes done
* to the configuration held by the #OobsObject will be forgotten.
*
* Return value: an #OobsResult enum with the error code.
**/
OobsResult
oobs_object_update (OobsObject *object)
{
DBusMessage *message, *reply;
OobsResult result = OOBS_RESULT_MALFORMED_DATA;
g_return_val_if_fail (OOBS_IS_OBJECT (object), OOBS_RESULT_MALFORMED_DATA);
message = get_update_message (object);
if (!message)
return OOBS_RESULT_MALFORMED_DATA;
reply = run_message (object, message, &result);
if (reply)
{
result = update_object_from_message (object, reply);
dbus_message_unref (reply);
}
dbus_message_unref (message);
return result;
}
/**
* oobs_object_update_async:
* @object: An #OobsObject
* @func: An #OobsObjectAsyncFunc that will be called when the asynchronous operation has ended.
* @data: Aditional data to pass to @func.
*
* Synchronizes the configuration held by the #OobsObject
* with the actual system configuration. All the changes done
* to the configuration held by the #OobsObject will be forgotten.
* The update operation will be asynchronous, being run the
* function @func when the update has been done.
*
* Return value: an #OobsResult enum with the error code. Due to the asynchronous nature
* of the function, only OOBS_RESULT_MALFORMED and OOBS_RESULT_OK can be returned.
**/
OobsResult
oobs_object_update_async (OobsObject *object,
OobsObjectAsyncFunc func,
gpointer data)
{
DBusMessage *message;
message = get_update_message (object);
if (!message)
return OOBS_RESULT_MALFORMED_DATA;
run_message_async (object, message, TRUE, func, data);
dbus_message_unref (message);
return OOBS_RESULT_OK;
}
/**
* oobs_object_process_requests:
* @object: An #OobsObject
*
* Blocks until all pending asynchronous requests to this object have been processed.
**/
void
oobs_object_process_requests (OobsObject *object)
{
OobsObjectPrivate *priv;
g_return_if_fail (OOBS_IS_OBJECT (object));
priv = object->_priv;
g_list_foreach (priv->pending_calls, (GFunc) dbus_pending_call_block, NULL);
}
syntax highlighted by Code2HTML, v. 0.9.1