From de273c875c25c1bd5ab8a012bd9e4ec01b6516d2 Mon Sep 17 00:00:00 2001 From: Luiz Augusto Von Dentz Date: Tue, 29 Dec 2009 10:53:21 +0200 Subject: [PATCH] Add initial implementation of g_dbus_add_signal_watch With g_dbus_add_signal_watch there is no need to register multiple filters for dbus nor add matching rules manually. --- gdbus/gdbus.h | 6 +- gdbus/watch.c | 517 +++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 321 insertions(+), 202 deletions(-) diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h index 244f797..170b669 100644 --- a/gdbus/gdbus.h +++ b/gdbus/gdbus.h @@ -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); diff --git a/gdbus/watch.c b/gdbus/watch.c index 45dc367..3883f4d 100644 --- a/gdbus/watch.c +++ b/gdbus/watch.c @@ -37,76 +37,206 @@ #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); } -- 2.7.4