1 /*
2 Copyright 2007-2016 David Robillard <http://drobilla.net>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <errno.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "lv2/atom.h"
22 #include "lv2/atom-forge.h"
23 #include "lv2/presets.h"
24 #include "lv2/state.h"
25
26 #include "lilv_config.h"
27 #include "lilv_internal.h"
28 #include "sratom/sratom.h"
29
30 #define USTR(s) ((const uint8_t*)(s))
31
32 typedef struct {
33 void* value; ///< Value/Object
34 size_t size; ///< Size of value
35 uint32_t key; ///< Key/Predicate (URID)
36 uint32_t type; ///< Type of value (URID)
37 uint32_t flags; ///< State flags (POD, etc)
38 } Property;
39
40 typedef struct {
41 char* symbol; ///< Symbol of port
42 void* value; ///< Value of port
43 uint32_t size; ///< Size of value
44 uint32_t type; ///< Type of value (URID)
45 } PortValue;
46
47 typedef struct {
48 char* abs; ///< Absolute path of actual file
49 char* rel; ///< Abstract path (relative path in state dir)
50 } PathMap;
51
52 typedef struct {
53 size_t n;
54 Property* props;
55 } PropertyArray;
56
57 struct LilvStateImpl {
58 LilvNode* plugin_uri; ///< Plugin URI
59 LilvNode* uri; ///< State/preset URI
60 char* dir; ///< Save directory (if saved)
61 char* file_dir; ///< Directory for files created by plugin
62 char* copy_dir; ///< Directory for snapshots of external files
63 char* link_dir; ///< Directory for links to external files
64 char* label; ///< State/Preset label
65 ZixTree* abs2rel; ///< PathMap sorted by abs
66 ZixTree* rel2abs; ///< PathMap sorted by rel
67 PropertyArray props; ///< State properties
68 PropertyArray metadata; ///< State metadata
69 PortValue* values; ///< Port values
70 uint32_t atom_Path; ///< atom:Path URID
71 uint32_t n_values; ///< Number of port values
72 };
73
74 static int
abs_cmp(const void * a,const void * b,void * user_data)75 abs_cmp(const void* a, const void* b, void* user_data)
76 {
77 return strcmp(((const PathMap*)a)->abs, ((const PathMap*)b)->abs);
78 }
79
80 static int
rel_cmp(const void * a,const void * b,void * user_data)81 rel_cmp(const void* a, const void* b, void* user_data)
82 {
83 return strcmp(((const PathMap*)a)->rel, ((const PathMap*)b)->rel);
84 }
85
86 static int
property_cmp(const void * a,const void * b)87 property_cmp(const void* a, const void* b)
88 {
89 return ((const Property*)a)->key - ((const Property*)b)->key;
90 }
91
92 static int
value_cmp(const void * a,const void * b)93 value_cmp(const void* a, const void* b)
94 {
95 return strcmp(((const PortValue*)a)->symbol,
96 ((const PortValue*)b)->symbol);
97 }
98
99 static void
path_rel_free(void * ptr)100 path_rel_free(void* ptr)
101 {
102 free(((PathMap*)ptr)->abs);
103 free(((PathMap*)ptr)->rel);
104 free(ptr);
105 }
106
107 static PortValue*
append_port_value(LilvState * state,const char * port_symbol,const void * value,uint32_t size,uint32_t type)108 append_port_value(LilvState* state,
109 const char* port_symbol,
110 const void* value,
111 uint32_t size,
112 uint32_t type)
113 {
114 PortValue* pv = NULL;
115 if (value) {
116 state->values = (PortValue*)realloc(
117 state->values, (++state->n_values) * sizeof(PortValue));
118 pv = &state->values[state->n_values - 1];
119 pv->symbol = lilv_strdup(port_symbol);
120 pv->value = malloc(size);
121 pv->size = size;
122 pv->type = type;
123 memcpy(pv->value, value, size);
124 }
125 return pv;
126 }
127
128 static const char*
lilv_state_rel2abs(const LilvState * state,const char * path)129 lilv_state_rel2abs(const LilvState* state, const char* path)
130 {
131 ZixTreeIter* iter = NULL;
132 const PathMap key = { NULL, (char*)path };
133 if (state->rel2abs && !zix_tree_find(state->rel2abs, &key, &iter)) {
134 return ((const PathMap*)zix_tree_get(iter))->abs;
135 }
136 return path;
137 }
138
139 static void
append_property(LilvState * state,PropertyArray * array,uint32_t key,const void * value,size_t size,uint32_t type,uint32_t flags)140 append_property(LilvState* state,
141 PropertyArray* array,
142 uint32_t key,
143 const void* value,
144 size_t size,
145 uint32_t type,
146 uint32_t flags)
147 {
148 array->props = (Property*)realloc(
149 array->props, (++array->n) * sizeof(Property));
150
151 Property* const prop = &array->props[array->n - 1];
152 if ((flags & LV2_STATE_IS_POD) || type == state->atom_Path) {
153 prop->value = malloc(size);
154 memcpy(prop->value, value, size);
155 } else {
156 prop->value = (void*)value;
157 }
158
159 prop->size = size;
160 prop->key = key;
161 prop->type = type;
162 prop->flags = flags;
163 }
164
165 static LV2_State_Status
store_callback(LV2_State_Handle handle,uint32_t key,const void * value,size_t size,uint32_t type,uint32_t flags)166 store_callback(LV2_State_Handle handle,
167 uint32_t key,
168 const void* value,
169 size_t size,
170 uint32_t type,
171 uint32_t flags)
172 {
173 LilvState* const state = (LilvState*)handle;
174 append_property(state, &state->props, key, value, size, type, flags);
175 return LV2_STATE_SUCCESS;
176 }
177
178 static const void*
retrieve_callback(LV2_State_Handle handle,uint32_t key,size_t * size,uint32_t * type,uint32_t * flags)179 retrieve_callback(LV2_State_Handle handle,
180 uint32_t key,
181 size_t* size,
182 uint32_t* type,
183 uint32_t* flags)
184 {
185 const LilvState* const state = (LilvState*)handle;
186 const Property search_key = { NULL, 0, key, 0, 0 };
187 const Property* const prop = (Property*)bsearch(
188 &search_key, state->props.props, state->props.n,
189 sizeof(Property), property_cmp);
190
191 if (prop) {
192 *size = prop->size;
193 *type = prop->type;
194 *flags = prop->flags;
195 return prop->value;
196 }
197 return NULL;
198 }
199
200 static bool
lilv_state_has_path(const char * path,void * state)201 lilv_state_has_path(const char* path, void* state)
202 {
203 return lilv_state_rel2abs((LilvState*)state, path) != path;
204 }
205
206 static char*
make_path(LV2_State_Make_Path_Handle handle,const char * path)207 make_path(LV2_State_Make_Path_Handle handle, const char* path)
208 {
209 LilvState* state = (LilvState*)handle;
210 if (!lilv_path_exists(state->dir, NULL)) {
211 lilv_mkdir_p(state->dir);
212 }
213
214 return lilv_path_join(state->dir, path);
215 }
216
217 static char*
abstract_path(LV2_State_Map_Path_Handle handle,const char * abs_path)218 abstract_path(LV2_State_Map_Path_Handle handle,
219 const char* abs_path)
220 {
221 LilvState* state = (LilvState*)handle;
222 char* path = NULL;
223 char* real_path = lilv_realpath(abs_path);
224 const PathMap key = { (char*)real_path, NULL };
225 ZixTreeIter* iter = NULL;
226
227 if (abs_path[0] == '\0') {
228 return lilv_strdup(abs_path);
229 } else if (!zix_tree_find(state->abs2rel, &key, &iter)) {
230 // Already mapped path in a previous call
231 PathMap* pm = (PathMap*)zix_tree_get(iter);
232 free(real_path);
233 return lilv_strdup(pm->rel);
234 } else if (lilv_path_is_child(real_path, state->dir)) {
235 // File in state directory (loaded, or created by plugin during save)
236 path = lilv_path_relative_to(real_path, state->dir);
237 } else if (lilv_path_is_child(real_path, state->file_dir)) {
238 // File created by plugin earlier
239 path = lilv_path_relative_to(real_path, state->file_dir);
240 if (state->copy_dir) {
241 if (!lilv_path_exists(state->copy_dir, NULL)) {
242 lilv_mkdir_p(state->copy_dir);
243 }
244 char* cpath = lilv_path_join(state->copy_dir, path);
245 char* copy = lilv_get_latest_copy(real_path, cpath);
246 if (!copy || !lilv_file_equals(real_path, copy)) {
247 // No recent enough copy, make a new one
248 free(copy);
249 copy = lilv_find_free_path(cpath, lilv_path_exists, NULL);
250 const int st = lilv_copy_file(real_path, copy);
251 if (st) {
252 LILV_ERRORF("Error copying state file %s (%s)\n",
253 copy, strerror(st));
254 }
255 }
256 free(real_path);
257 free(cpath);
258
259 // Refer to the latest copy in plugin state
260 real_path = copy;
261 }
262 } else if (state->link_dir) {
263 // New path outside state directory, make a link
264 const char* slash = strrchr(real_path, '/');
265 const char* name = slash ? (slash + 1) : real_path;
266
267 // Find a free name in the (virtual) state directory
268 path = lilv_find_free_path(name, lilv_state_has_path, state);
269 } else {
270 // No link directory, preserve absolute path
271 path = lilv_strdup(abs_path);
272 }
273
274 // Add record to path mapping
275 PathMap* pm = (PathMap*)malloc(sizeof(PathMap));
276 pm->abs = real_path;
277 pm->rel = lilv_strdup(path);
278 zix_tree_insert(state->abs2rel, pm, NULL);
279 zix_tree_insert(state->rel2abs, pm, NULL);
280
281 return path;
282 }
283
284 static char*
absolute_path(LV2_State_Map_Path_Handle handle,const char * state_path)285 absolute_path(LV2_State_Map_Path_Handle handle,
286 const char* state_path)
287 {
288 LilvState* state = (LilvState*)handle;
289 char* path = NULL;
290 if (lilv_path_is_absolute(state_path)) {
291 // Absolute path, return identical path
292 path = lilv_strdup(state_path);
293 } else if (state->dir) {
294 // Relative path inside state directory
295 path = lilv_path_join(state->dir, state_path);
296 } else {
297 // State has not been saved, unmap
298 path = lilv_strdup(lilv_state_rel2abs(state, state_path));
299 }
300
301 return path;
302 }
303
304 /** Return a new features array which is `feature` added to `features`. */
305 static const LV2_Feature**
add_features(const LV2_Feature * const * features,const LV2_Feature * map,const LV2_Feature * make)306 add_features(const LV2_Feature *const * features,
307 const LV2_Feature* map, const LV2_Feature* make)
308 {
309 size_t n_features = 0;
310 for (; features && features[n_features]; ++n_features) {}
311
312 const LV2_Feature** ret = (const LV2_Feature**)calloc(
313 n_features + 3, sizeof(LV2_Feature*));
314
315 if (features) {
316 memcpy(ret, features, n_features * sizeof(LV2_Feature*));
317 }
318
319 ret[n_features] = map;
320 ret[n_features + 1] = make;
321 return ret;
322 }
323
324 static char*
absolute_dir(const char * path)325 absolute_dir(const char* path)
326 {
327 char* abs_path = lilv_path_absolute(path);
328 char* base = lilv_path_join(abs_path, NULL);
329 free(abs_path);
330 return base;
331 }
332
333 static const char*
state_strerror(LV2_State_Status st)334 state_strerror(LV2_State_Status st)
335 {
336 switch (st) {
337 case LV2_STATE_SUCCESS: return "Completed successfully";
338 case LV2_STATE_ERR_BAD_TYPE: return "Unsupported type";
339 case LV2_STATE_ERR_BAD_FLAGS: return "Unsupported flags";
340 case LV2_STATE_ERR_NO_FEATURE: return "Missing features";
341 case LV2_STATE_ERR_NO_PROPERTY: return "Missing property";
342 default: return "Unknown error";
343 }
344 }
345
346 LILV_API LilvState*
lilv_state_new_from_instance(const LilvPlugin * plugin,LilvInstance * instance,LV2_URID_Map * map,const char * file_dir,const char * copy_dir,const char * link_dir,const char * save_dir,LilvGetPortValueFunc get_value,void * user_data,uint32_t flags,const LV2_Feature * const * features)347 lilv_state_new_from_instance(const LilvPlugin* plugin,
348 LilvInstance* instance,
349 LV2_URID_Map* map,
350 const char* file_dir,
351 const char* copy_dir,
352 const char* link_dir,
353 const char* save_dir,
354 LilvGetPortValueFunc get_value,
355 void* user_data,
356 uint32_t flags,
357 const LV2_Feature *const * features)
358 {
359 const LV2_Feature** sfeatures = NULL;
360 LilvWorld* const world = plugin->world;
361 LilvState* const state = (LilvState*)calloc(1, sizeof(LilvState));
362 state->plugin_uri = lilv_node_duplicate(lilv_plugin_get_uri(plugin));
363 state->abs2rel = zix_tree_new(false, abs_cmp, NULL, path_rel_free);
364 state->rel2abs = zix_tree_new(false, rel_cmp, NULL, NULL);
365 state->file_dir = file_dir ? absolute_dir(file_dir) : NULL;
366 state->copy_dir = copy_dir ? absolute_dir(copy_dir) : NULL;
367 state->link_dir = link_dir ? absolute_dir(link_dir) : NULL;
368 state->dir = save_dir ? absolute_dir(save_dir) : NULL;
369 state->atom_Path = map->map(map->handle, LV2_ATOM__Path);
370
371 LV2_State_Map_Path pmap = { state, abstract_path, absolute_path };
372 LV2_Feature pmap_feature = { LV2_STATE__mapPath, &pmap };
373 LV2_State_Make_Path pmake = { state, make_path };
374 LV2_Feature pmake_feature = { LV2_STATE__makePath, &pmake };
375 features = sfeatures = add_features(features, &pmap_feature,
376 save_dir ? &pmake_feature : NULL);
377
378 // Store port values
379 if (get_value) {
380 LilvNode* lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT);
381 LilvNode* lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT);
382 for (uint32_t i = 0; i < plugin->num_ports; ++i) {
383 const LilvPort* const port = plugin->ports[i];
384 if (lilv_port_is_a(plugin, port, lv2_ControlPort)
385 && lilv_port_is_a(plugin, port, lv2_InputPort)) {
386 uint32_t size, type;
387 const char* sym = lilv_node_as_string(port->symbol);
388 const void* value = get_value(sym, user_data, &size, &type);
389 append_port_value(state, sym, value, size, type);
390 }
391 }
392 lilv_node_free(lv2_ControlPort);
393 lilv_node_free(lv2_InputPort);
394 }
395
396 // Store properties
397 const LV2_Descriptor* desc = instance->lv2_descriptor;
398 const LV2_State_Interface* iface = (desc->extension_data)
399 ? (const LV2_State_Interface*)desc->extension_data(LV2_STATE__interface)
400 : NULL;
401
402 if (iface) {
403 LV2_State_Status st = iface->save(
404 instance->lv2_handle, store_callback, state, flags, features);
405 if (st) {
406 LILV_ERRORF("Error saving plugin state: %s\n", state_strerror(st));
407 free(state->props.props);
408 state->props.props = NULL;
409 state->props.n = 0;
410 } else {
411 qsort(state->props.props, state->props.n, sizeof(Property), property_cmp);
412 }
413 }
414
415 qsort(state->values, state->n_values, sizeof(PortValue), value_cmp);
416
417 free(sfeatures);
418 return state;
419 }
420
421 LILV_API void
lilv_state_emit_port_values(const LilvState * state,LilvSetPortValueFunc set_value,void * user_data)422 lilv_state_emit_port_values(const LilvState* state,
423 LilvSetPortValueFunc set_value,
424 void* user_data)
425 {
426 for (uint32_t i = 0; i < state->n_values; ++i) {
427 const PortValue* val = &state->values[i];
428 set_value(val->symbol, user_data, val->value, val->size, val->type);
429 }
430 }
431
432 LILV_API void
lilv_state_restore(const LilvState * state,const LV2_State_Interface * iface,LV2_Handle handle,LilvSetPortValueFunc set_value,void * user_data,uint32_t flags,const LV2_Feature * const * features)433 lilv_state_restore(const LilvState* state,
434 const LV2_State_Interface* iface,
435 LV2_Handle handle,
436 LilvSetPortValueFunc set_value,
437 void* user_data,
438 uint32_t flags,
439 const LV2_Feature *const * features)
440 {
441 if (iface && iface->restore) {
442 iface->restore(handle, retrieve_callback,
443 (LV2_State_Handle)state, flags, features);
444 }
445
446 if (set_value) {
447 lilv_state_emit_port_values(state, set_value, user_data);
448 }
449 }
450
451 static LilvState*
new_state_from_model(LilvWorld * world,LV2_URID_Map * map,SordModel * model,const SordNode * node,const char * dir)452 new_state_from_model(LilvWorld* world,
453 LV2_URID_Map* map,
454 SordModel* model,
455 const SordNode* node,
456 const char* dir)
457 {
458 // Check that we know at least something about this state subject
459 if (!sord_ask(model, node, 0, 0, 0)) {
460 return NULL;
461 }
462
463 // Allocate state
464 LilvState* const state = (LilvState*)calloc(1, sizeof(LilvState));
465 state->dir = lilv_strdup(dir);
466 state->atom_Path = map->map(map->handle, LV2_ATOM__Path);
467 state->uri = lilv_node_new_from_node(world, node);
468
469 // Get the plugin URI this state applies to
470 SordIter* i = sord_search(model, node, world->uris.lv2_appliesTo, 0, 0);
471 if (i) {
472 const SordNode* object = sord_iter_get_node(i, SORD_OBJECT);
473 const SordNode* graph = sord_iter_get_node(i, SORD_GRAPH);
474 state->plugin_uri = lilv_node_new_from_node(world, object);
475 if (!state->dir && graph) {
476 state->dir = lilv_strdup((const char*)sord_node_get_string(graph));
477 }
478 sord_iter_free(i);
479 } else if (sord_ask(model,
480 node,
481 world->uris.rdf_a,
482 world->uris.lv2_Plugin, 0)) {
483 // Loading plugin description as state (default state)
484 state->plugin_uri = lilv_node_new_from_node(world, node);
485 } else {
486 LILV_ERRORF("State %s missing lv2:appliesTo property\n",
487 sord_node_get_string(node));
488 }
489
490 // Get the state label
491 i = sord_search(model, node, world->uris.rdfs_label, NULL, NULL);
492 if (i) {
493 const SordNode* object = sord_iter_get_node(i, SORD_OBJECT);
494 const SordNode* graph = sord_iter_get_node(i, SORD_GRAPH);
495 state->label = lilv_strdup((const char*)sord_node_get_string(object));
496 if (!state->dir && graph) {
497 state->dir = lilv_strdup((const char*)sord_node_get_string(graph));
498 }
499 sord_iter_free(i);
500 }
501
502 Sratom* sratom = sratom_new(map);
503 SerdChunk chunk = { NULL, 0 };
504 LV2_Atom_Forge forge;
505 lv2_atom_forge_init(&forge, map);
506 lv2_atom_forge_set_sink(
507 &forge, sratom_forge_sink, sratom_forge_deref, &chunk);
508
509 // Get port values
510 SordIter* ports = sord_search(model, node, world->uris.lv2_port, 0, 0);
511 FOREACH_MATCH(ports) {
512 const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT);
513
514 SordNode* label = sord_get(model, port, world->uris.rdfs_label, 0, 0);
515 SordNode* symbol = sord_get(model, port, world->uris.lv2_symbol, 0, 0);
516 SordNode* value = sord_get(model, port, world->uris.pset_value, 0, 0);
517 if (!value) {
518 value = sord_get(model, port, world->uris.lv2_default, 0, 0);
519 }
520 if (!symbol) {
521 LILV_ERRORF("State `%s' port missing symbol.\n",
522 sord_node_get_string(node));
523 } else if (value) {
524 chunk.len = 0;
525 sratom_read(sratom, &forge, world->world, model, value);
526 const LV2_Atom* atom = (const LV2_Atom*)chunk.buf;
527
528 append_port_value(state,
529 (const char*)sord_node_get_string(symbol),
530 LV2_ATOM_BODY_CONST(atom),
531 atom->size, atom->type);
532
533 if (label) {
534 lilv_state_set_label(state,
535 (const char*)sord_node_get_string(label));
536 }
537 }
538 sord_node_free(world->world, value);
539 sord_node_free(world->world, symbol);
540 sord_node_free(world->world, label);
541 }
542 sord_iter_free(ports);
543
544 // Get properties
545 SordNode* statep = sord_new_uri(world->world, USTR(LV2_STATE__state));
546 SordNode* state_node = sord_get(model, node, statep, NULL, NULL);
547 if (state_node) {
548 SordIter* props = sord_search(model, state_node, 0, 0, 0);
549 FOREACH_MATCH(props) {
550 const SordNode* p = sord_iter_get_node(props, SORD_PREDICATE);
551 const SordNode* o = sord_iter_get_node(props, SORD_OBJECT);
552 const char* key = (const char*)sord_node_get_string(p);
553
554 chunk.len = 0;
555 lv2_atom_forge_set_sink(
556 &forge, sratom_forge_sink, sratom_forge_deref, &chunk);
557
558 sratom_read(sratom, &forge, world->world, model, o);
559 const LV2_Atom* atom = (const LV2_Atom*)chunk.buf;
560 uint32_t flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
561 Property prop = { NULL, 0, 0, 0, flags };
562
563 prop.key = map->map(map->handle, key);
564 prop.type = atom->type;
565 prop.size = atom->size;
566 prop.value = malloc(atom->size);
567 memcpy(prop.value, LV2_ATOM_BODY_CONST(atom), atom->size);
568 if (atom->type == forge.Path) {
569 prop.flags = LV2_STATE_IS_POD;
570 }
571
572 if (prop.value) {
573 state->props.props = (Property*)realloc(
574 state->props.props, (++state->props.n) * sizeof(Property));
575 state->props.props[state->props.n - 1] = prop;
576 }
577 }
578 sord_iter_free(props);
579 }
580 sord_node_free(world->world, state_node);
581 sord_node_free(world->world, statep);
582
583 free((void*)chunk.buf);
584 sratom_free(sratom);
585
586 if (state->props.props) {
587 qsort(state->props.props, state->props.n, sizeof(Property), property_cmp);
588 }
589 if (state->values) {
590 qsort(state->values, state->n_values, sizeof(PortValue), value_cmp);
591 }
592
593 return state;
594 }
595
596 LILV_API LilvState*
lilv_state_new_from_world(LilvWorld * world,const LV2_URID_Map * map,const LilvNode * node)597 lilv_state_new_from_world(LilvWorld* world,
598 const LV2_URID_Map* map,
599 const LilvNode* node)
600 {
601 if (!lilv_node_is_uri(node) && !lilv_node_is_blank(node)) {
602 LILV_ERRORF("Subject `%s' is not a URI or blank node.\n",
603 lilv_node_as_string(node));
604 return NULL;
605 }
606
607 return new_state_from_model(world, map, world->model, node->node, NULL);
608 }
609
610 LILV_API LilvState*
lilv_state_new_from_file(LilvWorld * world,const LV2_URID_Map * map,const LilvNode * subject,const char * path)611 lilv_state_new_from_file(LilvWorld* world,
612 const LV2_URID_Map* map,
613 const LilvNode* subject,
614 const char* path)
615 {
616 if (subject && !lilv_node_is_uri(subject)
617 && !lilv_node_is_blank(subject)) {
618 LILV_ERRORF("Subject `%s' is not a URI or blank node.\n",
619 lilv_node_as_string(subject));
620 return NULL;
621 }
622
623 uint8_t* abs_path = (uint8_t*)lilv_path_absolute(path);
624 SerdNode node = serd_node_new_file_uri(abs_path, NULL, NULL, 0);
625 SerdEnv* env = serd_env_new(&node);
626 SordModel* model = sord_new(world->world, SORD_SPO, false);
627 SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
628
629 serd_reader_read_file(reader, node.buf);
630
631 SordNode* subject_node = (subject)
632 ? subject->node
633 : sord_node_from_serd_node(world->world, env, &node, NULL, NULL);
634
635 char* dirname = lilv_dirname(path);
636 char* real_path = lilv_realpath(dirname);
637 LilvState* state = new_state_from_model(
638 world, map, model, subject_node, real_path);
639 free(dirname);
640 free(real_path);
641
642 serd_node_free(&node);
643 free(abs_path);
644 serd_reader_free(reader);
645 sord_free(model);
646 serd_env_free(env);
647 return state;
648 }
649
650 static void
set_prefixes(SerdEnv * env)651 set_prefixes(SerdEnv* env)
652 {
653 #define SET_PSET(e, p, u) serd_env_set_prefix_from_strings(e, p, u)
654 SET_PSET(env, USTR("atom"), USTR(LV2_ATOM_PREFIX));
655 SET_PSET(env, USTR("lv2"), USTR(LV2_CORE_PREFIX));
656 SET_PSET(env, USTR("pset"), USTR(LV2_PRESETS_PREFIX));
657 SET_PSET(env, USTR("rdf"), USTR(LILV_NS_RDF));
658 SET_PSET(env, USTR("rdfs"), USTR(LILV_NS_RDFS));
659 SET_PSET(env, USTR("state"), USTR(LV2_STATE_PREFIX));
660 SET_PSET(env, USTR("xsd"), USTR(LILV_NS_XSD));
661 }
662
663 LILV_API LilvState*
lilv_state_new_from_string(LilvWorld * world,const LV2_URID_Map * map,const char * str)664 lilv_state_new_from_string(LilvWorld* world,
665 const LV2_URID_Map* map,
666 const char* str)
667 {
668 if (!str) {
669 return NULL;
670 }
671
672 SerdNode base = SERD_NODE_NULL;
673 SerdEnv* env = serd_env_new(&base);
674 SordModel* model = sord_new(world->world, SORD_SPO|SORD_OPS, false);
675 SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
676
677 set_prefixes(env);
678 serd_reader_read_string(reader, USTR(str));
679
680 SordNode* o = sord_new_uri(world->world, USTR(LV2_PRESETS__Preset));
681 SordNode* s = sord_get(model, NULL, world->uris.rdf_a, o, NULL);
682
683 LilvState* state = new_state_from_model(world, map, model, s, NULL);
684
685 sord_node_free(world->world, s);
686 sord_node_free(world->world, o);
687 serd_reader_free(reader);
688 sord_free(model);
689 serd_env_free(env);
690
691 return state;
692 }
693
694 static SerdWriter*
ttl_writer(SerdSink sink,void * stream,const SerdNode * base,SerdEnv ** new_env)695 ttl_writer(SerdSink sink, void* stream, const SerdNode* base, SerdEnv** new_env)
696 {
697 SerdURI base_uri = SERD_URI_NULL;
698 if (base && base->buf) {
699 serd_uri_parse(base->buf, &base_uri);
700 }
701
702 SerdEnv* env = *new_env ? *new_env : serd_env_new(base);
703 set_prefixes(env);
704
705 SerdWriter* writer = serd_writer_new(
706 SERD_TURTLE,
707 (SerdStyle)(SERD_STYLE_RESOLVED |
708 SERD_STYLE_ABBREVIATED|
709 SERD_STYLE_CURIED),
710 env,
711 &base_uri,
712 sink,
713 stream);
714
715 if (!*new_env) {
716 *new_env = env;
717 }
718
719 return writer;
720 }
721
722 static SerdWriter*
ttl_file_writer(FILE * fd,const SerdNode * node,SerdEnv ** env)723 ttl_file_writer(FILE* fd, const SerdNode* node, SerdEnv** env)
724 {
725 SerdWriter* writer = ttl_writer(serd_file_sink, fd, node, env);
726
727 fseek(fd, 0, SEEK_END);
728 if (ftell(fd) == 0) {
729 serd_env_foreach(*env, (SerdPrefixSink)serd_writer_set_prefix, writer);
730 } else {
731 fprintf(fd, "\n");
732 }
733
734 return writer;
735 }
736
737 static void
add_to_model(SordWorld * world,SerdEnv * env,SordModel * model,const SerdNode s,const SerdNode p,const SerdNode o)738 add_to_model(SordWorld* world,
739 SerdEnv* env,
740 SordModel* model,
741 const SerdNode s,
742 const SerdNode p,
743 const SerdNode o)
744 {
745 SordNode* ss = sord_node_from_serd_node(world, env, &s, NULL, NULL);
746 SordNode* sp = sord_node_from_serd_node(world, env, &p, NULL, NULL);
747 SordNode* so = sord_node_from_serd_node(world, env, &o, NULL, NULL);
748
749 SordQuad quad = { ss, sp, so, NULL };
750 sord_add(model, quad);
751
752 sord_node_free(world, ss);
753 sord_node_free(world, sp);
754 sord_node_free(world, so);
755 }
756
757 static void
remove_manifest_entry(SordWorld * world,SordModel * model,const char * subject)758 remove_manifest_entry(SordWorld* world, SordModel* model, const char* subject)
759 {
760 SordNode* s = sord_new_uri(world, USTR(subject));
761 SordIter* i = sord_search(model, s, NULL, NULL, NULL);
762 while (!sord_iter_end(i)) {
763 sord_erase(model, i);
764 }
765 sord_iter_free(i);
766 sord_node_free(world, s);
767 }
768
769 static int
add_state_to_manifest(LilvWorld * lworld,const LilvNode * plugin_uri,const char * manifest_path,const char * state_uri,const char * state_path)770 add_state_to_manifest(LilvWorld* lworld,
771 const LilvNode* plugin_uri,
772 const char* manifest_path,
773 const char* state_uri,
774 const char* state_path)
775 {
776 SordWorld* world = lworld->world;
777 SerdNode manifest = serd_node_new_file_uri(USTR(manifest_path), 0, 0, 0);
778 SerdNode file = serd_node_new_file_uri(USTR(state_path), 0, 0, 0);
779 SerdEnv* env = serd_env_new(&manifest);
780 SordModel* model = sord_new(world, SORD_SPO, false);
781
782 FILE* rfd = fopen(manifest_path, "r");
783 if (rfd) {
784 // Read manifest into model
785 SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
786 lilv_flock(rfd, true);
787 serd_reader_read_file_handle(reader, rfd, manifest.buf);
788 serd_reader_free(reader);
789 }
790
791 // Choose state URI (use file URI if not given)
792 if (!state_uri) {
793 state_uri = (const char*)file.buf;
794 }
795
796 // Remove any existing manifest entries for this state
797 remove_manifest_entry(world, model, state_uri);
798
799 // Add manifest entry for this state to model
800 SerdNode s = serd_node_from_string(SERD_URI, USTR(state_uri));
801
802 // <state> a pset:Preset
803 add_to_model(world, env, model,
804 s,
805 serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")),
806 serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset)));
807
808 // <state> a pset:Preset
809 add_to_model(world, env, model,
810 s,
811 serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")),
812 serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset)));
813
814 // <state> rdfs:seeAlso <file>
815 add_to_model(world, env, model,
816 s,
817 serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso")),
818 file);
819
820 // <state> lv2:appliesTo <plugin>
821 add_to_model(world, env, model,
822 s,
823 serd_node_from_string(SERD_URI, USTR(LV2_CORE__appliesTo)),
824 serd_node_from_string(SERD_URI,
825 USTR(lilv_node_as_string(plugin_uri))));
826
827 // Write manifest model to file
828 FILE* wfd = fopen(manifest_path, "w");
829 if (wfd) {
830 SerdWriter* writer = ttl_file_writer(wfd, &manifest, &env);
831 sord_write(model, writer, NULL);
832 serd_writer_free(writer);
833 fclose(wfd);
834 } else {
835 LILV_ERRORF("Failed to open %s for writing (%s)\n",
836 manifest_path, strerror(errno));
837 }
838
839 sord_free(model);
840 serd_node_free(&file);
841 serd_node_free(&manifest);
842 serd_env_free(env);
843
844 if (rfd) {
845 lilv_flock(rfd, false);
846 fclose(rfd);
847 }
848
849 return 0;
850 }
851
852 static bool
link_exists(const char * path,void * data)853 link_exists(const char* path, void* data)
854 {
855 const char* target = (const char*)data;
856 if (!lilv_path_exists(path, NULL)) {
857 return false;
858 }
859 char* real_path = lilv_realpath(path);
860 bool matches = !strcmp(real_path, target);
861 free(real_path);
862 return !matches;
863 }
864
865 static void
write_property_array(const LilvState * state,const PropertyArray * array,Sratom * sratom,uint32_t flags,const SerdNode * subject,LV2_URID_Unmap * unmap,const char * dir)866 write_property_array(const LilvState* state,
867 const PropertyArray* array,
868 Sratom* sratom,
869 uint32_t flags,
870 const SerdNode* subject,
871 LV2_URID_Unmap* unmap,
872 const char* dir)
873 {
874 for (uint32_t i = 0; i < array->n; ++i) {
875 Property* prop = &array->props[i];
876 const char* key = unmap->unmap(unmap->handle, prop->key);
877
878 const SerdNode p = serd_node_from_string(SERD_URI, USTR(key));
879 if (prop->type == state->atom_Path && !dir) {
880 const char* path = (const char*)prop->value;
881 const char* abs_path = lilv_state_rel2abs(state, path);
882 LILV_WARNF("Writing absolute path %s\n", abs_path);
883 sratom_write(sratom, unmap, flags,
884 subject, &p, prop->type,
885 strlen(abs_path) + 1, abs_path);
886 } else if (prop->flags & LV2_STATE_IS_POD ||
887 prop->type == state->atom_Path) {
888 sratom_write(sratom, unmap, flags,
889 subject, &p, prop->type, prop->size, prop->value);
890 } else {
891 LILV_WARNF("Lost non-POD property <%s> on save\n", key);
892 }
893 }
894 }
895
896 static int
lilv_state_write(LilvWorld * world,LV2_URID_Map * map,LV2_URID_Unmap * unmap,const LilvState * state,SerdWriter * writer,const char * uri,const char * dir)897 lilv_state_write(LilvWorld* world,
898 LV2_URID_Map* map,
899 LV2_URID_Unmap* unmap,
900 const LilvState* state,
901 SerdWriter* writer,
902 const char* uri,
903 const char* dir)
904 {
905 SerdNode lv2_appliesTo = serd_node_from_string(
906 SERD_CURIE, USTR("lv2:appliesTo"));
907
908 const SerdNode* plugin_uri = sord_node_to_serd_node(
909 state->plugin_uri->node);
910
911 SerdNode subject = serd_node_from_string(SERD_URI, USTR(uri ? uri : ""));
912
913 // <subject> a pset:Preset
914 SerdNode p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type"));
915 SerdNode o = serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset));
916 serd_writer_write_statement(writer, 0, NULL,
917 &subject, &p, &o, NULL, NULL);
918
919 // <subject> lv2:appliesTo <http://example.org/plugin>
920 serd_writer_write_statement(writer, 0, NULL,
921 &subject,
922 &lv2_appliesTo,
923 plugin_uri, NULL, NULL);
924
925 // <subject> rdfs:label label
926 if (state->label) {
927 p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "label"));
928 o = serd_node_from_string(SERD_LITERAL, USTR(state->label));
929 serd_writer_write_statement(writer, 0,
930 NULL, &subject, &p, &o, NULL, NULL);
931 }
932
933 SerdEnv* env = serd_writer_get_env(writer);
934 const SerdNode* base = serd_env_get_base_uri(env, NULL);
935
936 Sratom* sratom = sratom_new(map);
937 sratom_set_sink(sratom, (const char*)base->buf,
938 (SerdStatementSink)serd_writer_write_statement,
939 (SerdEndSink)serd_writer_end_anon,
940 writer);
941
942 // Write metadata
943 sratom_set_pretty_numbers(sratom, false); // Use precise types
944 write_property_array(state, &state->metadata, sratom, 0,
945 &subject, unmap, dir);
946
947 // Write port values
948 sratom_set_pretty_numbers(sratom, true); // Use pretty numbers
949 for (uint32_t i = 0; i < state->n_values; ++i) {
950 PortValue* const value = &state->values[i];
951
952 const SerdNode port = serd_node_from_string(
953 SERD_BLANK, USTR(value->symbol));
954
955 // <> lv2:port _:symbol
956 p = serd_node_from_string(SERD_URI, USTR(LV2_CORE__port));
957 serd_writer_write_statement(writer, SERD_ANON_O_BEGIN,
958 NULL, &subject, &p, &port, NULL, NULL);
959
960 // _:symbol lv2:symbol "symbol"
961 p = serd_node_from_string(SERD_URI, USTR(LV2_CORE__symbol));
962 o = serd_node_from_string(SERD_LITERAL, USTR(value->symbol));
963 serd_writer_write_statement(writer, SERD_ANON_CONT,
964 NULL, &port, &p, &o, NULL, NULL);
965
966 // _:symbol pset:value value
967 p = serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__value));
968 sratom_write(sratom, unmap, SERD_ANON_CONT, &port, &p,
969 value->type, value->size, value->value);
970
971 serd_writer_end_anon(writer, &port);
972 }
973
974 // Write properties
975 const SerdNode body = serd_node_from_string(SERD_BLANK, USTR("body"));
976 if (state->props.n > 0) {
977 p = serd_node_from_string(SERD_URI, USTR(LV2_STATE__state));
978 serd_writer_write_statement(writer, SERD_ANON_O_BEGIN, NULL,
979 &subject, &p, &body, NULL, NULL);
980 }
981 sratom_set_pretty_numbers(sratom, false); // Use precise types
982 write_property_array(state, &state->props, sratom, SERD_ANON_CONT,
983 &body, unmap, dir);
984
985 if (state->props.n > 0) {
986 serd_writer_end_anon(writer, &body);
987 }
988
989 sratom_free(sratom);
990 return 0;
991 }
992
993 static void
lilv_state_make_links(const LilvState * state,const char * dir)994 lilv_state_make_links(const LilvState* state, const char* dir)
995 {
996 // Create symlinks to files
997 for (ZixTreeIter* i = zix_tree_begin(state->abs2rel);
998 i != zix_tree_end(state->abs2rel);
999 i = zix_tree_iter_next(i)) {
1000 const PathMap* pm = (const PathMap*)zix_tree_get(i);
1001
1002 char* path = lilv_path_join(dir, pm->rel);
1003 if (lilv_path_is_child(pm->abs, state->copy_dir)
1004 && strcmp(state->copy_dir, dir)) {
1005 // Link directly to snapshot in the copy directory
1006 char* target = lilv_path_relative_to(pm->abs, dir);
1007 lilv_symlink(target, path);
1008 free(target);
1009 } else if (!lilv_path_is_child(pm->abs, dir)) {
1010 const char* link_dir = state->link_dir ? state->link_dir : dir;
1011 char* pat = lilv_path_join(link_dir, pm->rel);
1012 if (!strcmp(dir, link_dir)) {
1013 // Link directory is save directory, make link at exact path
1014 remove(pat);
1015 lilv_symlink(pm->abs, pat);
1016 } else {
1017 // Make a link in the link directory to external file
1018 char* lpath = lilv_find_free_path(pat, link_exists, pm->abs);
1019 if (!lilv_path_exists(lpath, NULL)) {
1020 lilv_symlink(pm->abs, lpath);
1021 }
1022
1023 // Make a link in the save directory to the external link
1024 char* target = lilv_path_relative_to(lpath, dir);
1025 lilv_symlink(target, path);
1026 free(target);
1027 free(lpath);
1028 }
1029 free(pat);
1030 }
1031 free(path);
1032 }
1033 }
1034
1035 LILV_API int
lilv_state_save(LilvWorld * world,LV2_URID_Map * map,LV2_URID_Unmap * unmap,const LilvState * state,const char * uri,const char * dir,const char * filename)1036 lilv_state_save(LilvWorld* world,
1037 LV2_URID_Map* map,
1038 LV2_URID_Unmap* unmap,
1039 const LilvState* state,
1040 const char* uri,
1041 const char* dir,
1042 const char* filename)
1043 {
1044 if (!filename || !dir || lilv_mkdir_p(dir)) {
1045 return 1;
1046 }
1047
1048 char* abs_dir = absolute_dir(dir);
1049 char* const path = lilv_path_join(abs_dir, filename);
1050 FILE* fd = fopen(path, "w");
1051 if (!fd) {
1052 LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno));
1053 free(abs_dir);
1054 free(path);
1055 return 4;
1056 }
1057
1058 // Create symlinks to files if necessary
1059 lilv_state_make_links(state, abs_dir);
1060
1061 // Write state to Turtle file
1062 SerdNode file = serd_node_new_file_uri(USTR(path), NULL, NULL, false);
1063 SerdNode node = uri ? serd_node_from_string(SERD_URI, USTR(uri)) : file;
1064 SerdEnv* env = NULL;
1065 SerdWriter* ttl = ttl_file_writer(fd, &file, &env);
1066 int ret = lilv_state_write(
1067 world, map, unmap, state, ttl, (const char*)node.buf, dir);
1068
1069 // Set saved dir and uri (FIXME: const violation)
1070 SerdNode dir_uri = serd_node_new_file_uri(USTR(abs_dir), NULL, NULL, false);
1071 free(state->dir);
1072 lilv_node_free(state->uri);
1073 ((LilvState*)state)->dir = (char*)dir_uri.buf;
1074 ((LilvState*)state)->uri = lilv_new_uri(world, (const char*)node.buf);
1075
1076 serd_node_free(&file);
1077 serd_writer_free(ttl);
1078 serd_env_free(env);
1079 fclose(fd);
1080
1081 // Add entry to manifest
1082 char* const manifest = lilv_path_join(abs_dir, "manifest.ttl");
1083 add_state_to_manifest(world, state->plugin_uri, manifest, uri, path);
1084
1085 free(manifest);
1086 free(abs_dir);
1087 free(path);
1088 return ret;
1089 }
1090
1091 LILV_API char*
lilv_state_to_string(LilvWorld * world,LV2_URID_Map * map,LV2_URID_Unmap * unmap,const LilvState * state,const char * uri,const char * base_uri)1092 lilv_state_to_string(LilvWorld* world,
1093 LV2_URID_Map* map,
1094 LV2_URID_Unmap* unmap,
1095 const LilvState* state,
1096 const char* uri,
1097 const char* base_uri)
1098 {
1099 if (!uri) {
1100 LILV_ERROR("Attempt to serialise state with no URI\n");
1101 return NULL;
1102 }
1103
1104 SerdChunk chunk = { NULL, 0 };
1105 SerdEnv* env = NULL;
1106 SerdNode base = serd_node_from_string(SERD_URI, USTR(base_uri));
1107 SerdWriter* writer = ttl_writer(serd_chunk_sink, &chunk, &base, &env);
1108
1109 lilv_state_write(world, map, unmap, state, writer, uri, NULL);
1110
1111 serd_writer_free(writer);
1112 serd_env_free(env);
1113 return (char*)serd_chunk_sink_finish(&chunk);
1114 }
1115
1116 LILV_API int
lilv_state_delete(LilvWorld * world,const LilvState * state)1117 lilv_state_delete(LilvWorld* world,
1118 const LilvState* state)
1119 {
1120 if (!state->dir || !state->uri) {
1121 LILV_ERROR("Attempt to delete unsaved state\n");
1122 return -1;
1123 }
1124
1125 LilvNode* bundle = lilv_new_uri(world, state->dir);
1126 LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle);
1127 char* manifest_path = lilv_node_get_path(manifest, NULL);
1128 SordModel* model = sord_new(world->world, SORD_SPO, false);
1129
1130 {
1131 // Read manifest into temporary local model
1132 SerdEnv* env = serd_env_new(sord_node_to_serd_node(manifest->node));
1133 SerdReader* ttl = sord_new_reader(model, env, SERD_TURTLE, NULL);
1134 serd_reader_read_file(ttl, USTR(manifest_path));
1135 serd_reader_free(ttl);
1136 serd_env_free(env);
1137 }
1138
1139 SordNode* file = sord_get(
1140 model, state->uri->node, world->uris.rdfs_seeAlso, NULL, NULL);
1141 if (file) {
1142 // Remove state file
1143 char* path = lilv_file_uri_parse(
1144 (const char*)sord_node_get_string(file), NULL);
1145 if (unlink(path)) {
1146 LILV_ERRORF("Failed to remove %s (%s)\n", path, strerror(errno));
1147 }
1148 lilv_free(path);
1149 }
1150
1151 // Remove any existing manifest entries for this state
1152 remove_manifest_entry(
1153 world->world, model, lilv_node_as_string(state->uri));
1154 remove_manifest_entry(
1155 world->world, world->model, lilv_node_as_string(state->uri));
1156
1157 // Drop bundle from model
1158 lilv_world_unload_bundle(world, bundle);
1159
1160 if (sord_num_quads(model) == 0) {
1161 // Manifest is empty, attempt to remove bundle entirely
1162 if (unlink(manifest_path)) {
1163 LILV_ERRORF("Failed to remove %s (%s)\n",
1164 manifest_path, strerror(errno));
1165 }
1166 char* dir_path = lilv_file_uri_parse(state->dir, NULL);
1167 if (rmdir(dir_path)) {
1168 LILV_ERRORF("Failed to remove %s (%s)\n",
1169 dir_path, strerror(errno));
1170 }
1171 lilv_free(dir_path);
1172 } else {
1173 // Still something in the manifest, reload bundle
1174 lilv_world_load_bundle(world, bundle);
1175 }
1176
1177 sord_free(model);
1178 lilv_free(manifest_path);
1179 lilv_node_free(manifest);
1180 lilv_node_free(bundle);
1181
1182 return 0;
1183 }
1184
1185 static void
free_property_array(LilvState * state,PropertyArray * array)1186 free_property_array(LilvState* state, PropertyArray* array)
1187 {
1188 for (uint32_t i = 0; i < array->n; ++i) {
1189 Property* prop = &array->props[i];
1190 if ((prop->flags & LV2_STATE_IS_POD) ||
1191 prop->type == state->atom_Path) {
1192 free(prop->value);
1193 }
1194 }
1195 free(array->props);
1196 }
1197
1198 LILV_API void
lilv_state_free(LilvState * state)1199 lilv_state_free(LilvState* state)
1200 {
1201 if (state) {
1202 free_property_array(state, &state->props);
1203 free_property_array(state, &state->metadata);
1204 for (uint32_t i = 0; i < state->n_values; ++i) {
1205 free(state->values[i].value);
1206 free(state->values[i].symbol);
1207 }
1208 lilv_node_free(state->plugin_uri);
1209 lilv_node_free(state->uri);
1210 zix_tree_free(state->abs2rel);
1211 zix_tree_free(state->rel2abs);
1212 free(state->values);
1213 free(state->label);
1214 free(state->dir);
1215 free(state->file_dir);
1216 free(state->copy_dir);
1217 free(state->link_dir);
1218 free(state);
1219 }
1220 }
1221
1222 LILV_API bool
lilv_state_equals(const LilvState * a,const LilvState * b)1223 lilv_state_equals(const LilvState* a, const LilvState* b)
1224 {
1225 if (!lilv_node_equals(a->plugin_uri, b->plugin_uri)
1226 || (a->label && !b->label)
1227 || (b->label && !a->label)
1228 || (a->label && b->label && strcmp(a->label, b->label))
1229 || a->props.n != b->props.n
1230 || a->n_values != b->n_values) {
1231 return false;
1232 }
1233
1234 for (uint32_t i = 0; i < a->n_values; ++i) {
1235 PortValue* const av = &a->values[i];
1236 PortValue* const bv = &b->values[i];
1237 if (av->size != bv->size || av->type != bv->type
1238 || strcmp(av->symbol, bv->symbol)
1239 || memcmp(av->value, bv->value, av->size)) {
1240 return false;
1241 }
1242 }
1243
1244 for (uint32_t i = 0; i < a->props.n; ++i) {
1245 Property* const ap = &a->props.props[i];
1246 Property* const bp = &b->props.props[i];
1247 if (ap->key != bp->key
1248 || ap->type != bp->type
1249 || ap->flags != bp->flags) {
1250 return false;
1251 } else if (ap->type == a->atom_Path) {
1252 if (!lilv_file_equals(lilv_state_rel2abs(a, (char*)ap->value),
1253 lilv_state_rel2abs(b, (char*)bp->value))) {
1254 return false;
1255 }
1256 } else if (ap->size != bp->size
1257 || memcmp(ap->value, bp->value, ap->size)) {
1258 return false;
1259 }
1260 }
1261
1262 return true;
1263 }
1264
1265 LILV_API unsigned
lilv_state_get_num_properties(const LilvState * state)1266 lilv_state_get_num_properties(const LilvState* state)
1267 {
1268 return state->props.n;
1269 }
1270
1271 LILV_API const LilvNode*
lilv_state_get_plugin_uri(const LilvState * state)1272 lilv_state_get_plugin_uri(const LilvState* state)
1273 {
1274 return state->plugin_uri;
1275 }
1276
1277 LILV_API const LilvNode*
lilv_state_get_uri(const LilvState * state)1278 lilv_state_get_uri(const LilvState* state)
1279 {
1280 return state->uri;
1281 }
1282
1283 LILV_API const char*
lilv_state_get_label(const LilvState * state)1284 lilv_state_get_label(const LilvState* state)
1285 {
1286 return state->label;
1287 }
1288
1289 LILV_API void
lilv_state_set_label(LilvState * state,const char * label)1290 lilv_state_set_label(LilvState* state, const char* label)
1291 {
1292 const size_t len = strlen(label);
1293 state->label = (char*)realloc(state->label, len + 1);
1294 memcpy(state->label, label, len + 1);
1295 }
1296
1297 LILV_API int
lilv_state_set_metadata(LilvState * state,uint32_t key,const void * value,size_t size,uint32_t type,uint32_t flags)1298 lilv_state_set_metadata(LilvState* state,
1299 uint32_t key,
1300 const void* value,
1301 size_t size,
1302 uint32_t type,
1303 uint32_t flags)
1304 {
1305 append_property(state, &state->metadata, key, value, size, type, flags);
1306 return LV2_STATE_SUCCESS;
1307 }
1308