diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index 4c957ed71..1c7e8d854 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -58,7 +58,7 @@ public: void setDirection (ChatMessage::Direction dir); - void setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState); + void setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState, time_t stateChangeTime); std::list> getParticipantsInState (const ChatMessage::State state) const; void setState (ChatMessage::State newState, bool force = false); diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index 90eb244c2..ae75b0525 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -70,7 +70,7 @@ void ChatMessagePrivate::setIsReadOnly (bool readOnly) { isReadOnly = readOnly; } -void ChatMessagePrivate::setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState) { +void ChatMessagePrivate::setParticipantState (const IdentityAddress &participantAddress, ChatMessage::State newState, time_t stateChangeTime) { L_Q(); if (!(q->getChatRoom()->getCapabilities() & AbstractChatRoom::Capabilities::Conference) @@ -93,7 +93,7 @@ void ChatMessagePrivate::setParticipantState (const IdentityAddress &participant lInfo() << "Chat message " << this << ": moving participant '" << participantAddress.asString() << "' state to " << Utils::toString(newState); - mainDb->setChatMessageParticipantState(eventLog, participantAddress, newState); + mainDb->setChatMessageParticipantState(eventLog, participantAddress, newState, stateChangeTime); list states = mainDb->getChatMessageParticipantStates(eventLog); size_t nbDisplayedStates = 0; diff --git a/src/chat/notification/imdn.cpp b/src/chat/notification/imdn.cpp index 817ae3a2a..83ffa3b8d 100644 --- a/src/chat/notification/imdn.cpp +++ b/src/chat/notification/imdn.cpp @@ -170,6 +170,7 @@ void Imdn::parse (const shared_ptr &imdnMessage, xmlparsing_context if (!cm) { lWarning() << "Received IMDN for unknown message " << messageIdStr; } else { + time_t imdnTime = imdnMessage->getTime(); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(cr->getCore()->getCCore()); snprintf(xpathStr, sizeof(xpathStr), "%s[1]/imdn:delivery-notification/imdn:status", imdnPrefix.c_str()); xmlXPathObjectPtr deliveryStatusObject = linphone_get_xml_xpath_object_for_node_list(xmlCtx, xpathStr); @@ -180,9 +181,9 @@ void Imdn::parse (const shared_ptr &imdnMessage, xmlparsing_context xmlNodePtr node = deliveryStatusObject->nodesetval->nodeTab[0]; if (node->children && node->children->name) { if (strcmp((const char *)node->children->name, "delivered") == 0) { - cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::DeliveredToUser); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::DeliveredToUser, imdnTime); } else if (strcmp((const char *)node->children->name, "error") == 0) { - cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::NotDelivered); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::NotDelivered, imdnTime); } } } @@ -193,7 +194,7 @@ void Imdn::parse (const shared_ptr &imdnMessage, xmlparsing_context xmlNodePtr node = displayStatusObject->nodesetval->nodeTab[0]; if (node->children && node->children->name) { if (strcmp((const char *)node->children->name, "displayed") == 0) { - cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::Displayed); + cm->getPrivate()->setParticipantState(participantAddress, ChatMessage::State::Displayed, imdnTime); } } } diff --git a/src/db/main-db-p.h b/src/db/main-db-p.h index 8a8a52ad1..bd2076407 100644 --- a/src/db/main-db-p.h +++ b/src/db/main-db-p.h @@ -61,7 +61,7 @@ private: long long insertChatRoom (const std::shared_ptr &chatRoom); long long insertChatRoomParticipant (long long chatRoomId, long long participantSipAddressId, bool isAdmin); void insertChatRoomParticipantDevice (long long participantId, long long participantDeviceSipAddressId); - void insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state); + void insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state, time_t stateChangeTime); long long selectSipAddressId (const std::string &sipAddress) const; long long selectChatRoomId (long long peerSipAddressId, long long localSipAddressId) const; diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index e6cfd375d..a3d4b9edd 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -47,7 +47,7 @@ using namespace std; LINPHONE_BEGIN_NAMESPACE namespace { - constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 1); + constexpr unsigned int ModuleVersionEvents = makeVersion(1, 0, 2); constexpr unsigned int ModuleVersionFriends = makeVersion(1, 0, 0); constexpr unsigned int ModuleVersionLegacyFriendsImport = makeVersion(1, 0, 0); constexpr unsigned int ModuleVersionLegacyHistoryImport = makeVersion(1, 0, 0); @@ -408,12 +408,12 @@ void MainDbPrivate::insertChatRoomParticipantDevice ( soci::use(participantId), soci::use(participantDeviceSipAddressId); } -void MainDbPrivate::insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state) { - if (state != static_cast(ChatMessage::State::Displayed)) - *dbSession.getBackendSession() << - "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state)" - " VALUES (:chatMessageId, :sipAddressId, :state)", - soci::use(chatMessageId), soci::use(sipAddressId), soci::use(state); +void MainDbPrivate::insertChatMessageParticipant (long long chatMessageId, long long sipAddressId, int state, time_t stateChangeTime) { + const tm &stateChangeTm = Utils::getTimeTAsTm(stateChangeTime); + *dbSession.getBackendSession() << + "INSERT INTO chat_message_participant (event_id, participant_sip_address_id, state, state_change_time)" + " VALUES (:chatMessageId, :sipAddressId, :state, :stateChangeTm)", + soci::use(chatMessageId), soci::use(sipAddressId), soci::use(state), soci::use(stateChangeTm); } // ----------------------------------------------------------------------------- @@ -744,7 +744,7 @@ long long MainDbPrivate::insertConferenceChatMessageEvent (const shared_ptrgetChatRoom()->getParticipants()) { const long long &participantSipAddressId = selectSipAddressId(participant->getAddress().asString()); - insertChatMessageParticipant(eventId, participantSipAddressId, state); + insertChatMessageParticipant(eventId, participantSipAddressId, state, chatMessage->getTime()); } return eventId; @@ -982,6 +982,11 @@ void MainDbPrivate::updateSchema () { if (version < makeVersion(1, 0, 1)) *session << "ALTER TABLE chat_room_participant_device ADD COLUMN state TINYINT UNSIGNED DEFAULT 0"; + if (version < makeVersion(1, 0, 2)) { + *session << "DROP TRIGGER IF EXISTS chat_message_participant_deleter"; + *session << "ALTER TABLE chat_message_participant ADD COLUMN state_change_time" + + dbSession.timestampType() + " NOT NULL DEFAULT " + dbSession.currentTimestamp(); + } } // ----------------------------------------------------------------------------- @@ -1227,9 +1232,7 @@ void MainDbPrivate::importLegacyHistory (DbSession &inDbSession) { if (content) insertContent(eventId, *content); insertChatRoomParticipant(chatRoomId, remoteSipAddressId, false); - - if (state != int(ChatMessage::State::Displayed)) - insertChatMessageParticipant(eventId, remoteSipAddressId, state); + insertChatMessageParticipant(eventId, remoteSipAddressId, state, std::time(nullptr)); } tr.commit(); lInfo() << "Successful import of legacy messages."; @@ -1594,26 +1597,6 @@ void MainDb::init () { " version INT UNSIGNED NOT NULL" ") " + charset; - if (getBackend() == Backend::Mysql) { - *session << - "DROP TRIGGER IF EXISTS chat_message_participant_deleter"; - *session << - "CREATE TRIGGER chat_message_participant_deleter" - " AFTER UPDATE ON conference_chat_message_event FOR EACH ROW" - " BEGIN" - " IF NEW.state = " + Utils::toString(int(ChatMessage::State::Displayed)) + " THEN" - " DELETE FROM chat_message_participant WHERE event_id = NEW.event_id;" - " END IF;" - " END "; - } else - *session << - "CREATE TRIGGER IF NOT EXISTS chat_message_participant_deleter" - " AFTER UPDATE OF state ON conference_chat_message_event FOR EACH ROW" - " WHEN NEW.state = " + Utils::toString(int(ChatMessage::State::Displayed)) + - " BEGIN" - " DELETE FROM chat_message_participant WHERE event_id = NEW.event_id;" - " END "; - d->updateSchema(); d->updateModuleVersion("events", ModuleVersionEvents); @@ -2018,7 +2001,8 @@ ChatMessage::State MainDb::getChatMessageParticipantState ( void MainDb::setChatMessageParticipantState ( const shared_ptr &eventLog, const IdentityAddress &participantAddress, - ChatMessage::State state + ChatMessage::State state, + time_t stateChangeTime ) { L_DB_TRANSACTION { L_D(); @@ -2028,10 +2012,12 @@ void MainDb::setChatMessageParticipantState ( const long long &eventId = dEventKey->storageId; const long long &participantSipAddressId = d->selectSipAddressId(participantAddress.asString()); int stateInt = static_cast(state); + const tm &stateChangeTm = Utils::getTimeTAsTm(stateChangeTime); - *d->dbSession.getBackendSession() << "UPDATE chat_message_participant SET state = :state" + *d->dbSession.getBackendSession() << "UPDATE chat_message_participant SET state = :state," + " state_change_time = :stateChangeTm" " WHERE event_id = :eventId AND participant_sip_address_id = :participantSipAddressId", - soci::use(stateInt), soci::use(eventId), soci::use(participantSipAddressId); + soci::use(stateInt), soci::use(stateChangeTm), soci::use(eventId), soci::use(participantSipAddressId); tr.commit(); }; diff --git a/src/db/main-db.h b/src/db/main-db.h index ae46a5e3e..d7ce028e0 100644 --- a/src/db/main-db.h +++ b/src/db/main-db.h @@ -99,7 +99,8 @@ public: void setChatMessageParticipantState ( const std::shared_ptr &eventLog, const IdentityAddress &participantAddress, - ChatMessage::State state + ChatMessage::State state, + time_t stateChangeTime ); std::shared_ptr getLastChatMessage (const ChatRoomId &chatRoomId) const; diff --git a/src/db/session/db-session.cpp b/src/db/session/db-session.cpp index e9fbc4450..b78ae04e7 100644 --- a/src/db/session/db-session.cpp +++ b/src/db/session/db-session.cpp @@ -124,6 +124,31 @@ string DbSession::varcharPrimaryKeyStr (int length) const { return ""; } +string DbSession::currentTimestamp () const { + L_D(); + + switch (d->backend) { + case DbSessionPrivate::Backend::Mysql: + return " CURRENT_TIMESTAMP"; + case DbSessionPrivate::Backend::Sqlite3: + // Ugly hack but Sqlite3 does not allow table alteration where we add a date column using a default value + // of CURRENT_TIMESTAMP + { + const tm &now = Utils::getTimeTAsTm(std::time(nullptr)); + const size_t bufSize = 22; + char buffer[bufSize]; + snprintf(buffer, bufSize, "'%d-%02d-%02d %02d:%02d:%02d'", + now.tm_year + 1900, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec); + return buffer; + } + case DbSessionPrivate::Backend::None: + return ""; + } + + L_ASSERT(false); + return ""; +} + string DbSession::timestampType () const { L_D(); diff --git a/src/db/session/db-session.h b/src/db/session/db-session.h index 0189bf997..d05c47e18 100644 --- a/src/db/session/db-session.h +++ b/src/db/session/db-session.h @@ -47,6 +47,7 @@ public: std::string primaryKeyRefStr (const std::string &type = "INT") const; std::string varcharPrimaryKeyStr (int length) const; + std::string currentTimestamp () const; std::string timestampType () const; std::string noLimitValue () const;