Try the Wireless Extension driver first for now
[connman] / gdbus / watch.c
index fb0bef3..c7a4e69 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  D-Bus helper library
  *
- *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
 #define error(fmt...)
 #define debug(fmt...)
 
+static DBusHandlerResult name_exit_filter(DBusConnection *connection,
+                                       DBusMessage *message, void *user_data);
+
 static guint listener_id = 0;
 static GSList *name_listeners = NULL;
 
 struct name_callback {
-       GDBusWatchFunction func;
+       GDBusWatchFunction conn_func;
+       GDBusWatchFunction disc_func;
        void *user_data;
        guint id;
 };
@@ -50,6 +54,8 @@ struct name_data {
        DBusConnection *connection;
        char *name;
        GSList *callbacks;
+       GSList *processed;
+       gboolean lock;
 };
 
 static struct name_data *name_data_find(DBusConnection *connection,
@@ -61,26 +67,23 @@ static struct name_data *name_data_find(DBusConnection *connection,
                        current != NULL; current = current->next) {
                struct name_data *data = current->data;
 
-               if (name == NULL && data->name == NULL) {
-                       if (connection == data->connection)
-                               return data;
-               } else {
-                       if (strcmp(name, data->name) == 0)
-                               return data;
-               }
+               if (connection != data->connection)
+                       continue;
+
+               if (name == NULL || g_str_equal(name, data->name))
+                       return data;
        }
 
        return NULL;
 }
 
-static struct name_callback *name_callback_find(GSList *callbacks,
-                                       GDBusWatchFunction func, void *user_data)
+static struct name_callback *name_callback_find(GSList *callbacks, guint id)
 {
        GSList *current;
 
        for (current = callbacks; current != NULL; current = current->next) {
                struct name_callback *cb = current->data;
-               if (cb->func == func && cb->user_data == user_data)
+               if (cb->id == id)
                        return cb;
        }
 
@@ -93,8 +96,8 @@ static void name_data_call_and_free(struct name_data *data)
 
        for (l = data->callbacks; l != NULL; l = l->next) {
                struct name_callback *cb = l->data;
-               if (cb->func)
-                       cb->func(cb->user_data);
+               if (cb->disc_func)
+                       cb->disc_func(data->connection, cb->user_data);
                g_free(cb);
        }
 
@@ -116,7 +119,9 @@ static void name_data_free(struct name_data *data)
 }
 
 static int name_data_add(DBusConnection *connection, const char *name,
-                               GDBusWatchFunction func, void *user_data, guint id)
+                                               GDBusWatchFunction connect,
+                                               GDBusWatchFunction disconnect,
+                                               void *user_data, guint id)
 {
        int first = 1;
        struct name_data *data = NULL;
@@ -124,7 +129,8 @@ static int name_data_add(DBusConnection *connection, const char *name,
 
        cb = g_new(struct name_callback, 1);
 
-       cb->func = func;
+       cb->conn_func = connect;
+       cb->disc_func = disconnect;
        cb->user_data = user_data;
        cb->id = id;
 
@@ -142,12 +148,16 @@ static int name_data_add(DBusConnection *connection, const char *name,
        name_listeners = g_slist_append(name_listeners, data);
 
 done:
-       data->callbacks = g_slist_append(data->callbacks, cb);
+       if (data->lock)
+               data->processed = g_slist_append(data->processed, cb);
+       else
+               data->callbacks = g_slist_append(data->callbacks, cb);
+
        return first;
 }
 
 static void name_data_remove(DBusConnection *connection,
-                       const char *name, GDBusWatchFunction func, void *user_data)
+                                       const char *name, guint id)
 {
        struct name_data *data;
        struct name_callback *cb = NULL;
@@ -156,16 +166,24 @@ static void name_data_remove(DBusConnection *connection,
        if (!data)
                return;
 
-       cb = name_callback_find(data->callbacks, func, user_data);
+       cb = name_callback_find(data->callbacks, id);
        if (cb) {
                data->callbacks = g_slist_remove(data->callbacks, cb);
                g_free(cb);
        }
 
-       if (!data->callbacks) {
-               name_listeners = g_slist_remove(name_listeners, data);
-               name_data_free(data);
-       }
+       if (data->callbacks)
+               return;
+
+       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);
 }
 
 static gboolean add_match(DBusConnection *connection, const char *name)
@@ -217,8 +235,8 @@ static gboolean remove_match(DBusConnection *connection, const char *name)
 static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                                        DBusMessage *message, void *user_data)
 {
-       GSList *l;
        struct name_data *data;
+       struct name_callback *cb;
        char *name, *old, *new;
 
        if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
@@ -234,37 +252,69 @@ static DBusHandlerResult name_exit_filter(DBusConnection *connection,
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
 
-       /* We are not interested of service creations */
-       if (*new != '\0')
-               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;
        }
 
-       for (l = data->callbacks; l != NULL; l = l->next) {
-               struct name_callback *cb = l->data;
-               cb->func(cb->user_data);
+       data->lock = TRUE;
+
+       while (data->callbacks) {
+               cb = data->callbacks->data;
+
+               if (*new == '\0') {
+                       if (cb->disc_func)
+                               cb->disc_func(connection, cb->user_data);
+               } else {
+                       if (cb->conn_func)
+                               cb->conn_func(connection, cb->user_data);
+               }
+
+               /* Check if the watch was removed/freed by the callback
+                * function */
+               if (!g_slist_find(data->callbacks, cb))
+                       continue;
+
+               data->callbacks = g_slist_remove(data->callbacks, cb);
+
+               if (!cb->conn_func || !cb->disc_func) {
+                       g_free(cb);
+                       continue;
+               }
+
+               data->processed = g_slist_append(data->processed, cb);
        }
 
+       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 filter if there no listener left for the connection */
+       data = name_data_find(connection, NULL);
+       if (!data)
+               dbus_connection_remove_filter(connection, name_exit_filter,
+                                               NULL);
+
        remove_match(connection, name);
 
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
-guint g_dbus_add_disconnect_watch(DBusConnection *connection,
-                               const char *name,
-                               GDBusWatchFunction func,
+guint g_dbus_add_service_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction connect,
+                               GDBusWatchFunction disconnect,
                                void *user_data, GDBusDestroyFunction destroy)
 {
        int first;
 
-       if (!listener_id) {
+       if (!name_data_find(connection, NULL)) {
                if (!dbus_connection_add_filter(connection,
                                        name_exit_filter, NULL, NULL)) {
                        error("dbus_connection_add_filter() failed");
@@ -273,7 +323,8 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection,
        }
 
        listener_id++;
-       first = name_data_add(connection, name, func, user_data, 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)
@@ -283,7 +334,7 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection,
                debug("name_listener_add(%s)", name);
 
                if (!add_match(connection, name)) {
-                       name_data_remove(connection, name, func, user_data);
+                       name_data_remove(connection, name, listener_id);
                        return 0;
                }
        }
@@ -291,6 +342,14 @@ guint g_dbus_add_disconnect_watch(DBusConnection *connection,
        return listener_id;
 }
 
+guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name,
+                               GDBusWatchFunction func,
+                               void *user_data, GDBusDestroyFunction destroy)
+{
+       return g_dbus_add_service_watch(connection, name, NULL, func,
+                                                       user_data, destroy);
+}
+
 guint g_dbus_add_signal_watch(DBusConnection *connection,
                                const char *rule, GDBusSignalFunction function,
                                void *user_data, GDBusDestroyFunction destroy)
@@ -314,16 +373,23 @@ gboolean g_dbus_remove_watch(DBusConnection *connection, guint id)
                        if (cb->id == id)
                                goto remove;
                }
+               for (lcb = data->processed; lcb; lcb = lcb->next) {
+                       cb = lcb->data;
+                       if (cb->id == id)
+                               goto remove;
+               }
        }
 
        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 */
-       if (data->callbacks)
+       /* 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) {
@@ -334,6 +400,12 @@ remove:
        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;
 }
 
@@ -341,13 +413,10 @@ void g_dbus_remove_all_watches(DBusConnection *connection)
 {
        struct name_data *data;
 
-       data = name_data_find(connection, NULL);
-       if (!data) {
-               error("name_listener_indicate_disconnect: no listener found");
-               return;
+       while ((data = name_data_find(connection, NULL))) {
+               name_listeners = g_slist_remove(name_listeners, data);
+               name_data_call_and_free(data);
        }
 
-       debug("name_listener_indicate_disconnect");
-
-       name_data_call_and_free(data);
+       dbus_connection_remove_filter(connection, name_exit_filter, NULL);
 }