/* -*- 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 */ #include #include #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); }