From e04f7db7c287c3da12f3293b1c26e290dce7761c Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 14 Apr 2019 23:37:02 +0300 Subject: [PATCH] duck taped annoying scroll problem, started with local archive --- core/archive.cpp | 71 ++ core/archive.h | 57 ++ core/lmdb++.h | 1913 +++++++++++++++++++++++++++++++++++++++++++ main.cpp | 2 + ui/conversation.cpp | 32 +- ui/conversation.h | 2 + 6 files changed, 2063 insertions(+), 14 deletions(-) create mode 100644 core/archive.cpp create mode 100644 core/archive.h create mode 100644 core/lmdb++.h diff --git a/core/archive.cpp b/core/archive.cpp new file mode 100644 index 0000000..c468e9e --- /dev/null +++ b/core/archive.cpp @@ -0,0 +1,71 @@ +/* + * + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "archive.h" +#include +#include +#include +#include + +Core::Archive::Archive(const QString& p_jid, QObject* parent): + QObject(parent), + jid(p_jid), + opened(false), + environment(lmdb::env::create()), + dbi(0) +{ + +} + +Core::Archive::~Archive() +{ +} + +void Core::Archive::open(const QString& account) +{ + if (!opened) { + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + account; + int state1 = mkdir(path.toStdString().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state1 != 0 && errno != EEXIST) { + qDebug() << "Failed to create account " << account << " database folder"; + throw 1; + } + + path += "/" + jid; + + int state2 = mkdir(path.toStdString().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state2 != 0 && errno != EEXIST) { + qDebug() << "Failed to create " << jid.toStdString().c_str() << " database folder in account" << account; + throw 1; + } + + environment.set_mapsize(1UL * 1024UL * 1024UL * 1024UL); + environment.set_max_dbs(10); + environment.open(path.toStdString().c_str(), 0, 0664); + + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi = lmdb::dbi::open(wTrans, "main", MDB_CREATE); + wTrans.commit(); + } +} + +QString Core::Archive::addElement(const Shared::Message& message) +{ + +} diff --git a/core/archive.h b/core/archive.h new file mode 100644 index 0000000..bae4214 --- /dev/null +++ b/core/archive.h @@ -0,0 +1,57 @@ +/* + * + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CORE_ARCHIVE_H +#define CORE_ARCHIVE_H + +#include +#include "../global.h" +#include "lmdb++.h" + +namespace Core { + +class Archive : public QObject +{ + Q_OBJECT +public: + Archive(const QString& jid, QObject* parent); + ~Archive(); + + void open(const QString& account); + + QString addElement(const Shared::Message& message); + Shared::Message getElement(const QString& id) const; + Shared::Message oldest() const; + Shared::Message newest() const; + void removeElement(const QString& id); + void clear(); + void modifyElement(const QString& id, const Shared::Message& newValue); + unsigned int size() const; + +public: + const QString jid; + +private: + bool opened; + lmdb::env environment; + lmdb::dbi dbi; +}; + +} + +#endif // CORE_ARCHIVE_H diff --git a/core/lmdb++.h b/core/lmdb++.h new file mode 100644 index 0000000..ab75f8c --- /dev/null +++ b/core/lmdb++.h @@ -0,0 +1,1913 @@ +/* This is free and unencumbered software released into the public domain. */ + +#ifndef LMDBXX_H +#define LMDBXX_H + +/** + * - C++11 wrapper for LMDB. + * + * @author Arto Bendiken + * @see https://sourceforge.net/projects/lmdbxx/ + */ + +#ifndef __cplusplus +#error " requires a C++ compiler" +#endif + +#if __cplusplus < 201103L +#if !defined(_MSC_VER) || _MSC_VER < 1900 +#error " requires a C++11 compiler (CXXFLAGS='-std=c++11')" +#endif // _MSC_VER check +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include /* for MDB_*, mdb_*() */ + +#ifdef LMDBXX_DEBUG +#include /* for assert() */ +#endif +#include /* for std::size_t */ +#include /* for std::snprintf() */ +#include /* for std::strlen() */ +#include /* for std::runtime_error */ +#include /* for std::string */ +#include /* for std::is_pod<> */ + +namespace lmdb { + using mode = mdb_mode_t; +} + +//////////////////////////////////////////////////////////////////////////////// +/* Error Handling */ + +namespace lmdb { + class error; + class logic_error; + class fatal_error; + class runtime_error; + class key_exist_error; + class not_found_error; + class corrupted_error; + class panic_error; + class version_mismatch_error; + class map_full_error; + class bad_dbi_error; +} + +/** + * Base class for LMDB exception conditions. + * + * @see http://symas.com/mdb/doc/group__errors.html + */ +class lmdb::error : public std::runtime_error { +protected: + const int _code; + +public: + /** + * Throws an error based on the given LMDB return code. + */ + [[noreturn]] static inline void raise(const char* origin, int rc); + + /** + * Constructor. + */ + error(const char* const origin, + const int rc) noexcept + : runtime_error{origin}, + _code{rc} {} + + /** + * Returns the underlying LMDB error code. + */ + int code() const noexcept { + return _code; + } + + /** + * Returns the origin of the LMDB error. + */ + const char* origin() const noexcept { + return runtime_error::what(); + } + + /** + * Returns the underlying LMDB error code. + */ + virtual const char* what() const noexcept { + static thread_local char buffer[1024]; + std::snprintf(buffer, sizeof(buffer), + "%s: %s", origin(), ::mdb_strerror(code())); + return buffer; + } +}; + +/** + * Base class for logic error conditions. + */ +class lmdb::logic_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Base class for fatal error conditions. + */ +class lmdb::fatal_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Base class for runtime error conditions. + */ +class lmdb::runtime_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Exception class for `MDB_KEYEXIST` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga05dc5bbcc7da81a7345bd8676e8e0e3b + */ +class lmdb::key_exist_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_NOTFOUND` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gabeb52e4c4be21b329e31c4add1b71926 + */ +class lmdb::not_found_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_CORRUPTED` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gaf8148bf1b85f58e264e57194bafb03ef + */ +class lmdb::corrupted_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_PANIC` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gae37b9aedcb3767faba3de8c1cf6d3473 + */ +class lmdb::panic_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_VERSION_MISMATCH` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga909b2db047fa90fb0d37a78f86a6f99b + */ +class lmdb::version_mismatch_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_MAP_FULL` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga0a83370402a060c9175100d4bbfb9f25 + */ +class lmdb::map_full_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_BAD_DBI` errors. + * + * @since 0.9.14 (2014/09/20) + * @see http://symas.com/mdb/doc/group__errors.html#gab4c82e050391b60a18a5df08d22a7083 + */ +class lmdb::bad_dbi_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +inline void +lmdb::error::raise(const char* const origin, + const int rc) { + switch (rc) { + case MDB_KEYEXIST: throw key_exist_error{origin, rc}; + case MDB_NOTFOUND: throw not_found_error{origin, rc}; + case MDB_CORRUPTED: throw corrupted_error{origin, rc}; + case MDB_PANIC: throw panic_error{origin, rc}; + case MDB_VERSION_MISMATCH: throw version_mismatch_error{origin, rc}; + case MDB_MAP_FULL: throw map_full_error{origin, rc}; +#ifdef MDB_BAD_DBI + case MDB_BAD_DBI: throw bad_dbi_error{origin, rc}; +#endif + default: throw lmdb::runtime_error{origin, rc}; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Metadata */ + +namespace lmdb { + // TODO: mdb_version() + // TODO: mdb_strerror() +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Environment */ + +namespace lmdb { + static inline void env_create(MDB_env** env); + static inline void env_open(MDB_env* env, + const char* path, unsigned int flags, mode mode); +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + static inline void env_copy(MDB_env* env, const char* path, unsigned int flags); + static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd, unsigned int flags); +#else + static inline void env_copy(MDB_env* env, const char* path); + static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd); +#endif + static inline void env_stat(MDB_env* env, MDB_stat* stat); + static inline void env_info(MDB_env* env, MDB_envinfo* stat); + static inline void env_sync(MDB_env* env, bool force); + static inline void env_close(MDB_env* env) noexcept; + static inline void env_set_flags(MDB_env* env, unsigned int flags, bool onoff); + static inline void env_get_flags(MDB_env* env, unsigned int* flags); + static inline void env_get_path(MDB_env* env, const char** path); + static inline void env_get_fd(MDB_env* env, mdb_filehandle_t* fd); + static inline void env_set_mapsize(MDB_env* env, std::size_t size); + static inline void env_set_max_readers(MDB_env* env, unsigned int count); + static inline void env_get_max_readers(MDB_env* env, unsigned int* count); + static inline void env_set_max_dbs(MDB_env* env, MDB_dbi count); + static inline unsigned int env_get_max_keysize(MDB_env* env); +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) + static inline void env_set_userctx(MDB_env* env, void* ctx); + static inline void* env_get_userctx(MDB_env* env); +#endif + // TODO: mdb_env_set_assert() + // TODO: mdb_reader_list() + // TODO: mdb_reader_check() +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaad6be3d8dcd4ea01f8df436f41d158d4 + */ +static inline void +lmdb::env_create(MDB_env** env) { + const int rc = ::mdb_env_create(env); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_create", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340 + */ +static inline void +lmdb::env_open(MDB_env* const env, + const char* const path, + const unsigned int flags, + const mode mode) { + const int rc = ::mdb_env_open(env, path, flags, mode); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_open", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga3bf50d7793b36aaddf6b481a44e24244 + * @see http://symas.com/mdb/doc/group__mdb.html#ga5d51d6130325f7353db0955dbedbc378 + */ +static inline void +lmdb::env_copy(MDB_env* const env, +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + const char* const path, + const unsigned int flags = 0) { + const int rc = ::mdb_env_copy2(env, path, flags); +#else + const char* const path) { + const int rc = ::mdb_env_copy(env, path); +#endif + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_copy2", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga5040d0de1f14000fa01fc0b522ff1f86 + * @see http://symas.com/mdb/doc/group__mdb.html#ga470b0bcc64ac417de5de5930f20b1a28 + */ +static inline void +lmdb::env_copy_fd(MDB_env* const env, +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + const mdb_filehandle_t fd, + const unsigned int flags = 0) { + const int rc = ::mdb_env_copyfd2(env, fd, flags); +#else + const mdb_filehandle_t fd) { + const int rc = ::mdb_env_copyfd(env, fd); +#endif + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_copyfd2", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaf881dca452050efbd434cd16e4bae255 + */ +static inline void +lmdb::env_stat(MDB_env* const env, + MDB_stat* const stat) { + const int rc = ::mdb_env_stat(env, stat); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_stat", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga18769362c7e7d6cf91889a028a5c5947 + */ +static inline void +lmdb::env_info(MDB_env* const env, + MDB_envinfo* const stat) { + const int rc = ::mdb_env_info(env, stat); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_info", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga85e61f05aa68b520cc6c3b981dba5037 + */ +static inline void +lmdb::env_sync(MDB_env* const env, + const bool force = true) { + const int rc = ::mdb_env_sync(env, force); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_sync", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga4366c43ada8874588b6a62fbda2d1e95 + */ +static inline void +lmdb::env_close(MDB_env* const env) noexcept { + ::mdb_env_close(env); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445 + */ +static inline void +lmdb::env_set_flags(MDB_env* const env, + const unsigned int flags, + const bool onoff = true) { + const int rc = ::mdb_env_set_flags(env, flags, onoff ? 1 : 0); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_flags", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga2733aefc6f50beb49dd0c6eb19b067d9 + */ +static inline void +lmdb::env_get_flags(MDB_env* const env, + unsigned int* const flags) { + const int rc = ::mdb_env_get_flags(env, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_flags", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac699fdd8c4f8013577cb933fb6a757fe + */ +static inline void +lmdb::env_get_path(MDB_env* const env, + const char** path) { + const int rc = ::mdb_env_get_path(env, path); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_path", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaf1570e7c0e5a5d860fef1032cec7d5f2 + */ +static inline void +lmdb::env_get_fd(MDB_env* const env, + mdb_filehandle_t* const fd) { + const int rc = ::mdb_env_get_fd(env, fd); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_fd", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5 + */ +static inline void +lmdb::env_set_mapsize(MDB_env* const env, + const std::size_t size) { + const int rc = ::mdb_env_set_mapsize(env, size); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_mapsize", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gae687966c24b790630be2a41573fe40e2 + */ +static inline void +lmdb::env_set_max_readers(MDB_env* const env, + const unsigned int count) { + const int rc = ::mdb_env_set_maxreaders(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_maxreaders", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga70e143cf11760d869f754c9c9956e6cc + */ +static inline void +lmdb::env_get_max_readers(MDB_env* const env, + unsigned int* const count) { + const int rc = ::mdb_env_get_maxreaders(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_maxreaders", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaa2fc2f1f37cb1115e733b62cab2fcdbc + */ +static inline void +lmdb::env_set_max_dbs(MDB_env* const env, + const MDB_dbi count) { + const int rc = ::mdb_env_set_maxdbs(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_maxdbs", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gaaf0be004f33828bf2fb09d77eb3cef94 + */ +static inline unsigned int +lmdb::env_get_max_keysize(MDB_env* const env) { + const int rc = ::mdb_env_get_maxkeysize(env); +#ifdef LMDBXX_DEBUG + assert(rc >= 0); +#endif + return static_cast(rc); +} + +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) +/** + * @throws lmdb::error on failure + * @since 0.9.11 (2014/01/15) + * @see http://symas.com/mdb/doc/group__mdb.html#gaf2fe09eb9c96eeb915a76bf713eecc46 + */ +static inline void +lmdb::env_set_userctx(MDB_env* const env, + void* const ctx) { + const int rc = ::mdb_env_set_userctx(env, ctx); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_userctx", rc); + } +} +#endif + +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) +/** + * @since 0.9.11 (2014/01/15) + * @see http://symas.com/mdb/doc/group__mdb.html#ga45df6a4fb150cda2316b5ae224ba52f1 + */ +static inline void* +lmdb::env_get_userctx(MDB_env* const env) { + return ::mdb_env_get_userctx(env); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Transactions */ + +namespace lmdb { + static inline void txn_begin( + MDB_env* env, MDB_txn* parent, unsigned int flags, MDB_txn** txn); + static inline MDB_env* txn_env(MDB_txn* txn) noexcept; +#ifdef LMDBXX_TXN_ID + static inline std::size_t txn_id(MDB_txn* txn) noexcept; +#endif + static inline void txn_commit(MDB_txn* txn); + static inline void txn_abort(MDB_txn* txn) noexcept; + static inline void txn_reset(MDB_txn* txn) noexcept; + static inline void txn_renew(MDB_txn* txn); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920 + */ +static inline void +lmdb::txn_begin(MDB_env* const env, + MDB_txn* const parent, + const unsigned int flags, + MDB_txn** txn) { + const int rc = ::mdb_txn_begin(env, parent, flags, txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_begin", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gaeb17735b8aaa2938a78a45cab85c06a0 + */ +static inline MDB_env* +lmdb::txn_env(MDB_txn* const txn) noexcept { + return ::mdb_txn_env(txn); +} + +#ifdef LMDBXX_TXN_ID +/** + * @note Only available in HEAD, not yet in any 0.9.x release (as of 0.9.16). + */ +static inline std::size_t +lmdb::txn_id(MDB_txn* const txn) noexcept { + return ::mdb_txn_id(txn); +} +#endif + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga846fbd6f46105617ac9f4d76476f6597 + */ +static inline void +lmdb::txn_commit(MDB_txn* const txn) { + const int rc = ::mdb_txn_commit(txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_commit", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga73a5938ae4c3239ee11efa07eb22b882 + */ +static inline void +lmdb::txn_abort(MDB_txn* const txn) noexcept { + ::mdb_txn_abort(txn); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga02b06706f8a66249769503c4e88c56cd + */ +static inline void +lmdb::txn_reset(MDB_txn* const txn) noexcept { + ::mdb_txn_reset(txn); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga6c6f917959517ede1c504cf7c720ce6d + */ +static inline void +lmdb::txn_renew(MDB_txn* const txn) { + const int rc = ::mdb_txn_renew(txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_renew", rc); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Databases */ + +namespace lmdb { + static inline void dbi_open( + MDB_txn* txn, const char* name, unsigned int flags, MDB_dbi* dbi); + static inline void dbi_stat(MDB_txn* txn, MDB_dbi dbi, MDB_stat* stat); + static inline void dbi_flags(MDB_txn* txn, MDB_dbi dbi, unsigned int* flags); + static inline void dbi_close(MDB_env* env, MDB_dbi dbi) noexcept; + static inline void dbi_drop(MDB_txn* txn, MDB_dbi dbi, bool del); + static inline void dbi_set_compare(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp); + static inline void dbi_set_dupsort(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp); + static inline void dbi_set_relfunc(MDB_txn* txn, MDB_dbi dbi, MDB_rel_func* rel); + static inline void dbi_set_relctx(MDB_txn* txn, MDB_dbi dbi, void* ctx); + static inline bool dbi_get(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data); + static inline bool dbi_put(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data, unsigned int flags); + static inline bool dbi_del(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, const MDB_val* data); + // TODO: mdb_cmp() + // TODO: mdb_dcmp() +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a + */ +static inline void +lmdb::dbi_open(MDB_txn* const txn, + const char* const name, + const unsigned int flags, + MDB_dbi* const dbi) { + const int rc = ::mdb_dbi_open(txn, name, flags, dbi); + if (rc != MDB_SUCCESS) { + error::raise("mdb_dbi_open", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gae6c1069febe94299769dbdd032fadef6 + */ +static inline void +lmdb::dbi_stat(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_stat* const result) { + const int rc = ::mdb_stat(txn, dbi, result); + if (rc != MDB_SUCCESS) { + error::raise("mdb_stat", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga95ba4cb721035478a8705e57b91ae4d4 + */ +static inline void +lmdb::dbi_flags(MDB_txn* const txn, + const MDB_dbi dbi, + unsigned int* const flags) { + const int rc = ::mdb_dbi_flags(txn, dbi, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_dbi_flags", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga52dd98d0c542378370cd6b712ff961b5 + */ +static inline void +lmdb::dbi_close(MDB_env* const env, + const MDB_dbi dbi) noexcept { + ::mdb_dbi_close(env, dbi); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gab966fab3840fc54a6571dfb32b00f2db + */ +static inline void +lmdb::dbi_drop(MDB_txn* const txn, + const MDB_dbi dbi, + const bool del = false) { + const int rc = ::mdb_drop(txn, dbi, del ? 1 : 0); + if (rc != MDB_SUCCESS) { + error::raise("mdb_drop", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga68e47ffcf72eceec553c72b1784ee0fe + */ +static inline void +lmdb::dbi_set_compare(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cmp_func* const cmp = nullptr) { + const int rc = ::mdb_set_compare(txn, dbi, cmp); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_compare", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gacef4ec3dab0bbd9bc978b73c19c879ae + */ +static inline void +lmdb::dbi_set_dupsort(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cmp_func* const cmp = nullptr) { + const int rc = ::mdb_set_dupsort(txn, dbi, cmp); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_dupsort", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga697d82c7afe79f142207ad5adcdebfeb + */ +static inline void +lmdb::dbi_set_relfunc(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_rel_func* const rel) { + const int rc = ::mdb_set_relfunc(txn, dbi, rel); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_relfunc", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga7c34246308cee01724a1839a8f5cc594 + */ +static inline void +lmdb::dbi_set_relctx(MDB_txn* const txn, + const MDB_dbi dbi, + void* const ctx) { + const int rc = ::mdb_set_relctx(txn, dbi, ctx); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_relctx", rc); + } +} + +/** + * @retval true if the key/value pair was retrieved + * @retval false if the key wasn't found + * @see http://symas.com/mdb/doc/group__mdb.html#ga8bf10cd91d3f3a83a34d04ce6b07992d + */ +static inline bool +lmdb::dbi_get(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + MDB_val* const data) { + const int rc = ::mdb_get(txn, dbi, const_cast(key), data); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_get", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @retval true if the key/value pair was inserted + * @retval false if the key already existed + * @see http://symas.com/mdb/doc/group__mdb.html#ga4fa8573d9236d54687c61827ebf8cac0 + */ +static inline bool +lmdb::dbi_put(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + MDB_val* const data, + const unsigned int flags = 0) { + const int rc = ::mdb_put(txn, dbi, const_cast(key), data, flags); + if (rc != MDB_SUCCESS && rc != MDB_KEYEXIST) { + error::raise("mdb_put", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @retval true if the key/value pair was removed + * @retval false if the key wasn't found + * @see http://symas.com/mdb/doc/group__mdb.html#gab8182f9360ea69ac0afd4a4eaab1ddb0 + */ +static inline bool +lmdb::dbi_del(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + const MDB_val* const data = nullptr) { + const int rc = ::mdb_del(txn, dbi, const_cast(key), const_cast(data)); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_del", rc); + } + return (rc == MDB_SUCCESS); +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Cursors */ + +namespace lmdb { + static inline void cursor_open(MDB_txn* txn, MDB_dbi dbi, MDB_cursor** cursor); + static inline void cursor_close(MDB_cursor* cursor) noexcept; + static inline void cursor_renew(MDB_txn* txn, MDB_cursor* cursor); + static inline MDB_txn* cursor_txn(MDB_cursor* cursor) noexcept; + static inline MDB_dbi cursor_dbi(MDB_cursor* cursor) noexcept; + static inline bool cursor_get(MDB_cursor* cursor, MDB_val* key, MDB_val* data, MDB_cursor_op op); + static inline void cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags); + static inline void cursor_del(MDB_cursor* cursor, unsigned int flags); + static inline void cursor_count(MDB_cursor* cursor, std::size_t& count); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga9ff5d7bd42557fd5ee235dc1d62613aa + */ +static inline void +lmdb::cursor_open(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cursor** const cursor) { + const int rc = ::mdb_cursor_open(txn, dbi, cursor); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_open", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gad685f5d73c052715c7bd859cc4c05188 + */ +static inline void +lmdb::cursor_close(MDB_cursor* const cursor) noexcept { + ::mdb_cursor_close(cursor); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac8b57befb68793070c85ea813df481af + */ +static inline void +lmdb::cursor_renew(MDB_txn* const txn, + MDB_cursor* const cursor) { + const int rc = ::mdb_cursor_renew(txn, cursor); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_renew", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga7bf0d458f7f36b5232fcb368ebda79e0 + */ +static inline MDB_txn* +lmdb::cursor_txn(MDB_cursor* const cursor) noexcept { + return ::mdb_cursor_txn(cursor); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga2f7092cf70ee816fb3d2c3267a732372 + */ +static inline MDB_dbi +lmdb::cursor_dbi(MDB_cursor* const cursor) noexcept { + return ::mdb_cursor_dbi(cursor); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga48df35fb102536b32dfbb801a47b4cb0 + */ +static inline bool +lmdb::cursor_get(MDB_cursor* const cursor, + MDB_val* const key, + MDB_val* const data, + const MDB_cursor_op op) { + const int rc = ::mdb_cursor_get(cursor, key, data, op); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_cursor_get", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga1f83ccb40011837ff37cc32be01ad91e + */ +static inline void +lmdb::cursor_put(MDB_cursor* const cursor, + MDB_val* const key, + MDB_val* const data, + const unsigned int flags = 0) { + const int rc = ::mdb_cursor_put(cursor, key, data, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_put", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga26a52d3efcfd72e5bf6bd6960bf75f95 + */ +static inline void +lmdb::cursor_del(MDB_cursor* const cursor, + const unsigned int flags = 0) { + const int rc = ::mdb_cursor_del(cursor, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_del", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga4041fd1e1862c6b7d5f10590b86ffbe2 + */ +static inline void +lmdb::cursor_count(MDB_cursor* const cursor, + std::size_t& count) { + const int rc = ::mdb_cursor_count(cursor, &count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_count", rc); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Values */ + +namespace lmdb { + class val; +} + +/** + * Wrapper class for `MDB_val` structures. + * + * @note Instances of this class are movable and copyable both. + * @see http://symas.com/mdb/doc/group__mdb.html#structMDB__val + */ +class lmdb::val { +protected: + MDB_val _val; + +public: + /** + * Default constructor. + */ + val() noexcept = default; + + /** + * Constructor. + */ + val(const std::string& data) noexcept + : val{data.data(), data.size()} {} + + /** + * Constructor. + */ + val(const char* const data) noexcept + : val{data, std::strlen(data)} {} + + /** + * Constructor. + */ + val(const void* const data, + const std::size_t size) noexcept + : _val{size, const_cast(data)} {} + + /** + * Move constructor. + */ + val(val&& other) noexcept = default; + + /** + * Move assignment operator. + */ + val& operator=(val&& other) noexcept = default; + + /** + * Destructor. + */ + ~val() noexcept = default; + + /** + * Returns an `MDB_val*` pointer. + */ + operator MDB_val*() noexcept { + return &_val; + } + + /** + * Returns an `MDB_val*` pointer. + */ + operator const MDB_val*() const noexcept { + return &_val; + } + + /** + * Determines whether this value is empty. + */ + bool empty() const noexcept { + return size() == 0; + } + + /** + * Returns the size of the data. + */ + std::size_t size() const noexcept { + return _val.mv_size; + } + + /** + * Returns a pointer to the data. + */ + template + T* data() noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + template + const T* data() const noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + char* data() noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + const char* data() const noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Assigns the value. + */ + template + val& assign(const T* const data, + const std::size_t size) noexcept { + _val.mv_size = size; + _val.mv_data = const_cast(reinterpret_cast(data)); + return *this; + } + + /** + * Assigns the value. + */ + val& assign(const char* const data) noexcept { + return assign(data, std::strlen(data)); + } + + /** + * Assigns the value. + */ + val& assign(const std::string& data) noexcept { + return assign(data.data(), data.size()); + } +}; + +#if !(defined(__COVERITY__) || defined(_MSC_VER)) +static_assert(std::is_pod::value, "lmdb::val must be a POD type"); +static_assert(sizeof(lmdb::val) == sizeof(MDB_val), "sizeof(lmdb::val) != sizeof(MDB_val)"); +#endif + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Environment */ + +namespace lmdb { + class env; +} + +/** + * Resource class for `MDB_env*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__env + */ +class lmdb::env { +protected: + MDB_env* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + static constexpr mode default_mode = 0644; /* -rw-r--r-- */ + + /** + * Creates a new LMDB environment. + * + * @param flags + * @throws lmdb::error on failure + */ + static env create(const unsigned int flags = default_flags) { + MDB_env* handle{nullptr}; + lmdb::env_create(&handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + if (flags) { + try { + lmdb::env_set_flags(handle, flags); + } + catch (const lmdb::error&) { + lmdb::env_close(handle); + throw; + } + } + return env{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_env*` handle + */ + env(MDB_env* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + env(env&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + env& operator=(env&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~env() noexcept { + try { close(); } catch (...) {} + } + + /** + * Returns the underlying `MDB_env*` handle. + */ + operator MDB_env*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_env*` handle. + */ + MDB_env* handle() const noexcept { + return _handle; + } + + /** + * Flushes data buffers to disk. + * + * @param force + * @throws lmdb::error on failure + */ + void sync(const bool force = true) { + lmdb::env_sync(handle(), force); + } + + /** + * Closes this environment, releasing the memory map. + * + * @note this method is idempotent + * @post `handle() == nullptr` + */ + void close() noexcept { + if (handle()) { + lmdb::env_close(handle()); + _handle = nullptr; + } + } + + /** + * Opens this environment. + * + * @param path + * @param flags + * @param mode + * @throws lmdb::error on failure + */ + env& open(const char* const path, + const unsigned int flags = default_flags, + const mode mode = default_mode) { + lmdb::env_open(handle(), path, flags, mode); + return *this; + } + + /** + * @param flags + * @param onoff + * @throws lmdb::error on failure + */ + env& set_flags(const unsigned int flags, + const bool onoff = true) { + lmdb::env_set_flags(handle(), flags, onoff); + return *this; + } + + /** + * @param size + * @throws lmdb::error on failure + */ + env& set_mapsize(const std::size_t size) { + lmdb::env_set_mapsize(handle(), size); + return *this; + } + + /** + * @param count + * @throws lmdb::error on failure + */ + env& set_max_readers(const unsigned int count) { + lmdb::env_set_max_readers(handle(), count); + return *this; + } + + /** + * @param count + * @throws lmdb::error on failure + */ + env& set_max_dbs(const MDB_dbi count) { + lmdb::env_set_max_dbs(handle(), count); + return *this; + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Transactions */ + +namespace lmdb { + class txn; +} + +/** + * Resource class for `MDB_txn*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__txn + */ +class lmdb::txn { +protected: + MDB_txn* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + + /** + * Creates a new LMDB transaction. + * + * @param env the environment handle + * @param parent + * @param flags + * @throws lmdb::error on failure + */ + static txn begin(MDB_env* const env, + MDB_txn* const parent = nullptr, + const unsigned int flags = default_flags) { + MDB_txn* handle{nullptr}; + lmdb::txn_begin(env, parent, flags, &handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + return txn{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_txn*` handle + */ + txn(MDB_txn* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + txn(txn&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + txn& operator=(txn&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~txn() noexcept { + if (_handle) { + try { abort(); } catch (...) {} + _handle = nullptr; + } + } + + /** + * Returns the underlying `MDB_txn*` handle. + */ + operator MDB_txn*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_txn*` handle. + */ + MDB_txn* handle() const noexcept { + return _handle; + } + + /** + * Returns the transaction's `MDB_env*` handle. + */ + MDB_env* env() const noexcept { + return lmdb::txn_env(handle()); + } + + /** + * Commits this transaction. + * + * @throws lmdb::error on failure + * @post `handle() == nullptr` + */ + void commit() { + lmdb::txn_commit(_handle); + _handle = nullptr; + } + + /** + * Aborts this transaction. + * + * @post `handle() == nullptr` + */ + void abort() noexcept { + lmdb::txn_abort(_handle); + _handle = nullptr; + } + + /** + * Resets this read-only transaction. + */ + void reset() noexcept { + lmdb::txn_reset(_handle); + } + + /** + * Renews this read-only transaction. + * + * @throws lmdb::error on failure + */ + void renew() { + lmdb::txn_renew(_handle); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Databases */ + +namespace lmdb { + class dbi; +} + +/** + * Resource class for `MDB_dbi` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b + */ +class lmdb::dbi { +protected: + MDB_dbi _handle{0}; + +public: + static constexpr unsigned int default_flags = 0; + static constexpr unsigned int default_put_flags = 0; + + /** + * Opens a database handle. + * + * @param txn the transaction handle + * @param name + * @param flags + * @throws lmdb::error on failure + */ + static dbi + open(MDB_txn* const txn, + const char* const name = nullptr, + const unsigned int flags = default_flags) { + MDB_dbi handle{}; + lmdb::dbi_open(txn, name, flags, &handle); + return dbi{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_dbi` handle + */ + dbi(const MDB_dbi handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + dbi(dbi&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + dbi& operator=(dbi&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~dbi() noexcept { + if (_handle) { + /* No need to call close() here. */ + } + } + + /** + * Returns the underlying `MDB_dbi` handle. + */ + operator MDB_dbi() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_dbi` handle. + */ + MDB_dbi handle() const noexcept { + return _handle; + } + + /** + * Returns statistics for this database. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + MDB_stat stat(MDB_txn* const txn) const { + MDB_stat result; + lmdb::dbi_stat(txn, handle(), &result); + return result; + } + + /** + * Retrieves the flags for this database handle. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + unsigned int flags(MDB_txn* const txn) const { + unsigned int result{}; + lmdb::dbi_flags(txn, handle(), &result); + return result; + } + + /** + * Returns the number of records in this database. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + std::size_t size(MDB_txn* const txn) const { + return stat(txn).ms_entries; + } + + /** + * @param txn a transaction handle + * @param del + * @throws lmdb::error on failure + */ + void drop(MDB_txn* const txn, + const bool del = false) { + lmdb::dbi_drop(txn, handle(), del); + } + + /** + * Sets a custom key comparison function for this database. + * + * @param txn a transaction handle + * @param cmp the comparison function + * @throws lmdb::error on failure + */ + dbi& set_compare(MDB_txn* const txn, + MDB_cmp_func* const cmp = nullptr) { + lmdb::dbi_set_compare(txn, handle(), cmp); + return *this; + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @param data + * @throws lmdb::error on failure + */ + bool get(MDB_txn* const txn, + const val& key, + val& data) { + return lmdb::dbi_get(txn, handle(), key, data); + } + + /** + * Retrieves a key from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const K& key) const { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + return lmdb::dbi_get(txn, handle(), k, v); + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @param val + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const K& key, + V& val) const { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + const bool result = lmdb::dbi_get(txn, handle(), k, v); + if (result) { + val = *v.data(); + } + return result; + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const char* const key, + V& val) const { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{}; + const bool result = lmdb::dbi_get(txn, handle(), k, v); + if (result) { + val = *v.data(); + } + return result; + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key + * @param data + * @param flags + * @throws lmdb::error on failure + */ + bool put(MDB_txn* const txn, + const val& key, + val& data, + const unsigned int flags = default_put_flags) { + return lmdb::dbi_put(txn, handle(), key, data, flags); + } + + /** + * Stores a key into this database. + * + * @param txn a transaction handle + * @param key + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const K& key, + const unsigned int flags = default_put_flags) { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key + * @param val + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const K& key, + const V& val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{&val, sizeof(V)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const char* const key, + const V& val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{&val, sizeof(V)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val a NUL-terminated string key + * @param flags + * @throws lmdb::error on failure + */ + bool put(MDB_txn* const txn, + const char* const key, + const char* const val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{val, std::strlen(val)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Removes a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + bool del(MDB_txn* const txn, + const val& key) { + return lmdb::dbi_del(txn, handle(), key); + } + + /** + * Removes a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + template + bool del(MDB_txn* const txn, + const K& key) { + const lmdb::val k{&key, sizeof(K)}; + return lmdb::dbi_del(txn, handle(), k); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Cursors */ + +namespace lmdb { + class cursor; +} + +/** + * Resource class for `MDB_cursor*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__cursor + */ +class lmdb::cursor { +protected: + MDB_cursor* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + + /** + * Creates an LMDB cursor. + * + * @param txn the transaction handle + * @param dbi the database handle + * @throws lmdb::error on failure + */ + static cursor + open(MDB_txn* const txn, + const MDB_dbi dbi) { + MDB_cursor* handle{}; + lmdb::cursor_open(txn, dbi, &handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + return cursor{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_cursor*` handle + */ + cursor(MDB_cursor* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + cursor(cursor&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + cursor& operator=(cursor&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~cursor() noexcept { + try { close(); } catch (...) {} + } + + /** + * Returns the underlying `MDB_cursor*` handle. + */ + operator MDB_cursor*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_cursor*` handle. + */ + MDB_cursor* handle() const noexcept { + return _handle; + } + + /** + * Closes this cursor. + * + * @note this method is idempotent + * @post `handle() == nullptr` + */ + void close() noexcept { + if (_handle) { + lmdb::cursor_close(_handle); + _handle = nullptr; + } + } + + /** + * Renews this cursor. + * + * @param txn the transaction scope + * @throws lmdb::error on failure + */ + void renew(MDB_txn* const txn) { + lmdb::cursor_renew(txn, handle()); + } + + /** + * Returns the cursor's transaction handle. + */ + MDB_txn* txn() const noexcept { + return lmdb::cursor_txn(handle()); + } + + /** + * Returns the cursor's database handle. + */ + MDB_dbi dbi() const noexcept { + return lmdb::cursor_dbi(handle()); + } + + /** + * Retrieves a key from the database. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + bool get(MDB_val* const key, + const MDB_cursor_op op) { + return get(key, nullptr, op); + } + + /** + * Retrieves a key from the database. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + bool get(lmdb::val& key, + const MDB_cursor_op op) { + return get(key, nullptr, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val (may be `nullptr`) + * @param op + * @throws lmdb::error on failure + */ + bool get(MDB_val* const key, + MDB_val* const val, + const MDB_cursor_op op) { + return lmdb::cursor_get(handle(), key, val, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val + * @param op + * @throws lmdb::error on failure + */ + bool get(lmdb::val& key, + lmdb::val& val, + const MDB_cursor_op op) { + return lmdb::cursor_get(handle(), key, val, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val + * @param op + * @throws lmdb::error on failure + */ + bool get(std::string& key, + std::string& val, + const MDB_cursor_op op) { + lmdb::val k{}, v{}; + const bool found = get(k, v, op); + if (found) { + key.assign(k.data(), k.size()); + val.assign(v.data(), v.size()); + } + return found; + } + + /** + * Positions this cursor at the given key. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + template + bool find(const K& key, + const MDB_cursor_op op = MDB_SET) { + lmdb::val k{&key, sizeof(K)}; + return get(k, nullptr, op); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif /* LMDBXX_H */ diff --git a/main.cpp b/main.cpp index 15fcf5c..7d0dae2 100644 --- a/main.cpp +++ b/main.cpp @@ -54,6 +54,8 @@ int main(int argc, char *argv[]) QObject::connect(squawk, SIGNAL(stateChanged(int)), &w, SLOT(stateChanged(int))); QObject::connect(squawk, SIGNAL(accountMessage(const QString&, const Shared::Message&)), &w, SLOT(accountMessage(const QString&, const Shared::Message&))); + //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + coreThread->start(); int result = app.exec(); diff --git a/ui/conversation.cpp b/ui/conversation.cpp index 4ac86f6..b7ba1d8 100644 --- a/ui/conversation.cpp +++ b/ui/conversation.cpp @@ -29,7 +29,8 @@ Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): ker(), activePalResource(), thread(), - scroll(nothing) + scroll(down), + manualSliderChange(false) { m_ui->setupUi(this); m_ui->splitter->setSizes({300, 0}); @@ -54,9 +55,11 @@ Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): line->setMyName(p_contact->getAccountName()); connect(line, SIGNAL(resize(int)), this, SLOT(onMessagesResize(int))); + QScrollBar* vs = m_ui->scrollArea->verticalScrollBar(); m_ui->scrollArea->setWidget(line); - m_ui->scrollArea->verticalScrollBar()->setBackgroundRole(QPalette::Base); - m_ui->scrollArea->verticalScrollBar()->setAutoFillBackground(true);; + vs->setBackgroundRole(QPalette::Base); + vs->setAutoFillBackground(true); + connect(vs, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); } Conversation::~Conversation() @@ -118,13 +121,6 @@ void Conversation::addMessage(const Shared::Message& data) return; } - if (scroll == nothing) { - if (pos == max) { - scroll = down; - } else if (place != MessageLine::end) { //todo make some better handling of that situation - scroll = keep; - } - } if (!data.getOutgoing()) { const QString& res = data.getPenPalResource(); if (res.size() > 0) { @@ -194,16 +190,24 @@ void Conversation::onEnterPressed() void Conversation::onMessagesResize(int amount) { + manualSliderChange = true; switch (scroll) { case down: m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum()); break; - case keep: - m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->value() - amount); - break; default: break; } - scroll = nothing; + manualSliderChange = false; } +void Conversation::onSliderValueChanged(int value) +{ + if (!manualSliderChange) { + if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) { + scroll = down; + } else { + scroll = nothing; + } + } +} diff --git a/ui/conversation.h b/ui/conversation.h index b9b8b1c..e88007b 100644 --- a/ui/conversation.h +++ b/ui/conversation.h @@ -69,6 +69,7 @@ protected slots: void onContactChanged(Models::Item* item, int row, int col); void onEnterPressed(); void onMessagesResize(int amount); + void onSliderValueChanged(int value); private: enum Scroll { @@ -83,6 +84,7 @@ private: QString activePalResource; QString thread; Scroll scroll; + bool manualSliderChange; }; #endif // CONVERSATION_H