/* Copyright 2007-2011 David Robillard Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define _XOPEN_SOURCE 500 #include #include #include #include #ifdef SLV2_DYN_MANIFEST #include #endif #include "slv2-config.h" #include "slv2_internal.h" #ifdef HAVE_SUIL #include "suil/suil.h" #endif #define NS_UI (const uint8_t*)"http://lv2plug.in/ns/extensions/ui#" #define NS_DOAP (const uint8_t*)"http://usefulinc.com/ns/doap#" #define NS_FOAF (const uint8_t*)"http://xmlns.com/foaf/0.1/" /** Ownership of @a uri is taken */ SLV2Plugin slv2_plugin_new(SLV2World world, SLV2Value uri, SLV2Value bundle_uri) { assert(bundle_uri); struct _SLV2Plugin* plugin = malloc(sizeof(struct _SLV2Plugin)); plugin->world = world; plugin->plugin_uri = uri; plugin->bundle_uri = bundle_uri; plugin->binary_uri = NULL; #ifdef SLV2_DYN_MANIFEST plugin->dynman_uri = NULL; #endif plugin->plugin_class = NULL; plugin->data_uris = slv2_values_new(); plugin->ports = NULL; plugin->num_ports = 0; plugin->loaded = false; plugin->replaced = false; return plugin; } void slv2_plugin_free(SLV2Plugin p) { slv2_value_free(p->plugin_uri); p->plugin_uri = NULL; slv2_value_free(p->bundle_uri); p->bundle_uri = NULL; slv2_value_free(p->binary_uri); p->binary_uri = NULL; #ifdef SLV2_DYN_MANIFEST slv2_value_free(p->dynman_uri); p->dynman_uri = NULL; #endif if (p->ports) { for (uint32_t i = 0; i < p->num_ports; ++i) slv2_port_free(p->ports[i]); free(p->ports); p->ports = NULL; } slv2_values_free(p->data_uris); p->data_uris = NULL; free(p); } static SLV2Values slv2_plugin_query_node(SLV2Plugin p, SLV2Node subject, SLV2Node predicate) { slv2_plugin_load_if_necessary(p); // ?value SLV2Matches results = slv2_plugin_find_statements( p, subject, predicate, NULL); if (slv2_matches_end(results)) { slv2_match_end(results); return NULL; } SLV2Values result = slv2_values_new(); FOREACH_MATCH(results) { SLV2Node node = slv2_match_object(results); SLV2Value value = slv2_value_new_from_node(p->world, node); if (value) slv2_array_append(result, value); } slv2_match_end(results); return result; } SLV2Value slv2_plugin_get_unique(SLV2Plugin p, SLV2Node subject, SLV2Node predicate) { SLV2Values values = slv2_plugin_query_node(p, subject, predicate); if (!values || slv2_values_size(values) != 1) { SLV2_ERRORF("Port does not have exactly one `%s' property\n", sord_node_get_string(predicate)); return NULL; } SLV2Value ret = slv2_value_duplicate(slv2_values_get_first(values)); slv2_values_free(values); return ret; } static SLV2Value slv2_plugin_get_one(SLV2Plugin p, SLV2Node subject, SLV2Node predicate) { SLV2Values values = slv2_plugin_query_node(p, subject, predicate); if (!values) { return NULL; } SLV2Value ret = slv2_value_duplicate(slv2_values_get_first(values)); slv2_values_free(values); return ret; } static void slv2_plugin_load(SLV2Plugin p) { // Parse all the plugin's data files into RDF model SLV2_FOREACH(i, p->data_uris) { SLV2Value data_uri_val = slv2_values_get(p->data_uris, i); sord_read_file(p->world->model, sord_node_get_string(data_uri_val->val.uri_val), p->bundle_uri->val.uri_val, slv2_world_blank_node_prefix(p->world)); } #ifdef SLV2_DYN_MANIFEST typedef void* LV2_Dyn_Manifest_Handle; // Load and parse dynamic manifest data, if this is a library if (p->dynman_uri) { const char* lib_path = slv2_uri_to_path(slv2_value_as_string(p->dynman_uri)); void* lib = dlopen(lib_path, RTLD_LAZY); if (!lib) { SLV2_WARNF("Unable to open dynamic manifest %s\n", slv2_value_as_string(p->dynman_uri)); return; } typedef int (*OpenFunc)(LV2_Dyn_Manifest_Handle*, const LV2_Feature *const *); OpenFunc open_func = (OpenFunc)slv2_dlfunc(lib, "lv2_dyn_manifest_open"); LV2_Dyn_Manifest_Handle handle = NULL; if (open_func) open_func(&handle, &dman_features); typedef int (*GetDataFunc)(LV2_Dyn_Manifest_Handle handle, FILE* fp, const char* uri); GetDataFunc get_data_func = (GetDataFunc)slv2_dlfunc( lib, "lv2_dyn_manifest_get_data"); if (get_data_func) { FILE* fd = tmpfile(); get_data_func(handle, fd, slv2_value_as_string(p->plugin_uri)); rewind(fd); sord_read_file_handle(p->world->model, fd, (const uint8_t*)slv2_value_as_uri(p->dynman_uri), p->bundle_uri->val.uri_val, slv2_world_blank_node_prefix(p->world)); fclose(fd); } typedef int (*CloseFunc)(LV2_Dyn_Manifest_Handle); CloseFunc close_func = (CloseFunc)slv2_dlfunc(lib, "lv2_dyn_manifest_close"); if (close_func) close_func(handle); } #endif p->loaded = true; } static void slv2_plugin_load_ports_if_necessary(SLV2Plugin p) { if (!p->loaded) slv2_plugin_load(p); if (!p->ports) { p->ports = malloc(sizeof(SLV2Port*)); p->ports[0] = NULL; SLV2Matches ports = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, p->world->lv2_port_node, NULL); FOREACH_MATCH(ports) { SLV2Value index = NULL; SLV2Node port = slv2_match_object(ports); SLV2Value symbol = slv2_plugin_get_unique( p, port, p->world->lv2_symbol_node); if (!slv2_value_is_string(symbol)) { SLV2_ERROR("port has a non-string symbol\n"); p->num_ports = 0; goto error; } index = slv2_plugin_get_unique(p, port, p->world->lv2_index_node); if (!slv2_value_is_int(index)) { SLV2_ERROR("port has a non-integer index\n"); p->num_ports = 0; goto error; } uint32_t this_index = slv2_value_as_int(index); SLV2Port this_port = NULL; if (p->num_ports > this_index) { this_port = p->ports[this_index]; } else { p->ports = realloc(p->ports, (this_index + 1) * sizeof(SLV2Port*)); memset(p->ports + p->num_ports, '\0', (this_index - p->num_ports) * sizeof(SLV2Port)); p->num_ports = this_index + 1; } // Havn't seen this port yet, add it to array if (!this_port) { this_port = slv2_port_new(p->world, this_index, slv2_value_as_string(symbol)); p->ports[this_index] = this_port; } SLV2Matches types = slv2_plugin_find_statements( p, port, p->world->rdf_a_node, NULL); FOREACH_MATCH(types) { SLV2Node type = slv2_match_object(types); if (sord_node_get_type(type) == SORD_URI) { slv2_array_append( this_port->classes, slv2_value_new_from_node(p->world, type)); } else { SLV2_WARN("port has non-URI rdf:type\n"); } } slv2_match_end(types); error: slv2_value_free(symbol); slv2_value_free(index); if (p->num_ports == 0) { if (p->ports) { for (uint32_t i = 0; i < p->num_ports; ++i) slv2_port_free(p->ports[i]); free(p->ports); p->ports = NULL; } break; // Invalid plugin } } slv2_match_end(ports); } } void slv2_plugin_load_if_necessary(SLV2Plugin p) { if (!p->loaded) slv2_plugin_load(p); } SLV2_API SLV2Value slv2_plugin_get_uri(SLV2Plugin p) { assert(p); assert(p->plugin_uri); return p->plugin_uri; } SLV2_API SLV2Value slv2_plugin_get_bundle_uri(SLV2Plugin p) { assert(p); assert(p->bundle_uri); return p->bundle_uri; } SLV2_API SLV2Value slv2_plugin_get_library_uri(SLV2Plugin p) { slv2_plugin_load_if_necessary(p); if (!p->binary_uri) { // lv2:binary ?binary SLV2Matches results = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, p->world->lv2_binary_node, NULL); FOREACH_MATCH(results) { SLV2Node binary_node = slv2_match_object(results); if (sord_node_get_type(binary_node) == SORD_URI) { p->binary_uri = slv2_value_new_from_node(p->world, binary_node); break; } } slv2_match_end(results); } if (!p->binary_uri) { SLV2_WARNF("Plugin <%s> has no lv2:binary\n", slv2_value_as_uri(slv2_plugin_get_uri(p))); } return p->binary_uri; } SLV2_API SLV2Values slv2_plugin_get_data_uris(SLV2Plugin p) { return p->data_uris; } SLV2_API SLV2PluginClass slv2_plugin_get_class(SLV2Plugin p) { slv2_plugin_load_if_necessary(p); if (!p->plugin_class) { // a ?class SLV2Matches results = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, p->world->rdf_a_node, NULL); FOREACH_MATCH(results) { SLV2Node class_node = slv2_node_copy(slv2_match_object(results)); if (sord_node_get_type(class_node) != SORD_URI) { continue; } SLV2Value class = slv2_value_new_from_node(p->world, class_node); if ( ! slv2_value_equals(class, p->world->lv2_plugin_class->uri)) { SLV2PluginClass plugin_class = slv2_plugin_classes_get_by_uri( p->world->plugin_classes, class); slv2_node_free(p->world, class_node); if (plugin_class) { p->plugin_class = plugin_class; slv2_value_free(class); break; } } slv2_value_free(class); } slv2_match_end(results); if (p->plugin_class == NULL) p->plugin_class = p->world->lv2_plugin_class; } return p->plugin_class; } SLV2_API bool slv2_plugin_verify(SLV2Plugin plugin) { SLV2Values results = slv2_plugin_get_value_by_qname(plugin, "rdf:type"); if (!results) { return false; } slv2_values_free(results); results = slv2_plugin_get_value(plugin, plugin->world->doap_name_val); if (!results) { return false; } slv2_values_free(results); results = slv2_plugin_get_value_by_qname(plugin, "doap:license"); if (!results) { return false; } slv2_values_free(results); results = slv2_plugin_get_value_by_qname(plugin, "lv2:port"); if (!results) { return false; } slv2_values_free(results); return true; } SLV2_API SLV2Value slv2_plugin_get_name(SLV2Plugin plugin) { SLV2Values results = slv2_plugin_get_value(plugin, plugin->world->doap_name_val); SLV2Value ret = NULL; if (results) { SLV2Value val = slv2_values_get_first(results); if (slv2_value_is_string(val)) ret = slv2_value_duplicate(val); slv2_values_free(results); } if (!ret) SLV2_WARNF("<%s> has no (mandatory) doap:name\n", slv2_value_as_string(slv2_plugin_get_uri(plugin))); return ret; } SLV2_API SLV2Values slv2_plugin_get_value(SLV2Plugin p, SLV2Value predicate) { return slv2_plugin_get_value_for_subject(p, p->plugin_uri, predicate); } SLV2_API SLV2Values slv2_plugin_get_value_by_qname(SLV2Plugin p, const char* predicate) { char* pred_uri = (char*)slv2_qname_expand(p, predicate); if (!pred_uri) { return NULL; } SLV2Value pred_value = slv2_value_new_uri(p->world, pred_uri); SLV2Values ret = slv2_plugin_get_value(p, pred_value); slv2_value_free(pred_value); free(pred_uri); return ret; } SLV2_API SLV2Values slv2_plugin_get_value_for_subject(SLV2Plugin p, SLV2Value subject, SLV2Value predicate) { if ( ! slv2_value_is_uri(subject) && ! slv2_value_is_blank(subject)) { SLV2_ERROR("Subject is not a resource\n"); return NULL; } if ( ! slv2_value_is_uri(predicate)) { SLV2_ERROR("Predicate is not a URI\n"); return NULL; } SLV2Node subject_node = (slv2_value_is_uri(subject)) ? slv2_node_copy(subject->val.uri_val) : sord_new_blank(p->world->world, (const uint8_t*)slv2_value_as_blank(subject)); if (!subject_node) { fprintf(stderr, "No such subject\n"); return NULL; } return slv2_plugin_query_node(p, subject_node, predicate->val.uri_val); } SLV2_API uint32_t slv2_plugin_get_num_ports(SLV2Plugin p) { slv2_plugin_load_ports_if_necessary(p); return p->num_ports; } SLV2_API void slv2_plugin_get_port_ranges_float(SLV2Plugin p, float* min_values, float* max_values, float* def_values) { slv2_plugin_load_ports_if_necessary(p); for (uint32_t i = 0; i < p->num_ports; ++i) { SLV2Value def, min, max; slv2_port_get_range(p, p->ports[i], &def, &min, &max); if (min_values) min_values[i] = min ? slv2_value_as_float(min) : NAN; if (max_values) max_values[i] = max ? slv2_value_as_float(max) : NAN; if (def_values) def_values[i] = def ? slv2_value_as_float(def) : NAN; slv2_value_free(def); slv2_value_free(min); slv2_value_free(max); } } SLV2_API uint32_t slv2_plugin_get_num_ports_of_class(SLV2Plugin p, SLV2Value class_1, ...) { slv2_plugin_load_ports_if_necessary(p); uint32_t ret = 0; va_list args; for (unsigned i = 0; i < p->num_ports; ++i) { SLV2Port port = p->ports[i]; if (!port || !slv2_port_is_a(p, port, class_1)) continue; va_start(args, class_1); bool matches = true; for (SLV2Value class_i = NULL; (class_i = va_arg(args, SLV2Value)) != NULL ; ) { if (!slv2_port_is_a(p, port, class_i)) { va_end(args); matches = false; break; } } if (matches) ++ret; va_end(args); } return ret; } SLV2_API bool slv2_plugin_has_latency(SLV2Plugin p) { SLV2Matches ports = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, p->world->lv2_port_node, NULL); bool ret = false; FOREACH_MATCH(ports) { SLV2Node port = slv2_match_object(ports); SLV2Matches reports_latency = slv2_plugin_find_statements( p, port, p->world->lv2_portproperty_node, p->world->lv2_reportslatency_node); const bool end = slv2_matches_end(reports_latency); slv2_match_end(reports_latency); if (!end) { ret = true; break; } } slv2_match_end(ports); return ret; } SLV2_API uint32_t slv2_plugin_get_latency_port_index(SLV2Plugin p) { SLV2Matches ports = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, p->world->lv2_port_node, NULL); uint32_t ret = 0; FOREACH_MATCH(ports) { SLV2Node port = slv2_match_object(ports); SLV2Matches reports_latency = slv2_plugin_find_statements( p, port, p->world->lv2_portproperty_node, p->world->lv2_reportslatency_node); if (!slv2_matches_end(reports_latency)) { SLV2Value index = slv2_plugin_get_unique( p, port, p->world->lv2_index_node); ret = slv2_value_as_int(index); slv2_value_free(index); slv2_match_end(reports_latency); break; } slv2_match_end(reports_latency); } slv2_match_end(ports); return ret; // FIXME: error handling } SLV2_API bool slv2_plugin_has_feature(SLV2Plugin p, SLV2Value feature) { SLV2Values features = slv2_plugin_get_supported_features(p); const bool ret = features && feature && slv2_values_contains(features, feature); slv2_values_free(features); return ret; } SLV2_API SLV2Values slv2_plugin_get_supported_features(SLV2Plugin p) { SLV2Values optional = slv2_plugin_get_optional_features(p); SLV2Values required = slv2_plugin_get_required_features(p); SLV2Values result = slv2_values_new(); SLV2_FOREACH(i, optional) slv2_array_append( result, slv2_value_duplicate(slv2_values_get(optional, i))); SLV2_FOREACH(i, required) slv2_array_append( result, slv2_value_duplicate(slv2_values_get(required, i))); slv2_values_free(optional); slv2_values_free(required); return result; } SLV2_API SLV2Values slv2_plugin_get_optional_features(SLV2Plugin p) { return slv2_plugin_get_value_by_qname(p, "lv2:optionalFeature"); } SLV2_API SLV2Values slv2_plugin_get_required_features(SLV2Plugin p) { return slv2_plugin_get_value_by_qname(p, "lv2:requiredFeature"); } SLV2_API SLV2Port slv2_plugin_get_port_by_index(SLV2Plugin p, uint32_t index) { slv2_plugin_load_ports_if_necessary(p); if (index < p->num_ports) return p->ports[index]; else return NULL; } SLV2_API SLV2Port slv2_plugin_get_port_by_symbol(SLV2Plugin p, SLV2Value symbol) { slv2_plugin_load_ports_if_necessary(p); for (uint32_t i = 0; i < p->num_ports; ++i) { SLV2Port port = p->ports[i]; if (slv2_value_equals(port->symbol, symbol)) return port; } return NULL; } static SLV2Node slv2_plugin_get_author(SLV2Plugin p) { SLV2Node doap_maintainer = sord_new_uri( p->world->world, NS_DOAP "maintainer"); SLV2Matches maintainers = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, doap_maintainer, NULL); slv2_node_free(p->world, doap_maintainer); if (slv2_matches_end(maintainers)) { return NULL; } SLV2Node author = slv2_node_copy(slv2_match_object(maintainers)); slv2_match_end(maintainers); return author; } SLV2_API SLV2Value slv2_plugin_get_author_name(SLV2Plugin plugin) { SLV2Node author = slv2_plugin_get_author(plugin); if (author) { return slv2_plugin_get_one( plugin, author, sord_new_uri( plugin->world->world, NS_FOAF "name")); } return NULL; } SLV2_API SLV2Value slv2_plugin_get_author_email(SLV2Plugin plugin) { SLV2Node author = slv2_plugin_get_author(plugin); if (author) { return slv2_plugin_get_one( plugin, author, sord_new_uri( plugin->world->world, NS_FOAF "mbox")); } return NULL; } SLV2_API SLV2Value slv2_plugin_get_author_homepage(SLV2Plugin plugin) { SLV2Node author = slv2_plugin_get_author(plugin); if (author) { return slv2_plugin_get_one( plugin, author, sord_new_uri( plugin->world->world, NS_FOAF "homepage")); } return NULL; } SLV2_API bool slv2_plugin_is_replaced(SLV2Plugin plugin) { return plugin->replaced; } SLV2_API SLV2UIs slv2_plugin_get_uis(SLV2Plugin p) { SLV2Node ui_ui_node = sord_new_uri(p->world->world, NS_UI "ui"); SLV2Node ui_binary_node = sord_new_uri(p->world->world, NS_UI "binary"); SLV2UIs result = slv2_uis_new(); SLV2Matches uis = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, ui_ui_node, NULL); FOREACH_MATCH(uis) { SLV2Node ui = slv2_match_object(uis); SLV2Value type = slv2_plugin_get_unique(p, ui, p->world->rdf_a_node); SLV2Value binary = slv2_plugin_get_unique(p, ui, ui_binary_node); if (sord_node_get_type(ui) != SORD_URI || !slv2_value_is_uri(type) || !slv2_value_is_uri(binary)) { slv2_value_free(binary); slv2_value_free(type); SLV2_ERROR("Corrupt UI\n"); continue; } SLV2UI slv2_ui = slv2_ui_new( p->world, slv2_value_new_from_node(p->world, ui), type, binary); slv2_sequence_insert(result, slv2_ui); } slv2_match_end(uis); slv2_node_free(p->world, ui_binary_node); slv2_node_free(p->world, ui_ui_node); if (slv2_uis_size(result) > 0) { return result; } else { slv2_uis_free(result); return NULL; } } SLV2_API SLV2UI slv2_plugin_get_default_ui(SLV2Plugin p, SLV2Value widget_type_uri) { #ifdef HAVE_SUIL SLV2Node ui_ui_node = sord_new_uri(p->world->world, NS_UI "ui"); SLV2Node ui_binary_node = sord_new_uri(p->world->world, NS_UI "binary"); SLV2Matches uis = slv2_plugin_find_statements( p, p->plugin_uri->val.uri_val, ui_ui_node, NULL); SLV2UI native = NULL; SLV2UI foreign = NULL; FOREACH_MATCH(uis) { SLV2Node ui = slv2_match_object(uis); SLV2Value type = slv2_plugin_get_unique(p, ui, p->world->rdf_a_node); SLV2Value binary = slv2_plugin_get_unique(p, ui, ui_binary_node); if (sord_node_get_type(ui) != SORD_URI || !slv2_value_is_uri(type) || !slv2_value_is_uri(binary)) { slv2_value_free(binary); slv2_value_free(type); SLV2_ERROR("Corrupt UI\n"); continue; } if (!native && slv2_value_equals(type, widget_type_uri)) { native = slv2_ui_new( p->world, slv2_value_new_from_node(p->world, ui), type, binary); break; } else if (!foreign && suil_ui_type_supported( slv2_value_as_uri(widget_type_uri), slv2_value_as_uri(type))) { foreign = slv2_ui_new( p->world, slv2_value_new_from_node(p->world, ui), type, binary); } else { slv2_value_free(binary); slv2_value_free(type); } } slv2_match_end(uis); slv2_node_free(p->world, ui_binary_node); slv2_node_free(p->world, ui_ui_node); return (native) ? native : foreign; #else return NULL; #endif }