diff options
Diffstat (limited to 'src/server/UndoStack.cpp')
-rw-r--r-- | src/server/UndoStack.cpp | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/server/UndoStack.cpp b/src/server/UndoStack.cpp new file mode 100644 index 00000000..f8a7f37b --- /dev/null +++ b/src/server/UndoStack.cpp @@ -0,0 +1,252 @@ +/* + 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 <ctime> +#include <iostream> + +#include "ingen/URIMap.hpp" +#include "ingen/URIs.hpp" +#include "lv2/lv2plug.in/ns/ext/atom/util.h" +#include "lv2/lv2plug.in/ns/ext/patch/patch.h" +#include "serd/serd.h" +#include "sratom/sratom.h" + +#include "UndoStack.hpp" + +#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#define USTR(s) ((const uint8_t*)(s)) + +namespace Ingen { +namespace Server { + +void +UndoStack::start_entry() +{ + if (_depth == 0) { + time_t now; + time(&now); + _stack.push_back(Entry(now)); + } + ++_depth; +} + +bool +UndoStack::write(const LV2_Atom* msg) +{ + _stack.back().push_event(msg); + return true; +} + +bool +UndoStack::ignore_later_event(const LV2_Atom* first, + const LV2_Atom* second) const +{ + if (first->type != _uris.atom_Object || first->type != second->type) { + return false; + } + + const LV2_Atom_Object* f = (const LV2_Atom_Object*)first; + const LV2_Atom_Object* s = (const LV2_Atom_Object*)second; + if (f->body.otype == _uris.patch_Set && f->body.otype == s->body.otype) { + const LV2_Atom* f_subject = NULL; + const LV2_Atom* f_property = NULL; + const LV2_Atom* s_subject = NULL; + const LV2_Atom* s_property = NULL; + lv2_atom_object_get(f, + (LV2_URID)_uris.patch_subject, &f_subject, + (LV2_URID)_uris.patch_property, &f_property, + 0); + lv2_atom_object_get(s, + (LV2_URID)_uris.patch_subject, &s_subject, + (LV2_URID)_uris.patch_property, &s_property, + 0); + return (lv2_atom_equals(f_subject, s_subject) && + lv2_atom_equals(f_property, s_property)); + } + + return false; +} + +void +UndoStack::finish_entry() +{ + if (--_depth > 0) { + return; + } else if (_stack.back().events.empty()) { + // Disregard empty entry + _stack.pop_back(); + } else if (_stack.size() > 1 && _stack.back().events.size() == 1) { + // This entry and the previous one have one event, attempt to merge + auto i = _stack.rbegin(); + ++i; + if (i->events.size() == 1) { + if (ignore_later_event(i->events[0], _stack.back().events[0])) { + _stack.pop_back(); + } + } + } +} + +UndoStack::Entry +UndoStack::pop() +{ + Entry top; + if (!_stack.empty()) { + top = _stack.back(); + _stack.pop_back(); + } + return top; +} + +struct BlankIDs { + BlankIDs(char c='b') : n(0), c(c) {} + + SerdNode get() { + snprintf(buf, sizeof(buf), "%c%u", c, n++); + return serd_node_from_string(SERD_BLANK, USTR(buf)); + } + + char buf[16]; + unsigned n; + const char c; +}; + +struct ListContext { + explicit ListContext(BlankIDs& ids, unsigned flags, const SerdNode* s, const SerdNode* p) + : ids(ids) + , s(*s) + , p(*p) + , flags(flags | SERD_LIST_O_BEGIN) + {} + + SerdNode start_node(SerdWriter* writer) { + const SerdNode node = ids.get(); + serd_writer_write_statement(writer, flags, NULL, &s, &p, &node, NULL, NULL); + return node; + } + + void append(SerdWriter* writer, unsigned oflags, const SerdNode* value) { + // s p node + const SerdNode node = start_node(writer); + + // node rdf:first value + p = serd_node_from_string(SERD_URI, NS_RDF "first"); + flags = SERD_LIST_CONT; + serd_writer_write_statement(writer, flags|oflags, NULL, &node, &p, value, NULL, NULL); + + end_node(writer, &node); + } + + void end_node(SerdWriter* writer, const SerdNode* node) { + // Prepare for next call: node rdf:rest ... + s = *node; + p = serd_node_from_string(SERD_URI, NS_RDF "rest"); + } + + void end(SerdWriter* writer) { + const SerdNode nil = serd_node_from_string(SERD_URI, NS_RDF "nil"); + serd_writer_write_statement(writer, flags, NULL, &s, &p, &nil, NULL, NULL); + } + + BlankIDs& ids; + SerdNode s; + SerdNode p; + unsigned flags; +}; + +void +UndoStack::write_entry(Sratom* sratom, + SerdWriter* writer, + const SerdNode* const subject, + const UndoStack::Entry& entry) +{ + char time_str[24]; + strftime(time_str, sizeof(time_str), "%FT%T", gmtime(&entry.time)); + + // entry rdf:type ingen:UndoEntry + SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "time")); + SerdNode o = serd_node_from_string(SERD_LITERAL, USTR(time_str)); + serd_writer_write_statement(writer, SERD_ANON_CONT, NULL, subject, &p, &o, NULL, NULL); + + p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "events")); + + BlankIDs ids('e'); + ListContext ctx(ids, SERD_ANON_CONT, subject, &p); + + for (const LV2_Atom* atom : entry.events) { + const SerdNode node = ctx.start_node(writer); + + p = serd_node_from_string(SERD_URI, NS_RDF "first"); + ctx.flags = SERD_LIST_CONT; + sratom_write(sratom, &_map.urid_unmap_feature()->urid_unmap, SERD_LIST_CONT, + &node, &p, + atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); + + ctx.end_node(writer, &node); + } + + ctx.end(writer); +} + +void +UndoStack::save(FILE* stream, const char* name) +{ + SerdEnv* env = serd_env_new(NULL); + serd_env_set_prefix_from_strings(env, USTR("atom"), USTR(LV2_ATOM_PREFIX)); + serd_env_set_prefix_from_strings(env, USTR("ingen"), USTR(INGEN_NS)); + serd_env_set_prefix_from_strings(env, USTR("patch"), USTR(LV2_PATCH_PREFIX)); + + const SerdNode base = serd_node_from_string(SERD_URI, USTR("ingen:/")); + SerdURI base_uri; + serd_uri_parse(base.buf, &base_uri); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), + env, + &base_uri, + serd_file_sink, + stream); + + // Configure sratom to write directly to the writer (and thus the socket) + Sratom* sratom = sratom_new(&_map.urid_map_feature()->urid_map); + sratom_set_sink(sratom, + (const char*)base.buf, + (SerdStatementSink)serd_writer_write_statement, + (SerdEndSink)serd_writer_end_anon, + writer); + + SerdNode s = serd_node_from_string(SERD_BLANK, (const uint8_t*)name); + SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "entries")); + + BlankIDs ids('u'); + ListContext ctx(ids, 0, &s, &p); + for (const Entry& e : _stack) { + const SerdNode entry = ids.get(); + ctx.append(writer, SERD_ANON_O_BEGIN, &entry); + write_entry(sratom, writer, &entry, e); + serd_writer_end_anon(writer, &entry); + } + ctx.end(writer); + + sratom_free(sratom); + serd_writer_finish(writer); + serd_writer_free(writer); +} + +} // namespace Server +} // namespace Ingen |