linphone-desktop/Linphone/core/friend/FriendCore.cpp
Julien Wadel 5a0dd7216e Fix wrong address on contact edition.
Fix wrong avatar initials in contact details and fix synchronization.
Make only one contact search and filter results on each lists.
Fix contact aggregation.
2024-03-14 14:38:46 +01:00

533 lines
18 KiB
C++

/*
* Copyright (c) 2010-2024 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 <http://www.gnu.org/licenses/>.
*/
#include "FriendCore.hpp"
#include "core/App.hpp"
#include "core/proxy/ListProxy.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
DEFINE_ABSTRACT_OBJECT(FriendCore)
const QString addressLabel = FriendCore::tr("Adresse SIP");
const QString phoneLabel = FriendCore::tr("Téléphone");
QVariant createFriendAddressVariant(const QString &label, const QString &address) {
QVariantMap map;
map.insert("label", label);
map.insert("address", address);
return map;
}
QSharedPointer<FriendCore> FriendCore::create(const std::shared_ptr<linphone::Friend> &contact) {
auto sharedPointer = QSharedPointer<FriendCore>(new FriendCore(contact), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact) : QObject(nullptr) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
if (contact) {
mustBeInLinphoneThread(getClassName());
mFriendModel = Utils::makeQObject_ptr<FriendModel>(contact);
mFriendModel->setSelf(mFriendModel);
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
auto vcard = contact->getVcard();
mOrganization = Utils::coreStringToAppString(vcard->getOrganization());
mJob = Utils::coreStringToAppString(vcard->getJobTitle());
mGivenName = Utils::coreStringToAppString(vcard->getGivenName());
mFamilyName = Utils::coreStringToAppString(vcard->getFamilyName());
auto addresses = contact->getAddresses();
for (auto &address : addresses) {
mAddressList.append(
createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(address->asStringUriOnly())));
}
mDefaultAddress =
contact->getAddress() ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString();
auto phoneNumbers = contact->getPhoneNumbersWithLabel();
for (auto &phoneNumber : phoneNumbers) {
mPhoneNumberList.append(
createFriendAddressVariant(Utils::coreStringToAppString(phoneNumber->getLabel()),
Utils::coreStringToAppString(phoneNumber->getPhoneNumber())));
}
mStarred = contact->getStarred();
mIsSaved = true;
} else {
mIsSaved = false;
mStarred = false;
}
connect(this, &FriendCore::addressChanged, &FriendCore::allAddressesChanged);
connect(this, &FriendCore::phoneNumberChanged, &FriendCore::allAddressesChanged);
}
FriendCore::FriendCore(const FriendCore &friendCore) {
// Only copy friend values without models for lambda using and avoid concurrencies.
mAddressList = friendCore.mAddressList;
mPhoneNumberList = friendCore.mPhoneNumberList;
mDefaultAddress = friendCore.mDefaultAddress;
mGivenName = friendCore.mGivenName;
mFamilyName = friendCore.mFamilyName;
mOrganization = friendCore.mOrganization;
mJob = friendCore.mJob;
mPictureUri = friendCore.mPictureUri;
mIsSaved = friendCore.mIsSaved;
}
FriendCore::~FriendCore() {
mustBeInMainThread("~" + getClassName());
if (mFriendModel) emit mFriendModel->removeListener();
}
void FriendCore::setSelf(SafeSharedPointer<FriendCore> me) {
setSelf(me.mQDataWeak.lock());
}
void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
if (me) {
if (mFriendModel) {
mCoreModelConnection = nullptr; // No more needed
mFriendModelConnection = QSharedPointer<SafeConnection<FriendCore, FriendModel>>(
new SafeConnection<FriendCore, FriendModel>(me, mFriendModel), &QObject::deleteLater);
mFriendModelConnection->makeConnectToModel(
&FriendModel::presenceReceived,
[this](LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp) {
mFriendModelConnection->invokeToCore([this, consolidatedPresence, presenceTimestamp]() {
setConsolidatedPresence(consolidatedPresence);
setPresenceTimestamp(presenceTimestamp);
});
});
mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](const QString &uri) {
mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::starredChanged, [this](bool starred) {
mFriendModelConnection->invokeToCore([this, starred]() { this->onStarredChanged(starred); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::givenNameChanged, [this](const QString &name) {
mFriendModelConnection->invokeToCore([this, name]() { setGivenName(name); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::familyNameChanged, [this](const QString &name) {
mFriendModelConnection->invokeToCore([this, name]() { setFamilyName(name); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::organizationChanged, [this](const QString &orga) {
mFriendModelConnection->invokeToCore([this, orga]() { setOrganization(orga); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::jobChanged, [this](const QString &job) {
mFriendModelConnection->invokeToCore([this, job]() { setJob(job); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::addressesChanged, [this]() {
auto numbers = mFriendModel->getAddresses();
QList<QVariant> addr;
for (auto &num : numbers) {
addr.append(
createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(num->asStringUriOnly())));
}
mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); });
});
mFriendModelConnection->makeConnectToModel(&FriendModel::phoneNumbersChanged, [this]() {
auto numbers = mFriendModel->getPhoneNumbers();
QList<QVariant> addr;
for (auto &num : numbers) {
addr.append(
createFriendAddressVariant(phoneLabel, Utils::coreStringToAppString(num->getPhoneNumber())));
}
mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); });
});
mFriendModelConnection->makeConnectToModel(
&FriendModel::objectNameChanged,
[this](const QString &objectName) { qDebug() << "object name changed" << objectName; });
// From GUI
mFriendModelConnection->makeConnectToCore(&FriendCore::lSetStarred, [this](bool starred) {
mFriendModelConnection->invokeToModel([this, starred]() { mFriendModel->setStarred(starred); });
});
} else { // Create
mCoreModelConnection = QSharedPointer<SafeConnection<FriendCore, CoreModel>>(
new SafeConnection<FriendCore, CoreModel>(me, CoreModel::getInstance()), &QObject::deleteLater);
}
}
}
void FriendCore::reset(const FriendCore &contact) {
resetAddresses(contact.getAddresses());
resetPhoneNumbers(contact.getPhoneNumbers());
setDefaultAddress(contact.getDefaultAddress());
setGivenName(contact.getGivenName());
setFamilyName(contact.getFamilyName());
setOrganization(contact.getOrganization());
setJob(contact.getJob());
setPictureUri(contact.getPictureUri());
setIsSaved(mFriendModel != nullptr);
}
QString FriendCore::getDisplayName() const {
return mGivenName + " " + mFamilyName;
}
QString FriendCore::getGivenName() const {
return mGivenName;
}
void FriendCore::setGivenName(const QString &name) {
if (mGivenName != name) {
mGivenName = name;
emit givenNameChanged(name);
emit displayNameChanged();
setIsSaved(false);
}
}
QString FriendCore::getOrganization() const {
return mOrganization;
}
void FriendCore::setOrganization(const QString &orga) {
if (mOrganization != orga) {
mOrganization = orga;
emit organizationChanged();
setIsSaved(false);
}
}
QString FriendCore::getJob() const {
return mJob;
}
void FriendCore::setJob(const QString &job) {
if (mJob != job) {
mJob = job;
emit jobChanged();
setIsSaved(false);
}
}
QString FriendCore::getFamilyName() const {
return mFamilyName;
}
void FriendCore::setFamilyName(const QString &name) {
if (mFamilyName != name) {
mFamilyName = name;
emit familyNameChanged(name);
emit displayNameChanged();
setIsSaved(false);
}
}
bool FriendCore::getStarred() const {
return mStarred;
}
void FriendCore::onStarredChanged(bool starred) {
mStarred = starred;
save();
emit starredChanged();
}
QList<QVariant> FriendCore::getPhoneNumbers() const {
return mPhoneNumberList;
}
QVariant FriendCore::getPhoneNumberAt(int index) const {
if (index < 0 || index >= mPhoneNumberList.count()) return QVariant();
return mPhoneNumberList[index];
}
void FriendCore::setPhoneNumberAt(int index, const QString &label, const QString &phoneNumber) {
if (index < 0 || index >= mPhoneNumberList.count()) return;
auto map = mPhoneNumberList[index].toMap();
auto oldLabel = map["label"].toString();
if (/*oldLabel != label || */ map["address"] != phoneNumber) {
mPhoneNumberList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, phoneNumber));
emit phoneNumberChanged();
setIsSaved(false);
}
}
void FriendCore::removePhoneNumber(int index) {
if (index != -1) mPhoneNumberList.remove(index);
emit phoneNumberChanged();
}
void FriendCore::appendPhoneNumber(const QString &label, const QString &number) {
mPhoneNumberList.append(createFriendAddressVariant(label, number));
emit phoneNumberChanged();
}
void FriendCore::resetPhoneNumbers(QList<QVariant> newList) {
mPhoneNumberList = newList;
emit phoneNumberChanged();
}
QList<QVariant> FriendCore::getAddresses() const {
return mAddressList;
}
QVariant FriendCore::getAddressAt(int index) const {
if (index < 0 || index >= mAddressList.count()) return QVariant();
return mAddressList[index];
}
void FriendCore::setAddressAt(int index, const QString &label, const QString &address) {
if (index < 0 || index >= mAddressList.count()) return;
auto map = mAddressList[index].toMap();
auto oldLabel = map["label"].toString();
if (/*oldLabel != label || */ map["address"] != address) {
mAddressList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, address));
emit addressChanged();
setIsSaved(false);
}
}
void FriendCore::removeAddress(int index) {
if (index < 0 && index >= mAddressList.size()) return;
auto map = mAddressList[index].toMap();
if (map["address"].toString() == mDefaultAddress) mDefaultAddress.clear();
mAddressList.remove(index);
emit addressChanged();
}
void FriendCore::appendAddress(const QString &addr) {
if (addr.isEmpty()) return;
mAddressList.append(createFriendAddressVariant(addressLabel, addr));
if (mDefaultAddress.isEmpty()) mDefaultAddress = addr;
emit addressChanged();
}
void FriendCore::resetAddresses(QList<QVariant> newList) {
mAddressList = newList;
emit addressChanged();
}
QList<QVariant> FriendCore::getAllAddresses() const {
return mAddressList + mPhoneNumberList;
}
QString FriendCore::getDefaultAddress() const {
return mDefaultAddress;
}
void FriendCore::setDefaultAddress(const QString &address) {
auto it = std::find_if(mAddressList.begin(), mAddressList.end(),
[address](const QVariant &a) { return a.toMap()["address"].toString() == address; });
if (it == mAddressList.end()) appendAddress(address);
if (mDefaultAddress != address) {
mDefaultAddress = address;
emit defaultAddressChanged();
}
}
LinphoneEnums::ConsolidatedPresence FriendCore::getConsolidatedPresence() const {
return mConsolidatedPresence;
}
void FriendCore::setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mConsolidatedPresence != presence) {
mConsolidatedPresence = presence;
emit consolidatedPresenceChanged(mConsolidatedPresence);
}
}
QDateTime FriendCore::getPresenceTimestamp() const {
return mPresenceTimestamp;
}
void FriendCore::setPresenceTimestamp(QDateTime presenceTimestamp) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mPresenceTimestamp != presenceTimestamp) {
mPresenceTimestamp = presenceTimestamp;
emit presenceTimestampChanged(mPresenceTimestamp);
}
}
QString FriendCore::getPictureUri() const {
return mPictureUri;
}
void FriendCore::setPictureUri(const QString &uri) {
if (mPictureUri != uri) {
mPictureUri = uri;
emit pictureUriChanged();
}
}
void FriendCore::onPictureUriChanged(QString uri) {
mPictureUri = uri;
emit pictureUriChanged();
}
bool FriendCore::getIsSaved() const {
return mIsSaved;
}
void FriendCore::setIsSaved(bool data) {
if (mIsSaved != data) {
mIsSaved = data;
emit isSavedChanged(mIsSaved);
}
}
void FriendCore::writeIntoModel(std::shared_ptr<FriendModel> model) const {
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
model->getFriend()->edit();
// needed to create the vcard if not created yet
model->setName(mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName);
auto core = CoreModel::getInstance()->getCore();
std::list<std::shared_ptr<linphone::Address>> addresses;
for (auto &addr : mAddressList) {
auto friendAddress = addr.toMap();
auto num =
linphone::Factory::get()->createAddress(Utils::appStringToCoreString(friendAddress["address"].toString()));
addresses.push_back(num);
}
model->resetAddresses(addresses);
model->setAddress(ToolModel::interpretUrl(mDefaultAddress));
std::list<std::shared_ptr<linphone::FriendPhoneNumber>> phones;
for (auto &number : mPhoneNumberList) {
auto friendAddress = number.toMap();
auto num = linphone::Factory::get()->createFriendPhoneNumber(
Utils::appStringToCoreString(friendAddress["address"].toString()),
Utils::appStringToCoreString(friendAddress["label"].toString()));
phones.push_back(num);
}
model->resetPhoneNumbers(phones);
model->setGivenName(mGivenName);
model->setFamilyName(mFamilyName);
model->setOrganization(mOrganization);
model->setJob(mJob);
model->setPictureUri(mPictureUri);
model->getFriend()->done();
}
void FriendCore::writeFromModel(const std::shared_ptr<FriendModel> &model) {
mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO);
QList<QVariant> addresses;
for (auto &addr : model->getAddresses()) {
addresses.append(
createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(addr->asStringUriOnly())));
}
mAddressList = addresses;
QList<QVariant> phones;
for (auto &number : model->getPhoneNumbers()) {
phones.append(createFriendAddressVariant(Utils::coreStringToAppString(number->getLabel()),
Utils::coreStringToAppString(number->getPhoneNumber())));
}
mPhoneNumberList = phones;
mGivenName = model->getGivenName();
mFamilyName = model->getFamilyName();
mOrganization = model->getOrganization();
mJob = model->getJob();
mPictureUri = model->getPictureUri();
}
void FriendCore::remove() {
if (mFriendModel) { // Update
mFriendModelConnection->invokeToModel([this]() {
auto contact = mFriendModel->getFriend();
contact->remove();
emit CoreModel::getInstance()->friendRemoved();
mFriendModelConnection->invokeToCore([this]() { removed(this); });
});
}
}
void FriendCore::save() { // Save Values to model
if (mAddressList.size() > 0) {
auto it = std::find_if(mAddressList.begin(), mAddressList.end(), [this](const QVariant &a) {
return a.toMap()["address"].toString() == mDefaultAddress;
});
if (it == mAddressList.end()) {
mDefaultAddress = mAddressList[0].toMap()["address"].toString();
emit defaultAddressChanged();
}
} else {
mDefaultAddress = "";
emit defaultAddressChanged();
}
FriendCore *thisCopy = new FriendCore(*this); // Pointer to avoid multiple copies in lambdas
if (mFriendModel) {
mFriendModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency
thisCopy->writeIntoModel(mFriendModel);
thisCopy->deleteLater();
mFriendModelConnection->invokeToCore([this]() { saved(); });
setIsSaved(true);
});
} else {
mCoreModelConnection->invokeToModel([this, thisCopy]() {
std::shared_ptr<linphone::Friend> contact;
auto core = CoreModel::getInstance()->getCore();
for (auto &addr : mAddressList) {
auto friendAddress = addr.toMap();
auto linphoneAddr = ToolModel::interpretUrl(friendAddress["address"].toString());
contact = core->findFriend(linphoneAddr);
if (contact) break;
}
if (contact != nullptr) {
auto friendModel = Utils::makeQObject_ptr<FriendModel>(contact);
friendModel->setSelf(friendModel);
thisCopy->writeIntoModel(friendModel);
thisCopy->deleteLater();
if (mFriendModelConnection) mFriendModelConnection->invokeToCore([this] { saved(); });
else mCoreModelConnection->invokeToCore([this] { saved(); });
} else {
auto contact = core->createFriend();
std::shared_ptr<FriendModel> friendModel;
friendModel = Utils::makeQObject_ptr<FriendModel>(contact);
friendModel->setSelf(friendModel);
thisCopy->writeIntoModel(friendModel);
thisCopy->deleteLater();
bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK);
if (created) {
core->getDefaultFriendList()->updateSubscriptions();
emit CoreModel::getInstance()->friendAdded();
}
mCoreModelConnection->invokeToCore([this, created]() {
if (created) setSelf(mCoreModelConnection->mCore);
setIsSaved(created);
});
}
});
}
}
void FriendCore::undo() { // Retrieve values from model
if (mFriendModel) {
mFriendModelConnection->invokeToModel([this]() {
FriendCore *contact = new FriendCore(*this);
contact->writeFromModel(mFriendModel);
contact->moveToThread(App::getInstance()->thread());
mFriendModelConnection->invokeToCore([this, contact]() mutable {
this->reset(*contact);
contact->deleteLater();
});
});
}
}