From 1938ae65e0f3510465b38ecf8804a6d87e12e829 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Fri, 24 Nov 2023 17:41:32 +0100 Subject: [PATCH] Feature: Contacts/Friends. - Add Gui/Core/Model for friends. - Add MagicSearch. - Fix double free on Account list. - Fix concurrency in SafeConnection destruction. - Update SDK : Use of onAccountAdded from SDK. --- Linphone/core/App.cpp | 6 + Linphone/core/CMakeLists.txt | 5 + Linphone/core/account/AccountList.cpp | 1 - Linphone/core/friend/FriendCore.cpp | 215 ++++++++++++++++++++ Linphone/core/friend/FriendCore.hpp | 106 ++++++++++ Linphone/core/friend/FriendGui.cpp | 41 ++++ Linphone/core/friend/FriendGui.hpp | 42 ++++ Linphone/core/search/MagicSearchList.cpp | 105 ++++++++++ Linphone/core/search/MagicSearchList.hpp | 57 ++++++ Linphone/core/search/MagicSearchProxy.cpp | 40 ++++ Linphone/core/search/MagicSearchProxy.hpp | 49 +++++ Linphone/model/CMakeLists.txt | 4 + Linphone/model/account/AccountManager.cpp | 1 - Linphone/model/core/CoreModel.cpp | 8 +- Linphone/model/core/CoreModel.hpp | 8 +- Linphone/model/friend/FriendModel.cpp | 52 +++++ Linphone/model/friend/FriendModel.hpp | 57 ++++++ Linphone/model/listener/Listener.hpp | 2 +- Linphone/model/search/MagicSearchModel.cpp | 58 ++++++ Linphone/model/search/MagicSearchModel.hpp | 58 ++++++ Linphone/tool/LinphoneEnums.cpp | 7 + Linphone/tool/LinphoneEnums.hpp | 11 + Linphone/tool/thread/SafeConnection.cpp | 5 +- Linphone/view/CMakeLists.txt | 1 + Linphone/view/Prototype/FriendPrototype.qml | 100 +++++++++ external/linphone-sdk | 2 +- 26 files changed, 1030 insertions(+), 11 deletions(-) create mode 100644 Linphone/core/friend/FriendCore.cpp create mode 100644 Linphone/core/friend/FriendCore.hpp create mode 100644 Linphone/core/friend/FriendGui.cpp create mode 100644 Linphone/core/friend/FriendGui.hpp create mode 100644 Linphone/core/search/MagicSearchList.cpp create mode 100644 Linphone/core/search/MagicSearchList.hpp create mode 100644 Linphone/core/search/MagicSearchProxy.cpp create mode 100644 Linphone/core/search/MagicSearchProxy.hpp create mode 100644 Linphone/model/friend/FriendModel.cpp create mode 100644 Linphone/model/friend/FriendModel.hpp create mode 100644 Linphone/model/search/MagicSearchModel.cpp create mode 100644 Linphone/model/search/MagicSearchModel.hpp create mode 100644 Linphone/view/Prototype/FriendPrototype.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 43db88310..11e1e1527 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -34,11 +34,14 @@ #include "core/account/AccountProxy.hpp" #include "core/call/CallCore.hpp" #include "core/call/CallGui.hpp" +#include "core/friend/FriendCore.hpp" +#include "core/friend/FriendGui.hpp" #include "core/logger/QtLogger.hpp" #include "core/login/LoginPage.hpp" #include "core/notifier/Notifier.hpp" #include "core/phone-number/PhoneNumber.hpp" #include "core/phone-number/PhoneNumberProxy.hpp" +#include "core/search/MagicSearchProxy.hpp" #include "core/singleapplication/singleapplication.h" #include "model/object/VariantObject.hpp" #include "tool/Constants.hpp" @@ -134,6 +137,9 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); + qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "MagicSearchProxy"); LinphoneEnums::registerMetaTypes(); } diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index bd8138e25..dd63f0bc9 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -6,6 +6,8 @@ list(APPEND _LINPHONEAPP_SOURCES core/App.cpp core/call/CallCore.cpp core/call/CallGui.cpp + core/friend/FriendCore.cpp + core/friend/FriendGui.cpp core/logger/QtLogger.cpp core/login/LoginPage.cpp core/notifier/Notifier.cpp @@ -14,6 +16,9 @@ list(APPEND _LINPHONEAPP_SOURCES core/phone-number/PhoneNumberList.cpp core/phone-number/PhoneNumberProxy.cpp + core/search/MagicSearchList.cpp + core/search/MagicSearchProxy.cpp + core/setting/Settings.cpp core/proxy/ListProxy.cpp diff --git a/Linphone/core/account/AccountList.cpp b/Linphone/core/account/AccountList.cpp index cbed1c2c3..2c08ef58b 100644 --- a/Linphone/core/account/AccountList.cpp +++ b/Linphone/core/account/AccountList.cpp @@ -45,7 +45,6 @@ AccountList::AccountList(QObject *parent) : ListProxy(parent) { AccountList::~AccountList() { qDebug() << "[AccountList] delete" << this; mustBeInMainThread("~" + getClassName()); - if (mModelConnection) mModelConnection->deleteLater(); } void AccountList::setSelf(QSharedPointer me) { diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp new file mode 100644 index 000000000..d9715d2e1 --- /dev/null +++ b/Linphone/core/friend/FriendCore.cpp @@ -0,0 +1,215 @@ +/* + * 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 . + */ + +#include "FriendCore.hpp" +#include "core/App.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +DEFINE_ABSTRACT_OBJECT(FriendCore) + +QSharedPointer FriendCore::create(const std::shared_ptr &contact) { + auto sharedPointer = QSharedPointer(new FriendCore(contact), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +FriendCore::FriendCore(const std::shared_ptr &contact) : QObject(nullptr) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + if (contact) { + mustBeInLinphoneThread(getClassName()); + mFriendModel = Utils::makeQObject_ptr(contact); + mFriendModel->setSelf(mFriendModel); + mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()); + mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); + auto address = contact->getAddress(); + mAddress = address ? Utils::coreStringToAppString(contact->getAddress()->asString()) : "NoAddress"; + mIsSaved = true; + } else mIsSaved = false; +} + +FriendCore::FriendCore(const FriendCore &friendCore) { + // Only copy friend values without models for lambda using and avoid concurrencies. + mAddress = friendCore.mAddress; + mIsSaved = friendCore.mIsSaved; +} + +FriendCore::~FriendCore() { +} + +void FriendCore::setSelf(QSharedPointer me) { + setSelf(me.objectCast()); +} + +void FriendCore::setSelf(SafeSharedPointer me) { + if (mFriendModel) { + mFriendModelConnection = QSharedPointer( + new SafeConnection(me, std::dynamic_pointer_cast(mFriendModel)), &QObject::deleteLater); + mFriendModelConnection->makeConnect( + mFriendModel.get(), &FriendModel::presenceReceived, + [this](LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp) { + mFriendModelConnection->invokeToCore([this, consolidatedPresence, presenceTimestamp]() { + setConsolidatedPresence(consolidatedPresence); + setPresenceTimestamp(presenceTimestamp); + }); + }); + + } else { // Create + mFriendModelConnection = QSharedPointer( + new SafeConnection(me, std::dynamic_pointer_cast(CoreModel::getInstance())), + &QObject::deleteLater); + } +} + +void FriendCore::reset(const FriendCore &contact) { + setAddress(contact.getAddress()); + setName(contact.getName()); + setIsSaved(mFriendModel != nullptr); +} + +QString FriendCore::getName() const { + return mName; +} + +void FriendCore::setName(QString data) { + if (mName != data) { + mName = data; + emit addressChanged(mName); + setIsSaved(false); + } +} + +QString FriendCore::getAddress() const { + return mAddress; +} + +void FriendCore::setAddress(QString address) { + if (mAddress != address) { + mAddress = address; + emit addressChanged(mAddress); + setIsSaved(false); + } +} + +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); + } +} + +bool FriendCore::getIsSaved() const { + return mIsSaved; +} +void FriendCore::setIsSaved(bool data) { + if (mIsSaved != data) { + mIsSaved = data; + emit isSavedChanged(mIsSaved); + } +} + +void FriendCore::writeInto(std::shared_ptr contact) const { + mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO); + auto core = CoreModel::getInstance()->getCore(); + auto newAddress = core->createAddress(Utils::appStringToCoreString(mAddress)); + contact->edit(); + if (newAddress) contact->setAddress(newAddress); + else qDebug() << "Bad address : " << mAddress; + contact->done(); +} + +void FriendCore::writeFrom(const std::shared_ptr &contact) { + mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO); + auto address = contact->getAddress(); + mAddress = (address ? Utils::coreStringToAppString(address->asString()) : ""); + mName = Utils::coreStringToAppString(contact->getName()); +} + +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 + FriendCore *thisCopy = new FriendCore(*this); // Pointer to avoid multiple copies in lambdas + if (mFriendModel) { // Update + mFriendModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency + auto core = CoreModel::getInstance()->getCore(); + auto contact = mFriendModel->getFriend(); + thisCopy->writeInto(contact); + thisCopy->deleteLater(); + mFriendModelConnection->invokeToCore([this]() { saved(); }); + }); + } else { // Creation + mFriendModelConnection->invokeToModel([this, thisCopy]() { + auto core = CoreModel::getInstance()->getCore(); + auto contact = core->createFriend(); + thisCopy->writeInto(contact); + thisCopy->deleteLater(); + bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); + if (created) { + mFriendModel = Utils::makeQObject_ptr(contact); + mFriendModel->setSelf(mFriendModel); + } + emit CoreModel::getInstance()->friendAdded(); + mFriendModelConnection->invokeToCore([this, created]() { + if (created) setSelf(mFriendModelConnection->mCore); + setIsSaved(created); + }); + }); + } +} + +void FriendCore::undo() { // Retrieve values from model + if (mFriendModel) { + mFriendModelConnection->invokeToModel([this]() { + FriendCore *contact = new FriendCore(*this); + contact->writeFrom(mFriendModel->getFriend()); + mFriendModelConnection->invokeToCore([this, contact]() { + this->reset(*contact); + contact->deleteLater(); + }); + }); + } +} diff --git a/Linphone/core/friend/FriendCore.hpp b/Linphone/core/friend/FriendCore.hpp new file mode 100644 index 000000000..c025bc569 --- /dev/null +++ b/Linphone/core/friend/FriendCore.hpp @@ -0,0 +1,106 @@ +/* + * 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 . + */ + +#ifndef FRIEND_CORE_H_ +#define FRIEND_CORE_H_ + +#include "model/friend/FriendModel.hpp" +#include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeSharedPointer.hpp" +#include +#include +#include +#include + +class SafeConnection; + +// This object is defferent from usual Core. It set internal data from directly from GUI. +// Values are saved on request. +// This allow revert feature. + +class FriendCore : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString address READ getAddress WRITE setAddress NOTIFY addressChanged) + Q_PROPERTY(QDateTime presenceTimestamp READ getPresenceTimestamp NOTIFY presenceTimestampChanged) + Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY + consolidatedPresenceChanged) + Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged) + +public: + // Should be call from model Thread. Will be automatically in App thread after initialization + static QSharedPointer create(const std::shared_ptr &contact); + FriendCore(const std::shared_ptr &contact); + FriendCore(const FriendCore &friendCore); + ~FriendCore(); + void setSelf(QSharedPointer me); + void setSelf(SafeSharedPointer me); + void reset(const FriendCore &contact); + + QString getName() const; + void setName(QString data); + + QString getAddress() const; + void setAddress(QString address); + + LinphoneEnums::ConsolidatedPresence getConsolidatedPresence() const; + void setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence); + + QDateTime getPresenceTimestamp() const; + void setPresenceTimestamp(QDateTime presenceTimestamp); + + bool getIsSaved() const; + void setIsSaved(bool isSaved); + + void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); + + Q_INVOKABLE void remove(); + Q_INVOKABLE void save(); + Q_INVOKABLE void undo(); + +signals: + void contactUpdated(); + void nameChanged(QString name); + void addressChanged(QString address); + void consolidatedPresenceChanged(LinphoneEnums::ConsolidatedPresence level); + void presenceTimestampChanged(QDateTime presenceTimestamp); + void sipAddressAdded(const QString &sipAddress); + void sipAddressRemoved(const QString &sipAddress); + void saved(); + void isSavedChanged(bool isSaved); + void removed(FriendCore *contact); + +protected: + void writeInto(std::shared_ptr contact) const; + void writeFrom(const std::shared_ptr &contact); + + LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline; + QDateTime mPresenceTimestamp; + QString mName; + QString mAddress; + bool mIsSaved; + std::shared_ptr mFriendModel; + QSharedPointer mFriendModelConnection; + + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(FriendCore *) +#endif diff --git a/Linphone/core/friend/FriendGui.cpp b/Linphone/core/friend/FriendGui.cpp new file mode 100644 index 000000000..738cb921b --- /dev/null +++ b/Linphone/core/friend/FriendGui.cpp @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#include "FriendGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(FriendGui) + +FriendGui::FriendGui(QObject *parent) : QObject(parent) { + mCore = FriendCore::create(nullptr); +} +FriendGui::FriendGui(QSharedPointer core) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; + if (isInLinphoneThread()) moveToThread(App::getInstance()->thread()); +} + +FriendGui::~FriendGui() { + mustBeInMainThread("~" + getClassName()); +} + +FriendCore *FriendGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/friend/FriendGui.hpp b/Linphone/core/friend/FriendGui.hpp new file mode 100644 index 000000000..d98e0d6c1 --- /dev/null +++ b/Linphone/core/friend/FriendGui.hpp @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +#ifndef FRIEND_GUI_H_ +#define FRIEND_GUI_H_ + +#include "FriendCore.hpp" +#include +#include + +class FriendGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(FriendCore *core READ getCore CONSTANT) + +public: + FriendGui(QObject *parent = nullptr); + FriendGui(QSharedPointer core); + ~FriendGui(); + FriendCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/search/MagicSearchList.cpp b/Linphone/core/search/MagicSearchList.cpp new file mode 100644 index 000000000..3ae465169 --- /dev/null +++ b/Linphone/core/search/MagicSearchList.cpp @@ -0,0 +1,105 @@ +/* + * 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 . + */ + +#include "MagicSearchList.hpp" +#include "core/App.hpp" +#include "core/friend/FriendCore.hpp" +#include "core/friend/FriendGui.hpp" +#include "tool/Utils.hpp" +#include +#include + +// ============================================================================= + +DEFINE_ABSTRACT_OBJECT(MagicSearchList) + +QSharedPointer MagicSearchList::create() { + auto model = QSharedPointer(new MagicSearchList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +MagicSearchList::MagicSearchList(QObject *parent) : ListProxy(parent) { + mustBeInMainThread(getClassName()); + App::postModelSync([this]() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto linphoneSearch = CoreModel::getInstance()->getCore()->createMagicSearch(); + linphoneSearch->setLimitedSearch(false); + mMagicSearch = Utils::makeQObject_ptr(linphoneSearch); + mMagicSearch->mSourceFlags = + (int)linphone::MagicSearch::Source::Friends | (int)linphone::MagicSearch::Source::LdapServers; + mMagicSearch->mAggregationFlag = linphone::MagicSearch::Aggregation::Friend; + mMagicSearch->setSelf(mMagicSearch); + }); +} + +MagicSearchList::~MagicSearchList() { + mustBeInMainThread("~" + getClassName()); +} + +void MagicSearchList::setSelf(QSharedPointer me) { + mModelConnection = QSharedPointer( + new SafeConnection(me.objectCast(), std::dynamic_pointer_cast(mMagicSearch)), + &QObject::deleteLater); + mModelConnection->makeConnect(this, &MagicSearchList::lSearch, [this](QString filter) { + mModelConnection->invokeToModel([this, filter]() { mMagicSearch->search(filter); }); + }); + mModelConnection->makeConnect(mMagicSearch.get(), &MagicSearchModel::searchResultsReceived, + [this](const std::list> &results) { + auto *contacts = new QList>(); + for (auto it : results) { + QSharedPointer contact; + if (it->getFriend()) { + contact = FriendCore::create(it->getFriend()); + contacts->append(contact); + } + } + mModelConnection->invokeToCore([this, contacts]() { + setResults(*contacts); + delete contacts; + }); + }); +} + +void MagicSearchList::setResults(const QList> &contacts) { + resetData(); + for (auto it : contacts) { + connect(it.get(), &FriendCore::removed, this, qOverload(&MagicSearchList::remove)); + } + add(contacts); +} + +void MagicSearchList::setSearch(const QString &search) { + if (!search.isEmpty()) { + lSearch(search); + } else { + beginResetModel(); + mList.clear(); + endResetModel(); + } +} + +QVariant MagicSearchList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) return QVariant::fromValue(new FriendGui(mList[row].objectCast())); + return QVariant(); +} diff --git a/Linphone/core/search/MagicSearchList.hpp b/Linphone/core/search/MagicSearchList.hpp new file mode 100644 index 000000000..8316b0d6a --- /dev/null +++ b/Linphone/core/search/MagicSearchList.hpp @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +#ifndef MAGIC_SEARCH_LIST_H_ +#define MAGIC_SEARCH_LIST_H_ + +#include "../proxy/ListProxy.hpp" +#include "model/search/MagicSearchModel.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/thread/SafeConnection.hpp" +#include + +class FriendCore; +// ============================================================================= + +// Return FriendGui list to Ui +class MagicSearchList : public ListProxy, public AbstractObject { + Q_OBJECT +public: + static QSharedPointer create(); + MagicSearchList(QObject *parent = Q_NULLPTR); + ~MagicSearchList(); + + void setSelf(QSharedPointer me); + void setSearch(const QString &search); + void setResults(const QList> &contacts); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + void lSearch(QString filter); + +private: + std::shared_ptr mMagicSearch; + QSharedPointer mModelConnection; + QSharedPointer mCoreModelConnection; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/search/MagicSearchProxy.cpp b/Linphone/core/search/MagicSearchProxy.cpp new file mode 100644 index 000000000..0845d4498 --- /dev/null +++ b/Linphone/core/search/MagicSearchProxy.cpp @@ -0,0 +1,40 @@ +/* + * 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 . + */ + +#include "MagicSearchProxy.hpp" +#include "MagicSearchList.hpp" + +MagicSearchProxy::MagicSearchProxy(QObject *parent) : SortFilterProxy(parent) { + mList = MagicSearchList::create(); + setSourceModel(mList.get()); + sort(0); +} + +MagicSearchProxy::~MagicSearchProxy() { +} + +QString MagicSearchProxy::getSearchText() const { + return mSearchText; +} + +void MagicSearchProxy::setSearchText(const QString &search) { + mSearchText = search; + qobject_cast(sourceModel())->setSearch(mSearchText); +} diff --git a/Linphone/core/search/MagicSearchProxy.hpp b/Linphone/core/search/MagicSearchProxy.hpp new file mode 100644 index 000000000..30b5b10f9 --- /dev/null +++ b/Linphone/core/search/MagicSearchProxy.hpp @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +#ifndef MAGIC_SEARCH_PROXY_H_ +#define MAGIC_SEARCH_PROXY_H_ + +#include "../proxy/SortFilterProxy.hpp" +#include "core/search/MagicSearchList.hpp" + +// ============================================================================= + +class MagicSearchProxy : public SortFilterProxy { + Q_OBJECT + + Q_PROPERTY(QString searchText READ getSearchText WRITE setSearchText NOTIFY searchTextChanged) + +public: + MagicSearchProxy(QObject *parent = Q_NULLPTR); + ~MagicSearchProxy(); + + QString getSearchText() const; + void setSearchText(const QString &search); + +signals: + void searchTextChanged(); + +protected: + QString mSearchText; + QSharedPointer mList; +}; + +#endif diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index aebbf6d7a..0516b0453 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -6,6 +6,8 @@ list(APPEND _LINPHONEAPP_SOURCES model/core/CoreModel.cpp + model/friend/FriendModel.cpp + model/listener/Listener.hpp model/logger/LoggerModel.cpp @@ -14,6 +16,8 @@ list(APPEND _LINPHONEAPP_SOURCES model/object/SafeObject.cpp model/object/VariantObject.cpp + model/search/MagicSearchModel.cpp + model/setting/SettingsModel.cpp model/tool/ToolModel.cpp diff --git a/Linphone/model/account/AccountManager.cpp b/Linphone/model/account/AccountManager.cpp index 8eb5c8376..06c5d8d42 100644 --- a/Linphone/model/account/AccountManager.cpp +++ b/Linphone/model/account/AccountManager.cpp @@ -79,7 +79,6 @@ bool AccountManager::login(QString username, QString password) { connect(mAccountModel.get(), &AccountModel::registrationStateChanged, this, &AccountManager::onRegistrationStateChanged); core->addAccount(account); - emit CoreModel::getInstance()->accountAdded(); return true; } diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index fe0ab9f7d..68bd0fe60 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -139,10 +139,6 @@ void CoreModel::setPathAfterStart() { //--------------------------------------------------------------------------------------------------------------------------- -void CoreModel::onAccountAdded() { - emit accountAdded(); -} - void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -238,6 +234,10 @@ void CoreModel::onMessagesReceived(const std::shared_ptr &core, const std::list> &messages) { emit messagesReceived(core, room, messages); } +void CoreModel::onNewAccountAdded(const std::shared_ptr &core, + const std::shared_ptr &account) { + emit accountAdded(core, account); +} void CoreModel::onNewMessageReaction(const std::shared_ptr &core, const std::shared_ptr &chatRoom, const std::shared_ptr &message, diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index bdbfd7a8d..0e134dc79 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -57,6 +57,8 @@ public: signals: void loggerInitialized(); void defaultAccountChanged(); + void friendAdded(); + void friendRemoved(); private: QString mConfigPath; @@ -72,7 +74,6 @@ private: //-------------------------------------------------------------------------------- // LINPHONE //-------------------------------------------------------------------------------- - virtual void onAccountAdded(); // override (Not yet implemented by SDK) virtual void onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -129,6 +130,9 @@ private: virtual void onMessagesReceived(const std::shared_ptr &core, const std::shared_ptr &room, const std::list> &messages) override; + virtual void onNewAccountAdded(const std::shared_ptr &core, + const std::shared_ptr &account) override; + virtual void onNewMessageReaction(const std::shared_ptr &core, const std::shared_ptr &chatRoom, const std::shared_ptr &message, @@ -154,7 +158,7 @@ private: const std::string &url) override; signals: - void accountAdded(); + void accountAdded(const std::shared_ptr &core, const std::shared_ptr &account); void accountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, diff --git a/Linphone/model/friend/FriendModel.cpp b/Linphone/model/friend/FriendModel.cpp new file mode 100644 index 000000000..784591d60 --- /dev/null +++ b/Linphone/model/friend/FriendModel.cpp @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +#include "FriendModel.hpp" + +#include + +#include "model/core/CoreModel.hpp" + +DEFINE_ABSTRACT_OBJECT(FriendModel) + +FriendModel::FriendModel(const std::shared_ptr &contact, QObject *parent) + : ::Listener(contact, parent) { + mustBeInLinphoneThread(getClassName()); +} + +FriendModel::~FriendModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +std::shared_ptr FriendModel::getFriend() const { + return mMonitor; +} + +QDateTime FriendModel::getPresenceTimestamp() const { + if (mMonitor->getPresenceModel()) { + time_t timestamp = mMonitor->getPresenceModel()->getLatestActivityTimestamp(); + if (timestamp == -1) return QDateTime(); + else return QDateTime::fromMSecsSinceEpoch(timestamp * 1000); + } else return QDateTime(); +} + +void FriendModel::onPresenceReceived(const std::shared_ptr &contact) { + emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp()); +} diff --git a/Linphone/model/friend/FriendModel.hpp b/Linphone/model/friend/FriendModel.hpp new file mode 100644 index 000000000..cd993d8f1 --- /dev/null +++ b/Linphone/model/friend/FriendModel.hpp @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +#ifndef FRIEND_MODEL_H_ +#define FRIEND_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" + +#include +#include +#include +#include + +class FriendModel : public ::Listener, + public linphone::FriendListener, + public AbstractObject { + Q_OBJECT +public: + FriendModel(const std::shared_ptr &contact, QObject *parent = nullptr); + ~FriendModel(); + + QDateTime getPresenceTimestamp() const; + + std::shared_ptr getFriend() const; + +private: + DECLARE_ABSTRACT_OBJECT + + //-------------------------------------------------------------------------------- + // LINPHONE + //-------------------------------------------------------------------------------- + virtual void onPresenceReceived(const std::shared_ptr &contact) override; + +signals: + void presenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); +}; + +#endif diff --git a/Linphone/model/listener/Listener.hpp b/Linphone/model/listener/Listener.hpp index 6ebe79608..ef9885149 100644 --- a/Linphone/model/listener/Listener.hpp +++ b/Linphone/model/listener/Listener.hpp @@ -45,7 +45,7 @@ public: } ~Listener() { qDebug() << "Destroying Listener"; - if (mMonitor && mSelf) mMonitor->removeListener(mSelf); + setSelf(nullptr); } virtual void onRemoveListener() { setSelf(nullptr); diff --git a/Linphone/model/search/MagicSearchModel.cpp b/Linphone/model/search/MagicSearchModel.cpp new file mode 100644 index 000000000..d9a3deb8a --- /dev/null +++ b/Linphone/model/search/MagicSearchModel.cpp @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#include "MagicSearchModel.hpp" + +#include + +#include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" + +DEFINE_ABSTRACT_OBJECT(MagicSearchModel) + +MagicSearchModel::MagicSearchModel(const std::shared_ptr &data, QObject *parent) + : ::Listener(data, parent) { + mustBeInLinphoneThread(getClassName()); + // Removed is managed by FriendCore that allow to remove result automatically. + // No need to restart a new search in this case + connect(CoreModel::getInstance().get(), &CoreModel::friendAdded, this, [this]() { + if (!mLastSearch.isEmpty()) search(mLastSearch); + }); +} + +MagicSearchModel::~MagicSearchModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +void MagicSearchModel::search(QString filter) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mLastSearch = filter; + mMonitor->getContactsListAsync(filter != "*" ? Utils::appStringToCoreString(filter) : "", "", mSourceFlags, + mAggregationFlag); +} + +void MagicSearchModel::onSearchResultsReceived(const std::shared_ptr &magicSearch) { + emit searchResultsReceived(magicSearch->getLastSearch()); +} + +void MagicSearchModel::onLdapHaveMoreResults(const std::shared_ptr &magicSearch, + const std::shared_ptr &ldap) { + // emit ldapHaveMoreResults(ldap); +} diff --git a/Linphone/model/search/MagicSearchModel.hpp b/Linphone/model/search/MagicSearchModel.hpp new file mode 100644 index 000000000..f478abf73 --- /dev/null +++ b/Linphone/model/search/MagicSearchModel.hpp @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef MAGIC_SEARCH_MODEL_H_ +#define MAGIC_SEARCH_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" + +#include +#include +#include + +class MagicSearchModel : public ::Listener, + public linphone::MagicSearchListener, + public AbstractObject { + Q_OBJECT +public: + MagicSearchModel(const std::shared_ptr &data, QObject *parent = nullptr); + ~MagicSearchModel(); + + void search(QString filter); + + int mSourceFlags = (int)linphone::MagicSearch::Source::All; + linphone::MagicSearch::Aggregation mAggregationFlag = linphone::MagicSearch::Aggregation::None; + QString mLastSearch; + +private: + DECLARE_ABSTRACT_OBJECT + + //-------------------------------------------------------------------------------- + // LINPHONE + //-------------------------------------------------------------------------------- + virtual void onSearchResultsReceived(const std::shared_ptr &magicSearch) override; + virtual void onLdapHaveMoreResults(const std::shared_ptr &magicSearch, + const std::shared_ptr &ldap) override; +signals: + void searchResultsReceived(const std::list> &results); +}; + +#endif diff --git a/Linphone/tool/LinphoneEnums.cpp b/Linphone/tool/LinphoneEnums.cpp index 1d9615acb..f583fdb7b 100644 --- a/Linphone/tool/LinphoneEnums.cpp +++ b/Linphone/tool/LinphoneEnums.cpp @@ -140,6 +140,13 @@ LinphoneEnums::ConferenceSchedulerState LinphoneEnums::fromLinphone(const linpho return static_cast(state); } +linphone::ConsolidatedPresence LinphoneEnums::toLinphone(const LinphoneEnums::ConsolidatedPresence &data) { + return static_cast(data); +} +LinphoneEnums::ConsolidatedPresence LinphoneEnums::fromLinphone(const linphone::ConsolidatedPresence &data) { + return static_cast(data); +} + linphone::LogLevel LinphoneEnums::toLinphone(const QtMsgType &data) { switch (data) { case QtDebugMsg: diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index 0662b0bb9..8e30ecb28 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -191,6 +191,17 @@ Q_ENUM_NS(ConferenceSchedulerState) linphone::ConferenceScheduler::State toLinphone(const LinphoneEnums::ConferenceSchedulerState &state); LinphoneEnums::ConferenceSchedulerState fromLinphone(const linphone::ConferenceScheduler::State &state); +enum class ConsolidatedPresence { + Online = int(linphone::ConsolidatedPresence::Online), + Busy = int(linphone::ConsolidatedPresence::Busy), + DoNotDisturb = int(linphone::ConsolidatedPresence::DoNotDisturb), + Offline = int(linphone::ConsolidatedPresence::Offline) +}; +Q_ENUM_NS(ConsolidatedPresence); + +linphone::ConsolidatedPresence toLinphone(const LinphoneEnums::ConsolidatedPresence &state); +LinphoneEnums::ConsolidatedPresence fromLinphone(const linphone::ConsolidatedPresence &state); + linphone::LogLevel toLinphone(const QtMsgType &data); QtMsgType fromLinphone(const linphone::LogLevel &data); diff --git a/Linphone/tool/thread/SafeConnection.cpp b/Linphone/tool/thread/SafeConnection.cpp index 9180d1c34..00f315812 100644 --- a/Linphone/tool/thread/SafeConnection.cpp +++ b/Linphone/tool/thread/SafeConnection.cpp @@ -24,6 +24,9 @@ SafeConnection::SafeConnection(SafeSharedPointer core, SafeSharedPointe : mCore(core), mModel(model) { } SafeConnection::~SafeConnection() { + mLocker.lock(); if (mCore.mCountRef != 0 || mModel.mCountRef != 0) - qCritical() << "[SafeConnection] Destruction while still having references"; + qCritical() << "[SafeConnection] Destruction while still having references. CoreRef=" << mCore.mCountRef + << "ModelRef=" << mModel.mCountRef; + mLocker.unlock(); } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index fe323b7b5..252db4179 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -46,6 +46,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Prototype/PhoneNumberPrototype.qml view/Prototype/AccountsPrototype.qml view/Prototype/CallPrototype.qml + view/Prototype/FriendPrototype.qml view/Prototype/ItemPrototype.qml ) diff --git a/Linphone/view/Prototype/FriendPrototype.qml b/Linphone/view/Prototype/FriendPrototype.qml new file mode 100644 index 000000000..9fdf6cd99 --- /dev/null +++ b/Linphone/view/Prototype/FriendPrototype.qml @@ -0,0 +1,100 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.0 +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +// Snippet +Window{ + id: mainItem + height: 800 + width: 1000 + visible: true + ColumnLayout{ + anchors.fill: parent + RowLayout{ + FriendGui{ + id: contact + } + TextInput{ + placeholderText: 'Name' + initialText: contact.core.name + onTextChanged: contact.core.name = text + } + TextInput{ + placeholderText: 'Address' + initialText: contact.core.address + onTextChanged: contact.core.address = text + } + Button { + text: 'Create' + onClicked: { + contact.core.save() + } + } + Text{ + text: 'IsSaved:'+contact.core.isSaved + } + } + ListView{ + id: friends + Layout.fillHeight: true + Layout.fillWidth: true + + model: MagicSearchProxy{ + id: search + searchText: '' + } + delegate: Rectangle{ + height: 50 + width: friends.width + RowLayout{ + anchors.fill: parent + Text{ + text: modelData.core.presenceTimestamp + " == " +modelData.core.consolidatedPresence + " / " + } + Button { + text: 'X' + onClicked: { + modelData.core.remove() + } + } + Text{ + text: modelData.core.address + } + TextInput{ + initialText: modelData.core.address + onTextChanged: if(modelData.core.address != text){ + modelData.core.address = text + resetText() + } + } + Text{ + text: 'IsSaved:'+modelData.core.isSaved + } + Button { + text: 'Revert' + onClicked: { + modelData.core.undo() + } + } + Button { + text: 'Save' + onClicked: { + modelData.core.save() + } + } + } + } + } + Button { + text: 'Get' + Layout.rightMargin: 20 + onClicked: { + search.searchText = '*' + } + } + } + +} + diff --git a/external/linphone-sdk b/external/linphone-sdk index a9a7ff8bb..477d4b7d5 160000 --- a/external/linphone-sdk +++ b/external/linphone-sdk @@ -1 +1 @@ -Subproject commit a9a7ff8bb52d7535033b0cb60e2113b9ee377664 +Subproject commit 477d4b7d56e256ce6581e6c31d12d3c7f49f9f9a