#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); }