summaryrefslogtreecommitdiff
path: root/libgupnp
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2022-06-19 20:01:45 +0200
committerJens Georg <mail@jensge.org>2022-06-19 20:06:12 +0200
commit67a109cc7e219c5be7a23fd4318db5d0758b05cc (patch)
treeb420bb660dbd8b6461e67647503f8ad519fdc0d3 /libgupnp
parent532e46739f8e160bdd9d38f21c46129d7e4d628e (diff)
downloadgupnp-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.c424
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);
}
/**