summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--lilv.pc.in2
-rw-r--r--lilv/lilv.h166
-rw-r--r--src/lilv_internal.h11
-rw-r--r--src/plugin.c20
-rw-r--r--src/state.c794
-rw-r--r--src/world.c20
-rw-r--r--src/zix/tree.c2
-rw-r--r--wscript38
9 files changed, 1026 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog
index e582776..c7265d7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,10 @@ lilv (UNRELEASED) unstable; urgency=low
* Print presets in lv2info
* Remove locale smashing kludges and use new serd functions for converting
nodes to/from numbers.
+ * Add LilvState API for handling plugin state. This makes it simple to
+ save and restore plugin state both in memory and on disk, as well as
+ save presets in a host-sharable way since the disk format is identical
+ to the LV2 presets format.
-- David Robillard <d@drobilla.net> (UNRELEASED)
diff --git a/lilv.pc.in b/lilv.pc.in
index f7ffb35..3a171ec 100644
--- a/lilv.pc.in
+++ b/lilv.pc.in
@@ -6,6 +6,6 @@ includedir=@INCLUDEDIR@
Name: Lilv
Version: @LILV_VERSION@
Description: Simple C library for hosting LV2 plugins
-Requires: lv2core
+Requires: lv2core lv2-lv2plug.in-ns-ext-state
Libs: -L${libdir} -l@LIB_LILV@ -ldl
Cflags: -I${includedir}/lilv-@LILV_MAJOR_VERSION@
diff --git a/lilv/lilv.h b/lilv/lilv.h
index ca3809d..7d04cda 100644
--- a/lilv/lilv.h
+++ b/lilv/lilv.h
@@ -27,6 +27,7 @@
#include <stdio.h>
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#ifdef LILV_SHARED
# ifdef __WIN32__
@@ -74,6 +75,7 @@ typedef struct LilvUIImpl LilvUI; /**< Plugin UI. */
typedef struct LilvNodeImpl LilvNode; /**< Typed Value. */
typedef struct LilvWorldImpl LilvWorld; /**< Lilv World. */
typedef struct LilvInstanceImpl LilvInstance; /**< Plugin instance. */
+typedef struct LilvStateImpl LilvState; /**< Plugin state. */
typedef void LilvIter; /**< Collection iterator */
typedef void LilvPluginClasses; /**< set<PluginClass>. */
@@ -1093,6 +1095,170 @@ lilv_port_get_scale_points(const LilvPlugin* plugin,
/**
@}
+ @name Plugin State
+ @{
+*/
+
+/**
+ Free @c state.
+*/
+LILV_API
+void
+lilv_state_free(LilvState* state);
+
+/**
+ Get the URI of the plugin @c state applies to.
+*/
+LILV_API
+const LilvNode*
+lilv_state_get_plugin_uri(const LilvState* state);
+
+/**
+ Get the label of @c state.
+*/
+LILV_API
+const char*
+lilv_state_get_label(const LilvState* state);
+
+/**
+ Set the label of @c state.
+*/
+LILV_API
+void
+lilv_state_set_label(LilvState* state,
+ const char* label);
+
+/**
+ Load a state snapshot from @c world's RDF model.
+ @param subject The subject of the state description (e.g. a preset URI).
+ @return A new LilvState which must be freed with lilv_state_free().
+*/
+LILV_API
+LilvState*
+lilv_state_new_from_world(LilvWorld* world,
+ LV2_URID_Map* map,
+ const LilvNode* subject);
+
+/**
+ Load a state snapshot from a file.
+ @param subject The subject of the state description (e.g. a preset URI).
+ @param path The path of the file containing the state description.
+ @return A new LilvState which must be freed with lilv_state_free().
+
+ If @c subject is NULL, it is taken to be the URI of the file (i.e.
+ "<>" in Turtle).
+
+ This function parses the file separately to create the state, it does not
+ parse the file into the world model, i.e. the returned state is the only
+ new memory consumed once this function returns.
+*/
+LILV_API
+LilvState*
+lilv_state_new_from_file(LilvWorld* world,
+ LV2_URID_Map* map,
+ const LilvNode* subject,
+ const char* path);
+
+/**
+ Save state to a file.
+ @param unmap URID unmapper.
+ @param state State to save.
+ @param uri URI of state, may be NULL.
+ @param path Path of file to save state to, may be NULL.
+ @param manifest_path Path of manifest file to add entry to, may be NULL.
+
+ The format of state on disk is compatible with that defined in the LV2
+ preset extension, i.e. this function may be used to save presets which can
+ be loaded by any host. If @c path is NULL, then the default user preset
+ bundle (~/.lv2/presets.lv2) is used. In this case, the label of @c state
+ MUST be set since it is used to generate a filename.
+
+ If @c uri is NULL, the state will be saved without an absolute URI (but
+ the bundle will still work correctly as a preset bundle).
+*/
+LILV_API
+int
+lilv_state_save(LilvWorld* world,
+ LV2_URID_Unmap* unmap,
+ const LilvState* state,
+ const char* uri,
+ const char* path,
+ const char* manifest_path);
+
+/**
+ Function to get a port value.
+ @return A node the caller (lilv) takes ownership of and must free.
+*/
+typedef LilvNode* (*LilvGetPortValueFunc)(const char* port_symbol,
+ void* user_data);
+
+/**
+ Create a new state snapshot from a plugin instance.
+ @param plugin The plugin this state applies to.
+ @param instance An instance of @c plugin.
+ @param flags Bitwise OR of LV2_State_Flags values.
+ @param features Features to pass LV2_State_Interface.save().
+ @return A new LilvState which must be freed with lilv_state_free().
+
+ The returned state will have all properties set from the plugin, but no port
+ values set (since it is impossible for Lilv to do this in general). To get
+ a complete snapshot of plugin state, call this function, then set the
+ appropriate port values with lilv_state_set_port_value.
+
+ This function may be called simultaneously with any instance function
+ (except discovery functions) unless the threading class of that function
+ explicitly disallows this.
+
+ See <a href="http://lv2plug.in/ns/ext/state/state.h">state.h</a> from the
+ LV2 State extension for details on the @c flags and @c features parameters.
+*/
+LILV_API
+LilvState*
+lilv_state_new_from_instance(const LilvPlugin* plugin,
+ LilvInstance* instance,
+ LilvGetPortValueFunc get_value,
+ void* user_data,
+ uint32_t flags,
+ const LV2_Feature *const * features);
+
+/**
+ Function to set a port value.
+*/
+typedef void (*LilvSetPortValueFunc)(const char* port_symbol,
+ const LilvNode* value,
+ void* user_data);
+
+/**
+ Restore a plugin instance from a state snapshot.
+ @param state The state to restore, which must apply to the correct plugin.
+ @param instance An instance of the plugin @c state applies to.
+ @param set_value A function to set a port value (may be NULL).
+ @param flags Bitwise OR of LV2_State_Flags values.
+ @param features Features to pass LV2_State_Interface.restore().
+
+ This will set all the properties of @c instance to the values stored in @c
+ state. If @c set_value is provided, it will be called (with the given @c
+ user_data) to restore each port value, otherwise the host must restore the
+ port values itself (using lilv_state_get_port_value) in order to completely
+ restore @c state.
+
+ This function is in the "instantiation" threading class, i.e. it MUST NOT be
+ called simultaneously with any function on the same plugin instance.
+
+ See <a href="http://lv2plug.in/ns/ext/state/state.h">state.h</a> from the
+ LV2 State extension for details on the @c flags and @c features parameters.
+*/
+LILV_API
+void
+lilv_state_restore(const LilvState* state,
+ LilvInstance* instance,
+ LilvSetPortValueFunc set_value,
+ void* user_data,
+ uint32_t flags,
+ const LV2_Feature *const * features);
+
+/**
+ @}
@name Scale Point
@{
*/
diff --git a/src/lilv_internal.h b/src/lilv_internal.h
index a700d6f..75e21a2 100644
--- a/src/lilv_internal.h
+++ b/src/lilv_internal.h
@@ -65,7 +65,6 @@ struct LilvPortImpl {
LilvNodes* classes; ///< rdf:type
};
-
struct LilvSpecImpl {
SordNode* spec;
SordNode* bundle;
@@ -132,6 +131,7 @@ struct LilvWorldImpl {
LilvNodes* loaded_files;
SordNode* dc_replaces_node;
SordNode* dyn_manifest_node;
+ SordNode* lv2_appliesTo_node;
SordNode* lv2_binary_node;
SordNode* lv2_default_node;
SordNode* lv2_index_node;
@@ -143,6 +143,7 @@ struct LilvWorldImpl {
SordNode* lv2_reportslatency_node;
SordNode* lv2_specification_node;
SordNode* lv2_symbol_node;
+ SordNode* pset_value_node;
SordNode* rdf_a_node;
SordNode* rdf_value_node;
SordNode* rdfs_class_node;
@@ -154,7 +155,6 @@ struct LilvWorldImpl {
SordNode* xsd_double_node;
SordNode* xsd_integer_node;
LilvNode* doap_name_val;
- LilvNode* lv2_applies_to_val;
LilvNode* lv2_extensionData_val;
LilvNode* lv2_name_val;
LilvNode* lv2_optionalFeature_val;
@@ -290,6 +290,13 @@ lilv_match_subject(SordIter* iter) {
}
static inline const SordNode*
+lilv_match_predicate(SordIter* iter) {
+ SordQuad tup;
+ sord_iter_get(iter, tup);
+ return tup[SORD_PREDICATE];
+}
+
+static inline const SordNode*
lilv_match_object(SordIter* iter) {
SordQuad tup;
sord_iter_get(iter, tup);
diff --git a/src/plugin.c b/src/plugin.c
index b305291..fd4b300 100644
--- a/src/plugin.c
+++ b/src/plugin.c
@@ -670,7 +670,7 @@ lilv_plugin_has_extension_data(const LilvPlugin* p,
return false;
}
}
-
+
LILV_API
LilvNodes*
lilv_plugin_get_extension_data(const LilvPlugin* p)
@@ -833,8 +833,11 @@ LilvNodes*
lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type)
{
LilvWorld* const world = plugin->world;
- LilvNodes* const related = lilv_world_find_nodes(
- world, NULL, world->lv2_applies_to_val, lilv_plugin_get_uri(plugin));
+ LilvNodes* const related = lilv_world_query_values_internal(
+ world,
+ NULL,
+ world->lv2_appliesTo_node,
+ lilv_plugin_get_uri(plugin)->val.uri_val);
if (!type) {
return related;
@@ -857,13 +860,6 @@ lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type)
return matches;
}
-static size_t
-file_sink(const void* buf, size_t len, void* stream)
-{
- FILE* file = (FILE*)stream;
- return fwrite(buf, 1, len, file);
-}
-
static SerdEnv*
new_lv2_env(const SerdNode* base)
{
@@ -910,7 +906,7 @@ lilv_plugin_write_description(LilvWorld* world,
SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
env,
NULL,
- file_sink,
+ serd_file_sink,
plugin_file);
// Write prefixes if this is a new file
@@ -950,7 +946,7 @@ lilv_plugin_write_manifest_entry(LilvWorld* world,
SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
env,
NULL,
- file_sink,
+ serd_file_sink,
manifest_file);
// Write prefixes if this is a new file
diff --git a/src/state.c b/src/state.c
new file mode 100644
index 0000000..aae3fab
--- /dev/null
+++ b/src/state.c
@@ -0,0 +1,794 @@
+/*
+ Copyright 2007-2011 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#define _POSIX_SOURCE 1 /* for fileno */
+#define _BSD_SOURCE 1 /* for lockf */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lilv-config.h"
+#include "lilv_internal.h"
+
+#ifdef HAVE_LV2_STATE
+# include "lv2/lv2plug.in/ns/ext/state/state.h"
+#endif
+
+#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_MKDIR
+# include <sys/stat.h>
+# include <sys/types.h>
+#endif
+
+#define NS_ATOM "http://lv2plug.in/ns/ext/atom#"
+#define NS_PSET "http://lv2plug.in/ns/ext/presets#"
+#define NS_STATE "http://lv2plug.in/ns/ext/state#"
+
+#define USTR(s) ((const uint8_t*)(s))
+
+typedef struct {
+ void* value;
+ size_t size;
+ uint32_t key;
+ uint32_t type;
+ uint32_t flags;
+} Property;
+
+typedef struct {
+ char* symbol;
+ LilvNode* value;
+} PortValue;
+
+struct LilvStateImpl {
+ LilvNode* plugin_uri;
+ Property* props;
+ PortValue* values;
+ char* label;
+ uint32_t num_props;
+ uint32_t num_values;
+};
+
+static int
+property_cmp(const void* a, const void* b)
+{
+ const Property* pa = (const Property*)a;
+ const Property* pb = (const Property*)b;
+ return pa->key - pb->key;
+}
+
+LILV_API
+const LilvNode*
+lilv_state_get_plugin_uri(const LilvState* state)
+{
+ return state->plugin_uri;
+}
+
+static PortValue*
+append_port_value(LilvState* state,
+ const char* port_symbol,
+ LilvNode* value)
+{
+ state->values = realloc(state->values,
+ (++state->num_values) * sizeof(PortValue));
+ PortValue* pv = &state->values[state->num_values - 1];
+ pv->symbol = lilv_strdup(port_symbol);
+ pv->value = value;
+ return pv;
+}
+
+LILV_API
+const char*
+lilv_state_get_label(const LilvState* state)
+{
+ return state->label;
+}
+
+LILV_API
+void
+lilv_state_set_label(LilvState* state,
+ const char* label)
+{
+ if (state->label) {
+ free(state->label);
+ }
+
+ state->label = label ? lilv_strdup(label) : NULL;
+}
+
+#ifdef HAVE_LV2_STATE
+static int
+store_callback(void* handle,
+ uint32_t key,
+ const void* value,
+ size_t size,
+ uint32_t type,
+ uint32_t flags)
+{
+ if (!(flags & LV2_STATE_IS_POD)) {
+ // TODO: A flag so we know if we can hold a reference would be nice
+ LILV_WARN("Non-POD property ignored.\n");
+ return 1;
+ }
+
+ LilvState* const state = (LilvState*)handle;
+ state->props = realloc(state->props,
+ (++state->num_props) * sizeof(Property));
+ Property* const prop = &state->props[state->num_props - 1];
+
+ prop->value = malloc(size);
+ memcpy(prop->value, value, size);
+
+ prop->size = size;
+ prop->key = key;
+ prop->type = type;
+ prop->flags = flags;
+
+ return 0;
+}
+#endif // HAVE_LV2_STATE
+
+LILV_API
+LilvState*
+lilv_state_new_from_instance(const LilvPlugin* plugin,
+ LilvInstance* instance,
+ LilvGetPortValueFunc get_value,
+ void* user_data,
+ uint32_t flags,
+ const LV2_Feature *const * features)
+{
+ LilvWorld* const world = plugin->world;
+ LilvState* const state = malloc(sizeof(LilvState));
+ memset(state, '\0', sizeof(LilvState));
+ state->plugin_uri = lilv_node_duplicate(lilv_plugin_get_uri(plugin));
+
+ // Store port values
+ LilvNode* lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT);
+ LilvNode* lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT);
+ for (uint32_t i = 0; i < plugin->num_ports; ++i) {
+ const LilvPort* const port = plugin->ports[i];
+ if (lilv_port_is_a(plugin, port, lv2_ControlPort)
+ && lilv_port_is_a(plugin, port, lv2_InputPort)) {
+ const char* sym = lilv_node_as_string(port->symbol);
+ append_port_value(state, sym, get_value(sym, user_data));
+ }
+ }
+ lilv_node_free(lv2_ControlPort);
+ lilv_node_free(lv2_InputPort);
+
+ // Store properties
+#ifdef HAVE_LV2_STATE
+ const LV2_Descriptor* descriptor = instance->lv2_descriptor;
+ const LV2_State_Interface* iface = (descriptor->extension_data)
+ ? descriptor->extension_data(LV2_STATE_INTERFACE_URI)
+ : NULL;
+
+ iface->save(instance->lv2_handle, store_callback, state, flags, features);
+#endif // HAVE_LV2_STATE
+
+ qsort(state->props, state->num_props, sizeof(Property), property_cmp);
+
+ return state;
+}
+
+static const void*
+retrieve_callback(void* handle,
+ uint32_t key,
+ size_t* size,
+ uint32_t* type,
+ uint32_t* flags)
+{
+ const LilvState* const state = (LilvState*)handle;
+ const Property search_key = { NULL, 0, key, 0, 0 };
+ const Property* const prop = (Property*)bsearch(
+ &search_key, state->props, state->num_props,
+ sizeof(Property), property_cmp);
+
+ if (prop) {
+ *size = prop->size;
+ *type = prop->type;
+ *flags = prop->flags;
+ return prop->value;
+ }
+ return NULL;
+}
+
+LILV_API
+void
+lilv_state_restore(const LilvState* state,
+ LilvInstance* instance,
+ LilvSetPortValueFunc set_value,
+ void* user_data,
+ uint32_t flags,
+ const LV2_Feature *const * features)
+{
+#ifdef HAVE_LV2_STATE
+ const LV2_Descriptor* descriptor = instance->lv2_descriptor;
+ const LV2_State_Interface* iface = (descriptor->extension_data)
+ ? descriptor->extension_data(LV2_STATE_INTERFACE_URI)
+ : NULL;
+
+ iface->restore(instance->lv2_handle, retrieve_callback,
+ (LV2_State_Handle)state, flags, features);
+
+ if (set_value) {
+ for (uint32_t i = 0; i < state->num_values; ++i) {
+ set_value(state->values[i].symbol,
+ state->values[i].value,
+ user_data);
+ }
+ }
+#endif // HAVE_LV2_STATE
+}
+
+static SordNode*
+get_one(SordModel* model, const SordNode* s, const SordNode* p)
+{
+ const SordQuad pat = { s, p, NULL, NULL };
+ SordIter* const i = sord_find(model, pat);
+ SordNode* const node = i ? sord_node_copy(lilv_match_object(i)) : NULL;
+ sord_iter_free(i);
+ return node;
+}
+
+static void
+property_from_node(LilvWorld* world,
+ LV2_URID_Map* map,
+ const LilvNode* node,
+ Property* prop)
+{
+ const char* str = lilv_node_as_string(node);
+ switch (node->type) {
+ case LILV_VALUE_URI:
+ prop->value = malloc(sizeof(uint32_t));
+ *(uint32_t*)prop->value = map->map(map->handle, str);
+ prop->type = map->map(map->handle, NS_ATOM "URID");
+ prop->size = sizeof(uint32_t);
+ break;
+ case LILV_VALUE_BLANK:
+ // TODO: Hmm...
+ break;
+ case LILV_VALUE_STRING:
+ prop->size = strlen(str) + 1;
+ prop->value = malloc(prop->size);
+ memcpy(prop->value, str, prop->size + 1);
+ prop->type = map->map(map->handle, NS_ATOM "String");
+ break;
+ case LILV_VALUE_BOOL:
+ prop->value = malloc(sizeof(uint32_t));
+ *(uint32_t*)prop->value = lilv_node_as_bool(node) ? 1 : 0;
+ prop->type = map->map(map->handle, NS_ATOM "Bool");
+ prop->size = sizeof(uint32_t);
+ break;
+ case LILV_VALUE_INT:
+ prop->value = malloc(sizeof(uint32_t));
+ *(uint32_t*)prop->value = lilv_node_as_int(node);
+ prop->type = map->map(map->handle, NS_ATOM "Int32");
+ prop->size = sizeof(uint32_t);
+ break;
+ case LILV_VALUE_FLOAT:
+ prop->value = malloc(sizeof(float));
+ *(float*)prop->value = lilv_node_as_float(node);
+ prop->type = map->map(map->handle, NS_ATOM "Float");
+ prop->size = sizeof(float);
+ break;
+ }
+ prop->flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
+}
+
+static LilvState*
+new_state_from_model(LilvWorld* world,
+ LV2_URID_Map* map,
+ SordModel* model,
+ const SordNode* node)
+{
+ LilvState* const state = malloc(sizeof(LilvState));
+ memset(state, '\0', sizeof(LilvState));
+
+ // Get the plugin URI this state applies to
+ const SordQuad pat1 = {
+ node, world->lv2_appliesTo_node, NULL, NULL };
+ SordIter* i = sord_find(model, pat1);
+ if (i) {
+ state->plugin_uri = lilv_node_new_from_node(
+ world, lilv_match_object(i));
+ sord_iter_free(i);
+ } else {
+ LILV_ERRORF("State %s missing lv2:appliesTo property\n",
+ sord_node_get_string(node));
+ }
+
+ // Get port values
+ const SordQuad pat2 = { node, world->lv2_port_node, NULL, NULL };
+ SordIter* ports = sord_find(model, pat2);
+ FOREACH_MATCH(ports) {
+ const SordNode* port = lilv_match_object(ports);
+ const SordNode* label = get_one(model, port, world->rdfs_label_node);
+ const SordNode* symbol = get_one(model, port, world->lv2_symbol_node);
+ const SordNode* value = get_one(model, port, world->pset_value_node);
+ if (!symbol) {
+ LILV_ERRORF("State `%s' port missing symbol.\n",
+ sord_node_get_string(node));
+ } else if (!value) {
+ LILV_ERRORF("State `%s' port `%s' missing value.\n",
+ sord_node_get_string(symbol),
+ sord_node_get_string(node));
+ } else {
+ const char* sym = (const char*)sord_node_get_string(symbol);
+ LilvNode* lvalue = lilv_node_new_from_node(world, value);
+ append_port_value(state, sym, lvalue);
+
+ if (label) {
+ lilv_state_set_label(
+ state, (const char*)sord_node_get_string(label));
+ }
+ }
+ }
+ sord_iter_free(ports);
+
+ // Get properties
+ SordNode* statep = sord_new_uri(world->world, USTR(NS_STATE "state"));
+ const SordNode* state_node = get_one(model, node, statep);
+ if (state_node) {
+ const SordQuad pat3 = { state_node, NULL, NULL };
+ SordIter* props = sord_find(model, pat3);
+ FOREACH_MATCH(props) {
+ const SordNode* p = lilv_match_predicate(props);
+ const SordNode* o = lilv_match_object(props);
+ LilvNode* onode = lilv_node_new_from_node(world, o);
+
+ Property prop = { NULL, 0, 0, 0, 0 };
+ prop.key = map->map(map->handle,
+ (const char*)sord_node_get_string(p));
+ property_from_node(world, map, onode, &prop);
+ if (prop.value) {
+ state->props = realloc(
+ state->props, (++state->num_props) * sizeof(Property));
+ state->props[state->num_props - 1] = prop;
+ }
+
+ lilv_node_free(onode);
+ }
+ sord_iter_free(props);
+ }
+ sord_node_free(world->world, statep);
+
+ qsort(state->props, state->num_props, sizeof(Property), property_cmp);
+
+ return state;
+}
+
+LILV_API
+LilvState*
+lilv_state_new_from_world(LilvWorld* world,
+ LV2_URID_Map* map,
+ const LilvNode* node)
+{
+ if (!lilv_node_is_uri(node) && !lilv_node_is_blank(node)) {
+ LILV_ERRORF("Subject `%s' is not a URI or blank node.\n",
+ lilv_node_as_string(node));
+ return NULL;
+ }
+
+ return new_state_from_model(world, map, world->model, node->val.uri_val);
+}
+
+LILV_API
+LilvState*
+lilv_state_new_from_file(LilvWorld* world,
+ LV2_URID_Map* map,
+ const LilvNode* subject,
+ const char* path)
+{
+ if (subject && !lilv_node_is_uri(subject)
+ && !lilv_node_is_blank(subject)) {
+ LILV_ERRORF("Subject `%s' is not a URI or blank node.\n",
+ lilv_node_as_string(subject));
+ return NULL;
+ }
+
+ uint8_t* uri = (uint8_t*)lilv_strjoin("file://", path, NULL);
+ SerdNode base = serd_node_from_string(SERD_URI, uri);
+ SerdEnv* env = serd_env_new(&base);
+ SordModel* model = sord_new(world->world, SORD_SPO, false);
+ SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
+
+ serd_reader_read_file(reader, uri);
+
+ SordNode* subject_node = (subject)
+ ? subject->val.uri_val
+ : sord_node_from_serd_node(world->world, env, &base, NULL, NULL);
+
+ LilvState* state = new_state_from_model(world, map, model, subject_node);
+
+ serd_reader_free(reader);
+ sord_free(model);
+ serd_env_free(env);
+ free(uri);
+ return state;
+}
+
+static LilvNode*
+node_from_property(LilvWorld* world, const char* type, void* value, size_t size)
+{
+ if (!strcmp(type, NS_ATOM "String")) {
+ return lilv_new_string(world, (const char*)value);
+ } else if (!strcmp(type, NS_ATOM "Int32")) {
+ if (size == sizeof(int32_t)) {
+ return lilv_new_int(world, *(int32_t*)value);
+ } else {
+ LILV_WARNF("Int32 property <%s> has size %zu\n", type, size);
+ }
+ } else if (!strcmp(type, NS_ATOM "Float")) {
+ if (size == sizeof(float)) {
+ return lilv_new_float(world, *(float*)value);
+ } else {
+ LILV_WARNF("Float property <%s> has size %zu\n", type, size);
+ }
+ } else if (!strcmp(type, NS_ATOM "Bool")) {
+ if (size == sizeof(int32_t)) {
+ return lilv_new_bool(world, *(int32_t*)value);
+ } else {
+ LILV_WARNF("Bool property <%s> has size %zu\n", type, size);
+ }
+ }
+ return NULL;
+}
+
+static void
+node_to_serd(const LilvNode* node, SerdNode* value, SerdNode* type)
+{
+ const char* type_uri = NULL;
+ switch (node->type) {
+ case LILV_VALUE_URI:
+ *value = serd_node_from_string(SERD_URI, USTR(node->str_val));
+ break;
+ case LILV_VALUE_BLANK:
+ *value = serd_node_from_string(SERD_BLANK, USTR(node->str_val));
+ break;
+ default:
+ *value = serd_node_from_string(SERD_LITERAL, USTR(node->str_val));
+ switch (node->type) {
+ case LILV_VALUE_BOOL:
+ type_uri = LILV_NS_XSD "boolean";
+ break;
+ case LILV_VALUE_INT:
+ type_uri = LILV_NS_XSD "integer";
+ break;
+ case LILV_VALUE_FLOAT:
+ type_uri = LILV_NS_XSD "decimal";
+ break;
+ default:
+ break;
+ }
+ }
+ *type = (type_uri)
+ ? serd_node_from_string(SERD_URI, USTR(type_uri))
+ : SERD_NODE_NULL;
+}
+
+static int
+add_state_to_manifest(const LilvNode* plugin_uri,
+ const char* manifest_path,
+ const char* state_uri,
+ const char* state_file_uri)
+{
+ FILE* fd = fopen((char*)manifest_path, "a");
+ if (!fd) {
+ fprintf(stderr, "error: Failed to open %s (%s)\n",
+ manifest_path, strerror(errno));
+ return 4;
+ }
+
+ // Make path relative if it is in the same directory as manifest
+ const char* last_slash = strrchr(state_file_uri, '/');
+ if (last_slash) {
+ const size_t len = last_slash - state_file_uri;
+ if (!strncmp(manifest_path, state_file_uri, len)) {
+ state_file_uri = last_slash + 1;
+ }
+ }
+
+ SerdEnv* env = serd_env_new(NULL);
+ serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
+ serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
+ serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
+
+#if defined(HAVE_LOCKF) && defined(HAVE_FILENO)
+ lockf(fileno(fd), F_LOCK, 0);
+#endif
+
+ char* const manifest_uri = lilv_strjoin("file://", manifest_path, NULL);
+
+ SerdURI base_uri;
+ SerdNode base = serd_node_new_uri_from_string(
+ (const uint8_t*)manifest_uri, NULL, &base_uri);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE, SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
+ env, &base_uri,
+ serd_file_sink,
+ fd);
+
+ fseek(fd, 0, SEEK_END);
+ if (ftell(fd) == 0) {
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+ } else {
+ fprintf(fd, "\n");
+ }
+
+ if (!state_uri) {
+ state_uri = state_file_uri;
+ }
+
+ SerdNode s = serd_node_from_string(SERD_URI, USTR(state_uri));
+ SerdNode file = serd_node_from_string(SERD_URI, USTR(state_file_uri));
+
+ // <state> a pset:Preset
+ SerdNode p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
+ SerdNode o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset"));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ // <state> rdfs:seeAlso <file>
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso"));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &file, NULL, NULL);
+
+ // <state> lv2:appliesTo <plugin>
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_LV2 "appliesTo"));
+ o = serd_node_from_string(
+ SERD_URI, USTR(lilv_node_as_string(plugin_uri)));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ serd_writer_free(writer);
+ serd_node_free(&base);
+
+#ifdef HAVE_LOCKF
+ lockf(fileno(fd), F_ULOCK, 0);
+#endif
+
+ fclose(fd);
+ free(manifest_uri);
+ serd_env_free(env);
+
+ return 0;
+}
+
+static char*
+pathify(const char* in)
+{
+ const size_t in_len = strlen(in);
+
+ char* out = calloc(in_len + 1, 1);
+ for (size_t i = 0; i < in_len; ++i) {
+ char c = in[i];
+ if (!((c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9'))) {
+ c = '-';
+ }
+ out[i] = c;
+ }
+ return out;
+}
+
+LILV_API
+int
+lilv_state_save(LilvWorld* world,
+ LV2_URID_Unmap* unmap,
+ const LilvState* state,
+ const char* uri,
+ const char* path,
+ const char* manifest_path)
+{
+ char* default_path = NULL;
+ char* default_manifest_path = NULL;
+ if (!path) {
+#ifdef HAVE_MKDIR
+ if (!state->label) {
+ LILV_ERROR("Attempt to save state with no label or path.\n");
+ return 1;
+ }
+
+ const char* const home = getenv("HOME");
+ if (!home) {
+ fprintf(stderr, "error: $HOME is undefined\n");
+ return 2;
+ }
+
+ // Create ~/.lv2/
+ char* const lv2dir = lilv_strjoin(home, "/.lv2/", NULL);
+ if (mkdir(lv2dir, 0755) && errno != EEXIST) {
+ fprintf(stderr, "error: Unable to create %s (%s)\n",
+ lv2dir, strerror(errno));
+ free(lv2dir);
+ return 3;
+ }
+
+ // Create ~/.lv2/presets.lv2/
+ char* const bundle = lilv_strjoin(lv2dir, "presets.lv2/", NULL);
+ if (mkdir(bundle, 0755) && errno != EEXIST) {
+ fprintf(stderr, "error: Unable to create %s (%s)\n",
+ lv2dir, strerror(errno));
+ free(lv2dir);
+ free(bundle);
+ return 4;
+ }
+
+ char* const filename = pathify(state->label);
+ default_path = lilv_strjoin(bundle, filename, ".ttl", NULL);
+ default_manifest_path = lilv_strjoin(bundle, "manifest.ttl", NULL);
+
+ path = default_path;
+ manifest_path = default_manifest_path;
+
+ free(lv2dir);
+ free(bundle);
+ free(filename);
+#else
+ LILV_ERROR("Save to default state path but mkdir is unavailable.\n");
+ return 1;
+#endif
+ }
+
+ FILE* fd = fopen(path, "w");
+ if (!fd) {
+ fprintf(stderr, "error: Failed to open %s (%s)\n",
+ path, strerror(errno));
+ free(default_path);
+ free(default_manifest_path);
+ return 4;
+ }
+
+ SerdEnv* env = serd_env_new(NULL);
+ serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2));
+ serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
+ serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
+ serd_env_set_prefix_from_strings(env, USTR("state"), USTR(NS_STATE));
+
+ SerdNode lv2_appliesTo = serd_node_from_string(
+ SERD_CURIE, USTR("lv2:appliesTo"));
+
+ const SerdNode* plugin_uri = sord_node_to_serd_node(
+ state->plugin_uri->val.uri_val);
+
+ SerdNode subject = serd_node_from_string(SERD_URI, USTR(uri ? uri : ""));
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
+ env,
+ &SERD_URI_NULL,
+ serd_file_sink,
+ fd);
+
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+
+ // <subject> a pset:Preset
+ SerdNode p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
+ SerdNode o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset"));
+ serd_writer_write_statement(writer, 0, NULL,
+ &subject, &p, &o, NULL, NULL);
+
+ // <subject> lv2:appliesTo <http://example.org/plugin>
+ serd_writer_write_statement(writer, 0, NULL,
+ &subject,
+ &lv2_appliesTo,
+ plugin_uri, NULL, NULL);
+
+ // <subject> rdfs:label label
+ if (state->label) {
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "label"));
+ o = serd_node_from_string(SERD_LITERAL, USTR(state->label));
+ serd_writer_write_statement(writer, 0,
+ NULL, &subject, &p, &o, NULL, NULL);
+ }
+
+ // Save port values
+ for (uint32_t i = 0; i < state->num_values; ++i) {
+ PortValue* const value = &state->values[i];
+
+ const SerdNode port = serd_node_from_string(
+ SERD_BLANK, USTR(value->symbol));
+
+ // <> lv2:port _:symbol
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_LV2 "port"));
+ serd_writer_write_statement(writer, SERD_ANON_O_BEGIN,
+ NULL, &subject, &p, &port, NULL, NULL);
+
+ // _:symbol lv2:symbol "symbol"
+ p = serd_node_from_string(SERD_URI, USTR(LILV_NS_LV2 "symbol"));
+ o = serd_node_from_string(SERD_LITERAL, USTR(value->symbol));
+ serd_writer_write_statement(writer, SERD_ANON_CONT,
+ NULL, &port, &p, &o, NULL, NULL);
+
+ // _:symbol pset:value value
+ p = serd_node_from_string(SERD_URI, USTR(NS_PSET "value"));
+ SerdNode t;
+ node_to_serd(value->value, &o, &t);
+ serd_writer_write_statement(writer, SERD_ANON_CONT, NULL,
+ &port, &p, &o, &t, NULL);
+
+ serd_writer_end_anon(writer, &port);
+ }
+
+ // Save properties
+ const SerdNode state_node = serd_node_from_string(SERD_BLANK,
+ USTR("2state"));
+ if (state->num_props > 0) {
+ p = serd_node_from_string(SERD_URI, USTR(NS_STATE "state"));
+ serd_writer_write_statement(writer, SERD_ANON_O_BEGIN, NULL,
+ &subject, &p, &state_node, NULL, NULL);
+
+ }
+ for (uint32_t i = 0; i < state->num_props; ++i) {
+ Property* prop = &state->props[i];
+ const char* key = unmap->unmap(unmap->handle, prop->key);
+ const char* type = unmap->unmap(unmap->handle, prop->type);
+ if (!key) {
+ LILV_WARNF("Failed to unmap property key `%d'\n", prop->key);
+ } else if (!type) {
+ LILV_WARNF("Failed to unmap property type `%d'\n", prop->type);
+ } else if (!(prop->flags & LV2_STATE_IS_PORTABLE)) {
+ LILV_WARNF("Unable to save non-portable property <%s>\n", type);
+ } else {
+ LilvNode* const node = node_from_property(
+ world, type, prop->value, prop->size);
+ if (node) {
+ p = serd_node_from_string(SERD_URI, USTR(key));
+ SerdNode t;
+ node_to_serd(node, &o, &t);
+ serd_writer_write_statement(
+ writer, SERD_ANON_CONT, NULL,
+ &state_node, &p, &o, &t, NULL);
+ } else {
+ LILV_WARNF("Unable to save property type <%s>\n", type);
+ }
+ }
+ }
+ if (state->num_props > 0) {
+ serd_writer_end_anon(writer, &state_node);
+ }
+
+ // Close state file and clean up Serd
+ serd_writer_free(writer);
+ fclose(fd);
+ serd_env_free(env);
+
+ if (manifest_path) {
+ add_state_to_manifest(
+ state->plugin_uri, manifest_path, uri, path);
+ }
+
+ free(default_path);
+ free(default_manifest_path);
+ return 0;
+}
+
+LILV_API
+void
+lilv_state_free(LilvState* state)
+{
+ if (state) {
+ lilv_node_free(state->plugin_uri);
+ free(state->props);
+ free(state->values);
+ free(state->label);
+ free(state);
+ }
+}
diff --git a/src/world.c b/src/world.c
index ddcf5d1..af20bc3 100644
--- a/src/world.c
+++ b/src/world.c
@@ -14,7 +14,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#define _POSIX_SOURCE 1
+#define _POSIX_SOURCE 1 /* for wordexp */
#include <assert.h>
#include <errno.h>
@@ -22,12 +22,13 @@
#include <string.h>
#include <dirent.h>
-#ifdef HAVE_WORDEXP
-#include <wordexp.h>
-#endif
#include "lilv_internal.h"
+#ifdef HAVE_WORDEXP
+# include <wordexp.h>
+#endif
+
LILV_API
LilvWorld*
lilv_world_new(void)
@@ -48,14 +49,16 @@ lilv_world_new(void)
world->loaded_files = zix_tree_new(
false, lilv_resource_node_cmp, NULL, (ZixDestroyFunc)lilv_node_free);
-#define NS_DYNMAN "http://lv2plug.in/ns/ext/dynmanifest#"
#define NS_DCTERMS "http://purl.org/dc/terms/"
+#define NS_DYNMAN "http://lv2plug.in/ns/ext/dynmanifest#"
+#define NS_PSET "http://lv2plug.in/ns/ext/presets#"
#define NEW_URI(uri) sord_new_uri(world->world, (const uint8_t*)uri)
#define NEW_URI_VAL(uri) lilv_new_uri(world, (const char*)(uri));
world->dc_replaces_node = NEW_URI(NS_DCTERMS "replaces");
world->dyn_manifest_node = NEW_URI(NS_DYNMAN "DynManifest");
+ world->lv2_appliesTo_node = NEW_URI(LILV_NS_LV2 "appliesTo");
world->lv2_binary_node = NEW_URI(LILV_NS_LV2 "binary");
world->lv2_default_node = NEW_URI(LILV_NS_LV2 "default");
world->lv2_index_node = NEW_URI(LILV_NS_LV2 "index");
@@ -67,6 +70,7 @@ lilv_world_new(void)
world->lv2_reportslatency_node = NEW_URI(LILV_NS_LV2 "reportsLatency");
world->lv2_specification_node = NEW_URI(LILV_NS_LV2 "Specification");
world->lv2_symbol_node = NEW_URI(LILV_NS_LV2 "symbol");
+ world->pset_value_node = NEW_URI(NS_PSET "value");
world->rdf_a_node = NEW_URI(LILV_NS_RDF "type");
world->rdf_value_node = NEW_URI(LILV_NS_RDF "value");
world->rdfs_class_node = NEW_URI(LILV_NS_RDFS "Class");
@@ -79,7 +83,6 @@ lilv_world_new(void)
world->xsd_integer_node = NEW_URI(LILV_NS_XSD "integer");
world->doap_name_val = NEW_URI_VAL(LILV_NS_DOAP "name");
- world->lv2_applies_to_val = NEW_URI_VAL(LILV_NS_LV2 "appliesTo");
world->lv2_extensionData_val = NEW_URI_VAL(LILV_NS_LV2 "extensionData");
world->lv2_name_val = NEW_URI_VAL(LILV_NS_LV2 "name");
world->lv2_optionalFeature_val = NEW_URI_VAL(LILV_NS_LV2 "optionalFeature");
@@ -113,6 +116,7 @@ lilv_world_free(LilvWorld* world)
sord_node_free(world->world, world->dc_replaces_node);
sord_node_free(world->world, world->dyn_manifest_node);
+ sord_node_free(world->world, world->lv2_appliesTo_node);
sord_node_free(world->world, world->lv2_binary_node);
sord_node_free(world->world, world->lv2_default_node);
sord_node_free(world->world, world->lv2_index_node);
@@ -124,6 +128,7 @@ lilv_world_free(LilvWorld* world)
sord_node_free(world->world, world->lv2_reportslatency_node);
sord_node_free(world->world, world->lv2_specification_node);
sord_node_free(world->world, world->lv2_symbol_node);
+ sord_node_free(world->world, world->pset_value_node);
sord_node_free(world->world, world->rdf_a_node);
sord_node_free(world->world, world->rdf_value_node);
sord_node_free(world->world, world->rdfs_class_node);
@@ -135,7 +140,6 @@ lilv_world_free(LilvWorld* world)
sord_node_free(world->world, world->xsd_double_node);
sord_node_free(world->world, world->xsd_integer_node);
lilv_node_free(world->doap_name_val);
- lilv_node_free(world->lv2_applies_to_val);
lilv_node_free(world->lv2_extensionData_val);
lilv_node_free(world->lv2_name_val);
lilv_node_free(world->lv2_optionalFeature_val);
@@ -585,7 +589,7 @@ expand(const char* path)
wordexp_t p;
if (wordexp(path, &p, 0)) {
LILV_ERRORF("Error expanding path `%s'\n", path);
- return lilv_strdup (path);
+ return lilv_strdup(path);
}
if (p.we_wordc == 0) {
/* Literal directory path (e.g. no variables or ~) */
diff --git a/src/zix/tree.c b/src/zix/tree.c
index 1be9227..1189230 100644
--- a/src/zix/tree.c
+++ b/src/zix/tree.c
@@ -461,7 +461,7 @@ ZIX_API
void*
zix_tree_get(ZixTreeIter* ti)
{
- return ti->data;
+ return ti ? ti->data : NULL;
}
ZIX_API
diff --git a/wscript b/wscript
index 8858413..13a382f 100644
--- a/wscript
+++ b/wscript
@@ -8,7 +8,7 @@ import waflib.Options as Options
import waflib.Logs as Logs
# Version of this package (even if built as a child)
-LILV_VERSION = '0.8.0'
+LILV_VERSION = '0.9.0'
LILV_MAJOR_VERSION = '0'
# Library version (UNIX style major, minor, micro)
@@ -65,19 +65,40 @@ def configure(conf):
conf.line_just = 51
autowaf.configure(conf)
autowaf.display_header('Lilv Configuration')
+ conf.env.append_unique('CFLAGS', '-std=c99')
autowaf.check_pkg(conf, 'lv2core', uselib_store='LV2CORE', mandatory=True)
autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD',
atleast_version='0.7.0', mandatory=True)
autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD',
atleast_version='0.5.0', mandatory=True)
+ autowaf.check_pkg(conf, 'lv2-lv2plug.in-ns-ext-state',
+ uselib_store='LV2_STATE', mandatory=True)
+
+ conf.check_cc(function_name='wordexp',
+ header_name='wordexp.h',
+ defines='_POSIX_SOURCE',
+ define_name='HAVE_WORDEXP',
+ mandatory=False)
+
+ conf.check_cc(function_name='lockf',
+ header_name='unistd.h',
+ defines='_BSD_SOURCE',
+ define_name='HAVE_LOCKF',
+ mandatory=False)
+
+ conf.check_cc(function_name='fileno',
+ header_name='stdio.h',
+ defines='_POSIX_SOURCE',
+ define_name='HAVE_FILENO',
+ mandatory=False)
+
+ conf.check_cc(function_name='mkdir',
+ header_name=['sys/stat.h','sys/types.h'],
+ defines='_POSIX_SOURCE',
+ define_name='HAVE_MKDIR',
+ mandatory=False)
- conf.check(function_name='wordexp',
- header_name='wordexp.h',
- define_name='HAVE_WORDEXP',
- mandatory=False)
-
- conf.env.append_unique('CFLAGS', '-std=c99')
autowaf.define(conf, 'LILV_VERSION', LILV_VERSION)
if Options.options.dyn_manifest:
autowaf.define(conf, 'LILV_DYN_MANIFEST', 1)
@@ -131,6 +152,8 @@ def configure(conf):
bool(conf.env['BUILD_TESTS']))
autowaf.display_msg(conf, "Dynamic manifest support",
bool(conf.env['LILV_DYN_MANIFEST']))
+ autowaf.display_msg(conf, "State/Preset support",
+ conf.is_defined('HAVE_LV2_STATE'))
autowaf.display_msg(conf, "Python bindings",
conf.is_defined('LILV_PYTHON'))
print('')
@@ -154,6 +177,7 @@ def build(bld):
src/port.c
src/query.c
src/scalepoint.c
+ src/state.c
src/ui.c
src/util.c
src/world.c