diff --git a/CMakeLists.txt b/CMakeLists.txt index 705e980..f462b3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,41 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.16) -project(storage) +project(storage VERSION 0.0.1 LANGUAGES CXX) -add_executable(storage main.cpp) +cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0079 NEW) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +if (NOT DEFINED QT_VERSION_MAJOR) + find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +endif() +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +qt_standard_project_setup() + +find_package(LMDB REQUIRED) + +# Build type +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif () + +add_executable(storage + main.cpp + exception.cpp +) + + +target_include_directories(storage PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}) +target_include_directories(storage PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}) + +target_link_libraries(storage + PRIVATE + Qt${QT_VERSION_MAJOR}::Core +) +target_link_libraries(storage PRIVATE lmdb) install(TARGETS storage RUNTIME DESTINATION bin) diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake new file mode 100644 index 0000000..d6f2cd3 --- /dev/null +++ b/cmake/FindLMDB.cmake @@ -0,0 +1,52 @@ +#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license +#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code + +# Try to find LMDB headers and library. +# +# Usage of this module as follows: +# +# find_package(LMDB) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# LMDB_ROOT_DIR Set this variable to the root installation of +# LMDB if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# LMDB_FOUND System has LMDB library/headers. +# LMDB_LIBRARIES The LMDB library. +# LMDB_INCLUDE_DIRS The location of LMDB headers. + +find_path(LMDB_ROOT_DIR + NAMES include/lmdb.h + ) + +find_library(LMDB_LIBRARIES + NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible + HINTS ${LMDB_ROOT_DIR}/lib + ) + +add_library(lmdb UNKNOWN IMPORTED) +set_target_properties(lmdb PROPERTIES + IMPORTED_LOCATION ${LMDB_LIBRARIES} + ) + +find_path(LMDB_INCLUDE_DIRS + NAMES lmdb.h + HINTS ${LMDB_ROOT_DIR}/include + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LMDB DEFAULT_MSG + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS + ) + +mark_as_advanced( + LMDB_ROOT_DIR + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS +) diff --git a/exception.cpp b/exception.cpp new file mode 100644 index 0000000..3dee9b3 --- /dev/null +++ b/exception.cpp @@ -0,0 +1,33 @@ +/* + * Squawk messenger. + * 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 "exception.h" + +Utils::Exception::Exception() +{ +} + +Utils::Exception::~Exception() +{ +} + +const char* Utils::Exception::what() const noexcept( true ) +{ + std::string* msg = new std::string(getMessage()); + return msg->c_str(); +} diff --git a/exception.h b/exception.h new file mode 100644 index 0000000..4c66c2d --- /dev/null +++ b/exception.h @@ -0,0 +1,40 @@ +/* + * Squawk messenger. + * 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 EXCEPTION_H +#define EXCEPTION_H + +#include +#include + +namespace Utils +{ + class Exception: + public std::exception + { + public: + Exception(); + virtual ~Exception(); + + virtual std::string getMessage() const = 0; + + const char* what() const noexcept( true ); + }; +} + +#endif // EXCEPTION_H diff --git a/main.cpp b/main.cpp index 8bb47f1..5d0f628 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,7 @@ #include +#include + int main(int argc, char **argv) { std::cout << "Hello, world!" << std::endl; return 0; diff --git a/storage.h b/storage.h new file mode 100644 index 0000000..6670f6a --- /dev/null +++ b/storage.h @@ -0,0 +1,158 @@ +/* + * Squawk messenger. + * 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_STORAGE_H +#define CORE_STORAGE_H + +#include +#include + + +#include "exception.h" + +namespace Core { + +/** + * @todo write docs + */ +template +class Storage +{ +public: + Storage(const QString& name); + ~Storage(); + + void open(); + void close(); + + void addRecord(const K& key, const V& value); + void changeRecord(const K& key, const V& value); + void removeRecord(const K& key); + V getRecord(const K& key) const; + QString getName() const; + + +private: + QString name; + bool opened; + MDB_env* environment; + MDB_dbi base; + +public: + class Directory: + public Utils::Exception + { + public: + Directory(const std::string& p_path):Exception(), path(p_path){} + + std::string getMessage() const{return "Can't create directory for database at " + path;} + private: + std::string path; + }; + + class Closed: + public Utils::Exception + { + public: + Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){} + + std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;} + private: + std::string operation; + std::string account; + }; + + class NotFound: + public Utils::Exception + { + public: + NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){} + + std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;} + private: + std::string key; + std::string account; + }; + + class Empty: + public Utils::Exception + { + public: + Empty(const std::string& acc):Exception(), account(acc){} + + std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";} + private: + std::string account; + }; + + class Exist: + public Utils::Exception + { + public: + Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){} + + std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";} + private: + std::string account; + std::string key; + }; + + class NoAvatar: + public Utils::Exception + { + public: + NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ + if (resource.size() == 0) { + resource = "for himself"; + } + } + + std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} + private: + std::string element; + std::string resource; + }; + + class Unknown: + public Utils::Exception + { + public: + Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){} + + std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;} + private: + std::string account; + std::string msg; + }; +}; + +} + +MDB_val& operator << (MDB_val& data, QString& value); +MDB_val& operator >> (MDB_val& data, QString& value); + +MDB_val& operator << (MDB_val& data, uint32_t& value); +MDB_val& operator >> (MDB_val& data, uint32_t& value); + +namespace std { + std::string to_string(const QString& str); +} + +#include "storage.hpp" + +#endif // CORE_STORAGE_H diff --git a/storage.hpp b/storage.hpp new file mode 100644 index 0000000..477d70e --- /dev/null +++ b/storage.hpp @@ -0,0 +1,226 @@ +/* + * Squawk messenger. + * 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_STORAGE_HPP +#define CORE_STORAGE_HPP + +#include +#include + +#include "storage.h" +#include + +template +Core::Storage::Storage(const QString& p_name): + name(p_name), + opened(false), + environment(), + base() +{ +} + +template +Core::Storage::~Storage() +{ + close(); +} + +template +void Core::Storage::open() +{ + if (!opened) { + mdb_env_create(&environment); + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + name; + QDir cache(path); + + if (!cache.exists()) { + bool res = cache.mkpath(path); + if (!res) { + throw Directory(path.toStdString()); + } + } + + mdb_env_set_maxdbs(environment, 1); + mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL); + mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); + + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + mdb_dbi_open(txn, "base", MDB_CREATE, &base); + mdb_txn_commit(txn); + opened = true; + } +} + +template +void Core::Storage::close() +{ + if (opened) { + mdb_dbi_close(environment, base); + mdb_env_close(environment); + opened = false; + } +} + +template +void Core::Storage::addRecord(const K& key, const V& value) +{ + if (!opened) { + throw Closed("addRecord", name.toStdString()); + } + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << value; + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + lmdbData.mv_size = ba.size(); + lmdbData.mv_data = (uint8_t*)ba.data(); + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc == MDB_KEYEXIST) { + throw Exist(name.toStdString(), std::to_string(key)); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +void Core::Storage::changeRecord(const K& key, const V& value) +{ + if (!opened) { + throw Closed("changeRecord", name.toStdString()); + } + + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << value; + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + lmdbData.mv_size = ba.size(); + lmdbData.mv_data = (uint8_t*)ba.data(); + MDB_txn *txn; + mdb_txn_begin(environment, NULL, 0, &txn); + int rc; + rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + mdb_txn_abort(txn); + if (rc) { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +V Core::Storage::getRecord(const K& key) const +{ + if (!opened) { + throw Closed("addElement", name.toStdString()); + } + + MDB_val lmdbKey, lmdbData; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + rc = mdb_get(txn, base, &lmdbKey, &lmdbData); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), name.toStdString()); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); + QDataStream ds(&ba, QIODevice::ReadOnly); + V value; + ds >> value; + mdb_txn_abort(txn); + + return value; + } +} + +template +void Core::Storage::removeRecord(const K& key) +{ + if (!opened) { + throw Closed("addElement", name.toStdString()); + } + + MDB_val lmdbKey; + lmdbKey << key; + + MDB_txn *txn; + int rc; + mdb_txn_begin(environment, NULL, 0, &txn); + rc = mdb_del(txn, base, &lmdbKey, NULL); + if (rc) { + mdb_txn_abort(txn); + if (rc == MDB_NOTFOUND) { + throw NotFound(std::to_string(key), name.toStdString()); + } else { + throw Unknown(name.toStdString(), mdb_strerror(rc)); + } + } else { + mdb_txn_commit(txn); + } +} + +template +QString Core::Storage::getName() const { + return name;} + +MDB_val& operator << (MDB_val& data, const QString& value) { + QByteArray ba = value.toUtf8(); + data.mv_size = ba.size(); + data.mv_data = ba.data(); + return data; +} +MDB_val& operator >> (MDB_val& data, QString& value) { + value = QString::fromUtf8((const char*)data.mv_data, data.mv_size); + return data; +} + +MDB_val& operator << (MDB_val& data, uint32_t& value) { + data.mv_size = 4; + data.mv_data = &value; + return data; +} +MDB_val& operator >> (MDB_val& data, uint32_t& value) { + std::memcpy(&value, data.mv_data, data.mv_size); + return data; +} + +std::string std::to_string(const QString& str) { + return str.toStdString(); +} +#endif //CORE_STORAGE_HPP