mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-02-01 11:39:26 +00:00
- do deleteLater on QObjects. - Queued QQuickFramebufferObject updates. - Clean Linphone SDK window ID on Camera deletion (Qml side, it cannot be done on C++ beacause of asynchroneous signals). - Do cleaning synchronization between calls window and fullscreen. - Avoid to rebuild all entries of ChatRoom when below minimum limits (keep this feature to avoid loading time when initiate the call).
272 lines
11 KiB
C++
272 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2010-2020 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 "PluginsManager.hpp"
|
|
//#include "ContactsImporterModel.hpp"
|
|
#include "../../../include/LinphoneApp/LinphonePlugin.hpp"
|
|
#include "../../../include/LinphoneApp/PluginDataAPI.hpp"
|
|
#include "../../../include/LinphoneApp/PluginNetworkHelper.hpp"
|
|
|
|
#include "utils/Utils.hpp"
|
|
#include "app/paths/Paths.hpp"
|
|
#include "components/contact/VcardModel.hpp"
|
|
#include "components/contacts/ContactsListModel.hpp"
|
|
#include "components/contacts/ContactsImporterListModel.hpp"
|
|
#include "components/contacts/ContactsImporterModel.hpp"
|
|
#include "components/core/CoreManager.hpp"
|
|
#include "components/sip-addresses/SipAddressesModel.hpp"
|
|
|
|
|
|
#include <QDir>
|
|
#include <QPluginLoader>
|
|
#include <QDebug>
|
|
#include <QJsonDocument>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
|
|
|
|
// =============================================================================
|
|
|
|
QMap<QString, QString> PluginsManager::gPluginsMap;
|
|
QString PluginsManager::gPluginsConfigSection = "AppPlugin";
|
|
|
|
PluginsManager::PluginsManager(QObject * parent) : QObject(parent){
|
|
}
|
|
|
|
QPluginLoader * PluginsManager::getPlugin(const QString &pluginIdentity){
|
|
QStringList pluginPaths = Paths::getPluginsAppFolders();// Get all paths
|
|
if( gPluginsMap.contains(pluginIdentity)){
|
|
for(int i = 0 ; i < pluginPaths.size() ; ++i) {
|
|
QString pluginPath = pluginPaths[i] +gPluginsMap[pluginIdentity];
|
|
QPluginLoader * loader = new QPluginLoader(pluginPath);
|
|
loader->setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload.
|
|
if( auto instance = loader->instance()) {
|
|
auto plugin = qobject_cast< LinphonePlugin* >(instance);
|
|
if (plugin )
|
|
return loader;
|
|
else{
|
|
qWarning() << loader->errorString();
|
|
loader->unload();
|
|
}
|
|
}
|
|
loader->deleteLater();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void * PluginsManager::createInstance(const QString &pluginIdentity){
|
|
void * dataInstance = nullptr;
|
|
LinphonePlugin * plugin = nullptr;
|
|
if( gPluginsMap.contains(pluginIdentity)){
|
|
QStringList pluginPaths = Paths::getPluginsAppFolders();
|
|
for(int i = 0 ; i < pluginPaths.size() ; ++i) {
|
|
QString pluginPath = pluginPaths[i] +gPluginsMap[pluginIdentity];
|
|
QPluginLoader * loader = new QPluginLoader(pluginPath);
|
|
loader->setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload.
|
|
if( auto instance = loader->instance()) {
|
|
plugin = qobject_cast< LinphonePlugin* >(instance);
|
|
if (plugin) {
|
|
try{
|
|
dataInstance = plugin->createInstance(CoreManager::getInstance()->getCore().get(), loader);
|
|
return dataInstance;
|
|
}catch(...){
|
|
loader->unload();
|
|
}
|
|
}else
|
|
loader->unload();
|
|
}
|
|
loader->deleteLater();
|
|
}
|
|
}
|
|
return dataInstance;
|
|
}
|
|
|
|
QJsonDocument PluginsManager::getJson(const QString &pluginIdentity){
|
|
QJsonDocument doc;
|
|
QPluginLoader * pluginLoader = getPlugin(pluginIdentity);
|
|
if( pluginLoader ){
|
|
auto instance = pluginLoader->instance();
|
|
if( instance ){
|
|
LinphonePlugin * plugin = qobject_cast< LinphonePlugin* >(instance);
|
|
if( plugin ){
|
|
doc = QJsonDocument::fromJson(plugin->getGUIDescriptionToJson().toUtf8());
|
|
}
|
|
}
|
|
pluginLoader->unload();
|
|
pluginLoader->deleteLater();
|
|
}
|
|
return doc;
|
|
}
|
|
QList<PluginsModel*> PluginsManager::getImporterModels(const QStringList &capabilities){
|
|
QList<PluginsModel*> models;
|
|
for(int i = 0 ; i < capabilities.size() ; ++i){
|
|
if( capabilities[i] == "CONTACTS")
|
|
models += CoreManager::getInstance()->getContactsImporterListModel()->getList();
|
|
}
|
|
return models;
|
|
}
|
|
void PluginsManager::openNewPlugin(const QString &pTitle){
|
|
|
|
QString fileName = QFileDialog::getOpenFileName(nullptr, pTitle);
|
|
QString pluginIdentity;
|
|
QStringList capabilities;
|
|
QList<PluginsModel*> modelsToReset;
|
|
int doCopy = QMessageBox::Yes;
|
|
bool cannotRemovePlugin = false;
|
|
//QVersionNumber pluginVersion, apiVersion = LinphonePlugin::gPluginVersion;
|
|
if(fileName != ""){
|
|
QFileInfo fileInfo(fileName);
|
|
QString path = Utils::coreStringToAppString(Paths::getPluginsAppDirPath());
|
|
if( !QLibrary::isLibrary(fileName)){
|
|
QMessageBox::information(nullptr, pTitle, "The file is not a plugin");
|
|
}else{
|
|
QPluginLoader loader(fileName);
|
|
loader.setLoadHints(0);
|
|
QJsonObject metaData = loader.metaData()["MetaData"].toObject();
|
|
if( metaData.contains("ID") && metaData.contains("Capabilities")){
|
|
capabilities = metaData["Capabilities"].toString().toUpper().remove(' ').split(",");
|
|
pluginIdentity = metaData["ID"].toString();
|
|
}
|
|
if(!pluginIdentity.isEmpty()){// Check all plugins that have this title
|
|
QStringList oldPlugins;
|
|
if( gPluginsMap.contains(pluginIdentity))
|
|
oldPlugins << gPluginsMap[pluginIdentity];
|
|
if( QFile::exists(path+fileInfo.fileName()))
|
|
oldPlugins << path+fileInfo.fileName();
|
|
if(oldPlugins.size() > 0){
|
|
doCopy = QMessageBox::question(nullptr, pTitle, "The plugin already exists. Do you want to overwrite it?\n"+oldPlugins.join('\n'), QMessageBox::Yes, QMessageBox::No);
|
|
if( doCopy == QMessageBox::Yes){
|
|
if(gPluginsMap.contains(pluginIdentity)){
|
|
auto importers = CoreManager::getInstance()->getContactsImporterListModel()->getList();
|
|
for(auto importer : importers){
|
|
QJsonObject pluginMetaData(importer->getDataAPI()->getPluginLoader()->metaData());
|
|
if( pluginMetaData.contains("ID") && pluginMetaData["ID"].toString() == pluginIdentity){
|
|
importer->setDataAPI(nullptr);
|
|
modelsToReset.append(importer);
|
|
}
|
|
}
|
|
QStringList pluginPaths = Paths::getPluginsAppFolders();
|
|
for(int i = 0 ; !cannotRemovePlugin && i < pluginPaths.size()-1 ; ++i) {// Ignore the last path as it is the app folder
|
|
QString pluginPath = pluginPaths[i];
|
|
if(QFile::exists(pluginPath+gPluginsMap[pluginIdentity])){
|
|
cannotRemovePlugin = !QFile::remove(pluginPath+gPluginsMap[pluginIdentity]);
|
|
}
|
|
}
|
|
}
|
|
if(!cannotRemovePlugin && QFile::exists(path+fileInfo.fileName()))
|
|
cannotRemovePlugin = !QFile::remove(path+fileInfo.fileName());
|
|
if(!cannotRemovePlugin)
|
|
gPluginsMap[pluginIdentity] = "";
|
|
}
|
|
}
|
|
}else
|
|
doCopy = QMessageBox::No;
|
|
if(doCopy == QMessageBox::Yes ){
|
|
|
|
if( cannotRemovePlugin)// Qt will not unload library from memory so files cannot be removed. See https://bugreports.qt.io/browse/QTBUG-68880
|
|
QMessageBox::information(nullptr, pTitle, "The plugin cannot be replaced. You have to exit the application and delete manually the plugin file in\n"+path);
|
|
else if( !QFile::copy(fileName, path+fileInfo.fileName()))
|
|
QMessageBox::information(nullptr, pTitle, "The plugin cannot be copied. You have to copy manually the plugin file to\n"+path);
|
|
else {
|
|
gPluginsMap[pluginIdentity] = fileInfo.fileName();
|
|
for(auto importer : modelsToReset)
|
|
importer->setDataAPI(static_cast<PluginDataAPI*>(createInstance(pluginIdentity)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariantList PluginsManager::getPlugins(const int& capabilities) {
|
|
QVariantList plugins;
|
|
QStringList pluginPaths = Paths::getPluginsAppFolders();
|
|
if(capabilities<0)
|
|
gPluginsMap.clear();
|
|
for(int pathIndex = pluginPaths.size()-1 ; pathIndex >= 0 ; --pathIndex) {// Start from app package. This sort ensure the priority on user plugins
|
|
QString pluginPath = pluginPaths[pathIndex];
|
|
QDir dir(pluginPath);
|
|
QStringList pluginFiles = dir.entryList(QDir::Files);
|
|
for(int i = 0 ; i < pluginFiles.size() ; ++i) {
|
|
if( QLibrary::isLibrary(pluginPath+pluginFiles[i])){
|
|
QPluginLoader loader(pluginPath+pluginFiles[i]);
|
|
loader.setLoadHints(0); // this force Qt to unload the plugin from memory when we request it. Be carefull by not having a plugin instance or data created inside the plugin after the unload.
|
|
if (auto instance = loader.instance()) {
|
|
LinphonePlugin * plugin = qobject_cast< LinphonePlugin* >(instance);
|
|
if ( plugin){
|
|
QJsonObject metaData = loader.metaData()["MetaData"].toObject();
|
|
if( metaData.contains("ID")){
|
|
bool getIt = false;
|
|
if(capabilities>=0 ){
|
|
if(metaData.contains("Capabilities")){
|
|
QString pluginCapabilities = metaData["Capabilities"].toString().toUpper().remove(' ');
|
|
if( (capabilities & PluginDataAPI::CONTACTS) == PluginDataAPI::CONTACTS && pluginCapabilities.contains("CONTACTS")){
|
|
getIt = true;
|
|
}
|
|
}else
|
|
qWarning()<< "The plugin " << pluginFiles[i] << " must have Capabilities in its metadata";
|
|
}else
|
|
getIt = true;
|
|
if(getIt){
|
|
QJsonDocument doc = QJsonDocument::fromJson(plugin->getGUIDescriptionToJson().toUtf8());
|
|
QVariantMap desc;
|
|
desc["pluginTitle"] = doc["pluginTitle"];
|
|
desc["pluginID"] = metaData["ID"].toString();
|
|
if(!doc["pluginTitle"].toString().isEmpty()){
|
|
gPluginsMap[metaData["ID"].toString()] = pluginFiles[i];
|
|
plugins.push_back(desc);
|
|
}
|
|
}
|
|
}else
|
|
qWarning()<< "The plugin " << pluginFiles[i] << " must have ID in its metadata";
|
|
} else {
|
|
qWarning()<< "The plugin " << pluginFiles[i] << " should be updated to this version of API : " << loader.metaData()["IID"].toString();
|
|
}
|
|
loader.unload();
|
|
} else {
|
|
qWarning()<< "The plugin " << pluginFiles[i] << " cannot be used : " << loader.errorString();
|
|
}
|
|
}
|
|
}
|
|
std::sort(plugins.begin(), plugins.end());
|
|
}
|
|
return plugins;
|
|
}
|
|
QVariantMap PluginsManager::getPluginDescription(const QString& pluginIdentity) {
|
|
QVariantMap description;
|
|
QJsonDocument doc = getJson(pluginIdentity);
|
|
description = doc.toVariant().toMap();
|
|
return description;
|
|
}
|
|
|
|
|
|
QVariantMap PluginsManager::getDefaultValues(const QString& pluginIdentity){
|
|
QVariantMap defaultValues;
|
|
QVariantMap description;
|
|
QJsonDocument doc = getJson(pluginIdentity);
|
|
description = doc.toVariant().toMap();
|
|
for(auto field : description["fields"].toList()){
|
|
auto details = field.toMap();
|
|
if( details.contains("fieldId") && details.contains("defaultData")){
|
|
defaultValues[details["fieldId"].toString()] = details["defaultData"].toString();
|
|
}
|
|
}
|
|
return defaultValues;
|
|
}
|