[gupnp/wip/context-manager-tests: 3/3] ContextManager: Handle filter events properly




commit 67a109cc7e219c5be7a23fd4318db5d0758b05cc
Author: Jens Georg <mail jensge org>
Date:   Sun Jun 19 20:01:45 2022 +0200

    ContextManager: Handle filter events properly
    
    If the filter is changed, announce all changed to contexts as well
    
    This means if the filter is changed, causing a known device not to match
    anymore, it will signal "context-unavailable" and drop all managed
    devices attached to that context.
    
    Likewise, for filter changes that cause contexts to not being filtered
    out any more, "context-available" will be signalled.
    
    Fixes #36
    Fixes #37

 libgupnp/gupnp-context-manager.c | 424 ++++++++++++++++++++-------------------
 tests/test-context-manager.c     | 209 +++++++++++++++++--
 2 files changed, 420 insertions(+), 213 deletions(-)
---
diff --git a/libgupnp/gupnp-context-manager.c b/libgupnp/gupnp-context-manager.c
index 9501f3d..b9cf511 100644
--- a/libgupnp/gupnp-context-manager.c
+++ b/libgupnp/gupnp-context-manager.c
@@ -47,10 +47,16 @@ struct _GUPnPContextManagerPrivate {
 
         GUPnPContextManager *impl;
 
-        GList *objects; /* control points and root devices */
+        GPtrArray *control_points;
+        GPtrArray *root_devices;
+
         GList *filtered; /* Filtered contexts */
 
+        // map of context -> managed objects, doubles also as a set of seen contexts
+        GHashTable *contexts;
+
         GUPnPContextFilter *context_filter;
+        gboolean syntesized_internal;
 };
 typedef struct _GUPnPContextManagerPrivate GUPnPContextManagerPrivate;
 
@@ -92,9 +98,10 @@ enum {
 
 static guint signals[SIGNAL_LAST];
 
-static gint32
-handle_update (GUPnPRootDevice *root_device)
+static void
+handle_update (GUPnPRootDevice *root_device, gpointer user_data)
 {
+        gint32 *output = user_data;
         gint32 boot_id;
         GSSDPResourceGroup *group = NULL;
         GSSDPClient *client = NULL;
@@ -104,7 +111,48 @@ handle_update (GUPnPRootDevice *root_device)
         g_object_get (G_OBJECT (client), "boot-id", &boot_id, NULL);
         gssdp_resource_group_update (group, ++boot_id);
 
-        return boot_id;
+        *output = boot_id;
+}
+
+static gboolean
+context_filtered (GUPnPContextFilter *filter, GUPnPContext *context)
+{
+        return !gupnp_context_filter_is_empty (filter) &&
+               gupnp_context_filter_get_enabled (filter) &&
+               !gupnp_context_filter_check_context (filter, context);
+}
+
+static GPtrArray *
+ensure_context (GHashTable *contexts, GUPnPContext *context)
+{
+        GPtrArray *objects = g_hash_table_lookup (contexts, context);
+        if (objects == NULL) {
+                objects = g_ptr_array_new_with_free_func (g_object_unref);
+                g_hash_table_insert (contexts, g_object_ref (context), objects);
+        }
+
+        return objects;
+}
+
+static void
+do_boot_id_update_for_root_devices (GUPnPContextManager *manager)
+{
+        GUPnPContextManagerPrivate *priv;
+        priv = gupnp_context_manager_get_instance_private (manager);
+
+        // Nothing to do for UDA 1.0. It does not have the boot-id
+        // concept
+        if (priv->uda_version == GSSDP_UDA_VERSION_1_0) {
+                return;
+        }
+
+        gint32 boot_id = -1;
+        g_ptr_array_foreach (priv->root_devices,
+                             (GFunc) handle_update,
+                             &boot_id);
+        if (boot_id > 1) {
+                priv->boot_id = boot_id;
+        }
 }
 
 static void
@@ -112,54 +160,31 @@ on_context_available (GUPnPContextManager    *manager,
                       GUPnPContext           *context,
                       G_GNUC_UNUSED gpointer *user_data)
 {
-        GUPnPContextFilter *context_filter;
         GUPnPContextManagerPrivate *priv;
-        gboolean enabled = TRUE;
-
         priv = gupnp_context_manager_get_instance_private (manager);
 
-        context_filter = priv->context_filter;
+        if (priv->syntesized_internal)
+                return;
+
+        ensure_context (priv->contexts, context);
 
-        /* Try to catch the notification, only if the context filter
-         * is enabled, not empty and the context doesn't match */
-        if (!gupnp_context_filter_is_empty (context_filter) &&
-            gupnp_context_filter_get_enabled (context_filter) &&
-            !gupnp_context_filter_check_context (context_filter, context)) {
+        if (context_filtered (priv->context_filter, context)) {
                 /* If the context doesn't match, block the notification
                  * and disable the context */
                 g_signal_stop_emission_by_name (manager, "context-available");
 
                 /* Make sure we don't send anything on now blocked network */
                 g_object_set (context, "active", FALSE, NULL);
-                enabled = FALSE;
 
                 /* Save it in case we need to re-enable it */
                 priv->filtered =
                         g_list_prepend (priv->filtered, g_object_ref (context));
-        }
 
-        /* Ignore the boot-id handling for UDA 1.0 */
-        if (priv->uda_version == GSSDP_UDA_VERSION_1_0)
                 return;
-
-        if (enabled) {
-                /* We have a new context, so we need to send ssdp:update and
-                 * re-announce on the old clients */
-                GList *l = priv->objects;
-                gint32 boot_id = -1;
-
-                while (l) {
-                        if (GUPNP_IS_ROOT_DEVICE (l->data)) {
-                                boot_id = handle_update (GUPNP_ROOT_DEVICE (l->data));
-                        }
-                        l = l->next;
-                }
-
-                if (boot_id > -1) {
-                        priv->boot_id = boot_id;
-                }
         }
 
+        do_boot_id_update_for_root_devices (manager);
+
         /* The new client gets the current boot-id */
         gssdp_client_set_boot_id (GSSDP_CLIENT (context), priv->boot_id);
 }
@@ -169,199 +194,176 @@ on_context_unavailable (GUPnPContextManager    *manager,
                         GUPnPContext           *context,
                         G_GNUC_UNUSED gpointer *user_data)
 {
-        GList *l;
-        GList *filtered_context;
         GUPnPContextManagerPrivate *priv;
-
         priv = gupnp_context_manager_get_instance_private (manager);
 
+        if (priv->syntesized_internal)
+                return;
+
         /* Make sure we don't send anything on now unavailable network */
         g_object_set (context, "active", FALSE, NULL);
 
-        /* Unref all associated objects */
-        l = priv->objects;
-
-        while (l) {
-                GUPnPContext *obj_context = NULL;
-
-                if (GUPNP_IS_CONTROL_POINT (l->data)) {
-                        GUPnPControlPoint *cp;
-
-                        cp = GUPNP_CONTROL_POINT (l->data);
-                        obj_context = gupnp_control_point_get_context (cp);
-                } else if (GUPNP_IS_ROOT_DEVICE (l->data)) {
-                        GUPnPDeviceInfo *info;
-
-                        info = GUPNP_DEVICE_INFO (l->data);
-                        obj_context = gupnp_device_info_get_context (info);
-                } else {
-                        g_assert_not_reached ();
-                }
-
-                if (context == obj_context) {
-                        GList *next = l->next;
-
-                        g_object_unref (l->data);
+        GList *ctx = g_list_find (priv->filtered, context);
+        if (ctx != NULL) {
+                g_signal_stop_emission_by_name (manager, "context-unavailable");
 
-                        priv->objects = g_list_delete_link (priv->objects,
-                                                            l);
-                        l = next;
-                } else {
-                        l = l->next;
-                }
+                priv->filtered = g_list_remove_link (priv->filtered, ctx);
+                g_object_unref (ctx);
         }
 
-        filtered_context = g_list_find (priv->filtered, context);
-
-        if (filtered_context != NULL) {
-                g_signal_stop_emission_by_name (manager, "context-unavailable");
+        g_hash_table_remove (priv->contexts, context);
 
-                g_object_unref (filtered_context->data);
-                priv->filtered =
-                        g_list_delete_link (priv->filtered, filtered_context);
-        } else {
-                /* When UDA 1.0, ignore boot-id handling */
-                if (priv->uda_version == GSSDP_UDA_VERSION_1_0) {
-                        return;
-                }
-                /* We have lost a context, so we need to send ssdp:update and
-                 * re-announce on the old clients */
-                GList *l = priv->objects;
-                gint32 boot_id = -1;
-
-                while (l) {
-                        if (GUPNP_IS_ROOT_DEVICE (l->data)) {
-                                boot_id = handle_update (GUPNP_ROOT_DEVICE (l->data));
-                        }
-                        l = l->next;
-                }
+        // The context was not announced, we can just silenty leave after removing
+        // it from the list of all contexts.
+        if (ctx != NULL)
+                return;
 
-                if (boot_id > -1) {
-                        gssdp_client_set_boot_id (GSSDP_CLIENT (context), boot_id);
-                        priv->boot_id = boot_id;
-                }
-        }
+        // Handle the boot-id changes because of changed contexts
+        do_boot_id_update_for_root_devices (manager);
 }
 
 static void
-gupnp_context_manager_filter_context (GUPnPContextFilter *context_filter,
-                                      GUPnPContextManager *manager,
-                                      gboolean check)
+on_context_filter_change_cb (GUPnPContextFilter *context_filter,
+                             GParamSpec *pspec,
+                             gpointer user_data)
 {
-        GList *next;
-        GList *obj;
-        GList *iter;
-        gboolean match;
+        GUPnPContextManager *manager = GUPNP_CONTEXT_MANAGER (user_data);
         GUPnPContextManagerPrivate *priv;
+        gboolean enabled;
 
         priv = gupnp_context_manager_get_instance_private (manager);
+        enabled = gupnp_context_filter_get_enabled (context_filter);
 
-        obj = priv->objects;
-        iter = priv->filtered;
-
-        while (obj != NULL) {
-                /* If the context filter is empty, treat it as disabled */
-                if (check) {
-                        GUPnPContext *context;
-                        const char *property = "context";
+        if (!enabled) {
+                // Don't care. Nothing to do
+                return;
+        }
 
-                        if (GUPNP_IS_CONTROL_POINT (obj->data)) {
-                                property = "client";
+        GHashTableIter iter;
+        g_hash_table_iter_init (&iter, priv->contexts);
+        GUPnPContext *key;
+        while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+                GList *filtered = g_list_find (priv->filtered, key);
+
+                if (context_filtered (context_filter, key)) {
+                        // This context was already filtered. Nothing to
+                        // do
+                        if (filtered != NULL) {
+                                continue;
                         }
 
-                        g_object_get (G_OBJECT (obj->data),
-                                      property, &context,
-                                      NULL);
+                        // This context is now filtered
+                        priv->filtered = g_list_prepend (priv->filtered, key);
 
-                        match = gupnp_context_filter_check_context (
-                                context_filter,
-                                context);
+                        // Drop all references to the objects we manage
+                        g_hash_table_iter_replace (
+                                &iter,
+                                g_ptr_array_new_with_free_func (
+                                        g_object_unref));
 
-                        g_object_unref (context);
-                } else {
-                        /* Re-activate all context, if needed */
-                        match = TRUE;
-                }
+                        // Synthesize unavailable signal
+                        priv->syntesized_internal = TRUE;
+                        g_object_set (G_OBJECT (key), "active", FALSE, NULL);
+                        g_signal_emit (manager,
+                                       signals[CONTEXT_UNAVAILABLE],
+                                       0,
+                                       key);
+                        priv->syntesized_internal = FALSE;
 
-                if (GUPNP_IS_CONTROL_POINT (obj->data)) {
-                        GSSDPResourceBrowser *browser;
-
-                        browser = GSSDP_RESOURCE_BROWSER (obj->data);
-                        gssdp_resource_browser_set_active (browser, match);
-                } else if (GUPNP_IS_ROOT_DEVICE (obj->data)) {
-                        GSSDPResourceGroup *group;
+                } else {
+                        // The context is not filtered
+                        if (filtered == NULL) {
+                                // The context wasn't filtered before
+                                // -> nothing to do
+                                continue;
+                        }
 
-                        group = GSSDP_RESOURCE_GROUP (obj->data);
-                        gssdp_resource_group_set_available (group, match);
-                } else
-                        g_assert_not_reached ();
+                        // Drop the reference from the filter list
+                        priv->filtered =
+                                g_list_delete_link (priv->filtered, filtered);
 
-                obj = obj->next;
-        }
+                        g_object_set (G_OBJECT (key), "active", TRUE, NULL);
 
-        while (iter != NULL) {
-                /* If the context filter is empty, treat it as disabled */
-                if (check)
-                        /* Filter out context */
-                        match = gupnp_context_filter_check_context (
-                                context_filter,
-                                iter->data);
-                else
-                        /* Re-activate all context, if needed */
-                        match = TRUE;
-
-                if (!match) {
-                        iter = iter->next;
-                        continue;
+                        priv->syntesized_internal = TRUE;
+                        g_signal_emit (manager,
+                                       signals[CONTEXT_AVAILABLE],
+                                       0,
+                                       key);
+                        priv->syntesized_internal = FALSE;
                 }
-
-                next = iter->next;
-                g_object_set (iter->data, "active", TRUE, NULL);
-
-                g_signal_emit_by_name (manager,
-                                       "context-available",
-                                       iter->data);
-
-                g_object_unref (iter->data);
-                priv->filtered = g_list_delete_link (priv->filtered, iter);
-                iter = next;
         }
 }
 
-static void
-on_context_filter_change_cb (GUPnPContextFilter *context_filter,
-                             GParamSpec *pspec,
-                             gpointer user_data)
-{
-        GUPnPContextManager *manager = GUPNP_CONTEXT_MANAGER (user_data);
-        gboolean enabled;
-        gboolean is_empty;
-
-        enabled = gupnp_context_filter_get_enabled (context_filter);
-        is_empty = gupnp_context_filter_is_empty (context_filter);
-
-        if (enabled)
-                gupnp_context_manager_filter_context (context_filter,
-                                                      manager,
-                                                      !is_empty);
-}
-
 static void
 on_context_filter_enabled_cb (GUPnPContextFilter *context_filter,
                               GParamSpec *pspec,
                               gpointer user_data)
 {
         GUPnPContextManager *manager = GUPNP_CONTEXT_MANAGER (user_data);
+        GUPnPContextManagerPrivate *priv;
+
         gboolean enabled;
         gboolean is_empty;
 
         enabled = gupnp_context_filter_get_enabled (context_filter);
         is_empty = gupnp_context_filter_is_empty (context_filter);
+        priv = gupnp_context_manager_get_instance_private (manager);
+
+        // we have switched from enabled to disabled. Flush the filtered
+        // context queue
+        if (!enabled) {
+                while (priv->filtered != NULL) {
+                        // This is ok since the filter is disabled. The
+                        // callback will not modify the list as well
+
+                        // Do not block our handler, that is what we want here
+                        g_object_set (G_OBJECT (priv->filtered->data),
+                                      "active",
+                                      TRUE,
+                                      NULL);
+
+                        g_signal_emit (manager,
+                                       signals[CONTEXT_AVAILABLE],
+                                       0,
+                                       priv->filtered->data);
+
+                        priv->filtered = g_list_delete_link (priv->filtered,
+                                                             priv->filtered);
+                }
+
+                return;
+        }
+
+        // We have switched from disabled to enabled, but the filter is empty.
+        // Nothing to do.
+        if (enabled && is_empty) {
+                return;
+        }
 
-        if (!is_empty)
-                gupnp_context_manager_filter_context (context_filter,
-                                                      manager,
-                                                      enabled);
+        GHashTableIter iter;
+        g_hash_table_iter_init (&iter, priv->contexts);
+        GUPnPContext *key;
+        while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+                if (context_filtered (context_filter, key)) {
+                        // This context is now filtered
+                        priv->filtered = g_list_prepend (priv->filtered, key);
+
+                        // Drop all references to the objects we manage
+                        g_hash_table_iter_replace (
+                                &iter,
+                                g_ptr_array_new_with_free_func (
+                                        g_object_unref));
+
+                        // Synthesize unavailable signal
+                        priv->syntesized_internal = TRUE;
+                        g_object_set (G_OBJECT (key), "active", FALSE, NULL);
+                        g_signal_emit (manager,
+                                       signals[CONTEXT_UNAVAILABLE],
+                                       0,
+                                       key);
+                        priv->syntesized_internal = FALSE;
+                }
+        }
 }
 
 static void
@@ -372,6 +374,13 @@ gupnp_context_manager_init (GUPnPContextManager *manager)
         priv = gupnp_context_manager_get_instance_private (manager);
 
         priv->context_filter = g_object_new (GUPNP_TYPE_CONTEXT_FILTER, NULL);
+        priv->contexts =
+                g_hash_table_new_full (g_direct_hash,
+                                       g_direct_equal,
+                                       g_object_unref,
+                                       (GDestroyNotify) g_ptr_array_unref);
+        priv->control_points = g_ptr_array_new ();
+        priv->root_devices = g_ptr_array_new ();
 
         g_signal_connect_after (priv->context_filter,
                                 "notify::entries",
@@ -465,9 +474,12 @@ gupnp_context_manager_dispose (GObject *object)
                                               on_context_filter_change_cb,
                                               NULL);
 
-        g_list_free_full (priv->objects, g_object_unref);
-        priv->objects = NULL;
-        g_list_free_full (priv->filtered, g_object_unref);
+        g_hash_table_destroy (priv->contexts);
+
+        g_ptr_array_free (priv->control_points, TRUE);
+        g_ptr_array_free (priv->root_devices, TRUE);
+
+        g_list_free (priv->filtered);
         priv->filtered = NULL;
 
         g_clear_object (&filter);
@@ -719,23 +731,15 @@ gupnp_context_manager_create_full (GSSDPUDAVersion uda_version, GSocketFamily fa
 void
 gupnp_context_manager_rescan_control_points (GUPnPContextManager *manager)
 {
-        GList *l;
         GUPnPContextManagerPrivate *priv;
 
         g_return_if_fail (GUPNP_IS_CONTEXT_MANAGER (manager));
 
         priv = gupnp_context_manager_get_instance_private (manager);
-        l = priv->objects;
 
-        while (l) {
-                if (GUPNP_IS_CONTROL_POINT (l->data)) {
-                        GSSDPResourceBrowser *browser =
-                                GSSDP_RESOURCE_BROWSER (l->data);
-                        gssdp_resource_browser_rescan (browser);
-                }
-
-                l = l->next;
-        }
+        g_ptr_array_foreach (priv->control_points,
+                             (GFunc) gssdp_resource_browser_rescan,
+                             NULL);
 }
 
 /**
@@ -775,8 +779,17 @@ gupnp_context_manager_manage_control_point (GUPnPContextManager *manager,
         g_return_if_fail (GUPNP_IS_CONTROL_POINT (control_point));
 
         priv = gupnp_context_manager_get_instance_private (manager);
-        priv->objects = g_list_append (priv->objects,
-                                                g_object_ref (control_point));
+
+        GUPnPContext *ctx = GUPNP_CONTEXT (gssdp_resource_browser_get_client (
+                GSSDP_RESOURCE_BROWSER (control_point)));
+
+        GPtrArray *objects = ensure_context (priv->contexts, ctx);
+
+        g_ptr_array_add (objects, g_object_ref (control_point));
+
+        g_object_weak_ref (G_OBJECT (control_point),
+                           (GWeakNotify) g_ptr_array_remove_fast,
+                           priv->control_points);
 }
 
 /**
@@ -818,8 +831,17 @@ gupnp_context_manager_manage_root_device (GUPnPContextManager *manager,
         g_return_if_fail (GUPNP_IS_ROOT_DEVICE (root_device));
 
         priv = gupnp_context_manager_get_instance_private (manager);
-        priv->objects = g_list_append (priv->objects,
-                                       g_object_ref (root_device));
+
+        GUPnPContext *ctx =
+                gupnp_device_info_get_context (GUPNP_DEVICE_INFO (root_device));
+
+        GPtrArray *objects = ensure_context (priv->contexts, ctx);
+
+        g_ptr_array_add (objects, g_object_ref (root_device));
+
+        g_object_weak_ref (G_OBJECT (root_device),
+                           (GWeakNotify) g_ptr_array_remove_fast,
+                           priv->root_devices);
 }
 
 /**
diff --git a/tests/test-context-manager.c b/tests/test-context-manager.c
index ac42c6a..c10cf65 100644
--- a/tests/test-context-manager.c
+++ b/tests/test-context-manager.c
@@ -35,7 +35,12 @@ test_context_manager_manage ()
         GError *error = NULL;
 
         GUPnPContext *ctx = gupnp_context_new ("lo", 0, &error);
-        g_assert_null (error);
+        g_assert_no_error (error);
+        g_assert_nonnull (ctx);
+
+        GUPnPContext *ctx2 = gupnp_context_new ("lo", 0, &error);
+        g_assert_no_error (error);
+        g_assert_nonnull (ctx2);
 
         GUPnPControlPoint *cp =
                 gupnp_control_point_new (ctx, "upnp::rootdevice");
@@ -53,6 +58,13 @@ test_context_manager_manage ()
         // Check that the context manager has kept a reference on cp
         g_assert_nonnull (weak);
 
+        // Annunce ctx2, which does not have a bound cp
+        g_signal_emit_by_name (cm, "context-unavailable", ctx2, NULL);
+
+        // Check that the context manager dropped the reference if the
+        // context is gone
+        g_assert_nonnull (weak);
+
         g_signal_emit_by_name (cm, "context-unavailable", ctx, NULL);
 
         // Check that the context manager dropped the reference if the
@@ -73,20 +85,48 @@ test_context_manager_manage ()
         // Check that the context manager has kept a reference on cp
         g_assert_nonnull (weak);
 
+        // Unannunce ctx2, which does not have a bound root device
+        g_signal_emit_by_name (cm, "context-unavailable", ctx2, NULL);
+
+        // Check that the context manager dropped the reference if the
+        // context is gone
+        g_assert_nonnull (weak);
+
         g_signal_emit_by_name (cm, "context-unavailable", ctx, NULL);
 
         // Check that the context manager dropped the reference if the
         // context is gone
         g_assert_null (weak);
 
+        // Check that tearing down the context manager tears down the managed devices
+        cp = gupnp_control_point_new (ctx, "upnp::rootdevice");
+        rd = gupnp_root_device_new (ctx, "TestDevice.xml", DATA_PATH, &error);
+        gpointer weak_cp = cp;
+        gpointer weak_rd = rd;
+
+        g_object_add_weak_pointer (G_OBJECT (cp), &weak_cp);
+        g_object_add_weak_pointer (G_OBJECT (rd), &weak_rd);
+
+        gupnp_context_manager_manage_control_point (GUPNP_CONTEXT_MANAGER (cm),
+                                                    cp);
+        gupnp_context_manager_manage_root_device (GUPNP_CONTEXT_MANAGER (cm),
+                                                  rd);
+        g_object_unref (cp);
+        g_object_unref (rd);
         g_object_unref (cm);
+
+        g_assert_null (weak_cp);
+        g_assert_null (weak_rd);
+
         g_object_unref (ctx);
+        g_object_unref (ctx2);
 }
 
 
 typedef struct _EnableDisableTestData {
         GUPnPContext *expected_context;
-        gboolean triggered;
+        gboolean available_triggered;
+        gboolean unavailable_triggered;
 } EnableDisableTestData;
 
 void
@@ -97,7 +137,18 @@ on_context_available (GUPnPContextManager *cm,
         EnableDisableTestData *d = user_data;
 
         g_assert (d->expected_context == ctx);
-        d->triggered = TRUE;
+        d->available_triggered = TRUE;
+}
+
+void
+on_context_unavailable (GUPnPContextManager *cm,
+                        GUPnPContext *ctx,
+                        gpointer user_data)
+{
+        EnableDisableTestData *d = user_data;
+
+        g_assert (d->expected_context == ctx);
+        d->unavailable_triggered = TRUE;
 }
 
 void
@@ -119,10 +170,10 @@ test_context_manager_filter_enable_disable ()
                                             NULL);
         EnableDisableTestData context_available_triggered = {
                 .expected_context = ctx,
-                .triggered = FALSE
+                .available_triggered = FALSE,
+                .unavailable_triggered = FALSE
         };
 
-
         const char *iface = gssdp_client_get_interface (GSSDP_CLIENT (ctx));
 
         g_assert_no_error (error);
@@ -141,42 +192,172 @@ test_context_manager_filter_enable_disable ()
                           "context-available",
                           G_CALLBACK (on_context_available),
                           &context_available_triggered);
+        g_signal_connect (cm,
+                          "context-unavailable",
+                          G_CALLBACK (on_context_unavailable),
+                          &context_available_triggered);
 
         g_signal_emit_by_name (cm, "context-available", ctx, NULL);
 
-        g_assert (context_available_triggered.triggered);
+        g_assert (context_available_triggered.available_triggered);
 
         // Enable context filter. Since it is empty, we should still see the event
-        context_available_triggered.triggered = FALSE;
+        context_available_triggered.available_triggered = FALSE;
         gupnp_context_filter_set_enabled (cf, TRUE);
         g_assert (gupnp_context_filter_get_enabled (cf));
 
         g_signal_emit_by_name (cm, "context-available", ctx, NULL);
 
-        g_assert (context_available_triggered.triggered);
+        g_assert (context_available_triggered.available_triggered);
 
         // Enable context filter. Since it is empty, we should still see the event
-        context_available_triggered.triggered = FALSE;
+        context_available_triggered.available_triggered = FALSE;
         gupnp_context_filter_set_enabled (cf, TRUE);
         g_assert (gupnp_context_filter_get_enabled (cf));
 
         g_signal_emit_by_name (cm, "context-available", ctx, NULL);
 
-        g_assert (context_available_triggered.triggered);
+        g_assert (context_available_triggered.available_triggered);
 
         // Filter is enabled and not empty, we should get it since it matches
-        context_available_triggered.triggered = FALSE;
+        context_available_triggered.available_triggered = FALSE;
         g_assert (gupnp_context_filter_get_enabled (cf));
         gupnp_context_filter_add_entry (cf, iface);
         g_assert_false (gupnp_context_filter_is_empty (cf));
 
         g_signal_emit_by_name (cm, "context-available", ctx, NULL);
-        g_assert (context_available_triggered.triggered);
+        g_assert (context_available_triggered.available_triggered);
+
+        // Filter that does not match the triggered context, but is disabled
+        context_available_triggered.available_triggered = FALSE;
+        gupnp_context_filter_set_enabled (cf, FALSE);
+        gupnp_context_filter_clear (cf);
+
+        g_assert_false (gupnp_context_filter_get_enabled (cf));
+        gupnp_context_filter_add_entry (cf, "wl0ps2");
+        g_assert_false (gupnp_context_filter_is_empty (cf));
+
+        g_signal_emit_by_name (cm, "context-available", ctx, NULL);
+        g_assert (context_available_triggered.available_triggered);
+
+        // Now enable the filter. We should get the context-unavailable signal
+        context_available_triggered.available_triggered = FALSE;
+        gupnp_context_filter_set_enabled (cf, TRUE);
+        g_assert (context_available_triggered.unavailable_triggered);
+        g_assert_false (gssdp_client_get_active (GSSDP_CLIENT (ctx)));
+
+        // Now disable the filter. We should get the context-available signal
+        context_available_triggered.available_triggered = FALSE;
+        context_available_triggered.unavailable_triggered = FALSE;
+        gupnp_context_filter_set_enabled (cf, FALSE);
+        g_assert (context_available_triggered.available_triggered);
+        g_assert_false (context_available_triggered.unavailable_triggered);
+        g_assert (gssdp_client_get_active (GSSDP_CLIENT (ctx)));
 
         g_clear_object (&ctx);
         g_clear_error (&error);
 }
 
+void
+test_context_manager_filter_add_remove ()
+{
+        GError *error = NULL;
+
+        GUPnPContext *ctx = g_initable_new (GUPNP_TYPE_CONTEXT,
+                                            NULL,
+                                            &error,
+                                            "host-ip",
+                                            "127.0.0.1",
+
+                                            "network",
+                                            "Free WiFi!",
+
+                                            "active",
+                                            FALSE,
+                                            NULL);
+        EnableDisableTestData context_available_triggered = {
+                .expected_context = ctx,
+                .available_triggered = FALSE,
+                .unavailable_triggered = FALSE
+        };
+
+        const char *iface = gssdp_client_get_interface (GSSDP_CLIENT (ctx));
+
+        g_assert_no_error (error);
+        g_assert_nonnull (ctx);
+
+        TestContextManager *cm =
+                g_object_new (test_context_manager_get_type (), NULL);
+
+        // Check that the context filter is off and empty by default
+        GUPnPContextFilter *cf = gupnp_context_manager_get_context_filter (
+                GUPNP_CONTEXT_MANAGER (cm));
+        g_assert_false (gupnp_context_filter_get_enabled (cf));
+        g_assert (gupnp_context_filter_is_empty (cf));
+
+        g_signal_emit_by_name (cm, "context-available", ctx, NULL);
+
+        g_signal_connect (cm,
+                          "context-available",
+                          G_CALLBACK (on_context_available),
+                          &context_available_triggered);
+        g_signal_connect (cm,
+                          "context-unavailable",
+                          G_CALLBACK (on_context_unavailable),
+                          &context_available_triggered);
+
+        gupnp_context_filter_set_enabled (cf, TRUE);
+        g_assert (gupnp_context_filter_get_enabled (cf));
+        g_assert_false (context_available_triggered.available_triggered);
+        g_assert_false (context_available_triggered.unavailable_triggered);
+
+        context_available_triggered.available_triggered = FALSE;
+        context_available_triggered.unavailable_triggered = FALSE;
+        gupnp_context_filter_add_entry (cf, "wl3ps3");
+        g_assert_false (gupnp_context_filter_is_empty (cf));
+        g_assert_false (context_available_triggered.available_triggered);
+        g_assert (context_available_triggered.unavailable_triggered);
+        g_assert_false (gssdp_client_get_active (GSSDP_CLIENT (ctx)));
+
+        // Adding the same entry should not trigger any event
+        context_available_triggered.available_triggered = FALSE;
+        context_available_triggered.unavailable_triggered = FALSE;
+        gupnp_context_filter_add_entry (cf, "wl3ps3");
+        g_assert_false (gupnp_context_filter_is_empty (cf));
+        g_assert_false (context_available_triggered.available_triggered);
+        g_assert_false (context_available_triggered.unavailable_triggered);
+
+        // Adding an entry that allows the known interface
+        context_available_triggered.available_triggered = FALSE;
+        context_available_triggered.unavailable_triggered = FALSE;
+        gupnp_context_filter_add_entry (cf, iface);
+        g_assert_false (gupnp_context_filter_is_empty (cf));
+        g_assert (context_available_triggered.available_triggered);
+        g_assert_false (context_available_triggered.unavailable_triggered);
+        g_assert (gssdp_client_get_active (GSSDP_CLIENT (ctx)));
+
+        // Check that the manager gives up managed objects if a context
+        // because it disappears from filtering
+        GUPnPControlPoint *cp =
+                gupnp_control_point_new (ctx, "upnp::rootdevice");
+        gpointer weak = cp;
+
+        g_object_add_weak_pointer (G_OBJECT (cp), &weak);
+
+        gupnp_context_manager_manage_control_point (GUPNP_CONTEXT_MANAGER (cm),
+                                                    cp);
+        g_object_unref (cp);
+
+        context_available_triggered.available_triggered = FALSE;
+        context_available_triggered.unavailable_triggered = FALSE;
+        gupnp_context_filter_remove_entry (cf, iface);
+        g_assert_false (gupnp_context_filter_is_empty (cf));
+        g_assert_false (context_available_triggered.available_triggered);
+        g_assert (context_available_triggered.unavailable_triggered);
+        g_assert_false (gssdp_client_get_active (GSSDP_CLIENT (ctx)));
+        g_assert_null (weak);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -184,8 +365,12 @@ main (int argc, char *argv[])
 
         g_test_add_func ("/context-manager/manage",
                          test_context_manager_manage);
+
         g_test_add_func ("/context_manager/filter/enable_disable",
                          test_context_manager_filter_enable_disable);
 
+        g_test_add_func ("/context_manager/filter/add_remove",
+                         test_context_manager_filter_add_remove);
+
         return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]