diff --git a/CMakeLists.txt b/CMakeLists.txt index d7cb5928..3f4802e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,9 @@ find_package(log4cxx) set(event_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") find_package(event) +set(pqxx_DIR "${CMAKE_SOURCE_DIR}/cmake_modules") +find_package(pqxx) + find_package(Doxygen) INCLUDE(FindQt4) @@ -101,6 +104,16 @@ else (MYSQL_FOUND) message("MySQL : no (install mysql-devel)") endif (MYSQL_FOUND) +if (PQXX_FOUND) + ADD_DEFINITIONS(-DWITH_PQXX) + include_directories(${PQXX_INCLUDE_DIR}) + message("PostgreSQL : yes") +else (PQXX_FOUND) + set(PQXX_LIBRARY "") + set(PQ_LIBRARY "") + message("PostgreSQL : no (install libpqxx-devel)") +endif (PQXX_FOUND) + if (PROTOBUF_FOUND) ADD_DEFINITIONS(-DWITH_PROTOBUF) include_directories(${PROTOBUF_INCLUDE_DIRS}) diff --git a/cmake_modules/pqxxConfig.cmake b/cmake_modules/pqxxConfig.cmake new file mode 100644 index 00000000..9c53550c --- /dev/null +++ b/cmake_modules/pqxxConfig.cmake @@ -0,0 +1,16 @@ +FIND_PATH(PQXX_INCLUDE_DIR pqxx/pqxx PATHS) +MARK_AS_ADVANCED(PQXX_INCLUDE_DIR) + +FIND_LIBRARY(PQXX_LIBRARY pqxx ) +MARK_AS_ADVANCED(PQXX_LIBRARY) + +FIND_LIBRARY(PQ_LIBRARY pq ) +MARK_AS_ADVANCED(PQ_LIBRARY) + +if(PQXX_LIBRARY AND PQ_LIBRARY AND PQXX_INCLUDE_DIR) + set( PQXX_FOUND 1 ) + message( STATUS "Found pqxx: ${PQXX_LIBRARY}, ${PQ_LIBRARY}, ${PQXX_INCLUDE_DIR}") +else() + message(STATUS "Could NOT find pqxx and pq library") +endif() + diff --git a/include/transport/pqxxbackend.h b/include/transport/pqxxbackend.h new file mode 100644 index 00000000..77df7843 --- /dev/null +++ b/include/transport/pqxxbackend.h @@ -0,0 +1,106 @@ +/** + * libtransport -- C++ library for easy XMPP Transports development + * + * Copyright (C) 2011, Jan Kaluza + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#pragma once + +#ifdef WITH_PQXX + +#include +#include +#include "Swiften/Swiften.h" +#include "transport/storagebackend.h" +#include "transport/config.h" +#include "mysql.h" + +namespace Transport { + +/// Used to store transport data into SQLite3 database. +class PQXXBackend : public StorageBackend +{ + public: + /// Creates new PQXXBackend instance. + /// \param config cofiguration, this class uses following Config values: + /// - database.database - path to SQLite3 database file, database file is created automatically + /// - service.prefix - prefix for tables created by createDatabase method + PQXXBackend(Config *config); + + /// Destructor. + ~PQXXBackend(); + + /// Connects to the database and creates it if it's needed. This method call createDatabase() function + /// automatically. + /// \return true if database is opened successfully. + bool connect(); + void disconnect(); + + /// Creates database structure. + /// \see connect() + /// \return true if database structure has been created successfully. Note that it returns True also if database structure + /// already exists. + bool createDatabase(); + + /// Stores user into database. + /// \param user user struct containing all information about user which have to be stored + void setUser(const UserInfo &user); + + /// Gets user data from database and stores them into user reference. + /// \param barejid barejid of user + /// \param user UserInfo object where user data will be stored + /// \return true if user has been found in database + bool getUser(const std::string &barejid, UserInfo &user); + + /// Changes users online state variable in database. + /// \param id id of user - UserInfo.id + /// \param online online state + void setUserOnline(long id, bool online); + + /// Removes user and all connected data from database. + /// \param id id of user - UserInfo.id + /// \return true if user has been found in database and removed + bool removeUser(long id); + + /// Returns JIDs of all buddies in user's roster. + /// \param id id of user - UserInfo.id + /// \param roster string list used to store user's roster + /// \return true if user has been found in database and roster has been fetched + bool getBuddies(long id, std::list &roster); + + bool getOnlineUsers(std::vector &users); + + long addBuddy(long userId, const BuddyInfo &buddyInfo); + + void updateBuddy(long userId, const BuddyInfo &buddyInfo); + void removeBuddy(long id) {} + + void getUserSetting(long userId, const std::string &variable, int &type, std::string &value); + void updateUserSetting(long userId, const std::string &variable, const std::string &value); + + void beginTransaction(); + void commitTransaction(); + + private: + bool exec(const std::string &query); + Config *m_config; + std::string m_prefix; +}; + +} + +#endif diff --git a/spectrum/src/main.cpp b/spectrum/src/main.cpp index c0b8fb9b..ea8f33d6 100644 --- a/spectrum/src/main.cpp +++ b/spectrum/src/main.cpp @@ -5,6 +5,7 @@ #include "transport/logger.h" #include "transport/sqlite3backend.h" #include "transport/mysqlbackend.h" +#include "transport/pqxxbackend.h" #include "transport/userregistration.h" #include "transport/networkpluginserver.h" #include "transport/admininterface.h" @@ -379,7 +380,23 @@ int main(int argc, char **argv) } #endif - if (CONFIG_STRING(&config, "database.type") != "mysql" && CONFIG_STRING(&config, "database.type") != "sqlite3") { +#ifdef WITH_PQXX + if (CONFIG_STRING(&config, "database.type") == "pqxx") { + storageBackend = new PQXXBackend(&config); + if (!storageBackend->connect()) { + std::cerr << "Can't connect to database. Check the log to find out the reason.\n"; + return -1; + } + } +#else + if (CONFIG_STRING(&config, "database.type") == "pqxx") { + std::cerr << "Spectrum2 is not compiled with pqxx backend.\n"; + return -2; + } +#endif + + if (CONFIG_STRING(&config, "database.type") != "mysql" && CONFIG_STRING(&config, "database.type") != "sqlite3" + && CONFIG_STRING(&config, "database.type") != "pqxx") { std::cerr << "Unknown storage backend " << CONFIG_STRING(&config, "database.type") << "\n"; return -2; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a258e7a..ac077b20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,9 +35,9 @@ if (CMAKE_COMPILER_IS_GNUCXX) endif() if (WIN32) - TARGET_LINK_LIBRARIES(transport ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES}) + TARGET_LINK_LIBRARIES(transport ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES}) else (WIN32) - TARGET_LINK_LIBRARIES(transport ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES} ${POPT_LIBRARY}) + TARGET_LINK_LIBRARIES(transport ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES} ${POPT_LIBRARY}) endif(WIN32) SET_TARGET_PROPERTIES(transport PROPERTIES diff --git a/src/pqxxbackend.cpp b/src/pqxxbackend.cpp new file mode 100644 index 00000000..8b326a51 --- /dev/null +++ b/src/pqxxbackend.cpp @@ -0,0 +1,329 @@ +/** + * libtransport -- C++ library for easy XMPP Transports development + * + * Copyright (C) 2011, Jan Kaluza + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifdef WITH_PQXX + +#include "transport/pqxxbackend.h" +#include "transport/util.h" +#include +#include "log4cxx/logger.h" + +using namespace log4cxx; +using namespace boost; + +namespace Transport { + +static LoggerPtr logger = Logger::getLogger("PQXXBackend"); + +PQXXBackend::PQXXBackend(Config *config) { + m_config = config; + m_prefix = CONFIG_STRING(m_config, "database.prefix"); +} + +PQXXBackend::~PQXXBackend(){ + disconnect(); +} + +void PQXXBackend::disconnect() { + LOG4CXX_INFO(logger, "Disconnecting"); +} + +bool PQXXBackend::connect() { + LOG4CXX_INFO(logger, "Connecting PostgreSQL server " << CONFIG_STRING(m_config, "database.server") << ", user " << + CONFIG_STRING(m_config, "database.user") << ", database " << CONFIG_STRING(m_config, "database.database") << + ", port " << CONFIG_INT(m_config, "database.port") + ); + + createDatabase(); + + return true; +} + +bool PQXXBackend::createDatabase() { + int not_exist = exec("CREATE TABLE IF NOT EXISTS `" + m_prefix + "buddies` (" + "`id` int(10) unsigned NOT NULL auto_increment," + "`user_id` int(10) unsigned NOT NULL," + "`uin` varchar(255) collate utf8_bin NOT NULL," + "`subscription` enum('to','from','both','ask','none') collate utf8_bin NOT NULL," + "`nickname` varchar(255) collate utf8_bin NOT NULL," + "`groups` varchar(255) collate utf8_bin NOT NULL," + "`flags` smallint(4) NOT NULL DEFAULT '0'," + "PRIMARY KEY (`id`)," + "UNIQUE KEY `user_id` (`user_id`,`uin`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;"); + + if (not_exist) { + exec("CREATE TABLE IF NOT EXISTS `" + m_prefix + "buddies_settings` (" + "`user_id` int(10) unsigned NOT NULL," + "`buddy_id` int(10) unsigned NOT NULL," + "`var` varchar(50) collate utf8_bin NOT NULL," + "`type` smallint(4) unsigned NOT NULL," + "`value` varchar(255) collate utf8_bin NOT NULL," + "PRIMARY KEY (`buddy_id`,`var`)," + "KEY `user_id` (`user_id`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;"); + + exec("CREATE TABLE IF NOT EXISTS `" + m_prefix + "users` (" + "`id` int(10) unsigned NOT NULL auto_increment," + "`jid` varchar(255) collate utf8_bin NOT NULL," + "`uin` varchar(4095) collate utf8_bin NOT NULL," + "`password` varchar(255) collate utf8_bin NOT NULL," + "`language` varchar(25) collate utf8_bin NOT NULL," + "`encoding` varchar(50) collate utf8_bin NOT NULL default 'utf8'," + "`last_login` datetime," + "`vip` tinyint(1) NOT NULL default '0'," + "`online` tinyint(1) NOT NULL default '0'," + "PRIMARY KEY (`id`)," + "UNIQUE KEY `jid` (`jid`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;"); + + exec("CREATE TABLE IF NOT EXISTS `" + m_prefix + "users_settings` (" + "`user_id` int(10) unsigned NOT NULL," + "`var` varchar(50) collate utf8_bin NOT NULL," + "`type` smallint(4) unsigned NOT NULL," + "`value` varchar(255) collate utf8_bin NOT NULL," + "PRIMARY KEY (`user_id`,`var`)," + "KEY `user_id` (`user_id`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;"); + + exec("CREATE TABLE IF NOT EXISTS `" + m_prefix + "db_version` (" + "`ver` int(10) unsigned NOT NULL default '1'," + "UNIQUE KEY `ver` (`ver`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;"); + + exec("INSERT IGNORE INTO db_version (ver) VALUES ('2');"); + } + + return true; +} + +bool PQXXBackend::exec(const std::string &query) { +// if (mysql_query(&m_conn, query.c_str())) { +// LOG4CXX_ERROR(logger, query << " " << mysql_error(&m_conn)); +// return false; +// } + return true; +} + +void PQXXBackend::setUser(const UserInfo &user) { +// std::string encrypted = user.password; +// if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) { +// encrypted = Util::encryptPassword(encrypted, CONFIG_STRING(m_config, "database.encryption_key")); +// } +// *m_setUser << user.jid << user.uin << encrypted << user.language << user.encoding << user.vip << user.uin << encrypted; +// EXEC(m_setUser, setUser(user)); +} + +bool PQXXBackend::getUser(const std::string &barejid, UserInfo &user) { +// *m_getUser << barejid; +// EXEC(m_getUser, getUser(barejid, user)); +// if (!exec_ok) +// return false; + + int ret = false; +// while (m_getUser->fetch() == 0) { +// ret = true; +// *m_getUser >> user.id >> user.jid >> user.uin >> user.password >> user.encoding >> user.language >> user.vip; + +// if (!CONFIG_STRING(m_config, "database.encryption_key").empty()) { +// user.password = Util::decryptPassword(user.password, CONFIG_STRING(m_config, "database.encryption_key")); +// } +// } + + return ret; +} + +void PQXXBackend::setUserOnline(long id, bool online) { +// *m_setUserOnline << online << id; +// EXEC(m_setUserOnline, setUserOnline(id, online)); +} + +bool PQXXBackend::getOnlineUsers(std::vector &users) { +// EXEC(m_getOnlineUsers, getOnlineUsers(users)); +// if (!exec_ok) +// return false; + +// std::string jid; +// while (m_getOnlineUsers->fetch() == 0) { +// *m_getOnlineUsers >> jid; +// users.push_back(jid); +// } + + return true; +} + +long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { +// "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)" +// std::string groups = Util::serializeGroups(buddyInfo.groups); +// *m_addBuddy << userId << buddyInfo.legacyName << buddyInfo.subscription; +// *m_addBuddy << groups; +// *m_addBuddy << buddyInfo.alias << buddyInfo.flags; + +// EXEC(m_addBuddy, addBuddy(userId, buddyInfo)); + +// long id = (long) mysql_insert_id(&m_conn); + +// INSERT OR REPLACE INTO " + m_prefix + "buddies_settings (user_id, buddy_id, var, type, value) VALUES (?, ?, ?, ?, ?) +// if (!buddyInfo.settings.find("icon_hash")->second.s.empty()) { +// *m_updateBuddySetting << userId << id << buddyInfo.settings.find("icon_hash")->first << (int) TYPE_STRING << buddyInfo.settings.find("icon_hash")->second.s << buddyInfo.settings.find("icon_hash")->second.s; +// EXEC(m_updateBuddySetting, addBuddy(userId, buddyInfo)); +// } + + return 0; +} + +void PQXXBackend::updateBuddy(long userId, const BuddyInfo &buddyInfo) { +// "UPDATE " + m_prefix + "buddies SET groups=?, nickname=?, flags=?, subscription=? WHERE user_id=? AND uin=?" +// std::string groups = Util::serializeGroups(buddyInfo.groups); +// *m_updateBuddy << groups; +// *m_updateBuddy << buddyInfo.alias << buddyInfo.flags << buddyInfo.subscription; +// *m_updateBuddy << userId << buddyInfo.legacyName; + +// EXEC(m_updateBuddy, updateBuddy(userId, buddyInfo)); +} + +bool PQXXBackend::getBuddies(long id, std::list &roster) { +// SELECT id, uin, subscription, nickname, groups, flags FROM " + m_prefix + "buddies WHERE user_id=? ORDER BY id ASC +// *m_getBuddies << id; + +// "SELECT buddy_id, type, var, value FROM " + m_prefix + "buddies_settings WHERE user_id=? ORDER BY buddy_id ASC" +// *m_getBuddiesSettings << id; + +// SettingVariableInfo var; +// long buddy_id = -1; +// std::string key; + +// EXEC(m_getBuddies, getBuddies(id, roster)); +// if (!exec_ok) +// return false; + +// while (m_getBuddies->fetch() == 0) { +// BuddyInfo b; + +// std::string group; +// *m_getBuddies >> b.id >> b.legacyName >> b.subscription >> b.alias >> group >> b.flags; + +// if (!group.empty()) { +// b.groups = Util::deserializeGroups(group); +// } + +// roster.push_back(b); +// } + +// EXEC(m_getBuddiesSettings, getBuddies(id, roster)); +// if (!exec_ok) +// return false; + +// BOOST_FOREACH(BuddyInfo &b, roster) { +// if (buddy_id == b.id) { +//// std::cout << "Adding buddy info setting " << key << "\n"; +// b.settings[key] = var; +// buddy_id = -1; +// } + +// while(buddy_id == -1 && m_getBuddiesSettings->fetch() == 0) { +// std::string val; +// *m_getBuddiesSettings >> buddy_id >> var.type >> key >> val; + +// switch (var.type) { +// case TYPE_BOOLEAN: +// var.b = atoi(val.c_str()); +// break; +// case TYPE_STRING: +// var.s = val; +// break; +// default: +// if (buddy_id == b.id) { +// buddy_id = -1; +// } +// continue; +// break; +// } +// if (buddy_id == b.id) { +//// std::cout << "Adding buddy info setting " << key << "=" << val << "\n"; +// b.settings[key] = var; +// buddy_id = -1; +// } +// } +// } + +// while(m_getBuddiesSettings->fetch() == 0) { +// // TODO: probably remove those settings, because there's no buddy for them. +// // It should not happend, but one never know... +// } + + return true; +} + +bool PQXXBackend::removeUser(long id) { +// *m_removeUser << (int) id; +// EXEC(m_removeUser, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserSettings << (int) id; +// EXEC(m_removeUserSettings, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserBuddies << (int) id; +// EXEC(m_removeUserBuddies, removeUser(id)); +// if (!exec_ok) +// return false; + +// *m_removeUserBuddiesSettings << (int) id; +// EXEC(m_removeUserBuddiesSettings, removeUser(id)); +// if (!exec_ok) +// return false; + + return true; +} + +void PQXXBackend::getUserSetting(long id, const std::string &variable, int &type, std::string &value) { +//// "SELECT type, value FROM " + m_prefix + "users_settings WHERE user_id=? AND var=?" +// *m_getUserSetting << id << variable; +// EXEC(m_getUserSetting, getUserSetting(id, variable, type, value)); +// if (m_getUserSetting->fetch() != 0) { +//// "INSERT INTO " + m_prefix + "users_settings (user_id, var, type, value) VALUES (?,?,?,?)" +// *m_setUserSetting << id << variable << type << value; +// EXEC(m_setUserSetting, getUserSetting(id, variable, type, value)); +// } +// else { +// *m_getUserSetting >> type >> value; +// } +} + +void PQXXBackend::updateUserSetting(long id, const std::string &variable, const std::string &value) { +//// "UPDATE " + m_prefix + "users_settings SET value=? WHERE user_id=? AND var=?" +// *m_updateUserSetting << value << id << variable; +// EXEC(m_updateUserSetting, updateUserSetting(id, variable, value)); +} + +void PQXXBackend::beginTransaction() { +// exec("START TRANSACTION;"); +} + +void PQXXBackend::commitTransaction() { +// exec("COMMIT;"); +} + +} + +#endif