summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2015-02-08 07:02:59 +0000
committerDavid Robillard <d@drobilla.net>2015-02-08 07:02:59 +0000
commit0f9c8151d5b42b243a499bb31a1e1f0b2e8c5f6f (patch)
tree1ed4df4df4c3f160120544d92c681f1b4519e1aa
parent8733afb7ae9a04f46ac6318667182da16eca9fe5 (diff)
downloadingen-0f9c8151d5b42b243a499bb31a1e1f0b2e8c5f6f.tar.gz
ingen-0f9c8151d5b42b243a499bb31a1e1f0b2e8c5f6f.tar.bz2
ingen-0f9c8151d5b42b243a499bb31a1e1f0b2e8c5f6f.zip
Server-side copy paste with LV2 state support.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@5541 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--ingen/AtomWriter.hpp3
-rw-r--r--ingen/ClashAvoider.hpp75
-rw-r--r--ingen/Interface.hpp6
-rw-r--r--ingen/Node.hpp7
-rw-r--r--ingen/Status.hpp8
-rw-r--r--ingen/Store.hpp2
-rw-r--r--ingen/URIs.hpp1
-rw-r--r--ingen/client/ClientStore.hpp5
-rw-r--r--ingen/client/SigClientInterface.hpp4
-rw-r--r--ingen/client/ThreadedSigClientInterface.hpp5
-rw-r--r--ingen/serialisation/Parser.hpp10
-rw-r--r--ingen/serialisation/Serialiser.hpp38
-rw-r--r--src/AtomWriter.cpp14
-rw-r--r--src/ClashAvoider.cpp71
-rw-r--r--src/Store.cpp2
-rw-r--r--src/URIs.cpp1
-rw-r--r--src/client/BlockModel.cpp2
-rw-r--r--src/client/ClientStore.cpp24
-rw-r--r--src/gui/App.cpp2
-rw-r--r--src/gui/ConnectWindow.cpp2
-rw-r--r--src/gui/GraphCanvas.cpp142
-rw-r--r--src/gui/ThreadedLoader.cpp9
-rw-r--r--src/serialisation/Parser.cpp156
-rw-r--r--src/serialisation/Serialiser.cpp39
-rw-r--r--src/server/BlockImpl.hpp6
-rw-r--r--src/server/Broadcaster.hpp5
-rw-r--r--src/server/DuplexPort.cpp19
-rw-r--r--src/server/DuplexPort.hpp4
-rw-r--r--src/server/EventWriter.cpp9
-rw-r--r--src/server/EventWriter.hpp3
-rw-r--r--src/server/GraphImpl.cpp63
-rw-r--r--src/server/GraphImpl.hpp4
-rw-r--r--src/server/LV2Block.cpp41
-rw-r--r--src/server/LV2Block.hpp4
-rw-r--r--src/server/PortImpl.cpp2
-rw-r--r--src/server/events.hpp1
-rw-r--r--src/server/events/Copy.cpp130
-rw-r--r--src/server/events/Copy.hpp73
-rw-r--r--src/server/events/CreateBlock.cpp93
-rw-r--r--src/server/events/CreateBlock.hpp19
-rw-r--r--src/server/events/CreateGraph.cpp47
-rw-r--r--src/server/events/CreateGraph.hpp16
-rw-r--r--src/server/events/CreatePort.cpp30
-rw-r--r--src/server/events/Delta.cpp67
-rw-r--r--src/server/events/DisconnectAll.cpp3
-rw-r--r--src/server/events/Get.cpp20
-rw-r--r--src/server/events/Get.hpp15
-rw-r--r--src/server/ingen_lv2.cpp4
-rw-r--r--src/server/wscript1
-rw-r--r--wscript2
50 files changed, 848 insertions, 461 deletions
diff --git a/ingen/AtomWriter.hpp b/ingen/AtomWriter.hpp
index c7ac3cc6..4f10cd45 100644
--- a/ingen/AtomWriter.hpp
+++ b/ingen/AtomWriter.hpp
@@ -52,6 +52,9 @@ public:
const Resource::Properties& remove,
const Resource::Properties& add);
+ void copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri);
+
void move(const Raul::Path& old_path,
const Raul::Path& new_path);
diff --git a/ingen/ClashAvoider.hpp b/ingen/ClashAvoider.hpp
index 98d508d4..c07273f0 100644
--- a/ingen/ClashAvoider.hpp
+++ b/ingen/ClashAvoider.hpp
@@ -20,86 +20,35 @@
#include <inttypes.h>
#include <map>
-#include <string>
-#include "ingen/Interface.hpp"
-
-namespace Raul {
-class Atom;
-class Path;
-}
+#include "raul/Path.hpp"
+#include "raul/URI.hpp"
namespace Ingen {
class Store;
-/** A wrapper for an Interface that creates objects but possibly maps
- * symbol names to avoid clashes with the existing objects in a store.
+/** Maps paths so they do not clash with an existing object in a store.
*
- * @ingroup IngenShared
+ * @ingroup ingen
*/
-class ClashAvoider : public Interface
+class ClashAvoider
{
public:
- ClashAvoider(Store& store, Interface& target, Store* also_avoid=NULL)
- : _store(store), _target(target), _also_avoid(also_avoid) {}
-
- Raul::URI uri() const { return Raul::URI("ingen:/clients/clash_avoider"); }
-
- void set_target(Interface& target) { _target = target; }
-
- // Bundles
- void bundle_begin() { _target.bundle_begin(); }
- void bundle_end() { _target.bundle_end(); }
-
- // Object commands
-
- virtual void put(const Raul::URI& path,
- const Resource::Properties& properties,
- Resource::Graph ctx=Resource::Graph::DEFAULT);
-
- virtual void delta(const Raul::URI& path,
- const Resource::Properties& remove,
- const Resource::Properties& add);
-
- virtual void move(const Raul::Path& old_path,
- const Raul::Path& new_path);
-
- virtual void connect(const Raul::Path& tail,
- const Raul::Path& head);
-
- virtual void disconnect(const Raul::Path& tail,
- const Raul::Path& head);
+ ClashAvoider(const Store& store);
- virtual void disconnect_all(const Raul::Path& graph,
- const Raul::Path& path);
-
- virtual void set_property(const Raul::URI& subject_path,
- const Raul::URI& predicate,
- const Atom& value);
-
- virtual void del(const Raul::URI& uri);
-
- virtual void set_response_id(int32_t id) {}
- virtual void get(const Raul::URI& uri) {}
- virtual void response(int32_t id, Status status, const std::string& subject) {}
- virtual void error(const std::string& msg) {}
-
-private:
const Raul::URI map_uri(const Raul::URI& in);
const Raul::Path map_path(const Raul::Path& in);
- Store& _store;
- Interface& _target;
-
- Store* _also_avoid;
bool exists(const Raul::Path& path) const;
- typedef std::map<Raul::Path, unsigned> Offsets;
- Offsets _offsets;
-
+private:
+ typedef std::map<Raul::Path, unsigned> Offsets;
typedef std::map<Raul::Path, Raul::Path> SymbolMap;
- SymbolMap _symbol_map;
+
+ const Store& _store;
+ Offsets _offsets;
+ SymbolMap _symbol_map;
};
} // namespace Ingen
diff --git a/ingen/Interface.hpp b/ingen/Interface.hpp
index d5fe0b97..042bd3f7 100644
--- a/ingen/Interface.hpp
+++ b/ingen/Interface.hpp
@@ -21,6 +21,7 @@
#ifndef INGEN_INTERFACE_HPP
#define INGEN_INTERFACE_HPP
+#include <list>
#include <string>
#include "ingen/Resource.hpp"
@@ -67,6 +68,9 @@ public:
const Resource::Properties& remove,
const Resource::Properties& add) = 0;
+ virtual void copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri) = 0;
+
virtual void move(const Raul::Path& old_path,
const Raul::Path& new_path) = 0;
@@ -86,7 +90,7 @@ public:
const Atom& value) = 0;
/** Set the ID to use to respond to the next message.
- * Setting the ID to -1 will disable responses.
+ * Setting the ID to 0 will disable responses.
*/
virtual void set_response_id(int32_t id) = 0;
diff --git a/ingen/Node.hpp b/ingen/Node.hpp
index 2cbce01c..293ac058 100644
--- a/ingen/Node.hpp
+++ b/ingen/Node.hpp
@@ -72,6 +72,13 @@ public:
virtual const Raul::Symbol& symbol() const = 0;
virtual Node* graph_parent() const = 0;
+ Raul::URI base_uri() const {
+ if (uri()[uri().size() - 1] == '/') {
+ return uri();
+ }
+ return Raul::URI(uri() + '/');
+ }
+
static Raul::URI root_uri() { return Raul::URI("ingen:/root"); }
static bool uri_is_path(const Raul::URI& uri) {
diff --git a/ingen/Status.hpp b/ingen/Status.hpp
index b754702a..ed388ccf 100644
--- a/ingen/Status.hpp
+++ b/ingen/Status.hpp
@@ -33,7 +33,7 @@ enum class Status {
DIRECTION_MISMATCH,
EXISTS,
INTERNAL_ERROR,
- INVALID_PARENT_PATH,
+ INVALID_PARENT,
INVALID_POLY,
NOT_DELETABLE,
NOT_FOUND,
@@ -42,7 +42,7 @@ enum class Status {
NO_SPACE,
PARENT_DIFFERS,
PARENT_NOT_FOUND,
- PLUGIN_NOT_FOUND,
+ PROTOTYPE_NOT_FOUND,
PORT_NOT_FOUND,
TYPE_MISMATCH,
UNKNOWN_TYPE
@@ -65,7 +65,7 @@ ingen_status_string(Status st)
case Status::DIRECTION_MISMATCH: return "Direction mismatch";
case Status::EXISTS: return "Object exists";
case Status::INTERNAL_ERROR: return "Internal error";
- case Status::INVALID_PARENT_PATH: return "Invalid parent path";
+ case Status::INVALID_PARENT: return "Invalid parent";
case Status::INVALID_POLY: return "Invalid polyphony";
case Status::NOT_DELETABLE: return "Object not deletable";
case Status::NOT_FOUND: return "Object not found";
@@ -74,7 +74,7 @@ ingen_status_string(Status st)
case Status::NO_SPACE: return "Insufficient space";
case Status::PARENT_DIFFERS: return "Parent differs";
case Status::PARENT_NOT_FOUND: return "Parent not found";
- case Status::PLUGIN_NOT_FOUND: return "Plugin not found";
+ case Status::PROTOTYPE_NOT_FOUND: return "Prototype not found";
case Status::PORT_NOT_FOUND: return "Port not found";
case Status::TYPE_MISMATCH: return "Type mismatch";
case Status::UNKNOWN_TYPE: return "Unknown type";
diff --git a/ingen/Store.hpp b/ingen/Store.hpp
index 4856ff4e..77af3d66 100644
--- a/ingen/Store.hpp
+++ b/ingen/Store.hpp
@@ -68,7 +68,7 @@ public:
unsigned child_name_offset(const Raul::Path& parent,
const Raul::Symbol& symbol,
- bool allow_zero=true);
+ bool allow_zero=true) const;
std::mutex& mutex() { return _mutex; }
diff --git a/ingen/URIs.hpp b/ingen/URIs.hpp
index 4c31ccdc..6cb7ea15 100644
--- a/ingen/URIs.hpp
+++ b/ingen/URIs.hpp
@@ -130,6 +130,7 @@ public:
const Quark midi_noteNumber;
const Quark morph_currentType;
const Quark param_sampleRate;
+ const Quark patch_Copy;
const Quark patch_Delete;
const Quark patch_Get;
const Quark patch_Move;
diff --git a/ingen/client/ClientStore.hpp b/ingen/client/ClientStore.hpp
index f53217b4..1871fca2 100644
--- a/ingen/client/ClientStore.hpp
+++ b/ingen/client/ClientStore.hpp
@@ -55,7 +55,6 @@ public:
ClientStore(
URIs& uris,
Log& log,
- SPtr<Interface> engine = SPtr<Interface>(),
SPtr<SigClientInterface> emitter = SPtr<SigClientInterface>());
Raul::URI uri() const { return Raul::URI("ingen:/clients/store"); }
@@ -81,6 +80,9 @@ public:
const Resource::Properties& remove,
const Resource::Properties& add);
+ void copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri);
+
void move(const Raul::Path& old_path,
const Raul::Path& new_path);
@@ -129,7 +131,6 @@ private:
URIs& _uris;
Log& _log;
- SPtr<Interface> _engine;
SPtr<SigClientInterface> _emitter;
SPtr<Plugins> _plugins; ///< Map, keyed by plugin URI
diff --git a/ingen/client/SigClientInterface.hpp b/ingen/client/SigClientInterface.hpp
index 07e6e333..39fff895 100644
--- a/ingen/client/SigClientInterface.hpp
+++ b/ingen/client/SigClientInterface.hpp
@@ -53,6 +53,7 @@ public:
INGEN_SIGNAL(error, void, std::string)
INGEN_SIGNAL(put, void, Raul::URI, Resource::Properties, Resource::Graph)
INGEN_SIGNAL(delta, void, Raul::URI, Resource::Properties, Resource::Properties)
+ INGEN_SIGNAL(object_copied, void, Raul::Path, Raul::URI)
INGEN_SIGNAL(object_moved, void, Raul::Path, Raul::Path)
INGEN_SIGNAL(object_deleted, void, Raul::URI)
INGEN_SIGNAL(connection, void, Raul::Path, Raul::Path)
@@ -97,6 +98,9 @@ protected:
void del(const Raul::URI& uri)
{ EMIT(object_deleted, uri); }
+ void copy(const Raul::Path& old_path, const Raul::URI& new_uri)
+ { EMIT(object_copied, old_path, new_uri); }
+
void move(const Raul::Path& old_path, const Raul::Path& new_path)
{ EMIT(object_moved, old_path, new_path); }
diff --git a/ingen/client/ThreadedSigClientInterface.hpp b/ingen/client/ThreadedSigClientInterface.hpp
index ab9a7ee9..0d5d5e57 100644
--- a/ingen/client/ThreadedSigClientInterface.hpp
+++ b/ingen/client/ThreadedSigClientInterface.hpp
@@ -59,6 +59,7 @@ public:
, connection_slot(_signal_connection.make_slot())
, object_deleted_slot(_signal_object_deleted.make_slot())
, object_moved_slot(_signal_object_moved.make_slot())
+ , object_copied_slot(_signal_object_copied.make_slot())
, disconnection_slot(_signal_disconnection.make_slot())
, disconnect_all_slot(_signal_disconnect_all.make_slot())
, property_change_slot(_signal_property_change.make_slot())
@@ -97,6 +98,9 @@ public:
void move(const Raul::Path& old_path, const Raul::Path& new_path)
{ push_sig(sigc::bind(object_moved_slot, old_path, new_path)); }
+ void copy(const Raul::Path& old_path, const Raul::URI& new_uri)
+ { push_sig(sigc::bind(object_copied_slot, old_path, new_uri)); }
+
void disconnect(const Raul::Path& tail, const Raul::Path& head)
{ push_sig(sigc::bind(disconnection_slot, tail, head)); }
@@ -157,6 +161,7 @@ private:
sigc::slot<void, Raul::Path, Raul::Path> connection_slot;
sigc::slot<void, Raul::URI> object_deleted_slot;
sigc::slot<void, Raul::Path, Raul::Path> object_moved_slot;
+ sigc::slot<void, Raul::Path, Raul::URI> object_copied_slot;
sigc::slot<void, Raul::Path, Raul::Path> disconnection_slot;
sigc::slot<void, Raul::Path, Raul::Path> disconnect_all_slot;
sigc::slot<void, Raul::URI, Raul::URI, Atom> property_change_slot;
diff --git a/ingen/serialisation/Parser.hpp b/ingen/serialisation/Parser.hpp
index 6ece6965..446b3988 100644
--- a/ingen/serialisation/Parser.hpp
+++ b/ingen/serialisation/Parser.hpp
@@ -25,10 +25,10 @@
#include <list>
#include <boost/optional.hpp>
-#include <glibmm/ustring.h>
#include "ingen/Node.hpp"
#include "raul/Path.hpp"
+#include "raul/URI.hpp"
namespace Ingen {
@@ -53,16 +53,16 @@ public:
virtual bool parse_file(
World* world,
Interface* target,
- Glib::ustring path,
+ const std::string& path,
boost::optional<Raul::Path> parent = boost::optional<Raul::Path>(),
boost::optional<Raul::Symbol> symbol = boost::optional<Raul::Symbol>(),
boost::optional<Properties> data = boost::optional<Properties>());
- virtual bool parse_string(
+ virtual boost::optional<Raul::URI> parse_string(
World* world,
Interface* target,
- const Glib::ustring& str,
- const Glib::ustring& base_uri,
+ const std::string& str,
+ const std::string& base_uri,
boost::optional<Raul::Path> parent = boost::optional<Raul::Path>(),
boost::optional<Raul::Symbol> symbol = boost::optional<Raul::Symbol>(),
boost::optional<Properties> data = boost::optional<Properties>());
diff --git a/ingen/serialisation/Serialiser.hpp b/ingen/serialisation/Serialiser.hpp
index d0b65893..80a01b89 100644
--- a/ingen/serialisation/Serialiser.hpp
+++ b/ingen/serialisation/Serialiser.hpp
@@ -46,27 +46,49 @@ public:
explicit Serialiser(World& world);
virtual ~Serialiser();
- typedef Node::Properties Properties;
-
- virtual void to_file(SPtr<const Node> object,
- const std::string& filename);
-
+ /** Write a graph and all its contents as a complete bundle. */
virtual void write_bundle(SPtr<const Node> graph,
const std::string& path);
- virtual std::string to_string(SPtr<const Node> object,
- const std::string& base_uri);
-
+ /** Begin a serialization to a string.
+ *
+ * This must be called before any serializing methods.
+ *
+ * The results of the serialization will be returned by the finish() method after
+ * the desired objects have been serialised.
+ *
+ * All serialized paths will have the root path chopped from their prefix
+ * (therefore all serialized paths must be descendants of the root)
+ */
virtual void start_to_string(const Raul::Path& root,
const std::string& base_uri);
+ /** Begin a serialization to a file.
+ *
+ * This must be called before any serializing methods.
+ *
+ * All serialized paths will have the root path chopped from their prefix
+ * (therefore all serialized paths must be descendants of the root)
+ */
+ virtual void start_to_file(const Raul::Path& root,
+ const std::string& filename);
+
+ /** Serialize an object (graph, block, or port). */
virtual void serialise(SPtr<const Node> object)
throw (std::logic_error);
+ /** Serialize an arc. */
virtual void serialise_arc(const Sord::Node& parent,
SPtr<const Arc> arc)
throw (std::logic_error);
+ /** Finish serialization.
+ *
+ * If this is a file serialization, this must be called to finish and close
+ * the output file, and the empty string is returned.
+ *
+ * If this is a string serialization, the serialized result is returned.
+ */
virtual std::string finish();
private:
diff --git a/src/AtomWriter.cpp b/src/AtomWriter.cpp
index 7c956ece..eaf45243 100644
--- a/src/AtomWriter.cpp
+++ b/src/AtomWriter.cpp
@@ -168,6 +168,20 @@ AtomWriter::delta(const Raul::URI& uri,
}
void
+AtomWriter::copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri)
+{
+ LV2_Atom_Forge_Frame msg;
+ forge_request(&msg, _uris.patch_Copy);
+ lv2_atom_forge_key(&_forge, _uris.patch_subject);
+ forge_uri(Node::path_to_uri(old_path));
+ lv2_atom_forge_key(&_forge, _uris.patch_destination);
+ forge_uri(new_uri);
+ lv2_atom_forge_pop(&_forge, &msg);
+ finish_msg();
+}
+
+void
AtomWriter::move(const Raul::Path& old_path,
const Raul::Path& new_path)
{
diff --git a/src/ClashAvoider.cpp b/src/ClashAvoider.cpp
index 734b7d04..ba967291 100644
--- a/src/ClashAvoider.cpp
+++ b/src/ClashAvoider.cpp
@@ -26,6 +26,10 @@ using namespace std;
namespace Ingen {
+ClashAvoider::ClashAvoider(const Store& store)
+ : _store(store)
+{}
+
const Raul::URI
ClashAvoider::map_uri(const Raul::URI& in)
{
@@ -123,72 +127,7 @@ ClashAvoider::map_path(const Raul::Path& in)
bool
ClashAvoider::exists(const Raul::Path& path) const
{
- bool exists = (_store.find(path) != _store.end());
- if (exists)
- return true;
-
- if (_also_avoid)
- return (_also_avoid->find(path) != _also_avoid->end());
- else
- return false;
-}
-
-void
-ClashAvoider::put(const Raul::URI& path,
- const Resource::Properties& properties,
- Resource::Graph ctx)
-{
- _target.put(map_uri(path), properties, ctx);
-}
-
-void
-ClashAvoider::delta(const Raul::URI& path,
- const Resource::Properties& remove,
- const Resource::Properties& add)
-{
- _target.delta(map_uri(path), remove, add);
-}
-
-void
-ClashAvoider::move(const Raul::Path& old_path,
- const Raul::Path& new_path)
-{
- _target.move(map_path(old_path), map_path(new_path));
-}
-
-void
-ClashAvoider::connect(const Raul::Path& tail,
- const Raul::Path& head)
-{
- _target.connect(map_path(tail), map_path(head));
-}
-
-void
-ClashAvoider::disconnect(const Raul::Path& tail,
- const Raul::Path& head)
-{
- _target.disconnect(map_path(tail), map_path(head));
-}
-
-void
-ClashAvoider::disconnect_all(const Raul::Path& graph,
- const Raul::Path& path)
-{
- _target.disconnect_all(map_path(graph), map_path(path));
-}
-
-void
-ClashAvoider::set_property(const Raul::URI& subject,
- const Raul::URI& predicate,
- const Atom& value)
-{
- _target.set_property(map_uri(subject), predicate, value);
-}
-
-void
-ClashAvoider::del(const Raul::URI& uri)
-{
- _target.del(map_uri(uri));
+ return _store.find(path) != _store.end();
}
} // namespace Ingen
diff --git a/src/Store.cpp b/src/Store.cpp
index 560c2de9..f3eac729 100644
--- a/src/Store.cpp
+++ b/src/Store.cpp
@@ -118,7 +118,7 @@ Store::rename(const iterator top, const Raul::Path& new_path)
unsigned
Store::child_name_offset(const Raul::Path& parent,
const Raul::Symbol& symbol,
- bool allow_zero)
+ bool allow_zero) const
{
unsigned offset = 0;
diff --git a/src/URIs.cpp b/src/URIs.cpp
index 6d41482d..647c63b5 100644
--- a/src/URIs.cpp
+++ b/src/URIs.cpp
@@ -120,6 +120,7 @@ URIs::URIs(Forge& f, URIMap* map)
, midi_noteNumber (forge, map, LV2_MIDI__noteNumber)
, morph_currentType (forge, map, LV2_MORPH__currentType)
, param_sampleRate (forge, map, LV2_PARAMETERS__sampleRate)
+ , patch_Copy (forge, map, LV2_PATCH__Copy)
, patch_Delete (forge, map, LV2_PATCH__Delete)
, patch_Get (forge, map, LV2_PATCH__Get)
, patch_Move (forge, map, LV2_PATCH__Move)
diff --git a/src/client/BlockModel.cpp b/src/client/BlockModel.cpp
index f667cb3c..69aa9627 100644
--- a/src/client/BlockModel.cpp
+++ b/src/client/BlockModel.cpp
@@ -174,7 +174,7 @@ BlockModel::default_port_value_range(SPtr<const PortModel> port,
max = 1.0;
// Get range from client-side LV2 data
- if (_plugin && _plugin->type() == PluginModel::LV2) {
+ if (_plugin && _plugin->type() == PluginModel::LV2 && _plugin->lilv_plugin()) {
if (!_min_values) {
_num_values = lilv_plugin_get_num_ports(_plugin->lilv_plugin());
_min_values = new float[_num_values];
diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp
index 80f210fe..d6cd4248 100644
--- a/src/client/ClientStore.cpp
+++ b/src/client/ClientStore.cpp
@@ -33,11 +33,9 @@ namespace Client {
ClientStore::ClientStore(URIs& uris,
Log& log,
- SPtr<Interface> engine,
SPtr<SigClientInterface> emitter)
: _uris(uris)
, _log(log)
- , _engine(engine)
, _emitter(emitter)
, _plugins(new Plugins())
{
@@ -84,12 +82,13 @@ ClientStore::add_object(SPtr<ObjectModel> object)
(*this)[object->path()] = object;
_signal_new_object.emit(object);
+ } else {
+ _log.error(fmt("Object %1% with no parent\n") % object->path());
}
} else {
(*this)[object->path()] = object;
_signal_new_object.emit(object);
}
-
}
for (auto p : object->properties())
@@ -209,6 +208,13 @@ ClientStore::del(const Raul::URI& uri)
}
void
+ClientStore::copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri)
+{
+ _log.error("Client store copy unsupported\n");
+}
+
+void
ClientStore::move(const Raul::Path& old_path, const Raul::Path& new_path)
{
const iterator top = find(old_path);
@@ -291,7 +297,7 @@ ClientStore::put(const Raul::URI& uri,
bm->set_properties(properties);
add_object(bm);
} else {
- _log.warn(fmt("Block %1% has no plugin\n")
+ _log.warn(fmt("Block %1% has no prototype\n")
% path.c_str());
}
} else if (is_port) {
@@ -302,8 +308,6 @@ ClientStore::put(const Raul::URI& uri,
const Iterator i = properties.find(_uris.lv2_index);
if (i != properties.end() && i->second.type() == _uris.forge.Int) {
index = i->second.get<int32_t>();
- } else {
- _log.error(fmt("Port %1% has no index\n") % path);
}
SPtr<PortModel> p(new PortModel(uris(), path, index, pdir));
@@ -443,6 +447,10 @@ void
ClientStore::connect(const Raul::Path& src_path,
const Raul::Path& dst_path)
{
+#ifdef INGEN_CLIENT_STORE_DUMP
+ std::cerr << "Client connect " << src_path << " => " << dst_path << std::endl;
+#endif
+
attempt_connection(src_path, dst_path);
}
@@ -450,6 +458,10 @@ void
ClientStore::disconnect(const Raul::Path& src_path,
const Raul::Path& dst_path)
{
+#ifdef INGEN_CLIENT_STORE_DUMP
+ std::cerr << "Client disconnect " << src_path << " => " << dst_path << std::endl;
+#endif
+
SPtr<PortModel> tail = dynamic_ptr_cast<PortModel>(_object(src_path));
SPtr<PortModel> head = dynamic_ptr_cast<PortModel>(_object(dst_path));
diff --git a/src/gui/App.cpp b/src/gui/App.cpp
index e6fe35dc..a524220e 100644
--- a/src/gui/App.cpp
+++ b/src/gui/App.cpp
@@ -152,7 +152,7 @@ App::attach(SPtr<SigClientInterface> client)
}
_client = client;
- _store = SPtr<ClientStore>(new ClientStore(_world->uris(), _world->log(), _world->interface(), client));
+ _store = SPtr<ClientStore>(new ClientStore(_world->uris(), _world->log(), client));
_loader = SPtr<ThreadedLoader>(new ThreadedLoader(*this, _world->interface()));
_graph_tree_window->init(*this, *_store);
diff --git a/src/gui/ConnectWindow.cpp b/src/gui/ConnectWindow.cpp
index fd85316c..1921c708 100644
--- a/src/gui/ConnectWindow.cpp
+++ b/src/gui/ConnectWindow.cpp
@@ -452,7 +452,7 @@ ConnectWindow::gtk_callback()
_progress_label->set_text("Connected to engine");
_connect_stage = 0; // set ourselves up for next time (if there is one)
_finished_connecting = true;
- _app->interface()->set_response_id(0);
+ _app->interface()->set_response_id(1);
return false; // deregister this callback
}
diff --git a/src/gui/GraphCanvas.cpp b/src/gui/GraphCanvas.cpp
index e04baa5e..5a0c365a 100644
--- a/src/gui/GraphCanvas.cpp
+++ b/src/gui/GraphCanvas.cpp
@@ -14,6 +14,7 @@
along with Ingen. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <algorithm>
#include <cassert>
#include <map>
#include <set>
@@ -470,6 +471,10 @@ GraphCanvas::on_event(GdkEvent* event)
default: break;
}
+ case GDK_MOTION_NOTIFY:
+ _paste_count = 0;
+ break;
+
default: break;
}
@@ -564,96 +569,121 @@ serialise_arc(GanvEdge* arc, void* data)
void
GraphCanvas::copy_selection()
{
- static const char* base_uri = INGEN_NS "selection/";
Serialisation::Serialiser serialiser(*_app.world());
- serialiser.start_to_string(_graph->path(), base_uri);
+ serialiser.start_to_string(_graph->path(), _graph->base_uri());
for_each_selected_node(serialise_node, &serialiser);
for_each_selected_edge(serialise_arc, &serialiser);
- const std::string result = serialiser.finish();
- _paste_count = 0;
-
Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get();
- clipboard->set_text(result);
+ clipboard->set_text(serialiser.finish());
+ _paste_count = 0;
}
void
GraphCanvas::paste()
{
- Glib::ustring str = Gtk::Clipboard::get()->wait_for_text();
+ typedef Node::Properties::const_iterator PropIter;
+
+ const Glib::ustring str = Gtk::Clipboard::get()->wait_for_text();
SPtr<Serialisation::Parser> parser = _app.loader()->parser();
+ const URIs& uris = _app.uris();
+ const Raul::Path& parent = _graph->path();
if (!parser) {
_app.log().error("Unable to load parser, paste unavailable\n");
return;
}
+ // Prepare for paste
clear_selection();
_pastees.clear();
++_paste_count;
- const URIs& uris = _app.uris();
-
+ // Make a client store to serve as clipboard
ClientStore clipboard(_app.world()->uris(), _app.log());
clipboard.set_plugins(_app.store()->plugins());
-
- // mkdir -p
- string to_create = _graph->path().substr(1);
- string created = "/";
- Resource::Properties props;
- props.insert(make_pair(uris.rdf_type,
- Resource::Property(uris.ingen_Graph)));
- props.insert(make_pair(uris.ingen_polyphony,
- _app.forge().make(int32_t(_graph->internal_poly()))));
- clipboard.put(Node::root_uri(), props);
- size_t first_slash;
- while (to_create != "/" && !to_create.empty()
- && (first_slash = to_create.find("/")) != string::npos) {
- created += to_create.substr(0, first_slash);
- assert(Raul::Path::is_valid(created));
- clipboard.put(Node::path_to_uri(Raul::Path(created)), props);
- to_create = to_create.substr(first_slash + 1);
+ clipboard.put(Node::root_uri(),
+ {{uris.rdf_type, Resource::Property(uris.ingen_Graph)}});
+
+ // Parse clipboard text into clipboard store
+ boost::optional<Raul::URI> base_uri = parser->parse_string(
+ _app.world(), &clipboard, str, Node::root_uri());
+
+ // Figure out the copy graph base path
+ Raul::Path copy_root("/");
+ if (base_uri) {
+ std::string base = *base_uri;
+ if (base[base.size() - 1] == '/') {
+ base = base.substr(0, base.size() - 1);
+ }
+ copy_root = Node::uri_to_path(Raul::URI(base));
}
- if (!_graph->path().is_root())
- clipboard.put(_graph->uri(), props);
-
- boost::optional<Raul::Path> parent;
- boost::optional<Raul::Symbol> symbol;
-
- if (!_graph->path().is_root()) {
- parent = _graph->path();
+ // Find the minimum x and y coordinate of objects to be pasted
+ float min_x = std::numeric_limits<float>::max();
+ float min_y = std::numeric_limits<float>::max();
+ for (const auto& c : clipboard) {
+ if (c.first.parent() == Raul::Path("/")) {
+ const Atom& x = c.second->get_property(uris.ingen_canvasX);
+ const Atom& y = c.second->get_property(uris.ingen_canvasY);
+ if (x.type() == uris.atom_Float) {
+ min_x = std::min(min_x, x.get<float>());
+ }
+ if (y.type() == uris.atom_Float) {
+ min_y = std::min(min_y, y.get<float>());
+ }
+ }
}
- ClashAvoider avoider(*_app.store().get(), clipboard, &clipboard);
- static const char* base_uri = INGEN_NS "selection/";
- parser->parse_string(_app.world(), &avoider, str, base_uri,
- parent, symbol);
-
- // Create objects
+ // Find canvas paste origin based on pointer position
+ int widget_point_x, widget_point_y, scroll_x, scroll_y;
+ widget().get_pointer(widget_point_x, widget_point_y);
+ get_scroll_offsets(scroll_x, scroll_y);
+ const int paste_x = widget_point_x + scroll_x + (20.0f * _paste_count);
+ const int paste_y = widget_point_y + scroll_y + (20.0f * _paste_count);
+
+ // Put each top level object in the clipboard store
+ ClashAvoider avoider(*_app.store().get());
for (const auto& c : clipboard) {
- if (_graph->path().is_root() && c.first.is_root())
+ if (c.first.is_root() || c.first.parent() != Raul::Path("/")) {
continue;
+ }
- Node::Properties& props = c.second->properties();
-
- Node::Properties::iterator x = props.find(uris.ingen_canvasX);
- if (x != c.second->properties().end())
- x->second = _app.forge().make(
- x->second.get<float>() + (20.0f * _paste_count));
-
- Node::Properties::iterator y = props.find(uris.ingen_canvasY);
- if (y != c.second->properties().end())
- y->second = _app.forge().make(
- y->second.get<float>() + (20.0f * _paste_count));
+ const SPtr<Node> node = c.second;
+ const Raul::Path& old_path = copy_root.child(node->path());
+ const Raul::URI& old_uri = Node::path_to_uri(old_path);
+ const Raul::Path& new_path = avoider.map_path(parent.child(node->path()));
+
+ Node::Properties props{{uris.ingen_prototype,
+ _app.forge().alloc_uri(old_uri)}};
+
+ // Set the same types
+ const auto t = node->properties().equal_range(uris.rdf_type);
+ props.insert(t.first, t.second);
+
+ // Set coordinates so paste origin is at the mouse pointer
+ PropIter xi = node->properties().find(uris.ingen_canvasX);
+ PropIter yi = node->properties().find(uris.ingen_canvasY);
+ if (xi != node->properties().end()) {
+ const float x = xi->second.get<float>() - min_x + paste_x;
+ props.insert({xi->first, Resource::Property(_app.forge().make(x),
+ xi->second.context())});
+ }
+ if (yi != node->properties().end()) {
+ const float y = yi->second.get<float>() - min_y + paste_y;
+ props.insert({yi->first, Resource::Property(_app.forge().make(y),
+ yi->second.context())});
+ }
- _app.interface()->put(c.second->uri(), c.second->properties());
- _pastees.insert(c.first);
+ _app.interface()->put(Node::path_to_uri(new_path), props);
+ _pastees.insert(new_path);
}
// Connect objects
- for (auto a : clipboard.object(_graph->path())->arcs()) {
- _app.interface()->connect(a.second->tail_path(), a.second->head_path());
+ for (auto a : clipboard.object(Raul::Path("/"))->arcs()) {
+ _app.interface()->connect(
+ avoider.map_path(parent.child(a.second->tail_path())),
+ avoider.map_path(parent.child(a.second->head_path())));
}
}
diff --git a/src/gui/ThreadedLoader.cpp b/src/gui/ThreadedLoader.cpp
index 3dffa7f9..93cccdf1 100644
--- a/src/gui/ThreadedLoader.cpp
+++ b/src/gui/ThreadedLoader.cpp
@@ -127,10 +127,13 @@ ThreadedLoader::save_graph_event(SPtr<const Client::GraphModel> model,
const string& filename)
{
if (_app.serialiser()) {
- if (filename.find(".ingen") != string::npos)
+ if (filename.find(".ingen") != string::npos) {
_app.serialiser()->write_bundle(model, filename);
- else
- _app.serialiser()->to_file(model, filename);
+ } else {
+ _app.serialiser()->start_to_file(model->path(), filename);
+ _app.serialiser()->serialise(model);
+ _app.serialiser()->finish();
+ }
}
}
diff --git a/src/serialisation/Parser.cpp b/src/serialisation/Parser.cpp
index 6a8547c5..663a7833 100644
--- a/src/serialisation/Parser.cpp
+++ b/src/serialisation/Parser.cpp
@@ -22,7 +22,6 @@
#include <glibmm/convert.h>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
-#include <glibmm/ustring.h>
#include "ingen/Atom.hpp"
#include "ingen/Interface.hpp"
@@ -43,10 +42,10 @@ typedef set<Sord::Node> RDFNodes;
namespace Ingen {
namespace Serialisation {
-static Glib::ustring
-relative_uri(Glib::ustring base, const Glib::ustring uri, bool leading_slash)
+static std::string
+relative_uri(const std::string& base, const std::string& uri, bool leading_slash)
{
- Glib::ustring ret;
+ std::string ret;
if (uri != base) {
SerdURI base_uri;
serd_uri_parse((const uint8_t*)base.c_str(), &base_uri);
@@ -55,21 +54,19 @@ relative_uri(Glib::ustring base, const Glib::ustring uri, bool leading_slash)
SerdNode normal_base_uri_node = serd_node_new_uri_from_string(
(const uint8_t*)".", &base_uri, &normal_base_uri);
- Glib::ustring normal_base_str((const char*)normal_base_uri_node.buf);
+ std::string normal_base_str((const char*)normal_base_uri_node.buf);
ret = uri;
if (uri.length() >= normal_base_str.length()
&& uri.substr(0, normal_base_str.length()) == normal_base_str) {
ret = uri.substr(normal_base_str.length());
- if (leading_slash && ret[0] != '/')
- ret = Glib::ustring("/") + ret;
}
serd_node_free(&normal_base_uri_node);
}
if (leading_slash && ret[0] != '/') {
- ret = Glib::ustring("/").append(ret);
+ ret = std::string("/").append(ret);
}
return ret;
}
@@ -190,7 +187,7 @@ parse(
World* world,
Interface* target,
Sord::Model& model,
- Glib::ustring document_uri,
+ const std::string& base_uri,
Sord::Node& subject,
boost::optional<Raul::Path> parent = boost::optional<Raul::Path>(),
boost::optional<Raul::Symbol> symbol = boost::optional<Raul::Symbol>(),
@@ -201,6 +198,7 @@ parse_graph(
World* world,
Interface* target,
Sord::Model& model,
+ const std::string& base_uri,
const Sord::Node& subject,
boost::optional<Raul::Path> parent = boost::optional<Raul::Path>(),
boost::optional<Raul::Symbol> symbol = boost::optional<Raul::Symbol>(),
@@ -211,6 +209,7 @@ parse_block(
World* world,
Interface* target,
Sord::Model& model,
+ const std::string& base_uri,
const Sord::Node& subject,
const Raul::Path& path,
boost::optional<Resource::Properties> data = boost::optional<Resource::Properties>());
@@ -226,16 +225,18 @@ parse_properties(
static bool
parse_arcs(
- World* world,
- Interface* target,
- Sord::Model& model,
- const Sord::Node& subject,
- const Raul::Path& graph);
+ World* world,
+ Interface* target,
+ Sord::Model& model,
+ const std::string& base_uri,
+ const Sord::Node& subject,
+ const Raul::Path& graph);
static boost::optional<Raul::Path>
parse_block(Ingen::World* world,
Ingen::Interface* target,
Sord::Model& model,
+ const std::string& base_uri,
const Sord::Node& subject,
const Raul::Path& path,
boost::optional<Node::Properties> data)
@@ -256,26 +257,23 @@ parse_block(Ingen::World* world,
return boost::optional<Raul::Path>();
}
- const std::string type_uri = relative_uri(
- model.base_uri().to_string(),
- i.get_object().to_string(),
- false);
+ const std::string type_uri = relative_uri(base_uri,
+ i.get_object().to_string(),
+ false);
if (!serd_uri_string_has_scheme((const uint8_t*)type_uri.c_str())) {
- SerdURI base_uri;
- serd_uri_parse(model.base_uri().to_u_string(), &base_uri);
+ SerdURI base_uri_parts;
+ serd_uri_parse((const uint8_t*)base_uri.c_str(), &base_uri_parts);
SerdURI ignored;
SerdNode sub_uri = serd_node_new_uri_from_string(
i.get_object().to_u_string(),
- &base_uri,
+ &base_uri_parts,
&ignored);
- const std::string sub_uri_str((const char*)sub_uri.buf);
- std::string basename = get_basename(sub_uri_str);
-
- const std::string sub_file =
- string((const char*)sub_uri.buf) + "/" + basename + ".ttl";
+ const std::string sub_uri_str = (const char*)sub_uri.buf;
+ const std::string basename = get_basename(sub_uri_str);
+ const std::string sub_file = sub_uri_str + '/' + basename + ".ttl";
const SerdNode sub_base = serd_node_from_string(
SERD_URI, (const uint8_t*)sub_file.c_str());
@@ -286,10 +284,10 @@ parse_block(Ingen::World* world,
serd_env_free(env);
Sord::URI sub_node(*world->rdf_world(), sub_file);
- parse_graph(world, target, sub_model, sub_node,
+ parse_graph(world, target, sub_model, (const char*)sub_base.buf, sub_node,
path.parent(), Raul::Symbol(path.symbol()));
- parse_graph(world, target, model, subject,
+ parse_graph(world, target, model, base_uri, subject,
path.parent(), Raul::Symbol(path.symbol()));
} else {
Resource::Properties props = get_properties(world, model, subject);
@@ -304,12 +302,13 @@ static boost::optional<Raul::Path>
parse_graph(Ingen::World* world,
Ingen::Interface* target,
Sord::Model& model,
+ const std::string& base_uri,
const Sord::Node& subject_node,
boost::optional<Raul::Path> parent,
boost::optional<Raul::Symbol> a_symbol,
boost::optional<Node::Properties> data)
{
- URIs& uris = world->uris();
+ const URIs& uris = world->uris();
const Sord::URI ingen_block(*world->rdf_world(), uris.ingen_block);
const Sord::URI ingen_polyphony(*world->rdf_world(), uris.ingen_polyphony);
@@ -318,18 +317,17 @@ parse_graph(Ingen::World* world,
const Sord::Node& graph = subject_node;
const Sord::Node nil;
- const Glib::ustring base_uri = model.base_uri().to_string();
-
Raul::Symbol symbol("_");
if (a_symbol) {
symbol = *a_symbol;
- } else {
- const std::string basename = get_basename(base_uri);
}
string graph_path_str = relative_uri(base_uri, subject_node.to_string(), true);
- if (parent && a_symbol)
+ if (parent && a_symbol) {
graph_path_str = parent->child(*a_symbol);
+ } else if (parent) {
+ graph_path_str = *parent;
+ }
if (!Raul::Path::is_valid(graph_path_str)) {
world->log().error(fmt("Graph %1% has invalid path\n")
@@ -349,7 +347,7 @@ parse_graph(Ingen::World* world,
Raul::Symbol(get_basename(node.to_string())));
// Parse and create block
- parse_block(world, target, model, node, block_path,
+ parse_block(world, target, model, base_uri, node, block_path,
boost::optional<Node::Properties>());
// For each port on this block
@@ -395,19 +393,20 @@ parse_graph(Ingen::World* world,
p.second.second);
}
- parse_arcs(world, target, model, subject_node, graph_path);
+ parse_arcs(world, target, model, base_uri, subject_node, graph_path);
return graph_path;
}
static bool
-parse_arc(Ingen::World* world,
- Ingen::Interface* target,
- Sord::Model& model,
- const Sord::Node& subject,
- const Raul::Path& graph)
+parse_arc(Ingen::World* world,
+ Ingen::Interface* target,
+ Sord::Model& model,
+ const std::string& base_uri,
+ const Sord::Node& subject,
+ const Raul::Path& graph)
{
- URIs& uris = world->uris();
+ const URIs& uris = world->uris();
const Sord::URI ingen_tail(*world->rdf_world(), uris.ingen_tail);
const Sord::URI ingen_head(*world->rdf_world(), uris.ingen_head);
@@ -416,8 +415,6 @@ parse_arc(Ingen::World* world,
Sord::Iter t = model.find(subject, ingen_tail, nil);
Sord::Iter h = model.find(subject, ingen_head, nil);
- const Glib::ustring& base_uri = model.base_uri().to_string();
-
if (t.end()) {
world->log().error("Arc has no tail");
return false;
@@ -455,17 +452,18 @@ parse_arc(Ingen::World* world,
}
static bool
-parse_arcs(Ingen::World* world,
- Ingen::Interface* target,
- Sord::Model& model,
- const Sord::Node& subject,
- const Raul::Path& graph)
+parse_arcs(Ingen::World* world,
+ Ingen::Interface* target,
+ Sord::Model& model,
+ const std::string& base_uri,
+ const Sord::Node& subject,
+ const Raul::Path& graph)
{
const Sord::URI ingen_arc(*world->rdf_world(), world->uris().ingen_arc);
const Sord::Node nil;
for (Sord::Iter i = model.find(subject, ingen_arc, nil); !i.end(); ++i) {
- parse_arc(world, target, model, i.get_object(), graph);
+ parse_arc(world, target, model, base_uri, i.get_object(), graph);
}
return true;
@@ -494,13 +492,13 @@ static boost::optional<Raul::Path>
parse(Ingen::World* world,
Ingen::Interface* target,
Sord::Model& model,
- Glib::ustring document_uri,
+ const std::string& base_uri,
Sord::Node& subject,
boost::optional<Raul::Path> parent,
boost::optional<Raul::Symbol> symbol,
boost::optional<Node::Properties> data)
{
- URIs& uris = world->uris();
+ const URIs& uris = world->uris();
const Sord::URI graph_class (*world->rdf_world(), uris.ingen_Graph);
const Sord::URI block_class (*world->rdf_world(), uris.ingen_Block);
@@ -514,7 +512,7 @@ parse(Ingen::World* world,
// Parse explicit subject graph
if (subject.is_valid()) {
- return parse_graph(world, target, model, subject, parent, symbol, data);
+ return parse_graph(world, target, model, base_uri, subject, parent, symbol, data);
}
// Get all subjects and their types (?subject a ?type)
@@ -540,12 +538,12 @@ parse(Ingen::World* world,
const Sord::Node& s = i.first;
const std::set<Sord::Node>& types = i.second;
boost::optional<Raul::Path> ret;
- const Raul::Path path(
- relative_uri( model.base_uri().to_string(), s.to_string(), true));
+ const Raul::Path rel_path(relative_uri(base_uri, s.to_string(), true));
+ const Raul::Path path = parent ? parent->child(rel_path) : rel_path;
if (types.find(graph_class) != types.end()) {
- ret = parse_graph(world, target, model, s, parent, symbol, data);
+ ret = parse_graph(world, target, model, base_uri, s, parent, symbol, data);
} else if (types.find(block_class) != types.end()) {
- ret = parse_block(world, target, model, s, path, data);
+ ret = parse_block(world, target, model, base_uri, s, path, data);
} else if (types.find(in_port_class) != types.end() ||
types.find(out_port_class) != types.end()) {
parse_properties(
@@ -553,7 +551,7 @@ parse(Ingen::World* world,
ret = path;
} else if (types.find(arc_class) != types.end()) {
Raul::Path parent_path(parent ? parent.get() : Raul::Path("/"));
- parse_arc(world, target, model, s, parent_path);
+ parse_arc(world, target, model, base_uri, s, parent_path);
} else {
world->log().error("Subject has no known types\n");
}
@@ -568,23 +566,23 @@ parse(Ingen::World* world,
bool
Parser::parse_file(Ingen::World* world,
Ingen::Interface* target,
- Glib::ustring path,
+ const std::string& path,
boost::optional<Raul::Path> parent,
boost::optional<Raul::Symbol> symbol,
boost::optional<Node::Properties> data)
{
- if (Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
- // This is a bundle, append "/name.ttl" to get graph file path
- path = Glib::build_filename(path, get_basename(path) + ".ttl");
+ std::string file_path = path;
+ if (!Glib::path_is_absolute(file_path)) {
+ file_path = Glib::build_filename(Glib::get_current_dir(), file_path);
}
-
- if (!Glib::path_is_absolute(path)) {
- path = Glib::build_filename(Glib::get_current_dir(), path);
+ if (Glib::file_test(file_path, Glib::FILE_TEST_IS_DIR)) {
+ // This is a bundle, append "/name.ttl" to get graph file path
+ file_path = Glib::build_filename(path, get_basename(path) + ".ttl");
}
std::string uri;
try {
- uri = Glib::filename_to_uri(path, "");
+ uri = Glib::filename_to_uri(file_path, "");
} catch (const Glib::ConvertError& e) {
world->log().error(fmt("Path to URI conversion error: %1%\n")
% e.what());
@@ -601,7 +599,7 @@ Parser::parse_file(Ingen::World* world,
serd_env_free(env);
- world->log().info(fmt("Parsing %1%\n") % path);
+ world->log().info(fmt("Parsing %1%\n") % file_path);
if (parent)
world->log().info(fmt("Parent: %1%\n") % parent->c_str());
if (symbol)
@@ -609,7 +607,8 @@ Parser::parse_file(Ingen::World* world,
Sord::Node subject(*world->rdf_world(), Sord::Node::URI, uri);
boost::optional<Raul::Path> parsed_path
- = parse(world, target, model, path, subject, parent, symbol, data);
+ = parse(world, target, model, model.base_uri().to_string(),
+ subject, parent, symbol, data);
if (parsed_path) {
target->set_property(Node::path_to_uri(*parsed_path),
@@ -622,27 +621,34 @@ Parser::parse_file(Ingen::World* world,
}
}
-bool
+boost::optional<Raul::URI>
Parser::parse_string(Ingen::World* world,
Ingen::Interface* target,
- const Glib::ustring& str,
- const Glib::ustring& base_uri,
+ const std::string& str,
+ const std::string& base_uri,
boost::optional<Raul::Path> parent,
boost::optional<Raul::Symbol> symbol,
boost::optional<Node::Properties> data)
{
// Load string into model
Sord::Model model(*world->rdf_world(), base_uri, SORD_SPO|SORD_PSO, false);
- const SerdNode base = serd_node_from_string(
- SERD_URI, (const uint8_t*)base_uri.c_str());
- SerdEnv* env = serd_env_new(&base);
+
+ SerdEnv* env = serd_env_new(NULL);
+ if (!base_uri.empty()) {
+ const SerdNode base = serd_node_from_string(
+ SERD_URI, (const uint8_t*)base_uri.c_str());
+ serd_env_set_base_uri(env, &base);
+ }
model.load_string(env, SERD_TURTLE, str.c_str(), str.length(), base_uri);
+
+ Raul::URI actual_base((const char*)serd_env_get_base_uri(env, NULL)->buf);
serd_env_free(env);
world->log().info(fmt("Parsing string (base %1%)\n") % base_uri);
Sord::Node subject;
- return !!parse(world, target, model, base_uri, subject, parent, symbol, data);
+ parse(world, target, model, actual_base, subject, parent, symbol, data);
+ return actual_base;
}
} // namespace Serialisation
diff --git a/src/serialisation/Serialiser.cpp b/src/serialisation/Serialiser.cpp
index dd1be2b4..3cd05ef4 100644
--- a/src/serialisation/Serialiser.cpp
+++ b/src/serialisation/Serialiser.cpp
@@ -54,6 +54,8 @@ namespace Ingen {
namespace Serialisation {
struct Serialiser::Impl {
+ typedef Resource::Properties Properties;
+
explicit Impl(World& world)
: _root_path("/")
, _world(world)
@@ -116,16 +118,6 @@ Serialiser::~Serialiser()
}
void
-Serialiser::to_file(SPtr<const Node> object,
- const std::string& filename)
-{
- me->_root_path = object->path();
- me->start_to_filename(filename);
- serialise(object);
- finish();
-}
-
-void
Serialiser::Impl::write_manifest(const std::string& bundle_path,
SPtr<const Node> graph,
const std::string& graph_symbol)
@@ -196,15 +188,6 @@ Serialiser::Impl::write_bundle(SPtr<const Node> graph,
write_manifest(path, graph, symbol);
}
-string
-Serialiser::to_string(SPtr<const Node> object,
- const string& base_uri)
-{
- start_to_string(object->path(), base_uri);
- serialise(object);
- return finish();
-}
-
/** Begin a serialization to a file.
*
* This must be called before any serializing methods.
@@ -223,16 +206,6 @@ Serialiser::Impl::start_to_filename(const string& filename)
_mode = Mode::TO_FILE;
}
-/** Begin a serialization to a string.
- *
- * This must be called before any serializing methods.
- *
- * The results of the serialization will be returned by the finish() method after
- * the desired objects have been serialised.
- *
- * All serialized paths will have the root path chopped from their prefix
- * (therefore all serialized paths must be descendants of the root)
- */
void
Serialiser::start_to_string(const Raul::Path& root, const string& base_uri)
{
@@ -242,6 +215,13 @@ Serialiser::start_to_string(const Raul::Path& root, const string& base_uri)
me->_mode = Impl::Mode::TO_STRING;
}
+void
+Serialiser::start_to_file(const Raul::Path& root, const string& filename)
+{
+ me->_root_path = root;
+ me->start_to_filename(filename);
+}
+
std::string
Serialiser::finish()
{
@@ -274,7 +254,6 @@ Serialiser::Impl::path_rdf_node(const Raul::Path& path)
{
assert(_model);
assert(path == _root_path || path.is_child_of(_root_path));
- // FIXME: if path == root_path() then "/" ?
return Sord::URI(_model->world(),
path.substr(_root_path.base().length()),
_base_uri);
diff --git a/src/server/BlockImpl.hpp b/src/server/BlockImpl.hpp
index 845cd7df..b3064168 100644
--- a/src/server/BlockImpl.hpp
+++ b/src/server/BlockImpl.hpp
@@ -42,6 +42,7 @@ namespace Server {
class Buffer;
class BufferFactory;
class Context;
+class Engine;
class GraphImpl;
class PluginImpl;
class PortImpl;
@@ -83,6 +84,11 @@ public:
*/
virtual void deactivate();
+ /** Duplicate this Node. */
+ virtual BlockImpl* duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent) { return NULL; }
+
/** Return true iff this block is activated */
bool activated() const { return _activated; }
diff --git a/src/server/Broadcaster.hpp b/src/server/Broadcaster.hpp
index b9e37c44..e33594a4 100644
--- a/src/server/Broadcaster.hpp
+++ b/src/server/Broadcaster.hpp
@@ -114,6 +114,11 @@ public:
BROADCAST(delta, uri, remove, add);
}
+ void copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri) {
+ BROADCAST(copy, old_path, new_uri);
+ }
+
void move(const Raul::Path& old_path,
const Raul::Path& new_path) {
BROADCAST(move, old_path, new_path);
diff --git a/src/server/DuplexPort.cpp b/src/server/DuplexPort.cpp
index 1dd1672f..7b39a0de 100644
--- a/src/server/DuplexPort.cpp
+++ b/src/server/DuplexPort.cpp
@@ -67,6 +67,25 @@ DuplexPort::~DuplexPort()
}
}
+DuplexPort*
+DuplexPort::duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent)
+{
+ BufferFactory& bufs = *engine.buffer_factory();
+ const Atom polyphonic = get_property(bufs.uris().ingen_polyphonic);
+
+ DuplexPort* dup = new DuplexPort(
+ bufs, parent, symbol, _index,
+ polyphonic.type() == bufs.uris().atom_Bool && polyphonic.get<int32_t>(),
+ _poly, _type, _buffer_type,
+ _value, _buffer_size, _is_output);
+
+ dup->set_properties(properties());
+
+ return dup;
+}
+
void
DuplexPort::inherit_neighbour(const PortImpl* port,
Resource::Properties& remove,
diff --git a/src/server/DuplexPort.hpp b/src/server/DuplexPort.hpp
index a247841e..d8e01e42 100644
--- a/src/server/DuplexPort.hpp
+++ b/src/server/DuplexPort.hpp
@@ -56,6 +56,10 @@ public:
virtual ~DuplexPort();
+ DuplexPort* duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent);
+
void inherit_neighbour(const PortImpl* port,
Resource::Properties& remove,
Resource::Properties& add);
diff --git a/src/server/EventWriter.cpp b/src/server/EventWriter.cpp
index 8508fab3..5f3fe3c3 100644
--- a/src/server/EventWriter.cpp
+++ b/src/server/EventWriter.cpp
@@ -69,6 +69,15 @@ EventWriter::delta(const Raul::URI& uri,
}
void
+EventWriter::copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri)
+{
+ _engine.enqueue_event(
+ new Events::Copy(_engine, _respondee, _request_id, now(),
+ old_path, new_uri));
+}
+
+void
EventWriter::move(const Raul::Path& old_path,
const Raul::Path& new_path)
{
diff --git a/src/server/EventWriter.hpp b/src/server/EventWriter.hpp
index 89493051..1c031961 100644
--- a/src/server/EventWriter.hpp
+++ b/src/server/EventWriter.hpp
@@ -64,6 +64,9 @@ public:
const Resource::Properties& remove,
const Resource::Properties& add);
+ virtual void copy(const Raul::Path& old_path,
+ const Raul::URI& new_uri);
+
virtual void move(const Raul::Path& old_path,
const Raul::Path& new_path);
diff --git a/src/server/GraphImpl.cpp b/src/server/GraphImpl.cpp
index a8a44cf1..f9c0e767 100644
--- a/src/server/GraphImpl.cpp
+++ b/src/server/GraphImpl.cpp
@@ -15,6 +15,7 @@
*/
#include <cassert>
+#include <unordered_map>
#include "ingen/Log.hpp"
#include "ingen/URIs.hpp"
@@ -24,6 +25,7 @@
#include "ArcImpl.hpp"
#include "BlockImpl.hpp"
#include "BufferFactory.hpp"
+#include "Driver.hpp"
#include "DuplexPort.hpp"
#include "Engine.hpp"
#include "GraphImpl.hpp"
@@ -63,6 +65,67 @@ GraphImpl::~GraphImpl()
delete _plugin;
}
+BlockImpl*
+GraphImpl::duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent)
+{
+ BufferFactory& bufs = *engine.buffer_factory();
+ const SampleRate rate = engine.driver()->sample_rate();
+
+ // Duplicate graph
+ GraphImpl* dup = new GraphImpl(
+ engine, symbol, _polyphony, parent, rate, _poly_process);
+
+ Properties props = properties();
+ props.erase(bufs.uris().lv2_symbol);
+ props.insert({bufs.uris().lv2_symbol, bufs.forge().alloc(symbol.c_str())});
+ dup->set_properties(props);
+
+ // We need a map of port duplicates to duplicate arcs
+ typedef std::unordered_map<PortImpl*, PortImpl*> PortMap;
+ PortMap port_map;
+
+ // Add duplicates of all ports
+ dup->_ports = new Raul::Array<PortImpl*>(num_ports(), NULL);
+ for (Ports::iterator p = _inputs.begin(); p != _inputs.end(); ++p) {
+ DuplexPort* p_dup = p->duplicate(engine, p->symbol(), dup);
+ dup->_inputs.push_front(*p_dup);
+ (*dup->_ports)[p->index()] = p_dup;
+ port_map.insert({&*p, p_dup});
+ }
+ for (Ports::iterator p = _outputs.begin(); p != _outputs.end(); ++p) {
+ DuplexPort* p_dup = p->duplicate(engine, p->symbol(), dup);
+ dup->_outputs.push_front(*p_dup);
+ (*dup->_ports)[p->index()] = p_dup;
+ port_map.insert({&*p, p_dup});
+ }
+
+ // Add duplicates of all blocks
+ for (auto& b : _blocks) {
+ BlockImpl* b_dup = b.duplicate(engine, b.symbol(), dup);
+ dup->add_block(*b_dup);
+ b_dup->activate(*engine.buffer_factory());
+ for (uint32_t p = 0; p < b.num_ports(); ++p) {
+ port_map.insert({b.port_impl(p), b_dup->port_impl(p)});
+ }
+ }
+
+ // Add duplicates of all arcs
+ for (const auto& a : _arcs) {
+ SPtr<ArcImpl> arc = dynamic_ptr_cast<ArcImpl>(a.second);
+ if (arc) {
+ PortMap::iterator t = port_map.find(arc->tail());
+ PortMap::iterator h = port_map.find(arc->head());
+ if (t != port_map.end() && h != port_map.end()) {
+ dup->add_arc(SPtr<ArcImpl>(new ArcImpl(t->second, h->second)));
+ }
+ }
+ }
+
+ return dup;
+}
+
void
GraphImpl::activate(BufferFactory& bufs)
{
diff --git a/src/server/GraphImpl.hpp b/src/server/GraphImpl.hpp
index 61bbdba4..9601f9a1 100644
--- a/src/server/GraphImpl.hpp
+++ b/src/server/GraphImpl.hpp
@@ -60,6 +60,10 @@ public:
virtual GraphType graph_type() const { return GraphType::GRAPH; }
+ BlockImpl* duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent);
+
void activate(BufferFactory& bufs);
void deactivate();
diff --git a/src/server/LV2Block.cpp b/src/server/LV2Block.cpp
index 4369107e..3f6f4be1 100644
--- a/src/server/LV2Block.cpp
+++ b/src/server/LV2Block.cpp
@@ -22,6 +22,7 @@
#include "lv2/lv2plug.in/ns/ext/morph/morph.h"
#include "lv2/lv2plug.in/ns/ext/options/options.h"
#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
#include "raul/Maid.hpp"
#include "raul/Array.hpp"
@@ -432,6 +433,46 @@ LV2Block::instantiate(BufferFactory& bufs)
return ret;
}
+BlockImpl*
+LV2Block::duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent)
+{
+ const SampleRate rate = engine.driver()->sample_rate();
+
+ // Duplicate and instantiate block
+ LV2Block* dup = new LV2Block(_lv2_plugin, symbol, _polyphonic, parent, rate);
+ if (!dup->instantiate(*engine.buffer_factory())) {
+ delete dup;
+ return NULL;
+ }
+ dup->set_properties(properties());
+
+ // Set duplicate port values and properties to the same as ours
+ for (uint32_t p = 0; p < num_ports(); ++p) {
+ const Atom& val = port_impl(p)->value();
+ if (val.is_valid()) {
+ dup->port_impl(p)->set_value(val);
+ }
+ dup->port_impl(p)->set_properties(port_impl(p)->properties());
+ }
+
+ // Copy internal plugin state
+ for (uint32_t v = 0; v < _polyphony; ++v) {
+ LilvState* state = lilv_state_new_from_instance(
+ _lv2_plugin->lilv_plugin(), instance(v),
+ &engine.world()->uri_map().urid_map_feature()->urid_map,
+ NULL, NULL, NULL, NULL, NULL, NULL, LV2_STATE_IS_NATIVE, NULL);
+ if (state) {
+ lilv_state_restore(state, dup->instance(v),
+ NULL, NULL, LV2_STATE_IS_NATIVE, NULL);
+ lilv_state_free(state);
+ }
+ }
+
+ return dup;
+}
+
void
LV2Block::activate(BufferFactory& bufs)
{
diff --git a/src/server/LV2Block.hpp b/src/server/LV2Block.hpp
index 35a2d5c3..ec2f99f4 100644
--- a/src/server/LV2Block.hpp
+++ b/src/server/LV2Block.hpp
@@ -48,6 +48,10 @@ public:
bool instantiate(BufferFactory& bufs);
+ BlockImpl* duplicate(Engine& engine,
+ const Raul::Symbol& symbol,
+ GraphImpl* parent);
+
bool prepare_poly(BufferFactory& bufs, uint32_t poly);
bool apply_poly(ProcessContext& context, Raul::Maid& maid, uint32_t poly);
diff --git a/src/server/PortImpl.cpp b/src/server/PortImpl.cpp
index 2dafcf32..bf605856 100644
--- a/src/server/PortImpl.cpp
+++ b/src/server/PortImpl.cpp
@@ -383,7 +383,6 @@ void
PortImpl::clear_buffers()
{
switch (_type.id()) {
- case PortType::AUDIO:
case PortType::CONTROL:
case PortType::CV:
for (uint32_t v = 0; v < _poly; ++v) {
@@ -395,6 +394,7 @@ PortImpl::clear_buffers()
state.time = 0;
}
break;
+ case PortType::AUDIO:
default:
for (uint32_t v = 0; v < _poly; ++v) {
buffer(v)->clear();
diff --git a/src/server/events.hpp b/src/server/events.hpp
index 9a1346b7..fb8509eb 100644
--- a/src/server/events.hpp
+++ b/src/server/events.hpp
@@ -27,6 +27,7 @@
#include "events/DisconnectAll.hpp"
#include "events/Get.hpp"
#include "events/Move.hpp"
+#include "events/Copy.hpp"
#include "events/SetPortValue.hpp"
#endif // INGEN_ENGINE_EVENTS_HPP
diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp
new file mode 100644
index 00000000..67ab4747
--- /dev/null
+++ b/src/server/events/Copy.cpp
@@ -0,0 +1,130 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <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/Copy.hpp"
+
+namespace Ingen {
+namespace Server {
+namespace Events {
+
+Copy::Copy(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& old_path,
+ const Raul::URI& new_uri)
+ : Event(engine, client, id, timestamp)
+ , _old_path(old_path)
+ , _new_uri(new_uri)
+ , _parent(NULL)
+ , _block(NULL)
+ , _compiled_graph(NULL)
+{}
+
+bool
+Copy::pre_process()
+{
+ if (_old_path.empty() ||
+ !Node::uri_is_path(_new_uri) ||
+ _new_uri == Node::root_uri()) {
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+
+ // Only support a single source for now
+ const Raul::Path new_path = Node::uri_to_path(_new_uri);
+ if (!Raul::Symbol::is_valid(new_path.symbol())) {
+ return Event::pre_process_done(Status::BAD_REQUEST);
+ }
+
+ std::unique_lock<std::mutex> lock(_engine.store()->mutex());
+
+ // Find the old node
+ const Store::iterator i = _engine.store()->find(_old_path);
+ if (i == _engine.store()->end()) {
+ return Event::pre_process_done(Status::NOT_FOUND, _old_path);
+ }
+
+ // Ensure the new node doesn't already exists
+ if (_engine.store()->find(new_path) != _engine.store()->end()) {
+ return Event::pre_process_done(Status::EXISTS, new_path);
+ }
+
+ // Get old node block, or fail (ports not supported for now)
+ BlockImpl* old_block = dynamic_cast<BlockImpl*>(i->second.get());
+ if (!old_block) {
+ return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _old_path);
+ }
+
+ // Find new parent graph
+ const Raul::Path parent_path = new_path.parent();
+ const Store::iterator p = _engine.store()->find(parent_path);
+ if (p == _engine.store()->end()) {
+ return Event::pre_process_done(Status::NOT_FOUND, parent_path);
+ }
+ if (!(_parent = dynamic_cast<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
+ if (_parent->enabled()) {
+ _compiled_graph = _parent->compile();
+ }
+
+ return Event::pre_process_done(Status::SUCCESS);
+}
+
+void
+Copy::execute(ProcessContext& context)
+{
+ if (_block) {
+ _parent->set_compiled_graph(_compiled_graph);
+ _compiled_graph = NULL; // Graph takes ownership
+ }
+}
+
+void
+Copy::post_process()
+{
+ Broadcaster::Transfer t(*_engine.broadcaster());
+ if (respond() == Status::SUCCESS) {
+ _engine.broadcaster()->copy(_old_path, _new_uri);
+ }
+}
+
+} // namespace Events
+} // namespace Server
+} // namespace Ingen
diff --git a/src/server/events/Copy.hpp b/src/server/events/Copy.hpp
new file mode 100644
index 00000000..26c0c815
--- /dev/null
+++ b/src/server/events/Copy.hpp
@@ -0,0 +1,73 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <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 "Event.hpp"
+
+namespace Ingen {
+namespace Server {
+
+class BlockImpl;
+class CompiledGraph;
+class GraphImpl;
+
+namespace Events {
+
+/** \page methods
+ * <h2>COPY</h2>
+ * As per WebDAV (RFC4918 S9.8).
+ *
+ * Copy an object from its current location and insert it at a new location
+ * in a single operation.
+ */
+
+/** COPY a graph object to a new path (see \ref methods).
+ * \ingroup engine
+ */
+class Copy : public Event
+{
+public:
+ Copy(Engine& engine,
+ SPtr<Interface> client,
+ int32_t id,
+ SampleCount timestamp,
+ const Raul::Path& old_path,
+ const Raul::URI& new_uri);
+
+ bool pre_process();
+ void execute(ProcessContext& context);
+ void post_process();
+
+private:
+ const Raul::Path _old_path;
+ const Raul::URI _new_uri;
+ GraphImpl* _parent;
+ BlockImpl* _block;
+ CompiledGraph* _compiled_graph;
+};
+
+} // namespace Events
+} // namespace Server
+} // namespace Ingen
+
+#endif // INGEN_EVENTS_COPY_HPP
diff --git a/src/server/events/CreateBlock.cpp b/src/server/events/CreateBlock.cpp
index 1e1e2228..e14a3356 100644
--- a/src/server/events/CreateBlock.cpp
+++ b/src/server/events/CreateBlock.cpp
@@ -23,6 +23,7 @@
#include "BlockImpl.hpp"
#include "Broadcaster.hpp"
#include "CreateBlock.hpp"
+#include "Driver.hpp"
#include "Engine.hpp"
#include "GraphImpl.hpp"
#include "PluginImpl.hpp"
@@ -54,58 +55,72 @@ CreateBlock::~CreateBlock()
bool
CreateBlock::pre_process()
{
- Ingen::URIs& uris = _engine.world()->uris();
-
typedef Resource::Properties::const_iterator iterator;
+ const Ingen::URIs& uris = _engine.world()->uris();
+ const SPtr<Store> 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());
}
- std::string plugin_uri_str;
+ // Get prototype URI
const iterator t = _properties.find(uris.ingen_prototype);
- if (t != _properties.end() && t->second.type() == uris.forge.URI) {
- plugin_uri_str = t->second.ptr<char>();
- } else {
+ if (t == _properties.end() || t->second.type() != uris.forge.URI) {
return Event::pre_process_done(Status::BAD_REQUEST);
}
- if (_engine.store()->get(_path)) {
- return Event::pre_process_done(Status::EXISTS, _path);
- }
-
- _graph = dynamic_cast<GraphImpl*>(_engine.store()->get(_path.parent()));
- if (!_graph) {
- return Event::pre_process_done(Status::PARENT_NOT_FOUND, _path.parent());
- }
-
- const Raul::URI plugin_uri(plugin_uri_str);
- PluginImpl* plugin = _engine.block_factory()->plugin(plugin_uri);
- if (!plugin) {
- return Event::pre_process_done(Status::PLUGIN_NOT_FOUND,
- Raul::URI(plugin_uri));
- }
+ const Raul::URI prototype(t->second.ptr<char>());
+
+ // 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 (Node::uri_is_path(prototype)) {
+ // Prototype is an existing block
+ BlockImpl* const ancestor = dynamic_cast<BlockImpl*>(
+ store->get(Node::uri_to_path(prototype)));
+ if (!ancestor) {
+ return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype);
+ } else if (!(_block = ancestor->duplicate(
+ _engine, Raul::Symbol(_path.symbol()), _graph))) {
+ return Event::pre_process_done(Status::CREATION_FAILED, _path);
+ }
- const iterator p = _properties.find(uris.ingen_polyphonic);
- const bool polyphonic = (
- p != _properties.end() &&
- p->second.type() == _engine.world()->forge().Bool &&
- p->second.get<int32_t>());
-
- if (!(_block = plugin->instantiate(*_engine.buffer_factory(),
- Raul::Symbol(_path.symbol()),
- polyphonic,
- _graph,
- _engine))) {
- return Event::pre_process_done(Status::CREATION_FAILED, _path);
+ /* Replace prototype with the ancestor's. This is less informative,
+ but the client expects an actual LV2 plugin as prototype. */
+ _properties.erase(uris.ingen_prototype);
+ _properties.insert(std::make_pair(uris.ingen_prototype,
+ uris.forge.alloc_uri(ancestor->plugin()->uri())));
+ } else {
+ // Prototype is a plugin
+ PluginImpl* const plugin = _engine.block_factory()->plugin(prototype);
+ if (!plugin) {
+ return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype);
+ } else if (!(_block = plugin->instantiate(*_engine.buffer_factory(),
+ Raul::Symbol(_path.symbol()),
+ polyphonic,
+ _graph,
+ _engine))) {
+ return Event::pre_process_done(Status::CREATION_FAILED, _path);
+ }
}
+ // Activate block
_block->properties().insert(_properties.begin(), _properties.end());
_block->activate(*_engine.buffer_factory());
// Add block to the store and the graph's pre-processor only block list
_graph->add_block(*_block);
- _engine.store()->add(_block);
+ store->add(_block);
/* Compile graph with new block added for insertion in audio thread
TODO: Since the block is not connected at this point, a full compilation
@@ -114,11 +129,7 @@ CreateBlock::pre_process()
_compiled_graph = _graph->compile();
}
- _update.push_back(make_pair(_block->uri(), _block->properties()));
- for (uint32_t i = 0; i < _block->num_ports(); ++i) {
- const PortImpl* port = _block->port_impl(i);
- _update.push_back(std::make_pair(port->uri(), port->properties()));
- }
+ _update.put_block(_block);
return Event::pre_process_done(Status::SUCCESS);
}
@@ -137,9 +148,7 @@ CreateBlock::post_process()
{
Broadcaster::Transfer t(*_engine.broadcaster());
if (respond() == Status::SUCCESS) {
- for (const auto& u : _update) {
- _engine.broadcaster()->put(u.first, u.second);
- }
+ _update.send(_engine.broadcaster());
}
}
diff --git a/src/server/events/CreateBlock.hpp b/src/server/events/CreateBlock.hpp
index 36e35775..6e0d7f4d 100644
--- a/src/server/events/CreateBlock.hpp
+++ b/src/server/events/CreateBlock.hpp
@@ -17,12 +17,10 @@
#ifndef INGEN_EVENTS_CREATEBLOCK_HPP
#define INGEN_EVENTS_CREATEBLOCK_HPP
-#include <list>
-#include <utility>
-
#include "ingen/Resource.hpp"
#include "Event.hpp"
+#include "events/Get.hpp"
namespace Ingen {
namespace Server {
@@ -54,15 +52,12 @@ public:
void post_process();
private:
- /// Update put message to broadcast to clients
- typedef std::list< std::pair<Raul::URI, Resource::Properties> > Update;
-
- Raul::Path _path;
- Resource::Properties _properties;
- Update _update;
- GraphImpl* _graph;
- BlockImpl* _block;
- CompiledGraph* _compiled_graph;
+ Raul::Path _path;
+ Resource::Properties _properties;
+ Events::Get::Response _update;
+ GraphImpl* _graph;
+ BlockImpl* _block;
+ CompiledGraph* _compiled_graph;
};
} // namespace Events
diff --git a/src/server/events/CreateGraph.cpp b/src/server/events/CreateGraph.cpp
index 0355b0bb..06445a6b 100644
--- a/src/server/events/CreateGraph.cpp
+++ b/src/server/events/CreateGraph.cpp
@@ -41,8 +41,7 @@ CreateGraph::CreateGraph(Engine& engine,
, _graph(NULL)
, _parent(NULL)
, _compiled_graph(NULL)
-{
-}
+{}
bool
CreateGraph::pre_process()
@@ -76,13 +75,33 @@ CreateGraph::pre_process()
}
const Raul::Symbol symbol((_path.is_root()) ? "root" : _path.symbol());
- _graph = new GraphImpl(_engine, symbol, ext_poly, _parent,
- _engine.driver()->sample_rate(), int_poly);
- _graph->properties().insert(_properties.begin(), _properties.end());
- _graph->add_property(uris.rdf_type, uris.ingen_Graph);
- _graph->add_property(uris.rdf_type,
- Resource::Property(uris.ingen_Block,
- Resource::Graph::EXTERNAL));
+
+ // Create graph based on prototype
+ const iterator t = _properties.find(uris.ingen_prototype);
+ if (t != _properties.end() &&
+ Raul::URI::is_valid(t->second.ptr<char>()) &&
+ Node::uri_is_path(Raul::URI(t->second.ptr<char>()))) {
+ // Create a duplicate of an existing graph
+ const Raul::URI prototype(t->second.ptr<char>());
+ GraphImpl* ancestor = dynamic_cast<GraphImpl*>(
+ _engine.store()->get(Node::uri_to_path(prototype)));
+ if (!ancestor) {
+ return Event::pre_process_done(Status::PROTOTYPE_NOT_FOUND, prototype);
+ } else if (!(_graph = dynamic_cast<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.driver()->sample_rate(), int_poly);
+ _graph->add_property(uris.rdf_type, uris.ingen_Graph);
+ _graph->add_property(uris.rdf_type,
+ Resource::Property(uris.ingen_Block,
+ Resource::Graph::EXTERNAL));
+ }
+
+ _graph->set_properties(_properties);
_parent->add_block(*_graph);
if (_parent->enabled()) {
@@ -92,10 +111,12 @@ CreateGraph::pre_process()
_graph->activate(*_engine.buffer_factory());
- // Insert into Store
+ // Insert into store and build update to send to clients
_engine.store()->add(_graph);
-
- _update = _graph->properties();
+ _update.put_graph(_graph);
+ for (BlockImpl& block : _graph->blocks()) {
+ _engine.store()->add(&block);
+ }
return Event::pre_process_done(Status::SUCCESS);
}
@@ -113,7 +134,7 @@ CreateGraph::post_process()
{
Broadcaster::Transfer t(*_engine.broadcaster());
if (respond() == Status::SUCCESS) {
- _engine.broadcaster()->put(Node::path_to_uri(_path), _update);
+ _update.send(_engine.broadcaster());
}
}
diff --git a/src/server/events/CreateGraph.hpp b/src/server/events/CreateGraph.hpp
index 64fb92bd..542a2921 100644
--- a/src/server/events/CreateGraph.hpp
+++ b/src/server/events/CreateGraph.hpp
@@ -17,9 +17,11 @@
#ifndef INGEN_EVENTS_CREATEGRAPH_HPP
#define INGEN_EVENTS_CREATEGRAPH_HPP
-#include "Event.hpp"
#include "ingen/Resource.hpp"
+#include "Event.hpp"
+#include "events/Get.hpp"
+
namespace Ingen {
namespace Server {
@@ -47,12 +49,12 @@ public:
void post_process();
private:
- const Raul::Path _path;
- Resource::Properties _properties;
- Resource::Properties _update;
- GraphImpl* _graph;
- GraphImpl* _parent;
- CompiledGraph* _compiled_graph;
+ const Raul::Path _path;
+ Resource::Properties _properties;
+ Events::Get::Response _update;
+ GraphImpl* _graph;
+ GraphImpl* _parent;
+ CompiledGraph* _compiled_graph;
};
} // namespace Events
diff --git a/src/server/events/CreatePort.cpp b/src/server/events/CreatePort.cpp
index 2a1a7994..21602df6 100644
--- a/src/server/events/CreatePort.cpp
+++ b/src/server/events/CreatePort.cpp
@@ -87,31 +87,23 @@ CreatePort::pre_process()
{
if (_port_type == PortType::UNKNOWN) {
return Event::pre_process_done(Status::UNKNOWN_TYPE, _path);
- }
-
- if (_path.is_root()) {
+ } else if (_path.is_root()) {
return Event::pre_process_done(Status::BAD_URI, _path);
- }
-
- if (_engine.store()->get(_path)) {
+ } else if (_engine.store()->get(_path)) {
return Event::pre_process_done(_status, _path);
}
- Node* parent = _engine.store()->get(_path.parent());
+ const Raul::Path parent_path = _path.parent();
+ Node* const parent = _engine.store()->get(parent_path);
if (!parent) {
- return Event::pre_process_done(Status::PARENT_NOT_FOUND,
- _path.parent());
+ return Event::pre_process_done(Status::PARENT_NOT_FOUND, parent_path);
+ } else if (!(_graph = dynamic_cast<GraphImpl*>(parent))) {
+ return Event::pre_process_done(Status::INVALID_PARENT, parent_path);
}
- if (!(_graph = dynamic_cast<GraphImpl*>(parent))) {
- return Event::pre_process_done(Status::INVALID_PARENT_PATH,
- _path.parent());
- }
-
- const URIs& uris = _engine.world()->uris();
- const BufferFactory& buffer_factory = *_engine.buffer_factory();
-
- const uint32_t buf_size = buffer_factory.default_size(_buf_type);
+ const URIs& uris = _engine.world()->uris();
+ BufferFactory& bufs = *_engine.buffer_factory();
+ const uint32_t buf_size = bufs.default_size(_buf_type);
const int32_t old_n_ports = _graph->num_ports_non_rt();
typedef Resource::Properties::const_iterator PropIter;
@@ -133,7 +125,7 @@ CreatePort::pre_process()
poly_i->second.get<int32_t>());
if (!(_graph_port = _graph->create_port(
- *_engine.buffer_factory(), Raul::Symbol(_path.symbol()),
+ bufs, Raul::Symbol(_path.symbol()),
_port_type, _buf_type, buf_size, _is_output, polyphonic))) {
return Event::pre_process_done(Status::CREATION_FAILED, _path);
}
diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp
index 23285b9b..02f53410 100644
--- a/src/server/events/Delta.cpp
+++ b/src/server/events/Delta.cpp
@@ -135,9 +135,11 @@ Delta::pre_process()
path, is_output, _properties);
}
if (_create_event) {
- _create_event->pre_process();
- // Grab the object for applying properties, if the create-event succeeded
- _object = _engine.store()->get(path);
+ if (_create_event->pre_process()) {
+ _object = _engine.store()->get(path); // Get object for setting
+ } else {
+ return Event::pre_process_done(Status::CREATION_FAILED, _subject);
+ }
} else {
return Event::pre_process_done(Status::BAD_OBJECT_TYPE, _subject);
}
@@ -382,36 +384,39 @@ Delta::post_process()
Broadcaster::Transfer t(*_engine.broadcaster());
- for (auto& s : _set_events)
- s->post_process();
+ if (_create_event) {
+ _create_event->post_process();
+ if (_create_event->status() != Status::SUCCESS) {
+ return; // Creation failed, nothing else to do
+ }
+ }
- if (_status == Status::SUCCESS) {
- if (_create_event) {
- _create_event->post_process();
- } else {
- respond();
- switch (_type) {
- case Type::SET:
- /* Kludge to avoid feedback for set events only. The GUI
- depends on put responses to e.g. initially place blocks.
- Some more sensible way of controlling this is needed. */
- _engine.broadcaster()->set_ignore_client(_request_client);
- _engine.broadcaster()->set_property(
- _subject,
- (*_properties.begin()).first,
- (*_properties.begin()).second);
- _engine.broadcaster()->clear_ignore_client();
- break;
- case Type::PUT:
- _engine.broadcaster()->put(_subject, _properties, _context);
- break;
- case Type::PATCH:
- _engine.broadcaster()->delta(_subject, _remove, _properties);
- break;
- }
+ for (auto& s : _set_events) {
+ if (s->status() != Status::SUCCESS) {
+ s->post_process(); // Set failed, report error
+ }
+ }
+
+ if (respond() == Status::SUCCESS) {
+ switch (_type) {
+ case Type::SET:
+ /* Kludge to avoid feedback for set events only. The GUI
+ depends on put responses to e.g. initially place blocks.
+ Some more sensible way of controlling this is needed. */
+ _engine.broadcaster()->set_ignore_client(_request_client);
+ _engine.broadcaster()->set_property(
+ _subject,
+ (*_properties.begin()).first,
+ (*_properties.begin()).second);
+ _engine.broadcaster()->clear_ignore_client();
+ break;
+ case Type::PUT:
+ _engine.broadcaster()->put(_subject, _properties, _context);
+ break;
+ case Type::PATCH:
+ _engine.broadcaster()->delta(_subject, _remove, _properties);
+ break;
}
- } else {
- respond();
}
}
diff --git a/src/server/events/DisconnectAll.cpp b/src/server/events/DisconnectAll.cpp
index 22560c7e..262bfea8 100644
--- a/src/server/events/DisconnectAll.cpp
+++ b/src/server/events/DisconnectAll.cpp
@@ -100,8 +100,7 @@ DisconnectAll::pre_process()
if (object->parent_graph() != _parent
&& object->parent()->parent_graph() != _parent) {
- return Event::pre_process_done(Status::INVALID_PARENT_PATH,
- _parent_path);
+ return Event::pre_process_done(Status::INVALID_PARENT, _parent_path);
}
// Only one of these will succeed
diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp
index c984e576..8a9eb227 100644
--- a/src/server/events/Get.cpp
+++ b/src/server/events/Get.cpp
@@ -100,6 +100,19 @@ Get::Response::put_graph(const GraphImpl* graph)
}
}
+void
+Get::Response::send(Interface* dest)
+{
+ // Sort puts by URI so parents are sent first
+ std::sort(puts.begin(), puts.end());
+ for (const Response::Put& put : puts) {
+ dest->put(put.uri, put.properties, put.ctx);
+ }
+ for (const Response::Connect& connect : connects) {
+ dest->connect(connect.tail, connect.head);
+ }
+}
+
Get::Get(Engine& engine,
SPtr<Interface> client,
int32_t id,
@@ -162,12 +175,7 @@ Get::post_process()
uris.param_sampleRate,
uris.forge.make(int32_t(_engine.driver()->sample_rate())));
} else {
- for (const Response::Put& put : _response.puts) {
- _request_client->put(put.uri, put.properties, put.ctx);
- }
- for (const Response::Connect& connect : _response.connects) {
- _request_client->connect(connect.tail, connect.head);
- }
+ _response.send(_request_client.get());
}
}
}
diff --git a/src/server/events/Get.hpp b/src/server/events/Get.hpp
index 7aea8c77..e0ed3483 100644
--- a/src/server/events/Get.hpp
+++ b/src/server/events/Get.hpp
@@ -17,6 +17,8 @@
#ifndef INGEN_EVENTS_GET_HPP
#define INGEN_EVENTS_GET_HPP
+#include <vector>
+
#include "Event.hpp"
#include "BlockFactory.hpp"
#include "types.hpp"
@@ -24,7 +26,10 @@
namespace Ingen {
namespace Server {
+class BlockImpl;
+class GraphImpl;
class PluginImpl;
+class PortImpl;
namespace Events {
@@ -45,7 +50,6 @@ public:
void execute(ProcessContext& context) {}
void post_process();
-private:
/** A sequence of puts and connects to respond to client with.
* This is constructed in the pre_process() and later sent in
* post_process() to avoid the need to lock.
@@ -63,11 +67,17 @@ private:
void put_port(const PortImpl* port);
void put_block(const BlockImpl* block);
void put_graph(const GraphImpl* graph);
-
+
+ void send(Interface* dest);
+
struct Put {
Raul::URI uri;
Resource::Properties properties;
Resource::Graph ctx;
+
+ inline bool operator<(const Put& other) {
+ return uri < other.uri;
+ }
};
struct Connect {
@@ -79,6 +89,7 @@ private:
std::vector<Connect> connects;
};
+private:
const Raul::URI _uri;
const Node* _object;
const PluginImpl* _plugin;
diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp
index 56f106cb..065f42ac 100644
--- a/src/server/ingen_lv2.cpp
+++ b/src/server/ingen_lv2.cpp
@@ -713,7 +713,9 @@ ingen_save(LV2_Handle instance,
char* state_path = map_path->abstract_path(map_path->handle, real_path);
Ingen::Store::iterator root = plugin->world->store()->find(Raul::Path("/"));
- plugin->world->serialiser()->to_file(root->second, real_path);
+ plugin->world->serialiser()->start_to_file(root->second->path(), real_path);
+ plugin->world->serialiser()->serialise(root->second);
+ plugin->world->serialiser()->finish();
store(handle,
ingen_file,
diff --git a/src/server/wscript b/src/server/wscript
index 9d1238a4..16bca160 100644
--- a/src/server/wscript
+++ b/src/server/wscript
@@ -28,6 +28,7 @@ def build(bld):
SocketListener.cpp
Worker.cpp
events/Connect.cpp
+ events/Copy.cpp
events/CreateBlock.cpp
events/CreateGraph.cpp
events/CreatePort.cpp
diff --git a/wscript b/wscript
index 1092e4a1..1285df63 100644
--- a/wscript
+++ b/wscript
@@ -61,7 +61,7 @@ def configure(conf):
conf.check_cxx(header_name='boost/weak_ptr.hpp')
autowaf.check_pkg(conf, 'lv2', uselib_store='LV2',
- atleast_version='1.8.1', mandatory=True)
+ atleast_version='1.11.0', mandatory=True)
autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM',
atleast_version='2.14.0', mandatory=True)
autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD',