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 /libgupnp | |
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
Diffstat (limited to 'libgupnp')
-rw-r--r-- | libgupnp/gupnp-context-manager.c | 424 |
1 files changed, 223 insertions, 201 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); } /** |