/* This file is part of Ingen. * Copyright (C) 2007 Dave Robillard * * Ingen is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include // pair, make_pair #include #include #include #include // atof #include #include #include #include "Serializer.h" #include "PatchModel.h" #include "NodeModel.h" #include "ConnectionModel.h" #include "PortModel.h" #include "PresetModel.h" #include "ModelEngineInterface.h" #include "PluginModel.h" #include "raul/Path.h" #include "raul/Atom.h" #include "raul/AtomRaptor.h" using std::string; using std::vector; using std::pair; using std::cerr; using std::cout; using std::endl; using boost::optional; using namespace Raul; namespace Ingen { namespace Client { Serializer::Serializer() { _writer.add_prefix("ingen", "http://codeson.net/ns/ingen#"); _writer.add_prefix("ingenuity", "http://codeson.net/ns/ingenuity#"); _writer.add_prefix("lv2", "http://lv2plug.in/ontology#"); } /** Begin a serialization to a file. * * This must be called before any serializing methods. */ void Serializer::start_to_filename(const string& filename) throw (std::logic_error) { _writer.start_to_filename(filename); } /** Begin a serialization to a string. * * This must be called before any serializing methods. * * The results of the serialization will be returned by the finish() method after * the desired objects have been serialized. */ void Serializer::start_to_string() throw (std::logic_error) { _writer.start_to_string(); } /** Finish a serialization. * * If this was a serialization to a string, the serialization output * will be returned, otherwise the empty string is returned. */ string Serializer::finish() throw(std::logic_error) { return _writer.finish(); } /** Convert a path to an RDF blank node ID for serializing. */ RdfId Serializer::path_to_node_id(const Path& path) { string ret = path.substr(1); for (size_t i=0; i < ret.length(); ++i) { if (ret[i] == '/') ret[i] = '_'; } return RdfId(RdfId::ANONYMOUS, ret); } #if 0 /** Searches for the filename passed in the path, returning the full * path of the file, or the empty string if not found. * * This function tries to be as friendly a black box as possible - if the path * passed is an absolute path and the file is found there, it will return * that path, etc. * * additional_path is a list (colon delimeted as usual) of additional * directories to look in. ie the directory the parent patch resides in would * be a good idea to pass as additional_path, in the case of a subpatch. */ string Serializer::find_file(const string& filename, const string& additional_path) { string search_path = additional_path + ":" + _patch_search_path; // Try to open the raw filename first std::ifstream is(filename.c_str(), std::ios::in); if (is.good()) { is.close(); return filename; } string directory; string full_patch_path = ""; while (search_path != "") { directory = search_path.substr(0, search_path.find(':')); if (search_path.find(':') != string::npos) search_path = search_path.substr(search_path.find(':')+1); else search_path = ""; full_patch_path = directory +"/"+ filename; std::ifstream is; is.open(full_patch_path.c_str(), std::ios::in); if (is.good()) { is.close(); return full_patch_path; } else { cerr << "[Serializer] Could not find patch file " << full_patch_path << endl; } } return ""; } #endif void Serializer::serialize(SharedPtr object) throw (std::logic_error) { if (!_writer.serialization_in_progress()) throw std::logic_error("serialize called without serialization in progress"); // FIXME: depth SharedPtr patch = PtrCast(object); if (patch) { serialize_patch(patch, 0); return; } SharedPtr node = PtrCast(object); if (node) { serialize_node(node, 0); return; } SharedPtr port = PtrCast(object); if (port) { serialize_port(port, 0); return; } cerr << "[Serializer] WARNING: Unsupported object type, " << object->path() << " not serialized." << endl; } void Serializer::serialize_patch(SharedPtr patch, unsigned depth) { RdfId patch_id = path_to_node_id(patch->path()); // anonymous if (patch->path().length() < 2) patch_id = RdfId(RdfId::RESOURCE, string("")); else if (depth == 0) patch_id = RdfId(RdfId::RESOURCE, string("#") + patch->path().substr(1)); _writer.write( patch_id, NS_RDF("type"), NS_INGEN("Patch")); if (patch->path().name().length() > 0) { _writer.write( patch_id, NS_INGEN("name"), Atom(patch->path().name().c_str())); } _writer.write( patch_id, NS_INGEN("polyphony"), Atom((int)patch->poly())); for (NodeModelMap::const_iterator n = patch->nodes().begin(); n != patch->nodes().end(); ++n) { _writer.write(patch_id, NS_INGEN("node"), path_to_node_id(n->second->path())); serialize_node(n->second, depth+1); } for (PortModelList::const_iterator p = patch->ports().begin(); p != patch->ports().end(); ++p) { _writer.write(patch_id, NS_INGEN("port"), path_to_node_id((*p)->path())); serialize_port(*p, depth+1); } for (ConnectionList::const_iterator c = patch->connections().begin(); c != patch->connections().end(); ++c) { serialize_connection(*c); } } void Serializer::serialize_plugin(SharedPtr plugin) { const RdfId plugin_id = RdfId(RdfId::RESOURCE, plugin->uri()); _writer.write( plugin_id, NS_RDF("type"), RdfId(RdfId::RESOURCE, plugin->type_uri())); } void Serializer::serialize_node(SharedPtr node, unsigned depth) { const RdfId node_id = (depth == 0) ? RdfId(RdfId::RESOURCE, string("#") + node->path().substr(1)) : path_to_node_id(node->path()); // anonymous const RdfId plugin_id = RdfId(RdfId::RESOURCE, node->plugin()->uri()); _writer.write( node_id, NS_RDF("type"), NS_INGEN("Node")); _writer.write( node_id, NS_INGEN("name"), node->path().name()); _writer.write( node_id, NS_INGEN("plugin"), plugin_id); //serialize_plugin(node->plugin()); /*_writer.write(_serializer, node_uri_ref.c_str(), NS_INGEN("name"), Atom(node->path().name()));*/ for (PortModelList::const_iterator p = node->ports().begin(); p != node->ports().end(); ++p) { serialize_port(*p, depth+1); _writer.write(node_id, NS_INGEN("port"), path_to_node_id((*p)->path())); } for (MetadataMap::const_iterator m = node->metadata().begin(); m != node->metadata().end(); ++m) { if (_writer.expand_uri(m->first) != "") { _writer.write( node_id, RdfId(RdfId::RESOURCE, _writer.expand_uri(m->first.c_str()).c_str()), m->second); } } } /** Writes a port subject with various information only if there are some * predicate/object pairs to go with it (eg if the port has metadata, or a value, or..). * Audio output ports with no metadata will not be written, for example. */ void Serializer::serialize_port(SharedPtr port, unsigned depth) { const RdfId port_id = (depth == 0) ? RdfId(RdfId::RESOURCE, string("#") + port->path().substr(1)) : path_to_node_id(port->path()); // anonymous if (port->is_input()) _writer.write(port_id, NS_RDF("type"), NS_INGEN("InputPort")); else _writer.write(port_id, NS_RDF("type"), NS_INGEN("OutputPort")); _writer.write(port_id, NS_INGEN("name"), Atom(port->path().name().c_str())); _writer.write(port_id, NS_INGEN("dataType"), Atom(port->type())); if (port->is_control() && port->is_input()) _writer.write(port_id, NS_INGEN("value"), Atom(port->value())); if (port->metadata().size() > 0) { for (MetadataMap::const_iterator m = port->metadata().begin(); m != port->metadata().end(); ++m) { if (_writer.expand_uri(m->first) != "") { _writer.write( port_id, RdfId(RdfId::RESOURCE, _writer.expand_uri(m->first).c_str()), m->second); } } } } void Serializer::serialize_connection(SharedPtr connection) throw (std::logic_error) { const RdfId connection_id = RdfId(RdfId::ANONYMOUS, path_to_node_id(connection->src_port_path()).to_string() + "-" + path_to_node_id(connection->dst_port_path()).to_string()); _writer.write(path_to_node_id(connection->dst_port_path()), NS_INGEN("connectedTo"), path_to_node_id(connection->src_port_path())); /*_writer.write(connection_id, NS_RDF("type"), NS_INGEN("Connection")); _writer.write(connection_id, NS_INGEN("source"), path_to_node_id(connection->src_port_path())); _writer.write(connection_id, NS_INGEN("destination"), path_to_node_id(connection->dst_port_path())); */ } /** Load a patch into the engine (e.g. from a patch file). * * @param base_uri Base URI (e.g. URI of the file to load from). * * @param data_path Path of the patch relative to base_uri * (e.g. relative to file root patch). * * @param engine_path Path in the engine for the newly created patch * (i.e. 'destination' of the load). * * @param initial_data will be set last, so values passed there will override * any values loaded from the patch file (or values in the existing patch). * * @param merge If true, the loaded patch's contents will be loaded into a * currently existing patch. Errors may result if Nodes of conflicting names * (etc) exist - the loaded patch will take precendence (ie overwriting * existing values). If false and the patch at @a load_path already exists, * load is aborted and false is returned. * * @param poly Polyphony of new loaded patch if given, otherwise polyphony * will be loaded (unless saved polyphony isn't found, in which case it's 1). * * Returns whether or not patch was loaded. */ bool Serializer::load_patch(bool merge, const string& data_base_uri, const Path& data_path, MetadataMap engine_data, optional engine_parent, optional engine_name, optional engine_poly) { cerr << "LOAD: " << data_base_uri << ", " << data_path << ", " << engine_parent << endl; #if 0 cerr << "[Serializer] Loading patch " << filename << "" << endl; Path path = "/"; // path of the new patch const bool load_name = (name == ""); const bool load_poly = (poly == 0); if (initial_data.find("filename") == initial_data.end()) initial_data["filename"] = Atom(filename.c_str()); // FIXME: URL? xmlDocPtr doc = xmlParseFile(filename.c_str()); if (!doc) { cerr << "Unable to parse patch file." << endl; return ""; } xmlNodePtr cur = xmlDocGetRootElement(doc); if (!cur) { cerr << "Empty document." << endl; xmlFreeDoc(doc); return ""; } if (xmlStrcmp(cur->name, (const xmlChar*) "patch")) { cerr << "File is not an Ingen patch file (root node != )" << endl; xmlFreeDoc(doc); return ""; } xmlChar* key = NULL; cur = cur->xmlChildrenNode; // Load Patch attributes while (cur != NULL) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); if ((!xmlStrcmp(cur->name, (const xmlChar*)"name"))) { if (load_name) { assert(key != NULL); if (parent_path != "") path = Path(parent_path).base() + Path::nameify((char*)key); } } else if ((!xmlStrcmp(cur->name, (const xmlChar*)"polyphony"))) { if (load_poly) { poly = atoi((char*)key); } } else if (xmlStrcmp(cur->name, (const xmlChar*)"connection") && xmlStrcmp(cur->name, (const xmlChar*)"node") && xmlStrcmp(cur->name, (const xmlChar*)"subpatch") && xmlStrcmp(cur->name, (const xmlChar*)"filename") && xmlStrcmp(cur->name, (const xmlChar*)"preset")) { // Don't know what this tag is, add it as metadata without overwriting // (so caller can set arbitrary parameters which will be preserved) if (key) if (initial_data.find((const char*)cur->name) == initial_data.end()) initial_data[(const char*)cur->name] = (const char*)key; } xmlFree(key); key = NULL; // Avoid a (possible?) double free cur = cur->next; } if (poly == 0) poly = 1; // Create it, if we're not merging if (!existing) _engine->create_patch_with_data(path, poly, initial_data); // Load nodes cur = xmlDocGetRootElement(doc)->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar*)"node"))) load_node(path, doc, cur); cur = cur->next; } // Load subpatches cur = xmlDocGetRootElement(doc)->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar*)"subpatch"))) { load_subpatch(path, doc, cur); } cur = cur->next; } // Load connections cur = xmlDocGetRootElement(doc)->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar*)"connection"))) { load_connection(path, doc, cur); } cur = cur->next; } // Load presets (control values) cerr << "FIXME: load preset\n"; /*cur = xmlDocGetRootElement(doc)->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar*)"preset"))) { load_preset(pm, doc, cur); assert(preset_model != NULL); if (preset_model->name() == "default") _engine->set_preset(pm->path(), preset_model); } cur = cur->next; } */ xmlFreeDoc(doc); xmlCleanupParser(); // Done above.. late enough? //_engine->set_metadata_map(path, initial_data); if (!existing) _engine->enable_patch(path); _load_path_translations.clear(); return path; #endif return "/FIXME"; } } // namespace Client } // namespace Ingen