/* Copyright 2007-2011 David Robillard 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 #include #include #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 #endif #ifdef HAVE_MKDIR # include # include #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; } static int value_cmp(const void* a, const void* b) { const PortValue* pa = (const PortValue*)a; const PortValue* pb = (const PortValue*)b; return strcmp(pa->symbol, pb->symbol); } 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; } #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); qsort(state->values, state->num_values, sizeof(PortValue), value_cmp); return state; } #ifdef HAVE_LV2_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; } #endif // HAVE_LV2_STATE 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_STRING: prop->size = strlen(str) + 1; prop->value = malloc(prop->size); memcpy(prop->value, str, prop->size); 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; case LILV_VALUE_BLANK: case LILV_VALUE_BLOB: // TODO: Blank nodes in state break; } } 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 upat = { node, world->lv2_appliesTo_node, NULL, NULL }; SordIter* i = sord_find(model, upat); 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 the state label const SordQuad lpat = { node, world->rdfs_label_node, NULL, NULL }; i = sord_find(model, lpat); if (i) { state->label = lilv_strdup( (const char*)sord_node_get_string(lilv_match_object(i))); sord_iter_free(i); } else { } // Get port values const SordQuad ppat = { node, world->lv2_port_node, NULL, NULL }; SordIter* ports = sord_find(model, ppat); 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 spat = { state_node, NULL, NULL }; SordIter* props = sord_find(model, spat); FOREACH_MATCH(props) { const SordNode* p = lilv_match_predicate(props); const SordNode* o = lilv_match_object(props); uint32_t flags = 0; #ifdef HAVE_LV2_STATE flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE; #endif Property prop = { NULL, 0, 0, 0, flags }; prop.key = map->map(map->handle, (const char*)sord_node_get_string(p)); if (sord_node_get_type(o) == SORD_BLANK) { const SordNode* type = get_one(model, o, world->rdf_a_node); const SordNode* value = get_one(model, o, world->rdf_value_node); if (type && value) { size_t len; const uint8_t* b64 = sord_node_get_string_counted(value, &len); prop.value = serd_base64_decode(b64, len, &prop.size); prop.type = map->map(map->handle, (const char*)sord_node_get_string(type)); } else { LILV_ERRORF("Unable to parse blank node property <%p>\n", sord_node_get_string(p)); } } else { LilvNode* onode = lilv_node_new_from_node(world, o); property_from_node(world, map, onode, &prop); lilv_node_free(onode); } if (prop.value) { state->props = realloc( state->props, (++state->num_props) * sizeof(Property)); state->props[state->num_props - 1] = prop; } } sord_iter_free(props); } sord_node_free(world->world, statep); qsort(state->props, state->num_props, sizeof(Property), property_cmp); qsort(state->values, state->num_values, sizeof(PortValue), value_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, LV2_URID_Unmap* unmap, 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 "URID")) { const char* str = unmap->unmap(unmap->handle, *(uint32_t*)value); return lilv_new_uri(world, str); } 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) { LILV_ERRORF("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("rdf"), USTR(LILV_NS_RDF)); 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)); // 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); // rdfs:seeAlso p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso")); serd_writer_write_statement(writer, 0, NULL, &s, &p, &file, NULL, NULL); // lv2:appliesTo 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; } static int mkdir_p(const char* dir_path) { char* path = lilv_strdup(dir_path); const size_t path_len = strlen(path); for (size_t i = 1; i <= path_len; ++i) { if (path[i] == LILV_DIR_SEP[0] || path[i] == '\0') { path[i] = '\0'; if (mkdir(path, 0755) && errno != EEXIST) { LILV_ERRORF("Failed to create %s (%s)\n", path, strerror(errno)); free(path); return 1; } path[i] = LILV_DIR_SEP[0]; } } free(path); return 0; } static int lilv_default_state_path(LilvWorld* world, const LilvState* state, char** path, char** manifest_path) { #ifdef HAVE_MKDIR if (!state->label) { LILV_ERROR("Attempt to save state with no label or path.\n"); return 1; } char* state_bundle = getenv("LV2_STATE_BUNDLE"); if (!state_bundle) { state_bundle = LILV_DEFAULT_STATE_BUNDLE; } // Create ~/.lv2/presets.lv2/ char* const bundle = lilv_expand(state_bundle); if (mkdir_p(bundle)) { free(bundle); return 3; } char* const filename = pathify(state->label); *path = lilv_strjoin( bundle, LILV_DIR_SEP, filename, ".ttl", NULL); *manifest_path = lilv_strjoin( bundle, LILV_DIR_SEP, "manifest.ttl", NULL); free(bundle); free(filename); return 0; #else LILV_ERROR("Save to default state path but mkdir is unavailable.\n"); return 4; #endif } 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) { if (lilv_default_state_path( world, state, &default_path, &default_manifest_path)) { return 1; } path = default_path; manifest_path = default_manifest_path; } FILE* fd = fopen(path, "w"); if (!fd) { LILV_ERRORF("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("rdf"), USTR(LILV_NS_RDF)); 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); // 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); // lv2:appliesTo serd_writer_write_statement(writer, 0, NULL, &subject, &lv2_appliesTo, plugin_uri, NULL, NULL); // 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); #ifdef HAVE_LV2_STATE } else if (!(prop->flags & LV2_STATE_IS_PORTABLE) || !(prop->flags & LV2_STATE_IS_POD)) { LILV_WARNF("Unable to save non-portable property <%s>\n", type); #endif } else { SerdNode t; p = serd_node_from_string(SERD_URI, USTR(key)); LilvNode* const node = node_from_property( world, unmap, type, prop->value, prop->size); if (node) { node_to_serd(node, &o, &t); serd_writer_write_statement( writer, SERD_ANON_CONT, NULL, &state_node, &p, &o, &t, NULL); lilv_node_free(node); } else { char name[16]; snprintf(name, sizeof(name), "b%u", i); const SerdNode blank = serd_node_from_string( SERD_BLANK, (const uint8_t*)name); // [ serd_writer_write_statement( writer, SERD_ANON_O_BEGIN, NULL, &state_node, &p, &blank, NULL, NULL); // rdf:type p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")); o = serd_node_from_string(SERD_URI, USTR(type)); serd_writer_write_statement( writer, SERD_ANON_CONT, NULL, &blank, &p, &o, NULL, NULL); // rdf:value "string"^^ SerdNode blob = serd_node_new_blob(prop->value, prop->size, true); p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "value")); t = serd_node_from_string(SERD_URI, USTR(LILV_NS_XSD "base64Binary")); serd_writer_write_statement( writer, SERD_ANON_CONT, NULL, &blank, &p, &blob, &t, NULL); serd_node_free(&blob); serd_writer_end_anon(writer, &blank); // ] } } } 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) { for (uint32_t i = 0; i < state->num_props; ++i) { free(state->props[i].value); } for (uint32_t i = 0; i < state->num_values; ++i) { lilv_node_free(state->values[i].value); free(state->values[i].symbol); } lilv_node_free(state->plugin_uri); free(state->props); free(state->values); free(state->label); free(state); } } LILV_API bool lilv_state_equals(const LilvState* a, const LilvState* b) { if (!lilv_node_equals(a->plugin_uri, b->plugin_uri) || (a->label && !b->label) || (b->label && !a->label) || (a->label && b->label && strcmp(a->label, b->label)) || a->num_props != b->num_props || a->num_values != b->num_values) { return false; } for (uint32_t i = 0; i < a->num_values; ++i) { PortValue* const av = &a->values[i]; PortValue* const bv = &b->values[i]; if (strcmp(av->symbol, bv->symbol)) { return false; } else if (!lilv_node_equals(av->value, bv->value)) { return false; } } for (uint32_t i = 0; i < a->num_props; ++i) { Property* const ap = &a->props[i]; Property* const bp = &b->props[i]; if (ap->size != bp->size || ap->key != bp->key || ap->type != bp->type || ap->flags != bp->flags || memcmp(ap->value, bp->value, ap->size)) { return false; } } return true; } LILV_API unsigned lilv_state_get_num_properties(const LilvState* state) { return state->num_props; } LILV_API const LilvNode* lilv_state_get_plugin_uri(const LilvState* state) { return state->plugin_uri; } 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; }