mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-07 18:08:04 +02:00
lib: add new WpState API to save and load data from files
This commit is contained in:
parent
763f0f1795
commit
af423398c4
6 changed files with 519 additions and 0 deletions
|
|
@ -27,6 +27,7 @@ wp_lib_sources = files(
|
|||
'si-interfaces.c',
|
||||
'spa-pod.c',
|
||||
'spa-type.c',
|
||||
'state.c',
|
||||
'transition.c',
|
||||
'wp.c',
|
||||
)
|
||||
|
|
@ -61,6 +62,7 @@ wp_lib_headers = files(
|
|||
'si-interfaces.h',
|
||||
'spa-pod.h',
|
||||
'spa-type.h',
|
||||
'state.h',
|
||||
'transition.h',
|
||||
'wp.h',
|
||||
)
|
||||
|
|
|
|||
320
lib/wp/state.c
Normal file
320
lib/wp/state.c
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION: WpState
|
||||
*
|
||||
* The #WpState class saves and loads properties from a file
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "wp-state"
|
||||
|
||||
#define WP_STATE_DIR_NAME "wireplumber"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "state.h"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NAME,
|
||||
};
|
||||
|
||||
struct _WpState
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
/* Props */
|
||||
gchar *name;
|
||||
|
||||
gchar *location;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (WpState, wp_state, G_TYPE_OBJECT)
|
||||
|
||||
static gboolean
|
||||
path_exists (const char *path)
|
||||
{
|
||||
struct stat info;
|
||||
return stat (path, &info) == 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_new_location (const char *name)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
/* Get the config path */
|
||||
path = g_build_filename (g_get_user_config_dir (), WP_STATE_DIR_NAME, NULL);
|
||||
g_return_val_if_fail (path, NULL);
|
||||
|
||||
/* Create the directory if it doesn't exist */
|
||||
if (!path_exists (path))
|
||||
g_mkdir_with_parents (path, 0700);
|
||||
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
|
||||
G_OBJECT_CLASS (wp_state_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_state_init (WpState * self)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* WpState:name:
|
||||
* The file name where the state will be stored.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_new:
|
||||
* @name: the state name
|
||||
*
|
||||
* Returns: (transfer full): the new #WpState
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_get_name:
|
||||
* @self: the state
|
||||
*
|
||||
* Returns: the name of this state
|
||||
*/
|
||||
const gchar *
|
||||
wp_state_get_name (WpState *self)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_STATE (self), NULL);
|
||||
|
||||
return self->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_get_location:
|
||||
* @self: the state
|
||||
*
|
||||
* Returns: the location of this state
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_clear:
|
||||
* @self: the state
|
||||
*
|
||||
* Clears the state removing its file
|
||||
*/
|
||||
void
|
||||
wp_state_clear (WpState *self)
|
||||
{
|
||||
g_return_if_fail (WP_IS_STATE (self));
|
||||
wp_state_ensure_location (self);
|
||||
|
||||
if (path_exists (self->location))
|
||||
remove (self->location);
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_save:
|
||||
* @self: the state
|
||||
* @props: (transfer none): the properties to save
|
||||
*
|
||||
* Saves new properties in the state, overwriting all previous data.
|
||||
*
|
||||
* Returns: TRUE if the properties could be saved, FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
wp_state_save (WpState *self, WpProperties *props)
|
||||
{
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
g_autofree gchar *tmp_name = NULL, *tmp_location = NULL;
|
||||
gulong tmp_size;
|
||||
int fd;
|
||||
FILE *f;
|
||||
|
||||
g_return_val_if_fail (WP_IS_STATE (self), FALSE);
|
||||
wp_state_ensure_location (self);
|
||||
|
||||
wp_info_object (self, "saving state into %s", self->location);
|
||||
|
||||
/* Get the temporary name and location */
|
||||
tmp_size = strlen (self->name) + 5;
|
||||
tmp_name = g_malloc (tmp_size);
|
||||
g_snprintf (tmp_name, tmp_size, "%s.tmp", self->name);
|
||||
tmp_location = get_new_location (tmp_name);
|
||||
g_return_val_if_fail (tmp_location, FALSE);
|
||||
|
||||
/* Open */
|
||||
fd = open (tmp_location, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0700);
|
||||
if (fd == -1) {
|
||||
wp_critical_object (self, "can't open %s", tmp_location);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Write */
|
||||
f = fdopen(fd, "w");
|
||||
for (it = wp_properties_iterate (props);
|
||||
wp_iterator_next (it, &item);
|
||||
g_value_unset (&item)) {
|
||||
const gchar *p = wp_properties_iterator_item_get_key (&item);
|
||||
while (*p) {
|
||||
if (*p == ' ' || *p == '\\')
|
||||
fputc('\\', f);
|
||||
fprintf(f, "%c", *p++);
|
||||
}
|
||||
fprintf(f, " %s\n", wp_properties_iterator_item_get_value (&item));
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
/* Rename temporary file */
|
||||
if (rename(tmp_location, self->location) < 0) {
|
||||
wp_critical_object("can't rename temporary file '%s' to '%s'", tmp_name,
|
||||
self->name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_state_load:
|
||||
* @self: the state
|
||||
*
|
||||
* Loads the state data into new properties.
|
||||
*
|
||||
* Returns (transfer full): the new properties with the state data
|
||||
*/
|
||||
WpProperties *
|
||||
wp_state_load (WpState *self)
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
int fd;
|
||||
FILE *f;
|
||||
char line[1024];
|
||||
|
||||
g_return_val_if_fail (WP_IS_STATE (self), NULL);
|
||||
wp_state_ensure_location (self);
|
||||
|
||||
/* Open */
|
||||
wp_info_object (self, "loading state from %s", self->location);
|
||||
fd = open (self->location, O_CLOEXEC | O_RDONLY);
|
||||
if (fd == -1) {
|
||||
/* We consider empty state if fill does not exist */
|
||||
if (errno == ENOENT)
|
||||
return g_steal_pointer (&props);
|
||||
wp_critical_object (self, "can't open %s", self->location);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read */
|
||||
f = fdopen(fd, "r");
|
||||
while (fgets (line, sizeof(line)-1, f)) {
|
||||
char *val, *key, *k, *p;
|
||||
val = strrchr(line, '\n');
|
||||
if (val)
|
||||
*val = '\0';
|
||||
|
||||
key = k = p = line;
|
||||
while (*p) {
|
||||
if (*p == ' ')
|
||||
break;
|
||||
if (*p == '\\')
|
||||
p++;
|
||||
*k++ = *p++;
|
||||
}
|
||||
*k = '\0';
|
||||
val = ++p;
|
||||
wp_properties_set (props, key, val);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
return g_steal_pointer (&props);
|
||||
}
|
||||
47
lib/wp/state.h
Normal file
47
lib/wp/state.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_STATE_H__
|
||||
#define __WIREPLUMBER_STATE_H__
|
||||
|
||||
#include "properties.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* WpState */
|
||||
|
||||
/**
|
||||
* WP_TYPE_STATE:
|
||||
*
|
||||
* The #WpState #GType
|
||||
*/
|
||||
#define WP_TYPE_STATE (wp_state_get_type ())
|
||||
WP_API
|
||||
G_DECLARE_FINAL_TYPE (WpState, wp_state, WP, STATE, GObject)
|
||||
|
||||
WP_API
|
||||
WpState * wp_state_new (const gchar *name);
|
||||
|
||||
WP_API
|
||||
const gchar * wp_state_get_name (WpState *self);
|
||||
|
||||
WP_API
|
||||
const gchar * wp_state_get_location (WpState *self);
|
||||
|
||||
WP_API
|
||||
void wp_state_clear (WpState *self);
|
||||
|
||||
WP_API
|
||||
gboolean wp_state_save (WpState *self, WpProperties *props);
|
||||
|
||||
WP_API
|
||||
WpProperties * wp_state_load (WpState *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "si-interfaces.h"
|
||||
#include "spa-pod.h"
|
||||
#include "spa-type.h"
|
||||
#include "state.h"
|
||||
#include "transition.h"
|
||||
#include "wpenums.h"
|
||||
#include "wpversion.h"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ common_deps = [gobject_dep, gio_dep, wp_dep, pipewire_dep]
|
|||
common_env = [
|
||||
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
|
||||
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
|
||||
'XDG_CONFIG_HOME=@0@'.format(meson.current_source_dir() / '.config'),
|
||||
'WIREPLUMBER_MODULE_DIR=@0@'.format(meson.current_build_dir() / '..' / '..' / 'modules'),
|
||||
'WIREPLUMBER_DEBUG=7',
|
||||
]
|
||||
|
|
@ -85,6 +86,13 @@ test(
|
|||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-state',
|
||||
executable('test-state', 'state.c',
|
||||
dependencies: common_deps, c_args: common_args),
|
||||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-transition',
|
||||
executable('test-transition', 'transition.c',
|
||||
|
|
|
|||
141
tests/wp/state.c
Normal file
141
tests/wp/state.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
|
||||
static void
|
||||
test_state_basic (void)
|
||||
{
|
||||
g_autoptr (WpState) state = wp_state_new ("basic");
|
||||
g_assert_nonnull (state);
|
||||
|
||||
g_assert_cmpstr (wp_state_get_name (state), ==, "basic");
|
||||
g_assert_true (g_str_has_suffix (wp_state_get_location (state), "basic"));
|
||||
|
||||
/* Save */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "key1", "value1");
|
||||
wp_properties_set (props, "key2", "value2");
|
||||
wp_properties_set (props, "key3", "value3");
|
||||
g_assert_true (wp_state_save (state, props));
|
||||
}
|
||||
|
||||
/* Load */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_cmpstr (wp_properties_get (props, "key1"), ==, "value1");
|
||||
g_assert_cmpstr (wp_properties_get (props, "key2"), ==, "value2");
|
||||
g_assert_cmpstr (wp_properties_get (props, "key2"), ==, "value2");
|
||||
g_assert_null (wp_properties_get (props, "invalid"));
|
||||
}
|
||||
|
||||
/* Re-Save */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "new-key", "new-value");
|
||||
g_assert_true (wp_state_save (state, props));
|
||||
}
|
||||
|
||||
/* Re-Load */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_cmpstr (wp_properties_get (props, "new-key"), ==, "new-value");
|
||||
g_assert_null (wp_properties_get (props, "key1"));
|
||||
g_assert_null (wp_properties_get (props, "key2"));
|
||||
g_assert_null (wp_properties_get (props, "key3"));
|
||||
}
|
||||
|
||||
wp_state_clear (state);
|
||||
|
||||
/* Load empty */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_null (wp_properties_get (props, "new-key"));
|
||||
g_assert_null (wp_properties_get (props, "key1"));
|
||||
g_assert_null (wp_properties_get (props, "key2"));
|
||||
g_assert_null (wp_properties_get (props, "key3"));
|
||||
}
|
||||
|
||||
wp_state_clear (state);
|
||||
}
|
||||
|
||||
static void
|
||||
test_state_empty (void)
|
||||
{
|
||||
g_autoptr (WpState) state = wp_state_new ("empty");
|
||||
g_assert_nonnull (state);
|
||||
|
||||
/* Save */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "key", "value");
|
||||
g_assert_true (wp_state_save (state, props));
|
||||
}
|
||||
|
||||
/* Load */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_cmpstr (wp_properties_get (props, "key"), ==, "value");
|
||||
}
|
||||
|
||||
/* Save empty */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
g_assert_true (wp_state_save (state, props));
|
||||
}
|
||||
|
||||
/* Load empty */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_null (wp_properties_get (props, "key"));
|
||||
}
|
||||
|
||||
wp_state_clear (state);
|
||||
}
|
||||
|
||||
static void
|
||||
test_state_spaces (void)
|
||||
{
|
||||
g_autoptr (WpState) state = wp_state_new ("spaces");
|
||||
g_assert_nonnull (state);
|
||||
|
||||
/* Save */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "key", "value with spaces");
|
||||
g_assert_true (wp_state_save (state, props));
|
||||
}
|
||||
|
||||
/* Load */
|
||||
{
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_cmpstr (wp_properties_get (props, "key"), ==, "value with spaces");
|
||||
}
|
||||
|
||||
wp_state_clear (state);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
g_log_set_writer_func (wp_log_writer_default, NULL, NULL);
|
||||
|
||||
g_test_add_func ("/wp/state/basic", test_state_basic);
|
||||
g_test_add_func ("/wp/state/empty", test_state_empty);
|
||||
g_test_add_func ("/wp/state/spaces", test_state_spaces);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue