2020-10-14 14:36:12 -04:00
|
|
|
/* WirePlumber
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2020 Collabora Ltd.
|
|
|
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2021-06-15 17:43:22 +03:00
|
|
|
#include <errno.h>
|
2020-10-14 14:36:12 -04:00
|
|
|
|
2021-05-06 15:45:13 +03:00
|
|
|
#include "log.h"
|
2020-10-14 14:36:12 -04:00
|
|
|
#include "state.h"
|
2021-07-13 15:31:02 +10:00
|
|
|
#include "wp.h"
|
2020-10-14 14:36:12 -04:00
|
|
|
|
log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.
Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.
Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.
Messages coming from GLib are checked inside the handler.
An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 11:51:29 +03:00
|
|
|
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-state")
|
|
|
|
|
|
2024-01-04 16:38:33 +02:00
|
|
|
#define DEFAULT_TIMEOUT_MS 1000
|
2021-06-29 10:13:47 -04:00
|
|
|
#define ESCAPED_CHARACTER '\\'
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
escape_string (const gchar *str)
|
|
|
|
|
{
|
|
|
|
|
char *res = NULL;
|
|
|
|
|
size_t str_size, i, j;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (str, NULL);
|
|
|
|
|
str_size = strlen (str);
|
|
|
|
|
g_return_val_if_fail (str_size > 0, NULL);
|
|
|
|
|
|
|
|
|
|
res = g_malloc_n ((str_size * 2) + 1, sizeof(gchar));
|
|
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
|
for (i = 0; i < str_size; i++) {
|
|
|
|
|
switch (str[i]) {
|
|
|
|
|
case ESCAPED_CHARACTER:
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
break;
|
|
|
|
|
case ' ':
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
res[j++] = 's';
|
|
|
|
|
break;
|
|
|
|
|
case '=':
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
res[j++] = 'e';
|
|
|
|
|
break;
|
|
|
|
|
case '[':
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
res[j++] = 'o';
|
|
|
|
|
break;
|
|
|
|
|
case ']':
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
res[j++] = 'c';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
res[j++] = str[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
res[j++] = '\0';
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
compress_string (const gchar *str)
|
|
|
|
|
{
|
|
|
|
|
char *res = NULL;
|
|
|
|
|
size_t str_size, i, j;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (str, NULL);
|
|
|
|
|
str_size = strlen (str);
|
|
|
|
|
g_return_val_if_fail (str_size > 0, NULL);
|
|
|
|
|
|
|
|
|
|
res = g_malloc_n (str_size + 1, sizeof(gchar));
|
|
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
|
for (i = 0; i < str_size - 1; i++) {
|
|
|
|
|
if (str[i] == ESCAPED_CHARACTER) {
|
|
|
|
|
switch (str[i + 1]) {
|
|
|
|
|
case ESCAPED_CHARACTER:
|
|
|
|
|
res[j++] = ESCAPED_CHARACTER;
|
|
|
|
|
break;
|
|
|
|
|
case 's':
|
|
|
|
|
res[j++] = ' ';
|
|
|
|
|
break;
|
|
|
|
|
case 'e':
|
|
|
|
|
res[j++] = '=';
|
|
|
|
|
break;
|
|
|
|
|
case 'o':
|
|
|
|
|
res[j++] = '[';
|
|
|
|
|
break;
|
|
|
|
|
case 'c':
|
|
|
|
|
res[j++] = ']';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
res[j++] = str[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
} else {
|
|
|
|
|
res[j++] = str[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (i < str_size)
|
|
|
|
|
res[j++] = str[i];
|
|
|
|
|
res[j++] = '\0';
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 18:40:43 +03:00
|
|
|
/*! \defgroup wpstate WpState */
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \struct WpState
|
2021-05-13 17:54:58 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* The WpState class saves and loads properties from a file
|
2021-05-13 17:54:58 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \gproperties
|
|
|
|
|
* \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
|
|
|
|
|
* The file name where the state will be stored.}
|
2021-05-13 17:54:58 +03:00
|
|
|
*/
|
2021-05-21 18:40:43 +03:00
|
|
|
|
2020-10-14 14:36:12 -04:00
|
|
|
enum {
|
|
|
|
|
PROP_0,
|
|
|
|
|
PROP_NAME,
|
2024-01-04 16:38:33 +02:00
|
|
|
PROP_TIMEOUT,
|
2020-10-14 14:36:12 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct _WpState
|
|
|
|
|
{
|
|
|
|
|
GObject parent;
|
|
|
|
|
|
|
|
|
|
/* Props */
|
|
|
|
|
gchar *name;
|
2024-01-04 16:38:33 +02:00
|
|
|
guint timeout;
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
gchar *location;
|
2024-01-04 16:38:33 +02:00
|
|
|
GSource *timeout_source;
|
|
|
|
|
WpProperties *timeout_props;
|
2020-10-14 14:36:12 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (WpState, wp_state, G_TYPE_OBJECT)
|
|
|
|
|
|
2021-08-12 10:49:41 +03:00
|
|
|
/* Gets the full path to the WirePlumber XDG_STATE_HOME subdirectory */
|
|
|
|
|
static const gchar *
|
|
|
|
|
wp_get_xdg_state_dir (void)
|
|
|
|
|
{
|
|
|
|
|
static gchar xdg_dir[PATH_MAX] = {0};
|
|
|
|
|
if (xdg_dir[0] == '\0') {
|
|
|
|
|
g_autofree gchar *path = NULL;
|
|
|
|
|
g_autofree gchar *base = g_strdup (g_getenv ("XDG_STATE_HOME"));
|
|
|
|
|
if (!base)
|
|
|
|
|
base = g_build_filename (g_get_home_dir (), ".local", "state", NULL);
|
|
|
|
|
|
|
|
|
|
path = g_build_filename (base, "wireplumber", NULL);
|
|
|
|
|
(void) g_strlcpy (xdg_dir, path, sizeof (xdg_dir));
|
|
|
|
|
}
|
|
|
|
|
return xdg_dir;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-14 14:36:12 -04:00
|
|
|
static char *
|
|
|
|
|
get_new_location (const char *name)
|
|
|
|
|
{
|
2021-07-13 15:31:02 +10:00
|
|
|
const gchar *path = wp_get_xdg_state_dir ();
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
/* Create the directory if it doesn't exist */
|
2021-06-15 17:43:22 +03:00
|
|
|
if (g_mkdir_with_parents (path, 0700) < 0)
|
|
|
|
|
wp_warning ("failed to create directory %s: %s", path, g_strerror (errno));
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
return g_build_filename (path, name, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_ensure_location (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
if (!self->location)
|
|
|
|
|
self->location = get_new_location (self->name);
|
|
|
|
|
g_return_if_fail (self->location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_set_property (GObject * object, guint property_id,
|
|
|
|
|
const GValue * value, GParamSpec * pspec)
|
|
|
|
|
{
|
|
|
|
|
WpState *self = WP_STATE (object);
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
case PROP_NAME:
|
|
|
|
|
g_clear_pointer (&self->name, g_free);
|
|
|
|
|
self->name = g_value_dup_string (value);
|
|
|
|
|
break;
|
2024-01-04 16:38:33 +02:00
|
|
|
case PROP_TIMEOUT:
|
|
|
|
|
self->timeout = g_value_get_uint (value);
|
|
|
|
|
break;
|
2020-10-14 14:36:12 -04:00
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_get_property (GObject * object, guint property_id, GValue * value,
|
|
|
|
|
GParamSpec * pspec)
|
|
|
|
|
{
|
|
|
|
|
WpState *self = WP_STATE (object);
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
case PROP_NAME:
|
|
|
|
|
g_value_set_string (value, self->name);
|
|
|
|
|
break;
|
2024-01-04 16:38:33 +02:00
|
|
|
case PROP_TIMEOUT:
|
|
|
|
|
g_value_set_uint (value, self->timeout);
|
|
|
|
|
break;
|
2020-10-14 14:36:12 -04:00
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_finalize (GObject * object)
|
|
|
|
|
{
|
|
|
|
|
WpState * self = WP_STATE (object);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&self->name, g_free);
|
|
|
|
|
g_clear_pointer (&self->location, g_free);
|
2024-01-04 16:38:33 +02:00
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
g_clear_pointer (&self->timeout_props, wp_properties_unref);
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (wp_state_parent_class)->finalize (object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_init (WpState * self)
|
|
|
|
|
{
|
2024-01-04 16:38:33 +02:00
|
|
|
self->timeout = DEFAULT_TIMEOUT_MS;
|
2020-10-14 14:36:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_class_init (WpStateClass * klass)
|
|
|
|
|
{
|
|
|
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
|
|
|
|
|
|
object_class->finalize = wp_state_finalize;
|
|
|
|
|
object_class->set_property = wp_state_set_property;
|
|
|
|
|
object_class->get_property = wp_state_get_property;
|
|
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_NAME,
|
|
|
|
|
g_param_spec_string ("name", "name",
|
|
|
|
|
"The file name where the state will be stored", NULL,
|
|
|
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
2024-01-04 16:38:33 +02:00
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_TIMEOUT,
|
|
|
|
|
g_param_spec_uint ("timeout", "timeout",
|
|
|
|
|
"The timeout in milliseconds to save the state", 0, G_MAXUINT,
|
|
|
|
|
DEFAULT_TIMEOUT_MS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2020-10-14 14:36:12 -04:00
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-06-01 19:39:13 -04:00
|
|
|
* \brief Constructs a new state object
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param name the state name
|
|
|
|
|
* \returns (transfer full): the new WpState
|
2020-10-14 14:36:12 -04:00
|
|
|
*/
|
|
|
|
|
WpState *
|
|
|
|
|
wp_state_new (const gchar *name)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (name, NULL);
|
|
|
|
|
return g_object_new (wp_state_get_type (),
|
|
|
|
|
"name", name,
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-06-01 19:39:13 -04:00
|
|
|
* \brief Gets the name of a state object
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
|
|
|
|
* \returns the name of this state
|
2020-10-14 14:36:12 -04:00
|
|
|
*/
|
|
|
|
|
const gchar *
|
|
|
|
|
wp_state_get_name (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE (self), NULL);
|
|
|
|
|
|
|
|
|
|
return self->name;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-06-01 19:39:13 -04:00
|
|
|
* \brief Gets the location of a state object
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
|
|
|
|
* \returns the location of this state
|
2020-10-14 14:36:12 -04:00
|
|
|
*/
|
|
|
|
|
const gchar *
|
|
|
|
|
wp_state_get_location (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE (self), NULL);
|
|
|
|
|
wp_state_ensure_location (self);
|
|
|
|
|
|
|
|
|
|
return self->location;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Clears the state removing its file
|
|
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
2020-10-14 14:36:12 -04:00
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
wp_state_clear (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (WP_IS_STATE (self));
|
|
|
|
|
wp_state_ensure_location (self);
|
2021-06-15 17:43:22 +03:00
|
|
|
if (remove (self->location) < 0)
|
|
|
|
|
wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno));
|
2020-10-14 14:36:12 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
static gboolean
|
|
|
|
|
wp_state_save_internal (const gchar *name, const gchar *location,
|
|
|
|
|
WpProperties *props, GError ** error)
|
2020-10-14 14:36:12 -04:00
|
|
|
{
|
2021-06-04 18:22:48 +03:00
|
|
|
g_autoptr (GKeyFile) keyfile = g_key_file_new ();
|
2020-10-14 14:36:12 -04:00
|
|
|
g_autoptr (WpIterator) it = NULL;
|
|
|
|
|
g_auto (GValue) item = G_VALUE_INIT;
|
2021-06-04 18:22:48 +03:00
|
|
|
GError *err = NULL;
|
2020-10-14 14:36:12 -04:00
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
g_return_val_if_fail (name, FALSE);
|
|
|
|
|
g_return_val_if_fail (location, FALSE);
|
2021-06-04 18:22:48 +03:00
|
|
|
g_return_val_if_fail (props, FALSE);
|
2020-10-14 14:36:12 -04:00
|
|
|
|
2021-01-08 13:15:42 -05:00
|
|
|
/* Set the properties */
|
2021-02-05 10:20:33 -05:00
|
|
|
for (it = wp_properties_new_iterator (props);
|
2020-10-14 14:36:12 -04:00
|
|
|
wp_iterator_next (it, &item);
|
|
|
|
|
g_value_unset (&item)) {
|
2021-07-14 11:28:23 -04:00
|
|
|
WpPropertiesItem *pi = g_value_get_boxed (&item);
|
|
|
|
|
const gchar *key = wp_properties_item_get_key (pi);
|
|
|
|
|
const gchar *val = wp_properties_item_get_value (pi);
|
2021-06-29 10:13:47 -04:00
|
|
|
g_autofree gchar *escaped_key = escape_string (key);
|
|
|
|
|
if (escaped_key)
|
2026-04-21 08:01:56 -04:00
|
|
|
g_key_file_set_string (keyfile, name, escaped_key, val);
|
2020-10-14 14:36:12 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
if (!g_key_file_save_to_file (keyfile, location, &err)) {
|
|
|
|
|
g_propagate_prefixed_error (error, err, "could not save %s: ", name);
|
2020-10-14 14:36:12 -04:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
/*!
|
|
|
|
|
* \brief Saves new properties in the state, overwriting all previous data.
|
|
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
|
|
|
|
* \param props (transfer none): the properties to save
|
|
|
|
|
* \param error (out)(optional): return location for a GError, or NULL
|
|
|
|
|
* \returns TRUE if the properties could be saved, FALSE otherwise
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
wp_state_save (WpState *self, WpProperties *props, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE (self), FALSE);
|
|
|
|
|
|
|
|
|
|
wp_state_ensure_location (self);
|
|
|
|
|
return wp_state_save_internal (self->name, self->location, props, error);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-04 16:38:33 +02:00
|
|
|
static gboolean
|
|
|
|
|
timeout_save_state_callback (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
|
|
|
|
|
if (!wp_state_save (self, self->timeout_props, &error))
|
|
|
|
|
wp_warning_object (self, "%s", error->message);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
g_clear_pointer (&self->timeout_props, wp_properties_unref);
|
|
|
|
|
|
|
|
|
|
return G_SOURCE_REMOVE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Saves new properties in the state, overwriting all previous data,
|
|
|
|
|
* after a timeout
|
|
|
|
|
*
|
|
|
|
|
* This is similar to wp_state_save() but it will save the state after a timeout
|
|
|
|
|
* has elapsed. If the state is saved again before the timeout elapses, the
|
|
|
|
|
* timeout is reset.
|
|
|
|
|
*
|
|
|
|
|
* This function is useful to avoid saving the state too often. When called
|
|
|
|
|
* consecutively, it will save the state only once. Every time it is called,
|
|
|
|
|
* it will cancel the previous timer and start a new one, resulting in timing
|
|
|
|
|
* out only after the last call.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
|
|
|
|
* \param core the core, used to add the timeout callback to the main loop
|
|
|
|
|
* \param props (transfer none): the properties to save. This object will be
|
|
|
|
|
* referenced and kept alive until the timeout elapses, but not deep copied.
|
|
|
|
|
* \since 0.5.0
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
wp_state_save_after_timeout (WpState *self, WpCore *core, WpProperties *props)
|
|
|
|
|
{
|
|
|
|
|
/* Clear the current timeout callback */
|
|
|
|
|
if (self->timeout_source)
|
|
|
|
|
g_source_destroy (self->timeout_source);
|
|
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
g_clear_pointer (&self->timeout_props, wp_properties_unref);
|
|
|
|
|
|
|
|
|
|
self->timeout_props = wp_properties_ref (props);
|
|
|
|
|
|
|
|
|
|
/* Add the timeout callback */
|
|
|
|
|
wp_core_timeout_add_closure (core, &self->timeout_source, self->timeout,
|
|
|
|
|
g_cclosure_new_object (G_CALLBACK (timeout_save_state_callback),
|
|
|
|
|
G_OBJECT (self)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
static WpProperties *
|
|
|
|
|
wp_state_load_internal (const gchar *name, const gchar *location)
|
2020-10-14 14:36:12 -04:00
|
|
|
{
|
2026-04-21 08:01:56 -04:00
|
|
|
g_autoptr (GKeyFile) keyfile = NULL;
|
|
|
|
|
g_autoptr (WpProperties) props = NULL;
|
2021-01-08 13:15:42 -05:00
|
|
|
gchar ** keys = NULL;
|
2020-10-14 14:36:12 -04:00
|
|
|
|
2026-04-21 08:01:56 -04:00
|
|
|
g_return_val_if_fail (name, NULL);
|
|
|
|
|
g_return_val_if_fail (location, NULL);
|
|
|
|
|
|
|
|
|
|
props = wp_properties_new_empty ();
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
/* Open */
|
2026-04-21 08:01:56 -04:00
|
|
|
keyfile = g_key_file_new ();
|
|
|
|
|
if (!g_key_file_load_from_file (keyfile, location,
|
2021-01-08 13:15:42 -05:00
|
|
|
G_KEY_FILE_NONE, NULL))
|
|
|
|
|
return g_steal_pointer (&props);
|
|
|
|
|
|
|
|
|
|
/* Load all keys */
|
2026-04-21 08:01:56 -04:00
|
|
|
keys = g_key_file_get_keys (keyfile, name, NULL, NULL);
|
2021-01-08 13:15:42 -05:00
|
|
|
if (!keys)
|
|
|
|
|
return g_steal_pointer (&props);
|
|
|
|
|
|
|
|
|
|
for (guint i = 0; keys[i]; i++) {
|
2021-06-29 10:13:47 -04:00
|
|
|
g_autofree gchar *compressed_key = NULL;
|
2021-01-08 13:15:42 -05:00
|
|
|
const gchar *key = keys[i];
|
|
|
|
|
g_autofree gchar *val = NULL;
|
2026-04-21 08:01:56 -04:00
|
|
|
val = g_key_file_get_string (keyfile, name, key, NULL);
|
2021-01-08 13:15:42 -05:00
|
|
|
if (!val)
|
|
|
|
|
continue;
|
2021-06-29 10:13:47 -04:00
|
|
|
compressed_key = compress_string (key);
|
|
|
|
|
if (compressed_key)
|
|
|
|
|
wp_properties_set (props, compressed_key, val);
|
2020-10-14 14:36:12 -04:00
|
|
|
}
|
2021-01-08 13:15:42 -05:00
|
|
|
|
|
|
|
|
g_strfreev (keys);
|
2020-10-14 14:36:12 -04:00
|
|
|
|
|
|
|
|
return g_steal_pointer (&props);
|
2021-05-21 18:40:43 +03:00
|
|
|
}
|
2026-04-21 08:01:56 -04:00
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Loads the state data from the file system
|
|
|
|
|
*
|
|
|
|
|
* This function will never fail. If it cannot load the state, for any reason,
|
|
|
|
|
* it will simply return an empty WpProperties, behaving as if there was no
|
|
|
|
|
* previous state stored.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpstate
|
|
|
|
|
* \param self the state
|
|
|
|
|
* \returns (transfer full): a new WpProperties containing the state data
|
|
|
|
|
*/
|
|
|
|
|
WpProperties *
|
|
|
|
|
wp_state_load (WpState *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE (self), NULL);
|
|
|
|
|
|
|
|
|
|
wp_state_ensure_location (self);
|
|
|
|
|
return wp_state_load_internal (self->name, self->location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* WpStateMetadata */
|
|
|
|
|
|
|
|
|
|
/*! \defgroup wpstatemetadata WpStateMetadata */
|
|
|
|
|
/*!
|
|
|
|
|
* \struct WpStateMetadata
|
|
|
|
|
*
|
|
|
|
|
* The WpStateMetadata class saves and loads properties from a file and reflects
|
|
|
|
|
* the state in a metadata object.
|
|
|
|
|
*
|
|
|
|
|
* \gproperties
|
|
|
|
|
* \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
|
|
|
|
|
* The file name where the state will be stored.}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
STATE_METADATA_PROP_0,
|
|
|
|
|
STATE_METADATA_PROP_NAME,
|
|
|
|
|
STATE_METADATA_PROP_TIMEOUT,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct _WpStateMetadata
|
|
|
|
|
{
|
|
|
|
|
WpObject parent;
|
|
|
|
|
|
|
|
|
|
/* Props */
|
|
|
|
|
gchar *name;
|
|
|
|
|
guint timeout;
|
|
|
|
|
|
|
|
|
|
gchar *location;
|
|
|
|
|
WpProperties *metadata_props;
|
|
|
|
|
WpImplMetadata *metadata;
|
|
|
|
|
GSource *timeout_source;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (WpStateMetadata, wp_state_metadata, WP_TYPE_OBJECT)
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_init (WpStateMetadata * self)
|
|
|
|
|
{
|
|
|
|
|
self->timeout = DEFAULT_TIMEOUT_MS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_set_property (GObject * object, guint property_id,
|
|
|
|
|
const GValue * value, GParamSpec * pspec)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata *self = WP_STATE_METADATA (object);
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
case STATE_METADATA_PROP_NAME:
|
|
|
|
|
g_clear_pointer (&self->name, g_free);
|
|
|
|
|
self->name = g_value_dup_string (value);
|
|
|
|
|
break;
|
|
|
|
|
case STATE_METADATA_PROP_TIMEOUT:
|
|
|
|
|
self->timeout = g_value_get_uint (value);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_get_property (GObject * object, guint property_id,
|
|
|
|
|
GValue * value, GParamSpec * pspec)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata *self = WP_STATE_METADATA (object);
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
case STATE_METADATA_PROP_NAME:
|
|
|
|
|
g_value_set_string (value, self->name);
|
|
|
|
|
break;
|
|
|
|
|
case STATE_METADATA_PROP_TIMEOUT:
|
|
|
|
|
g_value_set_uint (value, self->timeout);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
STEP_LOAD = WP_TRANSITION_STEP_CUSTOM_START,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static WpObjectFeatures
|
|
|
|
|
wp_state_metadata_get_supported_features (WpObject * self)
|
|
|
|
|
{
|
|
|
|
|
return WP_STATE_METADATA_LOADED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static guint
|
|
|
|
|
wp_state_metadata_activate_get_next_step (WpObject * object,
|
|
|
|
|
WpFeatureActivationTransition * transition, guint step,
|
|
|
|
|
WpObjectFeatures missing)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (missing == WP_STATE_METADATA_LOADED,
|
|
|
|
|
WP_TRANSITION_STEP_ERROR);
|
|
|
|
|
|
|
|
|
|
return STEP_LOAD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
state_metadata_ensure_location (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
if (!self->location)
|
|
|
|
|
self->location = get_new_location (self->name);
|
|
|
|
|
g_return_if_fail (self->location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static WpProperties *
|
|
|
|
|
state_metadata_load (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
state_metadata_ensure_location (self);
|
|
|
|
|
return wp_state_load_internal (self->name, self->location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
state_metadata_save (WpStateMetadata *self, WpProperties *props,
|
|
|
|
|
GError ** error)
|
|
|
|
|
{
|
|
|
|
|
state_metadata_ensure_location (self);
|
|
|
|
|
return wp_state_save_internal (self->name, self->location, props, error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
state_metadata_timeout_save_cb (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
|
|
|
|
|
if (!state_metadata_save (self, self->metadata_props, &error))
|
|
|
|
|
wp_warning_object (self, "%s", error->message);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
|
|
|
|
|
wp_info_object (self, "saved changes on state metadata '%s'", self->name);
|
|
|
|
|
return G_SOURCE_REMOVE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
state_metadata_save_after_timeout (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
|
|
|
g_return_if_fail (core);
|
|
|
|
|
|
|
|
|
|
/* Clear the current timeout callback */
|
|
|
|
|
if (self->timeout_source)
|
|
|
|
|
g_source_destroy (self->timeout_source);
|
|
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
|
|
|
|
|
/* Add the timeout callback */
|
|
|
|
|
wp_core_timeout_add_closure (core, &self->timeout_source, self->timeout,
|
|
|
|
|
g_cclosure_new_object (G_CALLBACK (state_metadata_timeout_save_cb),
|
|
|
|
|
G_OBJECT (self)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
state_metadata_clear (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
if (self->metadata_props)
|
|
|
|
|
wp_properties_clear (self->metadata_props);
|
|
|
|
|
|
|
|
|
|
state_metadata_ensure_location (self);
|
|
|
|
|
if (remove (self->location) < 0)
|
|
|
|
|
wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_state_metadata_changed (WpMetadata *m, guint32 subject,
|
|
|
|
|
const gchar *key, const gchar *type, const gchar *value, gpointer d)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata * self = WP_STATE_METADATA (d);
|
|
|
|
|
|
|
|
|
|
/* Save new key after timeout if valid. Otherwise just clear the state */
|
|
|
|
|
if (key) {
|
|
|
|
|
wp_properties_set (self->metadata_props, key, value);
|
|
|
|
|
if (value)
|
|
|
|
|
wp_info_object (self, "key changed on state metadata '%s': %s = %s",
|
|
|
|
|
self->name, key, value);
|
|
|
|
|
else
|
|
|
|
|
wp_info_object (self, "key removed on state metadata '%s': %s",
|
|
|
|
|
self->name, key);
|
|
|
|
|
state_metadata_save_after_timeout (self);
|
|
|
|
|
} else {
|
|
|
|
|
state_metadata_clear (self);
|
|
|
|
|
wp_info_object (self, "cleared state metadata '%s'", self->name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_metadata_activated (WpObject * proxy, GAsyncResult * res,
|
|
|
|
|
WpTransition * transition)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata *self = wp_transition_get_source_object (transition);
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
g_autoptr (WpIterator) it = NULL;
|
|
|
|
|
GValue v = G_VALUE_INIT;
|
|
|
|
|
|
|
|
|
|
/* Make sure there were no errors when activating the metadata */
|
|
|
|
|
if (!wp_object_activate_finish (proxy, res, &error)) {
|
|
|
|
|
wp_transition_return_error (transition, g_error_new (
|
|
|
|
|
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"Failed to activate metadata for state %s: %s", self->name,
|
|
|
|
|
error->message));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Load the state keys into the metadata */
|
|
|
|
|
g_return_if_fail (self->metadata_props == NULL);
|
|
|
|
|
self->metadata_props = state_metadata_load (self);
|
|
|
|
|
it = wp_properties_new_iterator (self->metadata_props);
|
|
|
|
|
while (wp_iterator_next (it, &v)) {
|
|
|
|
|
WpPropertiesItem *pi = g_value_get_boxed (&v);
|
|
|
|
|
const gchar *key = wp_properties_item_get_key (pi);
|
|
|
|
|
const gchar *value = wp_properties_item_get_value (pi);
|
|
|
|
|
wp_metadata_set (WP_METADATA (self->metadata), 0, key, NULL, value);
|
|
|
|
|
g_value_unset (&v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle metadata changes */
|
|
|
|
|
g_signal_connect_object (self->metadata, "changed",
|
|
|
|
|
G_CALLBACK (on_state_metadata_changed), self, 0);
|
|
|
|
|
|
|
|
|
|
wp_object_update_features (WP_OBJECT (self), WP_STATE_METADATA_LOADED, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_activate_execute_step (WpObject * object,
|
|
|
|
|
WpFeatureActivationTransition * transition, guint step,
|
|
|
|
|
WpObjectFeatures missing)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata * self = WP_STATE_METADATA (object);
|
|
|
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
|
|
|
|
|
|
|
|
switch (step) {
|
|
|
|
|
case STEP_LOAD: {
|
|
|
|
|
g_return_if_fail (self->metadata == NULL);
|
|
|
|
|
self->metadata = wp_impl_metadata_new_full (core, self->name,
|
|
|
|
|
wp_properties_new ("wireplumber.state", "true", NULL));
|
|
|
|
|
wp_object_activate_closure (WP_OBJECT (self->metadata),
|
|
|
|
|
WP_OBJECT_FEATURES_ALL, NULL, g_cclosure_new_object (
|
|
|
|
|
(GCallback) on_metadata_activated, G_OBJECT (transition)));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case WP_TRANSITION_STEP_ERROR:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_deactivate (WpObject * object, WpObjectFeatures features)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata *self = WP_STATE_METADATA (object);
|
|
|
|
|
|
|
|
|
|
if (self->metadata)
|
|
|
|
|
g_signal_handlers_disconnect_by_data (self->metadata, self);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&self->metadata_props, wp_properties_unref);
|
|
|
|
|
g_clear_object (&self->metadata);
|
|
|
|
|
|
|
|
|
|
wp_object_update_features (WP_OBJECT (self), 0, WP_OBJECT_FEATURES_ALL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_finalize (GObject * object)
|
|
|
|
|
{
|
|
|
|
|
WpStateMetadata * self = WP_STATE_METADATA (object);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&self->name, g_free);
|
|
|
|
|
g_clear_pointer (&self->location, g_free);
|
|
|
|
|
g_clear_pointer (&self->timeout_source, g_source_unref);
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (wp_state_metadata_parent_class)->finalize (object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wp_state_metadata_class_init (WpStateMetadataClass * klass)
|
|
|
|
|
{
|
|
|
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
|
WpObjectClass * wpobject_class = (WpObjectClass *) klass;
|
|
|
|
|
|
|
|
|
|
object_class->finalize = wp_state_metadata_finalize;
|
|
|
|
|
object_class->set_property = wp_state_metadata_set_property;
|
|
|
|
|
object_class->get_property = wp_state_metadata_get_property;
|
|
|
|
|
|
|
|
|
|
wpobject_class->get_supported_features =
|
|
|
|
|
wp_state_metadata_get_supported_features;
|
|
|
|
|
wpobject_class->activate_get_next_step =
|
|
|
|
|
wp_state_metadata_activate_get_next_step;
|
|
|
|
|
wpobject_class->activate_execute_step =
|
|
|
|
|
wp_state_metadata_activate_execute_step;
|
|
|
|
|
wpobject_class->deactivate = wp_state_metadata_deactivate;
|
|
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, STATE_METADATA_PROP_NAME,
|
|
|
|
|
g_param_spec_string ("name", "name",
|
|
|
|
|
"The file name where the state metadata will be stored", NULL,
|
|
|
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, STATE_METADATA_PROP_TIMEOUT,
|
|
|
|
|
g_param_spec_uint ("timeout", "timeout",
|
|
|
|
|
"The timeout in milliseconds to save the state metadata", 0,
|
|
|
|
|
G_MAXUINT, DEFAULT_TIMEOUT_MS,
|
|
|
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Constructs a new state metadata object
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param core the core associated with the state metadata
|
|
|
|
|
* \param name the state metadata name
|
|
|
|
|
* \returns (transfer full): the new WpStateMetadata
|
|
|
|
|
*/
|
|
|
|
|
WpStateMetadata *
|
|
|
|
|
wp_state_metadata_new (WpCore *core, const gchar *name)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (core, NULL);
|
|
|
|
|
g_return_val_if_fail (name, NULL);
|
|
|
|
|
return g_object_new (wp_state_metadata_get_type (),
|
|
|
|
|
"core", core,
|
|
|
|
|
"name", name,
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Gets the name of a state metadata object
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param self the state metadata
|
|
|
|
|
* \returns the name of this state metadata
|
|
|
|
|
*/
|
|
|
|
|
const gchar *
|
|
|
|
|
wp_state_metadata_get_name (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
|
|
|
|
|
|
|
|
|
|
return self->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Gets the location of a state metadata object
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param self the state metadata
|
|
|
|
|
* \returns the location of this state metadata
|
|
|
|
|
*/
|
|
|
|
|
const gchar *
|
|
|
|
|
wp_state_metadata_get_location (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
|
|
|
|
|
|
|
|
|
|
state_metadata_ensure_location (self);
|
|
|
|
|
|
|
|
|
|
return self->location;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Clears the state metadata and removes its file
|
|
|
|
|
*
|
|
|
|
|
* If the state metadata has not been loaded, this won't do anything.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param self the state metadata
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
wp_state_metadata_clear (WpStateMetadata *self)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (WP_IS_STATE_METADATA (self));
|
|
|
|
|
|
|
|
|
|
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
|
|
|
|
|
WP_STATE_METADATA_LOADED))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (self->metadata);
|
|
|
|
|
wp_metadata_clear (WP_METADATA (self->metadata));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Gets a value from the state metadata
|
|
|
|
|
*
|
|
|
|
|
* If the state metadata has not been loaded, this won't do anything.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param self the state metadata
|
|
|
|
|
* \param key the key of the value
|
|
|
|
|
* \returns the value from the state metadata, or NULL if not found.
|
|
|
|
|
*/
|
|
|
|
|
const gchar *
|
|
|
|
|
wp_state_metadata_get (WpStateMetadata *self, const gchar *key)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
|
|
|
|
|
g_return_val_if_fail (key, NULL);
|
|
|
|
|
|
|
|
|
|
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
|
|
|
|
|
WP_STATE_METADATA_LOADED))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (self->metadata, NULL);
|
|
|
|
|
return wp_metadata_find (WP_METADATA (self->metadata), 0, key, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Sets a value into the state metadata
|
|
|
|
|
*
|
|
|
|
|
* If value is NULL, it will unset the given \a key. Note that this will also
|
|
|
|
|
* save the state after the timeout has elapsed.
|
|
|
|
|
*
|
|
|
|
|
* If the state metadata has not been loaded, this won't do anything.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpstatemetadata
|
|
|
|
|
* \param self the metadata object
|
|
|
|
|
* \param key: the key to set.
|
|
|
|
|
* \param value (nullable): the value to set, or NULL to unset the given \a key
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
wp_state_metadata_set (WpStateMetadata *self, const gchar *key,
|
|
|
|
|
const gchar *value)
|
|
|
|
|
{
|
|
|
|
|
g_return_if_fail (WP_IS_STATE_METADATA (self));
|
|
|
|
|
g_return_if_fail (key);
|
|
|
|
|
|
|
|
|
|
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
|
|
|
|
|
WP_STATE_METADATA_LOADED))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (self->metadata);
|
|
|
|
|
wp_metadata_set (WP_METADATA (self->metadata), 0, key, NULL, value);
|
|
|
|
|
}
|