1 /*
2 Copyright 2007-2019 David Robillard <d@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 "filesystem.h"
18 #include "lilv_config.h" // IWYU pragma: keep
19 #include "lilv_internal.h"
20
21 #include "lilv/lilv.h"
22 #include "serd/serd.h"
23 #include "sord/sord.h"
24 #include "zix/common.h"
25 #include "zix/tree.h"
26
27 #include "lv2/core/lv2.h"
28 #include "lv2/presets/presets.h"
29
30 #ifdef LILV_DYN_MANIFEST
31 # include "lv2/dynmanifest/dynmanifest.h"
32 # include <dlfcn.h>
33 #endif
34
35 #include <assert.h>
36 #include <stdbool.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 static int
43 lilv_world_drop_graph(LilvWorld* world, const SordNode* graph);
44
45 LilvWorld*
lilv_world_new(void)46 lilv_world_new(void)
47 {
48 LilvWorld* world = (LilvWorld*)calloc(1, sizeof(LilvWorld));
49
50 world->world = sord_world_new();
51 if (!world->world) {
52 goto fail;
53 }
54
55 world->model = sord_new(world->world, SORD_SPO | SORD_OPS, true);
56 if (!world->model) {
57 goto fail;
58 }
59
60 world->specs = NULL;
61 world->plugin_classes = lilv_plugin_classes_new();
62 world->plugins = lilv_plugins_new();
63 world->zombies = lilv_plugins_new();
64 world->loaded_files = zix_tree_new(
65 false, lilv_resource_node_cmp, NULL, (ZixDestroyFunc)lilv_node_free);
66
67 world->libs = zix_tree_new(false, lilv_lib_compare, NULL, NULL);
68
69 #define NS_DCTERMS "http://purl.org/dc/terms/"
70 #define NS_DYNMAN "http://lv2plug.in/ns/ext/dynmanifest#"
71 #define NS_OWL "http://www.w3.org/2002/07/owl#"
72
73 #define NEW_URI(uri) sord_new_uri(world->world, (const uint8_t*)(uri))
74
75 world->uris.dc_replaces = NEW_URI(NS_DCTERMS "replaces");
76 world->uris.dman_DynManifest = NEW_URI(NS_DYNMAN "DynManifest");
77 world->uris.doap_name = NEW_URI(LILV_NS_DOAP "name");
78 world->uris.lv2_Plugin = NEW_URI(LV2_CORE__Plugin);
79 world->uris.lv2_Specification = NEW_URI(LV2_CORE__Specification);
80 world->uris.lv2_appliesTo = NEW_URI(LV2_CORE__appliesTo);
81 world->uris.lv2_binary = NEW_URI(LV2_CORE__binary);
82 world->uris.lv2_default = NEW_URI(LV2_CORE__default);
83 world->uris.lv2_designation = NEW_URI(LV2_CORE__designation);
84 world->uris.lv2_extensionData = NEW_URI(LV2_CORE__extensionData);
85 world->uris.lv2_index = NEW_URI(LV2_CORE__index);
86 world->uris.lv2_latency = NEW_URI(LV2_CORE__latency);
87 world->uris.lv2_maximum = NEW_URI(LV2_CORE__maximum);
88 world->uris.lv2_microVersion = NEW_URI(LV2_CORE__microVersion);
89 world->uris.lv2_minimum = NEW_URI(LV2_CORE__minimum);
90 world->uris.lv2_minorVersion = NEW_URI(LV2_CORE__minorVersion);
91 world->uris.lv2_name = NEW_URI(LV2_CORE__name);
92 world->uris.lv2_optionalFeature = NEW_URI(LV2_CORE__optionalFeature);
93 world->uris.lv2_port = NEW_URI(LV2_CORE__port);
94 world->uris.lv2_portProperty = NEW_URI(LV2_CORE__portProperty);
95 world->uris.lv2_reportsLatency = NEW_URI(LV2_CORE__reportsLatency);
96 world->uris.lv2_requiredFeature = NEW_URI(LV2_CORE__requiredFeature);
97 world->uris.lv2_symbol = NEW_URI(LV2_CORE__symbol);
98 world->uris.lv2_prototype = NEW_URI(LV2_CORE__prototype);
99 world->uris.owl_Ontology = NEW_URI(NS_OWL "Ontology");
100 world->uris.pset_value = NEW_URI(LV2_PRESETS__value);
101 world->uris.rdf_a = NEW_URI(LILV_NS_RDF "type");
102 world->uris.rdf_value = NEW_URI(LILV_NS_RDF "value");
103 world->uris.rdfs_Class = NEW_URI(LILV_NS_RDFS "Class");
104 world->uris.rdfs_label = NEW_URI(LILV_NS_RDFS "label");
105 world->uris.rdfs_seeAlso = NEW_URI(LILV_NS_RDFS "seeAlso");
106 world->uris.rdfs_subClassOf = NEW_URI(LILV_NS_RDFS "subClassOf");
107 world->uris.xsd_base64Binary = NEW_URI(LILV_NS_XSD "base64Binary");
108 world->uris.xsd_boolean = NEW_URI(LILV_NS_XSD "boolean");
109 world->uris.xsd_decimal = NEW_URI(LILV_NS_XSD "decimal");
110 world->uris.xsd_double = NEW_URI(LILV_NS_XSD "double");
111 world->uris.xsd_integer = NEW_URI(LILV_NS_XSD "integer");
112 world->uris.null_uri = NULL;
113
114 world->lv2_plugin_class =
115 lilv_plugin_class_new(world, NULL, world->uris.lv2_Plugin, "Plugin");
116 assert(world->lv2_plugin_class);
117
118 world->n_read_files = 0;
119 world->opt.filter_language = true;
120 world->opt.dyn_manifest = true;
121
122 return world;
123
124 fail:
125 /* keep on rockin' in the */ free(world);
126 return NULL;
127 }
128
129 void
lilv_world_free(LilvWorld * world)130 lilv_world_free(LilvWorld* world)
131 {
132 if (!world) {
133 return;
134 }
135
136 lilv_plugin_class_free(world->lv2_plugin_class);
137 world->lv2_plugin_class = NULL;
138
139 for (SordNode** n = (SordNode**)&world->uris; *n; ++n) {
140 sord_node_free(world->world, *n);
141 }
142
143 for (LilvSpec* spec = world->specs; spec;) {
144 LilvSpec* next = spec->next;
145 sord_node_free(world->world, spec->spec);
146 sord_node_free(world->world, spec->bundle);
147 lilv_nodes_free(spec->data_uris);
148 free(spec);
149 spec = next;
150 }
151 world->specs = NULL;
152
153 LILV_FOREACH (plugins, i, world->plugins) {
154 const LilvPlugin* p = lilv_plugins_get(world->plugins, i);
155 lilv_plugin_free((LilvPlugin*)p);
156 }
157 zix_tree_free((ZixTree*)world->plugins);
158 world->plugins = NULL;
159
160 LILV_FOREACH (plugins, i, world->zombies) {
161 const LilvPlugin* p = lilv_plugins_get(world->zombies, i);
162 lilv_plugin_free((LilvPlugin*)p);
163 }
164 zix_tree_free((ZixTree*)world->zombies);
165 world->zombies = NULL;
166
167 zix_tree_free((ZixTree*)world->loaded_files);
168 world->loaded_files = NULL;
169
170 zix_tree_free(world->libs);
171 world->libs = NULL;
172
173 zix_tree_free((ZixTree*)world->plugin_classes);
174 world->plugin_classes = NULL;
175
176 sord_free(world->model);
177 world->model = NULL;
178
179 sord_world_free(world->world);
180 world->world = NULL;
181
182 free(world->opt.lv2_path);
183 free(world);
184 }
185
186 void
lilv_world_set_option(LilvWorld * world,const char * uri,const LilvNode * value)187 lilv_world_set_option(LilvWorld* world, const char* uri, const LilvNode* value)
188 {
189 if (!strcmp(uri, LILV_OPTION_DYN_MANIFEST)) {
190 if (lilv_node_is_bool(value)) {
191 world->opt.dyn_manifest = lilv_node_as_bool(value);
192 return;
193 }
194 } else if (!strcmp(uri, LILV_OPTION_FILTER_LANG)) {
195 if (lilv_node_is_bool(value)) {
196 world->opt.filter_language = lilv_node_as_bool(value);
197 return;
198 }
199 } else if (!strcmp(uri, LILV_OPTION_LV2_PATH)) {
200 if (lilv_node_is_string(value)) {
201 world->opt.lv2_path = lilv_strdup(lilv_node_as_string(value));
202 return;
203 }
204 }
205 LILV_WARNF("Unrecognized or invalid option `%s'\n", uri);
206 }
207
208 LilvNodes*
lilv_world_find_nodes(LilvWorld * world,const LilvNode * subject,const LilvNode * predicate,const LilvNode * object)209 lilv_world_find_nodes(LilvWorld* world,
210 const LilvNode* subject,
211 const LilvNode* predicate,
212 const LilvNode* object)
213 {
214 if (subject && !lilv_node_is_uri(subject) && !lilv_node_is_blank(subject)) {
215 LILV_ERRORF("Subject `%s' is not a resource\n",
216 sord_node_get_string(subject->node));
217 return NULL;
218 }
219
220 if (!predicate) {
221 LILV_ERROR("Missing required predicate\n");
222 return NULL;
223 }
224
225 if (!lilv_node_is_uri(predicate)) {
226 LILV_ERRORF("Predicate `%s' is not a URI\n",
227 sord_node_get_string(predicate->node));
228 return NULL;
229 }
230
231 if (!subject && !object) {
232 LILV_ERROR("Both subject and object are NULL\n");
233 return NULL;
234 }
235
236 return lilv_world_find_nodes_internal(world,
237 subject ? subject->node : NULL,
238 predicate->node,
239 object ? object->node : NULL);
240 }
241
242 LilvNode*
lilv_world_get(LilvWorld * world,const LilvNode * subject,const LilvNode * predicate,const LilvNode * object)243 lilv_world_get(LilvWorld* world,
244 const LilvNode* subject,
245 const LilvNode* predicate,
246 const LilvNode* object)
247 {
248 if (!object) {
249 // TODO: Improve performance (see lilv_plugin_get_one)
250 SordIter* stream = sord_search(world->model,
251 subject ? subject->node : NULL,
252 predicate ? predicate->node : NULL,
253 NULL,
254 NULL);
255
256 LilvNodes* nodes =
257 lilv_nodes_from_stream_objects(world, stream, SORD_OBJECT);
258
259 if (nodes) {
260 LilvNode* value = lilv_node_duplicate(lilv_nodes_get_first(nodes));
261 lilv_nodes_free(nodes);
262 return value;
263 }
264
265 return NULL;
266 }
267
268 SordNode* snode = sord_get(world->model,
269 subject ? subject->node : NULL,
270 predicate ? predicate->node : NULL,
271 object ? object->node : NULL,
272 NULL);
273 LilvNode* lnode = lilv_node_new_from_node(world, snode);
274 sord_node_free(world->world, snode);
275 return lnode;
276 }
277
278 SordIter*
lilv_world_query_internal(LilvWorld * world,const SordNode * subject,const SordNode * predicate,const SordNode * object)279 lilv_world_query_internal(LilvWorld* world,
280 const SordNode* subject,
281 const SordNode* predicate,
282 const SordNode* object)
283 {
284 return sord_search(world->model, subject, predicate, object, NULL);
285 }
286
287 bool
lilv_world_ask_internal(LilvWorld * world,const SordNode * subject,const SordNode * predicate,const SordNode * object)288 lilv_world_ask_internal(LilvWorld* world,
289 const SordNode* subject,
290 const SordNode* predicate,
291 const SordNode* object)
292 {
293 return sord_ask(world->model, subject, predicate, object, NULL);
294 }
295
296 bool
lilv_world_ask(LilvWorld * world,const LilvNode * subject,const LilvNode * predicate,const LilvNode * object)297 lilv_world_ask(LilvWorld* world,
298 const LilvNode* subject,
299 const LilvNode* predicate,
300 const LilvNode* object)
301 {
302 return sord_ask(world->model,
303 subject ? subject->node : NULL,
304 predicate ? predicate->node : NULL,
305 object ? object->node : NULL,
306 NULL);
307 }
308
309 SordModel*
lilv_world_filter_model(LilvWorld * world,SordModel * model,const SordNode * subject,const SordNode * predicate,const SordNode * object,const SordNode * graph)310 lilv_world_filter_model(LilvWorld* world,
311 SordModel* model,
312 const SordNode* subject,
313 const SordNode* predicate,
314 const SordNode* object,
315 const SordNode* graph)
316 {
317 SordModel* results = sord_new(world->world, SORD_SPO, false);
318 SordIter* i = sord_search(model, subject, predicate, object, graph);
319 for (; !sord_iter_end(i); sord_iter_next(i)) {
320 SordQuad quad;
321 sord_iter_get(i, quad);
322 sord_add(results, quad);
323 }
324 sord_iter_free(i);
325 return results;
326 }
327
328 LilvNodes*
lilv_world_find_nodes_internal(LilvWorld * world,const SordNode * subject,const SordNode * predicate,const SordNode * object)329 lilv_world_find_nodes_internal(LilvWorld* world,
330 const SordNode* subject,
331 const SordNode* predicate,
332 const SordNode* object)
333 {
334 return lilv_nodes_from_stream_objects(
335 world,
336 lilv_world_query_internal(world, subject, predicate, object),
337 (object == NULL) ? SORD_OBJECT : SORD_SUBJECT);
338 }
339
340 static SerdNode
lilv_new_uri_relative_to_base(const uint8_t * uri_str,const uint8_t * base_uri_str)341 lilv_new_uri_relative_to_base(const uint8_t* uri_str,
342 const uint8_t* base_uri_str)
343 {
344 SerdURI base_uri;
345 serd_uri_parse(base_uri_str, &base_uri);
346 return serd_node_new_uri_from_string(uri_str, &base_uri, NULL);
347 }
348
349 const uint8_t*
lilv_world_blank_node_prefix(LilvWorld * world)350 lilv_world_blank_node_prefix(LilvWorld* world)
351 {
352 static char str[32];
353 snprintf(str, sizeof(str), "%u", world->n_read_files++);
354 return (const uint8_t*)str;
355 }
356
357 /** Comparator for sequences (e.g. world->plugins). */
358 int
lilv_header_compare_by_uri(const void * a,const void * b,void * user_data)359 lilv_header_compare_by_uri(const void* a, const void* b, void* user_data)
360 {
361 const struct LilvHeader* const header_a = (const struct LilvHeader*)a;
362 const struct LilvHeader* const header_b = (const struct LilvHeader*)b;
363 return strcmp(lilv_node_as_uri(header_a->uri),
364 lilv_node_as_uri(header_b->uri));
365 }
366
367 /**
368 Comparator for libraries (world->libs).
369
370 Libraries do have a LilvHeader, but we must also compare the bundle to
371 handle the case where the same library is loaded with different bundles, and
372 consequently different contents (mainly plugins).
373 */
374 int
lilv_lib_compare(const void * a,const void * b,void * user_data)375 lilv_lib_compare(const void* a, const void* b, void* user_data)
376 {
377 const LilvLib* const lib_a = (const LilvLib*)a;
378 const LilvLib* const lib_b = (const LilvLib*)b;
379 int cmp = strcmp(lilv_node_as_uri(lib_a->uri), lilv_node_as_uri(lib_b->uri));
380 return cmp ? cmp : strcmp(lib_a->bundle_path, lib_b->bundle_path);
381 }
382
383 /** Get an element of a collection of any object with an LilvHeader by URI. */
384 static ZixTreeIter*
lilv_collection_find_by_uri(const ZixTree * seq,const LilvNode * uri)385 lilv_collection_find_by_uri(const ZixTree* seq, const LilvNode* uri)
386 {
387 ZixTreeIter* i = NULL;
388 if (lilv_node_is_uri(uri)) {
389 struct LilvHeader key = {NULL, (LilvNode*)uri};
390 zix_tree_find(seq, &key, &i);
391 }
392 return i;
393 }
394
395 /** Get an element of a collection of any object with an LilvHeader by URI. */
396 struct LilvHeader*
lilv_collection_get_by_uri(const ZixTree * seq,const LilvNode * uri)397 lilv_collection_get_by_uri(const ZixTree* seq, const LilvNode* uri)
398 {
399 ZixTreeIter* const i = lilv_collection_find_by_uri(seq, uri);
400
401 return i ? (struct LilvHeader*)zix_tree_get(i) : NULL;
402 }
403
404 static void
lilv_world_add_spec(LilvWorld * world,const SordNode * specification_node,const SordNode * bundle_node)405 lilv_world_add_spec(LilvWorld* world,
406 const SordNode* specification_node,
407 const SordNode* bundle_node)
408 {
409 LilvSpec* spec = (LilvSpec*)malloc(sizeof(LilvSpec));
410 spec->spec = sord_node_copy(specification_node);
411 spec->bundle = sord_node_copy(bundle_node);
412 spec->data_uris = lilv_nodes_new();
413
414 // Add all data files (rdfs:seeAlso)
415 SordIter* files = sord_search(
416 world->model, specification_node, world->uris.rdfs_seeAlso, NULL, NULL);
417 FOREACH_MATCH (files) {
418 const SordNode* file_node = sord_iter_get_node(files, SORD_OBJECT);
419 zix_tree_insert((ZixTree*)spec->data_uris,
420 lilv_node_new_from_node(world, file_node),
421 NULL);
422 }
423 sord_iter_free(files);
424
425 // Add specification to world specification list
426 spec->next = world->specs;
427 world->specs = spec;
428 }
429
430 static void
lilv_world_add_plugin(LilvWorld * world,const SordNode * plugin_node,const LilvNode * manifest_uri,void * dynmanifest,const SordNode * bundle)431 lilv_world_add_plugin(LilvWorld* world,
432 const SordNode* plugin_node,
433 const LilvNode* manifest_uri,
434 void* dynmanifest,
435 const SordNode* bundle)
436 {
437 LilvNode* plugin_uri = lilv_node_new_from_node(world, plugin_node);
438 ZixTreeIter* z = NULL;
439 LilvPlugin* plugin =
440 (LilvPlugin*)lilv_plugins_get_by_uri(world->plugins, plugin_uri);
441
442 if (plugin) {
443 // Existing plugin, if this is different bundle, ignore it
444 // (use the first plugin found in LV2_PATH)
445 const LilvNode* last_bundle = lilv_plugin_get_bundle_uri(plugin);
446 const char* plugin_uri_str = lilv_node_as_uri(plugin_uri);
447 if (sord_node_equals(bundle, last_bundle->node)) {
448 LILV_WARNF("Reloading plugin <%s>\n", plugin_uri_str);
449 plugin->loaded = false;
450 lilv_node_free(plugin_uri);
451 } else {
452 LILV_WARNF("Duplicate plugin <%s>\n", plugin_uri_str);
453 LILV_WARNF("... found in %s\n", lilv_node_as_string(last_bundle));
454 LILV_WARNF("... and %s (ignored)\n", sord_node_get_string(bundle));
455 lilv_node_free(plugin_uri);
456 return;
457 }
458 } else if ((z = lilv_collection_find_by_uri((const ZixTree*)world->zombies,
459 plugin_uri))) {
460 // Plugin bundle has been re-loaded, move from zombies to plugins
461 plugin = (LilvPlugin*)zix_tree_get(z);
462 zix_tree_remove((ZixTree*)world->zombies, z);
463 zix_tree_insert((ZixTree*)world->plugins, plugin, NULL);
464 lilv_node_free(plugin_uri);
465 lilv_plugin_clear(plugin, lilv_node_new_from_node(world, bundle));
466 } else {
467 // Add new plugin to the world
468 plugin = lilv_plugin_new(
469 world, plugin_uri, lilv_node_new_from_node(world, bundle));
470
471 // Add manifest as plugin data file (as if it were rdfs:seeAlso)
472 zix_tree_insert(
473 (ZixTree*)plugin->data_uris, lilv_node_duplicate(manifest_uri), NULL);
474
475 // Add plugin to world plugin sequence
476 zix_tree_insert((ZixTree*)world->plugins, plugin, NULL);
477 }
478
479 #ifdef LILV_DYN_MANIFEST
480 // Set dynamic manifest library URI, if applicable
481 if (dynmanifest) {
482 plugin->dynmanifest = (LilvDynManifest*)dynmanifest;
483 ++((LilvDynManifest*)dynmanifest)->refs;
484 }
485 #endif
486
487 // Add all plugin data files (rdfs:seeAlso)
488 SordIter* files = sord_search(
489 world->model, plugin_node, world->uris.rdfs_seeAlso, NULL, NULL);
490 FOREACH_MATCH (files) {
491 const SordNode* file_node = sord_iter_get_node(files, SORD_OBJECT);
492 zix_tree_insert((ZixTree*)plugin->data_uris,
493 lilv_node_new_from_node(world, file_node),
494 NULL);
495 }
496 sord_iter_free(files);
497 }
498
499 SerdStatus
lilv_world_load_graph(LilvWorld * world,SordNode * graph,const LilvNode * uri)500 lilv_world_load_graph(LilvWorld* world, SordNode* graph, const LilvNode* uri)
501 {
502 const SerdNode* base = sord_node_to_serd_node(uri->node);
503 SerdEnv* env = serd_env_new(base);
504 SerdReader* reader = sord_new_reader(world->model, env, SERD_TURTLE, graph);
505
506 const SerdStatus st = lilv_world_load_file(world, reader, uri);
507
508 serd_env_free(env);
509 serd_reader_free(reader);
510 return st;
511 }
512
513 static void
lilv_world_load_dyn_manifest(LilvWorld * world,SordNode * bundle_node,const LilvNode * manifest)514 lilv_world_load_dyn_manifest(LilvWorld* world,
515 SordNode* bundle_node,
516 const LilvNode* manifest)
517 {
518 #ifdef LILV_DYN_MANIFEST
519 if (!world->opt.dyn_manifest) {
520 return;
521 }
522
523 LV2_Dyn_Manifest_Handle handle = NULL;
524
525 // ?dman a dynman:DynManifest bundle_node
526 SordModel* model = lilv_world_filter_model(world,
527 world->model,
528 NULL,
529 world->uris.rdf_a,
530 world->uris.dman_DynManifest,
531 bundle_node);
532 SordIter* iter = sord_begin(model);
533 for (; !sord_iter_end(iter); sord_iter_next(iter)) {
534 const SordNode* dmanifest = sord_iter_get_node(iter, SORD_SUBJECT);
535
536 // ?dman lv2:binary ?binary
537 SordIter* binaries = sord_search(
538 world->model, dmanifest, world->uris.lv2_binary, NULL, bundle_node);
539 if (sord_iter_end(binaries)) {
540 sord_iter_free(binaries);
541 LILV_ERRORF("Dynamic manifest in <%s> has no binaries, ignored\n",
542 sord_node_get_string(bundle_node));
543 continue;
544 }
545
546 // Get binary path
547 const SordNode* binary = sord_iter_get_node(binaries, SORD_OBJECT);
548 const uint8_t* lib_uri = sord_node_get_string(binary);
549 char* lib_path = lilv_file_uri_parse((const char*)lib_uri, 0);
550 if (!lib_path) {
551 LILV_ERROR("No dynamic manifest library path\n");
552 sord_iter_free(binaries);
553 continue;
554 }
555
556 // Open library
557 dlerror();
558 void* lib = dlopen(lib_path, RTLD_LAZY);
559 if (!lib) {
560 LILV_ERRORF(
561 "Failed to open dynmanifest library `%s' (%s)\n", lib_path, dlerror());
562 sord_iter_free(binaries);
563 lilv_free(lib_path);
564 continue;
565 }
566
567 // Open dynamic manifest
568 typedef int (*OpenFunc)(LV2_Dyn_Manifest_Handle*,
569 const LV2_Feature* const*);
570 OpenFunc dmopen = (OpenFunc)lilv_dlfunc(lib, "lv2_dyn_manifest_open");
571 if (!dmopen || dmopen(&handle, &dman_features)) {
572 LILV_ERRORF("No `lv2_dyn_manifest_open' in `%s'\n", lib_path);
573 sord_iter_free(binaries);
574 dlclose(lib);
575 lilv_free(lib_path);
576 continue;
577 }
578
579 // Get subjects (the data that would be in manifest.ttl)
580 typedef int (*GetSubjectsFunc)(LV2_Dyn_Manifest_Handle, FILE*);
581 GetSubjectsFunc get_subjects_func =
582 (GetSubjectsFunc)lilv_dlfunc(lib, "lv2_dyn_manifest_get_subjects");
583 if (!get_subjects_func) {
584 LILV_ERRORF("No `lv2_dyn_manifest_get_subjects' in `%s'\n", lib_path);
585 sord_iter_free(binaries);
586 dlclose(lib);
587 lilv_free(lib_path);
588 continue;
589 }
590
591 LilvDynManifest* desc = (LilvDynManifest*)malloc(sizeof(LilvDynManifest));
592 desc->bundle = lilv_node_new_from_node(world, bundle_node);
593 desc->lib = lib;
594 desc->handle = handle;
595 desc->refs = 0;
596
597 sord_iter_free(binaries);
598
599 // Generate data file
600 FILE* fd = tmpfile();
601 get_subjects_func(handle, fd);
602 rewind(fd);
603
604 // Parse generated data file into temporary model
605 // FIXME
606 const SerdNode* base = sord_node_to_serd_node(dmanifest);
607 SerdEnv* env = serd_env_new(base);
608 SerdReader* reader = sord_new_reader(
609 world->model, env, SERD_TURTLE, sord_node_copy(dmanifest));
610 serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world));
611 serd_reader_read_file_handle(reader, fd, (const uint8_t*)"(dyn-manifest)");
612 serd_reader_free(reader);
613 serd_env_free(env);
614
615 // Close (and automatically delete) temporary data file
616 fclose(fd);
617
618 // ?plugin a lv2:Plugin
619 SordModel* plugins = lilv_world_filter_model(world,
620 world->model,
621 NULL,
622 world->uris.rdf_a,
623 world->uris.lv2_Plugin,
624 dmanifest);
625 SordIter* p = sord_begin(plugins);
626 FOREACH_MATCH (p) {
627 const SordNode* plug = sord_iter_get_node(p, SORD_SUBJECT);
628 lilv_world_add_plugin(world, plug, manifest, desc, bundle_node);
629 }
630 if (desc->refs == 0) {
631 lilv_dynmanifest_free(desc);
632 }
633 sord_iter_free(p);
634 sord_free(plugins);
635 lilv_free(lib_path);
636 }
637 sord_iter_free(iter);
638 sord_free(model);
639 #endif // LILV_DYN_MANIFEST
640 }
641
642 #ifdef LILV_DYN_MANIFEST
643 void
lilv_dynmanifest_free(LilvDynManifest * dynmanifest)644 lilv_dynmanifest_free(LilvDynManifest* dynmanifest)
645 {
646 typedef int (*CloseFunc)(LV2_Dyn_Manifest_Handle);
647 CloseFunc close_func =
648 (CloseFunc)lilv_dlfunc(dynmanifest->lib, "lv2_dyn_manifest_close");
649 if (close_func) {
650 close_func(dynmanifest->handle);
651 }
652
653 dlclose(dynmanifest->lib);
654 lilv_node_free(dynmanifest->bundle);
655 free(dynmanifest);
656 }
657 #endif // LILV_DYN_MANIFEST
658
659 LilvNode*
lilv_world_get_manifest_uri(LilvWorld * world,const LilvNode * bundle_uri)660 lilv_world_get_manifest_uri(LilvWorld* world, const LilvNode* bundle_uri)
661 {
662 SerdNode manifest_uri = lilv_new_uri_relative_to_base(
663 (const uint8_t*)"manifest.ttl", sord_node_get_string(bundle_uri->node));
664 LilvNode* manifest = lilv_new_uri(world, (const char*)manifest_uri.buf);
665 serd_node_free(&manifest_uri);
666 return manifest;
667 }
668
669 static SordModel*
load_plugin_model(LilvWorld * world,const LilvNode * bundle_uri,const LilvNode * plugin_uri)670 load_plugin_model(LilvWorld* world,
671 const LilvNode* bundle_uri,
672 const LilvNode* plugin_uri)
673 {
674 // Create model and reader for loading into it
675 SordNode* bundle_node = bundle_uri->node;
676 SordModel* model = sord_new(world->world, SORD_SPO | SORD_OPS, false);
677 SerdEnv* env = serd_env_new(sord_node_to_serd_node(bundle_node));
678 SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL);
679
680 // Load manifest
681 LilvNode* manifest_uri = lilv_world_get_manifest_uri(world, bundle_uri);
682 serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world));
683 serd_reader_read_file(reader,
684 (const uint8_t*)lilv_node_as_string(manifest_uri));
685
686 // Load any seeAlso files
687 SordModel* files = lilv_world_filter_model(
688 world, model, plugin_uri->node, world->uris.rdfs_seeAlso, NULL, NULL);
689
690 SordIter* f = sord_begin(files);
691 FOREACH_MATCH (f) {
692 const SordNode* file = sord_iter_get_node(f, SORD_OBJECT);
693 const uint8_t* file_str = sord_node_get_string(file);
694 if (sord_node_get_type(file) == SORD_URI) {
695 serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world));
696 serd_reader_read_file(reader, file_str);
697 }
698 }
699
700 sord_iter_free(f);
701 sord_free(files);
702 serd_reader_free(reader);
703 serd_env_free(env);
704 lilv_node_free(manifest_uri);
705
706 return model;
707 }
708
709 static LilvVersion
get_version(LilvWorld * world,SordModel * model,const LilvNode * subject)710 get_version(LilvWorld* world, SordModel* model, const LilvNode* subject)
711 {
712 const SordNode* minor_node =
713 sord_get(model, subject->node, world->uris.lv2_minorVersion, NULL, NULL);
714 const SordNode* micro_node =
715 sord_get(model, subject->node, world->uris.lv2_microVersion, NULL, NULL);
716
717 LilvVersion version = {0, 0};
718 if (minor_node && micro_node) {
719 version.minor = atoi((const char*)sord_node_get_string(minor_node));
720 version.micro = atoi((const char*)sord_node_get_string(micro_node));
721 }
722
723 return version;
724 }
725
726 void
lilv_world_load_bundle(LilvWorld * world,const LilvNode * bundle_uri)727 lilv_world_load_bundle(LilvWorld* world, const LilvNode* bundle_uri)
728 {
729 if (!lilv_node_is_uri(bundle_uri)) {
730 LILV_ERRORF("Bundle URI `%s' is not a URI\n",
731 sord_node_get_string(bundle_uri->node));
732 return;
733 }
734
735 SordNode* bundle_node = bundle_uri->node;
736 LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle_uri);
737
738 // Read manifest into model with graph = bundle_node
739 SerdStatus st = lilv_world_load_graph(world, bundle_node, manifest);
740 if (st > SERD_FAILURE) {
741 LILV_ERRORF("Error reading %s\n", lilv_node_as_string(manifest));
742 lilv_node_free(manifest);
743 return;
744 }
745
746 // ?plugin a lv2:Plugin
747 SordIter* plug_results = sord_search(
748 world->model, NULL, world->uris.rdf_a, world->uris.lv2_Plugin, bundle_node);
749
750 // Find any loaded plugins that will be replaced with a newer version
751 LilvNodes* unload_uris = lilv_nodes_new();
752 FOREACH_MATCH (plug_results) {
753 const SordNode* plug = sord_iter_get_node(plug_results, SORD_SUBJECT);
754
755 LilvNode* plugin_uri = lilv_node_new_from_node(world, plug);
756 const LilvPlugin* plugin =
757 lilv_plugins_get_by_uri(world->plugins, plugin_uri);
758 const LilvNode* last_bundle =
759 plugin ? lilv_plugin_get_bundle_uri(plugin) : NULL;
760 if (!plugin || sord_node_equals(bundle_node, last_bundle->node)) {
761 // No previously loaded version, or it's from the same bundle
762 lilv_node_free(plugin_uri);
763 continue;
764 }
765
766 // Compare versions
767 SordModel* this_model = load_plugin_model(world, bundle_uri, plugin_uri);
768 LilvVersion this_version = get_version(world, this_model, plugin_uri);
769 SordModel* last_model = load_plugin_model(world, last_bundle, plugin_uri);
770 LilvVersion last_version = get_version(world, last_model, plugin_uri);
771 sord_free(this_model);
772 sord_free(last_model);
773 const int cmp = lilv_version_cmp(&this_version, &last_version);
774 if (cmp > 0) {
775 zix_tree_insert(
776 (ZixTree*)unload_uris, lilv_node_duplicate(plugin_uri), NULL);
777 LILV_WARNF("Replacing version %d.%d of <%s> from <%s>\n",
778 last_version.minor,
779 last_version.micro,
780 sord_node_get_string(plug),
781 sord_node_get_string(last_bundle->node));
782 LILV_NOTEF("New version %d.%d found in <%s>\n",
783 this_version.minor,
784 this_version.micro,
785 sord_node_get_string(bundle_node));
786 } else if (cmp < 0) {
787 LILV_WARNF("Ignoring bundle <%s>\n", sord_node_get_string(bundle_node));
788 LILV_NOTEF("Newer version of <%s> loaded from <%s>\n",
789 sord_node_get_string(plug),
790 sord_node_get_string(last_bundle->node));
791 lilv_node_free(plugin_uri);
792 sord_iter_free(plug_results);
793 lilv_world_drop_graph(world, bundle_node);
794 lilv_node_free(manifest);
795 lilv_nodes_free(unload_uris);
796 return;
797 }
798 lilv_node_free(plugin_uri);
799 }
800
801 sord_iter_free(plug_results);
802
803 // Unload any old conflicting plugins
804 LilvNodes* unload_bundles = lilv_nodes_new();
805 LILV_FOREACH (nodes, i, unload_uris) {
806 const LilvNode* uri = lilv_nodes_get(unload_uris, i);
807 const LilvPlugin* plugin = lilv_plugins_get_by_uri(world->plugins, uri);
808 const LilvNode* bundle = lilv_plugin_get_bundle_uri(plugin);
809
810 // Unload plugin and record bundle for later unloading
811 lilv_world_unload_resource(world, uri);
812 zix_tree_insert(
813 (ZixTree*)unload_bundles, lilv_node_duplicate(bundle), NULL);
814 }
815 lilv_nodes_free(unload_uris);
816
817 // Now unload the associated bundles
818 // This must be done last since several plugins could be in the same bundle
819 LILV_FOREACH (nodes, i, unload_bundles) {
820 lilv_world_unload_bundle(world, lilv_nodes_get(unload_bundles, i));
821 }
822 lilv_nodes_free(unload_bundles);
823
824 // Re-search for plugin results now that old plugins are gone
825 plug_results = sord_search(
826 world->model, NULL, world->uris.rdf_a, world->uris.lv2_Plugin, bundle_node);
827
828 FOREACH_MATCH (plug_results) {
829 const SordNode* plug = sord_iter_get_node(plug_results, SORD_SUBJECT);
830 lilv_world_add_plugin(world, plug, manifest, NULL, bundle_node);
831 }
832 sord_iter_free(plug_results);
833
834 lilv_world_load_dyn_manifest(world, bundle_node, manifest);
835
836 // ?spec a lv2:Specification
837 // ?spec a owl:Ontology
838 const SordNode* spec_preds[] = {
839 world->uris.lv2_Specification, world->uris.owl_Ontology, NULL};
840 for (const SordNode** p = spec_preds; *p; ++p) {
841 SordIter* i =
842 sord_search(world->model, NULL, world->uris.rdf_a, *p, bundle_node);
843 FOREACH_MATCH (i) {
844 const SordNode* spec = sord_iter_get_node(i, SORD_SUBJECT);
845 lilv_world_add_spec(world, spec, bundle_node);
846 }
847 sord_iter_free(i);
848 }
849
850 lilv_node_free(manifest);
851 }
852
853 static int
lilv_world_drop_graph(LilvWorld * world,const SordNode * graph)854 lilv_world_drop_graph(LilvWorld* world, const SordNode* graph)
855 {
856 SordIter* i = sord_search(world->model, NULL, NULL, NULL, graph);
857 while (!sord_iter_end(i)) {
858 const SerdStatus st = sord_erase(world->model, i);
859 if (st) {
860 LILV_ERRORF("Error removing statement from <%s> (%s)\n",
861 sord_node_get_string(graph),
862 serd_strerror(st));
863 return st;
864 }
865 }
866 sord_iter_free(i);
867
868 return 0;
869 }
870
871 /** Remove loaded_files entry so file will be reloaded if requested. */
872 static int
lilv_world_unload_file(LilvWorld * world,const LilvNode * file)873 lilv_world_unload_file(LilvWorld* world, const LilvNode* file)
874 {
875 ZixTreeIter* iter = NULL;
876 if (!zix_tree_find((ZixTree*)world->loaded_files, file, &iter)) {
877 zix_tree_remove((ZixTree*)world->loaded_files, iter);
878 return 0;
879 }
880 return 1;
881 }
882
883 int
lilv_world_unload_bundle(LilvWorld * world,const LilvNode * bundle_uri)884 lilv_world_unload_bundle(LilvWorld* world, const LilvNode* bundle_uri)
885 {
886 if (!bundle_uri) {
887 return 0;
888 }
889
890 // Find all loaded files that are inside the bundle
891 LilvNodes* files = lilv_nodes_new();
892 LILV_FOREACH (nodes, i, world->loaded_files) {
893 const LilvNode* file = lilv_nodes_get(world->loaded_files, i);
894 if (!strncmp(lilv_node_as_string(file),
895 lilv_node_as_string(bundle_uri),
896 strlen(lilv_node_as_string(bundle_uri)))) {
897 zix_tree_insert((ZixTree*)files, lilv_node_duplicate(file), NULL);
898 }
899 }
900
901 // Unload all loaded files in the bundle
902 LILV_FOREACH (nodes, i, files) {
903 const LilvNode* file = lilv_nodes_get(world->plugins, i);
904 lilv_world_unload_file(world, file);
905 }
906
907 lilv_nodes_free(files);
908
909 /* Remove any plugins in the bundle from the plugin list. Since the
910 application may still have a pointer to the LilvPlugin, it can not be
911 destroyed here. Instead, we move it to the zombie plugin list, so it
912 will not be in the list returned by lilv_world_get_all_plugins() but can
913 still be used.
914 */
915 ZixTreeIter* i = zix_tree_begin((ZixTree*)world->plugins);
916 while (i != zix_tree_end((ZixTree*)world->plugins)) {
917 LilvPlugin* p = (LilvPlugin*)zix_tree_get(i);
918 ZixTreeIter* next = zix_tree_iter_next(i);
919
920 if (lilv_node_equals(lilv_plugin_get_bundle_uri(p), bundle_uri)) {
921 zix_tree_remove((ZixTree*)world->plugins, i);
922 zix_tree_insert((ZixTree*)world->zombies, p, NULL);
923 }
924
925 i = next;
926 }
927
928 // Drop everything in bundle graph
929 return lilv_world_drop_graph(world, bundle_uri->node);
930 }
931
932 static void
load_dir_entry(const char * dir,const char * name,void * data)933 load_dir_entry(const char* dir, const char* name, void* data)
934 {
935 LilvWorld* world = (LilvWorld*)data;
936 char* path = lilv_strjoin(dir, "/", name, "/", NULL);
937 SerdNode suri = serd_node_new_file_uri((const uint8_t*)path, 0, 0, true);
938 LilvNode* node = lilv_new_uri(world, (const char*)suri.buf);
939
940 lilv_world_load_bundle(world, node);
941 lilv_node_free(node);
942 serd_node_free(&suri);
943 free(path);
944 }
945
946 /** Load all bundles in the directory at `dir_path`. */
947 static void
lilv_world_load_directory(LilvWorld * world,const char * dir_path)948 lilv_world_load_directory(LilvWorld* world, const char* dir_path)
949 {
950 char* path = lilv_expand(dir_path);
951 if (path) {
952 lilv_dir_for_each(path, world, load_dir_entry);
953 free(path);
954 }
955 }
956
957 static const char*
first_path_sep(const char * path)958 first_path_sep(const char* path)
959 {
960 for (const char* p = path; *p != '\0'; ++p) {
961 if (*p == LILV_PATH_SEP[0]) {
962 return p;
963 }
964 }
965 return NULL;
966 }
967
968 /** Load all bundles found in `lv2_path`.
969 * @param lv2_path A colon-delimited list of directories. These directories
970 * should contain LV2 bundle directories (ie the search path is a list of
971 * parent directories of bundles, not a list of bundle directories).
972 */
973 static void
lilv_world_load_path(LilvWorld * world,const char * lv2_path)974 lilv_world_load_path(LilvWorld* world, const char* lv2_path)
975 {
976 while (lv2_path[0] != '\0') {
977 const char* const sep = first_path_sep(lv2_path);
978 if (sep) {
979 const size_t dir_len = sep - lv2_path;
980 char* const dir = (char*)malloc(dir_len + 1);
981 memcpy(dir, lv2_path, dir_len);
982 dir[dir_len] = '\0';
983 lilv_world_load_directory(world, dir);
984 free(dir);
985 lv2_path += dir_len + 1;
986 } else {
987 lilv_world_load_directory(world, lv2_path);
988 lv2_path = "\0";
989 }
990 }
991 }
992
993 void
lilv_world_load_specifications(LilvWorld * world)994 lilv_world_load_specifications(LilvWorld* world)
995 {
996 for (LilvSpec* spec = world->specs; spec; spec = spec->next) {
997 LILV_FOREACH (nodes, f, spec->data_uris) {
998 LilvNode* file = (LilvNode*)lilv_collection_get(spec->data_uris, f);
999 lilv_world_load_graph(world, NULL, file);
1000 }
1001 }
1002 }
1003
1004 void
lilv_world_load_plugin_classes(LilvWorld * world)1005 lilv_world_load_plugin_classes(LilvWorld* world)
1006 {
1007 /* FIXME: This loads all classes, not just lv2:Plugin subclasses.
1008 However, if the host gets all the classes via
1009 lilv_plugin_class_get_children starting with lv2:Plugin as the root (which
1010 is e.g. how a host would build a menu), they won't be seen anyway...
1011 */
1012
1013 SordIter* classes = sord_search(
1014 world->model, NULL, world->uris.rdf_a, world->uris.rdfs_Class, NULL);
1015 FOREACH_MATCH (classes) {
1016 const SordNode* class_node = sord_iter_get_node(classes, SORD_SUBJECT);
1017
1018 SordNode* parent = sord_get(
1019 world->model, class_node, world->uris.rdfs_subClassOf, NULL, NULL);
1020 if (!parent || sord_node_get_type(parent) != SORD_URI) {
1021 continue;
1022 }
1023
1024 SordNode* label =
1025 sord_get(world->model, class_node, world->uris.rdfs_label, NULL, NULL);
1026 if (!label) {
1027 sord_node_free(world->world, parent);
1028 continue;
1029 }
1030
1031 LilvPluginClass* pclass = lilv_plugin_class_new(
1032 world, parent, class_node, (const char*)sord_node_get_string(label));
1033 if (pclass) {
1034 zix_tree_insert((ZixTree*)world->plugin_classes, pclass, NULL);
1035 }
1036
1037 sord_node_free(world->world, label);
1038 sord_node_free(world->world, parent);
1039 }
1040 sord_iter_free(classes);
1041 }
1042
1043 void
lilv_world_load_all(LilvWorld * world)1044 lilv_world_load_all(LilvWorld* world)
1045 {
1046 const char* lv2_path = world->opt.lv2_path;
1047 if (!lv2_path) {
1048 lv2_path = getenv("LV2_PATH");
1049 }
1050 if (!lv2_path) {
1051 lv2_path = LILV_DEFAULT_LV2_PATH;
1052 }
1053
1054 // Discover bundles and read all manifest files into model
1055 lilv_world_load_path(world, lv2_path);
1056
1057 LILV_FOREACH (plugins, p, world->plugins) {
1058 const LilvPlugin* plugin =
1059 (const LilvPlugin*)lilv_collection_get((ZixTree*)world->plugins, p);
1060
1061 // ?new dc:replaces plugin
1062 if (sord_ask(world->model,
1063 NULL,
1064 world->uris.dc_replaces,
1065 lilv_plugin_get_uri(plugin)->node,
1066 NULL)) {
1067 // TODO: Check if replacement is a known plugin? (expensive)
1068 ((LilvPlugin*)plugin)->replaced = true;
1069 }
1070 }
1071
1072 // Query out things to cache
1073 lilv_world_load_specifications(world);
1074 lilv_world_load_plugin_classes(world);
1075 }
1076
1077 SerdStatus
lilv_world_load_file(LilvWorld * world,SerdReader * reader,const LilvNode * uri)1078 lilv_world_load_file(LilvWorld* world, SerdReader* reader, const LilvNode* uri)
1079 {
1080 ZixTreeIter* iter = NULL;
1081 if (!zix_tree_find((ZixTree*)world->loaded_files, uri, &iter)) {
1082 return SERD_FAILURE; // File has already been loaded
1083 }
1084
1085 size_t uri_len = 0;
1086 const uint8_t* const uri_str =
1087 sord_node_get_string_counted(uri->node, &uri_len);
1088 if (strncmp((const char*)uri_str, "file:", 5)) {
1089 return SERD_FAILURE; // Not a local file
1090 }
1091
1092 if (strcmp((const char*)uri_str + uri_len - 4, ".ttl")) {
1093 return SERD_FAILURE; // Not a Turtle file
1094 }
1095
1096 serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world));
1097 const SerdStatus st = serd_reader_read_file(reader, uri_str);
1098 if (st) {
1099 LILV_ERRORF("Error loading file `%s'\n", lilv_node_as_string(uri));
1100 return st;
1101 }
1102
1103 zix_tree_insert(
1104 (ZixTree*)world->loaded_files, lilv_node_duplicate(uri), NULL);
1105 return SERD_SUCCESS;
1106 }
1107
1108 int
lilv_world_load_resource(LilvWorld * world,const LilvNode * resource)1109 lilv_world_load_resource(LilvWorld* world, const LilvNode* resource)
1110 {
1111 if (!lilv_node_is_uri(resource) && !lilv_node_is_blank(resource)) {
1112 LILV_ERRORF("Node `%s' is not a resource\n",
1113 sord_node_get_string(resource->node));
1114 return -1;
1115 }
1116
1117 SordModel* files = lilv_world_filter_model(
1118 world, world->model, resource->node, world->uris.rdfs_seeAlso, NULL, NULL);
1119
1120 SordIter* f = sord_begin(files);
1121 int n_read = 0;
1122 FOREACH_MATCH (f) {
1123 const SordNode* file = sord_iter_get_node(f, SORD_OBJECT);
1124 const uint8_t* file_str = sord_node_get_string(file);
1125 LilvNode* file_node = lilv_node_new_from_node(world, file);
1126 if (sord_node_get_type(file) != SORD_URI) {
1127 LILV_ERRORF("rdfs:seeAlso node `%s' is not a URI\n", file_str);
1128 } else if (!lilv_world_load_graph(world, (SordNode*)file, file_node)) {
1129 ++n_read;
1130 }
1131 lilv_node_free(file_node);
1132 }
1133 sord_iter_free(f);
1134
1135 sord_free(files);
1136 return n_read;
1137 }
1138
1139 int
lilv_world_unload_resource(LilvWorld * world,const LilvNode * resource)1140 lilv_world_unload_resource(LilvWorld* world, const LilvNode* resource)
1141 {
1142 if (!lilv_node_is_uri(resource) && !lilv_node_is_blank(resource)) {
1143 LILV_ERRORF("Node `%s' is not a resource\n",
1144 sord_node_get_string(resource->node));
1145 return -1;
1146 }
1147
1148 SordModel* files = lilv_world_filter_model(
1149 world, world->model, resource->node, world->uris.rdfs_seeAlso, NULL, NULL);
1150
1151 SordIter* f = sord_begin(files);
1152 int n_dropped = 0;
1153 FOREACH_MATCH (f) {
1154 const SordNode* file = sord_iter_get_node(f, SORD_OBJECT);
1155 LilvNode* file_node = lilv_node_new_from_node(world, file);
1156 if (sord_node_get_type(file) != SORD_URI) {
1157 LILV_ERRORF("rdfs:seeAlso node `%s' is not a URI\n",
1158 sord_node_get_string(file));
1159 } else if (!lilv_world_drop_graph(world, file_node->node)) {
1160 lilv_world_unload_file(world, file_node);
1161 ++n_dropped;
1162 }
1163 lilv_node_free(file_node);
1164 }
1165 sord_iter_free(f);
1166
1167 sord_free(files);
1168 return n_dropped;
1169 }
1170
1171 const LilvPluginClass*
lilv_world_get_plugin_class(const LilvWorld * world)1172 lilv_world_get_plugin_class(const LilvWorld* world)
1173 {
1174 return world->lv2_plugin_class;
1175 }
1176
1177 const LilvPluginClasses*
lilv_world_get_plugin_classes(const LilvWorld * world)1178 lilv_world_get_plugin_classes(const LilvWorld* world)
1179 {
1180 return world->plugin_classes;
1181 }
1182
1183 const LilvPlugins*
lilv_world_get_all_plugins(const LilvWorld * world)1184 lilv_world_get_all_plugins(const LilvWorld* world)
1185 {
1186 return world->plugins;
1187 }
1188
1189 LilvNode*
lilv_world_get_symbol(LilvWorld * world,const LilvNode * subject)1190 lilv_world_get_symbol(LilvWorld* world, const LilvNode* subject)
1191 {
1192 // Check for explicitly given symbol
1193 SordNode* snode =
1194 sord_get(world->model, subject->node, world->uris.lv2_symbol, NULL, NULL);
1195
1196 if (snode) {
1197 LilvNode* ret = lilv_node_new_from_node(world, snode);
1198 sord_node_free(world->world, snode);
1199 return ret;
1200 }
1201
1202 if (!lilv_node_is_uri(subject)) {
1203 return NULL;
1204 }
1205
1206 // Find rightmost segment of URI
1207 SerdURI uri;
1208 serd_uri_parse((const uint8_t*)lilv_node_as_uri(subject), &uri);
1209 const char* str = "_";
1210 if (uri.fragment.buf) {
1211 str = (const char*)uri.fragment.buf + 1;
1212 } else if (uri.query.buf) {
1213 str = (const char*)uri.query.buf;
1214 } else if (uri.path.buf) {
1215 const char* last_slash = strrchr((const char*)uri.path.buf, '/');
1216 str = last_slash ? (last_slash + 1) : (const char*)uri.path.buf;
1217 }
1218
1219 // Replace invalid characters
1220 const size_t len = strlen(str);
1221 char* const sym = (char*)calloc(1, len + 1);
1222 for (size_t i = 0; i < len; ++i) {
1223 const char c = str[i];
1224 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') ||
1225 (i > 0 && c >= '0' && c <= '9'))) {
1226 sym[i] = '_';
1227 } else {
1228 sym[i] = str[i];
1229 }
1230 }
1231
1232 LilvNode* ret = lilv_new_string(world, sym);
1233 free(sym);
1234 return ret;
1235 }
1236