diff --git a/.travis.yml b/.travis.yml index 7648120..7c4cf87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,15 +19,15 @@ matrix: #- 7z.exe e sqlite-amalgamation-3320300.zip -oc:\sqlite\include #- wget -q https://www.sqlite.org/2020/sqlite-dll-win64-x64-3320300.zip #- 7z.exe e sqlite-dll-win64-x64-3320300.zip -oc:\sqlite\lib - - choco install doxygen.install - - choco install graphviz + #- choco install doxygen.install + #- choco install graphviz cache: directories: - boost_1_73_0/ script: - mkdir ${BUILD_DIR} && cd ${BUILD_DIR} - - cmake .. -G "MinGW Makefiles" - - mingw32-make + - cmake.exe .. -G "MinGW Makefiles" + - mingw32-make.exe install - os: linux addons: diff --git a/cmake/DeployQt.cmake b/cmake/DeployQt.cmake new file mode 100644 index 0000000..d040ea7 --- /dev/null +++ b/cmake/DeployQt.cmake @@ -0,0 +1,29 @@ +function(windeployqt target) + find_package(Qt5Core REQUIRED) + + find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINT ${_qt_bin_dir}) + + if (NOT WINDEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "windeployqt not found") + endif() + + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E + env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}" + --verbose 0 + --no-compiler-runtime + --no-translations + --no-angle + --no-opengl-sw + --dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/windeployqt" + "$" + COMMENT "Deploying Qt . . ." + ) + + if (MINGW) + get_filename_component(mingw_path ${CMAKE_CXX_COMPILER} PATH) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS ${mingw_path}/libgcc_s_seh-1.dll ${mingw_path}/libstdc++-6.dll) + endif() + + include(InstallRequiredSystemLibraries) +endfunction() diff --git a/scripts/install_env.py b/scripts/install_env.py index 97e9f70..16969d9 100644 --- a/scripts/install_env.py +++ b/scripts/install_env.py @@ -46,19 +46,21 @@ def MakeBoostInstaller(platform): print("Platform: " + platform) boost_filename = "boost_{}_{}_{}.tar.gz".format(boost_version_major, boost_version_minor, boost_version_patch) -boost_url = "https://sourceforge.net/projects/boost/files/boost/{}.{}.{}/{}/download".format(boost_version_major, boost_version_minor, boost_version_patch, boost_filename) -print("Download boost . . .") +if not os.path.isdir('boost_{}_{}_{}'.format(boost_version_major, boost_version_minor, boost_version_patch)): + boost_url = "https://sourceforge.net/projects/boost/files/boost/{}.{}.{}/{}/download".format(boost_version_major, boost_version_minor, boost_version_patch, boost_filename) -r = requests.get(boost_url, allow_redirects=True) + print("Download boost . . .") -open(boost_filename, "wb+").write(r.content) - -print("Unzip boost . . .") - -tar = tarfile.open(boost_filename, "r:gz") -tar.extractall() -tar.close() + r = requests.get(boost_url, allow_redirects=True) + + open(boost_filename, "wb+").write(r.content) + + print("Unzip boost . . .") + + tar = tarfile.open(boost_filename, "r:gz") + tar.extractall() + tar.close() print("Install boost . . .") diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index b1b15c8..5b8ac0b 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -61,7 +61,14 @@ target_link_libraries (${CLIENT_NAME} if (WIN32) add_definitions(-DWIN32_LEAN_AND_MEAN) target_link_libraries(${CLIENT_NAME} -lws2_32) -endif (WIN32) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${CMAKE_INSTALL_PREFIX}) + + include(${CMAKE_SOURCE_DIR}/cmake/DeployQt.cmake) + windeployqt(${CLIENT_NAME}) + + install(TARGETS ${CLIENT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}) + install(DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/windeployqt/" DESTINATION ${CMAKE_INSTALL_PREFIX}) +endif() if (NOT WIN32) install(TARGETS ${CLIENT_NAME} RUNTIME DESTINATION bin) diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index bf1aedd..1891fbc 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -35,6 +35,10 @@ find_library(SQLITE3_LIBRARY NAMES sqlite3) set(STORAGE_SRC ${DIR_STORAGE}/database.h ${DIR_STORAGE}/database.cpp + #${DIR_STORAGE}/database_.h + #${DIR_STORAGE}/database_.cpp + ${DIR_STORAGE}/sqlitedatabase.h + ${DIR_STORAGE}/sqlitedatabase.cpp ) set(SERVER_SOURCES diff --git a/src/server/channel/channel.h b/src/server/channel/channel.h index de7b16a..6232bc8 100644 --- a/src/server/channel/channel.h +++ b/src/server/channel/channel.h @@ -5,7 +5,7 @@ #include #include #include "iroom.h" -#include "storage/database.h" +#include "storage/sqlitedatabase.h" #include "log/logger.h" /** @@ -25,7 +25,7 @@ class Channel : public IRoom * * @param db */ - Channel(identifier_t room, database_ptr db) : channel_id(room) + Channel(identifier_t room, Storage::database_ptr db) : channel_id(room) { if (db == nullptr) { BOOST_LOG_TRIVIAL(info) << "Failed to load history. Database pointer is nullptr."; diff --git a/src/server/channel/channels_manager.cpp b/src/server/channel/channels_manager.cpp index 7d77cba..ebe6556 100644 --- a/src/server/channel/channels_manager.cpp +++ b/src/server/channel/channels_manager.cpp @@ -5,7 +5,7 @@ ChannelsManager::ChannelsManager() BOOST_LOG_TRIVIAL(info) << "create ChannelsManager"; } -bool ChannelsManager::join(subscriber_ptr new_sub, identifier_t new_room_id, database_ptr db) { +bool ChannelsManager::join(subscriber_ptr new_sub, identifier_t new_room_id, Storage::database_ptr db) { bool flag_result = true; BOOST_LOG_TRIVIAL(info) << "ChannelsManager::join"; if (auto it=channels.find(new_room_id); it!=channels.end()) { diff --git a/src/server/channel/channels_manager.h b/src/server/channel/channels_manager.h index c3d1862..4f66cdc 100644 --- a/src/server/channel/channels_manager.h +++ b/src/server/channel/channels_manager.h @@ -30,7 +30,7 @@ class ChannelsManager * * @param db */ - bool join(subscriber_ptr, identifier_t, database_ptr db); + bool join(subscriber_ptr, identifier_t, Storage::database_ptr db); /** * @brief Send message to specific room and specific user diff --git a/src/server/connection/connection.cpp b/src/server/connection/connection.cpp index 1a7d43f..c4a6833 100644 --- a/src/server/connection/connection.cpp +++ b/src/server/connection/connection.cpp @@ -219,7 +219,7 @@ void Connection::do_text_msg(Serialize::Request new_request) { // @todo ba::post(pool), bd into ChannelsManager ChannelsManager::Instance().send_to_channel(msg); - db->save_text_msg(msg); + db->save_text_message(msg); } else { BOOST_LOG_TRIVIAL(error) << "request without text_request"; } @@ -470,7 +470,7 @@ void Connection::do_read_pb_text_req(boost::system::error_code error, std::size_ // @todo ba::post(pool), bd into ChannelsManager ChannelsManager::Instance().send_to_channel(msg); - db->save_text_msg(msg); + db->save_text_message(msg); async_read_pb_header(); } diff --git a/src/server/connection/connection.h b/src/server/connection/connection.h index cfb912c..4bce365 100644 --- a/src/server/connection/connection.h +++ b/src/server/connection/connection.h @@ -25,7 +25,7 @@ class Connection : public ISubscriber, public std::enable_shared_from_this a_thread_pool, boost::asio::ip::tcp::socket&& _socket, database_ptr _db): + explicit Connection(std::shared_ptr a_thread_pool, boost::asio::ip::tcp::socket&& _socket, Storage::database_ptr _db): thread_pool(a_thread_pool), socket(std::move(_socket)), db(_db), @@ -89,7 +89,7 @@ class Connection : public ISubscriber, public std::enable_shared_from_this busy; private: diff --git a/src/server/connection/connection_manager.h b/src/server/connection/connection_manager.h index cd41cbc..f611de2 100644 --- a/src/server/connection/connection_manager.h +++ b/src/server/connection/connection_manager.h @@ -10,7 +10,7 @@ class ConnectionManager { public: - ConnectionManager(std::shared_ptr a_thread_pool, database_ptr n_db): + ConnectionManager(std::shared_ptr a_thread_pool, Storage::database_ptr n_db): thread_pool(a_thread_pool), db(n_db) { @@ -37,7 +37,7 @@ class ConnectionManager private: std::shared_ptr thread_pool; std::vector pool_connections; - database_ptr db; + Storage::database_ptr db; /** * @brief print all connections diff --git a/src/server/server.h b/src/server/server.h index 37b23b6..568718e 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -20,7 +20,7 @@ class Server { * @param port - number port * @param _db - database ptr */ - Server(unsigned short port, database_ptr _db): + Server(unsigned short port, Storage::database_ptr _db): endpoint(boost::asio::ip::tcp::v4(), port), acceptor(io_service, endpoint), // @todo thread_pool @@ -50,7 +50,7 @@ class Server { boost::asio::ip::tcp::acceptor acceptor; std::shared_ptr thread_pool; - database_ptr db; + Storage::database_ptr db; ConnectionManager connect_manager; /** diff --git a/src/server/startup_server.cpp b/src/server/startup_server.cpp index ea0f695..d24c0de 100644 --- a/src/server/startup_server.cpp +++ b/src/server/startup_server.cpp @@ -61,8 +61,21 @@ int main(int argc, char** argv) { if (set_parametrs(argc, argv, port)) { BOOST_LOG_TRIVIAL(info) << "starting server v.0.7"; try { - database_ptr db_sqlite = std::make_shared(); - std::unique_ptr server = std::make_unique(port, db_sqlite); + Storage::DatabaseConfiguration db_config; + db_config.FolderPath = std::string{std::getenv("HOME")} + "/AppChat/"; + db_config.ConnectionString = "file://" + db_config.FolderPath + "history.db"; + + Storage::database_ptr db = std::make_shared(db_config); + + if (!db->open()) { + BOOST_LOG_TRIVIAL(error) << "failed open database"; + return 0; + } + + db->create_table_history(); + db->create_table_logins(); + + std::unique_ptr server = std::make_unique(port, db); server->run(); } catch (const std::exception & ex) { BOOST_LOG_TRIVIAL(fatal) << "Exception " << ex.what(); diff --git a/src/server/storage/database.cpp b/src/server/storage/database.cpp index a2ba1e7..7018577 100644 --- a/src/server/storage/database.cpp +++ b/src/server/storage/database.cpp @@ -1,228 +1,16 @@ -#include "database.h" -#include -#include -#include +#include "database_.h" -std::string Database::create_table_history = std::string("create table if not exists history ") - + std::string("(author varchar[") + std::to_string(Block::LoginName) + std::string("], ") -// + std::string("client_id integer, ") - + std::string("room_id integer, ") - + std::string("datetime integer, ") - + std::string("message varchar[") + std::to_string(Block::TextMessage) + std::string("]);"); +#include +namespace Storage +{ -std::string Database::create_table_logins = std::string("create table if not exists logins ") - + std::string("(login varchar[") + std::to_string(Block::LoginName) + std::string("], ") - + std::string("login_id integer, ") - + std::string("password varchar[") + std::to_string(Block::Password) + std::string("]);"); +Database::Database(DatabaseConfiguration dbConfig) + : dbConfig_{dbConfig}, is_open_{false} +{ } +Database::~Database() { } -/** - * @brief Database::Database - * @todo add checks for create_directory - * @todo add open/close db methods - */ -Database::Database() { - BOOST_LOG_TRIVIAL(info) << "Database()"; - const auto dir_path = std::string(std::getenv("HOME")) + "/Appchat/"; - BOOST_LOG_TRIVIAL(info) << "Home dir: " << dir_path; +bool Database::is_open() { return is_open_; } - if (!boost::filesystem::exists(dir_path)) { - boost::filesystem::create_directory(dir_path); - BOOST_LOG_TRIVIAL(info) << "create dir for appchat: " << dir_path; - } - - db_name = "file://" + dir_path + "history.db"; - int rc = sqlite3_open_v2(db_name.c_str(), &db_ptr, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, NULL); - if(rc) { - BOOST_LOG_TRIVIAL(fatal) << "Cannot open database " << sqlite3_errmsg(db_ptr); - sqlite3_close(db_ptr); - db_ptr = NULL; - } - else { - BOOST_LOG_TRIVIAL(info) << "Database has been successfully opened"; - char* err_msg1 = nullptr; - rc = sqlite3_exec(db_ptr, create_table_history.c_str(), - [](void*, int, char**, char**){ return 0;}, - 0, &err_msg1); - if(rc != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "SQL error create_table_history" << err_msg1; - if (err_msg1) sqlite3_free(err_msg1); - sqlite3_close(db_ptr); - db_ptr = NULL; - } - else { - if (err_msg1) sqlite3_free(err_msg1); - - rc = sqlite3_exec(db_ptr, create_table_logins.c_str(), - [](void*, int, char**, char**){ return 0;}, - 0, &err_msg1); - if(rc != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "SQL error create_table_logins" << err_msg1; - if (err_msg1) sqlite3_free(err_msg1); - sqlite3_close(db_ptr); - db_ptr = NULL; - } - - } - } // finish open - -} - -Database::~Database() { - BOOST_LOG_TRIVIAL(info) << "~Database()"; - sqlite3_close(db_ptr); - db_ptr = NULL; -} - -void Database::save_text_msg(TextSendData msg) { -// Datetime dt(); // message->get_datetime(); - const std::string str_datetime = boost::str(boost::format("%1$d-%2$#02d-%3$#02d %4$#02s:%5$#02s:%6$#02s") - % (/*dt.year*/0 + 2000) % static_cast(/*dt.month*/1) % static_cast(/*dt.day*/1) - % static_cast(9/*dt.hours*/) % static_cast(15/*dt.minutes*/) % static_cast(12/*dt.seconds*/) - ); - - const std::string insert_query = std::string("insert into history values('") - + msg.login + std::string("', ") - + std::to_string(msg.room_id) + std::string(", strftime('%s','") - + str_datetime + std::string("'), '") - + msg.text + std::string("');"); - char* err_msg2 = nullptr; - int rc = sqlite3_exec(db_ptr, insert_query.c_str(), - [](void*, int, char**, char**){ return 0;}, - 0, &err_msg2); - if(rc != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "SQL error save_text_message" << err_msg2; - if (err_msg2) sqlite3_free(err_msg2); - sqlite3_close(db_ptr); - db_ptr = NULL; - return; - } -} - -std::deque Database::get_history(identifier_t roomid) { - std::deque history_room; - bool found = false; - sqlite3_stmt* stmt; - - std::string sql = std::string("select author, room_id, datetime(datetime, 'unixepoch'), message from history where room_id==") - + std::to_string(roomid) - + std::string(";"); - - if (sqlite3_prepare_v2(db_ptr, sql.c_str(), -1, &stmt, NULL) != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "ERROR: while compiling sql: " << sqlite3_errmsg(db_ptr); - sqlite3_finalize(stmt); - sqlite3_close(db_ptr); - db_ptr = NULL; - - } - - int ret_code = 0; - while((ret_code = sqlite3_step(stmt)) == SQLITE_ROW) { -// std::string author = (const char *)sqlite3_column_blob(stmt, 0); - std::string author = reinterpret_cast(sqlite3_column_blob(stmt, 0)); - - int room_id = sqlite3_column_int(stmt, 1); -// std::string dt = (const char *)sqlite3_column_blob(stmt, 2); -// std::string message = (const char *)sqlite3_column_blob(stmt, 3); - std::string dt = reinterpret_cast(sqlite3_column_blob(stmt, 2)); - std::string message = reinterpret_cast(sqlite3_column_blob(stmt, 3)); - - BOOST_LOG_TRIVIAL(info) <<"Db: " << author << " " << room_id << " " << dt << " " << message; -// text_response_ptr response = std::make_shared(author, DateTime(boost::posix_time::time_from_string(dt)), message, room_id); - // @todo return DateTime - history_room.push_back(TextSendData{room_id, author, message}); - found=true; - } - if(ret_code != SQLITE_DONE) { - BOOST_LOG_TRIVIAL(info) << "ERROR: while performing sql: " << sqlite3_errmsg(db_ptr) - << " ret_code = " << ret_code; - } - - BOOST_LOG_TRIVIAL(info) << "entry " << (found ? "found history" : "not found history"); - sqlite3_finalize(stmt); - - return history_room; -} - -void Database::add_logins(std::string login, identifier_t login_id, std::string password) { - const std::string insert_query = std::string("insert into logins values('") - + login + std::string("', ") - + std::to_string(login_id) + std::string(", '") - + password + std::string("');"); - char* err_msg2 = nullptr; - int rc = sqlite3_exec(db_ptr, insert_query.c_str(), - [](void*, int, char**, char**){ return 0;}, - 0, &err_msg2); - if(rc != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "SQL error add_logins" << err_msg2; - if (err_msg2) sqlite3_free(err_msg2); - } - if (err_msg2 && db_ptr) sqlite3_free(err_msg2); -} - -identifier_t Database::get_loginid(std::string login) const { - identifier_t result = -1; - bool found = false; - sqlite3_stmt* stmt; - - std::string sql = std::string("select * from logins where login=='") - + login - + std::string("';"); - - if (sqlite3_prepare_v2(db_ptr, sql.c_str(), -1, &stmt, NULL) != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "ERROR: while compiling sql: " << sqlite3_errmsg(db_ptr); - sqlite3_finalize(stmt); - } - - int ret_code = 0; - while((ret_code = sqlite3_step(stmt)) == SQLITE_ROW) { -// BOOST_LOG_TRIVIAL(info) << (const char *)sqlite3_column_blob(stmt, 0) << " " << sqlite3_column_int(stmt, 1) << " " -// << (const char *)sqlite3_column_blob(stmt, 2); - result = sqlite3_column_int(stmt, 1); - found = true; - } - if(ret_code != SQLITE_DONE) { - BOOST_LOG_TRIVIAL(error) << "ERROR: while performing sql: " << sqlite3_errmsg(db_ptr) - << " ret_code = " << ret_code; - } - - BOOST_LOG_TRIVIAL(info) << "login " << (found ? "found client" : "not found client"); - sqlite3_finalize(stmt); - - return result; -} - -identifier_t Database::check_client(std::string login, std::string password) const { - identifier_t result = -1; - bool found = false; - sqlite3_stmt* stmt; - - std::string sql = std::string("select * from logins where login=='") - + login - + std::string("';"); - - if (sqlite3_prepare_v2(db_ptr, sql.c_str(), -1, &stmt, NULL) != SQLITE_OK) { - BOOST_LOG_TRIVIAL(error) << "ERROR: while compiling sql: " << sqlite3_errmsg(db_ptr); - sqlite3_finalize(stmt); - } - int ret_code = 0; - - while((ret_code = sqlite3_step(stmt)) == SQLITE_ROW) { -// BOOST_LOG_TRIVIAL(info) << (const char *)sqlite3_column_blob(stmt, 0) << " " << sqlite3_column_int(stmt, 1) << " " -// << (const char *)sqlite3_column_blob(stmt, 2); - if ( password == reinterpret_cast(sqlite3_column_blob(stmt, 2))) { - result = sqlite3_column_int(stmt, 1); - } - found = true; - } - if(ret_code != SQLITE_DONE) { - BOOST_LOG_TRIVIAL(error) << "ERROR: while performing sql: " << sqlite3_errmsg(db_ptr) - << " ret_code = " << ret_code; - } - - BOOST_LOG_TRIVIAL(info) << "login " << (found ? "found client" : "not found client"); - sqlite3_finalize(stmt); - - return result; -} +} // namespace Storage diff --git a/src/server/storage/database.h b/src/server/storage/database.h index dbc58a6..a426245 100644 --- a/src/server/storage/database.h +++ b/src/server/storage/database.h @@ -1,38 +1,43 @@ -#ifndef DATABASE_H -#define DATABASE_H +#pragma once -#include #include -#include -#include "log/logger.h" #include -#include + +#include "databaseconfiguration.h" +#include "protocol/protocol.h" + +namespace Storage +{ class Database { public: - Database(); + Database(DatabaseConfiguration dbConfig); + virtual ~Database(); - ~Database(); + virtual bool open() = 0; + virtual void close() = 0; - void save_text_msg(TextSendData); + bool is_open(); - std::deque get_history(identifier_t roomid); + virtual bool create_table_history() = 0; + virtual bool create_table_logins() = 0; - void add_logins(std::string login, identifier_t logi_id, std::string password); + virtual bool save_text_message(TextSendData message) = 0; + + virtual std::deque get_history(identifier_t roomid) const = 0; - identifier_t get_loginid(std::string login) const; + virtual bool add_logins(std::string login, identifier_t logi_id, std::string password) = 0; - identifier_t check_client(std::string login, std::string password) const; + virtual identifier_t get_loginid(std::string login) const = 0; -private: - std::string db_name; - sqlite3* db_ptr = NULL; - static std::string create_table_history; - static std::string create_table_logins; + virtual identifier_t check_client(std::string login, std::string password) const = 0; +protected: + DatabaseConfiguration dbConfig_; + bool is_open_; }; using database_ptr = std::shared_ptr; -#endif // DATABASE_H +} //namespace Storage diff --git a/src/server/storage/databaseconfiguration.h b/src/server/storage/databaseconfiguration.h new file mode 100644 index 0000000..17c706e --- /dev/null +++ b/src/server/storage/databaseconfiguration.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace Storage +{ + +struct DatabaseConfiguration +{ + std::string ConnectionString; + std::string FolderPath; +}; + +} // namespace Storage diff --git a/src/server/storage/sqlitedatabase.cpp b/src/server/storage/sqlitedatabase.cpp new file mode 100644 index 0000000..81e6636 --- /dev/null +++ b/src/server/storage/sqlitedatabase.cpp @@ -0,0 +1,268 @@ +#include "sqlitedatabase.h" +#include "log/logger.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std::string_literals; +namespace fs = std::filesystem; + +namespace Storage +{ + + +SqliteDatabase::SqliteDatabase(DatabaseConfiguration dbConfig) + : Database(std::move(dbConfig)), db_{nullptr, &sqlite3_close} +{ + BOOST_LOG_TRIVIAL(info) << "SqliteDatabase()"; + + if (!fs::exists(dbConfig_.FolderPath)) { + fs::create_directory(dbConfig_.FolderPath); + BOOST_LOG_TRIVIAL(info) << "Create dir for appchat: " << dbConfig_.FolderPath; + } +} + + +SqliteDatabase::~SqliteDatabase() { } + + +bool SqliteDatabase::open() +{ + if (!is_open()) { + sqlite3* db_ptr = nullptr; + + const auto code = sqlite3_open_v2(dbConfig_.ConnectionString.c_str(), &db_ptr, + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, nullptr + ); + + db_.reset(db_ptr); + + if (code) { + BOOST_LOG_TRIVIAL(fatal) << "Cannot open database: " << sqlite3_errmsg(db_.get()); + } else { + is_open_ = true; + } + } + + return is_open(); +} + + +void SqliteDatabase::close() +{ + db_.reset(); + is_open_ = false; +} + + +bool SqliteDatabase::create_table_history() +{ + return create_table(create_table_history_query_); +} + + +bool SqliteDatabase::create_table_logins() +{ + return create_table(create_table_logins_query_); +} + + +bool SqliteDatabase::save_text_message(TextSendData message) +{ + //const auto dt = message->get_datetime(); + const DateTime dt; + const std::string str_datetime = boost::str( + boost::format("%1$d-%2$#02d-%3$#02d %4$#02s:%5$#02s:%6$#02s") + % (static_cast(dt.year) + 2000u) % static_cast(dt.month) % static_cast(dt.day) + % static_cast(dt.hours) % static_cast(dt.month) % static_cast(dt.seconds) + ); + + const std::string insert_query = + "INSERT INTO history VALUES ("s + + "'"s + message.login + "', "s + + std::to_string(message.room_id) + ", "s + + "strftime('%s', '"s + str_datetime + "'), "s + + "'"s + message.text + "');"s; + + char* err_msg_ptr = nullptr; + + auto code = sqlite3_exec(db_.get(), insert_query.c_str(), + [](void*, int, char**, char**) { return 0; }, + 0, &err_msg_ptr + ); + const std::unique_ptr err_msg{err_msg_ptr, &sqlite3_free}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "SQL error save_text_message: " << err_msg.get(); + return false; + } + + return true; +} + + +std::deque SqliteDatabase::get_history(identifier_t roomid) const +{ + const auto query = + "SELECT author, room_id, datetime(datetime, 'unixepoch'), message "s + + "FROM history WHERE room_id="s + std::to_string(roomid) + ";"s; + + sqlite3_stmt* stmt_ptr = nullptr; + + auto code = sqlite3_prepare_v2(db_.get(), query.c_str(), -1, &stmt_ptr, nullptr); + + const std::unique_ptr stmt{stmt_ptr, &sqlite3_finalize}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "get_history: while compiling sql: " << sqlite3_errmsg(db_.get()); + return {}; + } + + std::deque history_room; + + while ((code = sqlite3_step(stmt.get())) == SQLITE_ROW ) { + const std::string author = static_cast(sqlite3_column_blob(stmt.get(), 0)); + const int room_id = sqlite3_column_int(stmt.get(), 1); + const std::string dt = static_cast(sqlite3_column_blob(stmt.get(), 2)); + const std::string message = static_cast(sqlite3_column_blob(stmt.get(), 3)); + + BOOST_LOG_TRIVIAL(info) << "DB: " << author << " " << room_id << " " << dt << " " << message; + + history_room.push_back(TextSendData{room_id, author, message}); + } + + if (code != SQLITE_DONE) { + BOOST_LOG_TRIVIAL(error) << "get_history: while performing sql: " << sqlite3_errmsg(db_.get()) << " code = " << code; + } + + return history_room; +} + + +bool SqliteDatabase::add_logins(std::string login, identifier_t login_id, std::string password) +{ + const auto query = + "INSERT INTO logins VALUES ("s + + "'"s + std::move(login) + "', "s + + std::to_string(login_id) + ", "s + + "'"s + std::move(password) + "');"s; + + char* err_msg_ptr = nullptr; + + const auto code = sqlite3_exec(db_.get(), query.c_str(), + [](void*, int, char**, char**) { return 0; }, + 0, &err_msg_ptr + ); + const std::unique_ptr err_msg{err_msg_ptr, &sqlite3_free}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "SQL error add_logins" << err_msg.get(); + return false; + } + + return true; +} + + +identifier_t SqliteDatabase::get_loginid(std::string login) const +{ + identifier_t result = -1; + + const auto query = + "SELECT * FROM logins WHERE login='"s + login + "';"s; + + sqlite3_stmt* stmt_ptr = nullptr; + + auto code = sqlite3_prepare_v2(db_.get(), query.c_str(), -1, &stmt_ptr, nullptr); + + std::unique_ptr stmt{stmt_ptr, &sqlite3_finalize}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "get_loginid: while compiling sql: " << sqlite3_errmsg(db_.get()); + return result; + } + + while ((code = sqlite3_step(stmt.get())) == SQLITE_ROW) { + result = sqlite3_column_int(stmt.get(), 1); + } + + if (code != SQLITE_DONE) { + BOOST_LOG_TRIVIAL(error) << "get_loginid: while performing sql: " << sqlite3_errmsg(db_.get()); + } + + return result; +} + + +identifier_t SqliteDatabase::check_client(std::string login, std::string password) const +{ + identifier_t result = -1; + + sqlite3_stmt* stmt_ptr = nullptr; + + const auto query = + "SELECT * FROM logins WHERE login='" + login + "';"; + + auto code = sqlite3_prepare_v2(db_.get(), query.c_str(), -1, &stmt_ptr, nullptr); + + std::unique_ptr stmt{stmt_ptr, &sqlite3_finalize}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "check_client: while compiling sql: " << sqlite3_errmsg(db_.get()); + return result; + } + + while ((code = sqlite3_step(stmt.get())) == SQLITE_ROW) { + if (password == reinterpret_cast(sqlite3_column_blob(stmt.get(), 2))) { + result = sqlite3_column_int(stmt.get(), 1); + } + } + + if (code != SQLITE_DONE) { + BOOST_LOG_TRIVIAL(error) << "check_client: while preforming sql: " << sqlite3_errmsg(db_.get()) << " code = " << code; + } + + return result; +} + + +std::string SqliteDatabase::create_table_history_query_ = + "CREATE TABLE IF NOT EXISTS history ("s + + "author VARCHAR["s + std::to_string(Block::LoginName) + "], "s + + "room_id INTEGER, "s + + "datetime INTEGER, "s + + "message VARCHAR["s + std::to_string(Block::TextMessage) + "]);"s; + + +std::string SqliteDatabase::create_table_logins_query_ = + "CREATE TABLE IF NOT EXISTS logins ("s + + "login VARCHAR["s + std::to_string(Block::LoginName) + "], "s + + "login_id INTEGER, "s + + "password VARCHAR["s + std::to_string(Block::Password) + "]);"s; + + +bool SqliteDatabase::create_table(const std::string& query) +{ + char* err_msg_ptr = nullptr; + + const auto code = sqlite3_exec(db_.get(), query.c_str(), + [](void*, int, char**, char**) { return 0; }, + 0, &err_msg_ptr + ); + const std::unique_ptr err_msg{err_msg_ptr, &free}; + + if (code != SQLITE_OK) { + BOOST_LOG_TRIVIAL(error) << "SQL error create_table" << err_msg.get(); + return false; + } + + return true; +} + +} // namespace Storage diff --git a/src/server/storage/sqlitedatabase.h b/src/server/storage/sqlitedatabase.h new file mode 100644 index 0000000..79408e2 --- /dev/null +++ b/src/server/storage/sqlitedatabase.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "database.h" + +namespace Storage +{ + +class SqliteDatabase : public Database +{ +public: + SqliteDatabase(DatabaseConfiguration dbConfig); + virtual ~SqliteDatabase(); + + virtual bool open() override; + virtual void close() override; + + virtual bool create_table_history() override; + virtual bool create_table_logins() override; + + virtual bool save_text_message(TextSendData message) override; + + virtual std::deque get_history(identifier_t roomid) const override; + + virtual bool add_logins(std::string login, identifier_t login_id, std::string password) override; + + virtual identifier_t get_loginid(std::string login) const override; + + virtual identifier_t check_client(std::string login, std::string password) const override; + +private: + std::unique_ptr db_; + static std::string create_table_history_query_; + static std::string create_table_logins_query_; + + bool create_table(const std::string& query); +}; + +} // namespace Storage