mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-05-06 21:33:08 +00:00
feat(MainDb): increase performance of get history range
This commit is contained in:
parent
9fa36be4bf
commit
07443b1f60
9 changed files with 494 additions and 197 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
141
src/db/internal/safe-transaction.h
Normal file
141
src/db/internal/safe-transaction.h
Normal 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_
|
||||
76
src/db/internal/statements.cpp
Normal file
76
src/db/internal/statements.cpp
Normal 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
|
||||
46
src/db/internal/statements.h
Normal file
46
src/db/internal/statements.h
Normal 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_
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 &) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue