Add initial implementation of g_dbus_add_signal_watch
authorLuiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
Tue, 29 Dec 2009 08:53:21 +0000 (10:53 +0200)
committerMarcel Holtmann <marcel@holtmann.org>
Tue, 29 Dec 2009 09:19:57 +0000 (01:19 -0800)
With g_dbus_add_signal_watch there is no need to register multiple filters
for dbus nor add matching rules manually.

gdbus/gdbus.h
gdbus/watch.c

index 244f797..170b669 100644 (file)
@@ -125,8 +125,10 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
                                GDBusWatchFunction function,
                                void *user_data, GDBusDestroyFunction destroy);
 guint g_dbus_add_signal_watch(DBusConnection *connection,
-                               const char *rule, GDBusSignalFunction function,
-                               void *user_data, GDBusDestroyFunction destroy);
+                               const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function, void *user_data,
+                               GDBusDestroyFunction destroy);
 gboolean g_dbus_remove_watch(DBusConnection *connection, guint tag);
 void g_dbus_remove_all_watches(DBusConnection *connection);
 
index 45dc367..3883f4d 100644 (file)
 #define error(fmt...)
 #define debug(fmt...)
 
-static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+static DBusHandlerResult message_filter(DBusConnection *connection,
                                        DBusMessage *message, void *user_data);
 
 static guint listener_id = 0;
-static GSList *name_listeners = NULL;
+static GSList *listeners = NULL;
 
-struct name_callback {
+struct filter_callback {
        GDBusWatchFunction conn_func;
        GDBusWatchFunction disc_func;
+       GDBusSignalFunction signal_func;
+       GDBusDestroyFunction destroy_func;
        void *user_data;
        guint id;
 };
 
-struct name_data {
+struct filter_data {
        DBusConnection *connection;
-       char *name;
+       DBusHandleMessageFunction handle_func;
+       char *sender;
+       char *path;
+       char *interface;
+       char *member;
+       char *argument;
        GSList *callbacks;
        GSList *processed;
        gboolean lock;
+       gboolean registered;
 };
 
-static struct name_data *name_data_find(DBusConnection *connection,
-                                                       const char *name)
+static struct filter_data *filter_data_find(DBusConnection *connection,
+                                                       const char *sender,
+                                                       const char *path,
+                                                       const char *interface,
+                                                       const char *member,
+                                                       const char *argument)
 {
        GSList *current;
 
-       for (current = name_listeners;
+       for (current = listeners;
                        current != NULL; current = current->next) {
-               struct name_data *data = current->data;
+               struct filter_data *data = current->data;
 
                if (connection != data->connection)
                        continue;
 
-               if (name == NULL || g_str_equal(name, data->name))
-                       return data;
+               if (sender && data->sender &&
+                               g_str_equal(sender, data->sender) == FALSE)
+                       continue;
+
+               if (path && data->path &&
+                               g_str_equal(path, data->path) == FALSE)
+                       continue;
+
+               if (interface && data->interface &&
+                               g_str_equal(interface, data->interface) == FALSE)
+                       continue;
+
+               if (member && data->member &&
+                               g_str_equal(member, data->member) == FALSE)
+                       continue;
+
+               if (argument && data->argument &&
+                               g_str_equal(argument, data->argument) == FALSE)
+                       continue;
+
+               return data;
        }
 
        return NULL;
 }
 
-static struct name_callback *name_callback_find(GSList *callbacks, guint id)
+static void format_rule(struct filter_data *data, char *rule, size_t size)
 {
-       GSList *current;
+       int offset;
+
+       offset = snprintf(rule, size, "type='signal'");
+
+       if (data->sender)
+               offset += snprintf(rule + offset, size - offset,
+                               ",sender='%s'", data->sender);
+       if (data->path)
+               offset += snprintf(rule + offset, size - offset,
+                               ",path='%s'", data->path);
+       if (data->interface)
+               offset += snprintf(rule + offset, size - offset,
+                               ",interface='%s'", data->interface);
+       if (data->member)
+               offset += snprintf(rule + offset, size - offset,
+                               ",member='%s'", data->member);
+       if (data->argument)
+               snprintf(rule + offset, size - offset,
+                               ",arg0='%s'", data->argument);
+}
 
-       for (current = callbacks; current != NULL; current = current->next) {
-               struct name_callback *cb = current->data;
-               if (cb->id == id)
-                       return cb;
+static gboolean add_match(struct filter_data *data,
+                               DBusHandleMessageFunction filter)
+{
+       DBusError err;
+       char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
+
+       format_rule(data, rule, sizeof(rule));
+       dbus_error_init(&err);
+
+       dbus_bus_add_match(data->connection, rule, &err);
+       if (dbus_error_is_set(&err)) {
+               error("Adding match rule \"%s\" failed: %s", match_string,
+                               err.message);
+               dbus_error_free(&err);
+               return FALSE;
        }
 
-       return NULL;
+       data->handle_func = filter;
+       data->registered = TRUE;
+
+       return TRUE;
+}
+
+static gboolean remove_match(struct filter_data *data)
+{
+       DBusError err;
+       char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
+
+       format_rule(data, rule, sizeof(rule));
+
+       dbus_error_init(&err);
+
+       dbus_bus_remove_match(data->connection, rule, &err);
+       if (dbus_error_is_set(&err)) {
+               error("Removing owner match rule for %s failed: %s",
+                               name, err.message);
+               dbus_error_free(&err);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static struct filter_data *filter_data_get(DBusConnection *connection,
+                                       DBusHandleMessageFunction filter,
+                                       const char *sender,
+                                       const char *path,
+                                       const char *interface,
+                                       const char *member,
+                                       const char *argument)
+{
+       struct filter_data *data;
+
+       if (!filter_data_find(connection, NULL, NULL, NULL, NULL, NULL)) {
+               if (!dbus_connection_add_filter(connection,
+                                       message_filter, NULL, NULL)) {
+                       error("dbus_connection_add_filter() failed");
+                       return NULL;
+               }
+       }
+
+       data = filter_data_find(connection, sender, path, interface, member,
+                                       argument);
+       if (data)
+               return data;
+
+       data = g_new0(struct filter_data, 1);
+
+       data->connection = dbus_connection_ref(connection);
+       data->sender = g_strdup(sender);
+       data->path = g_strdup(path);
+       data->interface = g_strdup(interface);
+       data->member = g_strdup(member);
+       data->argument = g_strdup(argument);
+
+       if (!add_match(data, filter)) {
+               g_free(data);
+               return NULL;
+       }
+
+       listeners = g_slist_append(listeners, data);
+
+       return data;
 }
 
-static void name_data_call_and_free(struct name_data *data)
+static struct filter_callback *filter_data_find_callback(
+                                               struct filter_data *data,
+                                               guint id)
 {
        GSList *l;
 
-       for (l = data->callbacks; l != NULL; l = l->next) {
-               struct name_callback *cb = l->data;
-               if (cb->disc_func)
-                       cb->disc_func(data->connection, cb->user_data);
-               g_free(cb);
+       for (l = data->callbacks; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
+       }
+       for (l = data->processed; l; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->id == id)
+                       return cb;
        }
 
-       g_slist_free(data->callbacks);
-       g_free(data->name);
-       g_free(data);
+       return NULL;
 }
 
-static void name_data_free(struct name_data *data)
+static void filter_data_free(struct filter_data *data)
 {
        GSList *l;
 
@@ -114,135 +244,124 @@ static void name_data_free(struct name_data *data)
                g_free(l->data);
 
        g_slist_free(data->callbacks);
-       g_free(data->name);
+       g_free(data->sender);
+       g_free(data->path);
+       g_free(data->interface);
+       g_free(data->member);
+       g_free(data->argument);
+       dbus_connection_unref(data->connection);
        g_free(data);
 }
 
-static int name_data_add(DBusConnection *connection, const char *name,
+static void filter_data_call_and_free(struct filter_data *data)
+{
+       GSList *l;
+
+       for (l = data->callbacks; l != NULL; l = l->next) {
+               struct filter_callback *cb = l->data;
+               if (cb->disc_func)
+                       cb->disc_func(data->connection, cb->user_data);
+               if (cb->destroy_func)
+                       cb->destroy_func(cb->user_data);
+               g_free(cb);
+       }
+
+       filter_data_free(data);
+}
+
+static struct filter_callback *filter_data_add_callback(
+                                               struct filter_data *data,
                                                GDBusWatchFunction connect,
                                                GDBusWatchFunction disconnect,
-                                               void *user_data, guint id)
+                                               GDBusSignalFunction signal,
+                                               GDBusDestroyFunction destroy,
+                                               void *user_data)
 {
-       int first = 1;
-       struct name_data *data = NULL;
-       struct name_callback *cb = NULL;
+       struct filter_callback *cb = NULL;
 
-       cb = g_new(struct name_callback, 1);
+       cb = g_new(struct filter_callback, 1);
 
        cb->conn_func = connect;
        cb->disc_func = disconnect;
+       cb->signal_func = signal;
+       cb->destroy_func = destroy;
        cb->user_data = user_data;
-       cb->id = id;
-
-       data = name_data_find(connection, name);
-       if (data) {
-               first = 0;
-               goto done;
-       }
-
-       data = g_new0(struct name_data, 1);
-
-       data->connection = connection;
-       data->name = g_strdup(name);
-
-       name_listeners = g_slist_append(name_listeners, data);
+       cb->id = ++listener_id;
 
-done:
        if (data->lock)
                data->processed = g_slist_append(data->processed, cb);
        else
                data->callbacks = g_slist_append(data->callbacks, cb);
 
-       return first;
+       return cb;
 }
 
-static void name_data_remove(DBusConnection *connection,
-                                       const char *name, guint id)
+static gboolean filter_data_remove_callback(struct filter_data *data,
+                                               struct filter_callback *cb)
 {
-       struct name_data *data;
-       struct name_callback *cb = NULL;
+       data->callbacks = g_slist_remove(data->callbacks, cb);
+       data->processed = g_slist_remove(data->processed, cb);
 
-       data = name_data_find(connection, name);
-       if (!data)
-               return;
+       if (cb->destroy_func)
+               cb->destroy_func(cb->user_data);
 
-       cb = name_callback_find(data->callbacks, id);
-       if (cb) {
-               data->callbacks = g_slist_remove(data->callbacks, cb);
-               g_free(cb);
-       }
+       g_free(cb);
 
-       if (data->callbacks)
-               return;
+       /* Don't remove the filter if other callbacks exist or data is lock
+        * processing callbacks */
+       if (data->callbacks || data->lock)
+               return TRUE;
 
-       name_listeners = g_slist_remove(name_listeners, data);
-       name_data_free(data);
+       if (data->registered && !remove_match(data))
+               return FALSE;
 
        /* Remove filter if there are no listeners left for the connection */
-       data = name_data_find(connection, NULL);
+       data = filter_data_find(data->connection, NULL, NULL, NULL, NULL,
+                                       NULL);
        if (!data)
-               dbus_connection_remove_filter(connection,
-                                               name_exit_filter,
+               dbus_connection_remove_filter(data->connection, message_filter,
                                                NULL);
-}
 
-static gboolean add_match(DBusConnection *connection, const char *name)
-{
-       DBusError err;
-       char match_string[128];
-
-       snprintf(match_string, sizeof(match_string),
-                       "interface=%s,member=NameOwnerChanged,arg0=%s",
-                       DBUS_INTERFACE_DBUS, name);
-
-       dbus_error_init(&err);
-
-       dbus_bus_add_match(connection, match_string, &err);
-
-       if (dbus_error_is_set(&err)) {
-               error("Adding match rule \"%s\" failed: %s", match_string,
-                               err.message);
-               dbus_error_free(&err);
-               return FALSE;
-       }
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
 
        return TRUE;
 }
 
-static gboolean remove_match(DBusConnection *connection, const char *name)
+static DBusHandlerResult signal_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
 {
-       DBusError err;
-       char match_string[128];
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
 
-       snprintf(match_string, sizeof(match_string),
-                       "interface=%s,member=NameOwnerChanged,arg0=%s",
-                       DBUS_INTERFACE_DBUS, name);
+       while (data->callbacks) {
+               cb = data->callbacks->data;
 
-       dbus_error_init(&err);
+               if (cb->signal_func && !cb->signal_func(connection, message,
+                                                       cb->user_data)) {
+                       filter_data_remove_callback(data, cb);
+                       continue;
+               }
 
-       dbus_bus_remove_match(connection, match_string, &err);
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
 
-       if (dbus_error_is_set(&err)) {
-               error("Removing owner match rule for %s failed: %s",
-                               name, err.message);
-               dbus_error_free(&err);
-               return FALSE;
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+               data->processed = g_slist_append(data->processed, cb);
        }
 
-       return TRUE;
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+static DBusHandlerResult service_filter(DBusConnection *connection,
                                        DBusMessage *message, void *user_data)
 {
-       struct name_data *data;
-       struct name_callback *cb;
+       struct filter_data *data = user_data;
+       struct filter_callback *cb;
        char *name, *old, *new;
 
-       if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
-                                                       "NameOwnerChanged"))
-               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
        if (!dbus_message_get_args(message, NULL,
                                DBUS_TYPE_STRING, &name,
                                DBUS_TYPE_STRING, &old,
@@ -252,14 +371,6 @@ static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
 
-       data = name_data_find(connection, name);
-       if (!data) {
-               error("Got NameOwnerChanged signal for %s which has no listeners", name);
-               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-       }
-
-       data->lock = TRUE;
-
        while (data->callbacks) {
                cb = data->callbacks->data;
 
@@ -286,24 +397,56 @@ static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                data->processed = g_slist_append(data->processed, cb);
        }
 
-       data->callbacks = data->processed;
-       data->processed = NULL;
-       data->lock = FALSE;
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult message_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data)
+{
+       struct filter_data *data;
+       const char *sender, *path, *iface, *member, *arg = NULL;
+
+       /* Only filter signals */
+       if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       sender = dbus_message_get_sender(message);
+       path = dbus_message_get_path(message);
+       iface = dbus_message_get_interface(message);
+       member = dbus_message_get_member(message);
+       dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID);
+
+       data = filter_data_find(connection, sender, path, iface, member, arg);
+       if (!data) {
+               error("Got %s.%s signal which has no listeners", iface, member);
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+
+       if (data->handle_func) {
+               data->lock = TRUE;
+
+               data->handle_func(connection, message, data);
+
+               data->callbacks = data->processed;
+               data->processed = NULL;
+               data->lock = FALSE;
+       }
 
        if (data->callbacks)
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
-       name_listeners = g_slist_remove(name_listeners, data);
-       name_data_free(data);
+       remove_match(data);
+
+       listeners = g_slist_remove(listeners, data);
+       filter_data_free(data);
 
        /* Remove filter if there no listener left for the connection */
-       data = name_data_find(connection, NULL);
+       data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL);
        if (!data)
-               dbus_connection_remove_filter(connection, name_exit_filter,
+               dbus_connection_remove_filter(connection, message_filter,
                                                NULL);
 
-       remove_match(connection, name);
-
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
@@ -397,38 +540,27 @@ guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
                                GDBusWatchFunction disconnect,
                                void *user_data, GDBusDestroyFunction destroy)
 {
-       int first;
-
-       if (!name_data_find(connection, NULL)) {
-               if (!dbus_connection_add_filter(connection,
-                                       name_exit_filter, NULL, NULL)) {
-                       error("dbus_connection_add_filter() failed");
-                       return 0;
-               }
-       }
+       struct filter_data *data;
+       struct filter_callback *cb;
 
-       listener_id++;
-       first = name_data_add(connection, name, connect, disconnect,
-                                               user_data, listener_id);
-       /* The filter is already added if this is not the first callback
-        * registration for the name */
-       if (!first)
-               goto done;
+       if (!name)
+               return 0;
 
-       if (name) {
-               debug("name_listener_add(%s)", name);
+       data = filter_data_get(connection, service_filter, NULL, NULL,
+                               DBUS_INTERFACE_DBUS, "NameOwnerChanged",
+                               name);
+       if (!data)
+               return 0;
 
-               if (!add_match(connection, name)) {
-                       name_data_remove(connection, name, listener_id);
-                       return 0;
-               }
-       }
+       cb = filter_data_add_callback(data, connect, disconnect, NULL, NULL,
+                                       user_data);
+       if (!cb)
+               return 0;
 
-done:
        if (connect)
                check_service(connection, name, connect, user_data);
 
-       return listener_id;
+       return cb->id;
 }
 
 guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
@@ -440,72 +572,57 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
 }
 
 guint g_dbus_add_signal_watch(DBusConnection *connection,
-                               const char *rule, GDBusSignalFunction function,
-                               void *user_data, GDBusDestroyFunction destroy)
+                               const char *sender, const char *path,
+                               const char *interface, const char *member,
+                               GDBusSignalFunction function, void *user_data,
+                               GDBusDestroyFunction destroy)
 {
-       return 0;
+       struct filter_data *data;
+       struct filter_callback *cb;
+
+       data = filter_data_get(connection, signal_filter, sender, path,
+                               interface, member, NULL);
+       if (!data)
+               return 0;
+
+       cb = filter_data_add_callback(data, NULL, NULL, function, destroy,
+                                       user_data);
+       if (!cb)
+               return 0;
+
+       return cb->id;
 }
 
 gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
 {
-       struct name_data *data;
-       struct name_callback *cb;
-       GSList *ldata, *lcb;
+       struct filter_data *data;
+       struct filter_callback *cb;
+       GSList *ldata;
 
        if (id == 0)
                return FALSE;
 
-       for (ldata = name_listeners; ldata; ldata = ldata->next) {
+       for (ldata = listeners; ldata; ldata = ldata->next) {
                data = ldata->data;
-               for (lcb = data->callbacks; lcb; lcb = lcb->next) {
-                       cb = lcb->data;
-                       if (cb->id == id)
-                               goto remove;
-               }
-               for (lcb = data->processed; lcb; lcb = lcb->next) {
-                       cb = lcb->data;
-                       if (cb->id == id)
-                               goto remove;
+
+               cb = filter_data_find_callback(data, id);
+               if (cb) {
+                       filter_data_remove_callback(data, cb);
+                       return TRUE;
                }
        }
 
        return FALSE;
-
-remove:
-       data->callbacks = g_slist_remove(data->callbacks, cb);
-       data->processed = g_slist_remove(data->processed, cb);
-       g_free(cb);
-
-       /* Don't remove the filter if other callbacks exist or data is lock
-        * processing callbacks */
-       if (data->callbacks || data->lock)
-               return TRUE;
-
-       if (data->name) {
-               if (!remove_match(data->connection, data->name))
-                       return FALSE;
-       }
-
-       name_listeners = g_slist_remove(name_listeners, data);
-       name_data_free(data);
-
-       /* Remove filter if there are no listeners left for the connection */
-       data = name_data_find(connection, NULL);
-       if (!data)
-               dbus_connection_remove_filter(connection, name_exit_filter,
-                                               NULL);
-
-       return TRUE;
 }
 
 void g_dbus_remove_all_watches(DBusConnection *connection)
 {
-       struct name_data *data;
+       struct filter_data *data;
 
-       while ((data = name_data_find(connection, NULL))) {
-               name_listeners = g_slist_remove(name_listeners, data);
-               name_data_call_and_free(data);
+       while ((data = filter_data_find(connection, NULL, NULL, NULL, NULL, NULL))) {
+               listeners = g_slist_remove(listeners, data);
+               filter_data_call_and_free(data);
        }
 
-       dbus_connection_remove_filter(connection, name_exit_filter, NULL);
+       dbus_connection_remove_filter(connection, message_filter, NULL);
 }