diff options
author | David Robillard <d@drobilla.net> | 2011-12-23 02:41:56 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2011-12-23 02:41:56 +0000 |
commit | c5c4a6e935eecc2d2dac7bb9cccd36057b8dc123 (patch) | |
tree | aac647c5a0fcc59bfe583bb25c6b508f21db7a19 /src/state.c | |
parent | b760711088bb027d7c84eec7c19360d8f02680ec (diff) | |
download | lilv-c5c4a6e935eecc2d2dac7bb9cccd36057b8dc123.tar.gz lilv-c5c4a6e935eecc2d2dac7bb9cccd36057b8dc123.tar.bz2 lilv-c5c4a6e935eecc2d2dac7bb9cccd36057b8dc123.zip |
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.
git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3899 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/state.c')
-rw-r--r-- | src/state.c | 794 |
1 files changed, 794 insertions, 0 deletions
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); + } +} |