diff options
author | Jens Georg <mail@jensge.org> | 2022-06-19 20:01:45 +0200 |
---|---|---|
committer | Jens Georg <mail@jensge.org> | 2022-06-19 20:06:12 +0200 |
commit | 67a109cc7e219c5be7a23fd4318db5d0758b05cc (patch) | |
tree | b420bb660dbd8b6461e67647503f8ad519fdc0d3 | |
parent | 532e46739f8e160bdd9d38f21c46129d7e4d628e (diff) | |
download | gupnp-67a109cc7e219c5be7a23fd4318db5d0758b05cc.tar.gz |
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
-rw-r--r-- | libgupnp/gupnp-context-manager.c | 424 | ||||
-rw-r--r-- | 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 (); } |