feat(MainDb): increase performance of get history range

This commit is contained in:
Ronan Abhamon 2018-02-15 08:28:12 +01:00
parent 9fa36be4bf
commit 07443b1f60
9 changed files with 494 additions and 197 deletions

View file

@ -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)

View file

@ -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<Content* > contents;
Content internalContent;
std::unordered_map<std::string, std::string> customHeaders;
@ -176,6 +182,7 @@ private:
bool encryptionPrevented = false;
bool toBeStored = true;
mutable bool contentsNotLoadedFromDatabase = false;
L_DECLARE_PUBLIC(ChatMessage);
};

View file

@ -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<ChatMessage>(q->getSharedFromThis())
);
isReadOnly = true;
}
}
bool ChatMessage::isRead () const {
L_D();
@ -939,6 +961,7 @@ bool ChatMessage::isReadOnly () const {
const list<Content *> &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<AbstractChatRoom> 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> core = getCore();
if (lp_config_get_int(core->getCCore()->config, "misc", "store_rtt_messages", 1) == 1) {
// TODO: History.

View file

@ -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 <soci/soci.h>
#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 *>(_mainDb);
return *this;
}
const char *name = nullptr;
MainDb *mainDb = nullptr;
};
template<typename Function>
class SafeTransaction {
using InternalReturnType = typename std::remove_reference<decltype(std::declval<Function>()())>::type;
public:
using ReturnType = typename std::conditional<
std::is_same<InternalReturnType, void>::value,
bool,
InternalReturnType
>::type;
SafeTransaction (SafeTransactionInfo &info, Function function) : mFunction(std::move(function)) {
try {
mResult= exec<InternalReturnType>();
} 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<InternalReturnType>();
} 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 T>
typename std::enable_if<std::is_same<T, void>::value, bool>::type exec () const {
mFunction();
return true;
}
// Exec function with return type.
template<typename T>
typename std::enable_if<!std::is_same<T, void>::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 Function>
typename SafeTransaction<Function>::ReturnType operator* (SafeTransactionInfo &info, Function &&function) {
return SafeTransaction<Function>(info, std::forward<Function>(function));
}
LINPHONE_END_NAMESPACE
#endif // ifndef _L_SAFE_TRANSACTION_H_

View file

@ -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

View file

@ -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_

View file

@ -103,6 +103,21 @@ private:
const ChatRoomId &chatRoomId
) const;
// TODO: Remove me. Workaround to increase fetch performance.
std::shared_ptr<EventLog> selectConferenceChatMessageEvent (
long long eventId,
EventLog::Type type,
time_t creationTime,
std::shared_ptr<AbstractChatRoom> &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<EventLog> selectConferenceParticipantEvent (
long long eventId,
EventLog::Type type,

View file

@ -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> &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 *>(_mainDb);
return *this;
}
const char *name = nullptr;
MainDb *mainDb = nullptr;
};
template<typename Function>
class SafeTransaction {
using InternalReturnType = typename remove_reference<decltype(declval<Function>()())>::type;
public:
using ReturnType = typename std::conditional<
std::is_same<InternalReturnType, void>::value,
bool,
InternalReturnType
>::type;
SafeTransaction (SafeTransactionInfo &info, Function function) : mFunction(move(function)) {
try {
mResult= exec<InternalReturnType>();
} 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<InternalReturnType>();
} 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 T>
typename std::enable_if<std::is_same<T, void>::value, bool>::type exec () const {
mFunction();
return true;
}
// Exec function with return type.
template<typename T>
typename std::enable_if<!std::is_same<T, void>::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 Function>
typename SafeTransaction<Function>::ReturnType operator* (SafeTransactionInfo &info, Function &&function) {
return SafeTransaction<Function>(info, forward<Function>(function));
}
// -----------------------------------------------------------------------------
// Soci backend.
// -----------------------------------------------------------------------------
@ -709,13 +603,8 @@ shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
return nullptr;
}
bool hasFileTransferContent = false;
// 1 - Fetch chat message.
shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
if (chatMessage)
goto end;
{
if (!chatMessage) {
string fromSipAddress;
string toSipAddress;
@ -737,6 +626,42 @@ shared_ptr<EventLog> 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<ConferenceChatMessageEvent>(
creationTime,
chatMessage
);
}
shared_ptr<EventLog> MainDbPrivate::selectConferenceChatMessageEvent (
long long eventId,
EventLog::Type type,
time_t creationTime,
shared_ptr<AbstractChatRoom> &chatRoom,
const string &fromSipAddress,
const string &toSipAddress,
const tm &messageTime,
const string &imdnMessageId,
int state,
int direction,
int isSecured
) const {
shared_ptr<ChatMessage> chatMessage = getChatMessageFromCache(eventId);
if (!chatMessage) {
chatMessage = shared_ptr<ChatMessage>(new ChatMessage(
chatRoom,
ChatMessage::Direction(direction)
@ -751,66 +676,13 @@ shared_ptr<EventLog> 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<soci::row> rows = (session->prepare << query, soci::use(eventId));
for (const auto &row : rows) {
ContentType contentType(row.get<string>(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<string>(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<ConferenceChatMessageEvent>(
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<Statements::Create>(i), backend));
*session <<
"CREATE TABLE IF NOT EXISTS db_module_version ("
" name" + varcharPrimaryKeyStr(255) + ","
@ -2364,10 +2248,7 @@ list<shared_ptr<EventLog>> 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<shared_ptr<EventLog>> MainDb::getHistoryRange (
soci::session *session = d->dbSession.getBackendSession();
soci::transaction tr(*session);
shared_ptr<Core> core = getCore();
shared_ptr<AbstractChatRoom> 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<soci::row> rows = (session->prepare << query, soci::use(dbChatRoomId));
for (const auto &row : rows) {
long long eventId = d->dbSession.resolveId(row, 0);
shared_ptr<EventLog> event = d->getEventFromCache(eventId);
if (!event)
event = d->selectGenericConferenceEvent(
eventId,
EventLog::Type(row.get<int>(1)),
Utils::getTmAsTimeT(row.get<tm>(2)),
chatRoomId
);
if (!event) {
EventLog::Type type = EventLog::Type(row.get<int>(1));
// TODO: Remove me. Performance workaround.
if (type == EventLog::Type::ConferenceChatMessage)
event = d->selectConferenceChatMessageEvent(
eventId,
type,
Utils::getTmAsTimeT(row.get<tm>(2)),
chatRoom,
row.get<string>(3),
row.get<string>(4),
row.get<tm>(5),
row.get<string>(6),
row.get<int>(7),
row.get<int>(8),
row.get<int>(9)
);
else
event = d->selectGenericConferenceEvent(
eventId,
type,
Utils::getTmAsTimeT(row.get<tm>(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> &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<MainDbKey &>(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<soci::row> rows = (session->prepare << query, soci::use(eventId));
for (const auto &row : rows) {
ContentType contentType(row.get<string>(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<string>(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<ChatMessage> &) {}
void MainDb::cleanHistory (const ChatRoomId &, FilterMask) {}
bool MainDb::import (Backend, const string &) {

View file

@ -125,6 +125,12 @@ public:
void cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask = NoFilter);
// ---------------------------------------------------------------------------
// Chat messages.
// ---------------------------------------------------------------------------
void loadChatMessageContents (const std::shared_ptr<ChatMessage> &chatMessage);
// ---------------------------------------------------------------------------
// Chat rooms.
// ---------------------------------------------------------------------------