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