summaryrefslogtreecommitdiffstats
path: root/src/server/events
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/events')
-rw-r--r--src/server/events/Connect.cpp188
-rw-r--r--src/server/events/Connect.hpp74
-rw-r--r--src/server/events/Copy.cpp216
-rw-r--r--src/server/events/Copy.hpp68
-rw-r--r--src/server/events/CreateBlock.cpp180
-rw-r--r--src/server/events/CreateBlock.hpp66
-rw-r--r--src/server/events/CreateGraph.cpp236
-rw-r--r--src/server/events/CreateGraph.hpp74
-rw-r--r--src/server/events/CreatePort.cpp219
-rw-r--r--src/server/events/CreatePort.hpp82
-rw-r--r--src/server/events/Delete.cpp216
-rw-r--r--src/server/events/Delete.hpp86
-rw-r--r--src/server/events/Delta.cpp670
-rw-r--r--src/server/events/Delta.hpp133
-rw-r--r--src/server/events/Disconnect.cpp224
-rw-r--r--src/server/events/Disconnect.hpp87
-rw-r--r--src/server/events/DisconnectAll.cpp176
-rw-r--r--src/server/events/DisconnectAll.hpp78
-rw-r--r--src/server/events/Get.cpp111
-rw-r--r--src/server/events/Get.hpp65
-rw-r--r--src/server/events/Mark.cpp112
-rw-r--r--src/server/events/Mark.hpp69
-rw-r--r--src/server/events/Move.cpp91
-rw-r--r--src/server/events/Move.hpp57
-rw-r--r--src/server/events/SetPortValue.cpp139
-rw-r--r--src/server/events/SetPortValue.hpp71
-rw-r--r--src/server/events/Undo.cpp85
-rw-r--r--src/server/events/Undo.hpp58
28 files changed, 3931 insertions, 0 deletions
diff --git a/src/server/events/Connect.cpp b/src/server/events/Connect.cpp
new file mode 100644
index 00000000..99a90a49
--- /dev/null
+++ b/src/server/events/Connect.cpp
@@ -0,0 +1,188 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Store.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "ArcImpl.hpp"
+#include "Broadcaster.hpp"
+#include "BufferFactory.hpp"
+#include "Connect.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "InputPort.hpp"
+#include "PortImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "internals/BlockDelay.hpp"
+#include "types.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Connect::Connect(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Connect& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _graph(nullptr)
+ , _head(nullptr)
+{}
+
+bool
+Connect::pre_process(PreProcessContext& ctx)
+{
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ Node* tail = _engine.store()->get(_msg.tail);
+ if (!tail) {
+ return Event::pre_process_done(Status::NOT_FOUND, _msg.tail);
+ }
+
+ Node* head = _engine.store()->get(_msg.head);
+ if (!head) {
+ return Event::pre_process_done(Status::NOT_FOUND, _msg.head);
+ }
+
+ PortImpl* tail_output = dynamic_cast<PortImpl*>(tail);
+ _head = dynamic_cast<InputPort*>(head);
+ if (!tail_output || !_head) {
+ return Event::pre_process_done(Status::BAD_REQUEST, _msg.head);
+ }
+
+ BlockImpl* const tail_block = tail_output->parent_block();
+ BlockImpl* const head_block = _head->parent_block();
+ if (!tail_block || !head_block) {
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND, _msg.head);
+ }
+
+ if (tail_block->parent() != head_block->parent()
+ && tail_block != head_block->parent()
+ && tail_block->parent() != head_block) {
+ return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.head);
+ }
+
+ if (!ArcImpl::can_connect(tail_output, _head)) {
+ return Event::pre_process_done(Status::TYPE_MISMATCH, _msg.head);
+ }
+
+ if (tail_block->parent_graph() != head_block->parent_graph()) {
+ // Arc to a graph port from inside the graph
+ assert(tail_block->parent() == head_block || head_block->parent() == tail_block);
+ if (tail_block->parent() == head_block) {
+ _graph = dynamic_cast<GraphImpl*>(head_block);
+ } else {
+ _graph = dynamic_cast<GraphImpl*>(tail_block);
+ }
+ } else if (tail_block == head_block && dynamic_cast<GraphImpl*>(tail_block)) {
+ // Arc from a graph input to a graph output (pass through)
+ _graph = dynamic_cast<GraphImpl*>(tail_block);
+ } else {
+ // Normal arc between blocks with the same parent
+ _graph = tail_block->parent_graph();
+ }
+
+ if (_graph->has_arc(tail_output, _head)) {
+ return Event::pre_process_done(Status::EXISTS, _msg.head);
+ }
+
+ _arc = SPtr<ArcImpl>(new ArcImpl(tail_output, _head));
+
+ /* Need to be careful about graph port arcs here and adding a
+ block's parent as a dependant/provider, or adding a graph as its own
+ provider...
+ */
+ if (tail_block != head_block && tail_block->parent() == head_block->parent()) {
+ // Connection is between blocks inside a graph, compile graph
+
+ // The tail block is now a dependency (provider) of the head block
+ head_block->providers().insert(tail_block);
+
+ if (!dynamic_cast<internals::BlockDelayNode*>(tail_block)) {
+ /* Arcs leaving a delay node are ignored for the purposes of
+ compilation, since the output is from the previous cycle and
+ does not affect execution order. Otherwise, the head block is
+ now a dependant of the head block. */
+ tail_block->dependants().insert(head_block);
+ }
+
+ if (ctx.must_compile(*_graph)) {
+ if (!(_compiled_graph = compile(*_engine.maid(), *_graph))) {
+ head_block->providers().erase(tail_block);
+ tail_block->dependants().erase(head_block);
+ return Event::pre_process_done(Status::COMPILATION_FAILED);
+ }
+ }
+ }
+
+ _graph->add_arc(_arc);
+ _head->increment_num_arcs();
+
+ if (!_head->is_driver_port()) {
+ BufferFactory& bufs = *_engine.buffer_factory();
+ _voices = bufs.maid().make_managed<PortImpl::Voices>(_head->poly());
+ _head->pre_get_buffers(bufs, _voices, _head->poly());
+ }
+
+ tail_output->inherit_neighbour(_head, _tail_remove, _tail_add);
+ _head->inherit_neighbour(tail_output, _head_remove, _head_add);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Connect::execute(RunContext& context)
+{
+ if (_status == Status::SUCCESS) {
+ _head->add_arc(context, *_arc.get());
+ if (!_head->is_driver_port()) {
+ _head->set_voices(context, std::move(_voices));
+ }
+ _head->connect_buffers();
+ if (_compiled_graph) {
+ _graph->set_compiled_graph(std::move(_compiled_graph));
+ }
+ }
+}
+
+void
+Connect::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->message(_msg);
+ if (!_tail_remove.empty() || !_tail_add.empty()) {
+ _engine.broadcaster()->delta(
+ path_to_uri(_msg.tail), _tail_remove, _tail_add);
+ }
+ if (!_tail_remove.empty() || !_tail_add.empty()) {
+ _engine.broadcaster()->delta(
+ path_to_uri(_msg.tail), _tail_remove, _tail_add);
+ }
+ }
+}
+
+void
+Connect::undo(Interface& target)
+{
+ target.disconnect(_msg.tail, _msg.head);
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Connect.hpp b/src/server/events/Connect.hpp
new file mode 100644
index 00000000..16ea8bc9
--- /dev/null
+++ b/src/server/events/Connect.hpp
@@ -0,0 +1,74 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_CONNECT_HPP
+#define INGEN_EVENTS_CONNECT_HPP
+
+#include "raul/Path.hpp"
+
+#include "CompiledGraph.hpp"
+#include "Event.hpp"
+#include "PortImpl.hpp"
+#include "types.hpp"
+
+namespace Raul {
+template <typename T> class Array;
+}
+
+namespace ingen {
+namespace server {
+
+class ArcImpl;
+class GraphImpl;
+class InputPort;
+
+namespace events {
+
+/** Make an Arc between two Ports.
+ *
+ * \ingroup engine
+ */
+class Connect : public Event
+{
+public:
+ Connect(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Connect& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ const ingen::Connect _msg;
+ GraphImpl* _graph;
+ InputPort* _head;
+ MPtr<CompiledGraph> _compiled_graph;
+ SPtr<ArcImpl> _arc;
+ MPtr<PortImpl::Voices> _voices;
+ Properties _tail_remove;
+ Properties _tail_add;
+ Properties _head_remove;
+ Properties _head_add;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_CONNECT_HPP
diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp
new file mode 100644
index 00000000..5d2d511c
--- /dev/null
+++ b/src/server/events/Copy.cpp
@@ -0,0 +1,216 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Parser.hpp"
+#include "ingen/Serialiser.hpp"
+#include "ingen/Store.hpp"
+#include "raul/Path.hpp"
+
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "Engine.hpp"
+#include "EnginePort.hpp"
+#include "GraphImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "events/Copy.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Copy::Copy(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Copy& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _old_block(nullptr)
+ , _parent(nullptr)
+ , _block(nullptr)
+{}
+
+bool
+Copy::pre_process(PreProcessContext& ctx)
+{
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ if (uri_is_path(_msg.old_uri)) {
+ // Old URI is a path within the engine
+ const Raul::Path old_path = uri_to_path(_msg.old_uri);
+
+ // 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 it is a block (ports are not supported for now)
+ if (!(_old_block = dynamic_ptr_cast<BlockImpl>(i->second))) {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, old_path);
+ }
+
+ if (uri_is_path(_msg.new_uri)) {
+ // Copy to path within the engine
+ return engine_to_engine(ctx);
+ } else if (_msg.new_uri.scheme() == "file") {
+ // Copy to filesystem path (i.e. save)
+ return engine_to_filesystem(ctx);
+ } else {
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+ } else if (_msg.old_uri.scheme() == "file") {
+ if (uri_is_path(_msg.new_uri)) {
+ return filesystem_to_engine(ctx);
+ } else {
+ // Ingen is not your file manager
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+ }
+
+ return Event::pre_process_done(Status::BAD_URI);
+}
+
+bool
+Copy::engine_to_engine(PreProcessContext& ctx)
+{
+ // Only support a single source for now
+ const Raul::Path new_path = uri_to_path(_msg.new_uri);
+ if (!Raul::Symbol::is_valid(new_path.symbol())) {
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+
+ // Ensure the new node doesn't already exist
+ if (_engine.store()->find(new_path) != _engine.store()->end()) {
+ return Event::pre_process_done(Status::EXISTS, new_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<GraphImpl*>(p->second.get()))) {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, parent_path);
+ }
+
+ // Create new block
+ if (!(_block = dynamic_cast<BlockImpl*>(
+ _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
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_parent);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+static bool
+ends_with(const std::string& str, const std::string& end)
+{
+ if (str.length() >= end.length()) {
+ return !str.compare(str.length() - end.length(), end.length(), end);
+ }
+ return false;
+}
+
+bool
+Copy::engine_to_filesystem(PreProcessContext& ctx)
+{
+ // Ensure source is a graph
+ SPtr<GraphImpl> graph = dynamic_ptr_cast<GraphImpl>(_old_block);
+ if (!graph) {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _msg.old_uri);
+ }
+
+ if (!_engine.world()->serialiser()) {
+ return Event::pre_process_done(Status::INTERNAL_ERROR);
+ }
+
+ std::lock_guard<std::mutex> lock(_engine.world()->rdf_mutex());
+
+ if (ends_with(_msg.new_uri, ".ingen") || ends_with(_msg.new_uri, ".ingen/")) {
+ _engine.world()->serialiser()->write_bundle(graph, URI(_msg.new_uri));
+ } else {
+ _engine.world()->serialiser()->start_to_file(graph->path(), _msg.new_uri);
+ _engine.world()->serialiser()->serialise(graph);
+ _engine.world()->serialiser()->finish();
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+bool
+Copy::filesystem_to_engine(PreProcessContext& ctx)
+{
+ if (!_engine.world()->parser()) {
+ return Event::pre_process_done(Status::INTERNAL_ERROR);
+ }
+
+ std::lock_guard<std::mutex> lock(_engine.world()->rdf_mutex());
+
+ // Old URI is a filesystem path and new URI is a path within the engine
+ const std::string src_path(_msg.old_uri.path());
+ const Raul::Path dst_path = uri_to_path(_msg.new_uri);
+ boost::optional<Raul::Path> dst_parent;
+ boost::optional<Raul::Symbol> dst_symbol;
+ if (!dst_path.is_root()) {
+ dst_parent = dst_path.parent();
+ dst_symbol = Raul::Symbol(dst_path.symbol());
+ }
+
+ _engine.world()->parser()->parse_file(
+ _engine.world(), _engine.world()->interface().get(), src_path,
+ dst_parent, dst_symbol);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Copy::execute(RunContext& context)
+{
+ if (_block && _compiled_graph) {
+ _parent->set_compiled_graph(std::move(_compiled_graph));
+ }
+}
+
+void
+Copy::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->message(_msg);
+ }
+}
+
+void
+Copy::undo(Interface& target)
+{
+ if (uri_is_path(_msg.new_uri)) {
+ target.del(_msg.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..f31fe4d4
--- /dev/null
+++ b/src/server/events/Copy.hpp
@@ -0,0 +1,68 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_COPY_HPP
+#define INGEN_EVENTS_COPY_HPP
+
+#include <list>
+
+#include "ingen/Store.hpp"
+#include "raul/Path.hpp"
+
+#include "CompiledGraph.hpp"
+#include "Event.hpp"
+
+namespace ingen {
+namespace server {
+
+class BlockImpl;
+class GraphImpl;
+
+namespace events {
+
+/** Copy a graph object to a new path.
+ * \ingroup engine
+ */
+class Copy : public Event
+{
+public:
+ Copy(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Copy& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ bool engine_to_engine(PreProcessContext& ctx);
+ bool engine_to_filesystem(PreProcessContext& ctx);
+ bool filesystem_to_engine(PreProcessContext& ctx);
+
+ const ingen::Copy _msg;
+ SPtr<BlockImpl> _old_block;
+ GraphImpl* _parent;
+ BlockImpl* _block;
+ MPtr<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
new file mode 100644
index 00000000..fabdbd85
--- /dev/null
+++ b/src/server/events/CreateBlock.cpp
@@ -0,0 +1,180 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Forge.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIs.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "BlockFactory.hpp"
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "CreateBlock.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "LV2Block.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+CreateBlock::CreateBlock(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ Properties& properties)
+ : Event(engine, client, id, timestamp)
+ , _path(path)
+ , _properties(properties)
+ , _graph(nullptr)
+ , _block(nullptr)
+{}
+
+bool
+CreateBlock::pre_process(PreProcessContext& ctx)
+{
+ typedef Properties::const_iterator iterator;
+
+ const ingen::URIs& uris = _engine.world()->uris();
+ const SPtr<Store> 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<GraphImpl*>(store->get(_path.parent())))) {
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND, _path.parent());
+ }
+
+ // Map old ingen:prototype to new lv2:prototype
+ auto range = _properties.equal_range(uris.ingen_prototype);
+ for (auto i = range.first; i != range.second;) {
+ const auto value = i->second;
+ auto next = i;
+ next = _properties.erase(i);
+ _properties.emplace(uris.lv2_prototype, value);
+ i = next;
+ }
+
+ // Get prototype
+ iterator t = _properties.find(uris.lv2_prototype);
+ if (t == _properties.end() || !uris.forge.is_uri(t->second)) {
+ // Missing/invalid prototype
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+
+ const URI prototype(uris.forge.str(t->second, false));
+
+ // 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<int32_t>());
+
+ // Find and instantiate/duplicate prototype (plugin/existing node)
+ if (uri_is_path(prototype)) {
+ // Prototype is an existing block
+ BlockImpl* const ancestor = dynamic_cast<BlockImpl*>(
+ store->get(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);
+ }
+
+ /* 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.erase(uris.lv2_prototype);
+ _properties.emplace(uris.lv2_prototype,
+ uris.forge.make_urid(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);
+ }
+
+ // Load state from directory if given in properties
+ LilvState* state = nullptr;
+ auto s = _properties.find(uris.state_state);
+ if (s != _properties.end() && s->second.type() == uris.forge.Path) {
+ state = LV2Block::load_state(
+ _engine.world(), FilePath(s->second.ptr<char>()));
+ }
+
+ // Instantiate plugin
+ if (!(_block = plugin->instantiate(*_engine.buffer_factory(),
+ Raul::Symbol(_path.symbol()),
+ polyphonic,
+ _graph,
+ _engine,
+ state))) {
+ 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);
+ 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
+ could be avoided and the block simply appended. */
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_graph);
+
+ _update.put_block(_block);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+CreateBlock::execute(RunContext& context)
+{
+ if (_status == Status::SUCCESS && _compiled_graph) {
+ _graph->set_compiled_graph(std::move(_compiled_graph));
+ }
+}
+
+void
+CreateBlock::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _update.send(*_engine.broadcaster());
+ }
+}
+
+void
+CreateBlock::undo(Interface& target)
+{
+ target.del(_block->uri());
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/CreateBlock.hpp b/src/server/events/CreateBlock.hpp
new file mode 100644
index 00000000..02bfeda7
--- /dev/null
+++ b/src/server/events/CreateBlock.hpp
@@ -0,0 +1,66 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_CREATEBLOCK_HPP
+#define INGEN_EVENTS_CREATEBLOCK_HPP
+
+#include "ingen/Resource.hpp"
+
+#include "ClientUpdate.hpp"
+#include "CompiledGraph.hpp"
+#include "Event.hpp"
+
+namespace ingen {
+namespace server {
+
+class BlockImpl;
+class GraphImpl;
+
+namespace events {
+
+/** An event to load a Block and insert it into a Graph.
+ *
+ * \ingroup engine
+ */
+class CreateBlock : public Event
+{
+public:
+ CreateBlock(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ Properties& properties);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ Raul::Path _path;
+ Properties& _properties;
+ ClientUpdate _update;
+ GraphImpl* _graph;
+ BlockImpl* _block;
+ MPtr<CompiledGraph> _compiled_graph;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_CREATEBLOCK_HPP
diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp
new file mode 100644
index 00000000..27781cbc
--- /dev/null
+++ b/src/server/events/CreateGraph.cpp
@@ -0,0 +1,236 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Forge.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIs.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "Broadcaster.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "events/CreateGraph.hpp"
+#include "events/CreatePort.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+CreateGraph::CreateGraph(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ const Properties& properties)
+ : Event(engine, client, id, timestamp)
+ , _path(path)
+ , _properties(properties)
+ , _graph(nullptr)
+ , _parent(nullptr)
+{}
+
+CreateGraph::~CreateGraph()
+{
+ for (Event* ev : _child_events) {
+ delete ev;
+ }
+}
+
+void
+CreateGraph::build_child_events()
+{
+ const ingen::URIs& uris = _engine.world()->uris();
+
+ // Properties common to both ports
+ Properties control_properties;
+ control_properties.put(uris.atom_bufferType, uris.atom_Sequence);
+ control_properties.put(uris.atom_supports, uris.patch_Message);
+ control_properties.put(uris.lv2_designation, uris.lv2_control);
+ control_properties.put(uris.lv2_portProperty, uris.lv2_connectionOptional);
+ control_properties.put(uris.rdf_type, uris.atom_AtomPort);
+ control_properties.put(uris.rsz_minimumSize, uris.forge.make(4096));
+
+ // Add control port (message receive)
+ Properties in_properties(control_properties);
+ in_properties.put(uris.lv2_index, uris.forge.make(0));
+ in_properties.put(uris.lv2_name, uris.forge.alloc("Control"));
+ in_properties.put(uris.rdf_type, uris.lv2_InputPort);
+ in_properties.put(uris.ingen_canvasX, uris.forge.make(32.0f),
+ Resource::Graph::EXTERNAL);
+ in_properties.put(uris.ingen_canvasY, uris.forge.make(32.0f),
+ Resource::Graph::EXTERNAL);
+
+ _child_events.push_back(
+ new events::CreatePort(
+ _engine, _request_client, -1, _time,
+ _path.child(Raul::Symbol("control")),
+ in_properties));
+
+ // Add notify port (message respond)
+ Properties out_properties(control_properties);
+ out_properties.put(uris.lv2_index, uris.forge.make(1));
+ out_properties.put(uris.lv2_name, uris.forge.alloc("Notify"));
+ out_properties.put(uris.rdf_type, uris.lv2_OutputPort);
+ out_properties.put(uris.ingen_canvasX, uris.forge.make(128.0f),
+ Resource::Graph::EXTERNAL);
+ out_properties.put(uris.ingen_canvasY, uris.forge.make(32.0f),
+ Resource::Graph::EXTERNAL);
+
+ _child_events.push_back(
+ new events::CreatePort(_engine, _request_client, -1, _time,
+ _path.child(Raul::Symbol("notify")),
+ out_properties));
+}
+
+bool
+CreateGraph::pre_process(PreProcessContext& ctx)
+{
+ if (_engine.store()->get(_path)) {
+ return Event::pre_process_done(Status::EXISTS, _path);
+ }
+
+ if (!_path.is_root()) {
+ const Raul::Path up(_path.parent());
+ if (!(_parent = dynamic_cast<GraphImpl*>(_engine.store()->get(up)))) {
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND, up);
+ }
+ }
+
+ const ingen::URIs& uris = _engine.world()->uris();
+
+ typedef Properties::const_iterator iterator;
+
+ uint32_t ext_poly = 1;
+ uint32_t int_poly = 1;
+ iterator p = _properties.find(uris.ingen_polyphony);
+ if (p != _properties.end() && p->second.type() == uris.forge.Int) {
+ int_poly = p->second.get<int32_t>();
+ }
+
+ if (int_poly < 1 || int_poly > 128) {
+ return Event::pre_process_done(Status::INVALID_POLY, _path);
+ }
+
+ if (!_parent || int_poly == _parent->internal_poly()) {
+ ext_poly = int_poly;
+ }
+
+ const Raul::Symbol symbol(_path.is_root() ? "graph" : _path.symbol());
+
+ // Get graph prototype
+ iterator t = _properties.find(uris.lv2_prototype);
+ if (t == _properties.end()) {
+ t = _properties.find(uris.lv2_prototype);
+ }
+
+ if (t != _properties.end() &&
+ uris.forge.is_uri(t->second) &&
+ URI::is_valid(uris.forge.str(t->second, false)) &&
+ uri_is_path(URI(uris.forge.str(t->second, false)))) {
+ // Create a duplicate of an existing graph
+ const URI prototype(uris.forge.str(t->second, false));
+ GraphImpl* ancestor = dynamic_cast<GraphImpl*>(
+ _engine.store()->get(uri_to_path(prototype)));
+ if (!ancestor) {
+ return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype);
+ } else if (!(_graph = dynamic_cast<GraphImpl*>(
+ 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.sample_rate(), int_poly);
+ _graph->add_property(uris.rdf_type, uris.ingen_Graph.urid);
+ _graph->add_property(uris.rdf_type,
+ Property(uris.ingen_Block,
+ Resource::Graph::EXTERNAL));
+ }
+
+ _graph->set_properties(_properties);
+
+ if (_parent) {
+ // Add graph to parent
+ _parent->add_block(*_graph);
+ if (_parent->enabled()) {
+ _graph->enable();
+ }
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_parent);
+ }
+
+ _graph->activate(*_engine.buffer_factory());
+
+ // Insert into store and build update to send to clients
+ _engine.store()->add(_graph);
+ _update.put_graph(_graph);
+ for (BlockImpl& block : _graph->blocks()) {
+ _engine.store()->add(&block);
+ }
+
+ // Build and pre-process child events to create standard ports
+ build_child_events();
+ for (Event* ev : _child_events) {
+ ev->pre_process(ctx);
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+CreateGraph::execute(RunContext& context)
+{
+ if (_graph) {
+ if (_parent) {
+ if (_compiled_graph) {
+ _parent->set_compiled_graph(std::move(_compiled_graph));
+ }
+ } else {
+ _engine.set_root_graph(_graph);
+ _graph->enable();
+ }
+
+ for (Event* ev : _child_events) {
+ ev->execute(context);
+ }
+ }
+}
+
+void
+CreateGraph::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _update.send(*_engine.broadcaster());
+ }
+
+ if (_graph) {
+ for (Event* ev : _child_events) {
+ ev->post_process();
+ }
+ }
+}
+
+void
+CreateGraph::undo(Interface& target)
+{
+ target.del(_graph->uri());
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/CreateGraph.hpp b/src/server/events/CreateGraph.hpp
new file mode 100644
index 00000000..1537283e
--- /dev/null
+++ b/src/server/events/CreateGraph.hpp
@@ -0,0 +1,74 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_CREATEGRAPH_HPP
+#define INGEN_EVENTS_CREATEGRAPH_HPP
+
+#include <list>
+
+#include "ingen/Resource.hpp"
+
+#include "CompiledGraph.hpp"
+#include "Event.hpp"
+#include "events/Get.hpp"
+
+namespace ingen {
+namespace server {
+
+class GraphImpl;
+
+namespace events {
+
+/** Creates a new Graph.
+ *
+ * \ingroup engine
+ */
+class CreateGraph : public Event
+{
+public:
+ CreateGraph(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ const Properties& properties);
+
+ ~CreateGraph();
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+ GraphImpl* graph() { return _graph; }
+
+private:
+ void build_child_events();
+
+ const Raul::Path _path;
+ Properties _properties;
+ ClientUpdate _update;
+ GraphImpl* _graph;
+ GraphImpl* _parent;
+ MPtr<CompiledGraph> _compiled_graph;
+ std::list<Event*> _child_events;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_CREATEGRAPH_HPP
diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp
new file mode 100644
index 00000000..a79c85ef
--- /dev/null
+++ b/src/server/events/CreatePort.cpp
@@ -0,0 +1,219 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <utility>
+
+#include "ingen/Atom.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIMap.hpp"
+#include "ingen/URIs.hpp"
+#include "raul/Array.hpp"
+#include "raul/Path.hpp"
+
+#include "Broadcaster.hpp"
+#include "BufferFactory.hpp"
+#include "CreatePort.hpp"
+#include "Driver.hpp"
+#include "DuplexPort.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "PortImpl.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+CreatePort::CreatePort(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ const Properties& properties)
+ : Event(engine, client, id, timestamp)
+ , _path(path)
+ , _port_type(PortType::UNKNOWN)
+ , _buf_type(0)
+ , _graph(nullptr)
+ , _graph_port(nullptr)
+ , _engine_port(nullptr)
+ , _properties(properties)
+{
+ const ingen::URIs& uris = _engine.world()->uris();
+
+ typedef Properties::const_iterator Iterator;
+ typedef std::pair<Iterator, Iterator> Range;
+
+ const Range types = properties.equal_range(uris.rdf_type);
+ for (Iterator i = types.first; i != types.second; ++i) {
+ const Atom& type = i->second;
+ if (type == uris.lv2_AudioPort) {
+ _port_type = PortType::AUDIO;
+ } else if (type == uris.lv2_ControlPort) {
+ _port_type = PortType::CONTROL;
+ } else if (type == uris.lv2_CVPort) {
+ _port_type = PortType::CV;
+ } else if (type == uris.atom_AtomPort) {
+ _port_type = PortType::ATOM;
+ } else if (type == uris.lv2_InputPort) {
+ _flow = Flow::INPUT;
+ } else if (type == uris.lv2_OutputPort) {
+ _flow = Flow::OUTPUT;
+ }
+ }
+
+ const Range buffer_types = properties.equal_range(uris.atom_bufferType);
+ for (Iterator i = buffer_types.first; i != buffer_types.second; ++i) {
+ if (uris.forge.is_uri(i->second)) {
+ _buf_type = _engine.world()->uri_map().map_uri(
+ uris.forge.str(i->second, false));
+ }
+ }
+}
+
+bool
+CreatePort::pre_process(PreProcessContext& ctx)
+{
+ if (_port_type == PortType::UNKNOWN) {
+ return Event::pre_process_done(Status::UNKNOWN_TYPE, _path);
+ } else if (!_flow) {
+ return Event::pre_process_done(Status::UNKNOWN_TYPE, _path);
+ } else if (_path.is_root()) {
+ return Event::pre_process_done(Status::BAD_URI, _path);
+ } else if (_engine.store()->get(_path)) {
+ return Event::pre_process_done(Status::EXISTS, _path);
+ }
+
+ 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, parent_path);
+ } else if (!(_graph = dynamic_cast<GraphImpl*>(parent))) {
+ return Event::pre_process_done(Status::INVALID_PARENT, parent_path);
+ } else if (!_graph->parent() && _engine.activated() &&
+ !_engine.driver()->dynamic_ports()) {
+ return Event::pre_process_done(Status::CREATION_FAILED, _path);
+ }
+
+ 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 Properties::const_iterator PropIter;
+
+ PropIter index_i = _properties.find(uris.lv2_index);
+ int32_t index = 0;
+ if (index_i != _properties.end()) {
+ // Ensure given index is sane and not taken
+ if (index_i->second.type() != uris.forge.Int) {
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+
+ index = index_i->second.get<int32_t>();
+ if (_graph->has_port_with_index(index)) {
+ return Event::pre_process_done(Status::BAD_INDEX);
+ }
+ } else {
+ // No index given, append
+ index = old_n_ports;
+ index_i = _properties.emplace(uris.lv2_index,
+ _engine.world()->forge().make(index));
+ }
+
+ const PropIter poly_i = _properties.find(uris.ingen_polyphonic);
+ const bool polyphonic = (poly_i != _properties.end() &&
+ poly_i->second.type() == uris.forge.Bool &&
+ poly_i->second.get<int32_t>());
+
+ // Create 0 value if the port requires one
+ Atom value;
+ if (_port_type == PortType::CONTROL || _port_type == PortType::CV) {
+ value = bufs.forge().make(0.0f);
+ }
+
+ // Create port
+ _graph_port = new DuplexPort(bufs, _graph, Raul::Symbol(_path.symbol()),
+ index,
+ polyphonic,
+ _port_type, _buf_type, buf_size,
+ value, _flow == Flow::OUTPUT);
+ assert((_flow == Flow::OUTPUT && _graph_port->is_output()) ||
+ (_flow == Flow::INPUT && _graph_port->is_input()));
+ _graph_port->properties().insert(_properties.begin(), _properties.end());
+
+ _engine.store()->add(_graph_port);
+ if (_flow == Flow::OUTPUT) {
+ _graph->add_output(*_graph_port);
+ } else {
+ _graph->add_input(*_graph_port);
+ }
+
+ if (!_graph->parent()) {
+ _engine_port = _engine.driver()->create_port(_graph_port);
+ }
+
+ _ports_array = bufs.maid().make_managed<GraphImpl::Ports>(
+ old_n_ports + 1, nullptr);
+
+ _update = _graph_port->properties();
+
+ assert(_graph_port->index() == (uint32_t)index_i->second.get<int32_t>());
+ assert(_graph->num_ports_non_rt() == (uint32_t)old_n_ports + 1);
+ assert(_ports_array->size() == _graph->num_ports_non_rt());
+ assert(_graph_port->index() < _ports_array->size());
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+CreatePort::execute(RunContext& context)
+{
+ if (_status == Status::SUCCESS) {
+ const MPtr<GraphImpl::Ports>& old_ports = _graph->external_ports();
+ if (old_ports) {
+ for (uint32_t i = 0; i < old_ports->size(); ++i) {
+ const auto* const old_port = (*old_ports)[i];
+ assert(old_port->index() < _ports_array->size());
+ (*_ports_array)[old_port->index()] = (*old_ports)[i];
+ }
+ }
+ assert(!(*_ports_array)[_graph_port->index()]);
+ (*_ports_array)[_graph_port->index()] = _graph_port;
+ _graph->set_external_ports(std::move(_ports_array));
+
+ if (_engine_port) {
+ _engine.driver()->add_port(context, _engine_port);
+ }
+ }
+}
+
+void
+CreatePort::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->put(path_to_uri(_path), _update);
+ }
+}
+
+void
+CreatePort::undo(Interface& target)
+{
+ target.del(_graph_port->uri());
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/CreatePort.hpp b/src/server/events/CreatePort.hpp
new file mode 100644
index 00000000..6c760d29
--- /dev/null
+++ b/src/server/events/CreatePort.hpp
@@ -0,0 +1,82 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_CREATEPORT_HPP
+#define INGEN_EVENTS_CREATEPORT_HPP
+
+#include <boost/optional.hpp>
+
+#include "ingen/Resource.hpp"
+#include "lv2/urid/urid.h"
+#include "raul/Array.hpp"
+#include "raul/Path.hpp"
+
+#include "BlockImpl.hpp"
+#include "Event.hpp"
+#include "PortType.hpp"
+
+namespace ingen {
+namespace server {
+
+class DuplexPort;
+class EnginePort;
+class GraphImpl;
+class PortImpl;
+
+namespace events {
+
+/** An event to add a Port to a Graph.
+ *
+ * \ingroup engine
+ */
+class CreatePort : public Event
+{
+public:
+ CreatePort(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& path,
+ const Properties& properties);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ enum class Flow {
+ INPUT,
+ OUTPUT
+ };
+
+ Raul::Path _path;
+ PortType _port_type;
+ LV2_URID _buf_type;
+ GraphImpl* _graph;
+ DuplexPort* _graph_port;
+ MPtr<BlockImpl::Ports> _ports_array; ///< New external port array for Graph
+ EnginePort* _engine_port; ///< Driver port if on the root
+ Properties _properties;
+ Properties _update;
+ boost::optional<Flow> _flow;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_CREATEPORT_HPP
diff --git a/src/server/events/Delete.cpp b/src/server/events/Delete.cpp
new file mode 100644
index 00000000..ff9f32b4
--- /dev/null
+++ b/src/server/events/Delete.cpp
@@ -0,0 +1,216 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Store.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "ControlBindings.hpp"
+#include "Delete.hpp"
+#include "DisconnectAll.hpp"
+#include "Driver.hpp"
+#include "Engine.hpp"
+#include "EnginePort.hpp"
+#include "GraphImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+#include "PreProcessContext.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Delete::Delete(Engine& engine,
+ SPtr<Interface> client,
+ FrameTime timestamp,
+ const ingen::Del& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _engine_port(nullptr)
+ , _disconnect_event(nullptr)
+{
+ if (uri_is_path(msg.uri)) {
+ _path = uri_to_path(msg.uri);
+ }
+}
+
+Delete::~Delete()
+{
+ delete _disconnect_event;
+ for (ControlBindings::Binding* b : _removed_bindings) {
+ delete b;
+ }
+}
+
+bool
+Delete::pre_process(PreProcessContext& ctx)
+{
+ const ingen::URIs& uris = _engine.world()->uris();
+ if (_path.is_root() || _path == "/control" || _path == "/notify") {
+ return Event::pre_process_done(Status::NOT_DELETABLE, _path);
+ }
+
+ _engine.control_bindings()->get_all(_path, _removed_bindings);
+
+ auto iter = _engine.store()->find(_path);
+ if (iter == _engine.store()->end()) {
+ return Event::pre_process_done(Status::NOT_FOUND, _path);
+ }
+
+ if (!(_block = dynamic_ptr_cast<BlockImpl>(iter->second))) {
+ _port = dynamic_ptr_cast<DuplexPort>(iter->second);
+ }
+
+ if ((!_block && !_port) || (_port && !_engine.driver()->dynamic_ports())) {
+ return Event::pre_process_done(Status::NOT_DELETABLE, _path);
+ }
+
+ GraphImpl* parent = _block ? _block->parent_graph() : _port->parent_graph();
+ if (!parent) {
+ return Event::pre_process_done(Status::INTERNAL_ERROR, _path);
+ }
+
+ // Take a writer lock while we modify the store
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ _engine.store()->remove(iter, _removed_objects);
+
+ if (_block) {
+ parent->remove_block(*_block);
+ _disconnect_event = new DisconnectAll(_engine, parent, _block.get());
+ _disconnect_event->pre_process(ctx);
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *parent);
+ } else if (_port) {
+ parent->remove_port(*_port);
+ _disconnect_event = new DisconnectAll(_engine, parent, _port.get());
+ _disconnect_event->pre_process(ctx);
+
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *parent);
+ if (parent->enabled()) {
+ _ports_array = parent->build_ports_array(*_engine.maid());
+ assert(_ports_array->size() == parent->num_ports_non_rt());
+
+ // Adjust port indices if necessary and record changes for later
+ for (size_t i = 0; i < _ports_array->size(); ++i) {
+ PortImpl* const port = _ports_array->at(i);
+ if (port->index() != i) {
+ _port_index_changes.emplace(
+ port->path(), std::make_pair(port->index(), i));
+ port->remove_property(uris.lv2_index, uris.patch_wildcard);
+ port->set_property(
+ uris.lv2_index,
+ _engine.buffer_factory()->forge().make((int32_t)i));
+ }
+ }
+ }
+
+ if (!parent->parent()) {
+ _engine_port = _engine.driver()->get_port(_port->path());
+ }
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Delete::execute(RunContext& context)
+{
+ if (_status != Status::SUCCESS) {
+ return;
+ }
+
+ if (_disconnect_event) {
+ _disconnect_event->execute(context);
+ }
+
+ if (!_removed_bindings.empty()) {
+ _engine.control_bindings()->remove(context, _removed_bindings);
+ }
+
+ GraphImpl* parent = _block ? _block->parent_graph() : nullptr;
+ if (_port) {
+ // Adjust port indices if necessary
+ for (size_t i = 0; i < _ports_array->size(); ++i) {
+ PortImpl* const port = _ports_array->at(i);
+ if (port->index() != i) {
+ port->set_index(context, i);
+ }
+ }
+
+ // Replace ports array in graph
+ parent = _port->parent_graph();
+ parent->set_external_ports(std::move(_ports_array));
+
+ if (_engine_port) {
+ _engine.driver()->remove_port(context, _engine_port);
+ }
+ }
+
+ if (parent && _compiled_graph) {
+ parent->set_compiled_graph(std::move(_compiled_graph));
+ }
+}
+
+void
+Delete::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS && (_block || _port)) {
+ if (_block) {
+ _block->deactivate();
+ }
+
+ _engine.broadcaster()->message(_msg);
+ }
+
+ if (_engine_port) {
+ _engine.driver()->unregister_port(*_engine_port);
+ delete _engine_port;
+ }
+}
+
+void
+Delete::undo(Interface& target)
+{
+ const ingen::URIs& uris = _engine.world()->uris();
+ ingen::Forge& forge = _engine.buffer_factory()->forge();
+
+ auto i = _removed_objects.find(_path);
+ if (i != _removed_objects.end()) {
+ // Undo disconnect
+ if (_disconnect_event) {
+ _disconnect_event->undo(target);
+ }
+
+ // Put deleted item back
+ target.put(_msg.uri, i->second->properties());
+
+ // Adjust port indices
+ for (const auto& c : _port_index_changes) {
+ if (c.first != _msg.uri.path()) {
+ target.set_property(path_to_uri(c.first),
+ uris.lv2_index,
+ forge.make(int32_t(c.second.first)));
+ }
+ }
+ }
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Delete.hpp b/src/server/events/Delete.hpp
new file mode 100644
index 00000000..4b0c314b
--- /dev/null
+++ b/src/server/events/Delete.hpp
@@ -0,0 +1,86 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_DELETE_HPP
+#define INGEN_EVENTS_DELETE_HPP
+
+#include <map>
+#include <vector>
+
+#include "ingen/Store.hpp"
+
+#include "CompiledGraph.hpp"
+#include "ControlBindings.hpp"
+#include "Event.hpp"
+#include "GraphImpl.hpp"
+
+namespace Raul {
+template<typename T> class Array;
+}
+
+namespace ingen {
+namespace server {
+
+class BlockImpl;
+class DuplexPort;
+class EnginePort;
+class PortImpl;
+
+namespace events {
+
+class DisconnectAll;
+
+/** Delete a graph object.
+ * \ingroup engine
+ */
+class Delete : public Event
+{
+public:
+ Delete(Engine& engine,
+ SPtr<Interface> client,
+ FrameTime timestamp,
+ const ingen::Del& msg);
+
+ ~Delete();
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ using IndexChange = std::pair<uint32_t, uint32_t>;
+ using IndexChanges = std::map<Raul::Path, IndexChange>;
+
+ const ingen::Del _msg;
+ Raul::Path _path;
+ SPtr<BlockImpl> _block; ///< Non-null iff a block
+ SPtr<DuplexPort> _port; ///< Non-null iff a port
+ EnginePort* _engine_port;
+ MPtr<GraphImpl::Ports> _ports_array; ///< New (external) ports for Graph
+ MPtr<CompiledGraph> _compiled_graph; ///< Graph's new process order
+ DisconnectAll* _disconnect_event;
+ Store::Objects _removed_objects;
+ IndexChanges _port_index_changes;
+
+ std::vector<ControlBindings::Binding*> _removed_bindings;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_DELETE_HPP
diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp
new file mode 100644
index 00000000..7404aea6
--- /dev/null
+++ b/src/server/events/Delta.cpp
@@ -0,0 +1,670 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <vector>
+#include <thread>
+
+#include "ingen/Log.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIs.hpp"
+#include "raul/Maid.hpp"
+
+#include "Broadcaster.hpp"
+#include "ControlBindings.hpp"
+#include "CreateBlock.hpp"
+#include "CreateGraph.hpp"
+#include "CreatePort.hpp"
+#include "Delta.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+#include "PortType.hpp"
+#include "SetPortValue.hpp"
+#include "events/Get.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Delta::Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Put& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _create_event(nullptr)
+ , _subject(msg.uri)
+ , _properties(msg.properties)
+ , _object(nullptr)
+ , _graph(nullptr)
+ , _binding(nullptr)
+ , _state(nullptr)
+ , _context(msg.ctx)
+ , _type(Type::PUT)
+ , _block(false)
+{
+ init();
+}
+
+Delta::Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Delta& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _create_event(nullptr)
+ , _subject(msg.uri)
+ , _properties(msg.add)
+ , _remove(msg.remove)
+ , _object(nullptr)
+ , _graph(nullptr)
+ , _binding(nullptr)
+ , _state(nullptr)
+ , _context(msg.ctx)
+ , _type(Type::PATCH)
+ , _block(false)
+{
+ init();
+}
+
+Delta::Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::SetProperty& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _create_event(nullptr)
+ , _subject(msg.subject)
+ , _properties{{msg.predicate, msg.value}}
+ , _object(nullptr)
+ , _graph(nullptr)
+ , _binding(nullptr)
+ , _state(nullptr)
+ , _context(msg.ctx)
+ , _type(Type::SET)
+ , _block(false)
+{
+ init();
+}
+
+Delta::~Delta()
+{
+ for (auto& s : _set_events) {
+ delete s;
+ }
+
+ delete _create_event;
+}
+
+void
+Delta::init()
+{
+ if (_context != Resource::Graph::DEFAULT) {
+ for (auto& p : _properties) {
+ p.second.set_context(_context);
+ }
+ }
+
+ // Set atomic execution if polyphony is to be changed
+ const ingen::URIs& uris = _engine.world()->uris();
+ if (_properties.count(uris.ingen_polyphonic) ||
+ _properties.count(uris.ingen_polyphony)) {
+ _block = true;
+ }
+}
+
+void
+Delta::add_set_event(const char* port_symbol,
+ const void* value,
+ uint32_t size,
+ uint32_t type)
+{
+ BlockImpl* block = dynamic_cast<BlockImpl*>(_object);
+ PortImpl* port = block->port_by_symbol(port_symbol);
+ if (!port) {
+ _engine.log().warn(fmt("Unknown port `%1%' in state") % port_symbol);
+ return;
+ }
+
+ SetPortValue* ev = new SetPortValue(
+ _engine, _request_client, _request_id, _time,
+ port, Atom(size, type, value), false, true);
+
+ _set_events.push_back(ev);
+}
+
+static void
+s_add_set_event(const char* port_symbol,
+ void* user_data,
+ const void* value,
+ uint32_t size,
+ uint32_t type)
+{
+ ((Delta*)user_data)->add_set_event(port_symbol, value, size, type);
+}
+
+static LilvNode*
+get_file_node(LilvWorld* lworld, const URIs& uris, const Atom& value)
+{
+ if (value.type() == uris.atom_Path) {
+ return lilv_new_file_uri(lworld, nullptr, value.ptr<char>());
+ } else if (uris.forge.is_uri(value)) {
+ const std::string str = uris.forge.str(value, false);
+ if (str.substr(0, 5) == "file:") {
+ return lilv_new_uri(lworld, value.ptr<char>());
+ }
+ }
+ return nullptr;
+}
+
+bool
+Delta::pre_process(PreProcessContext& ctx)
+{
+ const ingen::URIs& uris = _engine.world()->uris();
+
+ const bool is_graph_object = uri_is_path(_subject);
+ const bool is_client = (_subject == "ingen:/clients/this");
+ const bool is_engine = (_subject == "ingen:/");
+ const bool is_file = (_subject.scheme() == "file");
+
+ if (_type == Type::PUT && is_file) {
+ // Ensure type is Preset, the only supported file put
+ const auto t = _properties.find(uris.rdf_type);
+ if (t == _properties.end() || t->second != uris.pset_Preset) {
+ return Event::pre_process_done(Status::BAD_REQUEST, _subject);
+ }
+
+ // Get "prototype" for preset (node to save state for)
+ const auto p = _properties.find(uris.lv2_prototype);
+ if (p == _properties.end()) {
+ return Event::pre_process_done(Status::BAD_REQUEST, _subject);
+ } else if (!_engine.world()->forge().is_uri(p->second)) {
+ return Event::pre_process_done(Status::BAD_REQUEST, _subject);
+ }
+
+ const URI prot(_engine.world()->forge().str(p->second, false));
+ if (!uri_is_path(prot)) {
+ return Event::pre_process_done(Status::BAD_URI, _subject);
+ }
+
+ Node* node = _engine.store()->get(uri_to_path(prot));
+ if (!node) {
+ return Event::pre_process_done(Status::NOT_FOUND, prot);
+ }
+
+ BlockImpl* block = dynamic_cast<BlockImpl*>(node);
+ if (!block) {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, prot);
+ }
+
+ if ((_preset = block->save_preset(_subject, _properties))) {
+ return Event::pre_process_done(Status::SUCCESS);
+ } else {
+ return Event::pre_process_done(Status::FAILURE);
+ }
+ }
+
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ _object = is_graph_object
+ ? static_cast<ingen::Resource*>(_engine.store()->get(uri_to_path(_subject)))
+ : static_cast<ingen::Resource*>(_engine.block_factory()->plugin(_subject));
+
+ if (!_object && !is_client && !is_engine &&
+ (!is_graph_object || _type != Type::PUT)) {
+ return Event::pre_process_done(Status::NOT_FOUND, _subject);
+ }
+
+ if (is_graph_object && !_object) {
+ Raul::Path path(uri_to_path(_subject));
+ bool is_graph = false, is_block = false, is_port = false, is_output = false;
+ ingen::Resource::type(uris, _properties, is_graph, is_block, is_port, is_output);
+
+ if (is_graph) {
+ _create_event = new CreateGraph(
+ _engine, _request_client, _request_id, _time, path, _properties);
+ } else if (is_block) {
+ _create_event = new CreateBlock(
+ _engine, _request_client, _request_id, _time, path, _properties);
+ } else if (is_port) {
+ _create_event = new CreatePort(
+ _engine, _request_client, _request_id, _time,
+ path, _properties);
+ }
+ if (_create_event) {
+ if (_create_event->pre_process(ctx)) {
+ _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);
+ }
+ }
+
+ _types.reserve(_properties.size());
+
+ NodeImpl* obj = dynamic_cast<NodeImpl*>(_object);
+
+ // Remove any properties removed in delta
+ for (const auto& r : _remove) {
+ const URI& key = r.first;
+ const Atom& value = r.second;
+ if (key == uris.midi_binding && value == uris.patch_wildcard) {
+ PortImpl* port = dynamic_cast<PortImpl*>(_object);
+ if (port) {
+ _engine.control_bindings()->get_all(port->path(), _removed_bindings);
+ }
+ }
+ if (_object) {
+ _removed.emplace(key, value);
+ _object->remove_property(key, value);
+ } else if (is_engine && key == uris.ingen_loadedBundle) {
+ LilvWorld* lworld = _engine.world()->lilv_world();
+ LilvNode* bundle = get_file_node(lworld, uris, value);
+ if (bundle) {
+ for (const auto& p : _engine.block_factory()->plugins()) {
+ if (p.second->bundle_uri() == lilv_node_as_string(bundle)) {
+ p.second->set_is_zombie(true);
+ _update.del(p.second->uri());
+ }
+ }
+ lilv_world_unload_bundle(lworld, bundle);
+ _engine.block_factory()->refresh();
+ lilv_node_free(bundle);
+ } else {
+ _status = Status::BAD_VALUE;
+ }
+ }
+ }
+
+ // Remove all added properties if this is a put or set
+ if (_object && (_type == Type::PUT || _type == Type::SET)) {
+ for (auto p = _properties.begin();
+ p != _properties.end();
+ p = _properties.upper_bound(p->first)) {
+ for (auto q = _object->properties().find(p->first);
+ q != _object->properties().end() && q->first == p->first;) {
+ auto next = q;
+ ++next;
+
+ if (!_properties.contains(q->first, q->second)) {
+ const auto r = std::make_pair(q->first, q->second);
+ _object->properties().erase(q);
+ _object->on_property_removed(r.first, r.second);
+ _removed.insert(r);
+ }
+
+ q = next;
+ }
+ }
+ }
+
+ for (const auto& p : _properties) {
+ const URI& key = p.first;
+ const Property& value = p.second;
+ SpecialType op = SpecialType::NONE;
+ if (obj) {
+ Resource& resource = *obj;
+ if (value != uris.patch_wildcard) {
+ if (resource.add_property(key, value, value.context())) {
+ _added.emplace(key, value);
+ }
+ }
+
+ BlockImpl* block = nullptr;
+ PortImpl* port = dynamic_cast<PortImpl*>(_object);
+ if (port) {
+ if (key == uris.ingen_broadcast) {
+ if (value.type() == uris.forge.Bool) {
+ op = SpecialType::ENABLE_BROADCAST;
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+ } else if (key == uris.ingen_value || key == uris.ingen_activity) {
+ SetPortValue* ev = new SetPortValue(
+ _engine, _request_client, _request_id, _time, port, value,
+ key == uris.ingen_activity);
+ _set_events.push_back(ev);
+ } else if (key == uris.midi_binding) {
+ if (port->is_a(PortType::CONTROL) || port->is_a(PortType::CV)) {
+ if (value == uris.patch_wildcard) {
+ _engine.control_bindings()->start_learn(port);
+ } else if (value.type() == uris.atom_Object) {
+ op = SpecialType::CONTROL_BINDING;
+ _binding = new ControlBindings::Binding();
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+ } else {
+ _status = Status::BAD_OBJECT_TYPE;
+ }
+ } else if (key == uris.lv2_index) {
+ op = SpecialType::PORT_INDEX;
+ port->set_property(key, value);
+ }
+ } else if ((block = dynamic_cast<BlockImpl*>(_object))) {
+ if (key == uris.midi_binding && value == uris.patch_wildcard) {
+ op = SpecialType::CONTROL_BINDING; // Internal block learn
+ } else if (key == uris.ingen_enabled) {
+ if (value.type() == uris.forge.Bool) {
+ op = SpecialType::ENABLE;
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+ } else if (key == uris.pset_preset) {
+ URI uri;
+ if (uris.forge.is_uri(value)) {
+ const std::string uri_str = uris.forge.str(value, false);
+ if (URI::is_valid(uri_str)) {
+ uri = URI(uri_str);
+ }
+ } else if (value.type() == uris.forge.Path) {
+ uri = URI(FilePath(value.ptr<char>()));
+ }
+
+ if (!uri.empty()) {
+ op = SpecialType::PRESET;
+ if ((_state = block->load_preset(uri))) {
+ lilv_state_emit_port_values(
+ _state, s_add_set_event, this);
+ } else {
+ _engine.log().warn(fmt("Failed to load preset <%1%>\n") % uri);
+ }
+ } else {
+ _status = Status::BAD_VALUE;
+ }
+ }
+ }
+
+ if ((_graph = dynamic_cast<GraphImpl*>(_object))) {
+ if (key == uris.ingen_enabled) {
+ if (value.type() == uris.forge.Bool) {
+ op = SpecialType::ENABLE;
+ // FIXME: defer this until all other metadata has been processed
+ if (value.get<int32_t>() && !_graph->enabled()) {
+ if (!(_compiled_graph = compile(*_engine.maid(), *_graph))) {
+ _status = Status::COMPILATION_FAILED;
+ }
+ }
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+ } else if (key == uris.ingen_polyphony) {
+ if (value.type() == uris.forge.Int) {
+ if (value.get<int32_t>() < 1 || value.get<int32_t>() > 128) {
+ _status = Status::INVALID_POLY;
+ } else {
+ op = SpecialType::POLYPHONY;
+ _graph->prepare_internal_poly(
+ *_engine.buffer_factory(), value.get<int32_t>());
+ }
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+ }
+ }
+
+ if (!_create_event && key == uris.ingen_polyphonic) {
+ GraphImpl* parent = dynamic_cast<GraphImpl*>(obj->parent());
+ if (!parent) {
+ _status = Status::BAD_OBJECT_TYPE;
+ } else if (value.type() != uris.forge.Bool) {
+ _status = Status::BAD_VALUE_TYPE;
+ } else {
+ op = SpecialType::POLYPHONIC;
+ obj->set_property(key, value, value.context());
+ BlockImpl* block = dynamic_cast<BlockImpl*>(obj);
+ if (block) {
+ block->set_polyphonic(value.get<int32_t>());
+ }
+ if (value.get<int32_t>()) {
+ obj->prepare_poly(*_engine.buffer_factory(), parent->internal_poly());
+ } else {
+ obj->prepare_poly(*_engine.buffer_factory(), 1);
+ }
+ }
+ }
+ } else if (is_client && key == uris.ingen_broadcast) {
+ _engine.broadcaster()->set_broadcast(
+ _request_client, value.get<int32_t>());
+ } else if (is_engine && key == uris.ingen_loadedBundle) {
+ LilvWorld* lworld = _engine.world()->lilv_world();
+ LilvNode* bundle = get_file_node(lworld, uris, value);
+ if (bundle) {
+ lilv_world_load_bundle(lworld, bundle);
+ const std::set<PluginImpl*> new_plugins =
+ _engine.block_factory()->refresh();
+
+ for (PluginImpl* p : new_plugins) {
+ if (p->bundle_uri() == lilv_node_as_string(bundle)) {
+ _update.put_plugin(p);
+ }
+ }
+ lilv_node_free(bundle);
+ } else {
+ _status = Status::BAD_VALUE;
+ }
+ }
+
+ if (_status != Status::NOT_PREPARED) {
+ break;
+ }
+
+ _types.push_back(op);
+ }
+
+ for (auto& s : _set_events) {
+ s->pre_process(ctx);
+ }
+
+ return Event::pre_process_done(
+ _status == Status::NOT_PREPARED ? Status::SUCCESS : _status,
+ _subject);
+}
+
+void
+Delta::execute(RunContext& context)
+{
+ if (_status != Status::SUCCESS || _preset) {
+ return;
+ }
+
+ const ingen::URIs& uris = _engine.world()->uris();
+
+ if (_create_event) {
+ _create_event->set_time(_time);
+ _create_event->execute(context);
+ }
+
+ for (auto& s : _set_events) {
+ s->set_time(_time);
+ s->execute(context);
+ }
+
+ if (!_removed_bindings.empty()) {
+ _engine.control_bindings()->remove(context, _removed_bindings);
+ }
+
+ NodeImpl* const object = dynamic_cast<NodeImpl*>(_object);
+ BlockImpl* const block = dynamic_cast<BlockImpl*>(_object);
+ PortImpl* const port = dynamic_cast<PortImpl*>(_object);
+
+ std::vector<SpecialType>::const_iterator t = _types.begin();
+ for (const auto& p : _properties) {
+ const URI& key = p.first;
+ const Atom& value = p.second;
+ switch (*t++) {
+ case SpecialType::ENABLE_BROADCAST:
+ if (port) {
+ port->enable_monitoring(value.get<int32_t>());
+ }
+ break;
+ case SpecialType::ENABLE:
+ if (_graph) {
+ if (value.get<int32_t>()) {
+ if (_compiled_graph) {
+ _graph->set_compiled_graph(std::move(_compiled_graph));
+ }
+ _graph->enable();
+ } else {
+ _graph->disable(context);
+ }
+ } else if (block) {
+ block->set_enabled(value.get<int32_t>());
+ }
+ break;
+ case SpecialType::POLYPHONIC: {
+ GraphImpl* parent = reinterpret_cast<GraphImpl*>(object->parent());
+ if (value.get<int32_t>()) {
+ object->apply_poly(context, parent->internal_poly_process());
+ } else {
+ object->apply_poly(context, 1);
+ }
+ } break;
+ case SpecialType::POLYPHONY:
+ if (!_graph->apply_internal_poly(context,
+ *_engine.buffer_factory(),
+ *_engine.maid(),
+ value.get<int32_t>())) {
+ _status = Status::INTERNAL_ERROR;
+ }
+ break;
+ case SpecialType::PORT_INDEX:
+ if (port) {
+ port->set_index(context, value.get<int32_t>());
+ }
+ break;
+ case SpecialType::CONTROL_BINDING:
+ if (port) {
+ if (!_engine.control_bindings()->set_port_binding(context, port, _binding, value)) {
+ _status = Status::BAD_VALUE;
+ }
+ } else if (block) {
+ if (uris.ingen_Internal == block->plugin_impl()->type()) {
+ block->learn();
+ }
+ }
+ break;
+ case SpecialType::PRESET:
+ block->set_enabled(false);
+ break;
+ case SpecialType::NONE:
+ if (port) {
+ if (key == uris.lv2_minimum) {
+ port->set_minimum(value);
+ } else if (key == uris.lv2_maximum) {
+ port->set_maximum(value);
+ }
+ }
+ case SpecialType::LOADED_BUNDLE:
+ break;
+ }
+ }
+}
+
+void
+Delta::post_process()
+{
+ if (_state) {
+ BlockImpl* block = dynamic_cast<BlockImpl*>(_object);
+ if (block) {
+ block->apply_state(_engine.sync_worker(), _state);
+ block->set_enabled(true);
+ }
+ lilv_state_free(_state);
+ }
+
+ Broadcaster::Transfer t(*_engine.broadcaster());
+
+ if (_create_event) {
+ _create_event->post_process();
+ if (_create_event->status() != Status::SUCCESS) {
+ return; // Creation failed, nothing else to do
+ }
+ }
+
+ for (auto& s : _set_events) {
+ if (s->synthetic() || s->status() != Status::SUCCESS) {
+ s->post_process(); // Set failed, report error
+ }
+ }
+
+ if (respond() == Status::SUCCESS) {
+ _update.send(*_engine.broadcaster());
+
+ 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. */
+ if (_mode == Mode::NORMAL) {
+ _engine.broadcaster()->set_ignore_client(_request_client);
+ }
+ _engine.broadcaster()->set_property(
+ _subject,
+ _properties.begin()->first,
+ _properties.begin()->second);
+ if (_mode == Mode::NORMAL) {
+ _engine.broadcaster()->clear_ignore_client();
+ }
+ break;
+ case Type::PUT:
+ if (_type == Type::PUT && _subject.scheme() == "file") {
+ // Preset save
+ ClientUpdate response;
+ response.put(_preset->uri(), _preset->properties());
+ response.send(*_engine.broadcaster());
+ } else {
+ // Graph object put
+ _engine.broadcaster()->put(_subject, _properties, _context);
+ }
+ break;
+ case Type::PATCH:
+ _engine.broadcaster()->delta(_subject, _remove, _properties, _context);
+ break;
+ }
+ }
+}
+
+void
+Delta::undo(Interface& target)
+{
+ if (_create_event) {
+ _create_event->undo(target);
+ } else if (_type == Type::PATCH) {
+ target.delta(_subject, _added, _removed, _context);
+ } else if (_type == Type::SET || _type == Type::PUT) {
+ if (_removed.size() == 1) {
+ target.set_property(_subject,
+ _removed.begin()->first,
+ _removed.begin()->second,
+ _context);
+ } else if (_removed.empty()) {
+ target.delta(_subject, _added, {}, _context);
+ } else {
+ target.put(_subject, _removed, _context);
+ }
+ }
+}
+
+Event::Execution
+Delta::get_execution() const
+{
+ return _block ? Execution::ATOMIC : Execution::NORMAL;
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Delta.hpp b/src/server/events/Delta.hpp
new file mode 100644
index 00000000..d36e81aa
--- /dev/null
+++ b/src/server/events/Delta.hpp
@@ -0,0 +1,133 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_DELTA_HPP
+#define INGEN_EVENTS_DELTA_HPP
+
+#include <vector>
+
+#include <boost/optional.hpp>
+
+#include "lilv/lilv.h"
+
+#include "CompiledGraph.hpp"
+#include "ControlBindings.hpp"
+#include "Event.hpp"
+#include "PluginImpl.hpp"
+
+namespace ingen {
+
+class Resource;
+
+namespace server {
+
+class Engine;
+class GraphImpl;
+class RunContext;
+
+namespace events {
+
+class SetPortValue;
+
+/** Set properties of a graph object.
+ * \ingroup engine
+ */
+class Delta : public Event
+{
+public:
+ Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Put& msg);
+
+ Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Delta& msg);
+
+ Delta(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::SetProperty& msg);
+
+ ~Delta();
+
+ void add_set_event(const char* port_symbol,
+ const void* value,
+ uint32_t size,
+ uint32_t type);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+ Execution get_execution() const override;
+
+private:
+ enum class Type {
+ SET,
+ PUT,
+ PATCH
+ };
+
+ enum class SpecialType {
+ NONE,
+ ENABLE,
+ ENABLE_BROADCAST,
+ POLYPHONY,
+ POLYPHONIC,
+ PORT_INDEX,
+ CONTROL_BINDING,
+ PRESET,
+ LOADED_BUNDLE
+ };
+
+ typedef std::vector<SetPortValue*> SetEvents;
+
+ void init();
+
+ Event* _create_event;
+ SetEvents _set_events;
+ std::vector<SpecialType> _types;
+ std::vector<SpecialType> _remove_types;
+ URI _subject;
+ Properties _properties;
+ Properties _remove;
+ ClientUpdate _update;
+ ingen::Resource* _object;
+ GraphImpl* _graph;
+ MPtr<CompiledGraph> _compiled_graph;
+ ControlBindings::Binding* _binding;
+ LilvState* _state;
+ Resource::Graph _context;
+ Type _type;
+
+ Properties _added;
+ Properties _removed;
+
+ std::vector<ControlBindings::Binding*> _removed_bindings;
+
+ boost::optional<Resource> _preset;
+
+ bool _block;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_DELTA_HPP
diff --git a/src/server/events/Disconnect.cpp b/src/server/events/Disconnect.cpp
new file mode 100644
index 00000000..e803ce3d
--- /dev/null
+++ b/src/server/events/Disconnect.cpp
@@ -0,0 +1,224 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <set>
+
+#include "ingen/Store.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "ArcImpl.hpp"
+#include "Broadcaster.hpp"
+#include "Buffer.hpp"
+#include "DuplexPort.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "InputPort.hpp"
+#include "PortImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "RunContext.hpp"
+#include "ThreadManager.hpp"
+#include "events/Disconnect.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Disconnect::Disconnect(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Disconnect& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _graph(nullptr)
+ , _impl(nullptr)
+{
+}
+
+Disconnect::~Disconnect()
+{
+ delete _impl;
+}
+
+Disconnect::Impl::Impl(Engine& e,
+ GraphImpl* graph,
+ PortImpl* t,
+ InputPort* h)
+ : _engine(e)
+ , _tail(t)
+ , _head(h)
+ , _arc(graph->remove_arc(_tail, _head))
+{
+ ThreadManager::assert_thread(THREAD_PRE_PROCESS);
+
+ BlockImpl* const tail_block = _tail->parent_block();
+ BlockImpl* const head_block = _head->parent_block();
+
+ // Remove tail from head's providers
+ auto hp = head_block->providers().find(tail_block);
+ if (hp != head_block->providers().end()) {
+ head_block->providers().erase(hp);
+ }
+
+ // Remove head from tail's providers
+ auto td = tail_block->dependants().find(head_block);
+ if (td != tail_block->dependants().end()) {
+ tail_block->dependants().erase(td);
+ }
+
+ _head->decrement_num_arcs();
+
+ if (_head->num_arcs() == 0) {
+ if (!_head->is_driver_port()) {
+ BufferFactory& bufs = *_engine.buffer_factory();
+ _voices = bufs.maid().make_managed<PortImpl::Voices>(_head->poly());
+ _head->pre_get_buffers(bufs, _voices, _head->poly());
+
+ if (_head->is_a(PortType::CONTROL) ||
+ _head->is_a(PortType::CV)) {
+ // Reset buffer to control value
+ const float value = _head->value().get<float>();
+ for (uint32_t i = 0; i < _voices->size(); ++i) {
+ Buffer* buf = _voices->at(i).buffer.get();
+ buf->set_block(value, 0, e.block_length());
+ }
+ } else {
+ for (uint32_t i = 0; i < _voices->size(); ++i) {
+ _voices->at(i).buffer->clear();
+ }
+ }
+ }
+ }
+}
+
+bool
+Disconnect::pre_process(PreProcessContext& ctx)
+{
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ if (_msg.tail.parent().parent() != _msg.head.parent().parent()
+ && _msg.tail.parent() != _msg.head.parent().parent()
+ && _msg.tail.parent().parent() != _msg.head.parent()) {
+ return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.head);
+ }
+
+ PortImpl* tail = dynamic_cast<PortImpl*>(_engine.store()->get(_msg.tail));
+ if (!tail) {
+ return Event::pre_process_done(Status::PORT_NOT_FOUND, _msg.tail);
+ }
+
+ PortImpl* head = dynamic_cast<PortImpl*>(_engine.store()->get(_msg.head));
+ if (!head) {
+ return Event::pre_process_done(Status::PORT_NOT_FOUND, _msg.head);
+ }
+
+ BlockImpl* const tail_block = tail->parent_block();
+ BlockImpl* const head_block = head->parent_block();
+
+ if (tail_block->parent_graph() != head_block->parent_graph()) {
+ // Arc to a graph port from inside the graph
+ assert(tail_block->parent() == head_block || head_block->parent() == tail_block);
+ if (tail_block->parent() == head_block) {
+ _graph = dynamic_cast<GraphImpl*>(head_block);
+ } else {
+ _graph = dynamic_cast<GraphImpl*>(tail_block);
+ }
+ } else if (tail_block == head_block && dynamic_cast<GraphImpl*>(tail_block)) {
+ // Arc from a graph input to a graph output (pass through)
+ _graph = dynamic_cast<GraphImpl*>(tail_block);
+ } else {
+ // Normal arc between blocks with the same parent
+ _graph = tail_block->parent_graph();
+ }
+
+ if (!_graph) {
+ return Event::pre_process_done(Status::INTERNAL_ERROR, _msg.head);
+ } else if (!_graph->has_arc(tail, head)) {
+ return Event::pre_process_done(Status::NOT_FOUND, _msg.head);
+ }
+
+ if (tail_block == nullptr || head_block == nullptr) {
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND, _msg.head);
+ }
+
+ _impl = new Impl(_engine,
+ _graph,
+ dynamic_cast<PortImpl*>(tail),
+ dynamic_cast<InputPort*>(head));
+
+ _compiled_graph = ctx.maybe_compile(*_engine.maid(), *_graph);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+bool
+Disconnect::Impl::execute(RunContext& context, bool set_head_buffers)
+{
+ if (!_arc) {
+ return false;
+ }
+
+ _head->remove_arc(*_arc.get());
+ if (_head->is_driver_port()) {
+ return true;
+ }
+
+ if (set_head_buffers) {
+ if (_voices) {
+ _head->set_voices(context, std::move(_voices));
+ } else {
+ _head->setup_buffers(context, *_engine.buffer_factory(), _head->poly());
+ }
+ _head->connect_buffers();
+ } else {
+ _head->recycle_buffers();
+ }
+
+ return true;
+}
+
+void
+Disconnect::execute(RunContext& context)
+{
+ if (_status == Status::SUCCESS) {
+ if (_impl->execute(context, true)) {
+ if (_compiled_graph) {
+ _graph->set_compiled_graph(std::move(_compiled_graph));
+ }
+ } else {
+ _status = Status::NOT_FOUND;
+ }
+ }
+}
+
+void
+Disconnect::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->message(_msg);
+ }
+}
+
+void
+Disconnect::undo(Interface& target)
+{
+ target.connect(_msg.tail, _msg.head);
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Disconnect.hpp b/src/server/events/Disconnect.hpp
new file mode 100644
index 00000000..de4bfe57
--- /dev/null
+++ b/src/server/events/Disconnect.hpp
@@ -0,0 +1,87 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_DISCONNECT_HPP
+#define INGEN_EVENTS_DISCONNECT_HPP
+
+#include "raul/Path.hpp"
+
+#include "BufferFactory.hpp"
+#include "CompiledGraph.hpp"
+#include "Event.hpp"
+#include "GraphImpl.hpp"
+#include "types.hpp"
+
+namespace Raul {
+template <typename T> class Array;
+}
+
+namespace ingen {
+namespace server {
+
+class InputPort;
+class PortImpl;
+
+namespace events {
+
+/** Remove an Arc between two Ports.
+ *
+ * \ingroup engine
+ */
+class Disconnect : public Event
+{
+public:
+ Disconnect(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Disconnect& msg);
+
+ ~Disconnect();
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+ class Impl {
+ public:
+ Impl(Engine& e, GraphImpl* graph, PortImpl* t, InputPort* h);
+
+ bool execute(RunContext& context, bool set_head_buffers);
+
+ inline PortImpl* tail() { return _tail; }
+ inline InputPort* head() { return _head; }
+
+ private:
+ Engine& _engine;
+ PortImpl* _tail;
+ InputPort* _head;
+ SPtr<ArcImpl> _arc;
+ MPtr<PortImpl::Voices> _voices;
+ };
+
+private:
+ const ingen::Disconnect _msg;
+ GraphImpl* _graph;
+ Impl* _impl;
+ MPtr<CompiledGraph> _compiled_graph;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_DISCONNECT_HPP
diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp
new file mode 100644
index 00000000..e21fad4d
--- /dev/null
+++ b/src/server/events/DisconnectAll.cpp
@@ -0,0 +1,176 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <set>
+
+#include "ingen/Store.hpp"
+#include "raul/Array.hpp"
+#include "raul/Maid.hpp"
+#include "raul/Path.hpp"
+
+#include "ArcImpl.hpp"
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "Engine.hpp"
+#include "GraphImpl.hpp"
+#include "InputPort.hpp"
+#include "PortImpl.hpp"
+#include "PreProcessContext.hpp"
+#include "events/Disconnect.hpp"
+#include "events/DisconnectAll.hpp"
+#include "util.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+DisconnectAll::DisconnectAll(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::DisconnectAll& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _parent(nullptr)
+ , _block(nullptr)
+ , _port(nullptr)
+ , _deleting(false)
+{
+}
+
+/** Internal version for use by other events.
+ */
+DisconnectAll::DisconnectAll(Engine& engine,
+ GraphImpl* parent,
+ Node* object)
+ : Event(engine)
+ , _msg{0, parent->path(), object->path()}
+ , _parent(parent)
+ , _block(dynamic_cast<BlockImpl*>(object))
+ , _port(dynamic_cast<PortImpl*>(object))
+ , _deleting(true)
+{
+}
+
+DisconnectAll::~DisconnectAll()
+{
+ for (auto& i : _impls) {
+ delete i;
+ }
+}
+
+bool
+DisconnectAll::pre_process(PreProcessContext& ctx)
+{
+ std::unique_lock<Store::Mutex> lock(_engine.store()->mutex(),
+ std::defer_lock);
+
+ if (!_deleting) {
+ lock.lock();
+
+ _parent = dynamic_cast<GraphImpl*>(_engine.store()->get(_msg.graph));
+ if (!_parent) {
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND,
+ _msg.graph);
+ }
+
+ NodeImpl* const object = dynamic_cast<NodeImpl*>(
+ _engine.store()->get(_msg.path));
+ if (!object) {
+ return Event::pre_process_done(Status::NOT_FOUND, _msg.path);
+ }
+
+ if (object->parent_graph() != _parent
+ && object->parent()->parent_graph() != _parent) {
+ return Event::pre_process_done(Status::INVALID_PARENT, _msg.graph);
+ }
+
+ // Only one of these will succeed
+ _block = dynamic_cast<BlockImpl*>(object);
+ _port = dynamic_cast<PortImpl*>(object);
+
+ if (!_block && !_port) {
+ return Event::pre_process_done(Status::INTERNAL_ERROR, _msg.path);
+ }
+ }
+
+ // Find set of arcs to remove
+ std::set<ArcImpl*> to_remove;
+ for (const auto& a : _parent->arcs()) {
+ ArcImpl* const arc = (ArcImpl*)a.second.get();
+ if (_block) {
+ if (arc->tail()->parent_block() == _block
+ || arc->head()->parent_block() == _block) {
+ to_remove.insert(arc);
+ }
+ } else if (_port) {
+ if (arc->tail() == _port || arc->head() == _port) {
+ to_remove.insert(arc);
+ }
+ }
+ }
+
+ // Create disconnect events (which erases from _parent->arcs())
+ for (const auto& a : to_remove) {
+ _impls.push_back(new Disconnect::Impl(
+ _engine, _parent,
+ dynamic_cast<PortImpl*>(a->tail()),
+ dynamic_cast<InputPort*>(a->head())));
+ }
+
+ if (!_deleting && ctx.must_compile(*_parent)) {
+ if (!(_compiled_graph = compile(*_engine.maid(), *_parent))) {
+ return Event::pre_process_done(Status::COMPILATION_FAILED);
+ }
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+DisconnectAll::execute(RunContext& context)
+{
+ if (_status == Status::SUCCESS) {
+ for (auto& i : _impls) {
+ i->execute(context,
+ !_deleting || (i->head()->parent_block() != _block));
+ }
+ }
+
+ if (_compiled_graph) {
+ _parent->set_compiled_graph(std::move(_compiled_graph));
+ }
+}
+
+void
+DisconnectAll::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->message(_msg);
+ }
+}
+
+void
+DisconnectAll::undo(Interface& target)
+{
+ for (auto& i : _impls) {
+ target.connect(i->tail()->path(), i->head()->path());
+ }
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/DisconnectAll.hpp b/src/server/events/DisconnectAll.hpp
new file mode 100644
index 00000000..aca5fbce
--- /dev/null
+++ b/src/server/events/DisconnectAll.hpp
@@ -0,0 +1,78 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_DISCONNECTALL_HPP
+#define INGEN_EVENTS_DISCONNECTALL_HPP
+
+#include <list>
+
+#include "raul/Path.hpp"
+
+#include "CompiledGraph.hpp"
+#include "Disconnect.hpp"
+#include "Event.hpp"
+
+namespace ingen {
+namespace server {
+
+class BlockImpl;
+class GraphImpl;
+class PortImpl;
+
+namespace events {
+
+class Disconnect;
+
+/** An event to disconnect all connections to a Block.
+ *
+ * \ingroup engine
+ */
+class DisconnectAll : public Event
+{
+public:
+ DisconnectAll(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::DisconnectAll& msg);
+
+ DisconnectAll(Engine& engine,
+ GraphImpl* parent,
+ Node* object);
+
+ ~DisconnectAll();
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ typedef std::list<Disconnect::Impl*> Impls;
+
+ const ingen::DisconnectAll _msg;
+ GraphImpl* _parent;
+ BlockImpl* _block;
+ PortImpl* _port;
+ Impls _impls;
+ MPtr<CompiledGraph> _compiled_graph;
+ bool _deleting;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_DISCONNECTALL_HPP
diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp
new file mode 100644
index 00000000..ad412beb
--- /dev/null
+++ b/src/server/events/Get.cpp
@@ -0,0 +1,111 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2017 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <utility>
+
+#include "ingen/Interface.hpp"
+#include "ingen/Node.hpp"
+#include "ingen/Store.hpp"
+
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "BufferFactory.hpp"
+#include "Engine.hpp"
+#include "Get.hpp"
+#include "GraphImpl.hpp"
+#include "PluginImpl.hpp"
+#include "PortImpl.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Get::Get(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Get& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+ , _object(nullptr)
+ , _plugin(nullptr)
+{}
+
+bool
+Get::pre_process(PreProcessContext& ctx)
+{
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ const auto& uri = _msg.subject;
+ if (uri == "ingen:/plugins") {
+ _plugins = _engine.block_factory()->plugins();
+ return Event::pre_process_done(Status::SUCCESS);
+ } else if (uri == "ingen:/engine") {
+ return Event::pre_process_done(Status::SUCCESS);
+ } else if (uri_is_path(uri)) {
+ if ((_object = _engine.store()->get(uri_to_path(uri)))) {
+ const BlockImpl* block = nullptr;
+ const GraphImpl* graph = nullptr;
+ const PortImpl* port = nullptr;
+ if ((graph = dynamic_cast<const GraphImpl*>(_object))) {
+ _response.put_graph(graph);
+ } else if ((block = dynamic_cast<const BlockImpl*>(_object))) {
+ _response.put_block(block);
+ } else if ((port = dynamic_cast<const PortImpl*>(_object))) {
+ _response.put_port(port);
+ } else {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, uri);
+ }
+ return Event::pre_process_done(Status::SUCCESS);
+ }
+ return Event::pre_process_done(Status::NOT_FOUND, uri);
+ } else if ((_plugin = _engine.block_factory()->plugin(uri))) {
+ _response.put_plugin(_plugin);
+ return Event::pre_process_done(Status::SUCCESS);
+ } else {
+ return Event::pre_process_done(Status::NOT_FOUND, uri);
+ }
+}
+
+void
+Get::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS && _request_client) {
+ if (_msg.subject == "ingen:/plugins") {
+ _engine.broadcaster()->send_plugins_to(_request_client.get(), _plugins);
+ } else if (_msg.subject == "ingen:/engine") {
+ // TODO: Keep a proper RDF model of the engine
+ URIs& uris = _engine.world()->uris();
+ Properties props = {
+ { uris.param_sampleRate,
+ uris.forge.make(int32_t(_engine.sample_rate())) },
+ { uris.bufsz_maxBlockLength,
+ uris.forge.make(int32_t(_engine.block_length())) },
+ { uris.ingen_numThreads,
+ uris.forge.make(int32_t(_engine.n_threads())) } };
+
+ const Properties load_props = _engine.load_properties();
+ props.insert(load_props.begin(), load_props.end());
+ _request_client->put(URI("ingen:/engine"), props);
+ } else {
+ _response.send(*_request_client);
+ }
+ }
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Get.hpp b/src/server/events/Get.hpp
new file mode 100644
index 00000000..9598010c
--- /dev/null
+++ b/src/server/events/Get.hpp
@@ -0,0 +1,65 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_GET_HPP
+#define INGEN_EVENTS_GET_HPP
+
+#include <vector>
+
+#include "BlockFactory.hpp"
+#include "ClientUpdate.hpp"
+#include "Event.hpp"
+#include "types.hpp"
+
+namespace ingen {
+namespace server {
+
+class BlockImpl;
+class GraphImpl;
+class PluginImpl;
+class PortImpl;
+
+namespace events {
+
+/** A request from a client to send an object.
+ *
+ * \ingroup engine
+ */
+class Get : public Event
+{
+public:
+ Get(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Get& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override {}
+ void post_process() override;
+
+private:
+ const ingen::Get _msg;
+ const Node* _object;
+ PluginImpl* _plugin;
+ BlockFactory::Plugins _plugins;
+ ClientUpdate _response;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_GET_HPP
diff --git a/src/server/events/Mark.cpp b/src/server/events/Mark.cpp
new file mode 100644
index 00000000..be943533
--- /dev/null
+++ b/src/server/events/Mark.cpp
@@ -0,0 +1,112 @@
+/*
+ This file is part of Ingen.
+ Copyright 2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "Engine.hpp"
+#include "PreProcessContext.hpp"
+#include "UndoStack.hpp"
+#include "events/Mark.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Mark::Mark(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::BundleBegin& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _type(Type::BUNDLE_BEGIN)
+ , _depth(0)
+{}
+
+Mark::Mark(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::BundleEnd& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _type(Type::BUNDLE_END)
+ , _depth(0)
+{}
+
+bool
+Mark::pre_process(PreProcessContext& ctx)
+{
+ const UPtr<UndoStack>& stack = ((_mode == Mode::UNDO)
+ ? _engine.redo_stack()
+ : _engine.undo_stack());
+
+ switch (_type) {
+ case Type::BUNDLE_BEGIN:
+ ctx.set_in_bundle(true);
+ _depth = stack->start_entry();
+ break;
+ case Type::BUNDLE_END:
+ _depth = stack->finish_entry();
+ ctx.set_in_bundle(false);
+ if (!ctx.dirty_graphs().empty()) {
+ for (GraphImpl* g : ctx.dirty_graphs()) {
+ MPtr<CompiledGraph> cg = compile(*_engine.maid(), *g);
+ if (cg) {
+ _compiled_graphs.emplace(g, std::move(cg));
+ }
+ }
+ ctx.dirty_graphs().clear();
+ }
+ break;
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Mark::execute(RunContext& context)
+{
+ for (auto& g : _compiled_graphs) {
+ g.first->set_compiled_graph(std::move(g.second));
+ }
+}
+
+void
+Mark::post_process()
+{
+ respond();
+}
+
+Event::Execution
+Mark::get_execution() const
+{
+ if (!_engine.atomic_bundles()) {
+ return Execution::NORMAL;
+ }
+
+ switch (_type) {
+ case Type::BUNDLE_BEGIN:
+ if (_depth == 1) {
+ return Execution::BLOCK;
+ }
+ break;
+ case Type::BUNDLE_END:
+ if (_depth == 0) {
+ return Execution::UNBLOCK;
+ }
+ break;
+ }
+ return Execution::NORMAL;
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Mark.hpp b/src/server/events/Mark.hpp
new file mode 100644
index 00000000..076b67dd
--- /dev/null
+++ b/src/server/events/Mark.hpp
@@ -0,0 +1,69 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_MARK_HPP
+#define INGEN_EVENTS_MARK_HPP
+
+#include "Event.hpp"
+
+namespace ingen {
+namespace server {
+
+class Engine;
+
+namespace events {
+
+/** Delineate the start or end of a bundle of events.
+ *
+ * This is used to mark the ends of an undo transaction, so a single undo can
+ * undo the effects of many events (such as a paste or a graph load).
+ *
+ * \ingroup engine
+ */
+class Mark : public Event
+{
+public:
+ Mark(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::BundleBegin& msg);
+
+ Mark(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::BundleEnd& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+
+ Execution get_execution() const override;
+
+private:
+ enum class Type { BUNDLE_BEGIN, BUNDLE_END };
+
+ typedef std::map<GraphImpl*, MPtr<CompiledGraph>> CompiledGraphs;
+
+ CompiledGraphs _compiled_graphs;
+ Type _type;
+ int _depth;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_MARK_HPP
diff --git a/src/server/events/Move.cpp b/src/server/events/Move.cpp
new file mode 100644
index 00000000..1890dc5f
--- /dev/null
+++ b/src/server/events/Move.cpp
@@ -0,0 +1,91 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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/Move.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Move::Move(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Move& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _msg(msg)
+{
+}
+
+bool
+Move::pre_process(PreProcessContext& ctx)
+{
+ std::lock_guard<Store::Mutex> lock(_engine.store()->mutex());
+
+ if (!_msg.old_path.parent().is_parent_of(_msg.new_path)) {
+ return Event::pre_process_done(Status::PARENT_DIFFERS, _msg.new_path);
+ }
+
+ const Store::iterator i = _engine.store()->find(_msg.old_path);
+ if (i == _engine.store()->end()) {
+ return Event::pre_process_done(Status::NOT_FOUND, _msg.old_path);
+ }
+
+ if (_engine.store()->find(_msg.new_path) != _engine.store()->end()) {
+ return Event::pre_process_done(Status::EXISTS, _msg.new_path);
+ }
+
+ EnginePort* eport = _engine.driver()->get_port(_msg.old_path);
+ if (eport) {
+ _engine.driver()->rename_port(_msg.old_path, _msg.new_path);
+ }
+
+ _engine.store()->rename(i, _msg.new_path);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Move::execute(RunContext& context)
+{
+}
+
+void
+Move::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->message(_msg);
+ }
+}
+
+void
+Move::undo(Interface& target)
+{
+ target.move(_msg.new_path, _msg.old_path);
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Move.hpp b/src/server/events/Move.hpp
new file mode 100644
index 00000000..510fcfce
--- /dev/null
+++ b/src/server/events/Move.hpp
@@ -0,0 +1,57 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_MOVE_HPP
+#define INGEN_EVENTS_MOVE_HPP
+
+#include "ingen/Store.hpp"
+#include "raul/Path.hpp"
+
+#include "Event.hpp"
+
+namespace ingen {
+namespace server {
+
+class GraphImpl;
+class PortImpl;
+
+namespace events {
+
+/** Move a graph object to a new path.
+ * \ingroup engine
+ */
+class Move : public Event
+{
+public:
+ Move(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Move& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+ void undo(Interface& target) override;
+
+private:
+ const ingen::Move _msg;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_MOVE_HPP
diff --git a/src/server/events/SetPortValue.cpp b/src/server/events/SetPortValue.cpp
new file mode 100644
index 00000000..fa36d739
--- /dev/null
+++ b/src/server/events/SetPortValue.cpp
@@ -0,0 +1,139 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/LV2Features.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URIs.hpp"
+#include "ingen/World.hpp"
+
+#include "BlockImpl.hpp"
+#include "Broadcaster.hpp"
+#include "Buffer.hpp"
+#include "ControlBindings.hpp"
+#include "Engine.hpp"
+#include "PortImpl.hpp"
+#include "RunContext.hpp"
+#include "SetPortValue.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+/** Internal */
+SetPortValue::SetPortValue(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ PortImpl* port,
+ const Atom& value,
+ bool activity,
+ bool synthetic)
+ : Event(engine, client, id, timestamp)
+ , _port(port)
+ , _value(value)
+ , _activity(activity)
+ , _synthetic(synthetic)
+{
+}
+
+bool
+SetPortValue::pre_process(PreProcessContext& ctx)
+{
+ ingen::URIs& uris = _engine.world()->uris();
+ if (_port->is_output()) {
+ return Event::pre_process_done(Status::DIRECTION_MISMATCH, _port->path());
+ }
+
+ if (!_activity) {
+ // Set value metadata (does not affect buffers)
+ _port->set_value(_value);
+ _port->set_property(_engine.world()->uris().ingen_value, _value);
+ }
+
+ _binding = _engine.control_bindings()->port_binding(_port);
+
+ if (_port->buffer_type() == uris.atom_Sequence) {
+ _buffer = _engine.buffer_factory()->get_buffer(
+ _port->buffer_type(),
+ _value.type() == uris.atom_Float ? _value.type() : 0,
+ _engine.buffer_factory()->default_size(_port->buffer_type()));
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+SetPortValue::execute(RunContext& context)
+{
+ assert(_time >= context.start() && _time <= context.end());
+ apply(context);
+ _engine.control_bindings()->port_value_changed(context, _port, _binding, _value);
+}
+
+void
+SetPortValue::apply(RunContext& context)
+{
+ if (_status != Status::SUCCESS) {
+ return;
+ }
+
+ ingen::URIs& uris = _engine.world()->uris();
+ Buffer* buf = _port->buffer(0).get();
+
+ if (_buffer) {
+ if (_port->user_buffer(context)) {
+ buf = _port->user_buffer(context).get();
+ } else {
+ _port->set_user_buffer(context, _buffer);
+ buf = _buffer.get();
+ }
+ }
+
+ if (buf->type() == uris.atom_Sound || buf->type() == uris.atom_Float) {
+ if (_value.type() == uris.forge.Float) {
+ _port->set_control_value(context, _time, _value.get<float>());
+ } else {
+ _status = Status::TYPE_MISMATCH;
+ }
+ } else if (buf->type() == uris.atom_Sequence) {
+ if (!buf->append_event(_time - context.start(),
+ _value.size(),
+ _value.type(),
+ (const uint8_t*)_value.get_body())) {
+ _status = Status::NO_SPACE;
+ }
+ } else if (buf->type() == uris.atom_URID) {
+ buf->get<LV2_Atom_URID>()->body = _value.get<int32_t>();
+ } else {
+ _status = Status::BAD_VALUE_TYPE;
+ }
+}
+
+void
+SetPortValue::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS && !_activity) {
+ _engine.broadcaster()->set_property(
+ _port->uri(),
+ _engine.world()->uris().ingen_value,
+ _value);
+ }
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/SetPortValue.hpp b/src/server/events/SetPortValue.hpp
new file mode 100644
index 00000000..4fe42659
--- /dev/null
+++ b/src/server/events/SetPortValue.hpp
@@ -0,0 +1,71 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_SETPORTVALUE_HPP
+#define INGEN_EVENTS_SETPORTVALUE_HPP
+
+#include "ingen/Atom.hpp"
+
+#include "BufferRef.hpp"
+#include "ControlBindings.hpp"
+#include "Event.hpp"
+#include "types.hpp"
+
+namespace ingen {
+namespace server {
+
+class PortImpl;
+
+namespace events {
+
+/** An event to change the value of a port.
+ *
+ * \ingroup engine
+ */
+class SetPortValue : public Event
+{
+public:
+ SetPortValue(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ PortImpl* port,
+ const Atom& value,
+ bool activity,
+ bool synthetic = false);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+
+ bool synthetic() const { return _synthetic; }
+
+private:
+ void apply(RunContext& context);
+
+ PortImpl* _port;
+ const Atom _value;
+ BufferRef _buffer;
+ ControlBindings::Key _binding;
+ bool _activity;
+ bool _synthetic;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_SETPORTVALUE_HPP
diff --git a/src/server/events/Undo.cpp b/src/server/events/Undo.cpp
new file mode 100644
index 00000000..0c307e85
--- /dev/null
+++ b/src/server/events/Undo.cpp
@@ -0,0 +1,85 @@
+/*
+ This file is part of Ingen.
+ Copyright 2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/AtomReader.hpp"
+
+#include "Engine.hpp"
+#include "EventWriter.hpp"
+#include "Undo.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+Undo::Undo(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Undo& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _is_redo(false)
+{}
+
+Undo::Undo(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Redo& msg)
+ : Event(engine, client, msg.seq, timestamp)
+ , _is_redo(true)
+{}
+
+bool
+Undo::pre_process(PreProcessContext& ctx)
+{
+ const UPtr<UndoStack>& stack = _is_redo ? _engine.redo_stack() : _engine.undo_stack();
+ const Event::Mode mode = _is_redo ? Event::Mode::REDO : Event::Mode::UNDO;
+
+ if (stack->empty()) {
+ return Event::pre_process_done(Status::NOT_FOUND);
+ }
+
+ const Event::Mode orig_mode = _engine.event_writer()->get_event_mode();
+ _entry = stack->pop();
+ _engine.event_writer()->set_event_mode(mode);
+ if (_entry.events.size() > 1) {
+ _engine.interface()->bundle_begin();
+ }
+
+ for (const LV2_Atom* ev : _entry.events) {
+ _engine.atom_interface()->write(ev);
+ }
+
+ if (_entry.events.size() > 1) {
+ _engine.interface()->bundle_end();
+ }
+ _engine.event_writer()->set_event_mode(orig_mode);
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Undo::execute(RunContext& context)
+{
+}
+
+void
+Undo::post_process()
+{
+ respond();
+}
+
+} // namespace events
+} // namespace server
+} // namespace ingen
diff --git a/src/server/events/Undo.hpp b/src/server/events/Undo.hpp
new file mode 100644
index 00000000..f8469960
--- /dev/null
+++ b/src/server/events/Undo.hpp
@@ -0,0 +1,58 @@
+/*
+ This file is part of Ingen.
+ Copyright 2016 David Robillard <http://drobilla.net/>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INGEN_EVENTS_UNDO_HPP
+#define INGEN_EVENTS_UNDO_HPP
+
+#include "Event.hpp"
+#include "UndoStack.hpp"
+#include "types.hpp"
+
+namespace ingen {
+namespace server {
+namespace events {
+
+/** A request to undo the last change to the engine.
+ *
+ * \ingroup engine
+ */
+class Undo : public Event
+{
+public:
+ Undo(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Undo& msg);
+
+ Undo(Engine& engine,
+ SPtr<Interface> client,
+ SampleCount timestamp,
+ const ingen::Redo& msg);
+
+ bool pre_process(PreProcessContext& ctx) override;
+ void execute(RunContext& context) override;
+ void post_process() override;
+
+private:
+ UndoStack::Entry _entry;
+ bool _is_redo;
+};
+
+} // namespace events
+} // namespace server
+} // namespace ingen
+
+#endif // INGEN_EVENTS_UNDO_HPP