/* * SipAddressesModel.cpp * Copyright (C) 2017-2018 Belledonne Communications, Grenoble, France * * 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. * * Created on: February 2, 2017 * Author: Ronan Abhamon */ #include #include "../../utils/LinphoneUtils.hpp" #include "../../utils/Utils.hpp" #include "../core/CoreManager.hpp" #include "SipAddressesModel.hpp" using namespace std; // ============================================================================= SipAddressesModel::SipAddressesModel (QObject *parent) : QAbstractListModel(parent) { initSipAddresses(); CoreManager *coreManager = CoreManager::getInstance(); mCoreHandlers = coreManager->getHandlers(); QObject::connect(coreManager, &CoreManager::chatModelCreated, this, &SipAddressesModel::handleChatModelCreated); ContactsListModel *contacts = CoreManager::getInstance()->getContactsListModel(); QObject::connect(contacts, &ContactsListModel::contactAdded, this, &SipAddressesModel::handleContactAdded); QObject::connect(contacts, &ContactsListModel::contactRemoved, this, &SipAddressesModel::handleContactRemoved); QObject::connect(contacts, &ContactsListModel::sipAddressAdded, this, &SipAddressesModel::handleSipAddressAdded); QObject::connect(contacts, &ContactsListModel::sipAddressRemoved, this, &SipAddressesModel::handleSipAddressRemoved); CoreHandlers *coreHandlers = mCoreHandlers.get(); QObject::connect(coreHandlers, &CoreHandlers::messageReceived, this, &SipAddressesModel::handleMessageReceived); QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &SipAddressesModel::handleCallStateChanged); QObject::connect(coreHandlers, &CoreHandlers::presenceReceived, this, &SipAddressesModel::handlePresenceReceived); QObject::connect(coreHandlers, &CoreHandlers::isComposingChanged, this, &SipAddressesModel::handlerIsComposingChanged); } // ----------------------------------------------------------------------------- int SipAddressesModel::rowCount (const QModelIndex &) const { return mRefs.count(); } QHash SipAddressesModel::roleNames () const { QHash roles; roles[Qt::DisplayRole] = "$sipAddress"; return roles; } QVariant SipAddressesModel::data (const QModelIndex &index, int role) const { int row = index.row(); if (!index.isValid() || row < 0 || row >= mRefs.count()) return QVariant(); if (role == Qt::DisplayRole) return QVariant::fromValue(*mRefs[row]); return QVariant(); } // ----------------------------------------------------------------------------- QVariantMap SipAddressesModel::find (const QString &sipAddress) const { auto it = mSipAddresses.find(sipAddress); return it == mSipAddresses.end() ? QVariantMap() : *it; } // ----------------------------------------------------------------------------- ContactModel *SipAddressesModel::mapSipAddressToContact (const QString &sipAddress) const { auto it = mSipAddresses.find(sipAddress); if (it == mSipAddresses.end()) return nullptr; return it->value("contact").value(); } // ----------------------------------------------------------------------------- SipAddressObserver *SipAddressesModel::getSipAddressObserver (const QString &sipAddress) { SipAddressObserver *model = new SipAddressObserver(sipAddress); const QString cleanedSipAddress = cleanSipAddress(sipAddress); { auto it = mSipAddresses.find(cleanedSipAddress); if (it != mSipAddresses.end()) { model->setContact(it->value("contact").value()); model->setPresenceStatus( it->value("presenceStatus", Presence::PresenceStatus::Offline).value() ); model->setUnreadMessagesCount( it->value("unreadMessagesCount", 0).toInt() ); } } mObservers.insert(cleanedSipAddress, model); QObject::connect( model, &SipAddressObserver::destroyed, this, [this, model, cleanedSipAddress]() { // Do not use `model` methods here. `model` is partially destroyed here! if (mObservers.remove(cleanedSipAddress, model) == 0) qWarning() << QStringLiteral("Unable to remove sip address `%1` from observers.").arg(cleanedSipAddress); }); return model; } // ----------------------------------------------------------------------------- QString SipAddressesModel::getTransportFromSipAddress (const QString &sipAddress) const { const shared_ptr address = linphone::Factory::get()->createAddress( ::Utils::appStringToCoreString(sipAddress) ); if (!address) return QString(""); switch (address->getTransport()) { case linphone::TransportTypeUdp: return QStringLiteral("UDP"); case linphone::TransportTypeTcp: return QStringLiteral("TCP"); case linphone::TransportTypeTls: return QStringLiteral("TLS"); case linphone::TransportTypeDtls: return QStringLiteral("DTLS"); } return QString(""); } QString SipAddressesModel::addTransportToSipAddress (const QString &sipAddress, const QString &transport) const { shared_ptr address = linphone::Factory::get()->createAddress( ::Utils::appStringToCoreString(sipAddress) ); if (!address) return QString(""); address->setTransport(LinphoneUtils::stringToTransportType(transport.toUpper())); return ::Utils::coreStringToAppString(address->asString()); } // ----------------------------------------------------------------------------- QString SipAddressesModel::interpretSipAddress (const QString &sipAddress, bool checkUsername) { shared_ptr lAddress = CoreManager::getInstance()->getCore()->interpretUrl( ::Utils::appStringToCoreString(sipAddress) ); if (lAddress && (!checkUsername || !lAddress->getUsername().empty())) return ::Utils::coreStringToAppString(lAddress->asStringUriOnly()); return QString(""); } QString SipAddressesModel::interpretSipAddress (const QUrl &sipAddress) { return sipAddress.toString(); } bool SipAddressesModel::addressIsValid (const QString &address) { return !!linphone::Factory::get()->createAddress( ::Utils::appStringToCoreString(address) ); } bool SipAddressesModel::sipAddressIsValid (const QString &sipAddress) { shared_ptr address = linphone::Factory::get()->createAddress( ::Utils::appStringToCoreString(sipAddress) ); return address && !address->getUsername().empty(); } QString SipAddressesModel::cleanSipAddress (const QString &sipAddress) { const int index = sipAddress.lastIndexOf('<'); if (index == -1) return sipAddress; return sipAddress.mid(index + 1, sipAddress.lastIndexOf('>') - index - 1); } // ----------------------------------------------------------------------------- bool SipAddressesModel::removeRow (int row, const QModelIndex &parent) { return removeRows(row, 1, parent); } bool SipAddressesModel::removeRows (int row, int count, const QModelIndex &parent) { int limit = row + count - 1; if (row < 0 || count < 0 || limit >= mSipAddresses.count()) return false; beginRemoveRows(parent, row, limit); for (int i = 0; i < count; ++i) { const QVariantMap *map = mRefs.takeAt(row); QString sipAddress = (*map)["sipAddress"].toString(); qInfo() << QStringLiteral("Remove sip address: `%1`.").arg(sipAddress); mSipAddresses.remove(sipAddress); } endRemoveRows(); return true; } // ----------------------------------------------------------------------------- void SipAddressesModel::handleChatModelCreated (const shared_ptr &chatModel) { ChatModel *ptr = chatModel.get(); QObject::connect(ptr, &ChatModel::allEntriesRemoved, this, [this, ptr] { handleAllEntriesRemoved(ptr->getSipAddress()); }); QObject::connect(ptr, &ChatModel::messageSent, this, &SipAddressesModel::handleMessageSent); QObject::connect(ptr, &ChatModel::messagesCountReset, this, [this, ptr] { handleMessagesCountReset(ptr->getSipAddress()); }); } void SipAddressesModel::handleContactAdded (ContactModel *contact) { for (const auto &sipAddress : contact->getVcardModel()->getSipAddresses()) addOrUpdateSipAddress(sipAddress.toString(), contact); } void SipAddressesModel::handleContactRemoved (const ContactModel *contact) { for (const auto &sipAddress : contact->getVcardModel()->getSipAddresses()) removeContactOfSipAddress(sipAddress.toString()); } void SipAddressesModel::handleSipAddressAdded (ContactModel *contact, const QString &sipAddress) { ContactModel *mappedContact = mapSipAddressToContact(sipAddress); if (mappedContact) { qWarning() << "Unable to map sip address" << sipAddress << "to" << contact << "- already used by" << mappedContact; return; } addOrUpdateSipAddress(sipAddress, contact); } void SipAddressesModel::handleSipAddressRemoved (ContactModel *contact, const QString &sipAddress) { ContactModel *mappedContact = mapSipAddressToContact(sipAddress); if (contact != mappedContact) { qWarning() << "Unable to remove sip address" << sipAddress << "of" << contact << "- already used by" << mappedContact; return; } removeContactOfSipAddress(sipAddress); } void SipAddressesModel::handleMessageReceived (const shared_ptr &message) { const QString sipAddress = ::Utils::coreStringToAppString(message->getFromAddress()->asStringUriOnly()); addOrUpdateSipAddress(sipAddress, message); } void SipAddressesModel::handleCallStateChanged ( const shared_ptr &call, linphone::CallState state ) { // Ignore aborted calls. if (call->getCallLog()->getStatus() == linphone::CallStatus::CallStatusAborted) return; if (state == linphone::CallStateEnd || state == linphone::CallStateError) addOrUpdateSipAddress( ::Utils::coreStringToAppString(call->getRemoteAddress()->asStringUriOnly()), call ); } void SipAddressesModel::handlePresenceReceived ( const QString &sipAddress, const shared_ptr &presenceModel ) { Presence::PresenceStatus status; switch (presenceModel->getConsolidatedPresence()) { case linphone::ConsolidatedPresenceOnline: status = Presence::PresenceStatus::Online; break; case linphone::ConsolidatedPresenceBusy: status = Presence::PresenceStatus::Busy; break; case linphone::ConsolidatedPresenceDoNotDisturb: status = Presence::PresenceStatus::DoNotDisturb; break; case linphone::ConsolidatedPresenceOffline: status = Presence::PresenceStatus::Offline; break; } auto it = mSipAddresses.find(sipAddress); if (it != mSipAddresses.end()) { qInfo() << QStringLiteral("Update presence of `%1`: %2.").arg(sipAddress).arg(status); (*it)["presenceStatus"] = status; int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); emit dataChanged(index(row, 0), index(row, 0)); } updateObservers(sipAddress, status); } void SipAddressesModel::handleAllEntriesRemoved (const QString &sipAddress) { auto it = mSipAddresses.find(sipAddress); if (it == mSipAddresses.end()) { qWarning() << QStringLiteral("Unable to find sip address: `%1`.").arg(sipAddress); return; } int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); // No history, no contact => Remove sip address from list. if (!it->contains("contact")) { removeRow(row); return; } // Signal changes. it->remove("timestamp"); emit dataChanged(index(row, 0), index(row, 0)); } void SipAddressesModel::handleMessageSent (const shared_ptr &message) { addOrUpdateSipAddress( ::Utils::coreStringToAppString(message->getToAddress()->asStringUriOnly()), message ); } void SipAddressesModel::handleMessagesCountReset (const QString &sipAddress) { auto it = mSipAddresses.find(sipAddress); if (it != mSipAddresses.end()) { (*it)["unreadMessagesCount"] = 0; int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); emit dataChanged(index(row, 0), index(row, 0)); } updateObservers(sipAddress, 0); } void SipAddressesModel::handlerIsComposingChanged (const shared_ptr &chatRoom) { auto it = mSipAddresses.find(::Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly())); if (it != mSipAddresses.end()) { (*it)["isComposing"] = chatRoom->isRemoteComposing(); int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); emit dataChanged(index(row, 0), index(row, 0)); } } // ----------------------------------------------------------------------------- void SipAddressesModel::addOrUpdateSipAddress (QVariantMap &map, ContactModel *contact) { QString sipAddress = map["sipAddress"].toString(); if (contact) map["contact"] = QVariant::fromValue(contact); else if (map.remove("contact") == 0) qWarning() << QStringLiteral("`contact` field is empty on sip address: `%1`.").arg(sipAddress); updateObservers(sipAddress, contact); } void SipAddressesModel::addOrUpdateSipAddress (QVariantMap &map, const shared_ptr &call) { const shared_ptr callLog = call->getCallLog(); map["timestamp"] = callLog->getStatus() == linphone::CallStatus::CallStatusSuccess ? QDateTime::fromMSecsSinceEpoch((callLog->getStartDate() + callLog->getDuration()) * 1000) : QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); } void SipAddressesModel::addOrUpdateSipAddress (QVariantMap &map, const shared_ptr &message) { int count = message->getChatRoom()->getUnreadMessagesCount(); map["timestamp"] = QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000); map["unreadMessagesCount"] = count; updateObservers(map["sipAddress"].toString(), count); } template void SipAddressesModel::addOrUpdateSipAddress (const QString &sipAddress, T data) { auto it = mSipAddresses.find(sipAddress); if (it != mSipAddresses.end()) { addOrUpdateSipAddress(*it, data); int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); emit dataChanged(index(row, 0), index(row, 0)); return; } QVariantMap map; map["sipAddress"] = sipAddress; addOrUpdateSipAddress(map, data); int row = mRefs.count(); beginInsertRows(QModelIndex(), row, row); qInfo() << QStringLiteral("Add sip address: `%1`.").arg(sipAddress); mSipAddresses[sipAddress] = map; mRefs << &mSipAddresses[sipAddress]; endInsertRows(); } // ----------------------------------------------------------------------------- void SipAddressesModel::removeContactOfSipAddress (const QString &sipAddress) { auto it = mSipAddresses.find(sipAddress); if (it == mSipAddresses.end()) { qWarning() << QStringLiteral("Unable to remove unavailable sip address: `%1`.").arg(sipAddress); return; } // Try to map other contact on this sip address. ContactModel *contactModel = CoreManager::getInstance()->getContactsListModel()->findContactModelFromSipAddress(sipAddress); updateObservers(sipAddress, contactModel); qInfo() << QStringLiteral("Map new contact on sip address: `%1`.").arg(sipAddress) << contactModel; addOrUpdateSipAddress(*it, contactModel); int row = mRefs.indexOf(&(*it)); Q_ASSERT(row != -1); // History exists, signal changes. if (it->contains("timestamp") || contactModel) { emit dataChanged(index(row, 0), index(row, 0)); return; } // Remove sip address if no history. removeRow(row); } void SipAddressesModel::initSipAddresses () { shared_ptr core = CoreManager::getInstance()->getCore(); // Get sip addresses from chatrooms. for (const auto &chatRoom : core->getChatRooms()) { list > history = chatRoom->getHistory(0); if (history.size() == 0) continue; QString sipAddress = ::Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly()); QVariantMap map; map["sipAddress"] = sipAddress; map["timestamp"] = QDateTime::fromMSecsSinceEpoch(history.back()->getTime() * 1000); map["unreadMessagesCount"] = chatRoom->getUnreadMessagesCount(); mSipAddresses[sipAddress] = map; } // Get sip addresses from calls. QSet addressDone; for (const auto &callLog : core->getCallLogs()) { const QString sipAddress = ::Utils::coreStringToAppString(callLog->getRemoteAddress()->asStringUriOnly()); if (addressDone.contains(sipAddress)) continue; // Already used. if (callLog->getStatus() == linphone::CallStatusAborted) continue; // Ignore aborted calls. addressDone << sipAddress; QVariantMap map; map["sipAddress"] = sipAddress; // The duration can be wrong if status is not success. map["timestamp"] = callLog->getStatus() == linphone::CallStatus::CallStatusSuccess ? QDateTime::fromMSecsSinceEpoch((callLog->getStartDate() + callLog->getDuration()) * 1000) : QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000); auto it = mSipAddresses.find(sipAddress); if (it == mSipAddresses.end() || map["timestamp"] > (*it)["timestamp"]) mSipAddresses[sipAddress] = map; } for (const auto &map : mSipAddresses) { qInfo() << QStringLiteral("Add sip address: `%1`.").arg(map["sipAddress"].toString()); mRefs << ↦ } // Get sip addresses from contacts. for (auto &contact : CoreManager::getInstance()->getContactsListModel()->mList) handleContactAdded(contact); } // ----------------------------------------------------------------------------- void SipAddressesModel::updateObservers (const QString &sipAddress, ContactModel *contact) { for (auto &observer : mObservers.values(sipAddress)) observer->setContact(contact); } void SipAddressesModel::updateObservers (const QString &sipAddress, const Presence::PresenceStatus &presenceStatus) { for (auto &observer : mObservers.values(sipAddress)) observer->setPresenceStatus(presenceStatus); } void SipAddressesModel::updateObservers (const QString &sipAddress, int messagesCount) { for (auto &observer : mObservers.values(sipAddress)) observer->setUnreadMessagesCount(messagesCount); }