summaryrefslogtreecommitdiffstats
path: root/src/pipewire/properties.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/pipewire/properties.c733
1 files changed, 733 insertions, 0 deletions
diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c
new file mode 100644
index 0000000..be52a7c
--- /dev/null
+++ b/src/pipewire/properties.c
@@ -0,0 +1,733 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <spa/utils/json.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/array.h"
+#include "pipewire/log.h"
+#include "pipewire/utils.h"
+#include "pipewire/properties.h"
+
+PW_LOG_TOPIC_EXTERN(log_properties);
+#define PW_LOG_TOPIC_DEFAULT log_properties
+
+/** \cond */
+struct properties {
+ struct pw_properties this;
+
+ struct pw_array items;
+};
+/** \endcond */
+
+static int add_func(struct pw_properties *this, char *key, char *value)
+{
+ struct spa_dict_item *item;
+ struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this);
+
+ item = pw_array_add(&impl->items, sizeof(struct spa_dict_item));
+ if (item == NULL) {
+ free(key);
+ free(value);
+ return -errno;
+ }
+
+ item->key = key;
+ item->value = value;
+
+ this->dict.items = impl->items.data;
+ this->dict.n_items++;
+ return 0;
+}
+
+static void clear_item(struct spa_dict_item *item)
+{
+ free((char *) item->key);
+ free((char *) item->value);
+}
+
+static int find_index(const struct pw_properties *this, const char *key)
+{
+ const struct spa_dict_item *item;
+ item = spa_dict_lookup_item(&this->dict, key);
+ if (item == NULL)
+ return -1;
+ return item - this->dict.items;
+}
+
+static struct properties *properties_new(int prealloc)
+{
+ struct properties *impl;
+
+ impl = calloc(1, sizeof(struct properties));
+ if (impl == NULL)
+ return NULL;
+
+ pw_array_init(&impl->items, 16);
+ pw_array_ensure_size(&impl->items, sizeof(struct spa_dict_item) * prealloc);
+
+ return impl;
+}
+
+/** Make a new properties object
+ *
+ * \param key a first key
+ * \param ... value and more keys NULL terminated
+ * \return a newly allocated properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_new(const char *key, ...)
+{
+ struct properties *impl;
+ va_list varargs;
+ const char *value;
+
+ impl = properties_new(16);
+ if (impl == NULL)
+ return NULL;
+
+ va_start(varargs, key);
+ while (key != NULL) {
+ value = va_arg(varargs, char *);
+ if (value && key[0])
+ add_func(&impl->this, strdup(key), strdup(value));
+ key = va_arg(varargs, char *);
+ }
+ va_end(varargs);
+
+ return &impl->this;
+}
+
+/** Make a new properties object from the given dictionary
+ *
+ * \param dict a dictionary. keys and values are copied
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict)
+{
+ uint32_t i;
+ struct properties *impl;
+
+ impl = properties_new(SPA_ROUND_UP_N(dict->n_items, 16));
+ if (impl == NULL)
+ return NULL;
+
+ for (i = 0; i < dict->n_items; i++) {
+ const struct spa_dict_item *it = &dict->items[i];
+ if (it->key != NULL && it->key[0] && it->value != NULL)
+ add_func(&impl->this, strdup(it->key),
+ strdup(it->value));
+ }
+
+ return &impl->this;
+}
+
+/** Update the properties from the given string, overwriting any
+ * existing keys with the new values from \a str.
+ *
+ * \a str should be a whitespace separated list of key=value
+ * strings or a json object, see pw_properties_new_string().
+ *
+ * \return The number of properties added or updated
+ */
+SPA_EXPORT
+int pw_properties_update_string(struct pw_properties *props, const char *str, size_t size)
+{
+ struct properties *impl = SPA_CONTAINER_OF(props, struct properties, this);
+ struct spa_json it[2];
+ char key[1024], *val;
+ int count = 0;
+
+ spa_json_init(&it[0], str, size);
+ if (spa_json_enter_object(&it[0], &it[1]) <= 0)
+ spa_json_init(&it[1], str, size);
+
+ while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
+ int len;
+ const char *value;
+
+ if ((len = spa_json_next(&it[1], &value)) <= 0)
+ break;
+
+ if (spa_json_is_null(value, len))
+ val = NULL;
+ else {
+ if (spa_json_is_container(value, len))
+ len = spa_json_container_len(&it[1], value, len);
+
+ if ((val = malloc(len+1)) != NULL)
+ spa_json_parse_stringn(value, len, val, len+1);
+ }
+ count += pw_properties_set(&impl->this, key, val);
+ free(val);
+ }
+ return count;
+}
+
+/** Make a new properties object from the given str
+ *
+ * \a object should be a whitespace separated list of key=value
+ * strings or a json object.
+ *
+ * \param object a property description
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *
+pw_properties_new_string(const char *object)
+{
+ struct properties *impl;
+ int res;
+
+ impl = properties_new(16);
+ if (impl == NULL)
+ return NULL;
+
+ if ((res = pw_properties_update_string(&impl->this, object, strlen(object))) < 0)
+ goto error;
+
+ return &impl->this;
+error:
+ pw_properties_free(&impl->this);
+ errno = -res;
+ return NULL;
+}
+
+/** Copy a properties object
+ *
+ * \param properties properties to copy
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_copy(const struct pw_properties *properties)
+{
+ return pw_properties_new_dict(&properties->dict);
+}
+
+/** Copy multiple keys from one property to another
+ *
+ * \param props properties to copy to
+ * \param dict properties to copy from
+ * \param keys a NULL terminated list of keys to copy
+ * \return the number of keys changed in \a dest
+ */
+SPA_EXPORT
+int pw_properties_update_keys(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const keys[])
+{
+ int i, changed = 0;
+ const char *str;
+
+ for (i = 0; keys[i]; i++) {
+ if ((str = spa_dict_lookup(dict, keys[i])) != NULL)
+ changed += pw_properties_set(props, keys[i], str);
+ }
+ return changed;
+}
+
+static bool has_key(const char * const keys[], const char *key)
+{
+ int i;
+ for (i = 0; keys[i]; i++) {
+ if (spa_streq(keys[i], key))
+ return true;
+ }
+ return false;
+}
+
+SPA_EXPORT
+int pw_properties_update_ignore(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const ignore[])
+{
+ const struct spa_dict_item *it;
+ int changed = 0;
+
+ spa_dict_for_each(it, dict) {
+ if (ignore == NULL || !has_key(ignore, it->key))
+ changed += pw_properties_set(props, it->key, it->value);
+ }
+ return changed;
+}
+
+/** Clear a properties object
+ *
+ * \param properties properties to clear
+ */
+SPA_EXPORT
+void pw_properties_clear(struct pw_properties *properties)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ struct spa_dict_item *item;
+
+ pw_array_for_each(item, &impl->items)
+ clear_item(item);
+ pw_array_reset(&impl->items);
+ properties->dict.n_items = 0;
+}
+
+/** Update properties
+ *
+ * \param props properties to update
+ * \param dict new properties
+ * \return the number of changed properties
+ *
+ * The properties in \a props are updated with \a dict. Keys in \a dict
+ * with NULL values are removed from \a props.
+ */
+SPA_EXPORT
+int pw_properties_update(struct pw_properties *props,
+ const struct spa_dict *dict)
+{
+ const struct spa_dict_item *it;
+ int changed = 0;
+
+ spa_dict_for_each(it, dict)
+ changed += pw_properties_set(props, it->key, it->value);
+
+ return changed;
+}
+
+/** Add properties
+ *
+ * \param props properties to add
+ * \param dict new properties
+ * \return the number of added properties
+ *
+ * The properties from \a dict that are not yet in \a props are added.
+ */
+SPA_EXPORT
+int pw_properties_add(struct pw_properties *props,
+ const struct spa_dict *dict)
+{
+ uint32_t i;
+ int added = 0;
+
+ for (i = 0; i < dict->n_items; i++) {
+ if (pw_properties_get(props, dict->items[i].key) == NULL)
+ added += pw_properties_set(props, dict->items[i].key, dict->items[i].value);
+ }
+ return added;
+}
+
+/** Add keys
+ *
+ * \param props properties to add
+ * \param dict new properties
+ * \param keys a NULL terminated list of keys to add
+ * \return the number of added properties
+ *
+ * The properties with \a keys from \a dict that are not yet
+ * in \a props are added.
+ */
+SPA_EXPORT
+int pw_properties_add_keys(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const keys[])
+{
+ uint32_t i;
+ int added = 0;
+ const char *str;
+
+ for (i = 0; keys[i]; i++) {
+ if ((str = spa_dict_lookup(dict, keys[i])) == NULL)
+ continue;
+ if (pw_properties_get(props, keys[i]) == NULL)
+ added += pw_properties_set(props, keys[i], str);
+ }
+ return added;
+}
+
+/** Free a properties object
+ *
+ * \param properties the properties to free
+ */
+SPA_EXPORT
+void pw_properties_free(struct pw_properties *properties)
+{
+ struct properties *impl;
+
+ if (properties == NULL)
+ return;
+
+ impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ pw_properties_clear(properties);
+ pw_array_clear(&impl->items);
+ free(impl);
+}
+
+static int do_replace(struct pw_properties *properties, const char *key, char *value, bool copy)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ int index;
+
+ if (key == NULL || key[0] == 0)
+ goto exit_noupdate;
+
+ index = find_index(properties, key);
+
+ if (index == -1) {
+ if (value == NULL)
+ return 0;
+ add_func(properties, strdup(key), copy ? strdup(value) : value);
+ SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED);
+ } else {
+ struct spa_dict_item *item =
+ pw_array_get_unchecked(&impl->items, index, struct spa_dict_item);
+
+ if (value && spa_streq(item->value, value))
+ goto exit_noupdate;
+
+ if (value == NULL) {
+ struct spa_dict_item *last = pw_array_get_unchecked(&impl->items,
+ pw_array_get_len(&impl->items, struct spa_dict_item) - 1,
+ struct spa_dict_item);
+ clear_item(item);
+ item->key = last->key;
+ item->value = last->value;
+ impl->items.size -= sizeof(struct spa_dict_item);
+ properties->dict.n_items--;
+ SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED);
+ } else {
+ free((char *) item->value);
+ item->value = copy ? strdup(value) : value;
+ }
+ }
+ return 1;
+exit_noupdate:
+ if (!copy)
+ free(value);
+ return 0;
+}
+
+/** Set a property value
+ *
+ * \param properties the properties to change
+ * \param key a key
+ * \param value a value or NULL to remove the key
+ * \return 1 if the properties were changed. 0 if nothing was changed because
+ * the property already existed with the same value or because the key to remove
+ * did not exist.
+ *
+ * Set the property in \a properties with \a key to \a value. Any previous value
+ * of \a key will be overwritten. When \a value is NULL, the key will be
+ * removed.
+ */
+SPA_EXPORT
+int pw_properties_set(struct pw_properties *properties, const char *key, const char *value)
+{
+ return do_replace(properties, key, (char*)value, true);
+}
+
+SPA_EXPORT
+int pw_properties_setva(struct pw_properties *properties,
+ const char *key, const char *format, va_list args)
+{
+ char *value = NULL;
+ if (format != NULL) {
+ if (vasprintf(&value, format, args) < 0)
+ return -errno;
+ }
+ return do_replace(properties, key, value, false);
+}
+
+/** Set a property value by format
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param format a value
+ * \param ... extra arguments
+ * \return 1 if the property was changed. 0 if nothing was changed because
+ * the property already existed with the same value or because the key to remove
+ * did not exist.
+ *
+ * Set the property in \a properties with \a key to the value in printf style \a format
+ * Any previous value of \a key will be overwritten.
+ */
+SPA_EXPORT
+int pw_properties_setf(struct pw_properties *properties, const char *key, const char *format, ...)
+{
+ int res;
+ va_list varargs;
+
+ va_start(varargs, format);
+ res = pw_properties_setva(properties, key, format, varargs);
+ va_end(varargs);
+
+ return res;
+}
+
+/** Get a property
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \return the property for \a key or NULL when the key was not found
+ *
+ * Get the property in \a properties with \a key.
+ */
+SPA_EXPORT
+const char *pw_properties_get(const struct pw_properties *properties, const char *key)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ int index = find_index(properties, key);
+
+ if (index == -1)
+ return NULL;
+
+ return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->value;
+}
+
+/** Fetch a property as uint32_t.
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_uint32(const struct pw_properties *properties, const char *key,
+ uint32_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atou32(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as int32_t
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_int32(const struct pw_properties *properties, const char *key,
+ int32_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atoi32(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as uint64_t.
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_uint64(const struct pw_properties *properties, const char *key,
+ uint64_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atou64(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as uint64", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as int64_t
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_int64(const struct pw_properties *properties, const char *key,
+ int64_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atoi64(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int64", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as boolean value
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_bool(const struct pw_properties *properties, const char *key,
+ bool *value)
+{
+ const char *str = pw_properties_get(properties, key);
+
+ if (!str)
+ return -ENOENT;
+
+ *value = spa_atob(str);
+ return 0;
+}
+
+/** Iterate property values
+ *
+ * \param properties a \ref pw_properties
+ * \param state state
+ * \return The next key or NULL when there are no more keys to iterate.
+ *
+ * Iterate over \a properties, returning each key in turn. \a state should point
+ * to a pointer holding NULL to get the first element and will be updated
+ * after each iteration. When NULL is returned, all elements have been
+ * iterated.
+ */
+SPA_EXPORT
+const char *pw_properties_iterate(const struct pw_properties *properties, void **state)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ uint32_t index;
+
+ if (*state == NULL)
+ index = 0;
+ else
+ index = SPA_PTR_TO_INT(*state);
+
+ if (!pw_array_check_index(&impl->items, index, struct spa_dict_item))
+ return NULL;
+
+ *state = SPA_INT_TO_PTR(index + 1);
+
+ return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->key;
+}
+
+static int encode_string(FILE *f, const char *val)
+{
+ int len = 0;
+ len += fprintf(f, "\"");
+ while (*val) {
+ switch (*val) {
+ case '\n':
+ len += fprintf(f, "\\n");
+ break;
+ case '\r':
+ len += fprintf(f, "\\r");
+ break;
+ case '\b':
+ len += fprintf(f, "\\b");
+ break;
+ case '\t':
+ len += fprintf(f, "\\t");
+ break;
+ case '\f':
+ len += fprintf(f, "\\f");
+ break;
+ case '\\':
+ case '"':
+ len += fprintf(f, "\\%c", *val);
+ break;
+ default:
+ if (*val > 0 && *val < 0x20)
+ len += fprintf(f, "\\u%04x", *val);
+ else
+ len += fprintf(f, "%c", *val);
+ break;
+ }
+ val++;
+ }
+ len += fprintf(f, "\"");
+ return len-1;
+}
+
+SPA_EXPORT
+int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags)
+{
+ const struct spa_dict_item *it;
+ int count = 0;
+ char key[1024];
+
+ spa_dict_for_each(it, dict) {
+ size_t len = it->value ? strlen(it->value) : 0;
+
+ if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1)
+ continue;
+
+ fprintf(f, "%s%s %s: ",
+ count == 0 ? "" : ",",
+ flags & PW_PROPERTIES_FLAG_NL ? "\n" : "",
+ key);
+
+ if (it->value == NULL) {
+ fprintf(f, "null");
+ } else if (spa_json_is_null(it->value, len) ||
+ spa_json_is_float(it->value, len) ||
+ spa_json_is_bool(it->value, len) ||
+ spa_json_is_container(it->value, len) ||
+ spa_json_is_string(it->value, len)) {
+ fprintf(f, "%s", it->value);
+ } else {
+ encode_string(f, it->value);
+ }
+ count++;
+ }
+ return count;
+}