/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). * * 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 3 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, see . */ #include "ChatRoomModel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "app/App.hpp" #include "app/paths/Paths.hpp" #include "app/providers/ThumbnailProvider.hpp" #include "components/chat-events/ChatCallModel.hpp" #include "components/chat-events/ChatEvent.hpp" #include "components/chat-events/ChatMessageModel.hpp" #include "components/chat-events/ChatNoticeModel.hpp" #include "components/contact/ContactModel.hpp" #include "components/contact/VcardModel.hpp" #include "components/contacts/ContactsListModel.hpp" #include "components/core/CoreHandlers.hpp" #include "components/core/CoreManager.hpp" #include "components/notifier/Notifier.hpp" #include "components/settings/AccountSettingsModel.hpp" #include "components/settings/SettingsModel.hpp" #include "components/participant/ParticipantModel.hpp" #include "components/participant/ParticipantListModel.hpp" #include "components/presence/Presence.hpp" #include "components/timeline/TimelineModel.hpp" #include "components/timeline/TimelineListModel.hpp" #include "components/core/event-count-notifier/AbstractEventCountNotifier.hpp" #include "utils/QExifImageHeader.hpp" #include "utils/Utils.hpp" #include "utils/Constants.hpp" #include "utils/LinphoneEnums.hpp" // ============================================================================= using namespace std; // ----------------------------------------------------------------------------- ChatRoomModelListener::ChatRoomModelListener(ChatRoomModel * model, QObject* parent) : QObject(parent){ connect(this, &ChatRoomModelListener::isComposingReceived, model, &ChatRoomModel::onIsComposingReceived); connect(this, &ChatRoomModelListener::messageReceived, model, &ChatRoomModel::onMessageReceived); connect(this, &ChatRoomModelListener::newEvent, model, &ChatRoomModel::onNewEvent); connect(this, &ChatRoomModelListener::chatMessageReceived, model, &ChatRoomModel::onChatMessageReceived); connect(this, &ChatRoomModelListener::chatMessageSending, model, &ChatRoomModel::onChatMessageSending); connect(this, &ChatRoomModelListener::chatMessageSent, model, &ChatRoomModel::onChatMessageSent); connect(this, &ChatRoomModelListener::participantAdded, model, &ChatRoomModel::onParticipantAdded); connect(this, &ChatRoomModelListener::participantRemoved, model, &ChatRoomModel::onParticipantRemoved); connect(this, &ChatRoomModelListener::participantAdminStatusChanged, model, &ChatRoomModel::onParticipantAdminStatusChanged); connect(this, &ChatRoomModelListener::stateChanged, model, &ChatRoomModel::onStateChanged); connect(this, &ChatRoomModelListener::securityEvent, model, &ChatRoomModel::onSecurityEvent); connect(this, &ChatRoomModelListener::subjectChanged, model, &ChatRoomModel::onSubjectChanged); connect(this, &ChatRoomModelListener::undecryptableMessageReceived, model, &ChatRoomModel::onUndecryptableMessageReceived); connect(this, &ChatRoomModelListener::participantDeviceAdded, model, &ChatRoomModel::onParticipantDeviceAdded); connect(this, &ChatRoomModelListener::participantDeviceRemoved, model, &ChatRoomModel::onParticipantDeviceRemoved); connect(this, &ChatRoomModelListener::conferenceJoined, model, &ChatRoomModel::onConferenceJoined); connect(this, &ChatRoomModelListener::conferenceLeft, model, &ChatRoomModel::onConferenceLeft); connect(this, &ChatRoomModelListener::ephemeralEvent, model, &ChatRoomModel::onEphemeralEvent); connect(this, &ChatRoomModelListener::ephemeralMessageTimerStarted, model, &ChatRoomModel::onEphemeralMessageTimerStarted); connect(this, &ChatRoomModelListener::ephemeralMessageDeleted, model, &ChatRoomModel::onEphemeralMessageDeleted); connect(this, &ChatRoomModelListener::conferenceAddressGeneration, model, &ChatRoomModel::onConferenceAddressGeneration); connect(this, &ChatRoomModelListener::participantRegistrationSubscriptionRequested, model, &ChatRoomModel::onParticipantRegistrationSubscriptionRequested); connect(this, &ChatRoomModelListener::participantRegistrationUnsubscriptionRequested, model, &ChatRoomModel::onParticipantRegistrationUnsubscriptionRequested); connect(this, &ChatRoomModelListener::chatMessageShouldBeStored, model, &ChatRoomModel::onChatMessageShouldBeStored); connect(this, &ChatRoomModelListener::chatMessageParticipantImdnStateChanged, model, &ChatRoomModel::onChatMessageParticipantImdnStateChanged); } void ChatRoomModelListener::onIsComposingReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & remoteAddress, bool isComposing){ emit isComposingReceived(chatRoom, remoteAddress, isComposing); } void ChatRoomModelListener::onMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ emit messageReceived(chatRoom, message); } void ChatRoomModelListener::onNewEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit newEvent(chatRoom, eventLog); } void ChatRoomModelListener::onChatMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit chatMessageReceived(chatRoom, eventLog); } void ChatRoomModelListener::onChatMessageSending(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit chatMessageSending(chatRoom, eventLog); } void ChatRoomModelListener::onChatMessageSent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit chatMessageSent(chatRoom, eventLog); } void ChatRoomModelListener::onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantAdded(chatRoom, eventLog); } void ChatRoomModelListener::onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantRemoved(chatRoom, eventLog); } void ChatRoomModelListener::onParticipantAdminStatusChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantAdminStatusChanged(chatRoom, eventLog); } void ChatRoomModelListener::onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){ emit stateChanged(chatRoom, newState); } void ChatRoomModelListener::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit securityEvent(chatRoom, eventLog); } void ChatRoomModelListener::onSubjectChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit subjectChanged(chatRoom, eventLog); } void ChatRoomModelListener::onUndecryptableMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ emit undecryptableMessageReceived(chatRoom, message); } void ChatRoomModelListener::onParticipantDeviceAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantDeviceAdded(chatRoom, eventLog); } void ChatRoomModelListener::onParticipantDeviceRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit participantDeviceRemoved(chatRoom, eventLog); } void ChatRoomModelListener::onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit conferenceJoined(chatRoom, eventLog); } void ChatRoomModelListener::onConferenceLeft(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit conferenceLeft(chatRoom, eventLog); } void ChatRoomModelListener::onEphemeralEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit ephemeralEvent(chatRoom, eventLog); } void ChatRoomModelListener::onEphemeralMessageTimerStarted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit ephemeralMessageTimerStarted(chatRoom, eventLog); } void ChatRoomModelListener::onEphemeralMessageDeleted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ emit ephemeralMessageDeleted(chatRoom, eventLog); } void ChatRoomModelListener::onConferenceAddressGeneration(const std::shared_ptr & chatRoom){ emit conferenceAddressGeneration(chatRoom); } void ChatRoomModelListener::onParticipantRegistrationSubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ emit participantRegistrationSubscriptionRequested(chatRoom, participantAddress); } void ChatRoomModelListener::onParticipantRegistrationUnsubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ emit participantRegistrationUnsubscriptionRequested(chatRoom, participantAddress); } void ChatRoomModelListener::onChatMessageShouldBeStored(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ emit chatMessageShouldBeStored(chatRoom, message); } void ChatRoomModelListener::onChatMessageParticipantImdnStateChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & message, const std::shared_ptr & state){ emit chatMessageParticipantImdnStateChanged(chatRoom, message, state); } // ----------------------------------------------------------------------------- std::shared_ptr ChatRoomModel::create(std::shared_ptr chatRoom){ std::shared_ptr model = std::make_shared(chatRoom); if(model){ model->mSelf = model; //chatRoom->addListener(model); return model; }else return nullptr; } ChatRoomModel::ChatRoomModel (std::shared_ptr chatRoom, QObject * parent) : QAbstractListModel(parent){ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE CoreManager *coreManager = CoreManager::getInstance(); mCoreHandlers = coreManager->getHandlers(); mChatRoom = chatRoom; mChatRoomModelListener = std::make_shared(this, parent); mChatRoom->addListener(mChatRoomModelListener); // Get Max updatetime from chat room and last call event auto callHistory = CoreManager::getInstance()->getCore()->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress()); if(callHistory.size() > 0){ auto callDate = callHistory.front()->getStartDate(); if( callHistory.front()->getStatus() == linphone::Call::Status::Success ) callDate += callHistory.front()->getDuration(); setLastUpdateTime(QDateTime::fromMSecsSinceEpoch(max(mChatRoom->getLastUpdateTime(), callDate )*1000)); }else setLastUpdateTime(QDateTime::fromMSecsSinceEpoch(mChatRoom->getLastUpdateTime()*1000)); setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); setMissedCallsCount(0); // Get messages. mEntries.clear(); QElapsedTimer timer; timer.start(); CoreHandlers *coreHandlers = mCoreHandlers.get(); //QObject::connect(coreHandlers, &CoreHandlers::messageReceived, this, &ChatRoomModel::handleMessageReceived); QObject::connect(coreHandlers, &CoreHandlers::callCreated, this, &ChatRoomModel::handleCallCreated); QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &ChatRoomModel::handleCallStateChanged); QObject::connect(coreHandlers, &CoreHandlers::presenceStatusReceived, this, &ChatRoomModel::handlePresenceStatusReceived); //QObject::connect(coreHandlers, &CoreHandlers::isComposingChanged, this, &ChatRoomModel::handleIsComposingChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactAdded, this, &ChatRoomModel::fullPeerAddressChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactAdded, this, &ChatRoomModel::avatarChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactRemoved, this, &ChatRoomModel::fullPeerAddressChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactRemoved, this, &ChatRoomModel::avatarChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactUpdated, this, &ChatRoomModel::fullPeerAddressChanged); QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactUpdated, this, &ChatRoomModel::avatarChanged); connect(this, &ChatRoomModel::fullPeerAddressChanged, this, &ChatRoomModel::usernameChanged); //QObject::connect(this, &ChatRoomModel::messageCountReset, coreManager, &CoreManager::eventCountChanged ); if(mChatRoom){ mParticipantListModel = std::make_shared(this); connect(mParticipantListModel.get(), &ParticipantListModel::participantsChanged, this, &ChatRoomModel::fullPeerAddressChanged); auto participants = mChatRoom->getParticipants(); for(auto participant : participants){ auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString((participant)->getAddress()->asString())); if(contact) { connect(contact, &ContactModel::contactUpdated, this, &ChatRoomModel::fullPeerAddressChanged); } } }else mParticipantListModel = nullptr; } ChatRoomModel::~ChatRoomModel () { mParticipantListModel = nullptr; if(mChatRoom ){ mChatRoom->removeListener(mChatRoomModelListener); if(mDeleteChatRoom){ mDeleteChatRoom = false; if(CoreManager::getInstance()->getCore() ){ auto participants = mChatRoom->getParticipants(); std::list> participantsAddress; for(auto p : participants) participantsAddress.push_back(p->getAddress()->clone()); auto internalChatRoom = CoreManager::getInstance()->getCore()->searchChatRoom(mChatRoom->getCurrentParams(), mChatRoom->getLocalAddress(), mChatRoom->getPeerAddress(), participantsAddress); if( internalChatRoom) CoreManager::getInstance()->getCore()->deleteChatRoom(internalChatRoom); } } } mChatRoom = nullptr; } QHash ChatRoomModel::roleNames () const { QHash roles; roles[Roles::ChatEntry] = "$chatEntry"; roles[Roles::SectionDate] = "$sectionDate"; return roles; } int ChatRoomModel::rowCount (const QModelIndex &) const { return mEntries.count(); } QVariant ChatRoomModel::data (const QModelIndex &index, int role) const { int row = index.row(); if (!index.isValid() || row < 0 || row >= mEntries.count()) return QVariant(); switch (role) { case Roles::ChatEntry: { ChatEvent * ce = mEntries[row].get(); if( ce->mType == EntryType::MessageEntry) return QVariant::fromValue(dynamic_cast(ce)); else if( ce->mType == EntryType::NoticeEntry) return QVariant::fromValue(dynamic_cast(ce)); else if( ce->mType == EntryType::CallEntry) return QVariant::fromValue(dynamic_cast(ce)); else return QVariant(); } case Roles::SectionDate: return QVariant::fromValue(mEntries[row]->mTimestamp.date()); } return QVariant(); } bool ChatRoomModel::removeRow (int row, const QModelIndex &) { return removeRows(row, 1); } bool ChatRoomModel::removeRows (int row, int count, const QModelIndex &parent) { int limit = row + count - 1; if (row < 0 || count < 0 || limit >= mEntries.count()) return false; beginRemoveRows(parent, row, limit); for (int i = 0; i < count; ++i) { mEntries[row]->deleteEvent(); mEntries.removeAt(row); } endRemoveRows(); if (mEntries.count() == 0) emit allEntriesRemoved(mSelf.lock()); else if (limit == mEntries.count()) emit lastEntryRemoved(); emit focused();// Removing rows is like having focus. Don't wait asynchronous events. return true; } void ChatRoomModel::removeAllEntries () { qInfo() << QStringLiteral("Removing all entries of: (%1, %2).") .arg(getPeerAddress()).arg(getLocalAddress()); auto core = CoreManager::getInstance()->getCore(); bool standardChatEnabled = CoreManager::getInstance()->getSettingsModel()->getStandardChatEnabled(); beginResetModel(); mEntries.clear(); mChatRoom->deleteHistory(); if( isOneToOne() && // Remove calls only if chat room is one-one and not secure (if available) ( !standardChatEnabled || !isSecure()) ) { auto callLogs = core->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress()); for(auto callLog : callLogs) core->removeCallLog(callLog); } endResetModel(); emit allEntriesRemoved(mSelf.lock()); emit focused();// Removing all entries is like having focus. Don't wait asynchronous events. } void ChatRoomModel::removeEntry(ChatEvent* entry){ auto it = mEntries.begin(); while(it != mEntries.end() && (*it).get() != entry) ++it; if( it != mEntries.end() ){ int row = it - mEntries.begin(); //mEntries.indexOf(entry); if(row >=0) removeRow(row); } } void ChatRoomModel::emitFullPeerAddressChanged(){ emit fullPeerAddressChanged(); } //-------------------------------------------------------------------------------------------- QString ChatRoomModel::getPeerAddress () const { return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asStringUriOnly()) : ""; } QString ChatRoomModel::getLocalAddress () const { if(!mChatRoom) return ""; else { auto localAddress = mChatRoom->getLocalAddress()->clone(); localAddress->clean(); return Utils::coreStringToAppString( localAddress->asStringUriOnly() ); } } QString ChatRoomModel::getFullPeerAddress () const { return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asString()) : ""; } QString ChatRoomModel::getFullLocalAddress () const { return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getLocalAddress()->asString()) : ""; } QString ChatRoomModel::getConferenceAddress () const { if(!mChatRoom) return ""; else { auto address = mChatRoom->getConferenceAddress(); return address?Utils::coreStringToAppString(address->asString()):""; } } QString ChatRoomModel::getSubject () const { return mChatRoom ? QString::fromStdString(mChatRoom->getSubject()) : ""; // in UTF8 } QString ChatRoomModel::getUsername () const { QString username; if( !mChatRoom) return ""; if( !isOneToOne()) username = QString::fromStdString(mChatRoom->getSubject()); if(username != "") return username; if( mChatRoom->getNbParticipants() == 1 ) { auto call = mChatRoom->getCall(); if(call) username = Utils::getDisplayName(call->getRemoteAddress()); if(username != "") return username; } if( mChatRoom->getNbParticipants() >= 1) username = mParticipantListModel->displayNamesToString(); if(username != "") return username; if(haveEncryption() || isGroupEnabled()) return "";// Wait for more info username = Utils::getDisplayName(mChatRoom->getPeerAddress()); if(username != "") return username; return Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asStringUriOnly()); } QString ChatRoomModel::getAvatar () const { if( mChatRoom && mChatRoom->getNbParticipants() == 1){ auto participants = mChatRoom->getParticipants(); auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString((*participants.begin())->getAddress()->asString())); if(contact) return contact->getVcardModel()->getAvatar(); } return ""; } int ChatRoomModel::getPresenceStatus() const { if( mChatRoom && mChatRoom->getNbParticipants() == 1 && !isGroupEnabled()){ auto participants = mChatRoom->getParticipants(); auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString((*participants.begin())->getAddress()->asString())); if(contact) { return contact->getPresenceLevel(); } else return -1; }else return -1; } ParticipantListModel* ChatRoomModel::getParticipants() const{ return mParticipantListModel.get(); } int ChatRoomModel::getState() const { return mChatRoom ? (int)mChatRoom->getState() : 0; } bool ChatRoomModel::hasBeenLeft() const{ return mChatRoom && mChatRoom->hasBeenLeft(); } bool ChatRoomModel::isEphemeralEnabled() const{ return mChatRoom && mChatRoom->ephemeralEnabled(); } long ChatRoomModel::getEphemeralLifetime() const{ return mChatRoom ? mChatRoom->getEphemeralLifetime() : 0; } bool ChatRoomModel::canBeEphemeral(){ return isConference(); } bool ChatRoomModel::haveEncryption() const{ return mChatRoom && mChatRoom->getCurrentParams()->getEncryptionBackend() != linphone::ChatRoomEncryptionBackend::None; } bool ChatRoomModel::isSecure() const{ return mChatRoom && (mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Encrypted || mChatRoom->getSecurityLevel() == linphone::ChatRoomSecurityLevel::Safe); } int ChatRoomModel::getSecurityLevel() const{ return mChatRoom ? (int)mChatRoom->getSecurityLevel() : 0; } bool ChatRoomModel::isGroupEnabled() const{ return mChatRoom && mChatRoom->getCurrentParams()->groupEnabled(); } bool ChatRoomModel::isConference() const{ return mChatRoom && mChatRoom->hasCapability((int)linphone::ChatRoomCapabilities::Conference); } bool ChatRoomModel::isOneToOne() const{ return mChatRoom && mChatRoom->hasCapability((int)linphone::ChatRoomCapabilities::OneToOne); } bool ChatRoomModel::isMeAdmin() const{ return mChatRoom->getMe()->isAdmin(); } bool ChatRoomModel::isCurrentProxy() const{ return mChatRoom->getLocalAddress()->weakEqual(CoreManager::getInstance()->getAccountSettingsModel()->getUsedSipAddress()); } bool ChatRoomModel::canHandleParticipants() const{ return mChatRoom->canHandleParticipants(); } /* bool ChatRoomModel::getIsRemoteComposing () const { return mIsRemoteComposing; } */ std::shared_ptr ChatRoomModel::getChatRoom(){ return mChatRoom; } QList ChatRoomModel::getComposers(){ return mComposers.values(); } //------------------------------------------------------------------------------------------------ void ChatRoomModel::setSubject(QString& subject){ if(mChatRoom && getSubject() != subject){ mChatRoom->setSubject(subject.toUtf8().toStdString()); // in UTF8 emit subjectChanged(subject); } } void ChatRoomModel::setLastUpdateTime(const QDateTime& lastUpdateDate) { if(mLastUpdateTime != lastUpdateDate ) { mLastUpdateTime = lastUpdateDate; emit lastUpdateTimeChanged(); } } void ChatRoomModel::updateLastUpdateTime(){ QDateTime lastDateTime = QDateTime::fromMSecsSinceEpoch(mChatRoom->getLastUpdateTime()*1000); QDateTime lastCallTime = lastDateTime; for(auto e : mEntries){ if(e->mType == CallEntry && e->mTimestamp > lastCallTime) lastCallTime = e->mTimestamp; } setLastUpdateTime(lastCallTime); } void ChatRoomModel::setUnreadMessagesCount(const int& count){ if(count != mUnreadMessagesCount){ mUnreadMessagesCount = count; emit unreadMessagesCountChanged(); } } void ChatRoomModel::setMissedCallsCount(const int& count){ if(count != mMissedCallsCount){ mMissedCallsCount = count; emit missedCallsCountChanged(); } } void ChatRoomModel::addMissedCallsCount(std::shared_ptr call){ insertCall(call->getCallLog()); auto timeline = CoreManager::getInstance()->getTimelineListModel()->getTimeline(mChatRoom, false); if(!timeline || !timeline->mSelected){ setMissedCallsCount(mMissedCallsCount+1); CoreManager::getInstance()->getEventCountNotifier()->handleCallMissed(&call->getData("call-model")); } } void ChatRoomModel::setEphemeralEnabled(bool enabled){ if(isEphemeralEnabled() != enabled){ mChatRoom->enableEphemeral(enabled); emit ephemeralEnabledChanged(); } } void ChatRoomModel::setEphemeralLifetime(long lifetime){ if(getEphemeralLifetime() != lifetime){ mChatRoom->setEphemeralLifetime(lifetime); emit ephemeralLifetimeChanged(); } } //------------------------------------------------------------------------------------------------ void ChatRoomModel::deleteChatRoom(){ mDeleteChatRoom = true; } void ChatRoomModel::leaveChatRoom (){ if(mChatRoom) mChatRoom->leave(); } void ChatRoomModel::updateParticipants(const QVariantList& participants){ /* std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > chatRoomParticipants; std::shared_ptr localAddress; for(auto p : participants){ ParticipantModel* participant = p.value(); auto address = Utils::interpretUrl(participant->getSipAddress()); if( address) chatRoomParticipants.push_back( address ); } if(mChatRoom->canHandleParticipants()) { mChatRoom->addParticipants(newParticipants); mChatRoom->removeParticipants(removeParticipants); } linphone::ChatRoom;*/ } // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- void ChatRoomModel::sendMessage (const QString &message) { shared_ptr _message = mChatRoom->createMessageFromUtf8(message.toUtf8().toStdString()); _message->send(); emit messageSent(_message); } void ChatRoomModel::sendFileMessage (const QString &path) { QFile file(path); if (!file.exists()) return; qint64 fileSize = file.size(); if (fileSize > Constants::FileSizeLimit) { qWarning() << QStringLiteral("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit); return; } shared_ptr content = CoreManager::getInstance()->getCore()->createContent(); { QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/'); if (mimeType.length() != 2) { qWarning() << QStringLiteral("Unable to get supported mime type for: `%1`.").arg(path); return; } content->setType(Utils::appStringToCoreString(mimeType[0])); content->setSubtype(Utils::appStringToCoreString(mimeType[1])); } content->setSize(size_t(fileSize)); content->setName(QFileInfo(file).fileName().toStdString()); shared_ptr message = mChatRoom->createFileTransferMessage(content); message->getContents().front()->setFilePath(Utils::appStringToCoreString(path)); message->send(); emit messageSent(message); } // ----------------------------------------------------------------------------- void ChatRoomModel::compose () { if( mChatRoom) mChatRoom->compose(); } void ChatRoomModel::resetMessageCount () { if(mChatRoom && !mDeleteChatRoom){ if (mChatRoom->getUnreadMessagesCount() > 0){ mChatRoom->markAsRead();// Marking as read is only for messages. Not for calls. } setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); setMissedCallsCount(0); emit messageCountReset(); CoreManager::getInstance()->updateUnreadMessageCount(); } } //------------------------------------------------- // Entries Loading managment //------------------------------------------------- // For each type of events, a part of entries are loaded with a minimal count (=mLastEntriesStep). Like that, we have from 0 to 3*mLastEntriesStep events. // We store them in a list that will be sorted from oldest to newest. // From the oldest, we loop till having at least one type of event or if we hit the minimum limit. // As it was a request for each events, we ensure to get all available events after it. // Notations : M0 is the first Message event; N0, the first EventLog; C0, the first Call event. After '|', there are mLastEntriesStep events. // Available cases examples : // 'M0...N0....|...C0....' => '|...C0....' // 'M0C0N0|...' == 'C0N0|...' == '|N0....C0....' == '|N0....C0....' == '|.......' We suppose that we got all available events for the current scope. // 'N0...M0....C0...|...' => '|C0...' // // ------------------- // // When requesting more entries, we count the number of events we got. Each numbers represent the index from what we can retrieve next events from linphone database. // Like that, we avoid to load all database. A bad point is about loading call events : There are no range to retrieve and we don't want to load the entire database. So for this case, this is not fully optimized (optimization is only about GUI and connections) // // Request more entries are coming from GUI. Like that, we don't have to manage if events are filtered or not (only messages, call, events). class EntrySorterHelper{ public: EntrySorterHelper(time_t pTime, ChatRoomModel::EntryType pType,std::shared_ptr obj) : mTime(pTime), mType(pType), mObject(obj) {} time_t mTime; ChatRoomModel::EntryType mType; std::shared_ptr mObject; static void getLimitedSelection(QList > *resultEntries, QList& entries, const int& minEntries, ChatRoomModel * chatRoomModel) {// Sort and return a selection with at least 'minEntries' // Sort list std::sort(entries.begin(), entries.end(), [](const EntrySorterHelper& a, const EntrySorterHelper& b) { return a.mTime < b.mTime; }); // Keep max( minEntries, last(messages, events, calls) ) QList::iterator itEntries = entries.begin(); int spotted = 0; auto lastEntry = itEntries; while(itEntries != entries.end() && (spotted != 7 && (entries.end()-itEntries > minEntries)) ) { if( itEntries->mType == ChatRoomModel::EntryType::MessageEntry) { if( (spotted & 1) == 0) { lastEntry = itEntries; spotted |= 1; } }else if( itEntries->mType == ChatRoomModel::EntryType::CallEntry){ if( (spotted & 2) == 0){ lastEntry = itEntries; spotted |= 2; } }else { if( (spotted & 4) == 0){ lastEntry = itEntries; spotted |= 4; } } ++itEntries; } itEntries = lastEntry; if(itEntries - entries.begin() < 3) itEntries = entries.begin(); for(; itEntries != entries.end() ; ++itEntries){ if( (*itEntries).mType== ChatRoomModel::EntryType::MessageEntry) *resultEntries << ChatMessageModel::create(std::dynamic_pointer_cast(itEntries->mObject), chatRoomModel); else if( (*itEntries).mType == ChatRoomModel::EntryType::CallEntry) { auto entry = ChatCallModel::create(std::dynamic_pointer_cast(itEntries->mObject), true, chatRoomModel); if(entry) { *resultEntries << entry; if (entry->mStatus == LinphoneEnums::CallStatusSuccess) { entry = ChatCallModel::create(entry->getCallLog(), false, chatRoomModel); if(entry) *resultEntries << entry; } } }else{ auto entry = ChatNoticeModel::create(std::dynamic_pointer_cast(itEntries->mObject), chatRoomModel); if(entry) *resultEntries << entry; } } } }; void ChatRoomModel::initEntries(){ // On call : reinitialize all entries. This allow to free up memory QList > entries; QList prepareEntries; // Get chat messages for (auto &message : mChatRoom->getHistory(mLastEntriesStep)) prepareEntries << EntrySorterHelper(message->getTime() ,MessageEntry, message); // Get events for(auto &eventLog : mChatRoom->getHistoryEvents(mLastEntriesStep)) prepareEntries << EntrySorterHelper(eventLog->getCreationTime() , NoticeEntry, eventLog); // Get calls. if(!isSecure() ) { auto callHistory = CoreManager::getInstance()->getCore()->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress()); // callhistory is sorted from newest to oldest int count = 0; for (auto callLog = callHistory.begin() ; count < mLastEntriesStep && callLog != callHistory.end() ; ++callLog, ++count ){ prepareEntries << EntrySorterHelper((*callLog)->getStartDate(), CallEntry, *callLog); } } EntrySorterHelper::getLimitedSelection(&entries, prepareEntries, mLastEntriesStep, this); mIsInitialized = true; if(entries.size() >0){ beginResetModel(); mEntries = entries; endResetModel(); } } int ChatRoomModel::loadMoreEntries(){ QList > entries; QList prepareEntries; // Get current event count for each type QVector entriesCounts; entriesCounts.resize(3); for(auto itEntries = mEntries.begin() ; itEntries != mEntries.end() ; ++itEntries){ if( (*itEntries)->mType == MessageEntry) ++entriesCounts[0]; else if( (*itEntries)->mType == CallEntry){ if(dynamic_cast((*itEntries).get())->mIsStart) ++entriesCounts[1]; } else ++entriesCounts[2]; } // Messages for (auto &message : mChatRoom->getHistoryRange(entriesCounts[0], entriesCounts[0]+mLastEntriesStep)){ auto itEntries = mEntries.begin(); bool haveEntry = false; while(!haveEntry && itEntries != mEntries.end()){ auto entry = dynamic_cast(itEntries->get()); haveEntry = (entry && entry->getChatMessage() == message); ++itEntries; } if(!haveEntry) prepareEntries << EntrySorterHelper(message->getTime() ,MessageEntry, message); } // Calls if(!isSecure() ) { auto callHistory = CoreManager::getInstance()->getCore()->getCallHistory(mChatRoom->getPeerAddress(), mChatRoom->getLocalAddress()); int count = 0; auto itCallHistory = callHistory.begin(); while(count < entriesCounts[1] && itCallHistory != callHistory.end()){ ++itCallHistory; ++count; } count = 0; while( count < mLastEntriesStep && itCallHistory != callHistory.end()){ prepareEntries << EntrySorterHelper((*itCallHistory)->getStartDate(), CallEntry, *itCallHistory); ++itCallHistory; } } // Notices for (auto &eventLog : mChatRoom->getHistoryRangeEvents(entriesCounts[2], entriesCounts[2]+mLastEntriesStep)){ auto itEntries = mEntries.begin(); bool haveEntry = false; while(!haveEntry && itEntries != mEntries.end()){ auto entry = dynamic_cast(itEntries->get()); haveEntry = (entry && entry->getEventLog() == eventLog); ++itEntries; } if(!haveEntry) prepareEntries << EntrySorterHelper(eventLog->getCreationTime() , NoticeEntry, eventLog); } EntrySorterHelper::getLimitedSelection(&entries, prepareEntries, mLastEntriesStep, this); if(entries.size() >0){ beginInsertRows(QModelIndex(), 0, entries.size()-1); for(auto entry : entries) mEntries.prepend(entry); endInsertRows(); emit layoutChanged(); updateLastUpdateTime(); } return entries.size(); } //------------------------------------------------- //------------------------------------------------- void ChatRoomModel::callEnded(std::shared_ptr call){ if( call->getCallLog()->getStatus() == linphone::Call::Status::Missed) addMissedCallsCount(call); else{ insertCall(call->getCallLog()); } // When a call is end, a new log WILL be written in database. It may have information on display name. QTimer::singleShot(100, this, &ChatRoomModel::fullPeerAddressChanged); } // ----------------------------------------------------------------------------- void ChatRoomModel::insertCall (const std::shared_ptr &callLog) { if(mIsInitialized){ std::shared_ptr model = ChatCallModel::create(callLog, true, this); if(model){ int row = mEntries.count(); beginInsertRows(QModelIndex(), row, row); mEntries << model; endInsertRows(); if (callLog->getStatus() == linphone::Call::Status::Success) { model = ChatCallModel::create(callLog, false, this); if(model){ int row = mEntries.count(); beginInsertRows(QModelIndex(), row, row); mEntries << model; endInsertRows(); } } updateLastUpdateTime(); } } } void ChatRoomModel::insertCalls (const QList > &calls) { if(mIsInitialized){ QList > entries; for(auto callLog : calls) { std::shared_ptr model = ChatCallModel::create(callLog, true, this); if(model){ entries << model; if (callLog->getStatus() == linphone::Call::Status::Success) { model = ChatCallModel::create(callLog, false, this); if(model){ entries << model; } } } } if(entries.size() > 0){ beginInsertRows(QModelIndex(), 0, entries.size()-1); entries << mEntries; mEntries = entries; endInsertRows(); emit layoutChanged(); } } } void ChatRoomModel::insertMessageAtEnd (const std::shared_ptr &message) { if(mIsInitialized){ std::shared_ptr model = ChatMessageModel::create(message, this); if(model){ setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); int row = mEntries.count(); beginInsertRows(QModelIndex(), row, row); mEntries << model; endInsertRows(); } } } void ChatRoomModel::insertMessages (const QList > &messages) { if(mIsInitialized){ QList > entries; for(auto message : messages) { std::shared_ptr model = ChatMessageModel::create(message, this); if(model) entries << model; } if(entries.size() > 0){ setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); beginInsertRows(QModelIndex(), 0, entries.size()-1); entries << mEntries; mEntries = entries; endInsertRows(); emit layoutChanged(); } } } void ChatRoomModel::insertNotice (const std::shared_ptr &eventLog) { if(mIsInitialized){ std::shared_ptr model = ChatNoticeModel::create(eventLog, this); if(model){ int row = mEntries.count(); beginInsertRows(QModelIndex(), row, row); mEntries << model; endInsertRows(); } } } void ChatRoomModel::insertNotices (const QList> &eventLogs) { if(mIsInitialized){ QList > entries; for(auto eventLog : eventLogs) { std::shared_ptr model = ChatNoticeModel::create(eventLog, this); if(model) entries << model; } if(entries.size() > 0){ beginInsertRows(QModelIndex(), 0, entries.size()-1); entries << mEntries; mEntries = entries; endInsertRows(); emit layoutChanged(); } } } // ----------------------------------------------------------------------------- void ChatRoomModel::handleCallStateChanged (const std::shared_ptr &call, linphone::Call::State state) { /* if (state == linphone::Call::State::End || state == linphone::Call::State::Error){ shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr params = core->createDefaultChatRoomParams(); std::list> participants; auto chatRoom = core->searchChatRoom(params, mChatRoom->getLocalAddress() , call->getRemoteAddress() , participants); if( mChatRoom == chatRoom){ insertCall(call->getCallLog()); setMissedCallsCount(mMissedCallsCount+1); } } */ } void ChatRoomModel::handleCallCreated(const shared_ptr &call){ } void ChatRoomModel::handlePresenceStatusReceived(std::shared_ptr contact){ if(!mDeleteChatRoom && contact){ bool canUpdatePresence = false; auto contactAddresses = contact->getAddresses(); for( auto itContactAddress = contactAddresses.begin() ; !canUpdatePresence && itContactAddress != contactAddresses.end() ; ++itContactAddress){ //auto cleanContactAddress = (*itContactAddress)->clone(); //cleanContactAddress->clean(); canUpdatePresence = mChatRoom->getLocalAddress()->weakEqual(*itContactAddress); if(!canUpdatePresence && !isGroupEnabled() && mChatRoom->getNbParticipants() == 1){ auto participants = mChatRoom->getParticipants(); auto contact = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(Utils::coreStringToAppString((*participants.begin())->getAddress()->asString())); if(contact){ auto friendsAddresses = contact->getVcardModel()->getSipAddresses(); for(auto friendAddress = friendsAddresses.begin() ; !canUpdatePresence && friendAddress != friendsAddresses.end() ; ++friendAddress){ shared_ptr lAddress = CoreManager::getInstance()->getCore()->interpretUrl( Utils::appStringToCoreString(friendAddress->toString()) ); canUpdatePresence = lAddress->weakEqual(*itContactAddress); } } } } if(canUpdatePresence) { //emit presenceStatusChanged((int)contact->getPresenceModel()->getConsolidatedPresence()); emit presenceStatusChanged(); } } } //---------------------------------------------------------- //------ CHAT ROOM HANDLERS //---------------------------------------------------------- void ChatRoomModel::onIsComposingReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & remoteAddress, bool isComposing){ auto it = mComposers.begin(); while(it != mComposers.end() && !it.key()->weakEqual(remoteAddress)) ++it; if(it != mComposers.end()) mComposers.erase(it); if(isComposing) mComposers[remoteAddress] = Utils::getDisplayName(remoteAddress); emit isRemoteComposingChanged(); updateLastUpdateTime(); } void ChatRoomModel::onMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ setUnreadMessagesCount(chatRoom->getUnreadMessagesCount()); updateLastUpdateTime(); } void ChatRoomModel::onNewEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ if( eventLog->getType() == linphone::EventLog::Type::ConferenceCallEnd ){ setMissedCallsCount(mMissedCallsCount+1); }else if( eventLog->getType() == linphone::EventLog::Type::ConferenceCreated ){ emit fullPeerAddressChanged(); } updateLastUpdateTime(); } void ChatRoomModel::onChatMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { auto message = eventLog->getChatMessage(); if(message){ insertMessageAtEnd(message); updateLastUpdateTime(); emit messageReceived(message); } } void ChatRoomModel::onChatMessageSending(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto message = eventLog->getChatMessage(); if(message){ insertMessageAtEnd(message); updateLastUpdateTime(); emit messageReceived(message); } } void ChatRoomModel::onChatMessageSent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateLastUpdateTime(); } // Called when the core have the participant (= exists) void ChatRoomModel::onParticipantAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end() ) insertNotice(*e); updateLastUpdateTime(); emit participantAdded(chatRoom, eventLog); emit fullPeerAddressChanged(); } void ChatRoomModel::onParticipantRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end() ) insertNotice(*e); updateLastUpdateTime(); emit participantRemoved(chatRoom, eventLog); emit fullPeerAddressChanged(); } void ChatRoomModel::onParticipantAdminStatusChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end() ) insertNotice(*e); updateLastUpdateTime(); emit participantAdminStatusChanged(chatRoom, eventLog); emit isMeAdminChanged(); // It is not the case all the time but calling getters is not a heavy request } void ChatRoomModel::onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){ updateLastUpdateTime(); emit stateChanged(getState()); } void ChatRoomModel::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end() ) insertNotice(*e); updateLastUpdateTime(); emit securityLevelChanged((int)chatRoom->getSecurityLevel()); } void ChatRoomModel::onSubjectChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end() ) insertNotice(*e); updateLastUpdateTime(); emit subjectChanged(getSubject()); emit usernameChanged(); } void ChatRoomModel::onUndecryptableMessageReceived(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ updateLastUpdateTime(); } void ChatRoomModel::onParticipantDeviceAdded(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateLastUpdateTime(); emit participantDeviceAdded(chatRoom, eventLog); } void ChatRoomModel::onParticipantDeviceRemoved(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateLastUpdateTime(); emit participantDeviceRemoved(chatRoom, eventLog); } void ChatRoomModel::onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if(e != events.end() ) insertNotice(*e); else{ events = mChatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if(e != events.end() ) insertNotice(*e); } setUnreadMessagesCount(mChatRoom->getUnreadMessagesCount()); // Update message count. In the case of joining conference, the conference id was not valid thus, the missing count was not about the chat room but a global one. updateLastUpdateTime(); emit usernameChanged(); emit conferenceJoined(chatRoom, eventLog); emit hasBeenLeftChanged(); } void ChatRoomModel::onConferenceLeft(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ if( chatRoom->getState() != linphone::ChatRoom::State::Deleted) { auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if( e != events.end()) insertNotice(*e); else{ events = mChatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if(e != events.end() ) insertNotice(*e); } updateLastUpdateTime(); emit conferenceLeft(chatRoom, eventLog); emit hasBeenLeftChanged(); } } void ChatRoomModel::onEphemeralEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ auto events = chatRoom->getHistoryEvents(0); auto e = std::find(events.begin(), events.end(), eventLog); if(e != events.end() ) insertNotice(*e); updateLastUpdateTime(); } void ChatRoomModel::onEphemeralMessageTimerStarted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateLastUpdateTime(); } void ChatRoomModel::onEphemeralMessageDeleted(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ updateLastUpdateTime(); } void ChatRoomModel::onConferenceAddressGeneration(const std::shared_ptr & chatRoom){ updateLastUpdateTime(); } void ChatRoomModel::onParticipantRegistrationSubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ updateLastUpdateTime(); emit participantRegistrationSubscriptionRequested(chatRoom, participantAddress); } void ChatRoomModel::onParticipantRegistrationUnsubscriptionRequested(const std::shared_ptr & chatRoom, const std::shared_ptr & participantAddress){ emit participantRegistrationUnsubscriptionRequested(chatRoom, participantAddress); } void ChatRoomModel::onChatMessageShouldBeStored(const std::shared_ptr & chatRoom, const std::shared_ptr & message){ } void ChatRoomModel::onChatMessageParticipantImdnStateChanged(const std::shared_ptr & chatRoom, const std::shared_ptr & message, const std::shared_ptr & state){ }