From 5a9d0dfd3ebd9ad1fb696d6ede7c1754c86eaa57 Mon Sep 17 00:00:00 2001 From: Ronan Abhamon Date: Fri, 22 Dec 2017 12:42:54 +0100 Subject: [PATCH] feat(BasicToClientGroupChatRoom): capabilities contains Migrate if the proxy is a basic chatroom --- .../basic-to-client-group-chat-room.cpp | 5 +- src/db/main-db.cpp | 4485 +++++++++-------- 2 files changed, 2249 insertions(+), 2241 deletions(-) diff --git a/src/chat/chat-room/basic-to-client-group-chat-room.cpp b/src/chat/chat-room/basic-to-client-group-chat-room.cpp index 6a6637742..4be674e01 100644 --- a/src/chat/chat-room/basic-to-client-group-chat-room.cpp +++ b/src/chat/chat-room/basic-to-client-group-chat-room.cpp @@ -92,7 +92,10 @@ BasicToClientGroupChatRoom::BasicToClientGroupChatRoom (const shared_ptrchatRoom->getCapabilities() | BasicToClientGroupChatRoom::Capabilities::Migratable; + CapabilitiesMask capabilities = d->chatRoom->getCapabilities(); + if (capabilities.isSet(Capabilities::Basic)) + capabilities.set(Capabilities::Migratable); + return capabilities; } shared_ptr BasicToClientGroupChatRoom::createChatMessage () { diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index 10b7b0882..7decf2e3d 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -65,67 +65,67 @@ MainDb::MainDb (const shared_ptr &core) : AbstractDb(*new MainDbPrivate), // Soci backend. // ----------------------------------------------------------------------------- - template - struct EnumToSql { - T first; - const char *second; - }; +template +struct EnumToSql { + T first; + const char *second; +}; - template - static constexpr const char *mapEnumToSql (const EnumToSql enumToSql[], size_t n, T key) { - return n == 0 ? "" : ( - enumToSql[n - 1].first == key ? enumToSql[n - 1].second : mapEnumToSql(enumToSql, n - 1, key) - ); - } +template +static constexpr const char *mapEnumToSql (const EnumToSql enumToSql[], size_t n, T key) { + return n == 0 ? "" : ( + enumToSql[n - 1].first == key ? enumToSql[n - 1].second : mapEnumToSql(enumToSql, n - 1, key) + ); +} - static constexpr EnumToSql eventFilterToSql[] = { - { MainDb::ConferenceCallFilter, "3, 4" }, - { MainDb::ConferenceChatMessageFilter, "5" }, - { MainDb::ConferenceInfoFilter, "1, 2, 6, 7, 8, 9, 10, 11, 12" } - }; +static constexpr EnumToSql eventFilterToSql[] = { + { MainDb::ConferenceCallFilter, "3, 4" }, + { MainDb::ConferenceChatMessageFilter, "5" }, + { MainDb::ConferenceInfoFilter, "1, 2, 6, 7, 8, 9, 10, 11, 12" } +}; - static constexpr const char *mapEventFilterToSql (MainDb::Filter filter) { - return mapEnumToSql( - eventFilterToSql, sizeof eventFilterToSql / sizeof eventFilterToSql[0], filter - ); - } +static constexpr const char *mapEventFilterToSql (MainDb::Filter filter) { + return mapEnumToSql( + eventFilterToSql, sizeof eventFilterToSql / sizeof eventFilterToSql[0], filter + ); +} // ----------------------------------------------------------------------------- - static string buildSqlEventFilter ( - const list &filters, - MainDb::FilterMask mask, - const string &condKeyWord = "WHERE" - ) { - L_ASSERT( - find_if(filters.cbegin(), filters.cend(), [](const MainDb::Filter &filter) { - return filter == MainDb::NoFilter; - }) == filters.cend() - ); +static string buildSqlEventFilter ( + const list &filters, + MainDb::FilterMask mask, + const string &condKeyWord = "WHERE" +) { + L_ASSERT( + find_if(filters.cbegin(), filters.cend(), [](const MainDb::Filter &filter) { + return filter == MainDb::NoFilter; + }) == filters.cend() + ); - if (mask == MainDb::NoFilter) - return ""; + if (mask == MainDb::NoFilter) + return ""; - bool isStart = true; - string sql; - for (const auto &filter : filters) { - if (!mask.isSet(filter)) - continue; + bool isStart = true; + string sql; + for (const auto &filter : filters) { + if (!mask.isSet(filter)) + continue; - if (isStart) { - isStart = false; - sql += " " + condKeyWord + " type IN ("; - } else - sql += ", "; - sql += mapEventFilterToSql(filter); - } - - if (!isStart) - sql += ") "; - - return sql; + if (isStart) { + isStart = false; + sql += " " + condKeyWord + " type IN ("; + } else + sql += ", "; + sql += mapEventFilterToSql(filter); } + if (!isStart) + sql += ") "; + + return sql; +} + // ----------------------------------------------------------------------------- static inline vector blobToVector (soci::blob &in) { @@ -148,2421 +148,2426 @@ static constexpr string &blobToString (string &in) { // ----------------------------------------------------------------------------- - long long MainDbPrivate::resolveId (const soci::row &row, int col) const { - L_Q(); - // See: http://soci.sourceforge.net/doc/master/backends/ - // `row id` is not supported by soci on Sqlite3. It's necessary to cast id to int... - return q->getBackend() == AbstractDb::Sqlite3 - ? static_cast(row.get(0)) - : static_cast(row.get(0)); - } +long long MainDbPrivate::resolveId (const soci::row &row, int col) const { + L_Q(); + // See: http://soci.sourceforge.net/doc/master/backends/ + // `row id` is not supported by soci on Sqlite3. It's necessary to cast id to int... + return q->getBackend() == AbstractDb::Sqlite3 + ? static_cast(row.get(0)) + : static_cast(row.get(0)); +} // ----------------------------------------------------------------------------- - long long MainDbPrivate::insertSipAddress (const string &sipAddress) { - L_Q(); - soci::session *session = dbSession.getBackendSession(); +long long MainDbPrivate::insertSipAddress (const string &sipAddress) { + L_Q(); + soci::session *session = dbSession.getBackendSession(); - long long id = selectSipAddressId(sipAddress); - if (id >= 0) - return id; + long long id = selectSipAddressId(sipAddress); + if (id >= 0) + return id; - lInfo() << "Insert new sip address in database: `" << sipAddress << "`."; - *session << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress); - return q->getLastInsertId(); + lInfo() << "Insert new sip address in database: `" << sipAddress << "`."; + *session << "INSERT INTO sip_address (value) VALUES (:sipAddress)", soci::use(sipAddress); + return q->getLastInsertId(); +} + +void MainDbPrivate::insertContent (long long eventId, const Content &content) { + L_Q(); + soci::session *session = dbSession.getBackendSession(); + + const long long &contentTypeId = insertContentType(content.getContentType().asString()); + const string &body = content.getBodyAsString(); + *session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES" + " (:eventId, :contentTypeId, :body)", soci::use(eventId), soci::use(contentTypeId), + soci::use(body); + + const long long &chatMessageContentId = q->getLastInsertId(); + if (content.getContentType().isFile()) { + const FileContent &fileContent = static_cast(content); + const string &name = fileContent.getFileName(); + const size_t &size = fileContent.getFileSize(); + const string &path = fileContent.getFilePath(); + *session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES " + " (:chatMessageContentId, :name, :size, :path)", + soci::use(chatMessageContentId), soci::use(name), soci::use(size), soci::use(path); } - void MainDbPrivate::insertContent (long long eventId, const Content &content) { - L_Q(); - soci::session *session = dbSession.getBackendSession(); + for (const auto &appData : content.getAppDataMap()) + *session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, name, data) VALUES" + " (:chatMessageContentId, :name, :data)", + soci::use(chatMessageContentId), soci::use(appData.first), soci::use(appData.second); +} - const long long &contentTypeId = insertContentType(content.getContentType().asString()); - const string &body = content.getBodyAsString(); - *session << "INSERT INTO chat_message_content (event_id, content_type_id, body) VALUES" - " (:eventId, :contentTypeId, :body)", soci::use(eventId), soci::use(contentTypeId), - soci::use(body); +long long MainDbPrivate::insertContentType (const string &contentType) { + L_Q(); + soci::session *session = dbSession.getBackendSession(); - const long long &chatMessageContentId = q->getLastInsertId(); - if (content.getContentType().isFile()) { - const FileContent &fileContent = static_cast(content); - const string &name = fileContent.getFileName(); - const size_t &size = fileContent.getFileSize(); - const string &path = fileContent.getFilePath(); - *session << "INSERT INTO chat_message_file_content (chat_message_content_id, name, size, path) VALUES " - " (:chatMessageContentId, :name, :size, :path)", - soci::use(chatMessageContentId), soci::use(name), soci::use(size), soci::use(path); - } + long long id; + *session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(id); + if (session->got_data()) + return id; - for (const auto &appData : content.getAppDataMap()) - *session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, name, data) VALUES" - " (:chatMessageContentId, :name, :data)", - soci::use(chatMessageContentId), soci::use(appData.first), soci::use(appData.second); - } + lInfo() << "Insert new content type in database: `" << contentType << "`."; + *session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType); + return q->getLastInsertId(); +} - long long MainDbPrivate::insertContentType (const string &contentType) { - L_Q(); - soci::session *session = dbSession.getBackendSession(); +long long MainDbPrivate::insertOrUpdateImportedBasicChatRoom ( + long long peerSipAddressId, + long long localSipAddressId, + const tm &creationTime +) { + L_Q(); - long long id; - *session << "SELECT id FROM content_type WHERE value = :contentType", soci::use(contentType), soci::into(id); - if (session->got_data()) - return id; - - lInfo() << "Insert new content type in database: `" << contentType << "`."; - *session << "INSERT INTO content_type (value) VALUES (:contentType)", soci::use(contentType); - return q->getLastInsertId(); - } - - long long MainDbPrivate::insertOrUpdateImportedBasicChatRoom ( - long long peerSipAddressId, - long long localSipAddressId, - const tm &creationTime - ) { - L_Q(); - - soci::session *session = dbSession.getBackendSession(); - - long long id = selectChatRoomId(peerSipAddressId, localSipAddressId); - if (id >= 0) { - *session << "UPDATE chat_room SET last_update_time = :lastUpdateTime WHERE id = :id", - soci::use(creationTime), soci::use(id); - return id; - } - - static const int capabilities = ChatRoom::CapabilitiesMask( - { ChatRoom::Capabilities::Basic, ChatRoom::Capabilities::Migratable } - ); - lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << - ", local=" << localSipAddressId << ", capabilities=" << capabilities << ")."; - *session << "INSERT INTO chat_room (" - " peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities" - ") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities)", - soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime), - soci::use(capabilities); - - return q->getLastInsertId(); - } - - long long MainDbPrivate::insertChatRoom (const shared_ptr &chatRoom) { - L_Q(); - - soci::session *session = dbSession.getBackendSession(); - - const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); - const long long &peerSipAddressId = insertSipAddress(chatRoomId.getPeerAddress().asString()); - const long long &localSipAddressId = insertSipAddress(chatRoomId.getLocalAddress().asString()); - - long long id = selectChatRoomId(peerSipAddressId, localSipAddressId); - if (id >= 0) { - lError() << "Unable to insert chat room (it already exists): (peer=" << peerSipAddressId << - ", local=" << localSipAddressId << ")."; - return id; - } - - lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << - ", local=" << localSipAddressId << ")."; - - const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime()); - const int &capabilities = chatRoom->getCapabilities(); - const string &subject = chatRoom->getSubject(); - const int &flags = chatRoom->hasBeenLeft(); - *session << "INSERT INTO chat_room (" - " peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities, subject, flags" - ") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities, :subject, :flags)", - soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime), - soci::use(capabilities), soci::use(subject), soci::use(flags); - - id = q->getLastInsertId(); - if (!chatRoom->canHandleParticipants()) - return id; - - // Do not add 'me' when creating a server-group-chat-room. - if (chatRoomId.getLocalAddress() != chatRoomId.getPeerAddress()) { - shared_ptr me = chatRoom->getMe(); - long long meId = insertChatRoomParticipant( - id, - insertSipAddress(me->getAddress().asString()), - me->isAdmin() - ); - for (const auto &device : me->getPrivate()->getDevices()) - insertChatRoomParticipantDevice(meId, insertSipAddress(device->getAddress().asString())); - } - - for (const auto &participant : chatRoom->getParticipants()) { - long long participantId = insertChatRoomParticipant( - id, - insertSipAddress(participant->getAddress().asString()), - participant->isAdmin() - ); - for (const auto &device : participant->getPrivate()->getDevices()) - insertChatRoomParticipantDevice(participantId, insertSipAddress(device->getAddress().asString())); - } + soci::session *session = dbSession.getBackendSession(); + long long id = selectChatRoomId(peerSipAddressId, localSipAddressId); + if (id >= 0) { + *session << "UPDATE chat_room SET last_update_time = :lastUpdateTime WHERE id = :id", + soci::use(creationTime), soci::use(id); return id; } - long long MainDbPrivate::insertChatRoomParticipant ( - long long chatRoomId, - long long participantSipAddressId, - bool isAdmin - ) { - L_Q(); + static const int capabilities = ChatRoom::CapabilitiesMask( + { ChatRoom::Capabilities::Basic, ChatRoom::Capabilities::Migratable } + ); + lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << + ", local=" << localSipAddressId << ", capabilities=" << capabilities << ")."; + *session << "INSERT INTO chat_room (" + " peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities" + ") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities)", + soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime), + soci::use(capabilities); - soci::session *session = dbSession.getBackendSession(); - long long id = selectChatRoomParticipantId(chatRoomId, participantSipAddressId); - if (id >= 0) { - // See: https://stackoverflow.com/a/15299655 (cast to reference) - *session << "UPDATE chat_room_participant SET is_admin = :isAdmin WHERE id = :id", - soci::use(static_cast(isAdmin)), soci::use(id); - return id; - } + return q->getLastInsertId(); +} - lInfo() << "Insert new chat room participant in database: `" << participantSipAddressId << - "` (isAdmin=" << isAdmin << ")."; - *session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)" - " VALUES (:chatRoomId, :participantSipAddressId, :isAdmin)", - soci::use(chatRoomId), soci::use(participantSipAddressId), soci::use(static_cast(isAdmin)); +long long MainDbPrivate::insertChatRoom (const shared_ptr &chatRoom) { + L_Q(); - return q->getLastInsertId(); + soci::session *session = dbSession.getBackendSession(); + + const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); + const long long &peerSipAddressId = insertSipAddress(chatRoomId.getPeerAddress().asString()); + const long long &localSipAddressId = insertSipAddress(chatRoomId.getLocalAddress().asString()); + + long long id = selectChatRoomId(peerSipAddressId, localSipAddressId); + if (id >= 0) { + lError() << "Unable to insert chat room (it already exists): (peer=" << peerSipAddressId << + ", local=" << localSipAddressId << ")."; + return id; } - void MainDbPrivate::insertChatRoomParticipantDevice ( - long long participantId, - long long participantDeviceSipAddressId - ) { - soci::session *session = dbSession.getBackendSession(); - long long count; - *session << "SELECT COUNT(*) FROM chat_room_participant_device" - " WHERE chat_room_participant_id = :participantId" - " AND participant_device_sip_address_id = :participantDeviceSipAddressId", - soci::into(count), soci::use(participantId), soci::use(participantDeviceSipAddressId); - if (count) - return; + lInfo() << "Insert new chat room in database: (peer=" << peerSipAddressId << + ", local=" << localSipAddressId << ")."; - lInfo() << "Insert new chat room participant device in database: `" << participantDeviceSipAddressId << "`."; - *session << "INSERT INTO chat_room_participant_device (chat_room_participant_id, participant_device_sip_address_id)" - " VALUES (:participantId, :participantDeviceSipAddressId)", - soci::use(participantId), soci::use(participantDeviceSipAddressId); - } + const tm &creationTime = Utils::getTimeTAsTm(chatRoom->getCreationTime()); + // Remove capabilities like `Proxy`. + const int &capabilities = chatRoom->getCapabilities() & ChatRoom::CapabilitiesMask({ + ChatRoom::Capabilities::Basic, ChatRoom::Capabilities::RealTimeText, + ChatRoom::Capabilities::Conference, ChatRoom::Capabilities::Migratable + }); - void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) { - // TODO: Deal with read messages. - // Remove if displayed? Think a good alorithm for mark as read. - soci::session *session = dbSession.getBackendSession(); - soci::statement statement = ( - session->prepare << "UPDATE chat_message_participant SET state = :state" - " WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId", - soci::use(state), soci::use(eventId), soci::use(sipAddressId) + const string &subject = chatRoom->getSubject(); + const int &flags = chatRoom->hasBeenLeft(); + *session << "INSERT INTO chat_room (" + " peer_sip_address_id, local_sip_address_id, creation_time, last_update_time, capabilities, subject, flags" + ") VALUES (:peerSipAddressId, :localSipAddressId, :creationTime, :lastUpdateTime, :capabilities, :subject, :flags)", + soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::use(creationTime), soci::use(creationTime), + soci::use(capabilities), soci::use(subject), soci::use(flags); + + id = q->getLastInsertId(); + if (!chatRoom->canHandleParticipants()) + return id; + + // Do not add 'me' when creating a server-group-chat-room. + if (chatRoomId.getLocalAddress() != chatRoomId.getPeerAddress()) { + shared_ptr me = chatRoom->getMe(); + long long meId = insertChatRoomParticipant( + id, + insertSipAddress(me->getAddress().asString()), + me->isAdmin() ); - statement.execute(); - if (statement.get_affected_rows() == 0 && state != static_cast(ChatMessage::State::Displayed)) - *session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)" - " VALUES (:eventId, :sipAddressId, :state)", - soci::use(eventId), soci::use(sipAddressId), soci::use(state); + for (const auto &device : me->getPrivate()->getDevices()) + insertChatRoomParticipantDevice(meId, insertSipAddress(device->getAddress().asString())); } -// ----------------------------------------------------------------------------- - - long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const { - soci::session *session = dbSession.getBackendSession(); - - long long id; - *session << "SELECT id FROM sip_address WHERE value = :sipAddress", soci::use(sipAddress), soci::into(id); - return session->got_data() ? id : -1; - } - - long long MainDbPrivate::selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const { - soci::session *session = dbSession.getBackendSession(); - - long long id; - *session << "SELECT id FROM chat_room" - " WHERE peer_sip_address_id = :peerSipAddressId AND local_sip_address_id = :localSipAddressId", - soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::into(id); - return session->got_data() ? id : -1; - } - - long long MainDbPrivate::selectChatRoomId (const ChatRoomId &chatRoomId) const { - long long peerSipAddressId = selectSipAddressId(chatRoomId.getPeerAddress().asString()); - if (peerSipAddressId < 0) - return -1; - - long long localSipAddressId = selectSipAddressId(chatRoomId.getLocalAddress().asString()); - if (localSipAddressId < 0) - return -1; - - return selectChatRoomId(peerSipAddressId, localSipAddressId); - } - - long long MainDbPrivate::selectChatRoomParticipantId (long long chatRoomId, long long participantSipAddressId) const { - long long id; - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT id from chat_room_participant" - " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", - soci::into(id), soci::use(chatRoomId), soci::use(participantSipAddressId); - return session->got_data() ? id : -1; - } - -// ----------------------------------------------------------------------------- - - void MainDbPrivate::deleteContents (long long messageEventId) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_message_content WHERE event_id = :messageEventId", soci::use(messageEventId); - } - - void MainDbPrivate::deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_room_participant" - " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", - soci::use(chatRoomId), soci::use(participantSipAddressId); - } - - void MainDbPrivate::deleteChatRoomParticipantDevice ( - long long participantId, - long long participantDeviceSipAddressId - ) { - soci::session *session = dbSession.getBackendSession(); - *session << "DELETE FROM chat_room_participant_device" - " WHERE chat_room_participant_id = :participantId" - " AND participant_device_sip_address_id = :participantDeviceSipAddressId", - soci::use(participantId), soci::use(participantDeviceSipAddressId); - } - -// ----------------------------------------------------------------------------- - - shared_ptr MainDbPrivate::selectGenericConferenceEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - shared_ptr eventLog; - - switch (type) { - case EventLog::Type::None: - return nullptr; - - case EventLog::Type::ConferenceCreated: - case EventLog::Type::ConferenceTerminated: - eventLog = selectConferenceEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceCallStart: - case EventLog::Type::ConferenceCallEnd: - eventLog = selectConferenceCallEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceChatMessage: - eventLog = selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceParticipantAdded: - case EventLog::Type::ConferenceParticipantRemoved: - case EventLog::Type::ConferenceParticipantSetAdmin: - case EventLog::Type::ConferenceParticipantUnsetAdmin: - eventLog = selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceParticipantDeviceAdded: - case EventLog::Type::ConferenceParticipantDeviceRemoved: - eventLog = selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId); - break; - - case EventLog::Type::ConferenceSubjectChanged: - eventLog = selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId); - break; - } - - if (eventLog) - cache(eventLog, eventId); - - return eventLog; - } - - shared_ptr MainDbPrivate::selectConferenceEvent ( - long long, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - return make_shared( - type, - creationTime, - chatRoomId + for (const auto &participant : chatRoom->getParticipants()) { + long long participantId = insertChatRoomParticipant( + id, + insertSipAddress(participant->getAddress().asString()), + participant->isAdmin() ); + for (const auto &device : participant->getPrivate()->getDevices()) + insertChatRoomParticipantDevice(participantId, insertSipAddress(device->getAddress().asString())); } - shared_ptr MainDbPrivate::selectConferenceCallEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - // TODO. + return id; +} + +long long MainDbPrivate::insertChatRoomParticipant ( + long long chatRoomId, + long long participantSipAddressId, + bool isAdmin +) { + L_Q(); + + soci::session *session = dbSession.getBackendSession(); + long long id = selectChatRoomParticipantId(chatRoomId, participantSipAddressId); + if (id >= 0) { + // See: https://stackoverflow.com/a/15299655 (cast to reference) + *session << "UPDATE chat_room_participant SET is_admin = :isAdmin WHERE id = :id", + soci::use(static_cast(isAdmin)), soci::use(id); + return id; + } + + lInfo() << "Insert new chat room participant in database: `" << participantSipAddressId << + "` (isAdmin=" << isAdmin << ")."; + *session << "INSERT INTO chat_room_participant (chat_room_id, participant_sip_address_id, is_admin)" + " VALUES (:chatRoomId, :participantSipAddressId, :isAdmin)", + soci::use(chatRoomId), soci::use(participantSipAddressId), soci::use(static_cast(isAdmin)); + + return q->getLastInsertId(); +} + +void MainDbPrivate::insertChatRoomParticipantDevice ( + long long participantId, + long long participantDeviceSipAddressId +) { + soci::session *session = dbSession.getBackendSession(); + long long count; + *session << "SELECT COUNT(*) FROM chat_room_participant_device" + " WHERE chat_room_participant_id = :participantId" + " AND participant_device_sip_address_id = :participantDeviceSipAddressId", + soci::into(count), soci::use(participantId), soci::use(participantDeviceSipAddressId); + if (count) + return; + + lInfo() << "Insert new chat room participant device in database: `" << participantDeviceSipAddressId << "`."; + *session << "INSERT INTO chat_room_participant_device (chat_room_participant_id, participant_device_sip_address_id)" + " VALUES (:participantId, :participantDeviceSipAddressId)", + soci::use(participantId), soci::use(participantDeviceSipAddressId); +} + +void MainDbPrivate::insertChatMessageParticipant (long long eventId, long long sipAddressId, int state) { + // TODO: Deal with read messages. + // Remove if displayed? Think a good alorithm for mark as read. + soci::session *session = dbSession.getBackendSession(); + soci::statement statement = ( + session->prepare << "UPDATE chat_message_participant SET state = :state" + " WHERE event_id = :eventId AND participant_sip_address_id = :sipAddressId", + soci::use(state), soci::use(eventId), soci::use(sipAddressId) + ); + statement.execute(); + if (statement.get_affected_rows() == 0 && state != static_cast(ChatMessage::State::Displayed)) + *session << "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)" + " VALUES (:eventId, :sipAddressId, :state)", + soci::use(eventId), soci::use(sipAddressId), soci::use(state); +} + +// ----------------------------------------------------------------------------- + +long long MainDbPrivate::selectSipAddressId (const string &sipAddress) const { + soci::session *session = dbSession.getBackendSession(); + + long long id; + *session << "SELECT id FROM sip_address WHERE value = :sipAddress", soci::use(sipAddress), soci::into(id); + return session->got_data() ? id : -1; +} + +long long MainDbPrivate::selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const { + soci::session *session = dbSession.getBackendSession(); + + long long id; + *session << "SELECT id FROM chat_room" + " WHERE peer_sip_address_id = :peerSipAddressId AND local_sip_address_id = :localSipAddressId", + soci::use(peerSipAddressId), soci::use(localSipAddressId), soci::into(id); + return session->got_data() ? id : -1; +} + +long long MainDbPrivate::selectChatRoomId (const ChatRoomId &chatRoomId) const { + long long peerSipAddressId = selectSipAddressId(chatRoomId.getPeerAddress().asString()); + if (peerSipAddressId < 0) + return -1; + + long long localSipAddressId = selectSipAddressId(chatRoomId.getLocalAddress().asString()); + if (localSipAddressId < 0) + return -1; + + return selectChatRoomId(peerSipAddressId, localSipAddressId); +} + +long long MainDbPrivate::selectChatRoomParticipantId (long long chatRoomId, long long participantSipAddressId) const { + long long id; + soci::session *session = dbSession.getBackendSession(); + *session << "SELECT id from chat_room_participant" + " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", + soci::into(id), soci::use(chatRoomId), soci::use(participantSipAddressId); + return session->got_data() ? id : -1; +} + +// ----------------------------------------------------------------------------- + +void MainDbPrivate::deleteContents (long long messageEventId) { + soci::session *session = dbSession.getBackendSession(); + *session << "DELETE FROM chat_message_content WHERE event_id = :messageEventId", soci::use(messageEventId); +} + +void MainDbPrivate::deleteChatRoomParticipant (long long chatRoomId, long long participantSipAddressId) { + soci::session *session = dbSession.getBackendSession(); + *session << "DELETE FROM chat_room_participant" + " WHERE chat_room_id = :chatRoomId AND participant_sip_address_id = :participantSipAddressId", + soci::use(chatRoomId), soci::use(participantSipAddressId); +} + +void MainDbPrivate::deleteChatRoomParticipantDevice ( + long long participantId, + long long participantDeviceSipAddressId +) { + soci::session *session = dbSession.getBackendSession(); + *session << "DELETE FROM chat_room_participant_device" + " WHERE chat_room_participant_id = :participantId" + " AND participant_device_sip_address_id = :participantDeviceSipAddressId", + soci::use(participantId), soci::use(participantDeviceSipAddressId); +} + +// ----------------------------------------------------------------------------- + +shared_ptr MainDbPrivate::selectGenericConferenceEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + shared_ptr eventLog; + + switch (type) { + case EventLog::Type::None: + return nullptr; + + case EventLog::Type::ConferenceCreated: + case EventLog::Type::ConferenceTerminated: + eventLog = selectConferenceEvent(eventId, type, creationTime, chatRoomId); + break; + + case EventLog::Type::ConferenceCallStart: + case EventLog::Type::ConferenceCallEnd: + eventLog = selectConferenceCallEvent(eventId, type, creationTime, chatRoomId); + break; + + case EventLog::Type::ConferenceChatMessage: + eventLog = selectConferenceChatMessageEvent(eventId, type, creationTime, chatRoomId); + break; + + case EventLog::Type::ConferenceParticipantAdded: + case EventLog::Type::ConferenceParticipantRemoved: + case EventLog::Type::ConferenceParticipantSetAdmin: + case EventLog::Type::ConferenceParticipantUnsetAdmin: + eventLog = selectConferenceParticipantEvent(eventId, type, creationTime, chatRoomId); + break; + + case EventLog::Type::ConferenceParticipantDeviceAdded: + case EventLog::Type::ConferenceParticipantDeviceRemoved: + eventLog = selectConferenceParticipantDeviceEvent(eventId, type, creationTime, chatRoomId); + break; + + case EventLog::Type::ConferenceSubjectChanged: + eventLog = selectConferenceSubjectEvent(eventId, type, creationTime, chatRoomId); + break; + } + + if (eventLog) + cache(eventLog, eventId); + + return eventLog; +} + +shared_ptr MainDbPrivate::selectConferenceEvent ( + long long, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + return make_shared( + type, + creationTime, + chatRoomId + ); +} + +shared_ptr MainDbPrivate::selectConferenceCallEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + // TODO. + return nullptr; +} + +template +static void fetchContentAppData (soci::session *session, Content &content, long long contentId, T &data) { + static const string query = "SELECT name, data FROM chat_message_content_app_data" + " WHERE chat_message_content_id = :contentId"; + + string name; + soci::statement statement = (session->prepare << query, soci::use(contentId), soci::into(name), soci::into(data)); + statement.execute(); + while (statement.fetch()) + content.setAppData(name, blobToString(data)); +} + +shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + L_Q(); + + shared_ptr core = q->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 nullptr; } - template - static void fetchContentAppData (soci::session *session, Content &content, long long contentId, T &data) { - static const string query = "SELECT name, data FROM chat_message_content_app_data" - " WHERE chat_message_content_id = :contentId"; + bool hasFileTransferContent = false; - string name; - soci::statement statement = (session->prepare << query, soci::use(contentId), soci::into(name), soci::into(data)); - statement.execute(); - while (statement.fetch()) - content.setAppData(name, blobToString(data)); + // 1 - Fetch chat message. + shared_ptr chatMessage = getChatMessageFromCache(eventId); + if (chatMessage) + goto end; + { + string fromSipAddress; + string toSipAddress; + + tm messageTime; + + string imdnMessageId; + + int state; + int direction; + int isSecured; + + soci::session *session = dbSession.getBackendSession(); + *session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured" + " FROM event, conference_chat_message_event, sip_address AS from_sip_address, sip_address AS to_sip_address" + " WHERE event_id = :eventId" + " AND event_id = event.id" + " AND from_sip_address_id = from_sip_address.id" + " AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress), + soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction), + soci::into(isSecured), soci::use(eventId); + + chatMessage = shared_ptr(new ChatMessage( + chatRoom, + static_cast(direction) + )); + chatMessage->setIsSecured(static_cast(isSecured)); + + ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); + dChatMessage->setState(static_cast(state), true); + + dChatMessage->forceFromAddress(IdentityAddress(fromSipAddress)); + dChatMessage->forceToAddress(IdentityAddress(toSipAddress)); + + dChatMessage->setTime(Utils::getTmAsTimeT(messageTime)); + dChatMessage->setImdnMessageId(imdnMessageId); } - shared_ptr MainDbPrivate::selectConferenceChatMessageEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - L_Q(); + // 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 = resolveId(row, 0); + Content *content; - shared_ptr core = q->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 nullptr; - } - - bool hasFileTransferContent = false; - - // 1 - Fetch chat message. - shared_ptr chatMessage = getChatMessageFromCache(eventId); - if (chatMessage) - goto end; - { - string fromSipAddress; - string toSipAddress; - - tm messageTime; - - string imdnMessageId; - - int state; - int direction; - int isSecured; - - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT from_sip_address.value, to_sip_address.value, time, imdn_message_id, state, direction, is_secured" - " FROM event, conference_chat_message_event, sip_address AS from_sip_address, sip_address AS to_sip_address" - " WHERE event_id = :eventId" - " AND event_id = event.id" - " AND from_sip_address_id = from_sip_address.id" - " AND to_sip_address_id = to_sip_address.id", soci::into(fromSipAddress), soci::into(toSipAddress), - soci::into(messageTime), soci::into(imdnMessageId), soci::into(state), soci::into(direction), - soci::into(isSecured), soci::use(eventId); - - chatMessage = shared_ptr(new ChatMessage( - chatRoom, - static_cast(direction) - )); - chatMessage->setIsSecured(static_cast(isSecured)); - - ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); - dChatMessage->setState(static_cast(state), true); - - dChatMessage->forceFromAddress(IdentityAddress(fromSipAddress)); - dChatMessage->forceToAddress(IdentityAddress(toSipAddress)); - - dChatMessage->setTime(Utils::getTmAsTimeT(messageTime)); - dChatMessage->setImdnMessageId(imdnMessageId); - } - - // 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 = 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(static_cast(size)); - fileContent->setFilePath(path); - - content = fileContent; - } else - content = new Content(); - - content->setContentType(contentType); - content->setBody(row.get(3)); - - // 2.2 - Fetch contents' app data. - 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); + 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(static_cast(size)); + fileContent->setFilePath(path); + + content = fileContent; + } else + content = new Content(); + + content->setContentType(contentType); + content->setBody(row.get(3)); + + // 2.2 - Fetch contents' app data. + 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 - ); } - shared_ptr MainDbPrivate::selectConferenceParticipantEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - unsigned int notifyId; - string participantAddress; + // 3 - Load external body url from body into FileTransferContent if needed. + if (hasFileTransferContent) + chatMessage->getPrivate()->loadFileTransferUrlFromBodyToContent(); - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, participant_address.value" - " FROM conference_notified_event, conference_participant_event, sip_address as participant_address" - " WHERE conference_participant_event.event_id = :eventId" - " AND conference_notified_event.event_id = conference_participant_event.event_id" - " AND participant_address.id = participant_sip_address_id", - soci::into(notifyId), soci::into(participantAddress), soci::use(eventId); + cache(chatMessage, eventId); - return make_shared( - type, - creationTime, - chatRoomId, - notifyId, - IdentityAddress(participantAddress) - ); - } +end: + return make_shared( + creationTime, + chatMessage + ); +} - shared_ptr MainDbPrivate::selectConferenceParticipantDeviceEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - unsigned int notifyId; - string participantAddress; - string deviceAddress; +shared_ptr MainDbPrivate::selectConferenceParticipantEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + unsigned int notifyId; + string participantAddress; - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, participant_address.value, device_address.value" - " FROM conference_notified_event, conference_participant_event, conference_participant_device_event," - " sip_address AS participant_address, sip_address AS device_address" - " WHERE conference_participant_device_event.event_id = :eventId" - " AND conference_participant_event.event_id = conference_participant_device_event.event_id" - " AND conference_notified_event.event_id = conference_participant_event.event_id" - " AND participant_address.id = participant_sip_address_id" - " AND device_address.id = device_sip_address_id", - soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId); + soci::session *session = dbSession.getBackendSession(); + *session << "SELECT notify_id, participant_address.value" + " FROM conference_notified_event, conference_participant_event, sip_address as participant_address" + " WHERE conference_participant_event.event_id = :eventId" + " AND conference_notified_event.event_id = conference_participant_event.event_id" + " AND participant_address.id = participant_sip_address_id", + soci::into(notifyId), soci::into(participantAddress), soci::use(eventId); - return make_shared( - type, - creationTime, - chatRoomId, - notifyId, - IdentityAddress(participantAddress), - IdentityAddress(deviceAddress) - ); - } + return make_shared( + type, + creationTime, + chatRoomId, + notifyId, + IdentityAddress(participantAddress) + ); +} - shared_ptr MainDbPrivate::selectConferenceSubjectEvent ( - long long eventId, - EventLog::Type type, - time_t creationTime, - const ChatRoomId &chatRoomId - ) const { - unsigned int notifyId; - string subject; +shared_ptr MainDbPrivate::selectConferenceParticipantDeviceEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + unsigned int notifyId; + string participantAddress; + string deviceAddress; - soci::session *session = dbSession.getBackendSession(); - *session << "SELECT notify_id, subject" - " FROM conference_notified_event, conference_subject_event" - " WHERE conference_subject_event.event_id = :eventId" - " AND conference_notified_event.event_id = conference_subject_event.event_id", - soci::into(notifyId), soci::into(subject), soci::use(eventId); + soci::session *session = dbSession.getBackendSession(); + *session << "SELECT notify_id, participant_address.value, device_address.value" + " FROM conference_notified_event, conference_participant_event, conference_participant_device_event," + " sip_address AS participant_address, sip_address AS device_address" + " WHERE conference_participant_device_event.event_id = :eventId" + " AND conference_participant_event.event_id = conference_participant_device_event.event_id" + " AND conference_notified_event.event_id = conference_participant_event.event_id" + " AND participant_address.id = participant_sip_address_id" + " AND device_address.id = device_sip_address_id", + soci::into(notifyId), soci::into(participantAddress), soci::into(deviceAddress), soci::use(eventId); - return make_shared( - creationTime, - chatRoomId, - notifyId, - subject - ); - } + return make_shared( + type, + creationTime, + chatRoomId, + notifyId, + IdentityAddress(participantAddress), + IdentityAddress(deviceAddress) + ); +} + +shared_ptr MainDbPrivate::selectConferenceSubjectEvent ( + long long eventId, + EventLog::Type type, + time_t creationTime, + const ChatRoomId &chatRoomId +) const { + unsigned int notifyId; + string subject; + + soci::session *session = dbSession.getBackendSession(); + *session << "SELECT notify_id, subject" + " FROM conference_notified_event, conference_subject_event" + " WHERE conference_subject_event.event_id = :eventId" + " AND conference_notified_event.event_id = conference_subject_event.event_id", + soci::into(notifyId), soci::into(subject), soci::use(eventId); + + return make_shared( + creationTime, + chatRoomId, + notifyId, + subject + ); +} // ----------------------------------------------------------------------------- - long long MainDbPrivate::insertEvent (const shared_ptr &eventLog) { - L_Q(); +long long MainDbPrivate::insertEvent (const shared_ptr &eventLog) { + L_Q(); + soci::session *session = dbSession.getBackendSession(); + + const int &type = static_cast(eventLog->getType()); + const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime()); + *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", + soci::use(type), + soci::use(creationTime); + + return q->getLastInsertId(); +} + +long long MainDbPrivate::insertConferenceEvent (const shared_ptr &eventLog, long long *chatRoomId) { + shared_ptr conferenceEvent = static_pointer_cast(eventLog); + + long long eventId = -1; + const long long &curChatRoomId = selectChatRoomId(conferenceEvent->getChatRoomId()); + if (curChatRoomId < 0) { + // A conference event can be inserted in database only if chat room exists. + // Otherwise it's an error. + const ChatRoomId &chatRoomId = conferenceEvent->getChatRoomId(); + lError() << "Unable to find chat room storage id of (peer=" + + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + "`)."; + } else { + eventId = insertEvent(eventLog); + soci::session *session = dbSession.getBackendSession(); + *session << "INSERT INTO conference_event (event_id, chat_room_id)" + " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(curChatRoomId); - const int &type = static_cast(eventLog->getType()); - const tm &creationTime = Utils::getTimeTAsTm(eventLog->getCreationTime()); - *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", - soci::use(type), - soci::use(creationTime); + const tm &lastUpdateTime = Utils::getTimeTAsTm(eventLog->getCreationTime()); + *session << "UPDATE chat_room SET last_update_time = :lastUpdateTime" + " WHERE id = :chatRoomId", soci::use(lastUpdateTime), + soci::use(curChatRoomId); - return q->getLastInsertId(); + if (eventLog->getType() == EventLog::Type::ConferenceTerminated) + *session << "UPDATE chat_room SET flags = 1 WHERE id = :chatRoomId", soci::use(curChatRoomId); } - long long MainDbPrivate::insertConferenceEvent (const shared_ptr &eventLog, long long *chatRoomId) { - shared_ptr conferenceEvent = static_pointer_cast(eventLog); + if (chatRoomId) + *chatRoomId = curChatRoomId; - long long eventId = -1; - const long long &curChatRoomId = selectChatRoomId(conferenceEvent->getChatRoomId()); - if (curChatRoomId < 0) { - // A conference event can be inserted in database only if chat room exists. - // Otherwise it's an error. - const ChatRoomId &chatRoomId = conferenceEvent->getChatRoomId(); - lError() << "Unable to find chat room storage id of (peer=" + - chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + "`)."; - } else { - eventId = insertEvent(eventLog); + return eventId; +} - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_event (event_id, chat_room_id)" - " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(curChatRoomId); +long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr &eventLog) { + // TODO. + return 0; +} - const tm &lastUpdateTime = Utils::getTimeTAsTm(eventLog->getCreationTime()); - *session << "UPDATE chat_room SET last_update_time = :lastUpdateTime" - " WHERE id = :chatRoomId", soci::use(lastUpdateTime), - soci::use(curChatRoomId); +long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr &eventLog) { + shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); + shared_ptr chatRoom = chatMessage->getChatRoom(); + if (!chatRoom) { + lError() << "Unable to get a valid chat room. It was removed from database."; + return -1; + } - if (eventLog->getType() == EventLog::Type::ConferenceTerminated) - *session << "UPDATE chat_room SET flags = 1 WHERE id = :chatRoomId", soci::use(curChatRoomId); + const long long &eventId = insertConferenceEvent(eventLog); + if (eventId < 0) + return -1; + + soci::session *session = dbSession.getBackendSession(); + + const long long &fromSipAddressId = insertSipAddress(chatMessage->getFromAddress().asString()); + const long long &toSipAddressId = insertSipAddress(chatMessage->getToAddress().asString()); + const tm &messageTime = Utils::getTimeTAsTm(chatMessage->getTime()); + const int &state = static_cast(chatMessage->getState()); + const int &direction = static_cast(chatMessage->getDirection()); + const string &imdnMessageId = chatMessage->getImdnMessageId(); + const int &isSecured = chatMessage->isSecured() ? 1 : 0; + + *session << "INSERT INTO conference_chat_message_event (" + " event_id, from_sip_address_id, to_sip_address_id," + " time, state, direction, imdn_message_id, is_secured" + ") VALUES (" + " :eventId, :localSipaddressId, :remoteSipaddressId," + " :time, :state, :direction, :imdnMessageId, :isSecured" + ")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId), + soci::use(messageTime), soci::use(state), soci::use(direction), + soci::use(imdnMessageId), soci::use(isSecured); + + for (const Content *content : chatMessage->getContents()) + insertContent(eventId, *content); + + return eventId; +} + +void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr &eventLog) { + shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); + shared_ptr chatRoom = chatMessage->getChatRoom(); + if (!chatRoom) { + lError() << "Unable to get a valid chat room. It was removed from database."; + return; + } + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + const long long &eventId = dEventKey->storageId; + + soci::session *session = dbSession.getBackendSession(); + const int &state = static_cast(chatMessage->getState()); + const string &imdnMessageId = chatMessage->getImdnMessageId(); + *session << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = :imdnMessageId" + " WHERE event_id = :eventId", + soci::use(state), soci::use(imdnMessageId), soci::use(eventId); + + deleteContents(eventId); + for (const auto &content : chatMessage->getContents()) + insertContent(eventId, *content); +} + +long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr &eventLog, long long *chatRoomId) { + long long curChatRoomId; + const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId); + if (eventId < 0) + return -1; + + const unsigned int &lastNotifyId = static_pointer_cast(eventLog)->getNotifyId(); + + soci::session *session = dbSession.getBackendSession(); + *session << "INSERT INTO conference_notified_event (event_id, notify_id)" + " VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId); + *session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId", + soci::use(lastNotifyId), soci::use(curChatRoomId); + + if (chatRoomId) + *chatRoomId = curChatRoomId; + + return eventId; +} + +long long MainDbPrivate::insertConferenceParticipantEvent ( + const shared_ptr &eventLog, + long long *chatRoomId +) { + long long curChatRoomId; + const long long &eventId = insertConferenceNotifiedEvent(eventLog, &curChatRoomId); + if (eventId < 0) + return -1; + + shared_ptr participantEvent = + static_pointer_cast(eventLog); + + const long long &participantAddressId = insertSipAddress( + participantEvent->getParticipantAddress().asString() + ); + + soci::session *session = dbSession.getBackendSession(); + *session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)" + " VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId); + + bool isAdmin = eventLog->getType() == EventLog::Type::ConferenceParticipantSetAdmin; + switch (eventLog->getType()) { + case EventLog::Type::ConferenceParticipantAdded: + case EventLog::Type::ConferenceParticipantSetAdmin: + case EventLog::Type::ConferenceParticipantUnsetAdmin: + insertChatRoomParticipant(curChatRoomId, participantAddressId, isAdmin); + break; + + case EventLog::Type::ConferenceParticipantRemoved: + deleteChatRoomParticipant(curChatRoomId, participantAddressId); + break; + + default: + break; + } + + if (chatRoomId) + *chatRoomId = curChatRoomId; + + return eventId; +} + +long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr &eventLog) { + long long chatRoomId; + const long long &eventId = insertConferenceParticipantEvent(eventLog, &chatRoomId); + if (eventId < 0) + return -1; + + shared_ptr participantDeviceEvent = + static_pointer_cast(eventLog); + + const string participantAddress = participantDeviceEvent->getParticipantAddress().asString(); + const long long &participantAddressId = selectSipAddressId(participantAddress); + if (participantAddressId < 0) { + lError() << "Unable to find sip address id of: `" << participantAddress << "`."; + return -1; + } + const long long &participantId = selectChatRoomParticipantId(chatRoomId, participantAddressId); + if (participantId < 0) { + lError() << "Unable to find valid participant id in database with chat room id = " << chatRoomId << + " and participant address id = " << participantId; + return -1; + } + const long long &deviceAddressId = insertSipAddress( + participantDeviceEvent->getDeviceAddress().asString() + ); + + soci::session *session = dbSession.getBackendSession(); + *session << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)" + " VALUES (:eventId, :deviceAddressId)", soci::use(eventId), soci::use(deviceAddressId); + + switch (eventLog->getType()) { + case EventLog::Type::ConferenceParticipantDeviceAdded: + insertChatRoomParticipantDevice(participantId, deviceAddressId); + break; + + case EventLog::Type::ConferenceParticipantDeviceRemoved: + deleteChatRoomParticipantDevice(participantId, deviceAddressId); + break; + + default: + break; + } + + return eventId; +} + +long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr &eventLog) { + long long chatRoomId; + const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId); + if (eventId < 0) + return -1; + + const string &subject = static_pointer_cast(eventLog)->getSubject(); + + soci::session *session = dbSession.getBackendSession(); + *session << "INSERT INTO conference_subject_event (event_id, subject)" + " VALUES (:eventId, :subject)", soci::use(eventId), soci::use(subject); + + *session << "UPDATE chat_room SET subject = :subject" + " WHERE id = :chatRoomId", soci::use(subject), soci::use(chatRoomId); + + return eventId; +} + +// ----------------------------------------------------------------------------- + +shared_ptr MainDbPrivate::getEventFromCache (long long storageId) const { + auto it = storageIdToEvent.find(storageId); + if (it == storageIdToEvent.cend()) + return nullptr; + + shared_ptr eventLog = it->second.lock(); + // Must exist. If not, implementation bug. + L_ASSERT(eventLog); + return eventLog; +} + +shared_ptr MainDbPrivate::getChatMessageFromCache (long long storageId) const { + auto it = storageIdToChatMessage.find(storageId); + if (it == storageIdToChatMessage.cend()) + return nullptr; + + shared_ptr chatMessage = it->second.lock(); + // Must exist. If not, implementation bug. + L_ASSERT(chatMessage); + return chatMessage; +} + +void MainDbPrivate::cache (const shared_ptr &eventLog, long long storageId) const { + L_Q(); + + EventLogPrivate *dEventLog = eventLog->getPrivate(); + L_ASSERT(!dEventLog->dbKey.isValid()); + dEventLog->dbKey = MainDbEventKey(q->getCore(), storageId); + storageIdToEvent[storageId] = eventLog; + L_ASSERT(dEventLog->dbKey.isValid()); +} + +void MainDbPrivate::cache (const shared_ptr &chatMessage, long long storageId) const { + L_Q(); + + ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); + L_ASSERT(!dChatMessage->dbKey.isValid()); + dChatMessage->dbKey = MainDbChatMessageKey(q->getCore(), storageId); + storageIdToChatMessage[storageId] = chatMessage; + L_ASSERT(dChatMessage->dbKey.isValid()); +} + +void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) { + soci::session *session = dbSession.getBackendSession(); + soci::rowset rows = (session->prepare << query, soci::use(chatRoomId)); + for (const auto &row : rows) { + long long eventId = resolveId(row, 0); + shared_ptr eventLog = getEventFromCache(eventId); + if (eventLog) { + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + L_ASSERT(dEventLog->dbKey.isValid()); + dEventLog->dbKey = MainDbEventKey(); } + // TODO: Try to add a better code here... + shared_ptr chatMessage = getChatMessageFromCache(eventId); + if (chatMessage) { + const ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); + L_ASSERT(dChatMessage->dbKey.isValid()); + dChatMessage->dbKey = MainDbChatMessageKey(); + } + } +} - if (chatRoomId) - *chatRoomId = curChatRoomId; +// ----------------------------------------------------------------------------- - return eventId; +unsigned int MainDbPrivate::getModuleVersion (const string &name) { + soci::session *session = dbSession.getBackendSession(); + + unsigned int version; + *session << "SELECT version FROM db_module_version WHERE name = :name", soci::into(version), soci::use(name); + return session->got_data() ? version : 0; +} + +void MainDbPrivate::updateModuleVersion (const string &name, unsigned int version) { + unsigned int oldVersion = getModuleVersion(name); + if (oldVersion == version) + return; + + soci::session *session = dbSession.getBackendSession(); + *session << "REPLACE INTO db_module_version (name, version) VALUES (:name, :version)", + soci::use(name), soci::use(version); +} + +// ----------------------------------------------------------------------------- + +void MainDb::init () { + L_D(); + + const string charset = getBackend() == Mysql ? "DEFAULT CHARSET=utf8" : ""; + soci::session *session = d->dbSession.getBackendSession(); + + *session << + "CREATE TABLE IF NOT EXISTS sip_address (" + " id" + primaryKeyStr("BIGINT UNSIGNED") + "," + " value VARCHAR(255) UNIQUE NOT NULL" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS content_type (" + " id" + primaryKeyStr("SMALLINT UNSIGNED") + "," + " value VARCHAR(255) UNIQUE NOT NULL" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS event (" + " id" + primaryKeyStr("BIGINT UNSIGNED") + "," + " type TINYINT UNSIGNED NOT NULL," + " creation_time" + timestampType() + " NOT NULL" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_room (" + " id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + // Server (for conference) or user sip address. + " peer_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + " local_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + // Dialog creation time. + " creation_time" + timestampType() + " NOT NULL," + + // Last event time (call, message...). + " last_update_time" + timestampType() + " NOT NULL," + + // ConferenceChatRoom, BasicChatRoom, RTT... + " capabilities TINYINT UNSIGNED NOT NULL," + + // Chatroom subject. + " subject VARCHAR(255)," + + " last_notify_id INT UNSIGNED DEFAULT 0," + + " flags INT UNSIGNED DEFAULT 0," + + " UNIQUE (peer_sip_address_id, local_sip_address_id)," + + " FOREIGN KEY (peer_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE," + " FOREIGN KEY (local_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_room_participant (" + " id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " chat_room_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + + " is_admin BOOLEAN NOT NULL," + + " UNIQUE (chat_room_id, participant_sip_address_id)," + + " FOREIGN KEY (chat_room_id)" + " REFERENCES chat_room(id)" + " ON DELETE CASCADE," + " FOREIGN KEY (participant_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_room_participant_device (" + " chat_room_participant_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + " participant_device_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + + " PRIMARY KEY (chat_room_participant_id, participant_device_sip_address_id)," + + " FOREIGN KEY (chat_room_participant_id)" + " REFERENCES chat_room_participant(id)" + " ON DELETE CASCADE," + " FOREIGN KEY (participant_device_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " chat_room_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES event(id)" + " ON DELETE CASCADE," + " FOREIGN KEY (chat_room_id)" + " REFERENCES chat_room(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_notified_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " notify_id INT UNSIGNED NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_event(event_id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_participant_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_notified_event(event_id)" + " ON DELETE CASCADE," + " FOREIGN KEY (participant_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_participant_device_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " device_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_participant_event(event_id)" + " ON DELETE CASCADE," + " FOREIGN KEY (device_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_subject_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " subject VARCHAR(255) NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_notified_event(event_id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_chat_message_event (" + " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " from_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + " to_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + + " time" + timestampType() + " ," + + // See: https://tools.ietf.org/html/rfc5438#section-6.3 + " imdn_message_id VARCHAR(255) NOT NULL," + + " state TINYINT UNSIGNED NOT NULL," + " direction TINYINT UNSIGNED NOT NULL," + " is_secured BOOLEAN NOT NULL," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_event(event_id)" + " ON DELETE CASCADE," + " FOREIGN KEY (from_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE," + " FOREIGN KEY (to_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_message_participant (" + " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + " state TINYINT UNSIGNED NOT NULL," + + " PRIMARY KEY (event_id, participant_sip_address_id)," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_chat_message_event(event_id)" + " ON DELETE CASCADE," + " FOREIGN KEY (participant_sip_address_id)" + " REFERENCES sip_address(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_message_content (" + " id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," + " content_type_id" + primaryKeyRefStr("SMALLINT UNSIGNED") + " NOT NULL," + " body TEXT NOT NULL," + + " UNIQUE (id, event_id)," + + " FOREIGN KEY (event_id)" + " REFERENCES conference_chat_message_event(event_id)" + " ON DELETE CASCADE," + " FOREIGN KEY (content_type_id)" + " REFERENCES content_type(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_message_file_content (" + " chat_message_content_id" + primaryKeyStr("BIGINT UNSIGNED") + "," + + " name VARCHAR(256) NOT NULL," + " size INT UNSIGNED NOT NULL," + " path VARCHAR(512) NOT NULL," + + " FOREIGN KEY (chat_message_content_id)" + " REFERENCES chat_message_content(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS chat_message_content_app_data (" + " chat_message_content_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + + " name VARCHAR(255)," + " data BLOB NOT NULL," + + " PRIMARY KEY (chat_message_content_id, name)," + " FOREIGN KEY (chat_message_content_id)" + " REFERENCES chat_message_content(id)" + " ON DELETE CASCADE" + ") " + charset; + + *session << + "CREATE TABLE IF NOT EXISTS conference_message_crypto_data (" + " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," + + " name VARCHAR(255)," + " data BLOB NOT NULL," + + " PRIMARY KEY (event_id, name)," + " FOREIGN KEY (event_id)" + " REFERENCES conference_chat_message_event(event_id)" + " ON DELETE CASCADE" + ") " + charset; + + // Trigger to delete participant_message cache entries. + // TODO: Fix me in the future. (Problem on Mysql backend.) + #if 0 + string displayedId = Utils::toString(static_cast(ChatMessage::State::Displayed)); + string participantMessageDeleter = + "CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter" + " AFTER UPDATE OF state ON chat_message_participant FOR EACH ROW" + " WHEN NEW.state = "; + participantMessageDeleter += displayedId; + participantMessageDeleter += " AND (SELECT COUNT(*) FROM (" + " SELECT state FROM chat_message_participant WHERE" + " NEW.event_id = chat_message_participant.event_id" + " AND state <> "; + participantMessageDeleter += displayedId; + participantMessageDeleter += " LIMIT 1" + " )) = 0" + " BEGIN" + " DELETE FROM chat_message_participant WHERE NEW.event_id = chat_message_participant.event_id;" + " UPDATE conference_chat_message_event SET state = "; + participantMessageDeleter += displayedId; + participantMessageDeleter += " WHERE event_id = NEW.event_id;" + " END"; + + *session << participantMessageDeleter; + #endif + + *session << + "CREATE TABLE IF NOT EXISTS db_module_version (" + " name" + varcharPrimaryKeyStr(255) + "," + " version INT UNSIGNED NOT NULL" + ") " + charset; + + d->updateModuleVersion("events", DB_MODULE_VERSION_EVENTS); +} + +bool MainDb::addEvent (const shared_ptr &eventLog) { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to add event. Not connected."; + return false; } - long long MainDbPrivate::insertConferenceCallEvent (const shared_ptr &eventLog) { - // TODO. + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + if (dEventLog->dbKey.isValid()) { + lWarning() << "Unable to add an event twice!!!"; + return false; + } + + bool soFarSoGood = false; + long long storageId = 0; + + L_BEGIN_LOG_EXCEPTION + + soci::transaction tr(*d->dbSession.getBackendSession()); + + EventLog::Type type = eventLog->getType(); + switch (type) { + case EventLog::Type::None: + return false; + + case EventLog::Type::ConferenceCreated: + case EventLog::Type::ConferenceTerminated: + storageId = d->insertConferenceEvent(eventLog); + break; + + case EventLog::Type::ConferenceCallStart: + case EventLog::Type::ConferenceCallEnd: + storageId = d->insertConferenceCallEvent(eventLog); + break; + + case EventLog::Type::ConferenceChatMessage: + storageId = d->insertConferenceChatMessageEvent(eventLog); + break; + + case EventLog::Type::ConferenceParticipantAdded: + case EventLog::Type::ConferenceParticipantRemoved: + case EventLog::Type::ConferenceParticipantSetAdmin: + case EventLog::Type::ConferenceParticipantUnsetAdmin: + storageId = d->insertConferenceParticipantEvent(eventLog); + break; + + case EventLog::Type::ConferenceParticipantDeviceAdded: + case EventLog::Type::ConferenceParticipantDeviceRemoved: + storageId = d->insertConferenceParticipantDeviceEvent(eventLog); + break; + + case EventLog::Type::ConferenceSubjectChanged: + storageId = d->insertConferenceSubjectEvent(eventLog); + break; + } + + if (storageId >= 0) { + tr.commit(); + d->cache(eventLog, storageId); + + if (type == EventLog::Type::ConferenceChatMessage) + d->cache(static_pointer_cast(eventLog)->getChatMessage(), storageId); + + soFarSoGood = true; + } + + L_END_LOG_EXCEPTION + + return soFarSoGood; +} + +bool MainDb::updateEvent (const shared_ptr &eventLog) { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to update event. Not connected."; + return false; + } + + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + if (!dEventLog->dbKey.isValid()) { + lWarning() << "Unable to update an event that wasn't inserted yet!!!"; + return false; + } + + L_BEGIN_LOG_EXCEPTION + + soci::transaction tr(*d->dbSession.getBackendSession()); + + switch (eventLog->getType()) { + case EventLog::Type::None: + return false; + + case EventLog::Type::ConferenceChatMessage: + d->updateConferenceChatMessageEvent(eventLog); + break; + + case EventLog::Type::ConferenceCreated: + case EventLog::Type::ConferenceTerminated: + case EventLog::Type::ConferenceCallStart: + case EventLog::Type::ConferenceCallEnd: + case EventLog::Type::ConferenceParticipantAdded: + case EventLog::Type::ConferenceParticipantRemoved: + case EventLog::Type::ConferenceParticipantSetAdmin: + case EventLog::Type::ConferenceParticipantUnsetAdmin: + case EventLog::Type::ConferenceParticipantDeviceAdded: + case EventLog::Type::ConferenceParticipantDeviceRemoved: + case EventLog::Type::ConferenceSubjectChanged: + return false; + } + + tr.commit(); + + return true; + + L_END_LOG_EXCEPTION + + // Error. + return false; +} + +bool MainDb::deleteEvent (const shared_ptr &eventLog) { + const EventLogPrivate *dEventLog = eventLog->getPrivate(); + if (!dEventLog->dbKey.isValid()) { + lWarning() << "Unable to delete invalid event."; + return false; + } + + MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); + shared_ptr core = dEventKey->core.lock(); + L_ASSERT(core); + + MainDb &mainDb = *core->getPrivate()->mainDb.get(); + if (!mainDb.isConnected()) { + lWarning() << "Unable to delete event. Not connected."; + return false; + } + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = mainDb.getPrivate()->dbSession.getBackendSession(); + *session << "DELETE FROM event WHERE id = :id", soci::use(dEventKey->storageId); + + dEventLog->dbKey = MainDbEventKey(); + + if (eventLog->getType() == EventLog::Type::ConferenceChatMessage) + static_pointer_cast( + eventLog + )->getChatMessage()->getPrivate()->dbKey = MainDbChatMessageKey(); + + return true; + + L_END_LOG_EXCEPTION + + // Error. + return false; +} + +int MainDb::getEventCount (FilterMask mask) const { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to get events count. Not connected."; return 0; } - long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptr &eventLog) { - shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); - shared_ptr chatRoom = chatMessage->getChatRoom(); - if (!chatRoom) { - lError() << "Unable to get a valid chat room. It was removed from database."; - return -1; - } + string query = "SELECT COUNT(*) FROM event" + + buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter }, mask); + int count = 0; - const long long &eventId = insertConferenceEvent(eventLog); - if (eventId < 0) - return -1; + DurationLogger durationLogger( + "Get events count with mask=" + Utils::toString(mask) + "." + ); - soci::session *session = dbSession.getBackendSession(); + L_BEGIN_LOG_EXCEPTION - const long long &fromSipAddressId = insertSipAddress(chatMessage->getFromAddress().asString()); - const long long &toSipAddressId = insertSipAddress(chatMessage->getToAddress().asString()); - const tm &messageTime = Utils::getTimeTAsTm(chatMessage->getTime()); - const int &state = static_cast(chatMessage->getState()); - const int &direction = static_cast(chatMessage->getDirection()); - const string &imdnMessageId = chatMessage->getImdnMessageId(); - const int &isSecured = chatMessage->isSecured() ? 1 : 0; + soci::session *session = d->dbSession.getBackendSession(); + *session << query, soci::into(count); - *session << "INSERT INTO conference_chat_message_event (" - " event_id, from_sip_address_id, to_sip_address_id," - " time, state, direction, imdn_message_id, is_secured" - ") VALUES (" - " :eventId, :localSipaddressId, :remoteSipaddressId," - " :time, :state, :direction, :imdnMessageId, :isSecured" - ")", soci::use(eventId), soci::use(fromSipAddressId), soci::use(toSipAddressId), - soci::use(messageTime), soci::use(state), soci::use(direction), - soci::use(imdnMessageId), soci::use(isSecured); + L_END_LOG_EXCEPTION - for (const Content *content : chatMessage->getContents()) - insertContent(eventId, *content); + return count; +} - return eventId; - } - - void MainDbPrivate::updateConferenceChatMessageEvent (const shared_ptr &eventLog) { - shared_ptr chatMessage = static_pointer_cast(eventLog)->getChatMessage(); - shared_ptr chatRoom = chatMessage->getChatRoom(); - if (!chatRoom) { - lError() << "Unable to get a valid chat room. It was removed from database."; - return; - } - - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); - const long long &eventId = dEventKey->storageId; - - soci::session *session = dbSession.getBackendSession(); - const int &state = static_cast(chatMessage->getState()); - const string &imdnMessageId = chatMessage->getImdnMessageId(); - *session << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = :imdnMessageId" - " WHERE event_id = :eventId", - soci::use(state), soci::use(imdnMessageId), soci::use(eventId); - - deleteContents(eventId); - for (const auto &content : chatMessage->getContents()) - insertContent(eventId, *content); - } - - long long MainDbPrivate::insertConferenceNotifiedEvent (const shared_ptr &eventLog, long long *chatRoomId) { - long long curChatRoomId; - const long long &eventId = insertConferenceEvent(eventLog, &curChatRoomId); - if (eventId < 0) - return -1; - - const unsigned int &lastNotifyId = static_pointer_cast(eventLog)->getNotifyId(); - - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_notified_event (event_id, notify_id)" - " VALUES (:eventId, :notifyId)", soci::use(eventId), soci::use(lastNotifyId); - *session << "UPDATE chat_room SET last_notify_id = :lastNotifyId WHERE id = :chatRoomId", - soci::use(lastNotifyId), soci::use(curChatRoomId); - - if (chatRoomId) - *chatRoomId = curChatRoomId; - - return eventId; - } - - long long MainDbPrivate::insertConferenceParticipantEvent ( - const shared_ptr &eventLog, - long long *chatRoomId - ) { - long long curChatRoomId; - const long long &eventId = insertConferenceNotifiedEvent(eventLog, &curChatRoomId); - if (eventId < 0) - return -1; - - shared_ptr participantEvent = - static_pointer_cast(eventLog); - - const long long &participantAddressId = insertSipAddress( - participantEvent->getParticipantAddress().asString() - ); - - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_participant_event (event_id, participant_sip_address_id)" - " VALUES (:eventId, :participantAddressId)", soci::use(eventId), soci::use(participantAddressId); - - bool isAdmin = eventLog->getType() == EventLog::Type::ConferenceParticipantSetAdmin; - switch (eventLog->getType()) { - case EventLog::Type::ConferenceParticipantAdded: - case EventLog::Type::ConferenceParticipantSetAdmin: - case EventLog::Type::ConferenceParticipantUnsetAdmin: - insertChatRoomParticipant(curChatRoomId, participantAddressId, isAdmin); - break; - - case EventLog::Type::ConferenceParticipantRemoved: - deleteChatRoomParticipant(curChatRoomId, participantAddressId); - break; - - default: - break; - } - - if (chatRoomId) - *chatRoomId = curChatRoomId; - - return eventId; - } - - long long MainDbPrivate::insertConferenceParticipantDeviceEvent (const shared_ptr &eventLog) { - long long chatRoomId; - const long long &eventId = insertConferenceParticipantEvent(eventLog, &chatRoomId); - if (eventId < 0) - return -1; - - shared_ptr participantDeviceEvent = - static_pointer_cast(eventLog); - - const string participantAddress = participantDeviceEvent->getParticipantAddress().asString(); - const long long &participantAddressId = selectSipAddressId(participantAddress); - if (participantAddressId < 0) { - lError() << "Unable to find sip address id of: `" << participantAddress << "`."; - return -1; - } - const long long &participantId = selectChatRoomParticipantId(chatRoomId, participantAddressId); - if (participantId < 0) { - lError() << "Unable to find valid participant id in database with chat room id = " << chatRoomId << - " and participant address id = " << participantId; - return -1; - } - const long long &deviceAddressId = insertSipAddress( - participantDeviceEvent->getDeviceAddress().asString() - ); - - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_participant_device_event (event_id, device_sip_address_id)" - " VALUES (:eventId, :deviceAddressId)", soci::use(eventId), soci::use(deviceAddressId); - - switch (eventLog->getType()) { - case EventLog::Type::ConferenceParticipantDeviceAdded: - insertChatRoomParticipantDevice(participantId, deviceAddressId); - break; - - case EventLog::Type::ConferenceParticipantDeviceRemoved: - deleteChatRoomParticipantDevice(participantId, deviceAddressId); - break; - - default: - break; - } - - return eventId; - } - - long long MainDbPrivate::insertConferenceSubjectEvent (const shared_ptr &eventLog) { - long long chatRoomId; - const long long &eventId = insertConferenceNotifiedEvent(eventLog, &chatRoomId); - if (eventId < 0) - return -1; - - const string &subject = static_pointer_cast(eventLog)->getSubject(); - - soci::session *session = dbSession.getBackendSession(); - *session << "INSERT INTO conference_subject_event (event_id, subject)" - " VALUES (:eventId, :subject)", soci::use(eventId), soci::use(subject); - - *session << "UPDATE chat_room SET subject = :subject" - " WHERE id = :chatRoomId", soci::use(subject), soci::use(chatRoomId); - - return eventId; - } - -// ----------------------------------------------------------------------------- - - shared_ptr MainDbPrivate::getEventFromCache (long long storageId) const { - auto it = storageIdToEvent.find(storageId); - if (it == storageIdToEvent.cend()) - return nullptr; - - shared_ptr eventLog = it->second.lock(); - // Must exist. If not, implementation bug. - L_ASSERT(eventLog); - return eventLog; - } - - shared_ptr MainDbPrivate::getChatMessageFromCache (long long storageId) const { - auto it = storageIdToChatMessage.find(storageId); - if (it == storageIdToChatMessage.cend()) - return nullptr; - - shared_ptr chatMessage = it->second.lock(); - // Must exist. If not, implementation bug. - L_ASSERT(chatMessage); - return chatMessage; - } - - void MainDbPrivate::cache (const shared_ptr &eventLog, long long storageId) const { - L_Q(); - - EventLogPrivate *dEventLog = eventLog->getPrivate(); - L_ASSERT(!dEventLog->dbKey.isValid()); - dEventLog->dbKey = MainDbEventKey(q->getCore(), storageId); - storageIdToEvent[storageId] = eventLog; - L_ASSERT(dEventLog->dbKey.isValid()); - } - - void MainDbPrivate::cache (const shared_ptr &chatMessage, long long storageId) const { - L_Q(); - - ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); - L_ASSERT(!dChatMessage->dbKey.isValid()); - dChatMessage->dbKey = MainDbChatMessageKey(q->getCore(), storageId); - storageIdToChatMessage[storageId] = chatMessage; - L_ASSERT(dChatMessage->dbKey.isValid()); - } - - void MainDbPrivate::invalidConferenceEventsFromQuery (const string &query, long long chatRoomId) { - soci::session *session = dbSession.getBackendSession(); - soci::rowset rows = (session->prepare << query, soci::use(chatRoomId)); - for (const auto &row : rows) { - long long eventId = resolveId(row, 0); - shared_ptr eventLog = getEventFromCache(eventId); - if (eventLog) { - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - L_ASSERT(dEventLog->dbKey.isValid()); - dEventLog->dbKey = MainDbEventKey(); - } - // TODO: Try to add a better code here... - shared_ptr chatMessage = getChatMessageFromCache(eventId); - if (chatMessage) { - const ChatMessagePrivate *dChatMessage = chatMessage->getPrivate(); - L_ASSERT(dChatMessage->dbKey.isValid()); - dChatMessage->dbKey = MainDbChatMessageKey(); - } - } - } - -// ----------------------------------------------------------------------------- - - unsigned int MainDbPrivate::getModuleVersion (const string &name) { - soci::session *session = dbSession.getBackendSession(); - - unsigned int version; - *session << "SELECT version FROM db_module_version WHERE name = :name", soci::into(version), soci::use(name); - return session->got_data() ? version : 0; - } - - void MainDbPrivate::updateModuleVersion (const string &name, unsigned int version) { - unsigned int oldVersion = getModuleVersion(name); - if (oldVersion == version) - return; - - soci::session *session = dbSession.getBackendSession(); - *session << "REPLACE INTO db_module_version (name, version) VALUES (:name, :version)", - soci::use(name), soci::use(version); - } - -// ----------------------------------------------------------------------------- - - void MainDb::init () { - L_D(); - - const string charset = getBackend() == Mysql ? "DEFAULT CHARSET=utf8" : ""; - soci::session *session = d->dbSession.getBackendSession(); - - *session << - "CREATE TABLE IF NOT EXISTS sip_address (" - " id" + primaryKeyStr("BIGINT UNSIGNED") + "," - " value VARCHAR(255) UNIQUE NOT NULL" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS content_type (" - " id" + primaryKeyStr("SMALLINT UNSIGNED") + "," - " value VARCHAR(255) UNIQUE NOT NULL" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS event (" - " id" + primaryKeyStr("BIGINT UNSIGNED") + "," - " type TINYINT UNSIGNED NOT NULL," - " creation_time" + timestampType() + " NOT NULL" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_room (" - " id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - // Server (for conference) or user sip address. - " peer_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - " local_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - // Dialog creation time. - " creation_time" + timestampType() + " NOT NULL," - - // Last event time (call, message...). - " last_update_time" + timestampType() + " NOT NULL," - - // ConferenceChatRoom, BasicChatRoom, RTT... - " capabilities TINYINT UNSIGNED NOT NULL," - - // Chatroom subject. - " subject VARCHAR(255)," - - " last_notify_id INT UNSIGNED DEFAULT 0," - - " flags INT UNSIGNED DEFAULT 0," - - " UNIQUE (peer_sip_address_id, local_sip_address_id)," - - " FOREIGN KEY (peer_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE," - " FOREIGN KEY (local_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_room_participant (" - " id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " chat_room_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - - " is_admin BOOLEAN NOT NULL," - - " UNIQUE (chat_room_id, participant_sip_address_id)," - - " FOREIGN KEY (chat_room_id)" - " REFERENCES chat_room(id)" - " ON DELETE CASCADE," - " FOREIGN KEY (participant_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_room_participant_device (" - " chat_room_participant_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - " participant_device_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - - " PRIMARY KEY (chat_room_participant_id, participant_device_sip_address_id)," - - " FOREIGN KEY (chat_room_participant_id)" - " REFERENCES chat_room_participant(id)" - " ON DELETE CASCADE," - " FOREIGN KEY (participant_device_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " chat_room_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES event(id)" - " ON DELETE CASCADE," - " FOREIGN KEY (chat_room_id)" - " REFERENCES chat_room(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_notified_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " notify_id INT UNSIGNED NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_event(event_id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_participant_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_notified_event(event_id)" - " ON DELETE CASCADE," - " FOREIGN KEY (participant_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_participant_device_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " device_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_participant_event(event_id)" - " ON DELETE CASCADE," - " FOREIGN KEY (device_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_subject_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " subject VARCHAR(255) NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_notified_event(event_id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_chat_message_event (" - " event_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " from_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - " to_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - - " time" + timestampType() + " ," - - // See: https://tools.ietf.org/html/rfc5438#section-6.3 - " imdn_message_id VARCHAR(255) NOT NULL," - - " state TINYINT UNSIGNED NOT NULL," - " direction TINYINT UNSIGNED NOT NULL," - " is_secured BOOLEAN NOT NULL," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_event(event_id)" - " ON DELETE CASCADE," - " FOREIGN KEY (from_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE," - " FOREIGN KEY (to_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_message_participant (" - " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - " participant_sip_address_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - " state TINYINT UNSIGNED NOT NULL," - - " PRIMARY KEY (event_id, participant_sip_address_id)," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_chat_message_event(event_id)" - " ON DELETE CASCADE," - " FOREIGN KEY (participant_sip_address_id)" - " REFERENCES sip_address(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_message_content (" - " id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + " NOT NULL," - " content_type_id" + primaryKeyRefStr("SMALLINT UNSIGNED") + " NOT NULL," - " body TEXT NOT NULL," - - " UNIQUE (id, event_id)," - - " FOREIGN KEY (event_id)" - " REFERENCES conference_chat_message_event(event_id)" - " ON DELETE CASCADE," - " FOREIGN KEY (content_type_id)" - " REFERENCES content_type(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_message_file_content (" - " chat_message_content_id" + primaryKeyStr("BIGINT UNSIGNED") + "," - - " name VARCHAR(256) NOT NULL," - " size INT UNSIGNED NOT NULL," - " path VARCHAR(512) NOT NULL," - - " FOREIGN KEY (chat_message_content_id)" - " REFERENCES chat_message_content(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS chat_message_content_app_data (" - " chat_message_content_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - - " name VARCHAR(255)," - " data BLOB NOT NULL," - - " PRIMARY KEY (chat_message_content_id, name)," - " FOREIGN KEY (chat_message_content_id)" - " REFERENCES chat_message_content(id)" - " ON DELETE CASCADE" - ") " + charset; - - *session << - "CREATE TABLE IF NOT EXISTS conference_message_crypto_data (" - " event_id" + primaryKeyRefStr("BIGINT UNSIGNED") + "," - - " name VARCHAR(255)," - " data BLOB NOT NULL," - - " PRIMARY KEY (event_id, name)," - " FOREIGN KEY (event_id)" - " REFERENCES conference_chat_message_event(event_id)" - " ON DELETE CASCADE" - ") " + charset; - - // Trigger to delete participant_message cache entries. - // TODO: Fix me in the future. (Problem on Mysql backend.) - #if 0 - string displayedId = Utils::toString(static_cast(ChatMessage::State::Displayed)); - string participantMessageDeleter = - "CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter" - " AFTER UPDATE OF state ON chat_message_participant FOR EACH ROW" - " WHEN NEW.state = "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " AND (SELECT COUNT(*) FROM (" - " SELECT state FROM chat_message_participant WHERE" - " NEW.event_id = chat_message_participant.event_id" - " AND state <> "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " LIMIT 1" - " )) = 0" - " BEGIN" - " DELETE FROM chat_message_participant WHERE NEW.event_id = chat_message_participant.event_id;" - " UPDATE conference_chat_message_event SET state = "; - participantMessageDeleter += displayedId; - participantMessageDeleter += " WHERE event_id = NEW.event_id;" - " END"; - - *session << participantMessageDeleter; - #endif - - *session << - "CREATE TABLE IF NOT EXISTS db_module_version (" - " name" + varcharPrimaryKeyStr(255) + "," - " version INT UNSIGNED NOT NULL" - ") " + charset; - - d->updateModuleVersion("events", DB_MODULE_VERSION_EVENTS); - } - - bool MainDb::addEvent (const shared_ptr &eventLog) { - L_D(); - - if (!isConnected()) { - lWarning() << "Unable to add event. Not connected."; - return false; - } - - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - if (dEventLog->dbKey.isValid()) { - lWarning() << "Unable to add an event twice!!!"; - return false; - } - - bool soFarSoGood = false; - long long storageId = 0; - - L_BEGIN_LOG_EXCEPTION - - soci::transaction tr(*d->dbSession.getBackendSession()); - - EventLog::Type type = eventLog->getType(); - switch (type) { - case EventLog::Type::None: - return false; - - case EventLog::Type::ConferenceCreated: - case EventLog::Type::ConferenceTerminated: - storageId = d->insertConferenceEvent(eventLog); - break; - - case EventLog::Type::ConferenceCallStart: - case EventLog::Type::ConferenceCallEnd: - storageId = d->insertConferenceCallEvent(eventLog); - break; - - case EventLog::Type::ConferenceChatMessage: - storageId = d->insertConferenceChatMessageEvent(eventLog); - break; - - case EventLog::Type::ConferenceParticipantAdded: - case EventLog::Type::ConferenceParticipantRemoved: - case EventLog::Type::ConferenceParticipantSetAdmin: - case EventLog::Type::ConferenceParticipantUnsetAdmin: - storageId = d->insertConferenceParticipantEvent(eventLog); - break; - - case EventLog::Type::ConferenceParticipantDeviceAdded: - case EventLog::Type::ConferenceParticipantDeviceRemoved: - storageId = d->insertConferenceParticipantDeviceEvent(eventLog); - break; - - case EventLog::Type::ConferenceSubjectChanged: - storageId = d->insertConferenceSubjectEvent(eventLog); - break; - } - - if (storageId >= 0) { - tr.commit(); - d->cache(eventLog, storageId); - - if (type == EventLog::Type::ConferenceChatMessage) - d->cache(static_pointer_cast(eventLog)->getChatMessage(), storageId); - - soFarSoGood = true; - } - - L_END_LOG_EXCEPTION - - return soFarSoGood; - } - - bool MainDb::updateEvent (const shared_ptr &eventLog) { - L_D(); - - if (!isConnected()) { - lWarning() << "Unable to update event. Not connected."; - return false; - } - - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - if (!dEventLog->dbKey.isValid()) { - lWarning() << "Unable to update an event that wasn't inserted yet!!!"; - return false; - } - - L_BEGIN_LOG_EXCEPTION - - soci::transaction tr(*d->dbSession.getBackendSession()); - - switch (eventLog->getType()) { - case EventLog::Type::None: - return false; - - case EventLog::Type::ConferenceChatMessage: - d->updateConferenceChatMessageEvent(eventLog); - break; - - case EventLog::Type::ConferenceCreated: - case EventLog::Type::ConferenceTerminated: - case EventLog::Type::ConferenceCallStart: - case EventLog::Type::ConferenceCallEnd: - case EventLog::Type::ConferenceParticipantAdded: - case EventLog::Type::ConferenceParticipantRemoved: - case EventLog::Type::ConferenceParticipantSetAdmin: - case EventLog::Type::ConferenceParticipantUnsetAdmin: - case EventLog::Type::ConferenceParticipantDeviceAdded: - case EventLog::Type::ConferenceParticipantDeviceRemoved: - case EventLog::Type::ConferenceSubjectChanged: - return false; - } - - tr.commit(); - - return true; - - L_END_LOG_EXCEPTION - - // Error. - return false; - } - - bool MainDb::deleteEvent (const shared_ptr &eventLog) { - const EventLogPrivate *dEventLog = eventLog->getPrivate(); - if (!dEventLog->dbKey.isValid()) { - lWarning() << "Unable to delete invalid event."; - return false; - } - - MainDbKeyPrivate *dEventKey = static_cast(dEventLog->dbKey).getPrivate(); - shared_ptr core = dEventKey->core.lock(); - L_ASSERT(core); - - MainDb &mainDb = *core->getPrivate()->mainDb.get(); - if (!mainDb.isConnected()) { - lWarning() << "Unable to delete event. Not connected."; - return false; - } - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = mainDb.getPrivate()->dbSession.getBackendSession(); - *session << "DELETE FROM event WHERE id = :id", soci::use(dEventKey->storageId); - - dEventLog->dbKey = MainDbEventKey(); - - if (eventLog->getType() == EventLog::Type::ConferenceChatMessage) - static_pointer_cast( - eventLog - )->getChatMessage()->getPrivate()->dbKey = MainDbChatMessageKey(); - - return true; - - L_END_LOG_EXCEPTION - - // Error. - return false; - } - - int MainDb::getEventCount (FilterMask mask) const { - L_D(); - - if (!isConnected()) { - lWarning() << "Unable to get events count. Not connected."; - return 0; - } - - string query = "SELECT COUNT(*) FROM event" + - buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter }, mask); - int count = 0; - - DurationLogger durationLogger( - "Get events count with mask=" + Utils::toString(mask) + "." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - *session << query, soci::into(count); - - L_END_LOG_EXCEPTION - - return count; - } - - shared_ptr MainDb::getEventFromKey (const MainDbKey &dbKey) { - shared_ptr event; - - if (!dbKey.isValid()) { - lWarning() << "Unable to get event from invalid key."; - return event; - } - - unique_ptr &q = dbKey.getPrivate()->core.lock()->getPrivate()->mainDb; - MainDbPrivate *d = q->getPrivate(); - - if (!q->isConnected()) { - lWarning() << "Unable to get event from key. Not connected."; - return event; - } - - const long long &storageId = dbKey.getPrivate()->storageId; - event = d->getEventFromCache(storageId); - if (event) - return event; - - // TODO: Improve. Deal with all events in the future. - static const string query = "SELECT peer_sip_address.value, local_sip_address.value, type, event.creation_time" - " FROM event, conference_event, chat_room, sip_address AS peer_sip_address, sip_address as local_sip_address" - " WHERE event.id = :eventId" - " AND conference_event.event_id = event.id" - " AND conference_event.chat_room_id = chat_room.id" - " AND chat_room.peer_sip_address_id = peer_sip_address.id" - " AND chat_room.local_sip_address_id = local_sip_address.id"; - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - string peerSipAddress; - string localSipAddress; - int type; - tm creationTime; - *session << query, soci::into(peerSipAddress), soci::into(localSipAddress), soci::into(type), - soci::into(creationTime), soci::use(storageId); - - event = d->selectGenericConferenceEvent( - storageId, - static_cast(type), - Utils::getTmAsTimeT(creationTime), - ChatRoomId(IdentityAddress(peerSipAddress), IdentityAddress(localSipAddress)) - ); - - L_END_LOG_EXCEPTION +shared_ptr MainDb::getEventFromKey (const MainDbKey &dbKey) { + shared_ptr event; + if (!dbKey.isValid()) { + lWarning() << "Unable to get event from invalid key."; return event; } - list> MainDb::getConferenceNotifiedEvents ( - const ChatRoomId &chatRoomId, - unsigned int lastNotifyId - ) const { - static const string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_notified_event WHERE event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - " ) AND notify_id > :lastNotifyId" - " )"; + unique_ptr &q = dbKey.getPrivate()->core.lock()->getPrivate()->mainDb; + MainDbPrivate *d = q->getPrivate(); - L_D(); + if (!q->isConnected()) { + lWarning() << "Unable to get event from key. Not connected."; + return event; + } - list> events; + const long long &storageId = dbKey.getPrivate()->storageId; + event = d->getEventFromCache(storageId); + if (event) + return event; - if (!isConnected()) { - lWarning() << "Unable to get conference notified events. Not connected."; - return events; - } + // TODO: Improve. Deal with all events in the future. + static const string query = "SELECT peer_sip_address.value, local_sip_address.value, type, event.creation_time" + " FROM event, conference_event, chat_room, sip_address AS peer_sip_address, sip_address as local_sip_address" + " WHERE event.id = :eventId" + " AND conference_event.event_id = event.id" + " AND conference_event.chat_room_id = chat_room.id" + " AND chat_room.peer_sip_address_id = peer_sip_address.id" + " AND chat_room.local_sip_address_id = local_sip_address.id"; - DurationLogger durationLogger( - "Get conference notified events of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + - ", lastNotifyId=" + Utils::toString(lastNotifyId) + ")." - ); + L_BEGIN_LOG_EXCEPTION - L_BEGIN_LOG_EXCEPTION + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); + string peerSipAddress; + string localSipAddress; + int type; + tm creationTime; + *session << query, soci::into(peerSipAddress), soci::into(localSipAddress), soci::into(type), + soci::into(creationTime), soci::use(storageId); + + event = d->selectGenericConferenceEvent( + storageId, + static_cast(type), + Utils::getTmAsTimeT(creationTime), + ChatRoomId(IdentityAddress(peerSipAddress), IdentityAddress(localSipAddress)) + ); + + L_END_LOG_EXCEPTION + + return event; +} + +list> MainDb::getConferenceNotifiedEvents ( + const ChatRoomId &chatRoomId, + unsigned int lastNotifyId +) const { + static const string query = "SELECT id, type, creation_time FROM event" + " WHERE id IN (" + " SELECT event_id FROM conference_notified_event WHERE event_id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + " ) AND notify_id > :lastNotifyId" + " )"; + + L_D(); + + list> events; + + if (!isConnected()) { + lWarning() << "Unable to get conference notified events. Not connected."; + return events; + } + + DurationLogger durationLogger( + "Get conference notified events of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + + ", lastNotifyId=" + Utils::toString(lastNotifyId) + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + + soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId), soci::use(lastNotifyId)); + for (const auto &row : rows) { + long long eventId = d->resolveId(row, 0); + shared_ptr eventLog = d->getEventFromCache(eventId); + + events.push_back(eventLog ? eventLog : d->selectGenericConferenceEvent( + eventId, + static_cast(row.get(1)), + Utils::getTmAsTimeT(row.get(2)), + chatRoomId + )); + } + + return events; + + L_END_LOG_EXCEPTION + + // Error. + return list>(); +} + +int MainDb::getChatMessageCount (const ChatRoomId &chatRoomId) const { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to get messages count. Not connected."; + return 0; + } + + int count = 0; + + DurationLogger durationLogger( + "Get chat messages count of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + + string query = "SELECT COUNT(*) FROM conference_chat_message_event"; + if (!chatRoomId.isValid()) + *session << query, soci::into(count); + else { + query += " WHERE event_id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + ")"; const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + *session << query, soci::use(dbChatRoomId), soci::into(count); + } - soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId), soci::use(lastNotifyId)); - for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr eventLog = d->getEventFromCache(eventId); + L_END_LOG_EXCEPTION - events.push_back(eventLog ? eventLog : d->selectGenericConferenceEvent( + return count; +} + +int MainDb::getUnreadChatMessageCount (const ChatRoomId &chatRoomId) const { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to get unread messages count. Not connected."; + return 0; + } + + int count = 0; + + string query = "SELECT COUNT(*) FROM conference_chat_message_event WHERE"; + if (chatRoomId.isValid()) + query += " event_id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + ") AND"; + + query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + + + " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)); + + DurationLogger durationLogger( + "Get unread chat messages count of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + + if (!chatRoomId.isValid()) + *session << query, soci::into(count); + else { + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + *session << query, soci::use(dbChatRoomId), soci::into(count); + } + + L_END_LOG_EXCEPTION + + return count; +} + +void MainDb::markChatMessagesAsRead (const ChatRoomId &chatRoomId) const { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to mark messages as read. Not connected."; + return; + } + + if (getUnreadChatMessageCount(chatRoomId) == 0) + return; + + string query = "UPDATE conference_chat_message_event" + " SET state = " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + " "; + query += "WHERE"; + if (chatRoomId.isValid()) + query += " event_id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + ") AND"; + query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)); + + DurationLogger durationLogger( + "Mark chat messages as read of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + + if (!chatRoomId.isValid()) + *session << query; + else { + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + *session << query, soci::use(dbChatRoomId); + } + + L_END_LOG_EXCEPTION +} + +list> MainDb::getUnreadChatMessages (const ChatRoomId &chatRoomId) const { + L_D(); + + list> chatMessages; + + if (!isConnected()) { + lWarning() << "Unable to get unread chat messages. Not connected."; + return chatMessages; + } + + string query = "SELECT id, creation_time FROM event WHERE" + " id IN (" + " SELECT conference_event.event_id FROM conference_event, conference_chat_message_event" + " WHERE"; + if (chatRoomId.isValid()) + query += " chat_room_id = :chatRoomId AND "; + query += " conference_event.event_id = conference_chat_message_event.event_id" + " AND direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + + " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + + ")"; + + DurationLogger durationLogger( + "Get unread chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + long long dbChatRoomId; + if (chatRoomId.isValid()) + dbChatRoomId = d->selectChatRoomId(chatRoomId); + + soci::rowset rows = chatRoomId.isValid() + ? (session->prepare << query, soci::use(dbChatRoomId)) + : (session->prepare << query); + + for (const auto &row : rows) { + long long eventId = d->resolveId(row, 0); + shared_ptr event = d->getEventFromCache(eventId); + + if (!event) + event = d->selectGenericConferenceEvent( + eventId, + EventLog::Type::ConferenceChatMessage, + Utils::getTmAsTimeT(row.get(1)), + chatRoomId + ); + + if (event) + chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); + } + + return chatMessages; + + L_END_LOG_EXCEPTION + + // Error. + return list>(); +} + +shared_ptr MainDb::getLastChatMessage (const ChatRoomId &chatRoomId) const { + list> chatList = getHistory(chatRoomId, 1, Filter::ConferenceChatMessageFilter); + if (chatList.empty()) + return nullptr; + + return static_pointer_cast(chatList.front())->getChatMessage(); +} + +list> MainDb::findChatMessages ( + const ChatRoomId &chatRoomId, + const string &imdnMessageId +) const { + L_D(); + + list> chatMessages; + + if (!isConnected()) { + lWarning() << "Unable to find chat messages. Not connected."; + return chatMessages; + } + + static const string query = "SELECT id, type, creation_time FROM event" + " WHERE id IN (" + " SELECT event_id FROM conference_event" + " WHERE event_id IN (SELECT event_id FROM conference_chat_message_event WHERE imdn_message_id = :imdnMessageId)" + " AND chat_room_id = :chatRoomId" + " )"; + + DurationLogger durationLogger( + "Find chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + soci::rowset rows = (session->prepare << query, soci::use(imdnMessageId), soci::use(dbChatRoomId)); + for (const auto &row : rows) { + long long eventId = d->resolveId(row, 0); + shared_ptr event = d->getEventFromCache(eventId); + + if (!event) + event = d->selectGenericConferenceEvent( eventId, static_cast(row.get(1)), Utils::getTmAsTimeT(row.get(2)), chatRoomId - )); - } + ); + if (event) { + L_ASSERT(event->getType() == EventLog::Type::ConferenceChatMessage); + chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); + } else + lWarning() << "Unable to fetch event: " << eventId; + } + + return chatMessages; + + L_END_LOG_EXCEPTION + + // Error. + return list>(); +} + +list> MainDb::getHistory (const ChatRoomId &chatRoomId, int nLast, FilterMask mask) const { + return getHistoryRange(chatRoomId, 0, nLast, mask); +} + +list> MainDb::getHistoryRange ( + const ChatRoomId &chatRoomId, + int begin, + int end, + FilterMask mask +) const { + L_D(); + + list> events; + + if (!isConnected()) { + lWarning() << "Unable to get history. Not connected."; return events; - - L_END_LOG_EXCEPTION - - // Error. - return list>(); } - int MainDb::getChatMessageCount (const ChatRoomId &chatRoomId) const { - L_D(); + if (begin < 0) + begin = 0; - if (!isConnected()) { - lWarning() << "Unable to get messages count. Not connected."; - return 0; - } - - int count = 0; - - DurationLogger durationLogger( - "Get chat messages count of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - - string query = "SELECT COUNT(*) FROM conference_chat_message_event"; - if (!chatRoomId.isValid()) - *session << query, soci::into(count); - else { - query += " WHERE event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - ")"; - - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - *session << query, soci::use(dbChatRoomId), soci::into(count); - } - - L_END_LOG_EXCEPTION - - return count; + if (end > 0 && begin > end) { + lWarning() << "Unable to get history. Invalid range."; + return events; } - int MainDb::getUnreadChatMessageCount (const ChatRoomId &chatRoomId) const { - L_D(); + string query = "SELECT id, type, creation_time FROM event" + " WHERE id IN (" + " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + " )"; + query += buildSqlEventFilter({ + ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter + }, mask, "AND"); + query += " ORDER BY creation_time DESC"; - if (!isConnected()) { - lWarning() << "Unable to get unread messages count. Not connected."; - return 0; - } + if (end > 0) + query += " LIMIT " + Utils::toString(end - begin); + else + query += " LIMIT " + noLimitValue(); - int count = 0; + if (begin > 0) + query += " OFFSET " + Utils::toString(begin); - string query = "SELECT COUNT(*) FROM conference_chat_message_event WHERE"; - if (chatRoomId.isValid()) - query += " event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - ") AND"; + DurationLogger durationLogger( + "Get history range of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + + ", begin=" + Utils::toString(begin) + ", end=" + Utils::toString(end) + ")." + ); - query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + - + " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)); + L_BEGIN_LOG_EXCEPTION - DurationLogger durationLogger( - "Get unread chat messages count of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); - L_BEGIN_LOG_EXCEPTION + 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->resolveId(row, 0); + shared_ptr event = d->getEventFromCache(eventId); - soci::session *session = d->dbSession.getBackendSession(); + if (!event) + event = d->selectGenericConferenceEvent( + eventId, + static_cast(row.get(1)), + Utils::getTmAsTimeT(row.get(2)), + chatRoomId + ); - if (!chatRoomId.isValid()) - *session << query, soci::into(count); - else { - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - *session << query, soci::use(dbChatRoomId), soci::into(count); - } - - L_END_LOG_EXCEPTION - - return count; + if (event) + events.push_front(event); + else + lWarning() << "Unable to fetch event: " << eventId; } - void MainDb::markChatMessagesAsRead (const ChatRoomId &chatRoomId) const { - L_D(); + return events; - if (!isConnected()) { - lWarning() << "Unable to mark messages as read. Not connected."; - return; - } + L_END_LOG_EXCEPTION - if (getUnreadChatMessageCount(chatRoomId) == 0) - return; + // Error. + return list>(); +} - string query = "UPDATE conference_chat_message_event" - " SET state = " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + " "; - query += "WHERE"; - if (chatRoomId.isValid()) - query += " event_id IN (" - " SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" - ") AND"; - query += " direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)); +int MainDb::getHistorySize (const ChatRoomId &chatRoomId, FilterMask mask) const { + L_D(); - DurationLogger durationLogger( - "Mark chat messages as read of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - - if (!chatRoomId.isValid()) - *session << query; - else { - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - *session << query, soci::use(dbChatRoomId); - } - - L_END_LOG_EXCEPTION + if (!isConnected()) { + lWarning() << "Unable to get history size. Not connected."; + return 0; } - list> MainDb::getUnreadChatMessages (const ChatRoomId &chatRoomId) const { - L_D(); + int count = 0; - list> chatMessages; - - if (!isConnected()) { - lWarning() << "Unable to get unread chat messages. Not connected."; - return chatMessages; - } - - string query = "SELECT id, creation_time FROM event WHERE" - " id IN (" - " SELECT conference_event.event_id FROM conference_event, conference_chat_message_event" - " WHERE"; - if (chatRoomId.isValid()) - query += " chat_room_id = :chatRoomId AND "; - query += " conference_event.event_id = conference_chat_message_event.event_id" - " AND direction = " + Utils::toString(static_cast(ChatMessage::Direction::Incoming)) + - " AND state <> " + Utils::toString(static_cast(ChatMessage::State::Displayed)) + - ")"; - - DurationLogger durationLogger( - "Get unread chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - long long dbChatRoomId; - if (chatRoomId.isValid()) - dbChatRoomId = d->selectChatRoomId(chatRoomId); - - soci::rowset rows = chatRoomId.isValid() - ? (session->prepare << query, soci::use(dbChatRoomId)) - : (session->prepare << query); - - for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); - - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - EventLog::Type::ConferenceChatMessage, - Utils::getTmAsTimeT(row.get(1)), - chatRoomId - ); - - if (event) - chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); - } - - return chatMessages; - - L_END_LOG_EXCEPTION - - // Error. - return list>(); - } - - shared_ptr MainDb::getLastChatMessage (const ChatRoomId &chatRoomId) const { - list> chatList = getHistory(chatRoomId, 1, Filter::ConferenceChatMessageFilter); - if (chatList.empty()) - return nullptr; - - return static_pointer_cast(chatList.front())->getChatMessage(); - } - - list> MainDb::findChatMessages ( - const ChatRoomId &chatRoomId, - const string &imdnMessageId - ) const { - L_D(); - - list> chatMessages; - - if (!isConnected()) { - lWarning() << "Unable to find chat messages. Not connected."; - return chatMessages; - } - - static const string query = "SELECT id, type, creation_time FROM event" - " WHERE id IN (" - " SELECT event_id FROM conference_event" - " WHERE event_id IN (SELECT event_id FROM conference_chat_message_event WHERE imdn_message_id = :imdnMessageId)" - " AND chat_room_id = :chatRoomId" - " )"; - - DurationLogger durationLogger( - "Find chat messages: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - soci::rowset rows = (session->prepare << query, soci::use(imdnMessageId), soci::use(dbChatRoomId)); - for (const auto &row : rows) { - long long eventId = d->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); - - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - static_cast(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - ); - - if (event) { - L_ASSERT(event->getType() == EventLog::Type::ConferenceChatMessage); - chatMessages.push_back(static_pointer_cast(event)->getChatMessage()); - } else - lWarning() << "Unable to fetch event: " << eventId; - } - - return chatMessages; - - L_END_LOG_EXCEPTION - - // Error. - return list>(); - } - - list> MainDb::getHistory (const ChatRoomId &chatRoomId, int nLast, FilterMask mask) const { - return getHistoryRange(chatRoomId, 0, nLast, mask); - } - - list> MainDb::getHistoryRange ( - const ChatRoomId &chatRoomId, - int begin, - int end, - FilterMask mask - ) const { - L_D(); - - list> events; - - if (!isConnected()) { - lWarning() << "Unable to get history. Not connected."; - return events; - } - - if (begin < 0) - begin = 0; - - if (end > 0 && begin > end) { - lWarning() << "Unable to get history. Invalid range."; - 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" - " )"; - query += buildSqlEventFilter({ + const string query = "SELECT COUNT(*) FROM event, conference_event" + " WHERE chat_room_id = :chatRoomId" + " AND event_id = event.id" + + buildSqlEventFilter({ ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter }, mask, "AND"); - query += " ORDER BY creation_time DESC"; - if (end > 0) - query += " LIMIT " + Utils::toString(end - begin); - else - query += " LIMIT " + noLimitValue(); + L_BEGIN_LOG_EXCEPTION - if (begin > 0) - query += " OFFSET " + Utils::toString(begin); + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - DurationLogger durationLogger( - "Get history range of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + - ", begin=" + Utils::toString(begin) + ", end=" + Utils::toString(end) + ")." - ); + soci::session *session = d->dbSession.getBackendSession(); + *session << query, soci::into(count), soci::use(dbChatRoomId); - L_BEGIN_LOG_EXCEPTION + L_END_LOG_EXCEPTION - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); + return count; +} - 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->resolveId(row, 0); - shared_ptr event = d->getEventFromCache(eventId); +void MainDb::cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask) { + L_D(); - if (!event) - event = d->selectGenericConferenceEvent( - eventId, - static_cast(row.get(1)), - Utils::getTmAsTimeT(row.get(2)), - chatRoomId - ); - - if (event) - events.push_front(event); - else - lWarning() << "Unable to fetch event: " << eventId; - } - - return events; - - L_END_LOG_EXCEPTION - - // Error. - return list>(); + if (!isConnected()) { + lWarning() << "Unable to clean history. Not connected."; + return; } - int MainDb::getHistorySize (const ChatRoomId &chatRoomId, FilterMask mask) const { - L_D(); + const string query = "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + + buildSqlEventFilter({ + ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter + }, mask); - if (!isConnected()) { - lWarning() << "Unable to get history size. Not connected."; - return 0; - } + DurationLogger durationLogger( + "Clean history of: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + + ", mask=" + Utils::toString(mask) + ")." + ); - int count = 0; + L_BEGIN_LOG_EXCEPTION - const string query = "SELECT COUNT(*) FROM event, conference_event" - " WHERE chat_room_id = :chatRoomId" - " AND event_id = event.id" + - buildSqlEventFilter({ - ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter - }, mask, "AND"); + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); - L_BEGIN_LOG_EXCEPTION + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + d->invalidConferenceEventsFromQuery(query, dbChatRoomId); + *session << "DELETE FROM event WHERE id IN (" + query + ")", soci::use(dbChatRoomId); - soci::session *session = d->dbSession.getBackendSession(); - *session << query, soci::into(count), soci::use(dbChatRoomId); + tr.commit(); - L_END_LOG_EXCEPTION - - return count; - } - - void MainDb::cleanHistory (const ChatRoomId &chatRoomId, FilterMask mask) { - L_D(); - - if (!isConnected()) { - lWarning() << "Unable to clean history. Not connected."; - return; - } - - const string query = "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId" + - buildSqlEventFilter({ - ConferenceCallFilter, ConferenceChatMessageFilter, ConferenceInfoFilter - }, mask); - - DurationLogger durationLogger( - "Clean history of: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + - ", mask=" + Utils::toString(mask) + ")." - ); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - - d->invalidConferenceEventsFromQuery(query, dbChatRoomId); - *session << "DELETE FROM event WHERE id IN (" + query + ")", soci::use(dbChatRoomId); - - tr.commit(); - - L_END_LOG_EXCEPTION - } + L_END_LOG_EXCEPTION +} // ----------------------------------------------------------------------------- - list> MainDb::getChatRooms () const { - static const string query = "SELECT chat_room.id, peer_sip_address.value, local_sip_address.value, " - "creation_time, last_update_time, capabilities, subject, last_notify_id, flags" - " FROM chat_room, sip_address AS peer_sip_address, sip_address AS local_sip_address" - " WHERE chat_room.peer_sip_address_id = peer_sip_address.id AND chat_room.local_sip_address_id = local_sip_address.id" - " ORDER BY last_update_time DESC"; +list> MainDb::getChatRooms () const { + static const string query = "SELECT chat_room.id, peer_sip_address.value, local_sip_address.value, " + "creation_time, last_update_time, capabilities, subject, last_notify_id, flags" + " FROM chat_room, sip_address AS peer_sip_address, sip_address AS local_sip_address" + " WHERE chat_room.peer_sip_address_id = peer_sip_address.id AND chat_room.local_sip_address_id = local_sip_address.id" + " ORDER BY last_update_time DESC"; - L_D(); + L_D(); - list> chatRooms; - - if (!isConnected()) { - lWarning() << "Unable to get chat rooms. Not connected."; - return chatRooms; - } - - shared_ptr core = getCore(); - - DurationLogger durationLogger("Get chat rooms."); - - L_BEGIN_LOG_EXCEPTION - - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - soci::rowset rows = (session->prepare << query); - for (const auto &row : rows) { - ChatRoomId chatRoomId = ChatRoomId( - IdentityAddress(row.get(1)), - IdentityAddress(row.get(2)) - ); - shared_ptr chatRoom = core->findChatRoom(chatRoomId); - if (chatRoom) { - chatRooms.push_back(chatRoom); - continue; - } - - tm creationTime = row.get(3); - tm lastUpdateTime = row.get(4); - int capabilities = row.get(5); - string subject = row.get(6, ""); - unsigned int lastNotifyId = getBackend() == Backend::Mysql - ? row.get(7, 0) - : static_cast(row.get(7, 0)); - - if (capabilities & ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Basic)) { - chatRoom = core->getPrivate()->createBasicChatRoom(chatRoomId, capabilities); - chatRoom->setSubject(subject); - } else if (capabilities & ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Conference)) { - list> participants; - - const long long &dbChatRoomId = d->resolveId(row, 0); - static const string query = "SELECT chat_room_participant.id, sip_address.value, is_admin" - " FROM sip_address, chat_room, chat_room_participant" - " WHERE chat_room.id = :chatRoomId" - " AND sip_address.id = chat_room_participant.participant_sip_address_id" - " AND chat_room_participant.chat_room_id = chat_room.id"; - - // Fetch participants. - soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); - shared_ptr me; - for (const auto &row : rows) { - shared_ptr participant = make_shared(IdentityAddress(row.get(1))); - ParticipantPrivate *dParticipant = participant->getPrivate(); - dParticipant->setAdmin(!!row.get(2)); - - // Fetch devices. - { - const long long &participantId = d->resolveId(row, 0); - static const string query = "SELECT sip_address.value FROM chat_room_participant_device, sip_address" - " WHERE chat_room_participant_id = :participantId" - " AND participant_device_sip_address_id = sip_address.id"; - - soci::rowset rows = (session->prepare << query, soci::use(participantId)); - for (const auto &row : rows) - dParticipant->addDevice(IdentityAddress(row.get(0))); - } - - if (participant->getAddress() == chatRoomId.getLocalAddress().getAddressWithoutGruu()) - me = participant; - else - participants.push_back(participant); - } - - if (!linphone_core_conference_server_enabled(core->getCCore())) { - bool hasBeenLeft = !!row.get(8, 0); - if (!me) { - lError() << "Unable to find me in: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")."; - continue; - } - chatRoom = make_shared( - core, - chatRoomId, - me, - subject, - move(participants), - lastNotifyId - ); - AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); - dChatRoom->setState(ChatRoom::State::Instantiated); - dChatRoom->setState(hasBeenLeft - ? ChatRoom::State::Terminated - : ChatRoom::State::Created - ); - } else { - chatRoom = make_shared( - core, - chatRoomId.getPeerAddress(), - subject, - move(participants), - lastNotifyId - ); - AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); - dChatRoom->setState(ChatRoom::State::Instantiated); - dChatRoom->setState(ChatRoom::State::Created); - } - } - - if (!chatRoom) - continue; // Not fetched. - - AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); - dChatRoom->setCreationTime(Utils::getTmAsTimeT(creationTime)); - dChatRoom->setLastUpdateTime(Utils::getTmAsTimeT(lastUpdateTime)); - - lInfo() << "Found chat room in DB: (peer=" << - chatRoomId.getPeerAddress().asString() << ", local=" << chatRoomId.getLocalAddress().asString() << ")."; - - chatRooms.push_back(chatRoom); - } - - tr.commit(); + list> chatRooms; + if (!isConnected()) { + lWarning() << "Unable to get chat rooms. Not connected."; return chatRooms; - - L_END_LOG_EXCEPTION - - // Error. - return list>(); } - void MainDb::insertChatRoom (const shared_ptr &chatRoom) { - L_D(); + shared_ptr core = getCore(); - if (!isConnected()) { - lWarning() << "Unable to insert chat room. Not connected."; - return; + DurationLogger durationLogger("Get chat rooms."); + + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + soci::rowset rows = (session->prepare << query); + for (const auto &row : rows) { + ChatRoomId chatRoomId = ChatRoomId( + IdentityAddress(row.get(1)), + IdentityAddress(row.get(2)) + ); + shared_ptr chatRoom = core->findChatRoom(chatRoomId); + if (chatRoom) { + chatRooms.push_back(chatRoom); + continue; } - const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); - DurationLogger durationLogger( - "Insert chat room: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + ")." - ); + tm creationTime = row.get(3); + tm lastUpdateTime = row.get(4); + int capabilities = row.get(5); + string subject = row.get(6, ""); + unsigned int lastNotifyId = getBackend() == Backend::Mysql + ? row.get(7, 0) + : static_cast(row.get(7, 0)); - L_BEGIN_LOG_EXCEPTION + if (capabilities & ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Basic)) { + chatRoom = core->getPrivate()->createBasicChatRoom(chatRoomId, capabilities); + chatRoom->setSubject(subject); + } else if (capabilities & ChatRoom::CapabilitiesMask(ChatRoom::Capabilities::Conference)) { + list> participants; - soci::transaction tr(*d->dbSession.getBackendSession()); + const long long &dbChatRoomId = d->resolveId(row, 0); + static const string query = "SELECT chat_room_participant.id, sip_address.value, is_admin" + " FROM sip_address, chat_room, chat_room_participant" + " WHERE chat_room.id = :chatRoomId" + " AND sip_address.id = chat_room_participant.participant_sip_address_id" + " AND chat_room_participant.chat_room_id = chat_room.id"; - d->insertChatRoom(chatRoom); + // Fetch participants. + soci::rowset rows = (session->prepare << query, soci::use(dbChatRoomId)); + shared_ptr me; + for (const auto &row : rows) { + shared_ptr participant = make_shared(IdentityAddress(row.get(1))); + ParticipantPrivate *dParticipant = participant->getPrivate(); + dParticipant->setAdmin(!!row.get(2)); - tr.commit(); + // Fetch devices. + { + const long long &participantId = d->resolveId(row, 0); + static const string query = "SELECT sip_address.value FROM chat_room_participant_device, sip_address" + " WHERE chat_room_participant_id = :participantId" + " AND participant_device_sip_address_id = sip_address.id"; - L_END_LOG_EXCEPTION + soci::rowset rows = (session->prepare << query, soci::use(participantId)); + for (const auto &row : rows) + dParticipant->addDevice(IdentityAddress(row.get(0))); + } + + if (participant->getAddress() == chatRoomId.getLocalAddress().getAddressWithoutGruu()) + me = participant; + else + participants.push_back(participant); + } + + if (!linphone_core_conference_server_enabled(core->getCCore())) { + bool hasBeenLeft = !!row.get(8, 0); + if (!me) { + lError() << "Unable to find me in: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")."; + continue; + } + chatRoom = make_shared( + core, + chatRoomId, + me, + subject, + move(participants), + lastNotifyId + ); + AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); + dChatRoom->setState(ChatRoom::State::Instantiated); + dChatRoom->setState(hasBeenLeft + ? ChatRoom::State::Terminated + : ChatRoom::State::Created + ); + } else { + chatRoom = make_shared( + core, + chatRoomId.getPeerAddress(), + subject, + move(participants), + lastNotifyId + ); + AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); + dChatRoom->setState(ChatRoom::State::Instantiated); + dChatRoom->setState(ChatRoom::State::Created); + } + } + + if (!chatRoom) + continue; // Not fetched. + + AbstractChatRoomPrivate *dChatRoom = chatRoom->getPrivate(); + dChatRoom->setCreationTime(Utils::getTmAsTimeT(creationTime)); + dChatRoom->setLastUpdateTime(Utils::getTmAsTimeT(lastUpdateTime)); + + lInfo() << "Found chat room in DB: (peer=" << + chatRoomId.getPeerAddress().asString() << ", local=" << chatRoomId.getLocalAddress().asString() << ")."; + + chatRooms.push_back(chatRoom); } - void MainDb::deleteChatRoom (const ChatRoomId &chatRoomId) { - L_D(); + tr.commit(); - if (!isConnected()) { - lWarning() << "Unable to delete chat room. Not connected."; - return; - } + return chatRooms; - DurationLogger durationLogger( - "Delete chat room: (peer=" + chatRoomId.getPeerAddress().asString() + - ", local=" + chatRoomId.getLocalAddress().asString() + "`)." - ); + L_END_LOG_EXCEPTION - L_BEGIN_LOG_EXCEPTION + // Error. + return list>(); +} - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); +void MainDb::insertChatRoom (const shared_ptr &chatRoom) { + L_D(); - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - - d->invalidConferenceEventsFromQuery( - "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId", - dbChatRoomId - ); - - *session << "DELETE FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomId); - - tr.commit(); - - L_END_LOG_EXCEPTION + if (!isConnected()) { + lWarning() << "Unable to insert chat room. Not connected."; + return; } - void MainDb::migrateBasicToClientGroupChatRoom ( - const shared_ptr &basicChatRoom, - const shared_ptr &clientGroupChatRoom - ) { - L_D(); + const ChatRoomId &chatRoomId = chatRoom->getChatRoomId(); + DurationLogger durationLogger( + "Insert chat room: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + ")." + ); - if (!isConnected()) { - lWarning() << "Unable to migrate chat room. Not connected."; - return; - } + L_BEGIN_LOG_EXCEPTION - L_ASSERT(basicChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic)); - L_ASSERT(clientGroupChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Conference)); + soci::transaction tr(*d->dbSession.getBackendSession()); - L_BEGIN_LOG_EXCEPTION + d->insertChatRoom(chatRoom); - // TODO: Update events and chat messages. (Or wait signals.) + tr.commit(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); + L_END_LOG_EXCEPTION +} - const long long &dbChatRoomId = d->selectChatRoomId(basicChatRoom->getChatRoomId()); +void MainDb::deleteChatRoom (const ChatRoomId &chatRoomId) { + L_D(); - const ChatRoomId &newChatRoomId = clientGroupChatRoom->getChatRoomId(); - const long long &peerSipAddressId = d->insertSipAddress(newChatRoomId.getPeerAddress().asString()); - const long long &localSipAddressId = d->insertSipAddress(newChatRoomId.getLocalAddress().asString()); - const int &capabilities = clientGroupChatRoom->getCapabilities(); + if (!isConnected()) { + lWarning() << "Unable to delete chat room. Not connected."; + return; + } - *session << "UPDATE chat_room" - " SET capabilities = :capabilities," - " peer_sip_address_id = :peerSipAddressId," - " local_sip_address_id = :localSipAddressId" - " WHERE id = :chatRoomId", soci::use(capabilities), soci::use(peerSipAddressId), - soci::use(localSipAddressId), soci::use(dbChatRoomId); + DurationLogger durationLogger( + "Delete chat room: (peer=" + chatRoomId.getPeerAddress().asString() + + ", local=" + chatRoomId.getLocalAddress().asString() + "`)." + ); - shared_ptr me = clientGroupChatRoom->getMe(); - long long meId = d->insertChatRoomParticipant( + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + + d->invalidConferenceEventsFromQuery( + "SELECT event_id FROM conference_event WHERE chat_room_id = :chatRoomId", + dbChatRoomId + ); + + *session << "DELETE FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomId); + + tr.commit(); + + L_END_LOG_EXCEPTION +} + +void MainDb::migrateBasicToClientGroupChatRoom ( + const shared_ptr &basicChatRoom, + const shared_ptr &clientGroupChatRoom +) { + L_D(); + + if (!isConnected()) { + lWarning() << "Unable to migrate chat room. Not connected."; + return; + } + + L_ASSERT(basicChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic)); + L_ASSERT(clientGroupChatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Conference)); + + L_BEGIN_LOG_EXCEPTION + + // TODO: Update events and chat messages. (Or wait signals.) + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + const long long &dbChatRoomId = d->selectChatRoomId(basicChatRoom->getChatRoomId()); + + const ChatRoomId &newChatRoomId = clientGroupChatRoom->getChatRoomId(); + const long long &peerSipAddressId = d->insertSipAddress(newChatRoomId.getPeerAddress().asString()); + const long long &localSipAddressId = d->insertSipAddress(newChatRoomId.getLocalAddress().asString()); + const int &capabilities = clientGroupChatRoom->getCapabilities(); + + *session << "UPDATE chat_room" + " SET capabilities = :capabilities," + " peer_sip_address_id = :peerSipAddressId," + " local_sip_address_id = :localSipAddressId" + " WHERE id = :chatRoomId", soci::use(capabilities), soci::use(peerSipAddressId), + soci::use(localSipAddressId), soci::use(dbChatRoomId); + + shared_ptr me = clientGroupChatRoom->getMe(); + long long meId = d->insertChatRoomParticipant( + dbChatRoomId, + d->insertSipAddress(me->getAddress().asString()), + true + ); + for (const auto &device : me->getPrivate()->getDevices()) + d->insertChatRoomParticipantDevice(meId, d->insertSipAddress(device->getAddress().asString())); + + for (const auto &participant : clientGroupChatRoom->getParticipants()) { + long long participantId = d->insertChatRoomParticipant( dbChatRoomId, - d->insertSipAddress(me->getAddress().asString()), + d->insertSipAddress(participant->getAddress().asString()), true ); - for (const auto &device : me->getPrivate()->getDevices()) - d->insertChatRoomParticipantDevice(meId, d->insertSipAddress(device->getAddress().asString())); - - for (const auto &participant : clientGroupChatRoom->getParticipants()) { - long long participantId = d->insertChatRoomParticipant( - dbChatRoomId, - d->insertSipAddress(participant->getAddress().asString()), - true - ); - for (const auto &device : participant->getPrivate()->getDevices()) - d->insertChatRoomParticipantDevice(participantId, d->insertSipAddress(device->getAddress().asString())); - } - - tr.commit(); - - L_END_LOG_EXCEPTION + for (const auto &device : participant->getPrivate()->getDevices()) + d->insertChatRoomParticipantDevice(participantId, d->insertSipAddress(device->getAddress().asString())); } - void MainDb::enableChatRoomMigration (const ChatRoomId &chatRoomId, bool enable) { - L_D(); + tr.commit(); - if (!isConnected()) { - lWarning() << "Unable to enable chat room migration. Not connected."; - return; - } + L_END_LOG_EXCEPTION +} - L_BEGIN_LOG_EXCEPTION +void MainDb::enableChatRoomMigration (const ChatRoomId &chatRoomId, bool enable) { + L_D(); - soci::session *session = d->dbSession.getBackendSession(); - soci::transaction tr(*session); - - const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); - - int capabilities = 0; - *session << "SELECT capabilities FROM chat_room WHERE id = :chatRoomId", - soci::use(dbChatRoomId), soci::into(capabilities); - if (enable) - capabilities |= static_cast(ChatRoom::Capabilities::Migratable); - else - capabilities &= ~static_cast(ChatRoom::Capabilities::Migratable); - *session << "UPDATE chat_room SET capabilities = :capabilities WHERE id = :chatRoomId", - soci::use(capabilities), soci::use(dbChatRoomId); - - tr.commit(); - - L_END_LOG_EXCEPTION + if (!isConnected()) { + lWarning() << "Unable to enable chat room migration. Not connected."; + return; } + L_BEGIN_LOG_EXCEPTION + + soci::session *session = d->dbSession.getBackendSession(); + soci::transaction tr(*session); + + const long long &dbChatRoomId = d->selectChatRoomId(chatRoomId); + + int capabilities = 0; + *session << "SELECT capabilities FROM chat_room WHERE id = :chatRoomId", + soci::use(dbChatRoomId), soci::into(capabilities); + if (enable) + capabilities |= static_cast(ChatRoom::Capabilities::Migratable); + else + capabilities &= ~static_cast(ChatRoom::Capabilities::Migratable); + *session << "UPDATE chat_room SET capabilities = :capabilities WHERE id = :chatRoomId", + soci::use(capabilities), soci::use(dbChatRoomId); + + tr.commit(); + + L_END_LOG_EXCEPTION +} + // ----------------------------------------------------------------------------- - #define LEGACY_MESSAGE_COL_LOCAL_ADDRESS 1 - #define LEGACY_MESSAGE_COL_REMOTE_ADDRESS 2 - #define LEGACY_MESSAGE_COL_DIRECTION 3 - #define LEGACY_MESSAGE_COL_TEXT 4 - #define LEGACY_MESSAGE_COL_STATE 7 - #define LEGACY_MESSAGE_COL_URL 8 - #define LEGACY_MESSAGE_COL_DATE 9 - #define LEGACY_MESSAGE_COL_APP_DATA 10 - #define LEGACY_MESSAGE_COL_CONTENT_ID 11 - #define LEGACY_MESSAGE_COL_IMDN_MESSAGE_ID 12 - #define LEGACY_MESSAGE_COL_CONTENT_TYPE 13 - #define LEGACY_MESSAGE_COL_IS_SECURED 14 +#define LEGACY_MESSAGE_COL_LOCAL_ADDRESS 1 +#define LEGACY_MESSAGE_COL_REMOTE_ADDRESS 2 +#define LEGACY_MESSAGE_COL_DIRECTION 3 +#define LEGACY_MESSAGE_COL_TEXT 4 +#define LEGACY_MESSAGE_COL_STATE 7 +#define LEGACY_MESSAGE_COL_URL 8 +#define LEGACY_MESSAGE_COL_DATE 9 +#define LEGACY_MESSAGE_COL_APP_DATA 10 +#define LEGACY_MESSAGE_COL_CONTENT_ID 11 +#define LEGACY_MESSAGE_COL_IMDN_MESSAGE_ID 12 +#define LEGACY_MESSAGE_COL_CONTENT_TYPE 13 +#define LEGACY_MESSAGE_COL_IS_SECURED 14 - template - static T getValueFromLegacyMessage (const soci::row &message, int index, bool &isNull) { - isNull = false; +template +static T getValueFromLegacyMessage (const soci::row &message, int index, bool &isNull) { + isNull = false; - try { - return message.get(static_cast(index)); - } catch (const exception &) { - isNull = true; - } - - return T(); + try { + return message.get(static_cast(index)); + } catch (const exception &) { + isNull = true; } - bool MainDb::import (Backend, const string ¶meters) { - L_D(); + return T(); +} - if (!isConnected()) { - lWarning() << "Unable to import data. Not connected."; - return 0; - } +bool MainDb::import (Backend, const string ¶meters) { + L_D(); - // Backend is useless, it's sqlite3. (Only available legacy backend.) - const string uri = "sqlite3://" + parameters; - DbSession inDbSession = DbSessionProvider::getInstance()->getSession(uri); + if (!isConnected()) { + lWarning() << "Unable to import data. Not connected."; + return 0; + } - if (!inDbSession) { - lWarning() << "Unable to connect to: `" << uri << "`."; + // Backend is useless, it's sqlite3. (Only available legacy backend.) + const string uri = "sqlite3://" + parameters; + DbSession inDbSession = DbSessionProvider::getInstance()->getSession(uri); + + if (!inDbSession) { + lWarning() << "Unable to connect to: `" << uri << "`."; + return false; + } + + soci::session *inSession = inDbSession.getBackendSession(); + + // Import messages. + try { + soci::transaction tr(*d->dbSession.getBackendSession()); + + unsigned int version = d->getModuleVersion("legacy-history-import"); + if (version >= L_VERSION(1, 0, 0)) return false; - } + d->updateModuleVersion("legacy-history-import", DB_MODULE_VERSION_LEGACY_HISTORY_IMPORT); - soci::session *inSession = inDbSession.getBackendSession(); - - // Import messages. + soci::rowset messages = (inSession->prepare << "SELECT * FROM history"); try { - soci::transaction tr(*d->dbSession.getBackendSession()); - - unsigned int version = d->getModuleVersion("legacy-history-import"); - if (version >= L_VERSION(1, 0, 0)) - return false; - d->updateModuleVersion("legacy-history-import", DB_MODULE_VERSION_LEGACY_HISTORY_IMPORT); - - soci::rowset messages = (inSession->prepare << "SELECT * FROM history"); - try { - for (const auto &message : messages) { - const int direction = message.get(LEGACY_MESSAGE_COL_DIRECTION); - if (direction != 0 && direction != 1) { - lWarning() << "Unable to import legacy message with invalid direction."; - continue; - } - - const int &state = message.get( - LEGACY_MESSAGE_COL_STATE, static_cast(ChatMessage::State::Displayed) - ); - if (state < 0 || state > static_cast(ChatMessage::State::Displayed)) { - lWarning() << "Unable to import legacy message with invalid state."; - continue; - } - - const tm &creationTime = Utils::getTimeTAsTm(message.get(LEGACY_MESSAGE_COL_DATE, 0)); - - bool isNull; - getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_URL, isNull); - - const int &contentId = message.get(LEGACY_MESSAGE_COL_CONTENT_ID, -1); - ContentType contentType(message.get(LEGACY_MESSAGE_COL_CONTENT_TYPE, "")); - if (!contentType.isValid()) - contentType = contentId != -1 - ? ContentType::FileTransfer - : (isNull ? ContentType::PlainText : ContentType::ExternalBody); - if (contentType == ContentType::ExternalBody) { - lInfo() << "Import of external body content is skipped."; - continue; - } - - const string &text = getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_TEXT, isNull); - - Content content; - content.setContentType(contentType); - if (contentType == ContentType::PlainText) { - if (isNull) { - lWarning() << "Unable to import legacy message with no text."; - continue; - } - content.setBody(text); - } else { - if (contentType != ContentType::FileTransfer) { - lWarning() << "Unable to import unsupported legacy content."; - continue; - } - - const string appData = getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_APP_DATA, isNull); - if (isNull) { - lWarning() << "Unable to import legacy file message without app data."; - continue; - } - - content.setAppData("legacy", appData); - } - - soci::session *session = d->dbSession.getBackendSession(); - const int &eventType = static_cast(EventLog::Type::ConferenceChatMessage); - *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", - soci::use(eventType), soci::use(creationTime); - - const long long &eventId = getLastInsertId(); - const long long &localSipAddressId = d->insertSipAddress(message.get(LEGACY_MESSAGE_COL_LOCAL_ADDRESS)); - const long long &remoteSipAddressId = d->insertSipAddress(message.get(LEGACY_MESSAGE_COL_REMOTE_ADDRESS)); - const long long &chatRoomId = d->insertOrUpdateImportedBasicChatRoom( - remoteSipAddressId, - localSipAddressId, - creationTime - ); - const int &isSecured = message.get(LEGACY_MESSAGE_COL_IS_SECURED, 0); - - *session << "INSERT INTO conference_event (event_id, chat_room_id)" - " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(chatRoomId); - - *session << "INSERT INTO conference_chat_message_event (" - " event_id, from_sip_address_id, to_sip_address_id," - " time, state, direction, imdn_message_id, is_secured" - ") VALUES (" - " :eventId, :localSipAddressId, :remoteSipAddressId," - " :creationTime, :state, :direction, '', :isSecured" - ")", soci::use(eventId), soci::use(localSipAddressId), soci::use(remoteSipAddressId), - soci::use(creationTime), soci::use(state), soci::use(direction), - soci::use(isSecured); - - d->insertContent(eventId, content); - d->insertChatRoomParticipant(chatRoomId, remoteSipAddressId, false); - - if (state != static_cast(ChatMessage::State::Displayed)) - d->insertChatMessageParticipant(eventId, remoteSipAddressId, state); + for (const auto &message : messages) { + const int direction = message.get(LEGACY_MESSAGE_COL_DIRECTION); + if (direction != 0 && direction != 1) { + lWarning() << "Unable to import legacy message with invalid direction."; + continue; } - tr.commit(); - } catch (const exception &e) { - lInfo() << "Failed to import legacy messages from: `" << uri << "`. (" << e.what() << ")"; - return false; + const int &state = message.get( + LEGACY_MESSAGE_COL_STATE, static_cast(ChatMessage::State::Displayed) + ); + if (state < 0 || state > static_cast(ChatMessage::State::Displayed)) { + lWarning() << "Unable to import legacy message with invalid state."; + continue; + } + + const tm &creationTime = Utils::getTimeTAsTm(message.get(LEGACY_MESSAGE_COL_DATE, 0)); + + bool isNull; + getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_URL, isNull); + + const int &contentId = message.get(LEGACY_MESSAGE_COL_CONTENT_ID, -1); + ContentType contentType(message.get(LEGACY_MESSAGE_COL_CONTENT_TYPE, "")); + if (!contentType.isValid()) + contentType = contentId != -1 + ? ContentType::FileTransfer + : (isNull ? ContentType::PlainText : ContentType::ExternalBody); + if (contentType == ContentType::ExternalBody) { + lInfo() << "Import of external body content is skipped."; + continue; + } + + const string &text = getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_TEXT, isNull); + + Content content; + content.setContentType(contentType); + if (contentType == ContentType::PlainText) { + if (isNull) { + lWarning() << "Unable to import legacy message with no text."; + continue; + } + content.setBody(text); + } else { + if (contentType != ContentType::FileTransfer) { + lWarning() << "Unable to import unsupported legacy content."; + continue; + } + + const string appData = getValueFromLegacyMessage(message, LEGACY_MESSAGE_COL_APP_DATA, isNull); + if (isNull) { + lWarning() << "Unable to import legacy file message without app data."; + continue; + } + + content.setAppData("legacy", appData); + } + + soci::session *session = d->dbSession.getBackendSession(); + const int &eventType = static_cast(EventLog::Type::ConferenceChatMessage); + *session << "INSERT INTO event (type, creation_time) VALUES (:type, :creationTime)", + soci::use(eventType), soci::use(creationTime); + + const long long &eventId = getLastInsertId(); + const long long &localSipAddressId = d->insertSipAddress(message.get(LEGACY_MESSAGE_COL_LOCAL_ADDRESS)); + const long long &remoteSipAddressId = d->insertSipAddress(message.get(LEGACY_MESSAGE_COL_REMOTE_ADDRESS)); + const long long &chatRoomId = d->insertOrUpdateImportedBasicChatRoom( + remoteSipAddressId, + localSipAddressId, + creationTime + ); + const int &isSecured = message.get(LEGACY_MESSAGE_COL_IS_SECURED, 0); + + *session << "INSERT INTO conference_event (event_id, chat_room_id)" + " VALUES (:eventId, :chatRoomId)", soci::use(eventId), soci::use(chatRoomId); + + *session << "INSERT INTO conference_chat_message_event (" + " event_id, from_sip_address_id, to_sip_address_id," + " time, state, direction, imdn_message_id, is_secured" + ") VALUES (" + " :eventId, :localSipAddressId, :remoteSipAddressId," + " :creationTime, :state, :direction, '', :isSecured" + ")", soci::use(eventId), soci::use(localSipAddressId), soci::use(remoteSipAddressId), + soci::use(creationTime), soci::use(state), soci::use(direction), + soci::use(isSecured); + + d->insertContent(eventId, content); + d->insertChatRoomParticipant(chatRoomId, remoteSipAddressId, false); + + if (state != static_cast(ChatMessage::State::Displayed)) + d->insertChatMessageParticipant(eventId, remoteSipAddressId, state); } - lInfo() << "Successful import of legacy messages from: `" << uri << "`."; - } catch (const exception &) { - // Table doesn't exist. + + tr.commit(); + } catch (const exception &e) { + lInfo() << "Failed to import legacy messages from: `" << uri << "`. (" << e.what() << ")"; return false; } - - return true; + lInfo() << "Successful import of legacy messages from: `" << uri << "`."; + } catch (const exception &) { + // Table doesn't exist. + return false; } + return true; +} + // ----------------------------------------------------------------------------- // No backend. // ----------------------------------------------------------------------------- #else - void MainDb::init () {} +void MainDb::init () {} - bool MainDb::addEvent (const shared_ptr &) { - return false; - } +bool MainDb::addEvent (const shared_ptr &) { + return false; +} - bool MainDb::updateEvent (const shared_ptr &) { - return false; - } +bool MainDb::updateEvent (const shared_ptr &) { + return false; +} - bool MainDb::deleteEvent (const shared_ptr &) { - return false; - } +bool MainDb::deleteEvent (const shared_ptr &) { + return false; +} - int MainDb::getEventCount (FilterMask) const { - return 0; - } +int MainDb::getEventCount (FilterMask) const { + return 0; +} - shared_ptr MainDb::getEventFromKey (const MainDbKey &) { - return nullptr; - } +shared_ptr MainDb::getEventFromKey (const MainDbKey &) { + return nullptr; +} - list> MainDb::getConferenceNotifiedEvents ( - const ChatRoomId &, - unsigned int - ) const { - return list>(); - } +list> MainDb::getConferenceNotifiedEvents ( + const ChatRoomId &, + unsigned int +) const { + return list>(); +} - int MainDb::getChatMessageCount (const ChatRoomId &) const { - return 0; - } +int MainDb::getChatMessageCount (const ChatRoomId &) const { + return 0; +} - int MainDb::getUnreadChatMessageCount (const ChatRoomId &) const { - return 0; - } +int MainDb::getUnreadChatMessageCount (const ChatRoomId &) const { + return 0; +} - shared_ptr MainDb::getLastChatMessage (const ChatRoomId &) const { - return nullptr; - } +shared_ptr MainDb::getLastChatMessage (const ChatRoomId &) const { + return nullptr; +} - list> MainDb::findChatMessages (const ChatRoomId &, const string &) const { - return list>(); - } +list> MainDb::findChatMessages (const ChatRoomId &, const string &) const { + return list>(); +} - void MainDb::markChatMessagesAsRead (const ChatRoomId &) const {} +void MainDb::markChatMessagesAsRead (const ChatRoomId &) const {} - list> MainDb::getUnreadChatMessages (const ChatRoomId &) const { - return list>(); - } +list> MainDb::getUnreadChatMessages (const ChatRoomId &) const { + return list>(); +} - list> MainDb::getHistory (const ChatRoomId &, int, FilterMask) const { - return list>(); - } +list> MainDb::getHistory (const ChatRoomId &, int, FilterMask) const { + return list>(); +} - list> MainDb::getHistoryRange (const ChatRoomId &, int, int, FilterMask) const { - return list>(); - } +list> MainDb::getHistoryRange (const ChatRoomId &, int, int, FilterMask) const { + return list>(); +} - int MainDb::getHistorySize (const ChatRoomId &, FilterMask) const { - return 0; - } +int MainDb::getHistorySize (const ChatRoomId &, FilterMask) const { + return 0; +} - list> MainDb::getChatRooms () const { - return list>(); - } +list> MainDb::getChatRooms () const { + return list>(); +} - void MainDb::insertChatRoom (const shared_ptr &) {} +void MainDb::insertChatRoom (const shared_ptr &) {} - void MainDb::deleteChatRoom (const ChatRoomId &) {} +void MainDb::deleteChatRoom (const ChatRoomId &) {} - void MainDb::migrateBasicToClientGroupChatRoom ( - const shared_ptr &, - const shared_ptr & - ) {} +void MainDb::migrateBasicToClientGroupChatRoom ( + const shared_ptr &, + const shared_ptr & +) {} - void MainDb::cleanHistory (const ChatRoomId &, FilterMask) {} +void MainDb::cleanHistory (const ChatRoomId &, FilterMask) {} - bool MainDb::import (Backend, const string &) { - return false; - } +bool MainDb::import (Backend, const string &) { + return false; +} #endif // ifdef SOCI_ENABLED