/*
This file is part of Ingen.
Copyright 2007-2016 David Robillard
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or any later version.
Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for details.
You should have received a copy of the GNU Affero General Public License
along with Ingen. If not, see .
*/
#include "ingen/Log.hpp"
#include "ingen/client/ArcModel.hpp"
#include "ingen/client/BlockModel.hpp"
#include "ingen/client/ClientStore.hpp"
#include "ingen/client/GraphModel.hpp"
#include "ingen/client/ObjectModel.hpp"
#include "ingen/client/PluginModel.hpp"
#include "ingen/client/PortModel.hpp"
#include "ingen/client/SigClientInterface.hpp"
// #define INGEN_CLIENT_STORE_DUMP 1
using namespace std;
namespace Ingen {
namespace Client {
ClientStore::ClientStore(URIs& uris,
Log& log,
SPtr emitter)
: _uris(uris)
, _log(log)
, _emitter(emitter)
, _plugins(new Plugins())
{
if (!emitter)
return;
#define CONNECT(signal, method) \
emitter->signal_##signal().connect( \
sigc::mem_fun(this, &ClientStore::method));
CONNECT(object_deleted, del);
CONNECT(object_moved, move);
CONNECT(put, put);
CONNECT(delta, delta);
CONNECT(connection, connect);
CONNECT(disconnection, disconnect);
CONNECT(disconnect_all, disconnect_all);
CONNECT(property_change, set_property);
}
void
ClientStore::clear()
{
Store::clear();
_plugins->clear();
}
void
ClientStore::add_object(SPtr object)
{
// If we already have "this" object, merge the existing one into the new
// one (with precedence to the new values).
iterator existing = find(object->path());
if (existing != end()) {
dynamic_ptr_cast(existing->second)->set(object);
} else {
if (!object->path().is_root()) {
SPtr parent = _object(object->path().parent());
if (parent) {
assert(object->path().is_child_of(parent->path()));
object->set_parent(parent);
parent->add_child(object);
assert(parent && (object->parent() == parent));
(*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())
object->signal_property().emit(p.first, p.second);
}
SPtr
ClientStore::remove_object(const Raul::Path& path)
{
// Find the object, the "top" of the tree to remove
const iterator top = find(path);
if (top == end()) {
return SPtr();
}
SPtr object = dynamic_ptr_cast(top->second);
// Remove object and any adjacent arcs from parent if applicable
if (object && object->parent()) {
SPtr port = dynamic_ptr_cast(object);
if (port && dynamic_ptr_cast(port->parent())) {
disconnect_all(port->parent()->path(), path);
if (port->parent()->parent()) {
disconnect_all(port->parent()->parent()->path(), path);
}
} else if (port && port->parent()->parent()) {
disconnect_all(port->parent()->parent()->path(), path);
} else {
disconnect_all(object->parent()->path(), path);
}
object->parent()->remove_child(object);
}
// Remove the object and all its descendants
Objects removed;
remove(top, removed);
// Notify everything that needs to know this object has been removed
if (object) {
object->signal_destroyed().emit();
}
return object;
}
SPtr
ClientStore::_plugin(const Raul::URI& uri)
{
const Plugins::iterator i = _plugins->find(uri);
return (i == _plugins->end()) ? SPtr() : (*i).second;
}
SPtr
ClientStore::_plugin(const Atom& uri)
{
/* FIXME: SHould probably be stored with URIs rather than strings, to make this
a fast case. */
const Plugins::iterator i = _plugins->find(Raul::URI(_uris.forge.str(uri, false)));
return (i == _plugins->end()) ? SPtr() : (*i).second;
}
SPtr
ClientStore::plugin(const Raul::URI& uri) const
{
return const_cast(this)->_plugin(uri);
}
SPtr
ClientStore::_object(const Raul::Path& path)
{
const iterator i = find(path);
if (i == end()) {
return SPtr();
} else {
SPtr model = dynamic_ptr_cast(i->second);
assert(model);
assert(model->path().is_root() || model->parent());
return model;
}
}
SPtr
ClientStore::object(const Raul::Path& path) const
{
return const_cast(this)->_object(path);
}
SPtr
ClientStore::_resource(const Raul::URI& uri)
{
if (Node::uri_is_path(uri)) {
return _object(Node::uri_to_path(uri));
} else {
return _plugin(uri);
}
}
SPtr
ClientStore::resource(const Raul::URI& uri) const
{
return const_cast(this)->_resource(uri);
}
void
ClientStore::add_plugin(SPtr pm)
{
SPtr existing = _plugin(pm->uri());
if (existing) {
existing->set(pm);
} else {
_plugins->insert(make_pair(pm->uri(), pm));
_signal_new_plugin.emit(pm);
}
}
/* ****** Signal Handlers ******** */
void
ClientStore::del(const Raul::URI& uri)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client del " << uri << std::endl;
#endif
if (Node::uri_is_path(uri)) {
remove_object(Node::uri_to_path(uri));
} else {
Plugins::iterator p = _plugins->find(uri);
if (p != _plugins->end()) {
_plugins->erase(p);
_signal_plugin_deleted.emit(uri);
}
}
}
void
ClientStore::copy(const Raul::URI& old_uri,
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)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client move " << old_path
<< " => " << new_path << endl;
#endif
const iterator top = find(old_path);
if (top != end()) {
rename(top, new_path);
}
}
void
ClientStore::put(const Raul::URI& uri,
const Resource::Properties& properties,
Resource::Graph ctx)
{
typedef Resource::Properties::const_iterator Iterator;
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client put " << uri << " {" << endl;
for (auto p : properties)
std::cerr << '\t' << p.first << " " << _uris.forge.str(p.second)
<< "^^" << _uris.forge.str(_uris.forge.make_urid(p.second.type()), true) << endl;
std::cerr << "}" << endl;
#endif
bool is_graph, is_block, is_port, is_output;
Resource::type(uris(), properties,
is_graph, is_block, is_port, is_output);
// Check for specially handled types
const Iterator t = properties.find(_uris.rdf_type);
if (t != properties.end()) {
const Atom& type(t->second);
if (_uris.pset_Preset == type) {
const Iterator p = properties.find(_uris.lv2_appliesTo);
const Iterator l = properties.find(_uris.rdfs_label);
SPtr plug;
if (p == properties.end()) {
_log.error(fmt("Preset <%1%> with no plugin\n") % uri.c_str());
} else if (l == properties.end()) {
_log.error(fmt("Preset <%1%> with no label\n") % uri.c_str());
} else if (l->second.type() != _uris.forge.String) {
_log.error(fmt("Preset <%1%> label is not a string\n") % uri.c_str());
} else if (!(plug = _plugin(p->second))) {
_log.error(fmt("Preset <%1%> for unknown plugin %2%\n")
% uri.c_str() % _uris.forge.str(p->second));
} else {
plug->add_preset(uri, l->second.ptr());
}
return;
} else if (_uris.ingen_Graph == type) {
is_graph = true;
} else if (_uris.ingen_Internal == type || _uris.lv2_Plugin == type) {
SPtr p(new PluginModel(uris(), uri, type, properties));
add_plugin(p);
return;
}
}
if (!Node::uri_is_path(uri)) {
_log.error(fmt("Put for unknown subject <%1%>\n")
% uri.c_str());
return;
}
const Raul::Path path(Node::uri_to_path(uri));
SPtr obj = dynamic_ptr_cast(_object(path));
if (obj) {
obj->set_properties(properties);
return;
}
if (path.is_root()) {
is_graph = true;
}
if (is_graph) {
SPtr model(new GraphModel(uris(), path));
model->set_properties(properties);
add_object(model);
} else if (is_block) {
Iterator p = properties.find(_uris.lv2_prototype);
if (p == properties.end()) {
p = properties.find(_uris.ingen_prototype);
}
SPtr plug;
if (p->second.is_valid() && (p->second.type() == _uris.forge.URI ||
p->second.type() == _uris.forge.URID)) {
const Raul::URI uri(_uris.forge.str(p->second, false));
if (!(plug = _plugin(uri))) {
plug = SPtr(
new PluginModel(uris(),
uri,
Atom(),
Resource::Properties()));
add_plugin(plug);
}
SPtr bm(new BlockModel(uris(), plug, path));
bm->set_properties(properties);
add_object(bm);
} else {
_log.warn(fmt("Block %1% has no prototype\n") % path.c_str());
}
} else if (is_port) {
PortModel::Direction pdir = (is_output)
? PortModel::Direction::OUTPUT
: PortModel::Direction::INPUT;
uint32_t index = 0;
const Iterator i = properties.find(_uris.lv2_index);
if (i != properties.end() && i->second.type() == _uris.forge.Int) {
index = i->second.get();
}
SPtr p(new PortModel(uris(), path, index, pdir));
p->set_properties(properties);
add_object(p);
} else {
_log.warn(fmt("Ignoring %1% of unknown type\n") % path.c_str());
}
}
void
ClientStore::delta(const Raul::URI& uri,
const Resource::Properties& remove,
const Resource::Properties& add)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client delta " << uri << " {" << endl;
for (auto r : remove)
std::cerr << " - " << r.first
<< " " << _uris.forge.str(r.second)
<< "^^" << _uris.forge.str(_uris.forge.make_urid(r.second.type()), true) << endl;
for (auto a : add)
std::cerr << " + " << a.first
<< " " << _uris.forge.str(a.second)
<< "^^" << _uris.forge.str(_uris.forge.make_urid(a.second.type()), true) << endl;
std::cerr << "}" << endl;
#endif
if (uri == Raul::URI("ingen:/clients/this")) {
// Client property, which we don't store (yet?)
return;
}
if (!Node::uri_is_path(uri)) {
_log.error(fmt("Delta for unknown subject <%1%>\n")
% uri.c_str());
return;
}
const Raul::Path path(Node::uri_to_path(uri));
SPtr obj = _object(path);
if (obj) {
obj->remove_properties(remove);
obj->add_properties(add);
} else {
_log.warn(fmt("Failed to find object `%1%'\n")
% path.c_str());
}
}
void
ClientStore::set_property(const Raul::URI& subject_uri,
const Raul::URI& predicate,
const Atom& value)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client set " << subject_uri << " : "
<< predicate << " = " << _uris.forge.str(value) << std::endl;
#endif
if (subject_uri == Raul::URI("ingen:/engine")) {
_log.info(fmt("Engine property <%1%> = %2%\n")
% predicate.c_str() % _uris.forge.str(value));
return;
}
SPtr subject = _resource(subject_uri);
if (subject) {
if (predicate == _uris.ingen_activity) {
/* Activity is transient, trigger any live actions (like GUI
blinkenlights) but do not store the property. */
subject->on_property(predicate, value);
} else {
subject->set_property(predicate, value);
}
} else {
SPtr plugin = _plugin(subject_uri);
if (plugin) {
plugin->set_property(predicate, value);
} else if (predicate != _uris.ingen_activity) {
_log.warn(fmt("Property <%1%> for unknown object %2%\n")
% predicate.c_str() % subject_uri.c_str());
}
}
}
SPtr
ClientStore::connection_graph(const Raul::Path& tail_path,
const Raul::Path& head_path)
{
SPtr graph;
if (tail_path.parent() == head_path.parent())
graph = dynamic_ptr_cast(_object(tail_path.parent()));
if (!graph && tail_path.parent() == head_path.parent().parent())
graph = dynamic_ptr_cast(_object(tail_path.parent()));
if (!graph && tail_path.parent().parent() == head_path.parent())
graph = dynamic_ptr_cast(_object(head_path.parent()));
if (!graph)
graph = dynamic_ptr_cast(_object(tail_path.parent().parent()));
if (!graph)
_log.error(fmt("Unable to find graph for arc %1% => %2%\n")
% tail_path % head_path);
return graph;
}
bool
ClientStore::attempt_connection(const Raul::Path& tail_path,
const Raul::Path& head_path)
{
SPtr tail = dynamic_ptr_cast(_object(tail_path));
SPtr head = dynamic_ptr_cast(_object(head_path));
if (tail && head) {
SPtr graph = connection_graph(tail_path, head_path);
SPtr arc(new ArcModel(tail, head));
tail->connected_to(head);
head->connected_to(tail);
graph->add_arc(arc);
return true;
} else {
_log.warn(fmt("Failed to connect %1% => %2%\n")
% tail_path % head_path);
return false;
}
}
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);
}
void
ClientStore::disconnect(const Raul::Path& src_path,
const Raul::Path& dst_path)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client disconnect " << src_path << " => " << dst_path << std::endl;
#endif
SPtr tail = dynamic_ptr_cast(_object(src_path));
SPtr head = dynamic_ptr_cast(_object(dst_path));
if (tail)
tail->disconnected_from(head);
if (head)
head->disconnected_from(tail);
SPtr graph = connection_graph(src_path, dst_path);
if (graph)
graph->remove_arc(tail.get(), head.get());
}
void
ClientStore::disconnect_all(const Raul::Path& parent_graph,
const Raul::Path& path)
{
#ifdef INGEN_CLIENT_STORE_DUMP
std::cerr << "Client disconnect all " << path << " in " << parent_graph << std::endl;
#endif
SPtr graph = dynamic_ptr_cast(_object(parent_graph));
SPtr object = _object(path);
if (!graph || !object) {
_log.error(fmt("Bad disconnect all notification %1% in %2%\n")
% path % parent_graph);
return;
}
const GraphModel::Arcs arcs = graph->arcs();
for (auto a : arcs) {
SPtr arc = dynamic_ptr_cast(a.second);
if (arc->tail()->parent() == object
|| arc->head()->parent() == object
|| arc->tail()->path() == path
|| arc->head()->path() == path) {
arc->tail()->disconnected_from(arc->head());
arc->head()->disconnected_from(arc->tail());
graph->remove_arc(arc->tail().get(), arc->head().get());
}
}
}
} // namespace Client
} // namespace Ingen