diff --git a/lib/wp/state.c b/lib/wp/state.c index 4a8ef43e..85f9d400 100644 --- a/lib/wp/state.c +++ b/lib/wp/state.c @@ -15,6 +15,7 @@ WP_DEFINE_LOCAL_LOG_TOPIC ("wp-state") +#define DEFAULT_TIMEOUT_MS 1000 #define ESCAPED_CHARACTER '\\' static char * @@ -123,6 +124,7 @@ compress_string (const gchar *str) enum { PROP_0, PROP_NAME, + PROP_TIMEOUT, }; struct _WpState @@ -131,9 +133,11 @@ struct _WpState /* Props */ gchar *name; + guint timeout; gchar *location; - GKeyFile *keyfile; + GSource *timeout_source; + WpProperties *timeout_props; }; 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); self->name = g_value_dup_string (value); break; + case PROP_TIMEOUT: + self->timeout = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -202,6 +209,9 @@ wp_state_get_property (GObject * object, guint property_id, GValue * value, case PROP_NAME: g_value_set_string (value, self->name); break; + case PROP_TIMEOUT: + g_value_set_uint (value, self->timeout); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -215,6 +225,8 @@ wp_state_finalize (GObject * 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_clear_pointer (&self->timeout_props, wp_properties_unref); G_OBJECT_CLASS (wp_state_parent_class)->finalize (object); } @@ -222,6 +234,7 @@ wp_state_finalize (GObject * object) static void wp_state_init (WpState * self) { + self->timeout = DEFAULT_TIMEOUT_MS; } static void @@ -237,6 +250,11 @@ wp_state_class_init (WpStateClass * klass) 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)); + + 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; } +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 * diff --git a/lib/wp/state.h b/lib/wp/state.h index 1f8e126e..08caca5d 100644 --- a/lib/wp/state.h +++ b/lib/wp/state.h @@ -10,6 +10,7 @@ #define __WIREPLUMBER_STATE_H__ #include "properties.h" +#include "core.h" G_BEGIN_DECLS @@ -38,6 +39,10 @@ void wp_state_clear (WpState *self); WP_API 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 WpProperties * wp_state_load (WpState *self); diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index 2eca385b..10da39df 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -1522,6 +1522,16 @@ state_save (lua_State *L) 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 state_load (lua_State *L) { @@ -1534,6 +1544,7 @@ state_load (lua_State *L) static const luaL_Reg state_methods[] = { { "clear", state_clear }, { "save" , state_save }, + { "save_after_timeout", state_save_after_timeout }, { "load" , state_load }, { NULL, NULL } }; diff --git a/modules/module-settings.c b/modules/module-settings.c index 35858e06..6cbd6623 100644 --- a/modules/module-settings.c +++ b/modules/module-settings.c @@ -34,7 +34,6 @@ struct _WpSettingsPlugin WpImplMetadata *impl_metadata; WpState *state; WpProperties *settings; - GSource *timeout_source; }; enum { @@ -49,46 +48,18 @@ G_DEFINE_TYPE (WpSettingsPlugin, wp_settings_plugin, WP_TYPE_PLUGIN) #define NAME "sm-settings" #define PERSISTENT_SETTING "persistent.settings" -#define SAVE_INTERVAL_MS 1000 static void 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 on_metadata_changed (WpMetadata *m, guint32 subject, const gchar *setting, const gchar *type, const gchar *new_value, gpointer 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); if (!old_value) { @@ -102,7 +73,7 @@ on_metadata_changed (WpMetadata *m, guint32 subject, wp_properties_set (self->settings, setting, new_value); /* update the state */ - timeout_save_settings (self, SAVE_INTERVAL_MS); + wp_state_save_after_timeout (self->state, core, self->settings); } WpProperties * @@ -220,7 +191,7 @@ on_metadata_activated (WpMetadata * m, GAsyncResult * res, gpointer user_data) g_clear_pointer (&self->settings, wp_properties_unref); 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 { wp_info_object (self, PERSISTENT_SETTING " is enabled, current saved settings will be used"); @@ -277,10 +248,6 @@ wp_settings_plugin_disable (WpPlugin * 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_pointer (&self->settings, wp_properties_unref); g_clear_object (&self->state); diff --git a/src/scripts/default-nodes/state-default-nodes.lua b/src/scripts/default-nodes/state-default-nodes.lua index 5a45156f..35f74a6b 100644 --- a/src/scripts/default-nodes/state-default-nodes.lua +++ b/src/scripts/default-nodes/state-default-nodes.lua @@ -10,7 +10,6 @@ -- state file during the bootup, finally it has a hook which finds a default -- node out of the user preferences -cutils = require ("common-utils") settings = require ("settings-node") log = Log.open_topic ("s-default-nodes") @@ -175,7 +174,7 @@ function updateStored (def_node_type, stored) index = index + 1 until v == nil - cutils.storeAfterTimeout (state, state_table) + state:save_after_timeout (state_table) end function toggleState (enable) diff --git a/src/scripts/device/autoswitch-bluetooth-profile.lua b/src/scripts/device/autoswitch-bluetooth-profile.lua index b23db1d2..e9e54eae 100644 --- a/src/scripts/device/autoswitch-bluetooth-profile.lua +++ b/src/scripts/device/autoswitch-bluetooth-profile.lua @@ -87,7 +87,7 @@ streams_om = ObjectManager { local function saveHeadsetProfile (device, profile_name) local key = "saved-headset-profile:" .. device.properties ["device.name"] headset_profiles [key] = profile_name - cutils.storeAfterTimeout (state, headset_profiles) + state:save_after_timeout (headset_profiles) end local function getSavedHeadsetProfile (device) diff --git a/src/scripts/device/state-profile.lua b/src/scripts/device/state-profile.lua index b85a05b4..0da99465 100644 --- a/src/scripts/device/state-profile.lua +++ b/src/scripts/device/state-profile.lua @@ -119,7 +119,7 @@ function updateStoredProfile (device, profile) end state_table[dev_name] = profile.name - cutils.storeAfterTimeout (state, state_table) + state:save_after_timeout (state_table) log:info (device, string.format ( "stored profile '%s' (%d) for device '%s'", diff --git a/src/scripts/device/state-routes.lua b/src/scripts/device/state-routes.lua index eff60ca6..82fa45a5 100644 --- a/src/scripts/device/state-routes.lua +++ b/src/scripts/device/state-routes.lua @@ -242,7 +242,7 @@ function saveRouteProps (dev_info, route) iec958Codecs = props.iec958Codecs and Json.Array (props.iec958Codecs), }:to_string () - cutils.storeAfterTimeout (state, state_table) + state:save_after_timeout (state_table) end function getStoredRouteProps (dev_info, route) @@ -273,7 +273,7 @@ function saveProfileRoutes (dev_info, profile_name) if #routes > 0 then local key = dev_info.name .. ":profile:" .. profile_name state_table [key] = Json.Array (routes):to_string() - cutils.storeAfterTimeout (state, state_table) + state:save_after_timeout (state_table) end end diff --git a/src/scripts/lib/common-utils.lua b/src/scripts/lib/common-utils.lua index 04ee8504..a3ee1ab6 100644 --- a/src/scripts/lib/common-utils.lua +++ b/src/scripts/lib/common-utils.lua @@ -108,23 +108,6 @@ function cutils.arrayContains (a, value) return false 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 () return Core.get_properties()["application.name"] or "WirePlumber" end diff --git a/src/scripts/node/state-stream.lua b/src/scripts/node/state-stream.lua index 981902f5..c690f420 100644 --- a/src/scripts/node/state-stream.lua +++ b/src/scripts/node/state-stream.lua @@ -387,7 +387,7 @@ function saveStreamProps (key, p) p.channelVolumes = p.channelVolumes and Json.Array (p.channelVolumes) state_table [key] = Json.Object (p):to_string () - cutils.storeAfterTimeout (state, state_table) + state:save_after_timeout (state_table) end function formKey (properties)