summaryrefslogtreecommitdiffstats
path: root/src/server/UndoStack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/UndoStack.cpp')
-rw-r--r--src/server/UndoStack.cpp252
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