/*
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 "UndoStack.hpp"
#include "ingen/URIMap.hpp"
#include "ingen/URIs.hpp"
#include "lv2/atom/atom.h"
#include "lv2/atom/util.h"
#include "lv2/patch/patch.h"
#include "lv2/urid/urid.h"
#include "serd/serd.h"
#include "sratom/sratom.hpp"
#include
#include
#include
#include
#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
namespace ingen {
namespace server {
int
UndoStack::start_entry()
{
if (_depth == 0) {
time_t now;
time(&now);
_stack.emplace_back(Entry(now));
}
return ++_depth;
}
bool
UndoStack::write(const LV2_Atom* msg, int32_t)
{
_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 auto* f = (const LV2_Atom_Object*)first;
const auto* s = (const LV2_Atom_Object*)second;
if (f->body.otype == _uris.patch_Set && f->body.otype == s->body.otype) {
const LV2_Atom* f_subject = nullptr;
const LV2_Atom* f_property = nullptr;
const LV2_Atom* s_subject = nullptr;
const LV2_Atom* s_property = nullptr;
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 {
explicit BlankIDs(char c='b') : c(c) {}
serd::Node get() {
snprintf(buf, sizeof(buf), "%c%u", c, n++);
return serd::make_blank(buf);
}
char buf[16]{};
unsigned n{0};
const char c{'b'};
};
struct ListContext {
explicit ListContext(BlankIDs& ids,
serd::StatementFlags flags,
const serd::Node& s,
const serd::Node& p)
: ids(ids)
, s(s)
, p(p)
, flags(flags | serd::StatementFlag::list_O)
{}
serd::Node start_node(serd::Writer& writer) {
const serd::Node node = ids.get();
writer.sink().write(flags, s, p, node);
return node;
}
void append(serd::Writer& writer,
serd::StatementFlags oflags,
const serd::Node& value)
{
// s p node
const serd::Node node = start_node(writer);
// node rdf:first value
p = serd::make_uri(NS_RDF "first");
flags = {};
writer.sink().write(flags | oflags, node, p, value);
end_node(writer, node);
}
void end_node(serd::Writer&, const serd::Node& node) {
// Prepare for next call: node rdf:rest ...
s = node;
p = serd::make_uri(NS_RDF "rest");
}
void end(serd::Writer& writer) {
const serd::Node nil = serd::make_uri(NS_RDF "nil");
writer.sink().write(flags, s, p, nil);
}
BlankIDs& ids;
serd::Node s;
serd::Node p;
serd::StatementFlags flags;
};
void
UndoStack::write_entry(sratom::Streamer& streamer,
serd::Writer& writer,
const serd::Node& subject,
const UndoStack::Entry& entry)
{
char time_str[24];
strftime(time_str, sizeof(time_str), "%FT%T", gmtime(&entry.time));
writer.sink().write({},
subject,
serd::make_uri(INGEN_NS "time"),
serd::make_string(time_str));
serd::Node p = serd::make_uri(INGEN_NS "events");
BlankIDs ids('e');
ListContext ctx(ids, {}, subject, p);
for (const LV2_Atom* atom : entry.events) {
const serd::Node node = ctx.start_node(writer);
p = serd::make_uri(NS_RDF "first");
ctx.flags = {};
streamer.write(writer.sink(), node, p, *atom);
ctx.end_node(writer, node);
}
ctx.end(writer);
}
void
UndoStack::save(std::ofstream& stream, const char* name)
{
serd::Env env;
env.set_prefix("atom", LV2_ATOM_PREFIX);
env.set_prefix("ingen", INGEN_NS);
env.set_prefix("patch", LV2_PATCH_PREFIX);
const serd::Node base = serd::make_uri("ingen:/");
serd::Writer writer(_world,
serd::Syntax::Turtle,
{},
env,
stream);
// Configure sratom to write directly to the writer (and thus the socket)
sratom::Streamer streamer{_world,
_map.urid_map_feature()->urid_map,
_map.urid_unmap_feature()->urid_unmap};
serd::Node s = serd::make_blank(name);
serd::Node p = serd::make_uri(INGEN_NS "entries");
BlankIDs ids('u');
ListContext ctx(ids, {}, s, p);
for (const Entry& e : _stack) {
const serd::Node entry = ids.get();
ctx.append(writer, serd::StatementFlag::anon_O, entry);
write_entry(streamer, writer, entry, e);
writer.sink().end(entry);
}
ctx.end(writer);
writer.finish();
}
} // namespace server
} // namespace ingen