state: add save_after_timeout() method to replace all custom timeout code

This was a common pattern that we had in many places, so it makes sense
to consolidate it.
This commit is contained in:
George Kiagiadakis
2024-01-04 16:38:33 +02:00
parent 7fa16292c3
commit eb2d6efcd4
10 changed files with 95 additions and 61 deletions

View File

@@ -15,6 +15,7 @@
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-state") WP_DEFINE_LOCAL_LOG_TOPIC ("wp-state")
#define DEFAULT_TIMEOUT_MS 1000
#define ESCAPED_CHARACTER '\\' #define ESCAPED_CHARACTER '\\'
static char * static char *
@@ -123,6 +124,7 @@ compress_string (const gchar *str)
enum { enum {
PROP_0, PROP_0,
PROP_NAME, PROP_NAME,
PROP_TIMEOUT,
}; };
struct _WpState struct _WpState
@@ -131,9 +133,11 @@ struct _WpState
/* Props */ /* Props */
gchar *name; gchar *name;
guint timeout;
gchar *location; gchar *location;
GKeyFile *keyfile; GSource *timeout_source;
WpProperties *timeout_props;
}; };
G_DEFINE_TYPE (WpState, wp_state, G_TYPE_OBJECT) G_DEFINE_TYPE (WpState, wp_state, G_TYPE_OBJECT)
@@ -186,6 +190,9 @@ wp_state_set_property (GObject * object, guint property_id,
g_clear_pointer (&self->name, g_free); g_clear_pointer (&self->name, g_free);
self->name = g_value_dup_string (value); self->name = g_value_dup_string (value);
break; break;
case PROP_TIMEOUT:
self->timeout = g_value_get_uint (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break; break;
@@ -202,6 +209,9 @@ wp_state_get_property (GObject * object, guint property_id, GValue * value,
case PROP_NAME: case PROP_NAME:
g_value_set_string (value, self->name); g_value_set_string (value, self->name);
break; break;
case PROP_TIMEOUT:
g_value_set_uint (value, self->timeout);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break; break;
@@ -215,6 +225,8 @@ wp_state_finalize (GObject * object)
g_clear_pointer (&self->name, g_free); g_clear_pointer (&self->name, g_free);
g_clear_pointer (&self->location, g_free); g_clear_pointer (&self->location, g_free);
g_clear_pointer (&self->timeout_source, g_source_unref);
g_clear_pointer (&self->timeout_props, wp_properties_unref);
G_OBJECT_CLASS (wp_state_parent_class)->finalize (object); G_OBJECT_CLASS (wp_state_parent_class)->finalize (object);
} }
@@ -222,6 +234,7 @@ wp_state_finalize (GObject * object)
static void static void
wp_state_init (WpState * self) wp_state_init (WpState * self)
{ {
self->timeout = DEFAULT_TIMEOUT_MS;
} }
static void static void
@@ -237,6 +250,11 @@ wp_state_class_init (WpStateClass * klass)
g_param_spec_string ("name", "name", g_param_spec_string ("name", "name",
"The file name where the state will be stored", NULL, "The file name where the state will be stored", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
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));
} }
/*! /*!
@@ -339,6 +357,57 @@ wp_state_save (WpState *self, WpProperties *props, GError ** error)
return TRUE; return TRUE;
} }
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)));
}
/*! /*!
* \brief Loads the state data from the file system * \brief Loads the state data from the file system
* *

View File

@@ -10,6 +10,7 @@
#define __WIREPLUMBER_STATE_H__ #define __WIREPLUMBER_STATE_H__
#include "properties.h" #include "properties.h"
#include "core.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@@ -38,6 +39,10 @@ void wp_state_clear (WpState *self);
WP_API WP_API
gboolean wp_state_save (WpState *self, WpProperties *props, GError ** error); gboolean wp_state_save (WpState *self, WpProperties *props, GError ** error);
WP_API
void wp_state_save_after_timeout (WpState *self, WpCore *core,
WpProperties *props);
WP_API WP_API
WpProperties * wp_state_load (WpState *self); WpProperties * wp_state_load (WpState *self);

View File

@@ -1522,6 +1522,16 @@ state_save (lua_State *L)
return 2; return 2;
} }
static int
state_save_after_timeout (lua_State *L)
{
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
luaL_checktype (L, 2, LUA_TTABLE);
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
wp_state_save_after_timeout (state, get_wp_core (L), props);
return 0;
}
static int static int
state_load (lua_State *L) state_load (lua_State *L)
{ {
@@ -1534,6 +1544,7 @@ state_load (lua_State *L)
static const luaL_Reg state_methods[] = { static const luaL_Reg state_methods[] = {
{ "clear", state_clear }, { "clear", state_clear },
{ "save" , state_save }, { "save" , state_save },
{ "save_after_timeout", state_save_after_timeout },
{ "load" , state_load }, { "load" , state_load },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@@ -34,7 +34,6 @@ struct _WpSettingsPlugin
WpImplMetadata *impl_metadata; WpImplMetadata *impl_metadata;
WpState *state; WpState *state;
WpProperties *settings; WpProperties *settings;
GSource *timeout_source;
}; };
enum { enum {
@@ -49,46 +48,18 @@ G_DEFINE_TYPE (WpSettingsPlugin, wp_settings_plugin, WP_TYPE_PLUGIN)
#define NAME "sm-settings" #define NAME "sm-settings"
#define PERSISTENT_SETTING "persistent.settings" #define PERSISTENT_SETTING "persistent.settings"
#define SAVE_INTERVAL_MS 1000
static void static void
wp_settings_plugin_init (WpSettingsPlugin * self) wp_settings_plugin_init (WpSettingsPlugin * self)
{ {
} }
static gboolean
timeout_save_state_callback (WpSettingsPlugin *self)
{
g_autoptr (GError) error = NULL;
if (!wp_state_save (self->state, self->settings, &error))
wp_warning_object (self, "%s", error->message);
return G_SOURCE_REMOVE;
}
static void
timeout_save_settings (WpSettingsPlugin *self, guint ms)
{
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, ms,
g_cclosure_new_object (G_CALLBACK (timeout_save_state_callback),
G_OBJECT (self)));
}
static void static void
on_metadata_changed (WpMetadata *m, guint32 subject, on_metadata_changed (WpMetadata *m, guint32 subject,
const gchar *setting, const gchar *type, const gchar *new_value, gpointer d) const gchar *setting, const gchar *type, const gchar *new_value, gpointer d)
{ {
WpSettingsPlugin *self = WP_SETTINGS_PLUGIN(d); WpSettingsPlugin *self = WP_SETTINGS_PLUGIN(d);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
const gchar *old_value = wp_properties_get (self->settings, setting); const gchar *old_value = wp_properties_get (self->settings, setting);
if (!old_value) { if (!old_value) {
@@ -102,7 +73,7 @@ on_metadata_changed (WpMetadata *m, guint32 subject,
wp_properties_set (self->settings, setting, new_value); wp_properties_set (self->settings, setting, new_value);
/* update the state */ /* update the state */
timeout_save_settings (self, SAVE_INTERVAL_MS); wp_state_save_after_timeout (self->state, core, self->settings);
} }
WpProperties * WpProperties *
@@ -220,7 +191,7 @@ on_metadata_activated (WpMetadata * m, GAsyncResult * res, gpointer user_data)
g_clear_pointer (&self->settings, wp_properties_unref); g_clear_pointer (&self->settings, wp_properties_unref);
self->settings = g_steal_pointer (&config_settings); self->settings = g_steal_pointer (&config_settings);
timeout_save_settings (self, SAVE_INTERVAL_MS); wp_state_save_after_timeout (self->state, core, self->settings);
} else { } else {
wp_info_object (self, PERSISTENT_SETTING wp_info_object (self, PERSISTENT_SETTING
" is enabled, current saved settings will be used"); " is enabled, current saved settings will be used");
@@ -277,10 +248,6 @@ wp_settings_plugin_disable (WpPlugin * plugin)
{ {
WpSettingsPlugin * self = WP_SETTINGS_PLUGIN (plugin); WpSettingsPlugin * self = WP_SETTINGS_PLUGIN (plugin);
if (self->timeout_source)
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
g_clear_object (&self->impl_metadata); g_clear_object (&self->impl_metadata);
g_clear_pointer (&self->settings, wp_properties_unref); g_clear_pointer (&self->settings, wp_properties_unref);
g_clear_object (&self->state); g_clear_object (&self->state);

View File

@@ -10,7 +10,6 @@
-- state file during the bootup, finally it has a hook which finds a default -- state file during the bootup, finally it has a hook which finds a default
-- node out of the user preferences -- node out of the user preferences
cutils = require ("common-utils")
settings = require ("settings-node") settings = require ("settings-node")
log = Log.open_topic ("s-default-nodes") log = Log.open_topic ("s-default-nodes")
@@ -175,7 +174,7 @@ function updateStored (def_node_type, stored)
index = index + 1 index = index + 1
until v == nil until v == nil
cutils.storeAfterTimeout (state, state_table) state:save_after_timeout (state_table)
end end
function toggleState (enable) function toggleState (enable)

View File

@@ -87,7 +87,7 @@ streams_om = ObjectManager {
local function saveHeadsetProfile (device, profile_name) local function saveHeadsetProfile (device, profile_name)
local key = "saved-headset-profile:" .. device.properties ["device.name"] local key = "saved-headset-profile:" .. device.properties ["device.name"]
headset_profiles [key] = profile_name headset_profiles [key] = profile_name
cutils.storeAfterTimeout (state, headset_profiles) state:save_after_timeout (headset_profiles)
end end
local function getSavedHeadsetProfile (device) local function getSavedHeadsetProfile (device)

View File

@@ -119,7 +119,7 @@ function updateStoredProfile (device, profile)
end end
state_table[dev_name] = profile.name state_table[dev_name] = profile.name
cutils.storeAfterTimeout (state, state_table) state:save_after_timeout (state_table)
log:info (device, string.format ( log:info (device, string.format (
"stored profile '%s' (%d) for device '%s'", "stored profile '%s' (%d) for device '%s'",

View File

@@ -242,7 +242,7 @@ function saveRouteProps (dev_info, route)
iec958Codecs = props.iec958Codecs and Json.Array (props.iec958Codecs), iec958Codecs = props.iec958Codecs and Json.Array (props.iec958Codecs),
}:to_string () }:to_string ()
cutils.storeAfterTimeout (state, state_table) state:save_after_timeout (state_table)
end end
function getStoredRouteProps (dev_info, route) function getStoredRouteProps (dev_info, route)
@@ -273,7 +273,7 @@ function saveProfileRoutes (dev_info, profile_name)
if #routes > 0 then if #routes > 0 then
local key = dev_info.name .. ":profile:" .. profile_name local key = dev_info.name .. ":profile:" .. profile_name
state_table [key] = Json.Array (routes):to_string() state_table [key] = Json.Array (routes):to_string()
cutils.storeAfterTimeout (state, state_table) state:save_after_timeout (state_table)
end end
end end

View File

@@ -108,23 +108,6 @@ function cutils.arrayContains (a, value)
return false return false
end end
state_save_sources = {}
function cutils.storeAfterTimeout (state, state_table)
local state_name = state["name"]
if state_save_sources [state_name] ~= nil then
state_save_sources [state_name]:destroy ()
state_save_sources [state_name] = nil
end
state_save_sources [state_name] = Core.timeout_add (1000, function ()
local saved, err = state:save (state_table)
if not saved then
Log.warning (err)
end
return false
end)
end
function cutils.get_application_name () function cutils.get_application_name ()
return Core.get_properties()["application.name"] or "WirePlumber" return Core.get_properties()["application.name"] or "WirePlumber"
end end

View File

@@ -387,7 +387,7 @@ function saveStreamProps (key, p)
p.channelVolumes = p.channelVolumes and Json.Array (p.channelVolumes) p.channelVolumes = p.channelVolumes and Json.Array (p.channelVolumes)
state_table [key] = Json.Object (p):to_string () state_table [key] = Json.Object (p):to_string ()
cutils.storeAfterTimeout (state, state_table) state:save_after_timeout (state_table)
end end
function formKey (properties) function formKey (properties)