forked from mirrors/linphone-iphone
464 lines
14 KiB
C++
464 lines
14 KiB
C++
/*
|
|
* chat-room.cpp
|
|
* Copyright (C) 2010-2017 Belledonne Communications SARL
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
|
|
#include "c-wrapper/c-wrapper.h"
|
|
#include "event-log/conference/conference-chat-message-event.h"
|
|
#include "chat/chat-message/chat-message-p.h"
|
|
#include "chat/chat-room/chat-room-p.h"
|
|
#include "chat/notification/imdn.h"
|
|
#include "logger/logger.h"
|
|
#include "core/core-p.h"
|
|
|
|
// =============================================================================
|
|
|
|
using namespace std;
|
|
|
|
LINPHONE_BEGIN_NAMESPACE
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::addTransientEvent (const shared_ptr<EventLog> &log) {
|
|
auto iter = find(transientEvents.begin(), transientEvents.end(), log);
|
|
if (iter == transientEvents.end())
|
|
transientEvents.push_back(log);
|
|
}
|
|
|
|
void ChatRoomPrivate::removeTransientEvent (const shared_ptr<EventLog> &log) {
|
|
auto iter = find(transientEvents.begin(), transientEvents.end(), log);
|
|
if (iter != transientEvents.end()) {
|
|
transientEvents.erase(iter);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::release () {
|
|
isComposingHandler->stopTimers();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::setState (ChatRoom::State newState) {
|
|
if (newState != state) {
|
|
state = newState;
|
|
notifyStateChanged();
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::sendIsComposingNotification () {
|
|
L_Q();
|
|
LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(q->getCore()->getCCore());
|
|
if (linphone_im_notif_policy_get_send_is_composing(policy)) {
|
|
string payload = isComposingHandler->marshal(isComposing);
|
|
if (!payload.empty()) {
|
|
shared_ptr<ChatMessage> msg = createChatMessage(ChatMessage::Direction::Outgoing);
|
|
Content *content = new Content();
|
|
content->setContentType(ContentType::ImIsComposing);
|
|
content->setBody(payload);
|
|
msg->addContent(*content);
|
|
msg->getPrivate()->send();
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
shared_ptr<ChatMessage> ChatRoomPrivate::createChatMessage (ChatMessage::Direction direction) {
|
|
L_Q();
|
|
return shared_ptr<ChatMessage>(new ChatMessage(q->getSharedFromThis(), direction));
|
|
}
|
|
|
|
list<shared_ptr<ChatMessage> > ChatRoomPrivate::findMessages (const string &messageId) const {
|
|
L_Q();
|
|
return q->getCore()->getPrivate()->mainDb->findChatMessages(q->getChatRoomId(), messageId);
|
|
}
|
|
|
|
void ChatRoomPrivate::sendMessage (const shared_ptr<ChatMessage> &msg) {
|
|
L_Q();
|
|
|
|
// TODO: Check direction.
|
|
|
|
ChatMessagePrivate *dChatMessage = msg->getPrivate();
|
|
dChatMessage->setTime(ms_time(0));
|
|
dChatMessage->send();
|
|
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsParticipantAddedCb cb = linphone_chat_room_cbs_get_chat_message_sent(cbs);
|
|
|
|
if (cb) {
|
|
shared_ptr<ConferenceChatMessageEvent> event = static_pointer_cast<ConferenceChatMessageEvent>(
|
|
q->getCore()->getPrivate()->mainDb->getEventFromKey(dChatMessage->dbKey)
|
|
);
|
|
if (!event)
|
|
event = make_shared<ConferenceChatMessageEvent>(msg->getTime(), msg);
|
|
|
|
cb(cr, L_GET_C_BACK_PTR(event));
|
|
}
|
|
|
|
if (isComposing)
|
|
isComposing = false;
|
|
isComposingHandler->stopIdleTimer();
|
|
isComposingHandler->stopRefreshTimer();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
LinphoneReason ChatRoomPrivate::messageReceived (SalOp *op, const SalMessage *salMsg) {
|
|
L_Q();
|
|
|
|
bool increaseMsgCount = true;
|
|
LinphoneReason reason = LinphoneReasonNone;
|
|
shared_ptr<ChatMessage> msg;
|
|
|
|
shared_ptr<Core> core = q->getCore();
|
|
LinphoneCore *cCore = core->getCCore();
|
|
|
|
msg = createChatMessage(
|
|
IdentityAddress(op->get_from()) == q->getLocalAddress()
|
|
? ChatMessage::Direction::Outgoing
|
|
: ChatMessage::Direction::Incoming
|
|
);
|
|
|
|
Content content;
|
|
content.setContentType(salMsg->content_type);
|
|
content.setBody(salMsg->text ? salMsg->text : "");
|
|
msg->setInternalContent(content);
|
|
|
|
msg->getPrivate()->setTime(salMsg->time);
|
|
msg->setImdnMessageId(op->get_call_id());
|
|
|
|
const SalCustomHeader *ch = op->get_recv_custom_header();
|
|
if (ch)
|
|
msg->getPrivate()->setSalCustomHeaders(sal_custom_header_clone(ch));
|
|
|
|
reason = msg->getPrivate()->receive();
|
|
|
|
if (reason == LinphoneReasonNotAcceptable || reason == LinphoneReasonUnknown) {
|
|
/* Return LinphoneReasonNone to avoid flexisip resending us a message we can't decrypt */
|
|
reason = LinphoneReasonNone;
|
|
goto end;
|
|
}
|
|
|
|
if (msg->getPrivate()->getContentType() == ContentType::ImIsComposing) {
|
|
isComposingReceived(msg->getFromAddress(), msg->getPrivate()->getText());
|
|
increaseMsgCount = FALSE;
|
|
if (lp_config_get_int(linphone_core_get_config(cCore), "sip", "deliver_imdn", 0) != 1) {
|
|
goto end;
|
|
}
|
|
} else if (msg->getPrivate()->getContentType() == ContentType::Imdn) {
|
|
imdnReceived(msg->getPrivate()->getText());
|
|
increaseMsgCount = FALSE;
|
|
if (lp_config_get_int(linphone_core_get_config(cCore), "sip", "deliver_imdn", 0) != 1) {
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (increaseMsgCount) {
|
|
/* Mark the message as pending so that if ChatRoom::markAsRead() is called in the
|
|
* ChatRoomPrivate::chatMessageReceived() callback, it will effectively be marked as
|
|
* being read before being stored. */
|
|
pendingMessage = msg;
|
|
}
|
|
|
|
chatMessageReceived(msg);
|
|
|
|
pendingMessage = nullptr;
|
|
|
|
end:
|
|
return reason;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::chatMessageReceived (const shared_ptr<ChatMessage> &msg) {
|
|
L_Q();
|
|
|
|
if ((msg->getPrivate()->getContentType() != ContentType::Imdn) && (msg->getPrivate()->getContentType() != ContentType::ImIsComposing)) {
|
|
onChatMessageReceived(msg);
|
|
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsParticipantAddedCb cb = linphone_chat_room_cbs_get_chat_message_received(cbs);
|
|
shared_ptr<ConferenceChatMessageEvent> event = make_shared<ConferenceChatMessageEvent>(msg->getTime(), msg);
|
|
if (cb) {
|
|
cb(cr, L_GET_C_BACK_PTR(event));
|
|
}
|
|
// Legacy
|
|
notifyChatMessageReceived(msg);
|
|
|
|
const string fromAddress = msg->getFromAddress().asString();
|
|
isComposingHandler->stopRemoteRefreshTimer(fromAddress);
|
|
notifyIsComposingReceived(msg->getFromAddress(), false);
|
|
msg->sendDeliveryNotification(LinphoneReasonNone);
|
|
}
|
|
}
|
|
|
|
void ChatRoomPrivate::imdnReceived (const string &text) {
|
|
L_Q();
|
|
Imdn::parse(*q, text);
|
|
}
|
|
|
|
void ChatRoomPrivate::isComposingReceived (const Address &remoteAddr, const string &text) {
|
|
isComposingHandler->parse(remoteAddr, text);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::notifyChatMessageReceived (const shared_ptr<ChatMessage> &msg) {
|
|
L_Q();
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
if (!msg->getPrivate()->getText().empty()) {
|
|
/* Legacy API */
|
|
LinphoneAddress *fromAddress = linphone_address_new(msg->getFromAddress().asString().c_str());
|
|
linphone_core_notify_text_message_received(
|
|
q->getCore()->getCCore(),
|
|
cr,
|
|
fromAddress,
|
|
msg->getPrivate()->getText().c_str()
|
|
);
|
|
linphone_address_unref(fromAddress);
|
|
}
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsMessageReceivedCb cb = linphone_chat_room_cbs_get_message_received(cbs);
|
|
if (cb)
|
|
cb(cr, L_GET_C_BACK_PTR(msg));
|
|
linphone_core_notify_message_received(q->getCore()->getCCore(), cr, L_GET_C_BACK_PTR(msg));
|
|
}
|
|
|
|
void ChatRoomPrivate::notifyIsComposingReceived (const Address &remoteAddr, bool isComposing) {
|
|
L_Q();
|
|
|
|
if (isComposing)
|
|
remoteIsComposing.push_back(remoteAddr);
|
|
else
|
|
remoteIsComposing.remove(remoteAddr);
|
|
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsIsComposingReceivedCb cb = linphone_chat_room_cbs_get_is_composing_received(cbs);
|
|
if (cb) {
|
|
LinphoneAddress *lAddr = linphone_address_new(remoteAddr.asString().c_str());
|
|
cb(cr, lAddr, !!isComposing);
|
|
linphone_address_unref(lAddr);
|
|
}
|
|
// Legacy notification
|
|
linphone_core_notify_is_composing_received(q->getCore()->getCCore(), cr);
|
|
}
|
|
|
|
void ChatRoomPrivate::notifyStateChanged () {
|
|
L_Q();
|
|
linphone_core_notify_chat_room_state_changed(q->getCore()->getCCore(), L_GET_C_BACK_PTR(q), (LinphoneChatRoomState)state);
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsStateChangedCb cb = linphone_chat_room_cbs_get_state_changed(cbs);
|
|
if (cb)
|
|
cb(cr, (LinphoneChatRoomState)state);
|
|
}
|
|
|
|
void ChatRoomPrivate::notifyUndecryptableMessageReceived (const shared_ptr<ChatMessage> &msg) {
|
|
L_Q();
|
|
LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q);
|
|
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_callbacks(cr);
|
|
LinphoneChatRoomCbsUndecryptableMessageReceivedCb cb = linphone_chat_room_cbs_get_undecryptable_message_received(cbs);
|
|
if (cb)
|
|
cb(cr, L_GET_C_BACK_PTR(msg));
|
|
linphone_core_notify_message_received_unable_decrypt(q->getCore()->getCCore(), cr, L_GET_C_BACK_PTR(msg));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoomPrivate::onIsComposingStateChanged (bool isComposing) {
|
|
this->isComposing = isComposing;
|
|
sendIsComposingNotification();
|
|
}
|
|
|
|
void ChatRoomPrivate::onIsRemoteComposingStateChanged (const Address &remoteAddr, bool isComposing) {
|
|
notifyIsComposingReceived(remoteAddr, isComposing);
|
|
}
|
|
|
|
void ChatRoomPrivate::onIsComposingRefreshNeeded () {
|
|
sendIsComposingNotification();
|
|
}
|
|
|
|
// =============================================================================
|
|
|
|
ChatRoom::ChatRoom (ChatRoomPrivate &p, const shared_ptr<Core> &core, const ChatRoomId &chatRoomId) :
|
|
Object(p), CoreAccessor(core) {
|
|
L_D();
|
|
|
|
d->chatRoomId = chatRoomId;
|
|
d->isComposingHandler.reset(new IsComposing(core->getCCore(), d));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const ChatRoomId &ChatRoom::getChatRoomId () const {
|
|
L_D();
|
|
return d->chatRoomId;
|
|
}
|
|
|
|
const IdentityAddress &ChatRoom::getPeerAddress () const {
|
|
L_D();
|
|
return d->chatRoomId.getPeerAddress();
|
|
}
|
|
|
|
const IdentityAddress &ChatRoom::getLocalAddress () const {
|
|
L_D();
|
|
return d->chatRoomId.getLocalAddress();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
time_t ChatRoom::getCreationTime () const {
|
|
L_D();
|
|
return d->creationTime;
|
|
}
|
|
|
|
time_t ChatRoom::getLastUpdateTime () const {
|
|
L_D();
|
|
return d->lastUpdateTime;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
list<shared_ptr<EventLog>> ChatRoom::getHistory (int nLast) {
|
|
return getCore()->getPrivate()->mainDb->getHistory(getChatRoomId(), nLast);
|
|
}
|
|
|
|
list<shared_ptr<EventLog>> ChatRoom::getHistoryRange (int begin, int end) {
|
|
return getCore()->getPrivate()->mainDb->getHistoryRange(getChatRoomId(), begin, end);
|
|
}
|
|
|
|
int ChatRoom::getHistorySize () {
|
|
return getCore()->getPrivate()->mainDb->getHistorySize(getChatRoomId());
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::getLastChatMessageInHistory() const {
|
|
return getCore()->getPrivate()->mainDb->getLastChatMessage(getChatRoomId());
|
|
}
|
|
|
|
void ChatRoom::deleteHistory () {
|
|
getCore()->getPrivate()->mainDb->cleanHistory(getChatRoomId());
|
|
}
|
|
|
|
int ChatRoom::getChatMessageCount () {
|
|
return getCore()->getPrivate()->mainDb->getChatMessageCount(getChatRoomId());
|
|
}
|
|
|
|
int ChatRoom::getUnreadChatMessageCount () {
|
|
return getCore()->getPrivate()->mainDb->getUnreadChatMessageCount(getChatRoomId());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void ChatRoom::compose () {
|
|
L_D();
|
|
if (!d->isComposing) {
|
|
d->isComposing = true;
|
|
d->sendIsComposingNotification();
|
|
d->isComposingHandler->startRefreshTimer();
|
|
}
|
|
d->isComposingHandler->startIdleTimer();
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::createFileTransferMessage (const LinphoneContent *initialContent) {
|
|
shared_ptr<ChatMessage> chatMessage = createMessage();
|
|
chatMessage->getPrivate()->setFileTransferInformation(initialContent);
|
|
return chatMessage;
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::createMessage (const string &message) {
|
|
shared_ptr<ChatMessage> chatMessage = createMessage();
|
|
Content *content = new Content();
|
|
content->setContentType(ContentType::PlainText);
|
|
content->setBody(message);
|
|
chatMessage->addContent(*content);
|
|
return chatMessage;
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::createMessage () {
|
|
L_D();
|
|
return d->createChatMessage(ChatMessage::Direction::Outgoing);
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::findMessage (const string &messageId) {
|
|
L_D();
|
|
shared_ptr<ChatMessage> cm = nullptr;
|
|
list<shared_ptr<ChatMessage> > l = d->findMessages(messageId);
|
|
if (!l.empty()) {
|
|
cm = l.front();
|
|
}
|
|
return cm;
|
|
}
|
|
|
|
shared_ptr<ChatMessage> ChatRoom::findMessageWithDirection (const string &messageId, ChatMessage::Direction direction) {
|
|
L_D();
|
|
shared_ptr<ChatMessage> ret = nullptr;
|
|
list<shared_ptr<ChatMessage> > l = d->findMessages(messageId);
|
|
for (auto &message : l) {
|
|
if (message->getDirection() == direction) {
|
|
ret = message;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ChatRoom::isRemoteComposing () const {
|
|
L_D();
|
|
return d->remoteIsComposing.size() > 0;
|
|
}
|
|
|
|
std::list<Address> ChatRoom::getComposingAddresses () const {
|
|
L_D();
|
|
return d->remoteIsComposing;
|
|
}
|
|
|
|
void ChatRoom::markAsRead () {
|
|
L_D();
|
|
|
|
if (getUnreadChatMessageCount() == 0)
|
|
return;
|
|
|
|
CorePrivate *dCore = getCore()->getPrivate();
|
|
const string peerAddress = getPeerAddress().asString();
|
|
for (auto &chatMessage : dCore->mainDb->getUnreadChatMessages(d->chatRoomId))
|
|
chatMessage->sendDisplayNotification();
|
|
|
|
dCore->mainDb->markChatMessagesAsRead(d->chatRoomId);
|
|
|
|
if (d->pendingMessage) {
|
|
d->pendingMessage->updateState(ChatMessage::State::Displayed);
|
|
d->pendingMessage->sendDisplayNotification();
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
ChatRoom::State ChatRoom::getState () const {
|
|
L_D();
|
|
return d->state;
|
|
}
|
|
|
|
LINPHONE_END_NAMESPACE
|