From 0f9c8151d5b42b243a499bb31a1e1f0b2e8c5f6f Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 8 Feb 2015 07:02:59 +0000 Subject: Server-side copy paste with LV2 state support. git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@5541 a436a847-0d15-0410-975c-d299462d15a1 --- src/AtomWriter.cpp | 14 ++++ src/ClashAvoider.cpp | 71 ++-------------- src/Store.cpp | 2 +- src/URIs.cpp | 1 + src/client/BlockModel.cpp | 2 +- src/client/ClientStore.cpp | 24 ++++-- src/gui/App.cpp | 2 +- src/gui/ConnectWindow.cpp | 2 +- src/gui/GraphCanvas.cpp | 142 +++++++++++++++++++------------- src/gui/ThreadedLoader.cpp | 9 ++- src/serialisation/Parser.cpp | 156 +++++++++++++++++++----------------- src/serialisation/Serialiser.cpp | 39 +++------ src/server/BlockImpl.hpp | 6 ++ src/server/Broadcaster.hpp | 5 ++ src/server/DuplexPort.cpp | 19 +++++ src/server/DuplexPort.hpp | 4 + src/server/EventWriter.cpp | 9 +++ src/server/EventWriter.hpp | 3 + src/server/GraphImpl.cpp | 63 +++++++++++++++ src/server/GraphImpl.hpp | 4 + src/server/LV2Block.cpp | 41 ++++++++++ src/server/LV2Block.hpp | 4 + src/server/PortImpl.cpp | 2 +- src/server/events.hpp | 1 + src/server/events/Copy.cpp | 130 ++++++++++++++++++++++++++++++ src/server/events/Copy.hpp | 73 +++++++++++++++++ src/server/events/CreateBlock.cpp | 93 +++++++++++---------- src/server/events/CreateBlock.hpp | 19 ++--- src/server/events/CreateGraph.cpp | 47 ++++++++--- src/server/events/CreateGraph.hpp | 16 ++-- src/server/events/CreatePort.cpp | 30 +++---- src/server/events/Delta.cpp | 67 +++++++++------- src/server/events/DisconnectAll.cpp | 3 +- src/server/events/Get.cpp | 20 +++-- src/server/events/Get.hpp | 15 +++- src/server/ingen_lv2.cpp | 4 +- src/server/wscript | 1 + 37 files changed, 767 insertions(+), 376 deletions(-) create mode 100644 src/server/events/Copy.cpp create mode 100644 src/server/events/Copy.hpp (limited to 'src') diff --git a/src/AtomWriter.cpp b/src/AtomWriter.cpp index 7c956ece..eaf45243 100644 --- a/src/AtomWriter.cpp +++ b/src/AtomWriter.cpp @@ -167,6 +167,20 @@ AtomWriter::delta(const Raul::URI& uri, finish_msg(); } +void +AtomWriter::copy(const Raul::Path& old_path, + const Raul::URI& new_uri) +{ + LV2_Atom_Forge_Frame msg; + forge_request(&msg, _uris.patch_Copy); + lv2_atom_forge_key(&_forge, _uris.patch_subject); + forge_uri(Node::path_to_uri(old_path)); + lv2_atom_forge_key(&_forge, _uris.patch_destination); + forge_uri(new_uri); + lv2_atom_forge_pop(&_forge, &msg); + finish_msg(); +} + void AtomWriter::move(const Raul::Path& old_path, const Raul::Path& new_path) diff --git a/src/ClashAvoider.cpp b/src/ClashAvoider.cpp index 734b7d04..ba967291 100644 --- a/src/ClashAvoider.cpp +++ b/src/ClashAvoider.cpp @@ -26,6 +26,10 @@ using namespace std; namespace Ingen { +ClashAvoider::ClashAvoider(const Store& store) + : _store(store) +{} + const Raul::URI ClashAvoider::map_uri(const Raul::URI& in) { @@ -123,72 +127,7 @@ ClashAvoider::map_path(const Raul::Path& in) bool ClashAvoider::exists(const Raul::Path& path) const { - bool exists = (_store.find(path) != _store.end()); - if (exists) - return true; - - if (_also_avoid) - return (_also_avoid->find(path) != _also_avoid->end()); - else - return false; -} - -void -ClashAvoider::put(const Raul::URI& path, - const Resource::Properties& properties, - Resource::Graph ctx) -{ - _target.put(map_uri(path), properties, ctx); -} - -void -ClashAvoider::delta(const Raul::URI& path, - const Resource::Properties& remove, - const Resource::Properties& add) -{ - _target.delta(map_uri(path), remove, add); -} - -void -ClashAvoider::move(const Raul::Path& old_path, - const Raul::Path& new_path) -{ - _target.move(map_path(old_path), map_path(new_path)); -} - -void -ClashAvoider::connect(const Raul::Path& tail, - const Raul::Path& head) -{ - _target.connect(map_path(tail), map_path(head)); -} - -void -ClashAvoider::disconnect(const Raul::Path& tail, - const Raul::Path& head) -{ - _target.disconnect(map_path(tail), map_path(head)); -} - -void -ClashAvoider::disconnect_all(const Raul::Path& graph, - const Raul::Path& path) -{ - _target.disconnect_all(map_path(graph), map_path(path)); -} - -void -ClashAvoider::set_property(const Raul::URI& subject, - const Raul::URI& predicate, - const Atom& value) -{ - _target.set_property(map_uri(subject), predicate, value); -} - -void -ClashAvoider::del(const Raul::URI& uri) -{ - _target.del(map_uri(uri)); + return _store.find(path) != _store.end(); } } // namespace Ingen diff --git a/src/Store.cpp b/src/Store.cpp index 560c2de9..f3eac729 100644 --- a/src/Store.cpp +++ b/src/Store.cpp @@ -118,7 +118,7 @@ Store::rename(const iterator top, const Raul::Path& new_path) unsigned Store::child_name_offset(const Raul::Path& parent, const Raul::Symbol& symbol, - bool allow_zero) + bool allow_zero) const { unsigned offset = 0; diff --git a/src/URIs.cpp b/src/URIs.cpp index 6d41482d..647c63b5 100644 --- a/src/URIs.cpp +++ b/src/URIs.cpp @@ -120,6 +120,7 @@ URIs::URIs(Forge& f, URIMap* map) , midi_noteNumber (forge, map, LV2_MIDI__noteNumber) , morph_currentType (forge, map, LV2_MORPH__currentType) , param_sampleRate (forge, map, LV2_PARAMETERS__sampleRate) + , patch_Copy (forge, map, LV2_PATCH__Copy) , patch_Delete (forge, map, LV2_PATCH__Delete) , patch_Get (forge, map, LV2_PATCH__Get) , patch_Move (forge, map, LV2_PATCH__Move) diff --git a/src/client/BlockModel.cpp b/src/client/BlockModel.cpp index f667cb3c..69aa9627 100644 --- a/src/client/BlockModel.cpp +++ b/src/client/BlockModel.cpp @@ -174,7 +174,7 @@ BlockModel::default_port_value_range(SPtr port, max = 1.0; // Get range from client-side LV2 data - if (_plugin && _plugin->type() == PluginModel::LV2) { + if (_plugin && _plugin->type() == PluginModel::LV2 && _plugin->lilv_plugin()) { if (!_min_values) { _num_values = lilv_plugin_get_num_ports(_plugin->lilv_plugin()); _min_values = new float[_num_values]; diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp index 80f210fe..d6cd4248 100644 --- a/src/client/ClientStore.cpp +++ b/src/client/ClientStore.cpp @@ -33,11 +33,9 @@ namespace Client { ClientStore::ClientStore(URIs& uris, Log& log, - SPtr engine, SPtr emitter) : _uris(uris) , _log(log) - , _engine(engine) , _emitter(emitter) , _plugins(new Plugins()) { @@ -84,12 +82,13 @@ ClientStore::add_object(SPtr object) (*this)[object->path()] = object; _signal_new_object.emit(object); + } else { + _log.error(fmt("Object %1% with no parent\n") % object->path()); } } else { (*this)[object->path()] = object; _signal_new_object.emit(object); } - } for (auto p : object->properties()) @@ -208,6 +207,13 @@ ClientStore::del(const Raul::URI& uri) } } +void +ClientStore::copy(const Raul::Path& old_path, + const Raul::URI& new_uri) +{ + _log.error("Client store copy unsupported\n"); +} + void ClientStore::move(const Raul::Path& old_path, const Raul::Path& new_path) { @@ -291,7 +297,7 @@ ClientStore::put(const Raul::URI& uri, bm->set_properties(properties); add_object(bm); } else { - _log.warn(fmt("Block %1% has no plugin\n") + _log.warn(fmt("Block %1% has no prototype\n") % path.c_str()); } } else if (is_port) { @@ -302,8 +308,6 @@ ClientStore::put(const Raul::URI& uri, const Iterator i = properties.find(_uris.lv2_index); if (i != properties.end() && i->second.type() == _uris.forge.Int) { index = i->second.get(); - } else { - _log.error(fmt("Port %1% has no index\n") % path); } SPtr p(new PortModel(uris(), path, index, pdir)); @@ -443,6 +447,10 @@ void ClientStore::connect(const Raul::Path& src_path, const Raul::Path& dst_path) { +#ifdef INGEN_CLIENT_STORE_DUMP + std::cerr << "Client connect " << src_path << " => " << dst_path << std::endl; +#endif + attempt_connection(src_path, dst_path); } @@ -450,6 +458,10 @@ void ClientStore::disconnect(const Raul::Path& src_path, const Raul::Path& dst_path) { +#ifdef INGEN_CLIENT_STORE_DUMP + std::cerr << "Client disconnect " << src_path << " => " << dst_path << std::endl; +#endif + SPtr tail = dynamic_ptr_cast(_object(src_path)); SPtr head = dynamic_ptr_cast(_object(dst_path)); diff --git a/src/gui/App.cpp b/src/gui/App.cpp index e6fe35dc..a524220e 100644 --- a/src/gui/App.cpp +++ b/src/gui/App.cpp @@ -152,7 +152,7 @@ App::attach(SPtr client) } _client = client; - _store = SPtr(new ClientStore(_world->uris(), _world->log(), _world->interface(), client)); + _store = SPtr(new ClientStore(_world->uris(), _world->log(), client)); _loader = SPtr(new ThreadedLoader(*this, _world->interface())); _graph_tree_window->init(*this, *_store); diff --git a/src/gui/ConnectWindow.cpp b/src/gui/ConnectWindow.cpp index fd85316c..1921c708 100644 --- a/src/gui/ConnectWindow.cpp +++ b/src/gui/ConnectWindow.cpp @@ -452,7 +452,7 @@ ConnectWindow::gtk_callback() _progress_label->set_text("Connected to engine"); _connect_stage = 0; // set ourselves up for next time (if there is one) _finished_connecting = true; - _app->interface()->set_response_id(0); + _app->interface()->set_response_id(1); return false; // deregister this callback } diff --git a/src/gui/GraphCanvas.cpp b/src/gui/GraphCanvas.cpp index e04baa5e..5a0c365a 100644 --- a/src/gui/GraphCanvas.cpp +++ b/src/gui/GraphCanvas.cpp @@ -14,6 +14,7 @@ along with Ingen. If not, see . */ +#include #include #include #include @@ -470,6 +471,10 @@ GraphCanvas::on_event(GdkEvent* event) default: break; } + case GDK_MOTION_NOTIFY: + _paste_count = 0; + break; + default: break; } @@ -564,96 +569,121 @@ serialise_arc(GanvEdge* arc, void* data) void GraphCanvas::copy_selection() { - static const char* base_uri = INGEN_NS "selection/"; Serialisation::Serialiser serialiser(*_app.world()); - serialiser.start_to_string(_graph->path(), base_uri); + serialiser.start_to_string(_graph->path(), _graph->base_uri()); for_each_selected_node(serialise_node, &serialiser); for_each_selected_edge(serialise_arc, &serialiser); - const std::string result = serialiser.finish(); - _paste_count = 0; - Glib::RefPtr clipboard = Gtk::Clipboard::get(); - clipboard->set_text(result); + clipboard->set_text(serialiser.finish()); + _paste_count = 0; } void GraphCanvas::paste() { - Glib::ustring str = Gtk::Clipboard::get()->wait_for_text(); + typedef Node::Properties::const_iterator PropIter; + + const Glib::ustring str = Gtk::Clipboard::get()->wait_for_text(); SPtr parser = _app.loader()->parser(); + const URIs& uris = _app.uris(); + const Raul::Path& parent = _graph->path(); if (!parser) { _app.log().error("Unable to load parser, paste unavailable\n"); return; } + // Prepare for paste clear_selection(); _pastees.clear(); ++_paste_count; - const URIs& uris = _app.uris(); - + // Make a client store to serve as clipboard ClientStore clipboard(_app.world()->uris(), _app.log()); clipboard.set_plugins(_app.store()->plugins()); - - // mkdir -p - string to_create = _graph->path().substr(1); - string created = "/"; - Resource::Properties props; - props.insert(make_pair(uris.rdf_type, - Resource::Property(uris.ingen_Graph))); - props.insert(make_pair(uris.ingen_polyphony, - _app.forge().make(int32_t(_graph->internal_poly())))); - clipboard.put(Node::root_uri(), props); - size_t first_slash; - while (to_create != "/" && !to_create.empty() - && (first_slash = to_create.find("/")) != string::npos) { - created += to_create.substr(0, first_slash); - assert(Raul::Path::is_valid(created)); - clipboard.put(Node::path_to_uri(Raul::Path(created)), props); - to_create = to_create.substr(first_slash + 1); + clipboard.put(Node::root_uri(), + {{uris.rdf_type, Resource::Property(uris.ingen_Graph)}}); + + // Parse clipboard text into clipboard store + boost::optional base_uri = parser->parse_string( + _app.world(), &clipboard, str, Node::root_uri()); + + // Figure out the copy graph base path + Raul::Path copy_root("/"); + if (base_uri) { + std::string base = *base_uri; + if (base[base.size() - 1] == '/') { + base = base.substr(0, base.size() - 1); + } + copy_root = Node::uri_to_path(Raul::URI(base)); } - if (!_graph->path().is_root()) - clipboard.put(_graph->uri(), props); - - boost::optional parent; - boost::optional symbol; - - if (!_graph->path().is_root()) { - parent = _graph->path(); + // Find the minimum x and y coordinate of objects to be pasted + float min_x = std::numeric_limits::max(); + float min_y = std::numeric_limits::max(); + for (const auto& c : clipboard) { + if (c.first.parent() == Raul::Path("/")) { + const Atom& x = c.second->get_property(uris.ingen_canvasX); + const Atom& y = c.second->get_property(uris.ingen_canvasY); + if (x.type() == uris.atom_Float) { + min_x = std::min(min_x, x.get()); + } + if (y.type() == uris.atom_Float) { + min_y = std::min(min_y, y.get()); + } + } } - ClashAvoider avoider(*_app.store().get(), clipboard, &clipboard); - static const char* base_uri = INGEN_NS "selection/"; - parser->parse_string(_app.world(), &avoider, str, base_uri, - parent, symbol); - - // Create objects + // Find canvas paste origin based on pointer position + int widget_point_x, widget_point_y, scroll_x, scroll_y; + widget().get_pointer(widget_point_x, widget_point_y); + get_scroll_offsets(scroll_x, scroll_y); + const int paste_x = widget_point_x + scroll_x + (20.0f * _paste_count); + const int paste_y = widget_point_y + scroll_y + (20.0f * _paste_count); + + // Put each top level object in the clipboard store + ClashAvoider avoider(*_app.store().get()); for (const auto& c : clipboard) { - if (_graph->path().is_root() && c.first.is_root()) + if (c.first.is_root() || c.first.parent() != Raul::Path("/")) { continue; + } - Node::Properties& props = c.second->properties(); - - Node::Properties::iterator x = props.find(uris.ingen_canvasX); - if (x != c.second->properties().end()) - x->second = _app.forge().make( - x->second.get() + (20.0f * _paste_count)); - - Node::Properties::iterator y = props.find(uris.ingen_canvasY); - if (y != c.second->properties().end()) - y->second = _app.forge().make( - y->second.get() + (20.0f * _paste_count)); + const SPtr node = c.second; + const Raul::Path& old_path = copy_root.child(node->path()); + const Raul::URI& old_uri = Node::path_to_uri(old_path); + const Raul::Path& new_path = avoider.map_path(parent.child(node->path())); + + Node::Properties props{{uris.ingen_prototype, + _app.forge().alloc_uri(old_uri)}}; + + // Set the same types + const auto t = node->properties().equal_range(uris.rdf_type); + props.insert(t.first, t.second); + + // Set coordinates so paste origin is at the mouse pointer + PropIter xi = node->properties().find(uris.ingen_canvasX); + PropIter yi = node->properties().find(uris.ingen_canvasY); + if (xi != node->properties().end()) { + const float x = xi->second.get() - min_x + paste_x; + props.insert({xi->first, Resource::Property(_app.forge().make(x), + xi->second.context())}); + } + if (yi != node->properties().end()) { + const float y = yi->second.get() - min_y + paste_y; + props.insert({yi->first, Resource::Property(_app.forge().make(y), + yi->second.context())}); + } - _app.interface()->put(c.second->uri(), c.second->properties()); - _pastees.insert(c.first); + _app.interface()->put(Node::path_to_uri(new_path), props); + _pastees.insert(new_path); } // Connect objects - for (auto a : clipboard.object(_graph->path())->arcs()) { - _app.interface()->connect(a.second->tail_path(), a.second->head_path()); + for (auto a : clipboard.object(Raul::Path("/"))->arcs()) { + _app.interface()->connect( + avoider.map_path(parent.child(a.second->tail_path())), + avoider.map_path(parent.child(a.second->head_path()))); } } diff --git a/src/gui/ThreadedLoader.cpp b/src/gui/ThreadedLoader.cpp index 3dffa7f9..93cccdf1 100644 --- a/src/gui/ThreadedLoader.cpp +++ b/src/gui/ThreadedLoader.cpp @@ -127,10 +127,13 @@ ThreadedLoader::save_graph_event(SPtr model, const string& filename) { if (_app.serialiser()) { - if (filename.find(".ingen") != string::npos) + if (filename.find(".ingen") != string::npos) { _app.serialiser()->write_bundle(model, filename); - else - _app.serialiser()->to_file(model, filename); + } else { + _app.serialiser()->start_to_file(model->path(), filename); + _app.serialiser()->serialise(model); + _app.serialiser()->finish(); + } } } diff --git a/src/serialisation/Parser.cpp b/src/serialisation/Parser.cpp index 6a8547c5..663a7833 100644 --- a/src/serialisation/Parser.cpp +++ b/src/serialisation/Parser.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include "ingen/Atom.hpp" #include "ingen/Interface.hpp" @@ -43,10 +42,10 @@ typedef set RDFNodes; namespace Ingen { namespace Serialisation { -static Glib::ustring -relative_uri(Glib::ustring base, const Glib::ustring uri, bool leading_slash) +static std::string +relative_uri(const std::string& base, const std::string& uri, bool leading_slash) { - Glib::ustring ret; + std::string ret; if (uri != base) { SerdURI base_uri; serd_uri_parse((const uint8_t*)base.c_str(), &base_uri); @@ -55,21 +54,19 @@ relative_uri(Glib::ustring base, const Glib::ustring uri, bool leading_slash) SerdNode normal_base_uri_node = serd_node_new_uri_from_string( (const uint8_t*)".", &base_uri, &normal_base_uri); - Glib::ustring normal_base_str((const char*)normal_base_uri_node.buf); + std::string normal_base_str((const char*)normal_base_uri_node.buf); ret = uri; if (uri.length() >= normal_base_str.length() && uri.substr(0, normal_base_str.length()) == normal_base_str) { ret = uri.substr(normal_base_str.length()); - if (leading_slash && ret[0] != '/') - ret = Glib::ustring("/") + ret; } serd_node_free(&normal_base_uri_node); } if (leading_slash && ret[0] != '/') { - ret = Glib::ustring("/").append(ret); + ret = std::string("/").append(ret); } return ret; } @@ -190,7 +187,7 @@ parse( World* world, Interface* target, Sord::Model& model, - Glib::ustring document_uri, + const std::string& base_uri, Sord::Node& subject, boost::optional parent = boost::optional(), boost::optional symbol = boost::optional(), @@ -201,6 +198,7 @@ parse_graph( World* world, Interface* target, Sord::Model& model, + const std::string& base_uri, const Sord::Node& subject, boost::optional parent = boost::optional(), boost::optional symbol = boost::optional(), @@ -211,6 +209,7 @@ parse_block( World* world, Interface* target, Sord::Model& model, + const std::string& base_uri, const Sord::Node& subject, const Raul::Path& path, boost::optional data = boost::optional()); @@ -226,16 +225,18 @@ parse_properties( static bool parse_arcs( - World* world, - Interface* target, - Sord::Model& model, - const Sord::Node& subject, - const Raul::Path& graph); + World* world, + Interface* target, + Sord::Model& model, + const std::string& base_uri, + const Sord::Node& subject, + const Raul::Path& graph); static boost::optional parse_block(Ingen::World* world, Ingen::Interface* target, Sord::Model& model, + const std::string& base_uri, const Sord::Node& subject, const Raul::Path& path, boost::optional data) @@ -256,26 +257,23 @@ parse_block(Ingen::World* world, return boost::optional(); } - const std::string type_uri = relative_uri( - model.base_uri().to_string(), - i.get_object().to_string(), - false); + const std::string type_uri = relative_uri(base_uri, + i.get_object().to_string(), + false); if (!serd_uri_string_has_scheme((const uint8_t*)type_uri.c_str())) { - SerdURI base_uri; - serd_uri_parse(model.base_uri().to_u_string(), &base_uri); + SerdURI base_uri_parts; + serd_uri_parse((const uint8_t*)base_uri.c_str(), &base_uri_parts); SerdURI ignored; SerdNode sub_uri = serd_node_new_uri_from_string( i.get_object().to_u_string(), - &base_uri, + &base_uri_parts, &ignored); - const std::string sub_uri_str((const char*)sub_uri.buf); - std::string basename = get_basename(sub_uri_str); - - const std::string sub_file = - string((const char*)sub_uri.buf) + "/" + basename + ".ttl"; + const std::string sub_uri_str = (const char*)sub_uri.buf; + const std::string basename = get_basename(sub_uri_str); + const std::string sub_file = sub_uri_str + '/' + basename + ".ttl"; const SerdNode sub_base = serd_node_from_string( SERD_URI, (const uint8_t*)sub_file.c_str()); @@ -286,10 +284,10 @@ parse_block(Ingen::World* world, serd_env_free(env); Sord::URI sub_node(*world->rdf_world(), sub_file); - parse_graph(world, target, sub_model, sub_node, + parse_graph(world, target, sub_model, (const char*)sub_base.buf, sub_node, path.parent(), Raul::Symbol(path.symbol())); - parse_graph(world, target, model, subject, + parse_graph(world, target, model, base_uri, subject, path.parent(), Raul::Symbol(path.symbol())); } else { Resource::Properties props = get_properties(world, model, subject); @@ -304,12 +302,13 @@ static boost::optional parse_graph(Ingen::World* world, Ingen::Interface* target, Sord::Model& model, + const std::string& base_uri, const Sord::Node& subject_node, boost::optional parent, boost::optional a_symbol, boost::optional data) { - URIs& uris = world->uris(); + const URIs& uris = world->uris(); const Sord::URI ingen_block(*world->rdf_world(), uris.ingen_block); const Sord::URI ingen_polyphony(*world->rdf_world(), uris.ingen_polyphony); @@ -318,18 +317,17 @@ parse_graph(Ingen::World* world, const Sord::Node& graph = subject_node; const Sord::Node nil; - const Glib::ustring base_uri = model.base_uri().to_string(); - Raul::Symbol symbol("_"); if (a_symbol) { symbol = *a_symbol; - } else { - const std::string basename = get_basename(base_uri); } string graph_path_str = relative_uri(base_uri, subject_node.to_string(), true); - if (parent && a_symbol) + if (parent && a_symbol) { graph_path_str = parent->child(*a_symbol); + } else if (parent) { + graph_path_str = *parent; + } if (!Raul::Path::is_valid(graph_path_str)) { world->log().error(fmt("Graph %1% has invalid path\n") @@ -349,7 +347,7 @@ parse_graph(Ingen::World* world, Raul::Symbol(get_basename(node.to_string()))); // Parse and create block - parse_block(world, target, model, node, block_path, + parse_block(world, target, model, base_uri, node, block_path, boost::optional()); // For each port on this block @@ -395,19 +393,20 @@ parse_graph(Ingen::World* world, p.second.second); } - parse_arcs(world, target, model, subject_node, graph_path); + parse_arcs(world, target, model, base_uri, subject_node, graph_path); return graph_path; } static bool -parse_arc(Ingen::World* world, - Ingen::Interface* target, - Sord::Model& model, - const Sord::Node& subject, - const Raul::Path& graph) +parse_arc(Ingen::World* world, + Ingen::Interface* target, + Sord::Model& model, + const std::string& base_uri, + const Sord::Node& subject, + const Raul::Path& graph) { - URIs& uris = world->uris(); + const URIs& uris = world->uris(); const Sord::URI ingen_tail(*world->rdf_world(), uris.ingen_tail); const Sord::URI ingen_head(*world->rdf_world(), uris.ingen_head); @@ -416,8 +415,6 @@ parse_arc(Ingen::World* world, Sord::Iter t = model.find(subject, ingen_tail, nil); Sord::Iter h = model.find(subject, ingen_head, nil); - const Glib::ustring& base_uri = model.base_uri().to_string(); - if (t.end()) { world->log().error("Arc has no tail"); return false; @@ -455,17 +452,18 @@ parse_arc(Ingen::World* world, } static bool -parse_arcs(Ingen::World* world, - Ingen::Interface* target, - Sord::Model& model, - const Sord::Node& subject, - const Raul::Path& graph) +parse_arcs(Ingen::World* world, + Ingen::Interface* target, + Sord::Model& model, + const std::string& base_uri, + const Sord::Node& subject, + const Raul::Path& graph) { const Sord::URI ingen_arc(*world->rdf_world(), world->uris().ingen_arc); const Sord::Node nil; for (Sord::Iter i = model.find(subject, ingen_arc, nil); !i.end(); ++i) { - parse_arc(world, target, model, i.get_object(), graph); + parse_arc(world, target, model, base_uri, i.get_object(), graph); } return true; @@ -494,13 +492,13 @@ static boost::optional parse(Ingen::World* world, Ingen::Interface* target, Sord::Model& model, - Glib::ustring document_uri, + const std::string& base_uri, Sord::Node& subject, boost::optional parent, boost::optional symbol, boost::optional data) { - URIs& uris = world->uris(); + const URIs& uris = world->uris(); const Sord::URI graph_class (*world->rdf_world(), uris.ingen_Graph); const Sord::URI block_class (*world->rdf_world(), uris.ingen_Block); @@ -514,7 +512,7 @@ parse(Ingen::World* world, // Parse explicit subject graph if (subject.is_valid()) { - return parse_graph(world, target, model, subject, parent, symbol, data); + return parse_graph(world, target, model, base_uri, subject, parent, symbol, data); } // Get all subjects and their types (?subject a ?type) @@ -540,12 +538,12 @@ parse(Ingen::World* world, const Sord::Node& s = i.first; const std::set& types = i.second; boost::optional ret; - const Raul::Path path( - relative_uri( model.base_uri().to_string(), s.to_string(), true)); + const Raul::Path rel_path(relative_uri(base_uri, s.to_string(), true)); + const Raul::Path path = parent ? parent->child(rel_path) : rel_path; if (types.find(graph_class) != types.end()) { - ret = parse_graph(world, target, model, s, parent, symbol, data); + ret = parse_graph(world, target, model, base_uri, s, parent, symbol, data); } else if (types.find(block_class) != types.end()) { - ret = parse_block(world, target, model, s, path, data); + ret = parse_block(world, target, model, base_uri, s, path, data); } else if (types.find(in_port_class) != types.end() || types.find(out_port_class) != types.end()) { parse_properties( @@ -553,7 +551,7 @@ parse(Ingen::World* world, ret = path; } else if (types.find(arc_class) != types.end()) { Raul::Path parent_path(parent ? parent.get() : Raul::Path("/")); - parse_arc(world, target, model, s, parent_path); + parse_arc(world, target, model, base_uri, s, parent_path); } else { world->log().error("Subject has no known types\n"); } @@ -568,23 +566,23 @@ parse(Ingen::World* world, bool Parser::parse_file(Ingen::World* world, Ingen::Interface* target, - Glib::ustring path, + const std::string& path, boost::optional parent, boost::optional symbol, boost::optional data) { - if (Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) { - // This is a bundle, append "/name.ttl" to get graph file path - path = Glib::build_filename(path, get_basename(path) + ".ttl"); + std::string file_path = path; + if (!Glib::path_is_absolute(file_path)) { + file_path = Glib::build_filename(Glib::get_current_dir(), file_path); } - - if (!Glib::path_is_absolute(path)) { - path = Glib::build_filename(Glib::get_current_dir(), path); + if (Glib::file_test(file_path, Glib::FILE_TEST_IS_DIR)) { + // This is a bundle, append "/name.ttl" to get graph file path + file_path = Glib::build_filename(path, get_basename(path) + ".ttl"); } std::string uri; try { - uri = Glib::filename_to_uri(path, ""); + uri = Glib::filename_to_uri(file_path, ""); } catch (const Glib::ConvertError& e) { world->log().error(fmt("Path to URI conversion error: %1%\n") % e.what()); @@ -601,7 +599,7 @@ Parser::parse_file(Ingen::World* world, serd_env_free(env); - world->log().info(fmt("Parsing %1%\n") % path); + world->log().info(fmt("Parsing %1%\n") % file_path); if (parent) world->log().info(fmt("Parent: %1%\n") % parent->c_str()); if (symbol) @@ -609,7 +607,8 @@ Parser::parse_file(Ingen::World* world, Sord::Node subject(*world->rdf_world(), Sord::Node::URI, uri); boost::optional parsed_path - = parse(world, target, model, path, subject, parent, symbol, data); + = parse(world, target, model, model.base_uri().to_string(), + subject, parent, symbol, data); if (parsed_path) { target->set_property(Node::path_to_uri(*parsed_path), @@ -622,27 +621,34 @@ Parser::parse_file(Ingen::World* world, } } -bool +boost::optional Parser::parse_string(Ingen::World* world, Ingen::Interface* target, - const Glib::ustring& str, - const Glib::ustring& base_uri, + const std::string& str, + const std::string& base_uri, boost::optional parent, boost::optional symbol, boost::optional data) { // Load string into model Sord::Model model(*world->rdf_world(), base_uri, SORD_SPO|SORD_PSO, false); - const SerdNode base = serd_node_from_string( - SERD_URI, (const uint8_t*)base_uri.c_str()); - SerdEnv* env = serd_env_new(&base); + + SerdEnv* env = serd_env_new(NULL); + if (!base_uri.empty()) { + const SerdNode base = serd_node_from_string( + SERD_URI, (const uint8_t*)base_uri.c_str()); + serd_env_set_base_uri(env, &base); + } model.load_string(env, SERD_TURTLE, str.c_str(), str.length(), base_uri); + + Raul::URI actual_base((const char*)serd_env_get_base_uri(env, NULL)->buf); serd_env_free(env); world->log().info(fmt("Parsing string (base %1%)\n") % base_uri); Sord::Node subject; - return !!parse(world, target, model, base_uri, subject, parent, symbol, data); + parse(world, target, model, actual_base, subject, parent, symbol, data); + return actual_base; } } // namespace Serialisation diff --git a/src/serialisation/Serialiser.cpp b/src/serialisation/Serialiser.cpp index dd1be2b4..3cd05ef4 100644 --- a/src/serialisation/Serialiser.cpp +++ b/src/serialisation/Serialiser.cpp @@ -54,6 +54,8 @@ namespace Ingen { namespace Serialisation { struct Serialiser::Impl { + typedef Resource::Properties Properties; + explicit Impl(World& world) : _root_path("/") , _world(world) @@ -115,16 +117,6 @@ Serialiser::~Serialiser() delete me; } -void -Serialiser::to_file(SPtr object, - const std::string& filename) -{ - me->_root_path = object->path(); - me->start_to_filename(filename); - serialise(object); - finish(); -} - void Serialiser::Impl::write_manifest(const std::string& bundle_path, SPtr graph, @@ -196,15 +188,6 @@ Serialiser::Impl::write_bundle(SPtr graph, write_manifest(path, graph, symbol); } -string -Serialiser::to_string(SPtr object, - const string& base_uri) -{ - start_to_string(object->path(), base_uri); - serialise(object); - return finish(); -} - /** Begin a serialization to a file. * * This must be called before any serializing methods. @@ -223,16 +206,6 @@ Serialiser::Impl::start_to_filename(const string& filename) _mode = Mode::TO_FILE; } -/** 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 serialised. - * - * All serialized paths will have the root path chopped from their prefix - * (therefore all serialized paths must be descendants of the root) - */ void Serialiser::start_to_string(const Raul::Path& root, const string& base_uri) { @@ -242,6 +215,13 @@ Serialiser::start_to_string(const Raul::Path& root, const string& base_uri) me->_mode = Impl::Mode::TO_STRING; } +void +Serialiser::start_to_file(const Raul::Path& root, const string& filename) +{ + me->_root_path = root; + me->start_to_filename(filename); +} + std::string Serialiser::finish() { @@ -274,7 +254,6 @@ Serialiser::Impl::path_rdf_node(const Raul::Path& path) { assert(_model); assert(path == _root_path || path.is_child_of(_root_path)); - // FIXME: if path == root_path() then "/" ? return Sord::URI(_model->world(), path.substr(_root_path.base().length()), _base_uri); diff --git a/src/server/BlockImpl.hpp b/src/server/BlockImpl.hpp index 845cd7df..b3064168 100644 --- a/src/server/BlockImpl.hpp +++ b/src/server/BlockImpl.hpp @@ -42,6 +42,7 @@ namespace Server { class Buffer; class BufferFactory; class Context; +class Engine; class GraphImpl; class PluginImpl; class PortImpl; @@ -83,6 +84,11 @@ public: */ virtual void deactivate(); + /** Duplicate this Node. */ + virtual BlockImpl* duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent) { return NULL; } + /** Return true iff this block is activated */ bool activated() const { return _activated; } diff --git a/src/server/Broadcaster.hpp b/src/server/Broadcaster.hpp index b9e37c44..e33594a4 100644 --- a/src/server/Broadcaster.hpp +++ b/src/server/Broadcaster.hpp @@ -114,6 +114,11 @@ public: BROADCAST(delta, uri, remove, add); } + void copy(const Raul::Path& old_path, + const Raul::URI& new_uri) { + BROADCAST(copy, old_path, new_uri); + } + void move(const Raul::Path& old_path, const Raul::Path& new_path) { BROADCAST(move, old_path, new_path); diff --git a/src/server/DuplexPort.cpp b/src/server/DuplexPort.cpp index 1dd1672f..7b39a0de 100644 --- a/src/server/DuplexPort.cpp +++ b/src/server/DuplexPort.cpp @@ -67,6 +67,25 @@ DuplexPort::~DuplexPort() } } +DuplexPort* +DuplexPort::duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent) +{ + BufferFactory& bufs = *engine.buffer_factory(); + const Atom polyphonic = get_property(bufs.uris().ingen_polyphonic); + + DuplexPort* dup = new DuplexPort( + bufs, parent, symbol, _index, + polyphonic.type() == bufs.uris().atom_Bool && polyphonic.get(), + _poly, _type, _buffer_type, + _value, _buffer_size, _is_output); + + dup->set_properties(properties()); + + return dup; +} + void DuplexPort::inherit_neighbour(const PortImpl* port, Resource::Properties& remove, diff --git a/src/server/DuplexPort.hpp b/src/server/DuplexPort.hpp index a247841e..d8e01e42 100644 --- a/src/server/DuplexPort.hpp +++ b/src/server/DuplexPort.hpp @@ -56,6 +56,10 @@ public: virtual ~DuplexPort(); + DuplexPort* duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent); + void inherit_neighbour(const PortImpl* port, Resource::Properties& remove, Resource::Properties& add); diff --git a/src/server/EventWriter.cpp b/src/server/EventWriter.cpp index 8508fab3..5f3fe3c3 100644 --- a/src/server/EventWriter.cpp +++ b/src/server/EventWriter.cpp @@ -68,6 +68,15 @@ EventWriter::delta(const Raul::URI& uri, uri, add, remove)); } +void +EventWriter::copy(const Raul::Path& old_path, + const Raul::URI& new_uri) +{ + _engine.enqueue_event( + new Events::Copy(_engine, _respondee, _request_id, now(), + old_path, new_uri)); +} + void EventWriter::move(const Raul::Path& old_path, const Raul::Path& new_path) diff --git a/src/server/EventWriter.hpp b/src/server/EventWriter.hpp index 89493051..1c031961 100644 --- a/src/server/EventWriter.hpp +++ b/src/server/EventWriter.hpp @@ -64,6 +64,9 @@ public: const Resource::Properties& remove, const Resource::Properties& add); + virtual void copy(const Raul::Path& old_path, + const Raul::URI& new_uri); + virtual void move(const Raul::Path& old_path, const Raul::Path& new_path); diff --git a/src/server/GraphImpl.cpp b/src/server/GraphImpl.cpp index a8a44cf1..f9c0e767 100644 --- a/src/server/GraphImpl.cpp +++ b/src/server/GraphImpl.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "ingen/Log.hpp" #include "ingen/URIs.hpp" @@ -24,6 +25,7 @@ #include "ArcImpl.hpp" #include "BlockImpl.hpp" #include "BufferFactory.hpp" +#include "Driver.hpp" #include "DuplexPort.hpp" #include "Engine.hpp" #include "GraphImpl.hpp" @@ -63,6 +65,67 @@ GraphImpl::~GraphImpl() delete _plugin; } +BlockImpl* +GraphImpl::duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent) +{ + BufferFactory& bufs = *engine.buffer_factory(); + const SampleRate rate = engine.driver()->sample_rate(); + + // Duplicate graph + GraphImpl* dup = new GraphImpl( + engine, symbol, _polyphony, parent, rate, _poly_process); + + Properties props = properties(); + props.erase(bufs.uris().lv2_symbol); + props.insert({bufs.uris().lv2_symbol, bufs.forge().alloc(symbol.c_str())}); + dup->set_properties(props); + + // We need a map of port duplicates to duplicate arcs + typedef std::unordered_map PortMap; + PortMap port_map; + + // Add duplicates of all ports + dup->_ports = new Raul::Array(num_ports(), NULL); + for (Ports::iterator p = _inputs.begin(); p != _inputs.end(); ++p) { + DuplexPort* p_dup = p->duplicate(engine, p->symbol(), dup); + dup->_inputs.push_front(*p_dup); + (*dup->_ports)[p->index()] = p_dup; + port_map.insert({&*p, p_dup}); + } + for (Ports::iterator p = _outputs.begin(); p != _outputs.end(); ++p) { + DuplexPort* p_dup = p->duplicate(engine, p->symbol(), dup); + dup->_outputs.push_front(*p_dup); + (*dup->_ports)[p->index()] = p_dup; + port_map.insert({&*p, p_dup}); + } + + // Add duplicates of all blocks + for (auto& b : _blocks) { + BlockImpl* b_dup = b.duplicate(engine, b.symbol(), dup); + dup->add_block(*b_dup); + b_dup->activate(*engine.buffer_factory()); + for (uint32_t p = 0; p < b.num_ports(); ++p) { + port_map.insert({b.port_impl(p), b_dup->port_impl(p)}); + } + } + + // Add duplicates of all arcs + for (const auto& a : _arcs) { + SPtr arc = dynamic_ptr_cast(a.second); + if (arc) { + PortMap::iterator t = port_map.find(arc->tail()); + PortMap::iterator h = port_map.find(arc->head()); + if (t != port_map.end() && h != port_map.end()) { + dup->add_arc(SPtr(new ArcImpl(t->second, h->second))); + } + } + } + + return dup; +} + void GraphImpl::activate(BufferFactory& bufs) { diff --git a/src/server/GraphImpl.hpp b/src/server/GraphImpl.hpp index 61bbdba4..9601f9a1 100644 --- a/src/server/GraphImpl.hpp +++ b/src/server/GraphImpl.hpp @@ -60,6 +60,10 @@ public: virtual GraphType graph_type() const { return GraphType::GRAPH; } + BlockImpl* duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent); + void activate(BufferFactory& bufs); void deactivate(); diff --git a/src/server/LV2Block.cpp b/src/server/LV2Block.cpp index 4369107e..3f6f4be1 100644 --- a/src/server/LV2Block.cpp +++ b/src/server/LV2Block.cpp @@ -22,6 +22,7 @@ #include "lv2/lv2plug.in/ns/ext/morph/morph.h" #include "lv2/lv2plug.in/ns/ext/options/options.h" #include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" #include "raul/Maid.hpp" #include "raul/Array.hpp" @@ -432,6 +433,46 @@ LV2Block::instantiate(BufferFactory& bufs) return ret; } +BlockImpl* +LV2Block::duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent) +{ + const SampleRate rate = engine.driver()->sample_rate(); + + // Duplicate and instantiate block + LV2Block* dup = new LV2Block(_lv2_plugin, symbol, _polyphonic, parent, rate); + if (!dup->instantiate(*engine.buffer_factory())) { + delete dup; + return NULL; + } + dup->set_properties(properties()); + + // Set duplicate port values and properties to the same as ours + for (uint32_t p = 0; p < num_ports(); ++p) { + const Atom& val = port_impl(p)->value(); + if (val.is_valid()) { + dup->port_impl(p)->set_value(val); + } + dup->port_impl(p)->set_properties(port_impl(p)->properties()); + } + + // Copy internal plugin state + for (uint32_t v = 0; v < _polyphony; ++v) { + LilvState* state = lilv_state_new_from_instance( + _lv2_plugin->lilv_plugin(), instance(v), + &engine.world()->uri_map().urid_map_feature()->urid_map, + NULL, NULL, NULL, NULL, NULL, NULL, LV2_STATE_IS_NATIVE, NULL); + if (state) { + lilv_state_restore(state, dup->instance(v), + NULL, NULL, LV2_STATE_IS_NATIVE, NULL); + lilv_state_free(state); + } + } + + return dup; +} + void LV2Block::activate(BufferFactory& bufs) { diff --git a/src/server/LV2Block.hpp b/src/server/LV2Block.hpp index 35a2d5c3..ec2f99f4 100644 --- a/src/server/LV2Block.hpp +++ b/src/server/LV2Block.hpp @@ -48,6 +48,10 @@ public: bool instantiate(BufferFactory& bufs); + BlockImpl* duplicate(Engine& engine, + const Raul::Symbol& symbol, + GraphImpl* parent); + bool prepare_poly(BufferFactory& bufs, uint32_t poly); bool apply_poly(ProcessContext& context, Raul::Maid& maid, uint32_t poly); diff --git a/src/server/PortImpl.cpp b/src/server/PortImpl.cpp index 2dafcf32..bf605856 100644 --- a/src/server/PortImpl.cpp +++ b/src/server/PortImpl.cpp @@ -383,7 +383,6 @@ void PortImpl::clear_buffers() { switch (_type.id()) { - case PortType::AUDIO: case PortType::CONTROL: case PortType::CV: for (uint32_t v = 0; v < _poly; ++v) { @@ -395,6 +394,7 @@ PortImpl::clear_buffers() state.time = 0; } break; + case PortType::AUDIO: default: for (uint32_t v = 0; v < _poly; ++v) { buffer(v)->clear(); diff --git a/src/server/events.hpp b/src/server/events.hpp index 9a1346b7..fb8509eb 100644 --- a/src/server/events.hpp +++ b/src/server/events.hpp @@ -27,6 +27,7 @@ #include "events/DisconnectAll.hpp" #include "events/Get.hpp" #include "events/Move.hpp" +#include "events/Copy.hpp" #include "events/SetPortValue.hpp" #endif // INGEN_ENGINE_EVENTS_HPP diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp new file mode 100644 index 00000000..67ab4747 --- /dev/null +++ b/src/server/events/Copy.cpp @@ -0,0 +1,130 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "BlockImpl.hpp" +#include "Broadcaster.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "EnginePort.hpp" +#include "GraphImpl.hpp" +#include "events/Copy.hpp" + +namespace Ingen { +namespace Server { +namespace Events { + +Copy::Copy(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + const Raul::Path& old_path, + const Raul::URI& new_uri) + : Event(engine, client, id, timestamp) + , _old_path(old_path) + , _new_uri(new_uri) + , _parent(NULL) + , _block(NULL) + , _compiled_graph(NULL) +{} + +bool +Copy::pre_process() +{ + if (_old_path.empty() || + !Node::uri_is_path(_new_uri) || + _new_uri == Node::root_uri()) { + return Event::pre_process_done(Status::BAD_REQUEST); + } + + // Only support a single source for now + const Raul::Path new_path = Node::uri_to_path(_new_uri); + if (!Raul::Symbol::is_valid(new_path.symbol())) { + return Event::pre_process_done(Status::BAD_REQUEST); + } + + std::unique_lock lock(_engine.store()->mutex()); + + // Find the old node + const Store::iterator i = _engine.store()->find(_old_path); + if (i == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, _old_path); + } + + // Ensure the new node doesn't already exists + if (_engine.store()->find(new_path) != _engine.store()->end()) { + return Event::pre_process_done(Status::EXISTS, new_path); + } + + // Get old node block, or fail (ports not supported for now) + BlockImpl* old_block = dynamic_cast(i->second.get()); + if (!old_block) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _old_path); + } + + // Find new parent graph + const Raul::Path parent_path = new_path.parent(); + const Store::iterator p = _engine.store()->find(parent_path); + if (p == _engine.store()->end()) { + return Event::pre_process_done(Status::NOT_FOUND, parent_path); + } + if (!(_parent = dynamic_cast(p->second.get()))) { + return Event::pre_process_done(Status::BAD_OBJECT_TYPE, parent_path); + } + + // Create new block + if (!(_block = dynamic_cast( + old_block->duplicate(_engine, Raul::Symbol(new_path.symbol()), _parent)))) { + return Event::pre_process_done(Status::INTERNAL_ERROR); + } + + _block->activate(*_engine.buffer_factory()); + + // Add block to the store and the graph's pre-processor only block list + _parent->add_block(*_block); + _engine.store()->add(_block); + + // Compile graph with new block added for insertion in audio thread + if (_parent->enabled()) { + _compiled_graph = _parent->compile(); + } + + return Event::pre_process_done(Status::SUCCESS); +} + +void +Copy::execute(ProcessContext& context) +{ + if (_block) { + _parent->set_compiled_graph(_compiled_graph); + _compiled_graph = NULL; // Graph takes ownership + } +} + +void +Copy::post_process() +{ + Broadcaster::Transfer t(*_engine.broadcaster()); + if (respond() == Status::SUCCESS) { + _engine.broadcaster()->copy(_old_path, _new_uri); + } +} + +} // namespace Events +} // namespace Server +} // namespace Ingen diff --git a/src/server/events/Copy.hpp b/src/server/events/Copy.hpp new file mode 100644 index 00000000..26c0c815 --- /dev/null +++ b/src/server/events/Copy.hpp @@ -0,0 +1,73 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or 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 Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see . +*/ + +#ifndef INGEN_EVENTS_COPY_HPP +#define INGEN_EVENTS_COPY_HPP + +#include + +#include "ingen/Store.hpp" +#include "raul/Path.hpp" + +#include "Event.hpp" + +namespace Ingen { +namespace Server { + +class BlockImpl; +class CompiledGraph; +class GraphImpl; + +namespace Events { + +/** \page methods + *

COPY

+ * As per WebDAV (RFC4918 S9.8). + * + * Copy an object from its current location and insert it at a new location + * in a single operation. + */ + +/** COPY a graph object to a new path (see \ref methods). + * \ingroup engine + */ +class Copy : public Event +{ +public: + Copy(Engine& engine, + SPtr client, + int32_t id, + SampleCount timestamp, + const Raul::Path& old_path, + const Raul::URI& new_uri); + + bool pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const Raul::Path _old_path; + const Raul::URI _new_uri; + GraphImpl* _parent; + BlockImpl* _block; + CompiledGraph* _compiled_graph; +}; + +} // namespace Events +} // namespace Server +} // namespace Ingen + +#endif // INGEN_EVENTS_COPY_HPP diff --git a/src/server/events/CreateBlock.cpp b/src/server/events/CreateBlock.cpp index 1e1e2228..e14a3356 100644 --- a/src/server/events/CreateBlock.cpp +++ b/src/server/events/CreateBlock.cpp @@ -23,6 +23,7 @@ #include "BlockImpl.hpp" #include "Broadcaster.hpp" #include "CreateBlock.hpp" +#include "Driver.hpp" #include "Engine.hpp" #include "GraphImpl.hpp" #include "PluginImpl.hpp" @@ -54,58 +55,72 @@ CreateBlock::~CreateBlock() bool CreateBlock::pre_process() { - Ingen::URIs& uris = _engine.world()->uris(); - typedef Resource::Properties::const_iterator iterator; + const Ingen::URIs& uris = _engine.world()->uris(); + const SPtr store = _engine.store(); + + // Check sanity of target path if (_path.is_root()) { return Event::pre_process_done(Status::BAD_URI, _path); + } else if (store->get(_path)) { + return Event::pre_process_done(Status::EXISTS, _path); + } else if (!(_graph = dynamic_cast(store->get(_path.parent())))) { + return Event::pre_process_done(Status::PARENT_NOT_FOUND, _path.parent()); } - std::string plugin_uri_str; + // Get prototype URI const iterator t = _properties.find(uris.ingen_prototype); - if (t != _properties.end() && t->second.type() == uris.forge.URI) { - plugin_uri_str = t->second.ptr(); - } else { + if (t == _properties.end() || t->second.type() != uris.forge.URI) { return Event::pre_process_done(Status::BAD_REQUEST); } - if (_engine.store()->get(_path)) { - return Event::pre_process_done(Status::EXISTS, _path); - } - - _graph = dynamic_cast(_engine.store()->get(_path.parent())); - if (!_graph) { - return Event::pre_process_done(Status::PARENT_NOT_FOUND, _path.parent()); - } - - const Raul::URI plugin_uri(plugin_uri_str); - PluginImpl* plugin = _engine.block_factory()->plugin(plugin_uri); - if (!plugin) { - return Event::pre_process_done(Status::PLUGIN_NOT_FOUND, - Raul::URI(plugin_uri)); - } + const Raul::URI prototype(t->second.ptr()); + + // Find polyphony + const iterator p = _properties.find(uris.ingen_polyphonic); + const bool polyphonic = (p != _properties.end() && + p->second.type() == uris.forge.Bool && + p->second.get()); + + // Find and instantiate/duplicate prototype (plugin/existing node) + if (Node::uri_is_path(prototype)) { + // Prototype is an existing block + BlockImpl* const ancestor = dynamic_cast( + store->get(Node::uri_to_path(prototype))); + if (!ancestor) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } else if (!(_block = ancestor->duplicate( + _engine, Raul::Symbol(_path.symbol()), _graph))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } - const iterator p = _properties.find(uris.ingen_polyphonic); - const bool polyphonic = ( - p != _properties.end() && - p->second.type() == _engine.world()->forge().Bool && - p->second.get()); - - if (!(_block = plugin->instantiate(*_engine.buffer_factory(), - Raul::Symbol(_path.symbol()), - polyphonic, - _graph, - _engine))) { - return Event::pre_process_done(Status::CREATION_FAILED, _path); + /* Replace prototype with the ancestor's. This is less informative, + but the client expects an actual LV2 plugin as prototype. */ + _properties.erase(uris.ingen_prototype); + _properties.insert(std::make_pair(uris.ingen_prototype, + uris.forge.alloc_uri(ancestor->plugin()->uri()))); + } else { + // Prototype is a plugin + PluginImpl* const plugin = _engine.block_factory()->plugin(prototype); + if (!plugin) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } else if (!(_block = plugin->instantiate(*_engine.buffer_factory(), + Raul::Symbol(_path.symbol()), + polyphonic, + _graph, + _engine))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } } + // Activate block _block->properties().insert(_properties.begin(), _properties.end()); _block->activate(*_engine.buffer_factory()); // Add block to the store and the graph's pre-processor only block list _graph->add_block(*_block); - _engine.store()->add(_block); + store->add(_block); /* Compile graph with new block added for insertion in audio thread TODO: Since the block is not connected at this point, a full compilation @@ -114,11 +129,7 @@ CreateBlock::pre_process() _compiled_graph = _graph->compile(); } - _update.push_back(make_pair(_block->uri(), _block->properties())); - for (uint32_t i = 0; i < _block->num_ports(); ++i) { - const PortImpl* port = _block->port_impl(i); - _update.push_back(std::make_pair(port->uri(), port->properties())); - } + _update.put_block(_block); return Event::pre_process_done(Status::SUCCESS); } @@ -137,9 +148,7 @@ CreateBlock::post_process() { Broadcaster::Transfer t(*_engine.broadcaster()); if (respond() == Status::SUCCESS) { - for (const auto& u : _update) { - _engine.broadcaster()->put(u.first, u.second); - } + _update.send(_engine.broadcaster()); } } diff --git a/src/server/events/CreateBlock.hpp b/src/server/events/CreateBlock.hpp index 36e35775..6e0d7f4d 100644 --- a/src/server/events/CreateBlock.hpp +++ b/src/server/events/CreateBlock.hpp @@ -17,12 +17,10 @@ #ifndef INGEN_EVENTS_CREATEBLOCK_HPP #define INGEN_EVENTS_CREATEBLOCK_HPP -#include -#include - #include "ingen/Resource.hpp" #include "Event.hpp" +#include "events/Get.hpp" namespace Ingen { namespace Server { @@ -54,15 +52,12 @@ public: void post_process(); private: - /// Update put message to broadcast to clients - typedef std::list< std::pair > Update; - - Raul::Path _path; - Resource::Properties _properties; - Update _update; - GraphImpl* _graph; - BlockImpl* _block; - CompiledGraph* _compiled_graph; + Raul::Path _path; + Resource::Properties _properties; + Events::Get::Response _update; + GraphImpl* _graph; + BlockImpl* _block; + CompiledGraph* _compiled_graph; }; } // namespace Events diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp index 0355b0bb..06445a6b 100644 --- a/src/server/events/CreateGraph.cpp +++ b/src/server/events/CreateGraph.cpp @@ -41,8 +41,7 @@ CreateGraph::CreateGraph(Engine& engine, , _graph(NULL) , _parent(NULL) , _compiled_graph(NULL) -{ -} +{} bool CreateGraph::pre_process() @@ -76,13 +75,33 @@ CreateGraph::pre_process() } const Raul::Symbol symbol((_path.is_root()) ? "root" : _path.symbol()); - _graph = new GraphImpl(_engine, symbol, ext_poly, _parent, - _engine.driver()->sample_rate(), int_poly); - _graph->properties().insert(_properties.begin(), _properties.end()); - _graph->add_property(uris.rdf_type, uris.ingen_Graph); - _graph->add_property(uris.rdf_type, - Resource::Property(uris.ingen_Block, - Resource::Graph::EXTERNAL)); + + // Create graph based on prototype + const iterator t = _properties.find(uris.ingen_prototype); + if (t != _properties.end() && + Raul::URI::is_valid(t->second.ptr()) && + Node::uri_is_path(Raul::URI(t->second.ptr()))) { + // Create a duplicate of an existing graph + const Raul::URI prototype(t->second.ptr()); + GraphImpl* ancestor = dynamic_cast( + _engine.store()->get(Node::uri_to_path(prototype))); + if (!ancestor) { + return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype); + } else if (!(_graph = dynamic_cast( + ancestor->duplicate(_engine, symbol, _parent)))) { + return Event::pre_process_done(Status::CREATION_FAILED, _path); + } + } else { + // Create a new graph + _graph = new GraphImpl(_engine, symbol, ext_poly, _parent, + _engine.driver()->sample_rate(), int_poly); + _graph->add_property(uris.rdf_type, uris.ingen_Graph); + _graph->add_property(uris.rdf_type, + Resource::Property(uris.ingen_Block, + Resource::Graph::EXTERNAL)); + } + + _graph->set_properties(_properties); _parent->add_block(*_graph); if (_parent->enabled()) { @@ -92,10 +111,12 @@ CreateGraph::pre_process() _graph->activate(*_engine.buffer_factory()); - // Insert into Store + // Insert into store and build update to send to clients _engine.store()->add(_graph); - - _update = _graph->properties(); + _update.put_graph(_graph); + for (BlockImpl& block : _graph->blocks()) { + _engine.store()->add(&block); + } return Event::pre_process_done(Status::SUCCESS); } @@ -113,7 +134,7 @@ CreateGraph::post_process() { Broadcaster::Transfer t(*_engine.broadcaster()); if (respond() == Status::SUCCESS) { - _engine.broadcaster()->put(Node::path_to_uri(_path), _update); + _update.send(_engine.broadcaster()); } } diff --git a/src/server/events/CreateGraph.hpp b/src/server/events/CreateGraph.hpp index 64fb92bd..542a2921 100644 --- a/src/server/events/CreateGraph.hpp +++ b/src/server/events/CreateGraph.hpp @@ -17,9 +17,11 @@ #ifndef INGEN_EVENTS_CREATEGRAPH_HPP #define INGEN_EVENTS_CREATEGRAPH_HPP -#include "Event.hpp" #include "ingen/Resource.hpp" +#include "Event.hpp" +#include "events/Get.hpp" + namespace Ingen { namespace Server { @@ -47,12 +49,12 @@ public: void post_process(); private: - const Raul::Path _path; - Resource::Properties _properties; - Resource::Properties _update; - GraphImpl* _graph; - GraphImpl* _parent; - CompiledGraph* _compiled_graph; + const Raul::Path _path; + Resource::Properties _properties; + Events::Get::Response _update; + GraphImpl* _graph; + GraphImpl* _parent; + CompiledGraph* _compiled_graph; }; } // namespace Events diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp index 2a1a7994..21602df6 100644 --- a/src/server/events/CreatePort.cpp +++ b/src/server/events/CreatePort.cpp @@ -87,31 +87,23 @@ CreatePort::pre_process() { if (_port_type == PortType::UNKNOWN) { return Event::pre_process_done(Status::UNKNOWN_TYPE, _path); - } - - if (_path.is_root()) { + } else if (_path.is_root()) { return Event::pre_process_done(Status::BAD_URI, _path); - } - - if (_engine.store()->get(_path)) { + } else if (_engine.store()->get(_path)) { return Event::pre_process_done(_status, _path); } - Node* parent = _engine.store()->get(_path.parent()); + const Raul::Path parent_path = _path.parent(); + Node* const parent = _engine.store()->get(parent_path); if (!parent) { - return Event::pre_process_done(Status::PARENT_NOT_FOUND, - _path.parent()); + return Event::pre_process_done(Status::PARENT_NOT_FOUND, parent_path); + } else if (!(_graph = dynamic_cast(parent))) { + return Event::pre_process_done(Status::INVALID_PARENT, parent_path); } - if (!(_graph = dynamic_cast(parent))) { - return Event::pre_process_done(Status::INVALID_PARENT_PATH, - _path.parent()); - } - - const URIs& uris = _engine.world()->uris(); - const BufferFactory& buffer_factory = *_engine.buffer_factory(); - - const uint32_t buf_size = buffer_factory.default_size(_buf_type); + const URIs& uris = _engine.world()->uris(); + BufferFactory& bufs = *_engine.buffer_factory(); + const uint32_t buf_size = bufs.default_size(_buf_type); const int32_t old_n_ports = _graph->num_ports_non_rt(); typedef Resource::Properties::const_iterator PropIter; @@ -133,7 +125,7 @@ CreatePort::pre_process() poly_i->second.get()); if (!(_graph_port = _graph->create_port( - *_engine.buffer_factory(), Raul::Symbol(_path.symbol()), + bufs, Raul::Symbol(_path.symbol()), _port_type, _buf_type, buf_size, _is_output, polyphonic))) { return Event::pre_process_done(Status::CREATION_FAILED, _path); } diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp index 23285b9b..02f53410 100644 --- a/src/server/events/Delta.cpp +++ b/src/server/events/Delta.cpp @@ -135,9 +135,11 @@ Delta::pre_process() path, is_output, _properties); } if (_create_event) { - _create_event->pre_process(); - // Grab the object for applying properties, if the create-event succeeded - _object = _engine.store()->get(path); + if (_create_event->pre_process()) { + _object = _engine.store()->get(path); // Get object for setting + } else { + return Event::pre_process_done(Status::CREATION_FAILED, _subject); + } } else { return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _subject); } @@ -382,36 +384,39 @@ Delta::post_process() Broadcaster::Transfer t(*_engine.broadcaster()); - for (auto& s : _set_events) - s->post_process(); + if (_create_event) { + _create_event->post_process(); + if (_create_event->status() != Status::SUCCESS) { + return; // Creation failed, nothing else to do + } + } - if (_status == Status::SUCCESS) { - if (_create_event) { - _create_event->post_process(); - } else { - respond(); - switch (_type) { - case Type::SET: - /* Kludge to avoid feedback for set events only. The GUI - depends on put responses to e.g. initially place blocks. - Some more sensible way of controlling this is needed. */ - _engine.broadcaster()->set_ignore_client(_request_client); - _engine.broadcaster()->set_property( - _subject, - (*_properties.begin()).first, - (*_properties.begin()).second); - _engine.broadcaster()->clear_ignore_client(); - break; - case Type::PUT: - _engine.broadcaster()->put(_subject, _properties, _context); - break; - case Type::PATCH: - _engine.broadcaster()->delta(_subject, _remove, _properties); - break; - } + for (auto& s : _set_events) { + if (s->status() != Status::SUCCESS) { + s->post_process(); // Set failed, report error + } + } + + if (respond() == Status::SUCCESS) { + switch (_type) { + case Type::SET: + /* Kludge to avoid feedback for set events only. The GUI + depends on put responses to e.g. initially place blocks. + Some more sensible way of controlling this is needed. */ + _engine.broadcaster()->set_ignore_client(_request_client); + _engine.broadcaster()->set_property( + _subject, + (*_properties.begin()).first, + (*_properties.begin()).second); + _engine.broadcaster()->clear_ignore_client(); + break; + case Type::PUT: + _engine.broadcaster()->put(_subject, _properties, _context); + break; + case Type::PATCH: + _engine.broadcaster()->delta(_subject, _remove, _properties); + break; } - } else { - respond(); } } diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp index 22560c7e..262bfea8 100644 --- a/src/server/events/DisconnectAll.cpp +++ b/src/server/events/DisconnectAll.cpp @@ -100,8 +100,7 @@ DisconnectAll::pre_process() if (object->parent_graph() != _parent && object->parent()->parent_graph() != _parent) { - return Event::pre_process_done(Status::INVALID_PARENT_PATH, - _parent_path); + return Event::pre_process_done(Status::INVALID_PARENT, _parent_path); } // Only one of these will succeed diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp index c984e576..8a9eb227 100644 --- a/src/server/events/Get.cpp +++ b/src/server/events/Get.cpp @@ -100,6 +100,19 @@ Get::Response::put_graph(const GraphImpl* graph) } } +void +Get::Response::send(Interface* dest) +{ + // Sort puts by URI so parents are sent first + std::sort(puts.begin(), puts.end()); + for (const Response::Put& put : puts) { + dest->put(put.uri, put.properties, put.ctx); + } + for (const Response::Connect& connect : connects) { + dest->connect(connect.tail, connect.head); + } +} + Get::Get(Engine& engine, SPtr client, int32_t id, @@ -162,12 +175,7 @@ Get::post_process() uris.param_sampleRate, uris.forge.make(int32_t(_engine.driver()->sample_rate()))); } else { - for (const Response::Put& put : _response.puts) { - _request_client->put(put.uri, put.properties, put.ctx); - } - for (const Response::Connect& connect : _response.connects) { - _request_client->connect(connect.tail, connect.head); - } + _response.send(_request_client.get()); } } } diff --git a/src/server/events/Get.hpp b/src/server/events/Get.hpp index 7aea8c77..e0ed3483 100644 --- a/src/server/events/Get.hpp +++ b/src/server/events/Get.hpp @@ -17,6 +17,8 @@ #ifndef INGEN_EVENTS_GET_HPP #define INGEN_EVENTS_GET_HPP +#include + #include "Event.hpp" #include "BlockFactory.hpp" #include "types.hpp" @@ -24,7 +26,10 @@ namespace Ingen { namespace Server { +class BlockImpl; +class GraphImpl; class PluginImpl; +class PortImpl; namespace Events { @@ -45,7 +50,6 @@ public: void execute(ProcessContext& context) {} void post_process(); -private: /** A sequence of puts and connects to respond to client with. * This is constructed in the pre_process() and later sent in * post_process() to avoid the need to lock. @@ -63,11 +67,17 @@ private: void put_port(const PortImpl* port); void put_block(const BlockImpl* block); void put_graph(const GraphImpl* graph); - + + void send(Interface* dest); + struct Put { Raul::URI uri; Resource::Properties properties; Resource::Graph ctx; + + inline bool operator<(const Put& other) { + return uri < other.uri; + } }; struct Connect { @@ -79,6 +89,7 @@ private: std::vector connects; }; +private: const Raul::URI _uri; const Node* _object; const PluginImpl* _plugin; diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp index 56f106cb..065f42ac 100644 --- a/src/server/ingen_lv2.cpp +++ b/src/server/ingen_lv2.cpp @@ -713,7 +713,9 @@ ingen_save(LV2_Handle instance, char* state_path = map_path->abstract_path(map_path->handle, real_path); Ingen::Store::iterator root = plugin->world->store()->find(Raul::Path("/")); - plugin->world->serialiser()->to_file(root->second, real_path); + plugin->world->serialiser()->start_to_file(root->second->path(), real_path); + plugin->world->serialiser()->serialise(root->second); + plugin->world->serialiser()->finish(); store(handle, ingen_file, diff --git a/src/server/wscript b/src/server/wscript index 9d1238a4..16bca160 100644 --- a/src/server/wscript +++ b/src/server/wscript @@ -28,6 +28,7 @@ def build(bld): SocketListener.cpp Worker.cpp events/Connect.cpp + events/Copy.cpp events/CreateBlock.cpp events/CreateGraph.cpp events/CreatePort.cpp -- cgit v1.2.1