mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-02-02 04:09:28 +00:00
538 lines
18 KiB
C++
538 lines
18 KiB
C++
/*
|
|
* 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 <QDateTime>
|
|
|
|
#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<int, QByteArray> SipAddressesModel::roleNames () const {
|
|
QHash<int, QByteArray> 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<ContactModel *>();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
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<ContactModel *>());
|
|
model->setPresenceStatus(
|
|
it->value("presenceStatus", Presence::PresenceStatus::Offline).value<Presence::PresenceStatus>()
|
|
);
|
|
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<const linphone::Address> 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<linphone::Address> 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<linphone::Address> 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<linphone::Address> 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) {
|
|
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<linphone::ChatMessage> &message) {
|
|
const QString sipAddress = ::Utils::coreStringToAppString(message->getFromAddress()->asStringUriOnly());
|
|
addOrUpdateSipAddress(sipAddress, message);
|
|
}
|
|
|
|
void SipAddressesModel::handleCallStateChanged (
|
|
const shared_ptr<linphone::Call> &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<const linphone::PresenceModel> &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<linphone::ChatMessage> &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<linphone::ChatRoom> &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<linphone::Call> &call) {
|
|
const shared_ptr<linphone::CallLog> 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<linphone::ChatMessage> &message) {
|
|
int count = message->getChatRoom()->getUnreadMessagesCount();
|
|
|
|
map["timestamp"] = QDateTime::fromMSecsSinceEpoch(message->getTime() * 1000);
|
|
map["unreadMessagesCount"] = count;
|
|
|
|
updateObservers(map["sipAddress"].toString(), count);
|
|
}
|
|
|
|
template<typename T>
|
|
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<linphone::Core> core = CoreManager::getInstance()->getCore();
|
|
|
|
// Get sip addresses from chatrooms.
|
|
for (const auto &chatRoom : core->getChatRooms()) {
|
|
list<shared_ptr<linphone::ChatMessage> > 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<QString> 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);
|
|
}
|