summaryrefslogtreecommitdiffhomepage
path: root/storage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'storage.cpp')
-rw-r--r--storage.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/storage.cpp b/storage.cpp
new file mode 100644
index 0000000..92f274f
--- /dev/null
+++ b/storage.cpp
@@ -0,0 +1,235 @@
+#include "storage.h"
+
+#include "config.h"
+
+#include <chrono>
+#include <iostream>
+#include <random>
+
+#include <SQLiteCpp/SQLiteCpp.h>
+
+using namespace std::string_literals;
+
+Storage::Storage(const Config& config):
+ m_db(config.getDataPath() + "/whiteboard.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE),
+ m_maxage(config.getMaxage()),
+
+ // Note about VARCHAR(N): "SQLite does not impose any length restrictions" - handled elsewhere in application
+ m_stmt_create(m_db, "CREATE TABLE IF NOT EXISTS documents (id VARCHAR(16) PRIMARY KEY, value BLOB, rev INTEGER, cursorpos INTEGER, timestamp BIGINT)"),
+ m_stmt_getNumberOfDocuments(m_db, "SELECT COUNT(*) FROM documents"),
+ m_stmt_cleanup(m_db, "DELETE FROM documents WHERE timestamp + ? < ?"),
+ m_stmt_vacuum(m_db, "VACUUM"),
+ m_stmt_exists(m_db, "SELECT id FROM documents WHERE id = ?"),
+ m_stmt_getDocument(m_db, "SELECT value FROM documents WHERE id = ?"),
+ m_stmt_getRevision(m_db, "SELECT rev FROM documents WHERE id = ?"),
+ m_stmt_getCursorPos(m_db, "SELECT cursorpos FROM documents WHERE id = ?"),
+ m_stmt_getRow(m_db, "SELECT value, rev, cursorpos FROM documents WHERE id = ?"),
+ m_stmt_setDocument(m_db, "UPDATE documents SET value = ?, timestamp = ?, rev = rev + 1 WHERE id = ?"),
+ m_stmt_setDocument_new(m_db, "INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, ?)"),
+ m_stmt_setRevision(m_db, "UPDATE documents SET rev = ? WHERE id = ?"),
+ m_stmt_setCursorPos(m_db, "UPDATE documents SET cursorpos = ? WHERE id = ?"),
+ m_stmt_setRow(m_db, "INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, ?)"),
+ m_stmt_getDbSizeGross(m_db, "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"),
+ m_stmt_getDbSizeNet(m_db, "SELECT (page_count - freelist_count) * page_size as size FROM pragma_page_count(), pragma_freelist_count(), pragma_page_size()"),
+ m_stmt_touchDocument(m_db, "UPDATE documents SET timestamp = ? WHERE id = ?")
+{
+ CompiledSQL::Guard g{m_stmt_create};
+ m_stmt_create.execute();
+}
+
+Storage::~Storage()
+{
+}
+
+uint64_t Storage::getNumberOfDocuments()
+{
+ CompiledSQL::Guard g{m_stmt_getNumberOfDocuments};
+ if (!m_stmt_getNumberOfDocuments.execute())
+ throw std::runtime_error("Count not possible");
+
+ return m_stmt_getNumberOfDocuments.getColumn<int64_t>(0);
+}
+
+uint64_t Storage::dbsize_gross()
+{
+ CompiledSQL::Guard g{m_stmt_getDbSizeGross};
+ if (!m_stmt_getDbSizeGross.execute())
+ throw std::runtime_error("DB size count (gross) not possible");
+
+ return m_stmt_getDbSizeGross.getColumn<int64_t>(0);
+}
+
+uint64_t Storage::dbsize_net()
+{
+ CompiledSQL::Guard g{m_stmt_getDbSizeNet};
+ if (!m_stmt_getDbSizeNet.execute())
+ throw std::runtime_error("DB size count (net) not possible");
+
+ return m_stmt_getDbSizeNet.getColumn<int64_t>(0);
+}
+
+namespace {
+ uint64_t unixepoch()
+ {
+ const auto p1 = std::chrono::system_clock::now();
+ return std::chrono::duration_cast<std::chrono::seconds>(p1.time_since_epoch()).count();
+ }
+} // namespace
+
+void Storage::cleanup()
+{
+ if (m_maxage != 0) {
+ CompiledSQL::Guard g{m_stmt_cleanup};
+ m_stmt_cleanup.bind(1, static_cast<int64_t>(m_maxage));
+ m_stmt_cleanup.bind(2, static_cast<int64_t>(unixepoch()));
+ m_stmt_cleanup.execute();
+ }
+
+ CompiledSQL::Guard g{m_stmt_vacuum};
+ m_stmt_vacuum.execute();
+}
+
+bool Storage::exists(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_exists};
+ m_stmt_exists.bind(1, id);
+
+ return m_stmt_exists.execute();
+}
+
+std::string Storage::getDocument(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_getDocument};
+ m_stmt_getDocument.bind(1, id);
+
+ if (!m_stmt_getDocument.execute())
+ throw std::runtime_error("id "s + id + " not found"s);
+
+ return m_stmt_getDocument.getColumn<std::string>(0);
+}
+
+int Storage::getRevision(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_getRevision};
+ m_stmt_getRevision.bind(1, id);
+
+ if (!m_stmt_getRevision.execute())
+ throw std::runtime_error("id "s + id + " not found"s);
+
+ return m_stmt_getRevision.getColumn<int>(0);
+}
+
+int Storage::getCursorPos(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_getCursorPos};
+ m_stmt_getCursorPos.bind(1, id);
+
+ if (!m_stmt_getCursorPos.execute())
+ throw std::runtime_error("id "s + id + " not found"s);
+
+ return m_stmt_getCursorPos.getColumn<int>(0);
+}
+
+std::tuple<std::string, int, int> Storage::getRow(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_getRow};
+ m_stmt_getRow.bind(1, id);
+
+ if (!m_stmt_getRow.execute())
+ throw std::runtime_error("id "s + id + " not found"s);
+
+ return {m_stmt_getRow.getColumn<std::string>(0), m_stmt_getRow.getColumn<int>(1), m_stmt_getRow.getColumn<int>(2)};
+}
+
+void Storage::setDocument(const std::string& id, const std::string& document)
+{
+ CompiledSQL::Guard g{m_stmt_setDocument};
+ m_stmt_setDocument.bind(1, document);
+ m_stmt_setDocument.bind(2, static_cast<int64_t>(unixepoch()));
+ m_stmt_setDocument.bind(3, id);
+
+ if (!m_stmt_setDocument.execute()) {
+ CompiledSQL::Guard g{m_stmt_setDocument_new};
+ m_stmt_setDocument_new.bind(1, id);
+ m_stmt_setDocument_new.bind(2, document);
+ m_stmt_setDocument_new.bind(3, 0);
+ m_stmt_setDocument_new.bind(4, 0);
+ m_stmt_setDocument_new.bind(5, static_cast<int64_t>(unixepoch()));
+ if (!m_stmt_setDocument_new.execute())
+ throw std::runtime_error("Unable to create document with id "s + id);
+ }
+}
+
+void Storage::setRevision(const std::string& id, int rev)
+{
+ CompiledSQL::Guard g{m_stmt_setRevision};
+ m_stmt_setRevision.bind(1, rev);
+ m_stmt_setRevision.bind(2, id);
+
+ if (!m_stmt_setRevision.execute())
+ throw std::runtime_error("Unable to set revision for id "s + id);
+}
+
+void Storage::setCursorPos(const std::string& id, int cursorPos)
+{
+ CompiledSQL::Guard g{m_stmt_setCursorPos};
+ m_stmt_setCursorPos.bind(1, cursorPos);
+ m_stmt_setCursorPos.bind(2, id);
+
+ if (!m_stmt_setCursorPos.execute())
+ throw std::runtime_error("Unable to set cursor position for id "s + id);
+}
+
+void Storage::setRow(const std::string& id, const std::string& document, int rev, int cursorPos)
+{
+ CompiledSQL::Guard g{m_stmt_setRow};
+ m_stmt_setRow.bind(1, id);
+ m_stmt_setRow.bind(2, document);
+ m_stmt_setRow.bind(3, rev);
+ m_stmt_setRow.bind(4, cursorPos);
+ m_stmt_setRow.bind(5, static_cast<int64_t>(unixepoch()));
+ if (!m_stmt_setRow.execute())
+ throw std::runtime_error("Unable to insert row with id "s + id);
+}
+
+uint32_t checksum32(const std::string& s)
+{
+ uint32_t result{0};
+ for (unsigned int i = 0; i < s.size(); i++) {
+ result = ((result >> 1) | ((result & 1) << 31)) ^ (s[i] & 0xFF);
+ }
+ return result & 0x7FFFFFFF;
+}
+
+std::string Storage::generate_id()
+{
+ static std::random_device r;
+ static std::default_random_engine e1(r());
+ static std::uniform_int_distribution<int> uniform_dist(0, 35);
+
+ // limit tries
+ for (int j = 0; j < 100000; j++) {
+ std::string result;
+ for (int i = 0; i < 6; i++) {
+ char c{static_cast<char>('0' + uniform_dist(e1))};
+ if (c > '9')
+ c = c - '9' - 1 + 'a';
+ result.push_back(c);
+ }
+
+ if (!exists(result))
+ return result;
+ }
+
+ return "endofcodes";
+}
+
+void Storage::touchDocument(const std::string& id)
+{
+ CompiledSQL::Guard g{m_stmt_touchDocument};
+ m_stmt_touchDocument.bind(1, static_cast<int64_t>(unixepoch()));
+ m_stmt_touchDocument.bind(2, id);
+ if (!m_stmt_touchDocument.execute())
+ throw std::runtime_error("Unable to touch document with id "s + id);
+}
+