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