[pulseaudio-discuss] [PATCH v2 7/9] proplist: Add pa_proplist_update_info

Tanu Kaskinen tanuk at iki.fi
Wed Feb 20 10:24:02 PST 2013


I was writing function pa_device_port_update_proplist(), and I wanted
it to send change notifications only if the proplist actually changes.
pa_proplist_update() doesn't provide any indication about whether the
proplist changed or not, so some kind of a solution was needed.

The simple solution would be to create a copy of the port proplist
before calling pa_proplist_update() and check if the copy equals the
port proplist after calling pa_proplist_update(). That felt overly
wasteful, however: it would mean copying the whole property list and
comparing every property in it whenever someone changes even just one
property.

So, I invented a more complex solution: a pa_proplist_update_info
object that holds a description of per-property operations to be
applied to a property list. pa_proplist_apply_update_info() iterates
through the operations and applies them one by one, keeping track of
whether the operations cause actual changes.
---
 src/map-file         |    4 +
 src/pulse/proplist.c |  220 ++++++++++++++++++++++++++++++++++++++++++++++----
 src/pulse/proplist.h |   70 ++++++++++++++++
 3 files changed, 279 insertions(+), 15 deletions(-)

diff --git a/src/map-file b/src/map-file
index 91d61c2..3bd5ddb 100644
--- a/src/map-file
+++ b/src/map-file
@@ -224,6 +224,7 @@ pa_operation_set_state_callback;
 pa_operation_unref;
 pa_parse_sample_format;
 pa_path_get_filename;
+pa_proplist_apply_update_info;
 pa_proplist_clear;
 pa_proplist_contains;
 pa_proplist_copy;
@@ -246,6 +247,9 @@ pa_proplist_to_string_sep;
 pa_proplist_unset;
 pa_proplist_unset_many;
 pa_proplist_update;
+pa_proplist_update_info_add;
+pa_proplist_update_info_free;
+pa_proplist_update_info_new;
 pa_rtclock_now;
 pa_sample_format_is_be;
 pa_sample_format_is_le;
diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c
index abd551b..5236179 100644
--- a/src/pulse/proplist.c
+++ b/src/pulse/proplist.c
@@ -35,12 +35,22 @@
 
 #include "proplist.h"
 
+struct pa_proplist_update_info {
+    pa_hashmap *items;
+};
+
 struct property {
     char *key;
     void *value;
     size_t nbytes;
 };
 
+struct update_info_item {
+    pa_proplist_operation_t operation;
+    char *key;
+    char *sets_value;
+};
+
 #define MAKE_HASHMAP(p) ((pa_hashmap*) (p))
 #define MAKE_PROPLIST(p) ((pa_proplist*) (p))
 
@@ -73,11 +83,33 @@ void pa_proplist_free(pa_proplist* p) {
     pa_hashmap_free(MAKE_HASHMAP(p), (pa_free_cb_t) property_free);
 }
 
+/* Returns true if the proplist changed. */
+static bool pa_proplist_sets_internal(pa_proplist *proplist, const char *key, const char *value) {
+    size_t nbytes;
+    struct property *property;
+
+    nbytes = strlen(value) + 1;
+    property = pa_hashmap_get(MAKE_HASHMAP(proplist), key);
+
+    if (!property) {
+        property = pa_xnew(struct property, 1);
+        property->key = pa_xstrdup(key);
+        pa_hashmap_put(MAKE_HASHMAP(proplist), property->key, property);
+    } else {
+        if (nbytes == property->nbytes && !memcmp(value, property->value, nbytes))
+            return false;
+
+        pa_xfree(property->value);
+    }
+
+    property->value = pa_xstrdup(value);
+    property->nbytes = nbytes;
+
+    return true;
+}
+
 /** Will accept only valid UTF-8 */
 int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) {
-    struct property *prop;
-    pa_bool_t add = FALSE;
-
     pa_assert(p);
     pa_assert(key);
     pa_assert(value);
@@ -85,18 +117,7 @@ int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) {
     if (!pa_proplist_key_valid(key) || !pa_utf8_valid(value))
         return -1;
 
-    if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) {
-        prop = pa_xnew(struct property, 1);
-        prop->key = pa_xstrdup(key);
-        add = TRUE;
-    } else
-        pa_xfree(prop->value);
-
-    prop->value = pa_xstrdup(value);
-    prop->nbytes = strlen(value)+1;
-
-    if (add)
-        pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop);
+    pa_proplist_sets_internal(p, key, value);
 
     return 0;
 }
@@ -708,3 +729,172 @@ int pa_proplist_equal(pa_proplist *a, pa_proplist *b) {
 
     return 1;
 }
+
+static struct update_info_item *update_info_item_new(pa_proplist_operation_t operation, const char *key) {
+    struct update_info_item *item;
+
+    pa_assert(key);
+
+    item = pa_xnew0(struct update_info_item, 1);
+    item->operation = operation;
+    item->key = pa_xstrdup(key);
+
+    return item;
+}
+
+static void update_info_item_free(struct update_info_item *item) {
+    pa_assert(item);
+
+    pa_xfree(item->sets_value);
+    pa_xfree(item->key);
+    pa_xfree(item);
+}
+
+pa_proplist_update_info *pa_proplist_update_info_new(void) {
+    pa_proplist_update_info *info;
+
+    info = pa_xnew0(pa_proplist_update_info, 1);
+    info->items = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    return info;
+}
+
+void pa_proplist_update_info_free(pa_proplist_update_info *info) {
+    pa_assert(info);
+
+    pa_hashmap_free(info->items, (pa_free_cb_t) update_info_item_free);
+    pa_xfree(info);
+}
+
+int pa_proplist_update_info_add(pa_proplist_update_info *info, int operation, ...) {
+    /* This function will first validate and copy the function arguments into
+     * the new_items hashmap. After all inputs have been validated, the
+     * new_items contents are moved into info->items. If validation errors
+     * occur, info won't be altered. */
+
+    pa_proplist_operation_t op;
+    va_list ap;
+    pa_hashmap *new_items;
+    const char *key;
+    struct update_info_item *new_item;
+    void *state;
+
+    pa_assert(info);
+
+    op = operation;
+
+    if (op == PA_PROPLIST_OPERATION_INVALID)
+        return 0;
+
+    va_start(ap, operation);
+    new_items = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    /* The "copy and validate arguments" loop. */
+    while (op != PA_PROPLIST_OPERATION_INVALID) {
+        switch (op) {
+            case PA_PROPLIST_OPERATION_UNSET: {
+                struct update_info_item *item;
+
+                key = va_arg(ap, const char *);
+
+                if (!key || !pa_proplist_key_valid(key))
+                    goto fail;
+
+                item = pa_hashmap_get(new_items, key);
+
+                if (item)
+                    item->operation = PA_PROPLIST_OPERATION_UNSET;
+                else {
+                    item = update_info_item_new(PA_PROPLIST_OPERATION_UNSET, key);
+                    pa_hashmap_put(new_items, key, item);
+                }
+
+                break;
+            }
+
+            case PA_PROPLIST_OPERATION_SETS: {
+                const char *value;
+                struct update_info_item *item;
+
+                key = va_arg(ap, const char *);
+
+                if (!key || !pa_proplist_key_valid(key))
+                    goto fail;
+
+                value = va_arg(ap, const char *);
+
+                if (!value || !pa_utf8_valid(value))
+                    goto fail;
+
+                item = pa_hashmap_get(new_items, key);
+
+                if (item)
+                    item->operation = PA_PROPLIST_OPERATION_SETS;
+                else {
+                    item = update_info_item_new(PA_PROPLIST_OPERATION_SETS, key);
+                    pa_hashmap_put(new_items, key, item);
+                }
+
+                pa_xfree(item->sets_value);
+                item->sets_value = pa_xstrdup(value);
+
+                break;
+            }
+
+            default:
+                goto fail;
+        }
+
+        op = va_arg(ap, int);
+    }
+
+    va_end(ap);
+
+    /* The "move items from new_items to info->items" loop. */
+    PA_HASHMAP_FOREACH(new_item, new_items, state) {
+        struct update_info_item *old_item;
+
+        old_item = pa_hashmap_remove(info->items, new_item->key);
+
+        if (old_item)
+            update_info_item_free(old_item);
+
+        pa_hashmap_put(info->items, new_item->key, new_item);
+    }
+
+    pa_hashmap_free(new_items, NULL);
+
+    return 0;
+
+fail:
+    va_end(ap);
+    pa_hashmap_free(new_items, (pa_free_cb_t) update_info_item_free);
+
+    return -PA_ERR_INVALID;
+}
+
+int pa_proplist_apply_update_info(pa_proplist *proplist, pa_proplist_update_info *info) {
+    struct update_info_item *item;
+    void *state;
+    bool changed = false;
+
+    pa_assert(proplist);
+    pa_assert(info);
+
+    PA_HASHMAP_FOREACH(item, info->items, state) {
+        switch (item->operation) {
+            case PA_PROPLIST_OPERATION_INVALID:
+                pa_assert_not_reached();
+
+            case PA_PROPLIST_OPERATION_UNSET:
+                changed |= pa_proplist_unset(proplist, item->key) >= 0;
+                break;
+
+            case PA_PROPLIST_OPERATION_SETS:
+                changed |= pa_proplist_sets_internal(proplist, item->key, item->sets_value);
+                break;
+        }
+    }
+
+    return changed;
+}
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
index cb53cf4..a2bbeff 100644
--- a/src/pulse/proplist.h
+++ b/src/pulse/proplist.h
@@ -273,6 +273,28 @@ PA_C_DECL_BEGIN
  * as keys and arbitrary data as values. \since 0.9.11 */
 typedef struct pa_proplist pa_proplist;
 
+/** A description of changes to be applied to a property list. \since 4.0 */
+typedef struct pa_proplist_update_info pa_proplist_update_info;
+
+/** Operation type enumeration for pa_proplist_update_info_add(). \since 4.0 */
+typedef enum {
+    PA_PROPLIST_OPERATION_INVALID = -1,
+    /**< Invalid operation type. */
+
+    PA_PROPLIST_OPERATION_UNSET = 0,
+    /**< Unset a property. Takes the property name as a parameter. */
+
+    PA_PROPLIST_OPERATION_SETS
+    /**< Set a string property. Takes the property name and the new value as
+     * parameters. Accepts only UTF-8 strings. */
+} pa_proplist_operation_t;
+
+/** \cond fulldocs */
+#define PA_PROPLIST_OPERATION_INVALID PA_PROPLIST_OPERATION_INVALID
+#define PA_PROPLIST_OPERATION_UNSET PA_PROPLIST_OPERATION_UNSET
+#define PA_PROPLIST_OPERATION_SETS PA_PROPLIST_OPERATION_SETS
+/** \endcond */
+
 /** Allocate a property list. \since 0.9.11 */
 pa_proplist* pa_proplist_new(void);
 
@@ -406,6 +428,54 @@ int pa_proplist_isempty(pa_proplist *p);
  * \since 0.9.16 */
 int pa_proplist_equal(pa_proplist *a, pa_proplist *b);
 
+/** Allocate an empty update info object. \since 4.0 */
+pa_proplist_update_info *pa_proplist_update_info_new(void);
+
+/** Frees "info". \since 4.0 */
+void pa_proplist_update_info_free(pa_proplist_update_info *info);
+
+/** Add update operations to "info". The arguments are a list of operations,
+ * each item in the list consisting of the operation type and parameters
+ * specific for that operation. The operation type must be one of the constants
+ * defined in the #pa_proplist_operation_t enumeration. The operation
+ * parameters are documented in the documentation of #pa_proplist_operation_t.
+ * The list is terminated with #PA_PROPLIST_OPERATION_INVALID. Any strings
+ * given as operation parameters are copied. Returns 0 on success, or
+ * -PA_ERR_INVALID in case of invalid arguments. In case of failure, "info" is
+ * not modified at all.
+ *
+ * Example:
+ *
+ *     char *foo = ...;
+ *     char *bar = ...;
+ *     pa_proplist *proplist = ...;
+ *     pa_proplist_update_info *info = pa_proplist_info_new();
+ *     int changed;
+ *
+ *     if (foo && bar)
+ *             pa_proplist_update_info_add(info,
+ *                                         PA_PROPLIST_OPERATION_SETS, "some.property", foo,
+ *                                         PA_PROPLIST_OPERATION_SETS, "some.other.property", bar,
+ *                                         PA_PROPLIST_OPERATION_INVALID);
+ *     else
+ *             pa_proplist_update_info_add(info,
+ *                                         PA_PROPLIST_OPERATION_UNSET, "some.property",
+ *                                         PA_PROPLIST_OPEARTION_UNSET, "some.other.property",
+ *                                         PA_PROPLIST_OPERATION_INVALID);
+ *
+ *     changed = pa_proplist_apply_update_info(proplist, info);
+ *     pa_proplist_update_info_free(info);
+ *
+ *     if (changed)
+ *             printf("Property list changed!\n");
+ *
+ * \since 4.0 */
+int pa_proplist_update_info_add(pa_proplist_update_info *info, int operation, ...);
+
+/** Apply a list of updates to "proplist". Returns a non-zero value if the
+ * property list changed. \since 4.0 */
+int pa_proplist_apply_update_info(pa_proplist *proplist, pa_proplist_update_info *info);
+
 PA_C_DECL_END
 
 #endif
-- 
1.7.10.4



More information about the pulseaudio-discuss mailing list