From c9fa963e71258c5adfb71cf1996cd1bcb33df0bb Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 26 Feb 2023 08:54:17 +0100 Subject: Start with copy of whiteboard --- storage.cpp | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 storage.cpp (limited to 'storage.cpp') 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 +#include +#include + +#include + +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(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(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(0); +} + +namespace { + uint64_t unixepoch() + { + const auto p1 = std::chrono::system_clock::now(); + return std::chrono::duration_cast(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(m_maxage)); + m_stmt_cleanup.bind(2, static_cast(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(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(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(0); +} + +std::tuple 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(0), m_stmt_getRow.getColumn(1), m_stmt_getRow.getColumn(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(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(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(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 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('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(unixepoch())); + m_stmt_touchDocument.bind(2, id); + if (!m_stmt_touchDocument.execute()) + throw std::runtime_error("Unable to touch document with id "s + id); +} + -- cgit v1.2.3