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.
This commit is contained in:
Julien Wadel 2023-11-24 17:41:32 +01:00
parent 9ec84bb168
commit 1938ae65e0
26 changed files with 1030 additions and 11 deletions

View file

@ -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<AccountCore>(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable"));
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
qmlRegisterUncreatableType<CallCore>(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable"));
qmlRegisterType<FriendGui>(Constants::MainQmlUri, 1, 0, "FriendGui");
qmlRegisterUncreatableType<FriendCore>(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable"));
qmlRegisterType<MagicSearchProxy>(Constants::MainQmlUri, 1, 0, "MagicSearchProxy");
LinphoneEnums::registerMetaTypes();
}

View file

@ -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

View file

@ -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<AccountList> me) {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "FriendCore.hpp"
#include "core/App.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
DEFINE_ABSTRACT_OBJECT(FriendCore)
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();
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<FriendCore> me) {
setSelf(me.objectCast<QObject>());
}
void FriendCore::setSelf(SafeSharedPointer<QObject> me) {
if (mFriendModel) {
mFriendModelConnection = QSharedPointer<SafeConnection>(
new SafeConnection(me, std::dynamic_pointer_cast<QObject>(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<SafeConnection>(
new SafeConnection(me, std::dynamic_pointer_cast<QObject>(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<linphone::Friend> 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<linphone::Friend> &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<FriendModel>(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();
});
});
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef FRIEND_CORE_H_
#define FRIEND_CORE_H_
#include "model/friend/FriendModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeSharedPointer.hpp"
#include <QDateTime>
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
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<FriendCore> create(const std::shared_ptr<linphone::Friend> &contact);
FriendCore(const std::shared_ptr<linphone::Friend> &contact);
FriendCore(const FriendCore &friendCore);
~FriendCore();
void setSelf(QSharedPointer<FriendCore> me);
void setSelf(SafeSharedPointer<QObject> 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<linphone::Friend> contact) const;
void writeFrom(const std::shared_ptr<linphone::Friend> &contact);
LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline;
QDateTime mPresenceTimestamp;
QString mName;
QString mAddress;
bool mIsSaved;
std::shared_ptr<FriendModel> mFriendModel;
QSharedPointer<SafeConnection> mFriendModelConnection;
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(FriendCore *)
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "FriendGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(FriendGui)
FriendGui::FriendGui(QObject *parent) : QObject(parent) {
mCore = FriendCore::create(nullptr);
}
FriendGui::FriendGui(QSharedPointer<FriendCore> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef FRIEND_GUI_H_
#define FRIEND_GUI_H_
#include "FriendCore.hpp"
#include <QObject>
#include <QSharedPointer>
class FriendGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(FriendCore *core READ getCore CONSTANT)
public:
FriendGui(QObject *parent = nullptr);
FriendGui(QSharedPointer<FriendCore> core);
~FriendGui();
FriendCore *getCore() const;
QSharedPointer<FriendCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "MagicSearchList.hpp"
#include "core/App.hpp"
#include "core/friend/FriendCore.hpp"
#include "core/friend/FriendGui.hpp"
#include "tool/Utils.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(MagicSearchList)
QSharedPointer<MagicSearchList> MagicSearchList::create() {
auto model = QSharedPointer<MagicSearchList>(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<MagicSearchModel>(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<MagicSearchList> me) {
mModelConnection = QSharedPointer<SafeConnection>(
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(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<std::shared_ptr<linphone::SearchResult>> &results) {
auto *contacts = new QList<QSharedPointer<FriendCore>>();
for (auto it : results) {
QSharedPointer<FriendCore> 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<QSharedPointer<FriendCore>> &contacts) {
resetData();
for (auto it : contacts) {
connect(it.get(), &FriendCore::removed, this, qOverload<QObject *>(&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<FriendCore>()));
return QVariant();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <QLocale>
class FriendCore;
// =============================================================================
// Return FriendGui list to Ui
class MagicSearchList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<MagicSearchList> create();
MagicSearchList(QObject *parent = Q_NULLPTR);
~MagicSearchList();
void setSelf(QSharedPointer<MagicSearchList> me);
void setSearch(const QString &search);
void setResults(const QList<QSharedPointer<FriendCore>> &contacts);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lSearch(QString filter);
private:
std::shared_ptr<MagicSearchModel> mMagicSearch;
QSharedPointer<SafeConnection> mModelConnection;
QSharedPointer<SafeConnection> mCoreModelConnection;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<MagicSearchList *>(sourceModel())->setSearch(mSearchText);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<MagicSearchList> mList;
};
#endif

View file

@ -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

View file

@ -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;
}

View file

@ -139,10 +139,6 @@ void CoreModel::setPathAfterStart() {
//---------------------------------------------------------------------------------------------------------------------------
void CoreModel::onAccountAdded() {
emit accountAdded();
}
void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -238,6 +234,10 @@ void CoreModel::onMessagesReceived(const std::shared_ptr<linphone::Core> &core,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) {
emit messagesReceived(core, room, messages);
}
void CoreModel::onNewAccountAdded(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) {
emit accountAdded(core, account);
}
void CoreModel::onNewMessageReaction(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<linphone::ChatMessage> &message,

View file

@ -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<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -129,6 +130,9 @@ private:
virtual void onMessagesReceived(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) override;
virtual void onNewAccountAdded(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) override;
virtual void onNewMessageReaction(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<linphone::ChatMessage> &message,
@ -154,7 +158,7 @@ private:
const std::string &url) override;
signals:
void accountAdded();
void accountAdded(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account);
void accountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "FriendModel.hpp"
#include <QDebug>
#include "model/core/CoreModel.hpp"
DEFINE_ABSTRACT_OBJECT(FriendModel)
FriendModel::FriendModel(const std::shared_ptr<linphone::Friend> &contact, QObject *parent)
: ::Listener<linphone::Friend, linphone::FriendListener>(contact, parent) {
mustBeInLinphoneThread(getClassName());
}
FriendModel::~FriendModel() {
mustBeInLinphoneThread("~" + getClassName());
}
std::shared_ptr<linphone::Friend> 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<linphone::Friend> &contact) {
emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp());
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef FRIEND_MODEL_H_
#define FRIEND_MODEL_H_
#include "model/listener/Listener.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/LinphoneEnums.hpp"
#include <QDateTime>
#include <QObject>
#include <QTimer>
#include <linphone++/linphone.hh>
class FriendModel : public ::Listener<linphone::Friend, linphone::FriendListener>,
public linphone::FriendListener,
public AbstractObject {
Q_OBJECT
public:
FriendModel(const std::shared_ptr<linphone::Friend> &contact, QObject *parent = nullptr);
~FriendModel();
QDateTime getPresenceTimestamp() const;
std::shared_ptr<linphone::Friend> getFriend() const;
private:
DECLARE_ABSTRACT_OBJECT
//--------------------------------------------------------------------------------
// LINPHONE
//--------------------------------------------------------------------------------
virtual void onPresenceReceived(const std::shared_ptr<linphone::Friend> &contact) override;
signals:
void presenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp);
};
#endif

View file

@ -45,7 +45,7 @@ public:
}
~Listener() {
qDebug() << "Destroying Listener";
if (mMonitor && mSelf) mMonitor->removeListener(mSelf);
setSelf(nullptr);
}
virtual void onRemoveListener() {
setSelf(nullptr);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "MagicSearchModel.hpp"
#include <QDebug>
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(MagicSearchModel)
MagicSearchModel::MagicSearchModel(const std::shared_ptr<linphone::MagicSearch> &data, QObject *parent)
: ::Listener<linphone::MagicSearch, linphone::MagicSearchListener>(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<linphone::MagicSearch> &magicSearch) {
emit searchResultsReceived(magicSearch->getLastSearch());
}
void MagicSearchModel::onLdapHaveMoreResults(const std::shared_ptr<linphone::MagicSearch> &magicSearch,
const std::shared_ptr<linphone::Ldap> &ldap) {
// emit ldapHaveMoreResults(ldap);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef MAGIC_SEARCH_MODEL_H_
#define MAGIC_SEARCH_MODEL_H_
#include "model/listener/Listener.hpp"
#include "tool/AbstractObject.hpp"
#include <QObject>
#include <QTimer>
#include <linphone++/linphone.hh>
class MagicSearchModel : public ::Listener<linphone::MagicSearch, linphone::MagicSearchListener>,
public linphone::MagicSearchListener,
public AbstractObject {
Q_OBJECT
public:
MagicSearchModel(const std::shared_ptr<linphone::MagicSearch> &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<linphone::MagicSearch> &magicSearch) override;
virtual void onLdapHaveMoreResults(const std::shared_ptr<linphone::MagicSearch> &magicSearch,
const std::shared_ptr<linphone::Ldap> &ldap) override;
signals:
void searchResultsReceived(const std::list<std::shared_ptr<linphone::SearchResult>> &results);
};
#endif

View file

@ -140,6 +140,13 @@ LinphoneEnums::ConferenceSchedulerState LinphoneEnums::fromLinphone(const linpho
return static_cast<LinphoneEnums::ConferenceSchedulerState>(state);
}
linphone::ConsolidatedPresence LinphoneEnums::toLinphone(const LinphoneEnums::ConsolidatedPresence &data) {
return static_cast<linphone::ConsolidatedPresence>(data);
}
LinphoneEnums::ConsolidatedPresence LinphoneEnums::fromLinphone(const linphone::ConsolidatedPresence &data) {
return static_cast<LinphoneEnums::ConsolidatedPresence>(data);
}
linphone::LogLevel LinphoneEnums::toLinphone(const QtMsgType &data) {
switch (data) {
case QtDebugMsg:

View file

@ -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);

View file

@ -24,6 +24,9 @@ SafeConnection::SafeConnection(SafeSharedPointer<QObject> 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();
}

View file

@ -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
)

View file

@ -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 = '*'
}
}
}
}

@ -1 +1 @@
Subproject commit a9a7ff8bb52d7535033b0cb60e2113b9ee377664
Subproject commit 477d4b7d56e256ce6581e6c31d12d3c7f49f9f9a