From 07443b1f603df693ce6215039d1970bd0f096fd5 Mon Sep 17 00:00:00 2001 From: Ronan Abhamon Date: Thu, 15 Feb 2018 08:28:12 +0100 Subject: [PATCH] feat(MainDb): increase performance of get history range --- src/CMakeLists.txt | 12 +- src/chat/chat-message/chat-message-p.h | 13 +- src/chat/chat-message/chat-message.cpp | 32 ++- src/db/internal/safe-transaction.h | 141 ++++++++++ src/db/internal/statements.cpp | 76 ++++++ src/db/internal/statements.h | 46 ++++ src/db/main-db-p.h | 15 ++ src/db/main-db.cpp | 350 ++++++++++++------------- src/db/main-db.h | 6 + 9 files changed, 494 insertions(+), 197 deletions(-) create mode 100644 src/db/internal/safe-transaction.h create mode 100644 src/db/internal/statements.cpp create mode 100644 src/db/internal/statements.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bb174d7eb..fec6de2bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -272,15 +272,12 @@ set(LINPHONE_OBJC_SOURCE_FILES) if (APPLE) list(APPEND LINPHONE_OBJC_SOURCE_FILES core/paths/paths-apple.mm) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-apple.h) - elseif (ANDROID) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-android.cpp core/platform-helpers/android-platform-helpers.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-android.h) - elseif (WIN32) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-windows.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-windows.h) - elseif (UNIX) list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES core/paths/paths-linux.cpp) list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES core/paths/paths-linux.h) @@ -293,6 +290,15 @@ set(LINPHONE_CXX_OBJECTS_INCLUDE_DIRS ${BELR_INCLUDE_DIRS}) if (SOCI_FOUND) list(APPEND LINPHONE_CXX_OBJECTS_INCLUDE_DIRS ${SOCI_INCLUDE_DIRS} ${SOCI_MYSQL_INCLUDES}) add_definitions(-DSOCI_ENABLED) + + list(APPEND LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES + db/internal/safe-transaction.h + db/internal/statements.h + ) + + list(APPEND LINPHONE_CXX_OBJECTS_SOURCE_FILES + db/internal/statements.cpp + ) endif () set(LINPHONE_PRIVATE_HEADER_FILES) diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index fd374a39e..b6182c94f 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -69,14 +69,20 @@ public: void setImdnMessageId (const std::string &imdnMessageId); - inline void forceFromAddress (const IdentityAddress &fromAddress) { + void forceFromAddress (const IdentityAddress &fromAddress) { this->fromAddress = fromAddress; } - inline void forceToAddress (const IdentityAddress &toAddress) { + void forceToAddress (const IdentityAddress &toAddress) { this->toAddress = toAddress; } + void markContentsAsNotLoaded () { + contentsNotLoadedFromDatabase = true; + } + + void loadContentsFromDatabase () const; + belle_http_request_t *getHttpRequest () const; void setHttpRequest (belle_http_request_t *request); @@ -142,7 +148,7 @@ private: std::string imdnId; std::string rttMessage; bool isSecured = false; - bool isReadOnly = false; + mutable bool isReadOnly = false; std::list contents; Content internalContent; std::unordered_map customHeaders; @@ -176,6 +182,7 @@ private: bool encryptionPrevented = false; bool toBeStored = true; + mutable bool contentsNotLoadedFromDatabase = false; L_DECLARE_PUBLIC(ChatMessage); }; diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index a254ff42d..2436bcbb5 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -183,6 +183,7 @@ string ChatMessagePrivate::getSalCustomHeaderValue (const string &name) { // ----------------------------------------------------------------------------- bool ChatMessagePrivate::hasTextContent() const { + loadContentsFromDatabase(); for (const Content *c : contents) { if (c->getContentType() == ContentType::PlainText) { return true; @@ -192,6 +193,7 @@ bool ChatMessagePrivate::hasTextContent() const { } const Content* ChatMessagePrivate::getTextContent() const { + loadContentsFromDatabase(); for (const Content *c : contents) { if (c->getContentType() == ContentType::PlainText) { return c; @@ -201,6 +203,7 @@ const Content* ChatMessagePrivate::getTextContent() const { } bool ChatMessagePrivate::hasFileTransferContent() const { + loadContentsFromDatabase(); for (const Content *c : contents) { if (c->getContentType() == ContentType::FileTransfer) { return true; @@ -227,6 +230,7 @@ void ChatMessagePrivate::setFileTransferFilepath (const string &path) { } const string &ChatMessagePrivate::getAppdata () const { + loadContentsFromDatabase(); for (const Content *c : contents) { if (c->isFile()) { FileContent *fileContent = (FileContent *)c; @@ -237,6 +241,7 @@ const string &ChatMessagePrivate::getAppdata () const { } void ChatMessagePrivate::setAppdata (const string &data) { + loadContentsFromDatabase(); for (const Content *c : contents) { if (c->isFile()) { FileContent *fileContent = (FileContent *)c; @@ -256,6 +261,7 @@ const string &ChatMessagePrivate::getExternalBodyUrl () const { } const ContentType &ChatMessagePrivate::getContentType () { + loadContentsFromDatabase(); if (direction == ChatMessage::Direction::Incoming) { if (contents.size() > 0) { Content *content = contents.front(); @@ -277,6 +283,7 @@ const ContentType &ChatMessagePrivate::getContentType () { } void ChatMessagePrivate::setContentType (const ContentType &contentType) { + loadContentsFromDatabase(); if (contents.size() > 0 && internalContent.getContentType().isEmpty() && internalContent.isEmpty()) { internalContent.setBody(contents.front()->getBody()); } @@ -290,6 +297,7 @@ void ChatMessagePrivate::setContentType (const ContentType &contentType) { } const string &ChatMessagePrivate::getText () { + loadContentsFromDatabase(); if (direction == ChatMessage::Direction::Incoming) { if (hasTextContent()) { cText = getTextContent()->getBodyAsString(); @@ -313,6 +321,7 @@ const string &ChatMessagePrivate::getText () { } void ChatMessagePrivate::setText (const string &text) { + loadContentsFromDatabase(); if (contents.size() > 0 && internalContent.getContentType().isEmpty() && internalContent.isEmpty()) { internalContent.setContentType(contents.front()->getContentType()); } @@ -356,6 +365,7 @@ void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_co bool ChatMessagePrivate::downloadFile () { L_Q(); + loadContentsFromDatabase(); for (auto &content : contents) if (content->getContentType() == ContentType::FileTransfer) @@ -887,6 +897,18 @@ void ChatMessagePrivate::setImdnMessageId (const string &id) { imdnId = id; } +void ChatMessagePrivate::loadContentsFromDatabase () const { + L_Q(); + if (contentsNotLoadedFromDatabase) { + isReadOnly = false; + contentsNotLoadedFromDatabase = false; + q->getChatRoom()->getCore()->getPrivate()->mainDb->loadChatMessageContents( + const_pointer_cast(q->getSharedFromThis()) + ); + isReadOnly = true; + } +} + bool ChatMessage::isRead () const { L_D(); @@ -939,6 +961,7 @@ bool ChatMessage::isReadOnly () const { const list &ChatMessage::getContents () const { L_D(); + d->loadContentsFromDatabase(); return d->contents; } @@ -1000,6 +1023,7 @@ void ChatMessage::send () { return; } + d->loadContentsFromDatabase(); getChatRoom()->getPrivate()->sendChatMessage(getSharedFromThis()); } @@ -1039,9 +1063,9 @@ void ChatMessage::cancelFileTransfer () { int ChatMessage::putCharacter (uint32_t character) { L_D(); - static const uint32_t new_line = 0x2028; - static const uint32_t crlf = 0x0D0A; - static const uint32_t lf = 0x0A; + constexpr uint32_t newLine = 0x2028; + constexpr uint32_t crlf = 0x0D0A; + constexpr uint32_t lf = 0x0A; shared_ptr chatRoom = getChatRoom(); if (!(chatRoom->getCapabilities() & LinphonePrivate::ChatRoom::Capabilities::RealTimeText)) @@ -1056,7 +1080,7 @@ int ChatMessage::putCharacter (uint32_t character) { if (!call || !call->getPrivate()->getMediaStream(LinphoneStreamTypeText)) return -1; - if (character == new_line || character == crlf || character == lf) { + if (character == newLine || character == crlf || character == lf) { shared_ptr core = getCore(); if (lp_config_get_int(core->getCCore()->config, "misc", "store_rtt_messages", 1) == 1) { // TODO: History. diff --git a/src/db/internal/safe-transaction.h b/src/db/internal/safe-transaction.h new file mode 100644 index 000000000..865867110 --- /dev/null +++ b/src/db/internal/safe-transaction.h @@ -0,0 +1,141 @@ +/* + * safe-transaction.h + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * 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 02110-1301, USA. + */ + +#ifndef _L_SAFE_TRANSACTION_H_ +#define _L_SAFE_TRANSACTION_H_ + +#include + +#include "db/main-db.h" +#include "logger/logger.h" + +// ============================================================================= + +#define L_SAFE_TRANSACTION_C(CONTEXT) \ + LinphonePrivate::SafeTransactionInfo().set(__func__, CONTEXT) * [&]() + +#define L_SAFE_TRANSACTION L_SAFE_TRANSACTION_C(this) + +LINPHONE_BEGIN_NAMESPACE + +struct SafeTransactionInfo { + SafeTransactionInfo &set (const char *_name, const MainDb *_mainDb) { + name = _name; + mainDb = const_cast(_mainDb); + return *this; + } + + const char *name = nullptr; + MainDb *mainDb = nullptr; +}; + +template +class SafeTransaction { + using InternalReturnType = typename std::remove_reference()())>::type; + +public: + using ReturnType = typename std::conditional< + std::is_same::value, + bool, + InternalReturnType + >::type; + + SafeTransaction (SafeTransactionInfo &info, Function function) : mFunction(std::move(function)) { + try { + mResult= exec(); + } catch (const soci::soci_error &e) { + lWarning() << "Catched exception in MainDb::" << info.name << "(" << e.what() << ")."; + soci::soci_error::error_category category = e.get_error_category(); + if ( + (category == soci::soci_error::connection_error || category == soci::soci_error::unknown) && + info.mainDb->forceReconnect() + ) { + try { + mResult = exec(); + } catch (const std::exception &e) { + lError() << "Unable to execute query after reconnect in MainDb::" << info.name << "(" << e.what() << ")."; + } + return; + } + lError() << "Unhandled [" << getErrorCategoryAsString(category) << "] exception in MainDb::" << + info.name << ": `" << e.what() << "`."; + } catch (const std::exception &e) { + lError() << "Unhandled generic exception in MainDb::" << info.name << ": `" << e.what() << "`."; + } + } + + SafeTransaction (SafeTransaction &&safeTransaction) : mFunction(std::move(safeTransaction.mFunction)) {} + + operator ReturnType () const { + return mResult; + } + +private: + // Exec function with no return type. + template + typename std::enable_if::value, bool>::type exec () const { + mFunction(); + return true; + } + + // Exec function with return type. + template + typename std::enable_if::value, T>::type exec () const { + return mFunction(); + } + + static const char *getErrorCategoryAsString (soci::soci_error::error_category category) { + switch (category) { + case soci::soci_error::connection_error: + return "CONNECTION ERROR"; + case soci::soci_error::invalid_statement: + return "INVALID STATEMENT"; + case soci::soci_error::no_privilege: + return "NO PRIVILEGE"; + case soci::soci_error::no_data: + return "NO DATA"; + case soci::soci_error::constraint_violation: + return "CONSTRAINT VIOLATION"; + case soci::soci_error::unknown_transaction_state: + return "UNKNOWN TRANSACTION STATE"; + case soci::soci_error::system_error: + return "SYSTEM ERROR"; + case soci::soci_error::unknown: + return "UNKNOWN"; + } + + // Unreachable. + L_ASSERT(false); + return nullptr; + } + + Function mFunction; + ReturnType mResult{}; + + L_DISABLE_COPY(SafeTransaction); +}; + +template +typename SafeTransaction::ReturnType operator* (SafeTransactionInfo &info, Function &&function) { + return SafeTransaction(info, std::forward(function)); +} + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_SAFE_TRANSACTION_H_ diff --git a/src/db/internal/statements.cpp b/src/db/internal/statements.cpp new file mode 100644 index 000000000..900c6a927 --- /dev/null +++ b/src/db/internal/statements.cpp @@ -0,0 +1,76 @@ +/* + * statements.cpp + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * 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 02110-1301, USA. + */ + +#include "statements.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +namespace Statements { + // --------------------------------------------------------------------------- + // Create statements. + // --------------------------------------------------------------------------- + + constexpr const char *create[CreateCount] = { + [CreateConferenceEventView] = R"( + CREATE TEMP VIEW conference_event_view AS + SELECT id, type, creation_time, chat_room_id, from_sip_address_id, to_sip_address_id, time, imdn_message_id, state, direction, is_secured, notify_id, device_sip_address_id, participant_sip_address_id, subject + FROM event + LEFT JOIN conference_event ON conference_event.event_id = event.id + LEFT JOIN conference_chat_message_event ON conference_chat_message_event.event_id = event.id + LEFT JOIN conference_notified_event ON conference_notified_event.event_id = event.id + LEFT JOIN conference_participant_device_event ON conference_participant_device_event.event_id = event.id + LEFT JOIN conference_participant_event ON conference_participant_event.event_id = event.id + LEFT JOIN conference_subject_event ON conference_subject_event.event_id = event.id + )" + }; + + // --------------------------------------------------------------------------- + // Select statements. + // --------------------------------------------------------------------------- + + constexpr const char *select[SelectCount] = { + [SelectConferenceEvents] = R"( + SELECT conference_event_view.id AS event_id, type, creation_time, from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured, notify_id, device_sip_address.value, participant_sip_address.value, subject + FROM conference_event_view + LEFT JOIN sip_address AS from_sip_address ON from_sip_address.id = from_sip_address_id + LEFT JOIN sip_address AS to_sip_address ON to_sip_address.id = to_sip_address_id + LEFT JOIN sip_address AS device_sip_address ON device_sip_address.id = device_sip_address_id + LEFT JOIN sip_address AS participant_sip_address ON participant_sip_address.id = participant_sip_address_id + WHERE chat_room_id = :chatRoomId + )" + }; + + // --------------------------------------------------------------------------- + // Getters. + // --------------------------------------------------------------------------- + + const char *get (Create createStmt, AbstractDb::Backend backend) { + (void)backend; + return createStmt >= Create::CreateCount ? nullptr : create[createStmt]; + } + + const char *get (Select selectStmt, AbstractDb::Backend backend) { + (void)backend; + return selectStmt >= Select::SelectCount ? nullptr : select[selectStmt]; + } +} + +LINPHONE_END_NAMESPACE diff --git a/src/db/internal/statements.h b/src/db/internal/statements.h new file mode 100644 index 000000000..e76a4eeea --- /dev/null +++ b/src/db/internal/statements.h @@ -0,0 +1,46 @@ +/* + * statements.h + * Copyright (C) 2010-2018 Belledonne Communications SARL + * + * 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 02110-1301, USA. + */ + +#ifndef _L_STATEMENTS_H_ +#define _L_STATEMENTS_H_ + +#include "db/abstract/abstract-db.h" + +// ============================================================================= + +LINPHONE_BEGIN_NAMESPACE + +namespace Statements { + enum Create { + CreateConferenceEventView, + CreateCount + }; + + enum Select { + SelectConferenceEvents, + SelectCount + }; + + const char *get (Create createStmt, AbstractDb::Backend backend); + const char *get (Select selectStmt, AbstractDb::Backend backend); +} + +LINPHONE_END_NAMESPACE + +#endif // ifndef _L_STATEMENTS_H_ diff --git a/src/db/main-db-p.h b/src/db/main-db-p.h index 81e5535b4..08067eb73 100644 --- a/src/db/main-db-p.h +++ b/src/db/main-db-p.h @@ -103,6 +103,21 @@ private: const ChatRoomId &chatRoomId ) const; + // TODO: Remove me. Workaround to increase fetch performance. + std::shared_ptr selectConferenceChatMessageEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + std::shared_ptr &chatRoom, + const std::string &fromSipAddress, + const std::string &toSipAddress, + const tm &messageTime, + const std::string &imdnMessageId, + int state, + int direction, + int isSecured + ) const; + std::shared_ptr selectConferenceParticipantEvent ( long long eventId, EventLog::Type type, diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index 5868ba477..38cc9808c 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -36,6 +36,11 @@ #include "main-db-key-p.h" #include "main-db-p.h" +#ifdef SOCI_ENABLED + #include "internal/safe-transaction.h" + #include "internal/statements.h" +#endif // ifdef SOCI_ENABLED + // ============================================================================= // See: http://soci.sourceforge.net/doc/3.2/exchange.html @@ -88,117 +93,6 @@ MainDb::MainDb (const shared_ptr &core) : AbstractDb(*new MainDbPrivate), #ifdef SOCI_ENABLED -// ----------------------------------------------------------------------------- -// Errors handling. -// ----------------------------------------------------------------------------- - -#define L_SAFE_TRANSACTION_C(CONTEXT) \ - LinphonePrivate::SafeTransactionInfo().set(__func__, CONTEXT) * [&]() - -#define L_SAFE_TRANSACTION L_SAFE_TRANSACTION_C(this) - -struct SafeTransactionInfo { - SafeTransactionInfo &set (const char *_name, const MainDb *_mainDb) { - name = _name; - mainDb = const_cast(_mainDb); - return *this; - } - - const char *name = nullptr; - MainDb *mainDb = nullptr; -}; - -template -class SafeTransaction { - using InternalReturnType = typename remove_reference()())>::type; - -public: - using ReturnType = typename std::conditional< - std::is_same::value, - bool, - InternalReturnType - >::type; - - SafeTransaction (SafeTransactionInfo &info, Function function) : mFunction(move(function)) { - try { - mResult= exec(); - } catch (const soci::soci_error &e) { - lWarning() << "Catched exception in MainDb::" << info.name << "(" << e.what() << ")."; - soci::soci_error::error_category category = e.get_error_category(); - if ( - (category == soci::soci_error::connection_error || category == soci::soci_error::unknown) && - info.mainDb->forceReconnect() - ) { - try { - mResult = exec(); - } catch (const exception &e) { - lError() << "Unable to execute query after reconnect in MainDb::" << info.name << "(" << e.what() << ")."; - } - return; - } - lError() << "Unhandled [" << getErrorCategoryAsString(category) << "] exception in MainDb::" << - info.name << ": `" << e.what() << "`."; - } catch (const exception &e) { - lError() << "Unhandled generic exception in MainDb::" << info.name << ": `" << e.what() << "`."; - } - } - - SafeTransaction (SafeTransaction &&safeTransaction) : mFunction(move(safeTransaction.mFunction)) {} - - operator ReturnType () const { - return mResult; - } - -private: - // Exec function with no return type. - template - typename std::enable_if::value, bool>::type exec () const { - mFunction(); - return true; - } - - // Exec function with return type. - template - typename std::enable_if::value, T>::type exec () const { - return mFunction(); - } - - static const char *getErrorCategoryAsString (soci::soci_error::error_category category) { - switch (category) { - case soci::soci_error::connection_error: - return "CONNECTION ERROR"; - case soci::soci_error::invalid_statement: - return "INVALID STATEMENT"; - case soci::soci_error::no_privilege: - return "NO PRIVILEGE"; - case soci::soci_error::no_data: - return "NO DATA"; - case soci::soci_error::constraint_violation: - return "CONSTRAINT VIOLATION"; - case soci::soci_error::unknown_transaction_state: - return "UNKNOWN TRANSACTION STATE"; - case soci::soci_error::system_error: - return "SYSTEM ERROR"; - case soci::soci_error::unknown: - return "UNKNOWN"; - } - - // Unreachable. - L_ASSERT(false); - return nullptr; - } - - Function mFunction; - ReturnType mResult{}; - - L_DISABLE_COPY(SafeTransaction); -}; - -template -typename SafeTransaction::ReturnType operator* (SafeTransactionInfo &info, Function &&function) { - return SafeTransaction(info, forward(function)); -} - // ----------------------------------------------------------------------------- // Soci backend. // ----------------------------------------------------------------------------- @@ -709,13 +603,8 @@ shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( return nullptr; } - bool hasFileTransferContent = false; - - // 1 - Fetch chat message. shared_ptr chatMessage = getChatMessageFromCache(eventId); - if (chatMessage) - goto end; - { + if (!chatMessage) { string fromSipAddress; string toSipAddress; @@ -737,6 +626,42 @@ shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction), soci::into(isSecured), soci::use(eventId); + return selectConferenceChatMessageEvent ( + eventId, + type, + creationTime, + chatRoom, + fromSipAddress, + toSipAddress, + messageTime, + imdnMessageId, + state, + direction, + isSecured + ); + } + + return make_shared( + creationTime, + chatMessage + ); +} + +shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + shared_ptr &chatRoom, + const string &fromSipAddress, + const string &toSipAddress, + const tm &messageTime, + const string &imdnMessageId, + int state, + int direction, + int isSecured +) const { + shared_ptr chatMessage = getChatMessageFromCache(eventId); + if (!chatMessage) { chatMessage = shared_ptr(new ChatMessage( chatRoom, ChatMessage::Direction(direction) @@ -751,66 +676,13 @@ shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( dChatMessage->setTime(Utils::getTmAsTimeT(messageTime)); dChatMessage->setImdnMessageId(imdnMessageId); + + dChatMessage->markContentsAsNotLoaded(); + dChatMessage->setIsReadOnly(true); + + cache(chatMessage, eventId); } - // 2 - Fetch contents. - { - soci::session *session = dbSession.getBackendSession(); - static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body" - " FROM chat_message_content, content_type" - " WHERE event_id = :eventId AND content_type_id = content_type.id"; - soci::rowset rows = (session->prepare << query, soci::use(eventId)); - for (const auto &row : rows) { - ContentType contentType(row.get(2)); - const long long &contentId = dbSession.resolveId(row, 0); - Content *content; - - if (contentType == ContentType::FileTransfer) { - hasFileTransferContent = true; - content = new FileTransferContent(); - } - else if (contentType.isFile()) { - // 2.1 - Fetch contents' file informations. - string name; - int size; - string path; - - *session << "SELECT name, size, path FROM chat_message_file_content" - " WHERE chat_message_content_id = :contentId", - soci::into(name), soci::into(size), soci::into(path), soci::use(contentId); - - FileContent *fileContent = new FileContent(); - fileContent->setFileName(name); - fileContent->setFileSize(size_t(size)); - fileContent->setFilePath(path); - - content = fileContent; - } else - content = new Content(); - - content->setContentType(contentType); - content->setBody(row.get(3)); - - // 2.2 - Fetch contents' app data. - // TODO: Do not test backend, encapsulate!!! - if (q->getBackend() == MainDb::Backend::Sqlite3) { - soci::blob data(*session); - fetchContentAppData(session, *content, contentId, data); - } else { - string data; - fetchContentAppData(session, *content, contentId, data); - } - chatMessage->addContent(*content); - } - } - - // 3 - Load external body url from body into FileTransferContent if needed. - if (hasFileTransferContent) - chatMessage->getPrivate()->loadFileTransferUrlFromBodyToContent(); - - cache(chatMessage, eventId); - -end: return make_shared( creationTime, chatMessage @@ -1470,7 +1342,9 @@ void MainDbPrivate::importLegacyHistory (DbSession &inDbSession) { void MainDb::init () { L_D(); - const string charset = getBackend() == Mysql ? "DEFAULT CHARSET=utf8" : ""; + Backend backend = getBackend(); + + const string charset = backend == Mysql ? "DEFAULT CHARSET=utf8" : ""; soci::session *session = d->dbSession.getBackendSession(); using namespace placeholders; @@ -1479,6 +1353,13 @@ void MainDb::init () { auto timestampType = bind(&DbSession::timestampType, d->dbSession); auto varcharPrimaryKeyStr = bind(&DbSession::varcharPrimaryKeyStr, d->dbSession, _1); + auto createTableSanitizer = [this](const char *statement) { + // TODO. + string sanitized = statement; + return sanitized; + }; + + // TODO: Migrate all statements in statements.cpp. *session << "CREATE TABLE IF NOT EXISTS sip_address (" " id" + primaryKeyStr("BIGINT UNSIGNED") + "," @@ -1797,6 +1678,9 @@ void MainDb::init () { " ON DELETE CASCADE" ") " + charset; + for (int i = 0; i < int(Statements::CreateCount); ++i) + *session << createTableSanitizer(Statements::get(static_cast(i), backend)); + *session << "CREATE TABLE IF NOT EXISTS db_module_version (" " name" + varcharPrimaryKeyStr(255) + "," @@ -2364,10 +2248,7 @@ list> MainDb::getHistoryRange ( return events; } - string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - " )"; + string query = Statements::get(Statements::SelectConferenceEvents, Backend::Sqlite3); query += buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter, ConferenceInfoNoDeviceFilter }, mask, "AND"); @@ -2393,19 +2274,47 @@ list> MainDb::getHistoryRange ( soci::session *session = d->dbSession.getBackendSession(); soci::transaction tr(*session); + shared_ptr core = getCore(); + shared_ptr chatRoom = core->findChatRoom(chatRoomId); + if (!chatRoom) { + lError() << "Unable to find chat room storage id of (peer=" + + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + "`)."; + return events; + } + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); for (const auto &row : rows) { long long eventId = d->dbSession.resolveId(row, 0); shared_ptr event = d->getEventFromCache(eventId); - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - EventLog::Type(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - ); + if (!event) { + EventLog::Type type = EventLog::Type(row.get(1)); + + // TODO: Remove me. Performance workaround. + if (type == EventLog::Type::ConferenceChatMessage) + event = d->selectConferenceChatMessageEvent( + eventId, + type, + Utils::getTmAsTimeT(row.get(2)), + chatRoom, + row.get(3), + row.get(4), + row.get(5), + row.get(6), + row.get(7), + row.get(8), + row.get(9) + ); + else + event = d->selectGenericConferenceEvent( + eventId, + type, + Utils::getTmAsTimeT(row.get(2)), + chatRoomId + ); + } if (event) events.push_front(event); @@ -2438,6 +2347,71 @@ int MainDb::getHistorySize (const ChatRoomId &chatRoomId, FilterMask mask) const }; } +void MainDb::loadChatMessageContents (const shared_ptr &chatMessage) { + L_SAFE_TRANSACTION { + L_D(); + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + bool hasFileTransferContent = false; + + ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dChatMessage->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + + static const string query = "SELECT chat_message_content.id, content_type.id, content_type.value, body" + " FROM chat_message_content, content_type" + " WHERE event_id = :eventId AND content_type_id = content_type.id"; + soci::rowset rows = (session->prepare << query, soci::use(eventId)); + for (const auto &row : rows) { + ContentType contentType(row.get(2)); + const long long &contentId = d->dbSession.resolveId(row, 0); + Content *content; + + if (contentType == ContentType::FileTransfer) { + hasFileTransferContent = true; + content = new FileTransferContent(); + } else if (contentType.isFile()) { + // 1.1 - Fetch contents' file informations. + string name; + int size; + string path; + + *session << "SELECT name, size, path FROM chat_message_file_content" + " WHERE chat_message_content_id = :contentId", + soci::into(name), soci::into(size), soci::into(path), soci::use(contentId); + + FileContent *fileContent = new FileContent(); + fileContent->setFileName(name); + fileContent->setFileSize(size_t(size)); + fileContent->setFilePath(path); + + content = fileContent; + } else + content = new Content(); + + content->setContentType(contentType); + content->setBody(row.get(3)); + + // 1.2 - Fetch contents' app data. + // TODO: Do not test backend, encapsulate!!! + if (getBackend() == MainDb::Backend::Sqlite3) { + soci::blob data(*session); + fetchContentAppData(session, *content, contentId, data); + } else { + string data; + fetchContentAppData(session, *content, contentId, data); + } + chatMessage->addContent(*content); + } + + // 2 - Load external body url from body into FileTransferContent if needed. + if (hasFileTransferContent) + dChatMessage->loadFileTransferUrlFromBodyToContent(); + }; +} + void MainDb::cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask) { const string query = "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + buildSqlEventFilter({ @@ -2949,6 +2923,8 @@ IdentityAddress MainDb::findOneToOneConferenceChatRoomAddress ( return IdentityAddress(); } +void MainDb::loadChatMessageContents (const shared_ptr &) {} + void MainDb::cleanHistory (const ChatRoomId &, FilterMask) {} bool MainDb::import (Backend, const string &) { diff --git a/src/db/main-db.h b/src/db/main-db.h index 6b6c1ee90..a3e7574b1 100644 --- a/src/db/main-db.h +++ b/src/db/main-db.h @@ -125,6 +125,12 @@ public: void cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask = NoFilter); + // --------------------------------------------------------------------------- + // Chat messages. + // --------------------------------------------------------------------------- + + void loadChatMessageContents (const std::shared_ptr &chatMessage); + // --------------------------------------------------------------------------- // Chat rooms. // ---------------------------------------------------------------------------