/* This file is part of Ingen. Copyright 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 #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 { int UndoStack::start_entry() { if (_depth == 0) { time_t now; time(&now); _stack.push_back(Entry(now)); } return ++_depth; } bool UndoStack::write(const LV2_Atom* msg, int32_t default_id) { _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; } int UndoStack::finish_entry() { if (--_depth > 0) { return _depth; } 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(); } } } return _depth; } 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