summaryrefslogtreecommitdiffstats
path: root/src/state.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-12-30 08:23:19 +0000
committerDavid Robillard <d@drobilla.net>2011-12-30 08:23:19 +0000
commit8708ccb858ddbf5d521c4755e137bd04544a6ae5 (patch)
tree2da5030db36592ff19b13f8ff802a5f5c3bfb3b8 /src/state.c
parentc5c4a6e935eecc2d2dac7bb9cccd36057b8dc123 (diff)
downloadlilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.tar.gz
lilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.tar.bz2
lilv-8708ccb858ddbf5d521c4755e137bd04544a6ae5.zip
Support arbitrary binary data in plugin state via base64 encoding.
Unit testing for plugin instantiation and state. Build without LV2 state available. Support URID values in plugin state nicely. Fix various holes in state implementation exposed by tests. git-svn-id: http://svn.drobilla.net/lad/trunk/lilv@3908 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/state.c')
-rw-r--r--src/state.c184
1 files changed, 149 insertions, 35 deletions
diff --git a/src/state.c b/src/state.c
index aae3fab..f50557f 100644
--- a/src/state.c
+++ b/src/state.c
@@ -73,6 +73,14 @@ property_cmp(const void* a, const void* 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);
+}
+
LILV_API
const LilvNode*
lilv_state_get_plugin_uri(const LilvState* state)
@@ -131,7 +139,7 @@ store_callback(void* 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);
@@ -183,10 +191,12 @@ lilv_state_new_from_instance(const LilvPlugin* plugin,
#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,
@@ -208,6 +218,7 @@ retrieve_callback(void* handle,
}
return NULL;
}
+#endif // HAVE_LV2_STATE
LILV_API
void
@@ -261,9 +272,6 @@ property_from_node(LilvWorld* world,
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);
@@ -288,8 +296,11 @@ property_from_node(LilvWorld* world,
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;
}
- prop->flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
}
static LilvState*
@@ -302,9 +313,9 @@ new_state_from_model(LilvWorld* world,
memset(state, '\0', sizeof(LilvState));
// Get the plugin URI this state applies to
- const SordQuad pat1 = {
+ const SordQuad upat = {
node, world->lv2_appliesTo_node, NULL, NULL };
- SordIter* i = sord_find(model, pat1);
+ SordIter* i = sord_find(model, upat);
if (i) {
state->plugin_uri = lilv_node_new_from_node(
world, lilv_match_object(i));
@@ -314,9 +325,19 @@ new_state_from_model(LilvWorld* world,
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);
+ }
+
// Get port values
- const SordQuad pat2 = { node, world->lv2_port_node, NULL, NULL };
- SordIter* ports = sord_find(model, pat2);
+ 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);
@@ -346,31 +367,53 @@ new_state_from_model(LilvWorld* world,
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);
+ 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);
- LilvNode* onode = lilv_node_new_from_node(world, o);
-
- Property prop = { NULL, 0, 0, 0, 0 };
+ 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));
- property_from_node(world, map, onode, &prop);
+
+ 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;
}
- lilv_node_free(onode);
}
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;
}
@@ -425,10 +468,14 @@ lilv_state_new_from_file(LilvWorld* world,
}
static LilvNode*
-node_from_property(LilvWorld* world, const char* type, void* value, size_t size)
+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);
@@ -491,8 +538,8 @@ add_state_to_manifest(const LilvNode* plugin_uri,
{
FILE* fd = fopen((char*)manifest_path, "a");
if (!fd) {
- fprintf(stderr, "error: Failed to open %s (%s)\n",
- manifest_path, strerror(errno));
+ LILV_ERRORF("Failed to open %s (%s)\n",
+ manifest_path, strerror(errno));
return 4;
}
@@ -508,6 +555,7 @@ add_state_to_manifest(const LilvNode* plugin_uri,
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)
@@ -607,15 +655,14 @@ lilv_state_save(LilvWorld* world,
const char* const home = getenv("HOME");
if (!home) {
- fprintf(stderr, "error: $HOME is undefined\n");
+ LILV_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));
+ LILV_ERRORF("Unable to create %s (%s)\n", lv2dir, strerror(errno));
free(lv2dir);
return 3;
}
@@ -623,13 +670,12 @@ lilv_state_save(LilvWorld* world,
// 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));
+ LILV_ERRORF("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);
@@ -648,8 +694,7 @@ lilv_state_save(LilvWorld* world,
FILE* fd = fopen(path, "w");
if (!fd) {
- fprintf(stderr, "error: Failed to open %s (%s)\n",
- path, strerror(errno));
+ LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno));
free(default_path);
free(default_manifest_path);
return 4;
@@ -658,6 +703,7 @@ lilv_state_save(LilvWorld* world,
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));
@@ -744,20 +790,50 @@ lilv_state_save(LilvWorld* world,
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)) {
+#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, type, prop->value, prop->size);
+ world, unmap, 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);
+ char name[16];
+ snprintf(name, sizeof(name), "b%u", i);
+ const SerdNode blank = serd_node_from_string(
+ SERD_BLANK, (const uint8_t*)name);
+
+ // <state> <key> [
+ serd_writer_write_statement(
+ writer, SERD_ANON_O_BEGIN, NULL,
+ &state_node, &p, &blank, NULL, NULL);
+
+ // rdf:type <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"^^<xsd:base64Binary>
+ 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); // ]
}
}
}
@@ -781,6 +857,44 @@ lilv_state_save(LilvWorld* world,
}
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
void
lilv_state_free(LilvState* state)
{