diff --git a/docs/rst/library/c_api/state_api.rst b/docs/rst/library/c_api/state_api.rst index 36e4dd4d..41d7d0b0 100644 --- a/docs/rst/library/c_api/state_api.rst +++ b/docs/rst/library/c_api/state_api.rst @@ -8,9 +8,15 @@ State Storage digraph inheritance { rankdir=LR; GObject -> WpState; + GObject -> WpObject -> WpStateMetadata; } .. doxygenstruct:: WpState .. doxygengroup:: wpstate :content-only: + +.. doxygenstruct:: WpStateMetadata + +.. doxygengroup:: wpstatemetadata + :content-only: diff --git a/lib/wp/properties.c b/lib/wp/properties.c index c6a106f5..80716408 100644 --- a/lib/wp/properties.c +++ b/lib/wp/properties.c @@ -440,6 +440,22 @@ wp_properties_update_from_json (WpProperties * self, const WpSpaJson * json) wp_spa_json_get_size (json)); } +/*! + * \brief Clears all properties from \a self. + * + * \ingroup wpproperties + * \param self a properties object + */ +void +wp_properties_clear (WpProperties *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (!(self->flags & FLAG_IS_DICT)); + g_return_if_fail (!(self->flags & FLAG_NO_OWNERSHIP)); + + pw_properties_clear (self->props); +} + /*! * \brief Adds new properties in \a self, using the given \a props as a source. * diff --git a/lib/wp/properties.h b/lib/wp/properties.h index 92a2652f..1433b761 100644 --- a/lib/wp/properties.h +++ b/lib/wp/properties.h @@ -85,6 +85,9 @@ gint wp_properties_update_from_dict (WpProperties * self, WP_API gint wp_properties_update_from_json (WpProperties * self, const WpSpaJson * json); +WP_API +void wp_properties_clear (WpProperties *self); + /* add */ WP_API diff --git a/lib/wp/state.c b/lib/wp/state.c index 85f9d400..6e393e77 100644 --- a/lib/wp/state.c +++ b/lib/wp/state.c @@ -315,6 +315,39 @@ wp_state_clear (WpState *self) wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno)); } +static gboolean +wp_state_save_internal (const gchar *name, const gchar *location, + WpProperties *props, GError ** error) +{ + g_autoptr (GKeyFile) keyfile = g_key_file_new (); + g_autoptr (WpIterator) it = NULL; + g_auto (GValue) item = G_VALUE_INIT; + GError *err = NULL; + + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (location, FALSE); + g_return_val_if_fail (props, FALSE); + + /* Set the properties */ + for (it = wp_properties_new_iterator (props); + wp_iterator_next (it, &item); + g_value_unset (&item)) { + 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); + g_autofree gchar *escaped_key = escape_string (key); + if (escaped_key) + g_key_file_set_string (keyfile, name, escaped_key, val); + } + + if (!g_key_file_save_to_file (keyfile, location, &err)) { + g_propagate_prefixed_error (error, err, "could not save %s: ", name); + return FALSE; + } + + return TRUE; +} + /*! * \brief Saves new properties in the state, overwriting all previous data. * \ingroup wpstate @@ -326,35 +359,10 @@ wp_state_clear (WpState *self) gboolean wp_state_save (WpState *self, WpProperties *props, GError ** error) { - g_autoptr (GKeyFile) keyfile = g_key_file_new (); - g_autoptr (WpIterator) it = NULL; - g_auto (GValue) item = G_VALUE_INIT; - GError *err = NULL; - g_return_val_if_fail (WP_IS_STATE (self), FALSE); - g_return_val_if_fail (props, FALSE); + wp_state_ensure_location (self); - - wp_info_object (self, "saving state into %s", self->location); - - /* Set the properties */ - for (it = wp_properties_new_iterator (props); - wp_iterator_next (it, &item); - g_value_unset (&item)) { - 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); - g_autofree gchar *escaped_key = escape_string (key); - if (escaped_key) - g_key_file_set_string (keyfile, self->name, escaped_key, val); - } - - if (!g_key_file_save_to_file (keyfile, self->location, &err)) { - g_propagate_prefixed_error (error, err, "could not save %s: ", self->name); - return FALSE; - } - - return TRUE; + return wp_state_save_internal (self->name, self->location, props, error); } static gboolean @@ -408,6 +416,46 @@ wp_state_save_after_timeout (WpState *self, WpCore *core, WpProperties *props) G_OBJECT (self))); } +static WpProperties * +wp_state_load_internal (const gchar *name, const gchar *location) +{ + g_autoptr (GKeyFile) keyfile = NULL; + g_autoptr (WpProperties) props = NULL; + gchar ** keys = NULL; + + g_return_val_if_fail (name, NULL); + g_return_val_if_fail (location, NULL); + + props = wp_properties_new_empty (); + + /* Open */ + keyfile = g_key_file_new (); + if (!g_key_file_load_from_file (keyfile, location, + G_KEY_FILE_NONE, NULL)) + return g_steal_pointer (&props); + + /* Load all keys */ + keys = g_key_file_get_keys (keyfile, name, NULL, NULL); + if (!keys) + return g_steal_pointer (&props); + + for (guint i = 0; keys[i]; i++) { + g_autofree gchar *compressed_key = NULL; + const gchar *key = keys[i]; + g_autofree gchar *val = NULL; + val = g_key_file_get_string (keyfile, name, key, NULL); + if (!val) + continue; + compressed_key = compress_string (key); + if (compressed_key) + wp_properties_set (props, compressed_key, val); + } + + g_strfreev (keys); + + return g_steal_pointer (&props); +} + /*! * \brief Loads the state data from the file system * @@ -422,36 +470,437 @@ wp_state_save_after_timeout (WpState *self, WpCore *core, WpProperties *props) WpProperties * wp_state_load (WpState *self) { - g_autoptr (GKeyFile) keyfile = g_key_file_new (); - g_autoptr (WpProperties) props = wp_properties_new_empty (); - gchar ** keys = NULL; - g_return_val_if_fail (WP_IS_STATE (self), NULL); + wp_state_ensure_location (self); + return wp_state_load_internal (self->name, self->location); +} - /* Open */ - if (!g_key_file_load_from_file (keyfile, self->location, - G_KEY_FILE_NONE, NULL)) - return g_steal_pointer (&props); - /* Load all keys */ - keys = g_key_file_get_keys (keyfile, self->name, NULL, NULL); - if (!keys) - return g_steal_pointer (&props); +/* WpStateMetadata */ - for (guint i = 0; keys[i]; i++) { - g_autofree gchar *compressed_key = NULL; - const gchar *key = keys[i]; - g_autofree gchar *val = NULL; - val = g_key_file_get_string (keyfile, self->name, key, NULL); - if (!val) - continue; - compressed_key = compress_string (key); - if (compressed_key) - wp_properties_set (props, compressed_key, val); +/*! \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; } - g_strfreev (keys); + /* 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); + } - return g_steal_pointer (&props); + /* 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); } diff --git a/lib/wp/state.h b/lib/wp/state.h index 08caca5d..89956e5b 100644 --- a/lib/wp/state.h +++ b/lib/wp/state.h @@ -46,6 +46,46 @@ void wp_state_save_after_timeout (WpState *self, WpCore *core, WP_API WpProperties * wp_state_load (WpState *self); + +/* WpStateMetadata */ + +/*! + * \brief Flags to be used as WpObjectFeatures on WpStateMetadata. + * \ingroup wpstate + */ +typedef enum { /*< flags >*/ + /*! Loads the state metadata */ + WP_STATE_METADATA_LOADED = (1 << 0), +} WpStateMetadataFeatures; + +/*! + * \brief The WpStateMetadata GType + * \ingroup wpstatemetadata + */ +#define WP_TYPE_STATE_METADATA (wp_state_metadata_get_type ()) +WP_API +G_DECLARE_FINAL_TYPE (WpStateMetadata, wp_state_metadata, WP, STATE_METADATA, + WpObject) + +WP_API +WpStateMetadata * wp_state_metadata_new (WpCore *core, const gchar *name); + +WP_API +const gchar * wp_state_metadata_get_name (WpStateMetadata *self); + +WP_API +const gchar * wp_state_metadata_get_location (WpStateMetadata *self); + +WP_API +void wp_state_metadata_clear (WpStateMetadata *self); + +WP_API +const gchar * wp_state_metadata_get (WpStateMetadata *self, const gchar *key); + +WP_API +void wp_state_metadata_set (WpStateMetadata *self, const gchar *key, + const gchar *value); + G_END_DECLS #endif diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index 3b9dfdde..b15ac2cf 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -1697,6 +1697,55 @@ static const luaL_Reg state_methods[] = { { NULL, NULL } }; +/* WpStateMetadata */ + +static int +state_metadata_new (lua_State *L) +{ + const gchar *name = luaL_checkstring (L, 1); + WpStateMetadata *state_meta = wp_state_metadata_new (get_wp_core (L), name); + wplua_pushobject (L, state_meta); + return 1; +} + +static int +state_metadata_clear (lua_State *L) +{ + WpStateMetadata *state_meta = wplua_checkobject (L, 1, + WP_TYPE_STATE_METADATA); + wp_state_metadata_clear (state_meta); + return 0; +} + +static int +state_metadata_get (lua_State *L) +{ + WpStateMetadata *state_meta = wplua_checkobject (L, 1, + WP_TYPE_STATE_METADATA); + const gchar *key = luaL_checkstring (L, 2); + const gchar *v = wp_state_metadata_get (state_meta, key); + lua_pushstring (L, v); + return 1; +} + +static int +state_metadata_set (lua_State *L) +{ + WpStateMetadata *state_meta = wplua_checkobject (L, 1, + WP_TYPE_STATE_METADATA); + const gchar *key = luaL_checkstring (L, 2); + const gchar *value = luaL_opt (L, luaL_checkstring, 3, NULL); + wp_state_metadata_set (state_meta, key, value); + return 0; +} + +static const luaL_Reg state_metadata_methods[] = { + { "clear", state_metadata_clear }, + { "get", state_metadata_get }, + { "set", state_metadata_set }, + { NULL, NULL } +}; + /* ImplModule */ static int @@ -3262,6 +3311,8 @@ wp_lua_scripting_api_init (lua_State *L) NULL, pipewire_object_methods); wplua_register_type_methods (L, WP_TYPE_STATE, state_new, state_methods); + wplua_register_type_methods (L, WP_TYPE_STATE_METADATA, + state_metadata_new, state_metadata_methods); wplua_register_type_methods (L, WP_TYPE_IMPL_MODULE, impl_module_new, NULL); wplua_register_type_methods (L, WP_TYPE_EVENT, diff --git a/modules/module-lua-scripting/api/api.lua b/modules/module-lua-scripting/api/api.lua index fe9dcdcf..e75b9cff 100644 --- a/modules/module-lua-scripting/api/api.lua +++ b/modules/module-lua-scripting/api/api.lua @@ -234,6 +234,7 @@ SANDBOX_EXPORT = { Pod = WpSpaPod, Json = WpSpaJson, State = WpState_new, + StateMetadata = WpStateMetadata_new, LocalModule = WpImplModule_new, ImplMetadata = WpImplMetadata_new, Settings = WpSettings, diff --git a/src/scripts/device/state-profile.lua b/src/scripts/device/state-profile.lua index 375ad0bf..4e8bd7a7 100644 --- a/src/scripts/device/state-profile.lua +++ b/src/scripts/device/state-profile.lua @@ -14,9 +14,8 @@ cutils = require ("common-utils") log = Log.open_topic ("s-device") --- the state storage -state = nil -state_table = nil +-- the state meta storage +state_meta = nil find_stored_profile_hook = SimpleEventHook { name = "device/find-stored-profile", @@ -43,7 +42,7 @@ find_stored_profile_hook = SimpleEventHook { return end - local profile_name = state_table[dev_name] + local profile_name = state_meta:get (dev_name) if profile_name then for p in device:iterate_params ("EnumProfile") do @@ -100,7 +99,7 @@ function updateStoredProfile (device, profile) profile.name, profile.index, dev_name)) -- check if the new profile is the same as the current one - if state_table[dev_name] == profile.name then + if state_meta:get (dev_name) == profile.name then log:debug (device, " ... profile is already stored") return end @@ -121,25 +120,49 @@ function updateStoredProfile (device, profile) return end - state_table[dev_name] = profile.name - state:save_after_timeout (state_table) + reevaluate_on_state_changed_hook:remove () + state_meta:set (dev_name, profile.name) + reevaluate_on_state_changed_hook:register () log:info (device, string.format ( "stored profile '%s' (%d) for device '%s'", profile.name, index, dev_name)) end +reevaluate_on_state_changed_hook = SimpleEventHook { + name = "device/reevaluate-on-state-changed", + interests = { + EventInterest { + Constraint { "event.type", "=", "metadata-changed" }, + Constraint { "metadata.name", "=", "default-profile" }, + }, + }, + execute = function (event) + local source = event:get_source () + local device_om = source:call ("get-object-manager", "device") + for device in device_om:iterate () do + source:call ("push-event", "select-profile", device, nil) + end + end +} + function toggleState (enable) - if enable and not state then - state = State ("default-profile") - state_table = state:load () + if enable and not state_meta then + state_meta = StateMetadata ("default-profile") + state_meta:activate (Features.ALL, function (_, e) + if e then + log:warning ("failed to activate state metadata: " .. e) + end + end) find_stored_profile_hook:register () store_user_selected_profile_hook:register () + reevaluate_on_state_changed_hook:register () elseif not enable and state then - state = nil - state_table = nil + state_meta:deactivate (Features.ALL) + state_meta = nil find_stored_profile_hook:remove () store_user_selected_profile_hook:remove () + reevaluate_on_state_changed_hook:remove () end end