From 7a46b1ddf4193c2f4ca789ef86b099f9d70842f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20Badea?= Date: Sun, 19 Aug 2012 21:34:48 +0300 Subject: Use appropriate folder for storing the history file. diff --git a/Swift/Controllers/HistoryController.cpp b/Swift/Controllers/HistoryController.cpp index 6ab1235..d730236 100644 --- a/Swift/Controllers/HistoryController.cpp +++ b/Swift/Controllers/HistoryController.cpp @@ -5,19 +5,16 @@ */ #include -#include +#include #include #include namespace Swift { -HistoryController::HistoryController() : remoteArchiveSupported_(false) { - std::string file("testDB.db"); - localHistory_ = new SQLiteHistoryManager(file); +HistoryController::HistoryController(HistoryStorage* localHistoryStorage) : localHistory_(localHistoryStorage) { } HistoryController::~HistoryController() { - delete localHistory_; } void HistoryController::addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp) { diff --git a/Swift/Controllers/HistoryController.h b/Swift/Controllers/HistoryController.h index f231724..8c86409 100644 --- a/Swift/Controllers/HistoryController.h +++ b/Swift/Controllers/HistoryController.h @@ -12,14 +12,14 @@ #include #include #include -#include +#include namespace Swift { class JID; class HistoryController { public: - HistoryController(); + HistoryController(HistoryStorage* localHistoryStorage); ~HistoryController(); void addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp); @@ -34,7 +34,7 @@ namespace Swift { boost::signal onNewMessage; private: - HistoryManager* localHistory_; + HistoryStorage* localHistory_; bool remoteArchiveSupported_; }; } diff --git a/Swift/Controllers/HistoryViewController.h b/Swift/Controllers/HistoryViewController.h index 283254d..f44c968 100644 --- a/Swift/Controllers/HistoryViewController.h +++ b/Swift/Controllers/HistoryViewController.h @@ -11,9 +11,9 @@ #include #include -#include #include #include +#include #include namespace Swift { @@ -25,7 +25,6 @@ namespace Swift { class HistoryController; class NickResolver; class AvatarManager; - class HistoryMessage; class HistoryViewController { public: diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 9ac97f4..2955cee 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -172,7 +172,6 @@ MainController::MainController( idleDetector_->onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1)); xmlConsoleController_ = new XMLConsoleController(uiEventStream_, uiFactory_); - historyController_ = new HistoryController(); fileTransferListController_ = new FileTransferListController(uiEventStream_, uiFactory_); @@ -197,7 +196,6 @@ MainController::~MainController() { resetClient(); delete fileTransferListController_; delete xmlConsoleController_; - delete historyController_; delete xmppURIController_; delete soundEventController_; delete systemTrayController_; @@ -223,6 +221,8 @@ void MainController::resetClient() { eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; + delete historyController_; + historyController_ = NULL; delete historyViewController_; historyViewController_ = NULL; delete ftOverview_; @@ -301,9 +301,9 @@ void MainController::handleConnected() { * want to have the user's nick available and this means it will * be before they receive stanzas that need it (e.g. bookmarks).*/ client_->getVCardManager()->requestOwnVCard(); + historyController_ = new HistoryController(storages_->getHistoryStorage()); chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_); - historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp index 6447099..27c245d 100644 --- a/Swift/Controllers/Storages/FileStorages.cpp +++ b/Swift/Controllers/Storages/FileStorages.cpp @@ -9,6 +9,7 @@ #include "Swift/Controllers/Storages/AvatarFileStorage.h" #include "Swift/Controllers/Storages/CapsFileStorage.h" #include "Swift/Controllers/Storages/RosterFileStorage.h" +#include namespace Swift { @@ -18,6 +19,7 @@ FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& ji capsStorage = new CapsFileStorage(baseDir / "caps"); avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars"); rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml"); + historyStorage = new SQLiteHistoryStorage((baseDir / "history.db").string()); } FileStorages::~FileStorages() { @@ -25,6 +27,7 @@ FileStorages::~FileStorages() { delete avatarStorage; delete capsStorage; delete vcardStorage; + delete historyStorage; } VCardStorage* FileStorages::getVCardStorage() const { @@ -43,4 +46,8 @@ RosterStorage* FileStorages::getRosterStorage() const { return rosterStorage; } +HistoryStorage* FileStorages::getHistoryStorage() const { + return historyStorage; +} + } diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h index 28df314..5e89db8 100644 --- a/Swift/Controllers/Storages/FileStorages.h +++ b/Swift/Controllers/Storages/FileStorages.h @@ -15,6 +15,7 @@ namespace Swift { class AvatarFileStorage; class CapsFileStorage; class RosterFileStorage; + class HistoryStorage; class JID; /** @@ -43,11 +44,13 @@ namespace Swift { virtual AvatarStorage* getAvatarStorage() const; virtual CapsStorage* getCapsStorage() const; virtual RosterStorage* getRosterStorage() const; + virtual HistoryStorage* getHistoryStorage() const; private: VCardFileStorage* vcardStorage; AvatarFileStorage* avatarStorage; CapsFileStorage* capsStorage; RosterFileStorage* rosterStorage; + HistoryStorage* historyStorage; }; } diff --git a/Swiften/Client/MemoryStorages.cpp b/Swiften/Client/MemoryStorages.cpp index fe171f7..9bae4e3 100644 --- a/Swiften/Client/MemoryStorages.cpp +++ b/Swiften/Client/MemoryStorages.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Swift { @@ -17,6 +18,7 @@ MemoryStorages::MemoryStorages() { capsStorage = new CapsMemoryStorage(); avatarStorage = new AvatarMemoryStorage(); rosterStorage = new RosterMemoryStorage(); + historyStorage = new SQLiteHistoryStorage(":memory:"); } MemoryStorages::~MemoryStorages() { @@ -24,6 +26,7 @@ MemoryStorages::~MemoryStorages() { delete avatarStorage; delete capsStorage; delete vcardStorage; + delete historyStorage; } VCardStorage* MemoryStorages::getVCardStorage() const { @@ -42,5 +45,9 @@ RosterStorage* MemoryStorages::getRosterStorage() const { return rosterStorage; } +HistoryStorage* MemoryStorages::getHistoryStorage() const { + return historyStorage; +} + } diff --git a/Swiften/Client/MemoryStorages.h b/Swiften/Client/MemoryStorages.h index ca01a7a..403a89a 100644 --- a/Swiften/Client/MemoryStorages.h +++ b/Swiften/Client/MemoryStorages.h @@ -24,11 +24,13 @@ namespace Swift { virtual AvatarStorage* getAvatarStorage() const; virtual CapsStorage* getCapsStorage() const; virtual RosterStorage* getRosterStorage() const; + virtual HistoryStorage* getHistoryStorage() const; private: VCardMemoryStorage* vcardStorage; AvatarStorage* avatarStorage; CapsStorage* capsStorage; RosterStorage* rosterStorage; + HistoryStorage* historyStorage; }; } diff --git a/Swiften/Client/Storages.h b/Swiften/Client/Storages.h index 89b770c..76650a6 100644 --- a/Swiften/Client/Storages.h +++ b/Swiften/Client/Storages.h @@ -13,6 +13,7 @@ namespace Swift { class AvatarStorage; class CapsStorage; class RosterStorage; + class HistoryStorage; /** * An interface to hold storage classes for different @@ -26,5 +27,6 @@ namespace Swift { virtual AvatarStorage* getAvatarStorage() const = 0; virtual CapsStorage* getCapsStorage() const = 0; virtual RosterStorage* getRosterStorage() const = 0; + virtual HistoryStorage* getHistoryStorage() const = 0; }; } diff --git a/Swiften/History/HistoryManager.cpp b/Swiften/History/HistoryManager.cpp deleted file mode 100644 index 7eb66ab..0000000 --- a/Swiften/History/HistoryManager.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include - -namespace Swift { - -HistoryManager::~HistoryManager() { -} - -} diff --git a/Swiften/History/HistoryManager.h b/Swiften/History/HistoryManager.h deleted file mode 100644 index 33e9143..0000000 --- a/Swiften/History/HistoryManager.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Swift { - typedef std::map > ContactsMap; - - class HistoryManager { - /** - * Messages are stored using localtime timestamps. - */ - public: - virtual ~HistoryManager(); - - virtual void addMessage(const HistoryMessage& message) = 0; - - virtual std::vector getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; - virtual std::vector getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; - virtual std::vector getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; - virtual ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const = 0; - virtual boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const = 0; - }; -} diff --git a/Swiften/History/HistoryStorage.h b/Swiften/History/HistoryStorage.h new file mode 100644 index 0000000..fcf28b5 --- /dev/null +++ b/Swiften/History/HistoryStorage.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Swift { + typedef std::map > ContactsMap; + + class HistoryStorage { + /** + * Messages are stored using localtime timestamps. + */ + public: + virtual ~HistoryStorage() {}; + + virtual void addMessage(const HistoryMessage& message) = 0; + virtual std::vector getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; + virtual std::vector getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; + virtual std::vector getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const = 0; + virtual ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const = 0; + virtual boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const = 0; + }; +} diff --git a/Swiften/History/SConscript b/Swiften/History/SConscript index 975f839..bc0d64c 100644 --- a/Swiften/History/SConscript +++ b/Swiften/History/SConscript @@ -5,7 +5,6 @@ if myenv["target"] == "native": myenv.MergeFlags(swiften_env.get("SQLITE_FLAGS", {})) objects = myenv.SwiftenObject([ - "HistoryManager.cpp", - "SQLiteHistoryManager.cpp", + "SQLiteHistoryStorage.cpp", ]) swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/History/SQLiteHistoryManager.cpp b/Swiften/History/SQLiteHistoryManager.cpp deleted file mode 100644 index e385b1e..0000000 --- a/Swiften/History/SQLiteHistoryManager.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include -#include - -#include -#include -#include - -inline std::string getEscapedString(const std::string& s) { - std::string result(s); - - size_t pos = result.find('\''); - while (pos != std::string::npos) { - result.insert(pos, "'"); - pos = result.find('\'', pos + 2); - } - return result; -} - -namespace Swift { - -SQLiteHistoryManager::SQLiteHistoryManager(const std::string& file) : db_(0) { - sqlite3_open(file.c_str(), &db_); - if (!db_) { - std::cerr << "Error opening database " << file << std::endl; // FIXME - } - - char* errorMessage; - int result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS messages('message' STRING, 'fromBare' INTEGER, 'fromResource' STRING, 'toBare' INTEGER, 'toResource' STRING, 'type' INTEGER, 'time' INTEGER, 'offset' INTEGER)", 0, 0, &errorMessage); - if (result != SQLITE_OK) { - std::cerr << "SQL Error: " << errorMessage << std::endl; - sqlite3_free(errorMessage); - } - - result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS jids('id' INTEGER PRIMARY KEY ASC AUTOINCREMENT, 'jid' STRING UNIQUE NOT NULL)", 0, 0, &errorMessage); - if (result != SQLITE_OK) { - std::cerr << "SQL Error: " << errorMessage << std::endl; - sqlite3_free(errorMessage); - } -} - -SQLiteHistoryManager::~SQLiteHistoryManager() { - sqlite3_close(db_); -} - -void SQLiteHistoryManager::addMessage(const HistoryMessage& message) { - int secondsSinceEpoch = (message.getTime() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds(); - - std::string statement = std::string("INSERT INTO messages('message', 'fromBare', 'fromResource', 'toBare', 'toResource', 'type', 'time', 'offset') VALUES(") + - "'" + getEscapedString(message.getMessage()) + "', " + - boost::lexical_cast(getIDForJID(message.getFromJID().toBare())) + ", '" + - getEscapedString(message.getFromJID().getResource()) + "', " + - boost::lexical_cast(getIDForJID(message.getToJID().toBare())) + ", '" + - getEscapedString(message.getToJID().getResource()) + "', " + - boost::lexical_cast(message.getType()) + ", " + - boost::lexical_cast(secondsSinceEpoch) + ", " + - boost::lexical_cast(message.getOffset()) + ")"; - char* errorMessage; - int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage); - if (result != SQLITE_OK) { - std::cerr << "SQL Error: " << errorMessage << std::endl; - sqlite3_free(errorMessage); - } -} - -std::vector SQLiteHistoryManager::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { - sqlite3_stmt* selectStatement; - - boost::optional selfID = getIDFromJID(selfJID.toBare()); - boost::optional contactID = getIDFromJID(contactJID.toBare()); - - if (!selfID || !contactID) { - // JIDs missing from the database - return std::vector(); - } - - std::string selectQuery = "SELECT * FROM messages WHERE (type=" + boost::lexical_cast(type); - if (contactJID.isBare()) { - // match only bare jid - selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND toBare=" + - boost::lexical_cast(*contactID) + ") OR (fromBare=" + - boost::lexical_cast(*contactID) + " AND toBare=" + boost::lexical_cast(*selfID) + ")))"; - } - else { - // match resource too - selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND (toBare=" + - boost::lexical_cast(*contactID) +" AND toResource='" + - getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" + - boost::lexical_cast(*contactID) + " AND fromResource='" + - getEscapedString(contactJID.getResource()) + "') AND toBare=" + - boost::lexical_cast(*selfID) + ")))"; - } - - if (!date.is_not_a_date()) { - int lowerBound = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds(); - int upperBound = lowerBound + 86400; - - selectQuery += " AND (time>=" + boost::lexical_cast(lowerBound) + - " AND time<" + boost::lexical_cast(upperBound) + ")"; - } - - int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - r = sqlite3_step(selectStatement); - - // Retrieve result - std::vector result; - while (r == SQLITE_ROW) { - std::string message(reinterpret_cast(sqlite3_column_text(selectStatement, 0))); - - // fromJID - boost::optional fromJID(getJIDFromID(sqlite3_column_int(selectStatement, 1))); - std::string fromResource(reinterpret_cast(sqlite3_column_text(selectStatement, 2))); - if (fromJID) { - fromJID = boost::optional(JID(fromJID->getNode(), fromJID->getDomain(), fromResource)); - } - - // toJID - boost::optional toJID(getJIDFromID(sqlite3_column_int(selectStatement, 3))); - std::string toResource(reinterpret_cast(sqlite3_column_text(selectStatement, 4))); - if (toJID) { - toJID = boost::optional(JID(toJID->getNode(), toJID->getDomain(), toResource)); - } - - // message type - HistoryMessage::Type type = static_cast(sqlite3_column_int(selectStatement, 5)); - - // timestamp - int secondsSinceEpoch(sqlite3_column_int(selectStatement, 6)); - boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); - - // offset from utc - int offset = sqlite3_column_int(selectStatement, 7); - - result.push_back(HistoryMessage(message, (fromJID ? *fromJID : JID()), (toJID ? *toJID : JID()), type, time, offset)); - r = sqlite3_step(selectStatement); - } - if (r != SQLITE_DONE) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - sqlite3_finalize(selectStatement); - - return result; -} - -int SQLiteHistoryManager::getIDForJID(const JID& jid) { - boost::optional id = getIDFromJID(jid); - if (id) { - return *id; - } - else { - return addJID(jid); - } -} - -int SQLiteHistoryManager::addJID(const JID& jid) { - std::string statement = std::string("INSERT INTO jids('jid') VALUES('") + getEscapedString(jid.toString()) + "')"; - char* errorMessage; - int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage); - if (result != SQLITE_OK) { - std::cerr << "SQL Error: " << errorMessage << std::endl; - sqlite3_free(errorMessage); - } - return sqlite3_last_insert_rowid(db_); -} - -boost::optional SQLiteHistoryManager::getJIDFromID(int id) const { - boost::optional result; - sqlite3_stmt* selectStatement; - std::string selectQuery("SELECT jid FROM jids WHERE id=" + boost::lexical_cast(id)); - int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - r = sqlite3_step(selectStatement); - if (r == SQLITE_ROW) { - result = boost::optional(reinterpret_cast(sqlite3_column_text(selectStatement, 0))); - } - sqlite3_finalize(selectStatement); - return result; -} - -boost::optional SQLiteHistoryManager::getIDFromJID(const JID& jid) const { - boost::optional result; - sqlite3_stmt* selectStatement; - std::string selectQuery("SELECT id FROM jids WHERE jid='" + jid.toString() + "'"); - int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - r = sqlite3_step(selectStatement); - if (r == SQLITE_ROW) { - result = boost::optional(sqlite3_column_int(selectStatement, 0)); - } - sqlite3_finalize(selectStatement); - return result; -} - -ContactsMap SQLiteHistoryManager::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const { - ContactsMap result; - sqlite3_stmt* selectStatement; - - // get id - boost::optional id = getIDFromJID(selfJID); - if (!id) { - return result; - } - - // get contacts - std::string query = "SELECT DISTINCT messages.'fromBare', messages.'fromResource', messages.'toBare', messages.'toResource', messages.'time' " - "FROM messages WHERE (type=" - + boost::lexical_cast(type) + " AND (toBare=" - + boost::lexical_cast(*id) + " OR fromBare=" + boost::lexical_cast(*id) + "))"; - - // match keyword - if (getEscapedString(keyword).length()) { - query += " AND message LIKE '%" + getEscapedString(keyword) + "%'"; - } - - int r = sqlite3_prepare(db_, query.c_str(), query.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - - r = sqlite3_step(selectStatement); - while (r == SQLITE_ROW) { - int fromBareID = sqlite3_column_int(selectStatement, 0); - std::string fromResource(reinterpret_cast(sqlite3_column_text(selectStatement, 1))); - int toBareID = sqlite3_column_int(selectStatement, 2); - std::string toResource(reinterpret_cast(sqlite3_column_text(selectStatement, 3))); - std::string resource; - - int secondsSinceEpoch(sqlite3_column_int(selectStatement, 4)); - boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); - - boost::optional contactJID; - - if (fromBareID == *id) { - contactJID = getJIDFromID(toBareID); - resource = toResource; - } - else { - contactJID = getJIDFromID(fromBareID); - resource = fromResource; - } - - // check if it is a MUC contact (from a private conversation) - if (type == HistoryMessage::PrivateMessage) { - contactJID = boost::optional(JID(contactJID->getNode(), contactJID->getDomain(), resource)); - } - - if (contactJID) { - result[*contactJID].insert(time.date()); - } - - r = sqlite3_step(selectStatement); - } - - if (r != SQLITE_DONE) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - sqlite3_finalize(selectStatement); - - return result; -} - -boost::gregorian::date SQLiteHistoryManager::getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const { - sqlite3_stmt* selectStatement; - boost::optional selfID = getIDFromJID(selfJID.toBare()); - boost::optional contactID = getIDFromJID(contactJID.toBare()); - - if (!selfID || !contactID) { - // JIDs missing from the database - return boost::gregorian::date(boost::gregorian::not_a_date_time); - } - - std::string selectQuery = "SELECT time FROM messages WHERE (type=" + boost::lexical_cast(type); - if (contactJID.isBare()) { - // match only bare jid - selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND toBare=" + - boost::lexical_cast(*contactID) + ") OR (fromBare=" + - boost::lexical_cast(*contactID) + " AND toBare=" + boost::lexical_cast(*selfID) + ")))"; - } - else { - // match resource too - selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND (toBare=" + - boost::lexical_cast(*contactID) +" AND toResource='" + - getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" + - boost::lexical_cast(*contactID) + " AND fromResource='" + - getEscapedString(contactJID.getResource()) + "') AND toBare=" + - boost::lexical_cast(*selfID) + ")))"; - } - - int timeStamp = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds() + (reverseOrder ? 0 : 86400); - - selectQuery += " AND time" + (reverseOrder ? std::string("<") : std::string(">")) + boost::lexical_cast(timeStamp); - selectQuery += " ORDER BY time " + (reverseOrder ? std::string("DESC") : std::string("ASC")) + " LIMIT 1"; - - int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - - r = sqlite3_step(selectStatement); - if (r == SQLITE_ROW) { - int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0)); - boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); - std::cout << "next day is: " << time.date() << "\n"; - return time.date(); - } - - return boost::gregorian::date(boost::gregorian::not_a_date_time); -} - -std::vector SQLiteHistoryManager::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { - boost::gregorian::date nextDate = getNextDateWithLogs(selfJID, contactJID, type, date, false); - - if (nextDate.is_not_a_date()) { - return std::vector(); - } - - return getMessagesFromDate(selfJID, contactJID, type, nextDate); -} - -std::vector SQLiteHistoryManager::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { - boost::gregorian::date previousDate = getNextDateWithLogs(selfJID, contactJID, type, date, true); - - if (previousDate.is_not_a_date()) { - return std::vector(); - } - - return getMessagesFromDate(selfJID, contactJID, type, previousDate); -} - -boost::posix_time::ptime SQLiteHistoryManager::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const { - boost::optional selfID = getIDFromJID(selfJID.toBare()); - boost::optional mucID = getIDFromJID(mucJID.toBare()); - - if (!selfID || !mucID) { - // JIDs missing from the database - return boost::posix_time::ptime(boost::posix_time::not_a_date_time); - } - - - sqlite3_stmt* selectStatement; - std::string selectQuery = "SELECT messages.'time', messages.'offset' from messages WHERE type=1 AND (toBare=" + - boost::lexical_cast(*selfID) + " AND fromBare=" + - boost::lexical_cast(*mucID) + ") ORDER BY time DESC LIMIT 1"; - - int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); - if (r != SQLITE_OK) { - std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; - } - - r = sqlite3_step(selectStatement); - if (r == SQLITE_ROW) { - int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0)); - boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); - int offset = sqlite3_column_int(selectStatement, 1); - - return time - boost::posix_time::hours(offset); - } - - return boost::posix_time::ptime(boost::posix_time::not_a_date_time); -} - -} diff --git a/Swiften/History/SQLiteHistoryManager.h b/Swiften/History/SQLiteHistoryManager.h deleted file mode 100644 index b74fbde..0000000 --- a/Swiften/History/SQLiteHistoryManager.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#pragma once - -#include - -#include - -struct sqlite3; - -namespace Swift { - class SQLiteHistoryManager : public HistoryManager { - public: - SQLiteHistoryManager(const std::string& file); - ~SQLiteHistoryManager(); - - void addMessage(const HistoryMessage& message); - ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const; - std::vector getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; - std::vector getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; - std::vector getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; - boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const; - - private: - boost::gregorian::date getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const; - int getIDForJID(const JID&); - int addJID(const JID&); - - boost::optional getJIDFromID(int id) const; - boost::optional getIDFromJID(const JID& jid) const; - - sqlite3* db_; - }; -} diff --git a/Swiften/History/SQLiteHistoryStorage.cpp b/Swiften/History/SQLiteHistoryStorage.cpp new file mode 100644 index 0000000..5e3148b --- /dev/null +++ b/Swiften/History/SQLiteHistoryStorage.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include +#include + +#include +#include +#include + +inline std::string getEscapedString(const std::string& s) { + std::string result(s); + + size_t pos = result.find('\''); + while (pos != std::string::npos) { + result.insert(pos, "'"); + pos = result.find('\'', pos + 2); + } + return result; +} + +namespace Swift { + +SQLiteHistoryStorage::SQLiteHistoryStorage(const std::string& file) : db_(0) { + sqlite3_open(file.c_str(), &db_); + if (!db_) { + std::cerr << "Error opening database " << file << std::endl; + } + + char* errorMessage; + int result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS messages('message' STRING, 'fromBare' INTEGER, 'fromResource' STRING, 'toBare' INTEGER, 'toResource' STRING, 'type' INTEGER, 'time' INTEGER, 'offset' INTEGER)", 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } + + result = sqlite3_exec(db_, "CREATE TABLE IF NOT EXISTS jids('id' INTEGER PRIMARY KEY ASC AUTOINCREMENT, 'jid' STRING UNIQUE NOT NULL)", 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } +} + +SQLiteHistoryStorage::~SQLiteHistoryStorage() { + sqlite3_close(db_); +} + +void SQLiteHistoryStorage::addMessage(const HistoryMessage& message) { + int secondsSinceEpoch = (message.getTime() - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds(); + + std::string statement = std::string("INSERT INTO messages('message', 'fromBare', 'fromResource', 'toBare', 'toResource', 'type', 'time', 'offset') VALUES(") + + "'" + getEscapedString(message.getMessage()) + "', " + + boost::lexical_cast(getIDForJID(message.getFromJID().toBare())) + ", '" + + getEscapedString(message.getFromJID().getResource()) + "', " + + boost::lexical_cast(getIDForJID(message.getToJID().toBare())) + ", '" + + getEscapedString(message.getToJID().getResource()) + "', " + + boost::lexical_cast(message.getType()) + ", " + + boost::lexical_cast(secondsSinceEpoch) + ", " + + boost::lexical_cast(message.getOffset()) + ")"; + char* errorMessage; + int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } +} + +std::vector SQLiteHistoryStorage::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + sqlite3_stmt* selectStatement; + + boost::optional selfID = getIDFromJID(selfJID.toBare()); + boost::optional contactID = getIDFromJID(contactJID.toBare()); + + if (!selfID || !contactID) { + // JIDs missing from the database + return std::vector(); + } + + std::string selectQuery = "SELECT * FROM messages WHERE (type=" + boost::lexical_cast(type); + if (contactJID.isBare()) { + // match only bare jid + selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND toBare=" + + boost::lexical_cast(*contactID) + ") OR (fromBare=" + + boost::lexical_cast(*contactID) + " AND toBare=" + boost::lexical_cast(*selfID) + ")))"; + } + else { + // match resource too + selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND (toBare=" + + boost::lexical_cast(*contactID) +" AND toResource='" + + getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" + + boost::lexical_cast(*contactID) + " AND fromResource='" + + getEscapedString(contactJID.getResource()) + "') AND toBare=" + + boost::lexical_cast(*selfID) + ")))"; + } + + if (!date.is_not_a_date()) { + int lowerBound = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds(); + int upperBound = lowerBound + 86400; + + selectQuery += " AND (time>=" + boost::lexical_cast(lowerBound) + + " AND time<" + boost::lexical_cast(upperBound) + ")"; + } + + int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + + // Retrieve result + std::vector result; + while (r == SQLITE_ROW) { + std::string message(reinterpret_cast(sqlite3_column_text(selectStatement, 0))); + + // fromJID + boost::optional fromJID(getJIDFromID(sqlite3_column_int(selectStatement, 1))); + std::string fromResource(reinterpret_cast(sqlite3_column_text(selectStatement, 2))); + if (fromJID) { + fromJID = boost::optional(JID(fromJID->getNode(), fromJID->getDomain(), fromResource)); + } + + // toJID + boost::optional toJID(getJIDFromID(sqlite3_column_int(selectStatement, 3))); + std::string toResource(reinterpret_cast(sqlite3_column_text(selectStatement, 4))); + if (toJID) { + toJID = boost::optional(JID(toJID->getNode(), toJID->getDomain(), toResource)); + } + + // message type + HistoryMessage::Type type = static_cast(sqlite3_column_int(selectStatement, 5)); + + // timestamp + int secondsSinceEpoch(sqlite3_column_int(selectStatement, 6)); + boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); + + // offset from utc + int offset = sqlite3_column_int(selectStatement, 7); + + result.push_back(HistoryMessage(message, (fromJID ? *fromJID : JID()), (toJID ? *toJID : JID()), type, time, offset)); + r = sqlite3_step(selectStatement); + } + if (r != SQLITE_DONE) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + sqlite3_finalize(selectStatement); + + return result; +} + +int SQLiteHistoryStorage::getIDForJID(const JID& jid) { + boost::optional id = getIDFromJID(jid); + if (id) { + return *id; + } + else { + return addJID(jid); + } +} + +int SQLiteHistoryStorage::addJID(const JID& jid) { + std::string statement = std::string("INSERT INTO jids('jid') VALUES('") + getEscapedString(jid.toString()) + "')"; + char* errorMessage; + int result = sqlite3_exec(db_, statement.c_str(), 0, 0, &errorMessage); + if (result != SQLITE_OK) { + std::cerr << "SQL Error: " << errorMessage << std::endl; + sqlite3_free(errorMessage); + } + return sqlite3_last_insert_rowid(db_); +} + +boost::optional SQLiteHistoryStorage::getJIDFromID(int id) const { + boost::optional result; + sqlite3_stmt* selectStatement; + std::string selectQuery("SELECT jid FROM jids WHERE id=" + boost::lexical_cast(id)); + int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + result = boost::optional(reinterpret_cast(sqlite3_column_text(selectStatement, 0))); + } + sqlite3_finalize(selectStatement); + return result; +} + +boost::optional SQLiteHistoryStorage::getIDFromJID(const JID& jid) const { + boost::optional result; + sqlite3_stmt* selectStatement; + std::string selectQuery("SELECT id FROM jids WHERE jid='" + jid.toString() + "'"); + int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + result = boost::optional(sqlite3_column_int(selectStatement, 0)); + } + sqlite3_finalize(selectStatement); + return result; +} + +ContactsMap SQLiteHistoryStorage::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const { + ContactsMap result; + sqlite3_stmt* selectStatement; + + // get id + boost::optional id = getIDFromJID(selfJID); + if (!id) { + return result; + } + + // get contacts + std::string query = "SELECT DISTINCT messages.'fromBare', messages.'fromResource', messages.'toBare', messages.'toResource', messages.'time' " + "FROM messages WHERE (type=" + + boost::lexical_cast(type) + " AND (toBare=" + + boost::lexical_cast(*id) + " OR fromBare=" + boost::lexical_cast(*id) + "))"; + + // match keyword + if (getEscapedString(keyword).length()) { + query += " AND message LIKE '%" + getEscapedString(keyword) + "%'"; + } + + int r = sqlite3_prepare(db_, query.c_str(), query.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + + r = sqlite3_step(selectStatement); + while (r == SQLITE_ROW) { + int fromBareID = sqlite3_column_int(selectStatement, 0); + std::string fromResource(reinterpret_cast(sqlite3_column_text(selectStatement, 1))); + int toBareID = sqlite3_column_int(selectStatement, 2); + std::string toResource(reinterpret_cast(sqlite3_column_text(selectStatement, 3))); + std::string resource; + + int secondsSinceEpoch(sqlite3_column_int(selectStatement, 4)); + boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); + + boost::optional contactJID; + + if (fromBareID == *id) { + contactJID = getJIDFromID(toBareID); + resource = toResource; + } + else { + contactJID = getJIDFromID(fromBareID); + resource = fromResource; + } + + // check if it is a MUC contact (from a private conversation) + if (type == HistoryMessage::PrivateMessage) { + contactJID = boost::optional(JID(contactJID->getNode(), contactJID->getDomain(), resource)); + } + + if (contactJID) { + result[*contactJID].insert(time.date()); + } + + r = sqlite3_step(selectStatement); + } + + if (r != SQLITE_DONE) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + sqlite3_finalize(selectStatement); + + return result; +} + +boost::gregorian::date SQLiteHistoryStorage::getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const { + sqlite3_stmt* selectStatement; + boost::optional selfID = getIDFromJID(selfJID.toBare()); + boost::optional contactID = getIDFromJID(contactJID.toBare()); + + if (!selfID || !contactID) { + // JIDs missing from the database + return boost::gregorian::date(boost::gregorian::not_a_date_time); + } + + std::string selectQuery = "SELECT time FROM messages WHERE (type=" + boost::lexical_cast(type); + if (contactJID.isBare()) { + // match only bare jid + selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND toBare=" + + boost::lexical_cast(*contactID) + ") OR (fromBare=" + + boost::lexical_cast(*contactID) + " AND toBare=" + boost::lexical_cast(*selfID) + ")))"; + } + else { + // match resource too + selectQuery += " AND ((fromBare=" + boost::lexical_cast(*selfID) + " AND (toBare=" + + boost::lexical_cast(*contactID) +" AND toResource='" + + getEscapedString(contactJID.getResource()) + "')) OR ((fromBare=" + + boost::lexical_cast(*contactID) + " AND fromResource='" + + getEscapedString(contactJID.getResource()) + "') AND toBare=" + + boost::lexical_cast(*selfID) + ")))"; + } + + int timeStamp = (boost::posix_time::ptime(date) - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1))).total_seconds() + (reverseOrder ? 0 : 86400); + + selectQuery += " AND time" + (reverseOrder ? std::string("<") : std::string(">")) + boost::lexical_cast(timeStamp); + selectQuery += " ORDER BY time " + (reverseOrder ? std::string("DESC") : std::string("ASC")) + " LIMIT 1"; + + int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0)); + boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); + std::cout << "next day is: " << time.date() << "\n"; + return time.date(); + } + + return boost::gregorian::date(boost::gregorian::not_a_date_time); +} + +std::vector SQLiteHistoryStorage::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + boost::gregorian::date nextDate = getNextDateWithLogs(selfJID, contactJID, type, date, false); + + if (nextDate.is_not_a_date()) { + return std::vector(); + } + + return getMessagesFromDate(selfJID, contactJID, type, nextDate); +} + +std::vector SQLiteHistoryStorage::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + boost::gregorian::date previousDate = getNextDateWithLogs(selfJID, contactJID, type, date, true); + + if (previousDate.is_not_a_date()) { + return std::vector(); + } + + return getMessagesFromDate(selfJID, contactJID, type, previousDate); +} + +boost::posix_time::ptime SQLiteHistoryStorage::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const { + boost::optional selfID = getIDFromJID(selfJID.toBare()); + boost::optional mucID = getIDFromJID(mucJID.toBare()); + + if (!selfID || !mucID) { + // JIDs missing from the database + return boost::posix_time::ptime(boost::posix_time::not_a_date_time); + } + + + sqlite3_stmt* selectStatement; + std::string selectQuery = "SELECT messages.'time', messages.'offset' from messages WHERE type=1 AND (toBare=" + + boost::lexical_cast(*selfID) + " AND fromBare=" + + boost::lexical_cast(*mucID) + ") ORDER BY time DESC LIMIT 1"; + + int r = sqlite3_prepare(db_, selectQuery.c_str(), selectQuery.size(), &selectStatement, NULL); + if (r != SQLITE_OK) { + std::cout << "Error: " << sqlite3_errmsg(db_) << std::endl; + } + + r = sqlite3_step(selectStatement); + if (r == SQLITE_ROW) { + int secondsSinceEpoch(sqlite3_column_int(selectStatement, 0)); + boost::posix_time::ptime time(boost::gregorian::date(1970, 1, 1), boost::posix_time::seconds(secondsSinceEpoch)); + int offset = sqlite3_column_int(selectStatement, 1); + + return time - boost::posix_time::hours(offset); + } + + return boost::posix_time::ptime(boost::posix_time::not_a_date_time); +} + +} diff --git a/Swiften/History/SQLiteHistoryStorage.h b/Swiften/History/SQLiteHistoryStorage.h new file mode 100644 index 0000000..aae6db8 --- /dev/null +++ b/Swiften/History/SQLiteHistoryStorage.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include + +#include + +struct sqlite3; + +namespace Swift { + class SQLiteHistoryStorage : public HistoryStorage { + public: + SQLiteHistoryStorage(const std::string& file); + ~SQLiteHistoryStorage(); + + void addMessage(const HistoryMessage& message); + ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const; + std::vector getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) const; + + private: + boost::gregorian::date getNextDateWithLogs(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date, bool reverseOrder) const; + int getIDForJID(const JID&); + int addJID(const JID&); + + boost::optional getJIDFromID(int id) const; + boost::optional getIDFromJID(const JID& jid) const; + + sqlite3* db_; + }; +} -- cgit v0.10.2-6-g49f6