mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-04-21 21:52:03 +02:00
updateManager: create a plugin store and a plugin manager
Change-Id: I57ebec72c1cb6e2f245af011def82f880bc9573f
This commit is contained in:
parent
7f2c98a594
commit
7581f9397a
25 changed files with 1148 additions and 38 deletions
|
@ -206,6 +206,7 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/pluginadapter.cpp
|
||||
${APP_SRC_DIR}/deviceitemlistmodel.cpp
|
||||
${APP_SRC_DIR}/pluginlistmodel.cpp
|
||||
${APP_SRC_DIR}/pluginstorelistmodel.cpp
|
||||
${APP_SRC_DIR}/pluginhandlerlistmodel.cpp
|
||||
${APP_SRC_DIR}/preferenceitemlistmodel.cpp
|
||||
${APP_SRC_DIR}/mediacodeclistmodel.cpp
|
||||
|
@ -239,7 +240,8 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/currentcall.cpp
|
||||
${APP_SRC_DIR}/messageparser.cpp
|
||||
${APP_SRC_DIR}/previewengine.cpp
|
||||
${APP_SRC_DIR}/imagedownloader.cpp)
|
||||
${APP_SRC_DIR}/imagedownloader.cpp
|
||||
${APP_SRC_DIR}/pluginversionmanager.cpp)
|
||||
|
||||
set(COMMON_HEADERS
|
||||
${APP_SRC_DIR}/avatarimageprovider.h
|
||||
|
@ -267,6 +269,7 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/pluginadapter.h
|
||||
${APP_SRC_DIR}/deviceitemlistmodel.h
|
||||
${APP_SRC_DIR}/pluginlistmodel.h
|
||||
${APP_SRC_DIR}/pluginstorelistmodel.h
|
||||
${APP_SRC_DIR}/pluginhandlerlistmodel.h
|
||||
${APP_SRC_DIR}/preferenceitemlistmodel.h
|
||||
${APP_SRC_DIR}/mediacodeclistmodel.h
|
||||
|
@ -303,7 +306,8 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/currentcall.h
|
||||
${APP_SRC_DIR}/messageparser.h
|
||||
${APP_SRC_DIR}/htmlparser.h
|
||||
${APP_SRC_DIR}/imagedownloader.h)
|
||||
${APP_SRC_DIR}/imagedownloader.h
|
||||
${APP_SRC_DIR}/pluginversionmanager.h)
|
||||
|
||||
|
||||
# For libavutil/avframe.
|
||||
|
|
|
@ -49,6 +49,7 @@ extern const QString defaultDownloadPath;
|
|||
X(HideSelf, false) \
|
||||
X(HideSpectators, false) \
|
||||
X(AutoUpdate, true) \
|
||||
X(PluginAutoUpdate, false) \
|
||||
X(StartMinimized, false) \
|
||||
X(ShowChatviewHorizontally, true) \
|
||||
X(NeverShowMeAgain, false) \
|
||||
|
|
|
@ -166,8 +166,8 @@ AppVersionManager::AppVersionManager(const QString& url,
|
|||
LRCInstance* instance,
|
||||
QObject* parent)
|
||||
: NetworkManager(cm, parent)
|
||||
, pimpl_(std::make_unique<Impl>(url, instance, *this))
|
||||
, replyId_(new int(0))
|
||||
, pimpl_(std::make_unique<Impl>(url, instance, *this))
|
||||
{}
|
||||
|
||||
AppVersionManager::~AppVersionManager()
|
||||
|
|
|
@ -661,6 +661,7 @@ Item {
|
|||
property string enable: qsTr("Enable")
|
||||
property string pluginPreferences: qsTr("Preferences")
|
||||
property string reset: qsTr("Reset")
|
||||
property string disableAll: qsTr("Disable all")
|
||||
property string uninstall: qsTr("Uninstall")
|
||||
property string resetPreferences: qsTr("Reset Preferences")
|
||||
property string selectPluginInstall: qsTr("Select a plugin to install")
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "lrcinstance.h"
|
||||
#include "connectivitymonitor.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QMutex>
|
||||
|
@ -35,6 +36,7 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
|
|||
bool muteDaemon)
|
||||
: lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, !debugMode || muteDaemon))
|
||||
, updateManager_(std::make_unique<AppVersionManager>(updateUrl, connectivityMonitor, this))
|
||||
, connectivityMonitor_(*connectivityMonitor)
|
||||
, threadPool_(new QThreadPool(this))
|
||||
{
|
||||
debugMode_ = debugMode;
|
||||
|
@ -111,6 +113,12 @@ LRCInstance::pluginModel()
|
|||
return lrc_->getPluginModel();
|
||||
}
|
||||
|
||||
ConnectivityMonitor&
|
||||
LRCInstance::connectivityMonitor()
|
||||
{
|
||||
return connectivityMonitor_;
|
||||
}
|
||||
|
||||
bool
|
||||
LRCInstance::isConnected()
|
||||
{
|
||||
|
|
|
@ -79,6 +79,7 @@ public:
|
|||
ContactModel* getCurrentContactModel();
|
||||
AVModel& avModel();
|
||||
PluginModel& pluginModel();
|
||||
ConnectivityMonitor& connectivityMonitor();
|
||||
BehaviorController& behaviorController();
|
||||
|
||||
void subscribeToDebugReceived();
|
||||
|
@ -147,6 +148,8 @@ private:
|
|||
std::unique_ptr<Lrc> lrc_;
|
||||
std::unique_ptr<AppVersionManager> updateManager_;
|
||||
|
||||
ConnectivityMonitor& connectivityMonitor_;
|
||||
|
||||
QString selectedConvUid_;
|
||||
MapStringString contentDrafts_;
|
||||
MapStringString lastConferences_;
|
||||
|
|
|
@ -72,9 +72,10 @@ NetworkManager::sendGetRequest(const QUrl& url,
|
|||
|
||||
int
|
||||
NetworkManager::downloadFile(const QUrl& url,
|
||||
unsigned int replyId,
|
||||
int replyId,
|
||||
std::function<void(bool, const QString&)>&& onDoneCallback,
|
||||
const QString& filePath)
|
||||
const QString& filePath,
|
||||
const QString& extension)
|
||||
{
|
||||
// If there is already a download in progress, return.
|
||||
if ((downloadReplies_.value(replyId) != NULL || !(replyId == 0))
|
||||
|
@ -111,7 +112,7 @@ NetworkManager::downloadFile(const QUrl& url,
|
|||
const QFileInfo fileInfo(url.path());
|
||||
const QString fileName = fileInfo.fileName();
|
||||
auto& file = files_[uuid];
|
||||
file = new QFile(filePath + fileName + ".jpl");
|
||||
file = new QFile(filePath + fileName + extension);
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
Q_EMIT errorOccurred(GetError::ACCESS_DENIED);
|
||||
files_.remove(uuid);
|
||||
|
@ -122,8 +123,8 @@ NetworkManager::downloadFile(const QUrl& url,
|
|||
// Start the download.
|
||||
const QNetworkRequest request(url);
|
||||
|
||||
downloadReplies_[uuid] = manager_->get(request);
|
||||
auto* const reply = downloadReplies_[uuid];
|
||||
auto* const reply = manager_->get(request);
|
||||
downloadReplies_[uuid] = reply;
|
||||
connect(reply, &QNetworkReply::readyRead, this, [file, reply]() {
|
||||
if (file && file->isOpen()) {
|
||||
file->write(reply->readAll());
|
||||
|
@ -148,8 +149,10 @@ NetworkManager::downloadFile(const QUrl& url,
|
|||
Q_EMIT errorOccurred(GetError::NETWORK_ERROR);
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, uuid, onDoneCallback, reply]() {
|
||||
connect(reply, &QNetworkReply::finished, this, [this, uuid, onDoneCallback, reply, file]() {
|
||||
bool success = false;
|
||||
file->close();
|
||||
reply->deleteLater();
|
||||
QString errorMessage;
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
resetDownload(uuid);
|
||||
|
|
|
@ -41,9 +41,10 @@ public:
|
|||
void sendGetRequest(const QUrl& url, std::function<void(const QByteArray&)>&& onDoneCallback);
|
||||
|
||||
int downloadFile(const QUrl& url,
|
||||
unsigned int replyId,
|
||||
int replyId,
|
||||
std::function<void(bool, const QString&)>&& onDoneCallback,
|
||||
const QString& filePath);
|
||||
const QString& filePath,
|
||||
const QString& extension = "");
|
||||
void resetDownload(int replyId);
|
||||
void cancelDownload(int replyId);
|
||||
Q_SIGNALS:
|
||||
|
|
|
@ -20,9 +20,22 @@
|
|||
|
||||
#include "networkmanager.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "utilsadapter.h"
|
||||
|
||||
PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent)
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
|
||||
PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent, QString baseUrl)
|
||||
: QmlAdapterBase(instance, parent)
|
||||
, pluginStoreListModel_(new PluginStoreListModel(this))
|
||||
, pluginVersionManager_(new PluginVersionManager(instance, baseUrl, this))
|
||||
, pluginListModel_(new PluginListModel(this))
|
||||
, lrcInstance_(instance)
|
||||
, tempPath_(QDir::tempPath())
|
||||
, baseUrl_(baseUrl)
|
||||
|
||||
{
|
||||
set_isEnabled(lrcInstance_->pluginModel().getPluginsEnabled());
|
||||
|
@ -32,6 +45,73 @@ PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent)
|
|||
this,
|
||||
&PluginAdapter::updateHandlersListCount);
|
||||
connect(this, &PluginAdapter::isEnabledChanged, this, &PluginAdapter::updateHandlersListCount);
|
||||
connect(pluginVersionManager_,
|
||||
&PluginVersionManager::versionStatusChanged,
|
||||
pluginListModel_,
|
||||
&PluginListModel::onVersionStatusChanged);
|
||||
connect(pluginVersionManager_,
|
||||
&PluginVersionManager::versionStatusChanged,
|
||||
pluginStoreListModel_,
|
||||
&PluginStoreListModel::onVersionStatusChanged);
|
||||
connect(pluginStoreListModel_,
|
||||
&PluginStoreListModel::pluginAdded,
|
||||
this,
|
||||
&PluginAdapter::getPluginDetails);
|
||||
connect(pluginListModel_,
|
||||
&PluginListModel::versionCheckRequested,
|
||||
pluginVersionManager_,
|
||||
&PluginVersionManager::checkVersionStatus);
|
||||
connect(pluginListModel_,
|
||||
&PluginListModel::autoUpdateChanged,
|
||||
pluginVersionManager_,
|
||||
&PluginVersionManager::setAutoUpdate);
|
||||
connect(pluginListModel_,
|
||||
&PluginListModel::setVersionStatus,
|
||||
pluginStoreListModel_,
|
||||
&PluginStoreListModel::onVersionStatusChanged);
|
||||
getPluginsFromStore();
|
||||
}
|
||||
|
||||
void
|
||||
PluginAdapter::getPluginsFromStore()
|
||||
{
|
||||
pluginVersionManager_->sendGetRequest(QUrl(baseUrl_), [this](const QByteArray& data) {
|
||||
auto result = QJsonDocument::fromJson(data).array();
|
||||
auto pluginsInstalled = lrcInstance_->pluginModel().getPluginsId();
|
||||
QList<QVariantMap> plugins;
|
||||
for (const auto& plugin : result) {
|
||||
auto qPlugin = plugin.toVariant().toMap();
|
||||
if (!pluginsInstalled.contains(qPlugin["id"].toString())) {
|
||||
plugins.append(qPlugin);
|
||||
}
|
||||
}
|
||||
pluginStoreListModel_->setPlugins(plugins);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
PluginAdapter::getPluginDetails(const QString& pluginId)
|
||||
{
|
||||
pluginVersionManager_->sendGetRequest(QUrl(baseUrl_ + "/details/" + pluginId),
|
||||
[this](const QByteArray& data) {
|
||||
auto result = QJsonDocument::fromJson(data).object();
|
||||
// my response is a json object and I want to convert
|
||||
// it to a QVariantMap
|
||||
pluginStoreListModel_->addPlugin(
|
||||
result.toVariantMap());
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
PluginAdapter::installRemotePlugin(const QString& pluginId)
|
||||
{
|
||||
pluginVersionManager_->installRemotePlugin(pluginId);
|
||||
}
|
||||
|
||||
bool
|
||||
PluginAdapter::isAutoUpdaterEnabled()
|
||||
{
|
||||
return pluginVersionManager_->isAutoUpdaterEnabled();
|
||||
}
|
||||
|
||||
QVariant
|
||||
|
@ -77,3 +157,15 @@ PluginAdapter::updateHandlersListCount()
|
|||
set_chatHandlersListCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginAdapter::checkVersionStatus(const QString& pluginId)
|
||||
{
|
||||
pluginVersionManager_->checkVersionStatus(pluginId);
|
||||
}
|
||||
|
||||
QString
|
||||
PluginAdapter::baseUrl() const
|
||||
{
|
||||
return baseUrl_;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include "pluginlistmodel.h"
|
||||
#include "pluginhandlerlistmodel.h"
|
||||
#include "pluginlistpreferencemodel.h"
|
||||
#include "pluginversionmanager.h"
|
||||
#include "pluginstorelistmodel.h"
|
||||
#include "preferenceitemlistmodel.h"
|
||||
|
||||
#include <QObject>
|
||||
|
@ -36,9 +38,18 @@ class PluginAdapter final : public QmlAdapterBase
|
|||
QML_PROPERTY(bool, isEnabled)
|
||||
|
||||
public:
|
||||
explicit PluginAdapter(LRCInstance* instance, QObject* parent = nullptr);
|
||||
explicit PluginAdapter(LRCInstance* instance,
|
||||
QObject* parent = nullptr,
|
||||
QString baseUrl = "https://plugins.jami.net");
|
||||
~PluginAdapter() = default;
|
||||
|
||||
Q_INVOKABLE void getPluginsFromStore();
|
||||
Q_INVOKABLE void getPluginDetails(const QString& pluginId);
|
||||
Q_INVOKABLE void installRemotePlugin(const QString& pluginId);
|
||||
Q_INVOKABLE QString baseUrl() const;
|
||||
Q_INVOKABLE void checkVersionStatus(const QString& pluginId);
|
||||
Q_INVOKABLE bool isAutoUpdaterEnabled();
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE QVariant getMediaHandlerSelectableModel(const QString& callId);
|
||||
Q_INVOKABLE QVariant getChatHandlerSelectableModel(const QString& accountId,
|
||||
|
@ -51,6 +62,11 @@ private:
|
|||
void updateHandlersListCount();
|
||||
|
||||
std::unique_ptr<PluginHandlerListModel> pluginHandlerListModel_;
|
||||
|
||||
PluginStoreListModel* pluginStoreListModel_;
|
||||
PluginVersionManager* pluginVersionManager_;
|
||||
PluginListModel* pluginListModel_;
|
||||
LRCInstance* lrcInstance_;
|
||||
std::mutex mtx_;
|
||||
QString tempPath_;
|
||||
QString baseUrl_;
|
||||
};
|
||||
|
|
|
@ -142,3 +142,39 @@ PluginListModel::filterPlugins(VectorString& list) const
|
|||
}),
|
||||
list.cend());
|
||||
}
|
||||
|
||||
void
|
||||
PluginListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status)
|
||||
{
|
||||
auto pluginIndex = -1;
|
||||
for (auto& p : installedPlugins_) {
|
||||
auto details = lrcInstance_->pluginModel().getPluginDetails(p);
|
||||
if (details.name == pluginId) {
|
||||
pluginIndex = installedPlugins_.indexOf(p, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (status) {
|
||||
case PluginStatus::INSTALLED:
|
||||
addPlugin();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (pluginIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case PluginStatus::INSTALLABLE:
|
||||
removePlugin(pluginIndex);
|
||||
break;
|
||||
case PluginStatus::FAILED:
|
||||
qWarning() << "Failed to install plugin" << pluginId;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "abstractlistmodelbase.h"
|
||||
#include "pluginversionmanager.h"
|
||||
|
||||
class LRCInstance;
|
||||
|
||||
|
@ -52,6 +53,14 @@ public:
|
|||
Q_INVOKABLE void pluginChanged(int index);
|
||||
Q_INVOKABLE void addPlugin();
|
||||
|
||||
Q_SIGNALS:
|
||||
void versionCheckRequested(const QString& pluginId);
|
||||
void setVersionStatus(const QString& pluginId, PluginStatus::Role status);
|
||||
void autoUpdateChanged(bool state);
|
||||
|
||||
public Q_SLOTS:
|
||||
void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
|
||||
|
||||
private:
|
||||
void filterPlugins(VectorString& list) const;
|
||||
VectorString installedPlugins_ {};
|
||||
|
|
196
src/app/pluginstorelistmodel.cpp
Normal file
196
src/app/pluginstorelistmodel.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 "pluginstorelistmodel.h"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
PluginStoreListModel::PluginStoreListModel(QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
{}
|
||||
|
||||
int
|
||||
PluginStoreListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return plugins_.size();
|
||||
}
|
||||
/// A valid QModelIndex returns 0 as no entry has sub-elements.
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVariant
|
||||
PluginStoreListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
auto plugin = plugins_.at(index.row());
|
||||
switch (role) {
|
||||
case Role::Id:
|
||||
return QVariant(plugin["id"].toString());
|
||||
case Role::Title:
|
||||
return QVariant(plugin["name"].toString());
|
||||
case Role::IconPath:
|
||||
return QVariant(plugin["iconPath"].toString());
|
||||
case Role::Background:
|
||||
return QVariant(plugin["background"].toString());
|
||||
case Role::Description:
|
||||
return QVariant(plugin["description"].toString());
|
||||
case Role::Author:
|
||||
return QVariant(plugin["author"].toString());
|
||||
case Role::Status:
|
||||
return QVariant(plugin.value("status", PluginStatus::INSTALLABLE).toString());
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
PluginStoreListModel::roleNames() const
|
||||
{
|
||||
using namespace PluginStoreList;
|
||||
QHash<int, QByteArray> roles;
|
||||
#define X(role) roles[role] = #role;
|
||||
PLUGINSTORE_ROLES
|
||||
#undef X
|
||||
return roles;
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::reset()
|
||||
{
|
||||
beginResetModel();
|
||||
plugins_.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::addPlugin(const QVariantMap& plugin)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), plugins_.size(), plugins_.size());
|
||||
plugins_.append(plugin);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::setPlugins(const QList<QVariantMap>& plugins)
|
||||
{
|
||||
beginResetModel();
|
||||
plugins_ = plugins;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::removePlugin(const QString& pluginId)
|
||||
{
|
||||
auto index = 0;
|
||||
for (auto& plugin : plugins_) {
|
||||
if (plugin["id"].toString() == pluginId) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
plugins_.removeAt(index);
|
||||
endRemoveRows();
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::updatePlugin(const QVariantMap& plugin)
|
||||
{
|
||||
auto index = 0;
|
||||
for (auto& p : plugins_) {
|
||||
if (p["id"].toString() == plugin["id"].toString()) {
|
||||
p = plugin;
|
||||
Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0));
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
QColor
|
||||
PluginStoreListModel::computeAverageColorOfImage(const QString& file)
|
||||
{
|
||||
auto fileUrl = QUrl(file);
|
||||
// Return an invalid color if the file URL is invalid.
|
||||
if (!fileUrl.isValid()) {
|
||||
return QColor();
|
||||
}
|
||||
// Load the image.
|
||||
QImage image(fileUrl.toLocalFile());
|
||||
// If the image is valid...
|
||||
if (!image.isNull()) {
|
||||
static const QSize size(3, 3);
|
||||
static const int nPixels = size.width() * size.height();
|
||||
// Scale the image to 3x3 pixels.
|
||||
image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
// Return the average color of the image's pixels.
|
||||
double red = 0;
|
||||
double green = 0;
|
||||
double blue = 0;
|
||||
for (int i = 0; i < size.width(); i++) {
|
||||
for (int j = 0; j < size.height(); j++) {
|
||||
auto pixelColor = image.pixelColor(i, j);
|
||||
red += pixelColor.red();
|
||||
green += pixelColor.green();
|
||||
blue += pixelColor.blue();
|
||||
}
|
||||
}
|
||||
return QColor(red / nPixels, green / nPixels, blue / nPixels, 70);
|
||||
} else {
|
||||
// Return an invalid color.
|
||||
return QColor();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PluginStoreListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status)
|
||||
{
|
||||
auto plugin = QVariantMap();
|
||||
for (auto& p : plugins_) {
|
||||
if (p["id"].toString() == pluginId) {
|
||||
plugin = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (status) {
|
||||
case PluginStatus::INSTALLABLE:
|
||||
if (!plugin.isEmpty())
|
||||
break;
|
||||
pluginAdded(pluginId);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (plugin.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
plugin["status"] = status;
|
||||
|
||||
switch (status) {
|
||||
case PluginStatus::INSTALLED:
|
||||
removePlugin(pluginId);
|
||||
break;
|
||||
case PluginStatus::FAILED:
|
||||
qWarning() << "Failed to install plugin" << pluginId;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
74
src/app/pluginstorelistmodel.h
Normal file
74
src/app/pluginstorelistmodel.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Copyright (C) 2019-2023 Savoir-faire Linux Inc.
|
||||
* Author: Xavier Jouslin de Noray <xavier.jouslindenoray@savoirfairelinux.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "abstractlistmodelbase.h"
|
||||
#include "pluginversionmanager.h"
|
||||
|
||||
class QColor;
|
||||
class QString;
|
||||
|
||||
#define PLUGINSTORE_ROLES \
|
||||
X(Id) \
|
||||
X(Title) \
|
||||
X(IconPath) \
|
||||
X(Background) \
|
||||
X(Description) \
|
||||
X(Status) \
|
||||
X(Author)
|
||||
|
||||
namespace PluginStoreList {
|
||||
Q_NAMESPACE
|
||||
enum Role {
|
||||
DummyRole = Qt::UserRole + 1,
|
||||
#define X(role) role,
|
||||
PLUGINSTORE_ROLES
|
||||
#undef X
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace PluginStoreList
|
||||
|
||||
class PluginStoreListModel : public AbstractListModelBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PluginStoreListModel(QObject* parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void reset();
|
||||
|
||||
void addPlugin(const QVariantMap& plugin);
|
||||
void setPlugins(const QList<QVariantMap>& plugins);
|
||||
void removePlugin(const QString& pluginId);
|
||||
void updatePlugin(const QVariantMap& plugin);
|
||||
Q_INVOKABLE QColor computeAverageColorOfImage(const QString& fileUrl);
|
||||
|
||||
Q_SIGNALS:
|
||||
void pluginAdded(const QString& pluginId);
|
||||
|
||||
public Q_SLOTS:
|
||||
void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
|
||||
|
||||
private:
|
||||
using Role = PluginStoreList::Role;
|
||||
QList<QVariantMap> plugins_;
|
||||
};
|
221
src/app/pluginversionmanager.cpp
Normal file
221
src/app/pluginversionmanager.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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 "pluginversionmanager.h"
|
||||
#include "networkmanager.h"
|
||||
#include "appsettingsmanager.h"
|
||||
#include "lrcinstance.h"
|
||||
#include "api/pluginmodel.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include <QDir>
|
||||
|
||||
static constexpr int updatePeriod = 1000 * 60 * 60 * 24; // one day in millis
|
||||
|
||||
struct PluginVersionManager::Impl : public QObject
|
||||
{
|
||||
public:
|
||||
Impl(LRCInstance* instance, PluginVersionManager& parent)
|
||||
: QObject(nullptr)
|
||||
, parent_(parent)
|
||||
, appSettingsManager_(new AppSettingsManager(this))
|
||||
, lrcInstance_(instance)
|
||||
, tempPath_(QDir::tempPath())
|
||||
, updateTimer_(new QTimer(this))
|
||||
{
|
||||
connect(updateTimer_, &QTimer::timeout, this, [this] { checkForUpdates(); });
|
||||
connect(&parent_, &NetworkManager::downloadFinished, this, [this](int replyId) {
|
||||
auto pluginsId = parent_.pluginRepliesId.keys(replyId);
|
||||
if (pluginsId.size() == 0) {
|
||||
return;
|
||||
}
|
||||
for (const auto& pluginId : qAsConst(pluginsId)) {
|
||||
Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLING);
|
||||
parent_.pluginRepliesId.remove(pluginId);
|
||||
}
|
||||
});
|
||||
checkForUpdates();
|
||||
setAutoUpdateCheck(true);
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
setAutoUpdateCheck(false);
|
||||
}
|
||||
|
||||
void checkForUpdates()
|
||||
{
|
||||
if (!lrcInstance_) {
|
||||
return;
|
||||
}
|
||||
for (const auto& plugin : lrcInstance_->pluginModel().getInstalledPlugins()) {
|
||||
checkVersionStatusFromPath(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
void cancelUpdate(const QString& pluginId)
|
||||
{
|
||||
if (!parent_.pluginRepliesId.contains(pluginId)) {
|
||||
return;
|
||||
}
|
||||
parent_.cancelDownload(parent_.pluginRepliesId[pluginId]);
|
||||
};
|
||||
|
||||
bool isAutoUpdaterEnabled()
|
||||
{
|
||||
return appSettingsManager_->getValue(Settings::Key::PluginAutoUpdate).toBool();
|
||||
}
|
||||
|
||||
void setAutoUpdate(bool state)
|
||||
{
|
||||
appSettingsManager_->setValue(Settings::Key::PluginAutoUpdate, state);
|
||||
}
|
||||
|
||||
void checkVersionStatus(const QString& pluginId)
|
||||
{
|
||||
checkVersionStatusFromPath(lrcInstance_->pluginModel().getPluginPath(pluginId));
|
||||
}
|
||||
|
||||
void checkVersionStatusFromPath(const QString& pluginPath)
|
||||
{
|
||||
if (!lrcInstance_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto plugin = lrcInstance_->pluginModel().getPluginDetails(pluginPath);
|
||||
if (plugin.version == "" || plugin.id == "") {
|
||||
Q_EMIT parent_.versionStatusChanged(plugin.id, PluginStatus::Role::FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
parent_.sendGetRequest(QUrl(parent_.baseUrl + "/versions/" + plugin.id),
|
||||
[this, plugin](const QByteArray& data) {
|
||||
// `data` represents the version in this case.
|
||||
if (plugin.version < data) {
|
||||
if (isAutoUpdaterEnabled()) {
|
||||
installRemotePlugin(plugin.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
parent_.versionStatusChanged(plugin.id,
|
||||
PluginStatus::Role::UPDATABLE);
|
||||
});
|
||||
}
|
||||
|
||||
void installRemotePlugin(const QString& pluginId)
|
||||
{
|
||||
parent_.downloadFile(
|
||||
QUrl(parent_.baseUrl + "/download/" + pluginId),
|
||||
pluginId,
|
||||
0,
|
||||
[this, pluginId](bool success, const QString& error) {
|
||||
if (!success) {
|
||||
qDebug() << "Download Plugin error: " << error;
|
||||
parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
|
||||
return;
|
||||
}
|
||||
auto res = lrcInstance_->pluginModel().installPlugin(QDir(tempPath_).filePath(
|
||||
pluginId + ".jpl"),
|
||||
true);
|
||||
if (res) {
|
||||
parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLED);
|
||||
} else {
|
||||
parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
|
||||
}
|
||||
},
|
||||
tempPath_ + '/');
|
||||
Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::DOWNLOADING);
|
||||
}
|
||||
|
||||
void setAutoUpdateCheck(bool state)
|
||||
{
|
||||
// Quiet check for updates periodically, if set to.
|
||||
if (!state) {
|
||||
updateTimer_->stop();
|
||||
return;
|
||||
}
|
||||
updateTimer_->start(updatePeriod);
|
||||
};
|
||||
|
||||
PluginVersionManager& parent_;
|
||||
AppSettingsManager* appSettingsManager_ {nullptr};
|
||||
LRCInstance* lrcInstance_ {nullptr};
|
||||
QString tempPath_;
|
||||
QTimer* updateTimer_;
|
||||
};
|
||||
|
||||
PluginVersionManager::PluginVersionManager(LRCInstance* instance, QString& baseUrl, QObject* parent)
|
||||
: NetworkManager(&instance->connectivityMonitor(), parent)
|
||||
, baseUrl(baseUrl)
|
||||
, pimpl_(std::make_unique<Impl>(instance, *this))
|
||||
{}
|
||||
|
||||
PluginVersionManager::~PluginVersionManager()
|
||||
{
|
||||
for (const auto& pluginReplyId : qAsConst(pluginRepliesId)) {
|
||||
cancelDownload(pluginReplyId);
|
||||
}
|
||||
pluginRepliesId.clear();
|
||||
}
|
||||
|
||||
void
|
||||
PluginVersionManager::cancelUpdate(const QString& pluginId)
|
||||
{
|
||||
pimpl_->cancelUpdate(pluginId);
|
||||
}
|
||||
|
||||
bool
|
||||
PluginVersionManager::isAutoUpdaterEnabled()
|
||||
{
|
||||
return pimpl_->isAutoUpdaterEnabled();
|
||||
}
|
||||
|
||||
void
|
||||
PluginVersionManager::setAutoUpdate(bool state)
|
||||
{
|
||||
pimpl_->setAutoUpdate(state);
|
||||
}
|
||||
|
||||
int
|
||||
PluginVersionManager::downloadFile(const QUrl& url,
|
||||
const QString& pluginId,
|
||||
int replyId,
|
||||
std::function<void(bool, const QString&)>&& onDoneCallback,
|
||||
const QString& filePath,
|
||||
const QString& extension)
|
||||
{
|
||||
auto reply = NetworkManager::downloadFile(url,
|
||||
replyId,
|
||||
std::move(onDoneCallback),
|
||||
filePath,
|
||||
extension);
|
||||
pluginRepliesId[pluginId] = reply;
|
||||
return reply;
|
||||
}
|
||||
|
||||
void
|
||||
PluginVersionManager::checkVersionStatus(const QString& pluginId)
|
||||
{
|
||||
pimpl_->checkVersionStatus(pluginId);
|
||||
}
|
||||
|
||||
void
|
||||
PluginVersionManager::installRemotePlugin(const QString& pluginId)
|
||||
{
|
||||
pimpl_->installRemotePlugin(pluginId);
|
||||
}
|
80
src/app/pluginversionmanager.h
Normal file
80
src/app/pluginversionmanager.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "networkmanager.h"
|
||||
|
||||
class QString;
|
||||
class LRCInstance;
|
||||
|
||||
#define PLUGIN_STATUS_ROLES \
|
||||
X(INSTALLABLE) \
|
||||
X(DOWNLOADING) \
|
||||
X(INSTALLING) \
|
||||
X(INSTALLED) \
|
||||
X(FAILED) \
|
||||
X(UPDATABLE)
|
||||
|
||||
namespace PluginStatus {
|
||||
Q_NAMESPACE
|
||||
enum Role {
|
||||
DummyRole = Qt::UserRole + 1,
|
||||
#define X(role) role,
|
||||
PLUGIN_STATUS_ROLES
|
||||
#undef X
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace PluginStatus
|
||||
|
||||
class PluginVersionManager final : public NetworkManager
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PluginVersionManager(LRCInstance* instance,
|
||||
QString& baseUrl,
|
||||
QObject* parent = nullptr);
|
||||
~PluginVersionManager();
|
||||
|
||||
Q_INVOKABLE bool isAutoUpdaterEnabled();
|
||||
|
||||
Q_INVOKABLE void cancelUpdate(const QString& pluginId);
|
||||
int downloadFile(const QUrl& url,
|
||||
const QString& pluginId,
|
||||
int replyId,
|
||||
std::function<void(bool, const QString&)>&& onDoneCallback,
|
||||
const QString& filePath,
|
||||
const QString& extension = ".jpl");
|
||||
void installRemotePlugin(const QString& pluginId);
|
||||
|
||||
public Q_SLOTS:
|
||||
void checkVersionStatus(const QString& pluginId);
|
||||
void setAutoUpdate(bool state);
|
||||
|
||||
Q_SIGNALS:
|
||||
void versionStatusChanged(const QString& pluginId, PluginStatus::Role status);
|
||||
|
||||
private:
|
||||
QString baseUrl;
|
||||
bool autoUpdateCheck = false;
|
||||
QMap<QString, unsigned int> pluginRepliesId {};
|
||||
struct Impl;
|
||||
friend struct Impl;
|
||||
std::unique_ptr<Impl> pimpl_;
|
||||
};
|
||||
Q_DECLARE_METATYPE(PluginVersionManager*)
|
|
@ -56,6 +56,7 @@
|
|||
#include "mainapplication.h"
|
||||
#include "namedirectory.h"
|
||||
#include "pluginlistmodel.h"
|
||||
#include "pluginversionmanager.h"
|
||||
#include "appversionmanager.h"
|
||||
#include "pluginlistpreferencemodel.h"
|
||||
#include "preferenceitemlistmodel.h"
|
||||
|
@ -188,6 +189,7 @@ registerTypes(QQmlEngine* engine,
|
|||
QML_REGISTERNAMESPACE(NS_MODELS, ContactList::staticMetaObject, "ContactList");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, FilesToSend::staticMetaObject, "FilesToSend");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, MessageList::staticMetaObject, "MessageList");
|
||||
QML_REGISTERNAMESPACE(NS_MODELS, PluginStatus::staticMetaObject, "PluginStatus");
|
||||
|
||||
// Qml singleton components
|
||||
QML_REGISTERSINGLETONTYPE_URL(NS_CONSTANTS, "qrc:/constant/JamiTheme.qml", JamiTheme);
|
||||
|
|
176
src/app/settingsview/components/PluginAvailableDelagate.qml
Normal file
176
src/app/settingsview/components/PluginAvailableDelagate.qml
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
* Author: Xavier Jouslin de Noray <xjouslindenoray@savoirfairelinux.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import net.jami.Constants 1.1
|
||||
import "../../commoncomponents"
|
||||
import "../../mainview/components"
|
||||
|
||||
ItemDelegate {
|
||||
id: root
|
||||
property string pluginId
|
||||
property string pluginTitle
|
||||
property string pluginIcon
|
||||
property string pluginBackground
|
||||
property string pluginDescription
|
||||
property string pluginAuthor
|
||||
property string pluginShortDescription
|
||||
property int pluginStatus
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
Scaffold {
|
||||
}
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
anchors.fill: parent
|
||||
radius: 15
|
||||
}
|
||||
Page {
|
||||
id: plugin
|
||||
anchors.fill: parent
|
||||
header: Control {
|
||||
padding: 10
|
||||
background: Rectangle {
|
||||
color: pluginBackground
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
MaterialButton {
|
||||
id: install
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: 8
|
||||
Layout.topMargin: 8
|
||||
Layout.preferredHeight: 20
|
||||
TextMetrics {
|
||||
id: installTextSize
|
||||
font.weight: Font.Black
|
||||
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
|
||||
font.capitalization: Font.Medium
|
||||
text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
|
||||
}
|
||||
onClicked: installPlugin()
|
||||
secondary: true
|
||||
preferredWidth: installTextSize.width + JamiTheme.buttontextWizzardPadding
|
||||
text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
|
||||
fontSize: 15
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
|
||||
CachedImage {
|
||||
id: icon
|
||||
Component.onCompleted: {
|
||||
pluginBackground = PluginStoreListModel.computeAverageColorOfImage("file://" + UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg');
|
||||
}
|
||||
width: 50
|
||||
height: 50
|
||||
downloadUrl: PluginAdapter.baseUrl + "/icons/" + pluginId
|
||||
fileExtension: '.svg'
|
||||
localPath: UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg'
|
||||
}
|
||||
ColumnLayout {
|
||||
Label {
|
||||
text: pluginTitle
|
||||
font.kerning: true
|
||||
color: JamiTheme.textColor
|
||||
font.pointSize: JamiTheme.settingsFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Label {
|
||||
color: JamiTheme.textColor
|
||||
text: pluginShortDescription
|
||||
font.kerning: true
|
||||
font.pointSize: JamiTheme.settingsFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.pluginViewBackgroundColor
|
||||
}
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
contentWidth: description.width
|
||||
contentHeight: description.height
|
||||
clip: true
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollBar
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
Text {
|
||||
id: description
|
||||
width: parent.width
|
||||
color: JamiTheme.textColor
|
||||
text: pluginDescription
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
footer: Control {
|
||||
padding: 10
|
||||
background: Rectangle {
|
||||
color: JamiTheme.pluginViewBackgroundColor
|
||||
}
|
||||
contentItem: Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
color: JamiTheme.textColor
|
||||
|
||||
font.pointSize: JamiTheme.settingsFontSize
|
||||
font.kerning: true
|
||||
text: "By " + pluginAuthor
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
z: 2
|
||||
visible: hovered
|
||||
width: root.width
|
||||
height: root.height
|
||||
radius: 16
|
||||
color: Qt.rgba(0, 0.34, 0.6, 0.16)
|
||||
source: root
|
||||
transparentBorder: true
|
||||
samples: radius + 1
|
||||
cached: true
|
||||
}
|
||||
}
|
||||
function installPlugin() {
|
||||
if (isDownloading()) {
|
||||
return;
|
||||
}
|
||||
PluginAdapter.installRemotePlugin(pluginId);
|
||||
}
|
||||
|
||||
function isDownloading() {
|
||||
return pluginStatus === PluginStatus.DOWNLOADING;
|
||||
}
|
||||
}
|
|
@ -77,21 +77,7 @@ ItemDelegate {
|
|||
text: pluginName === "" ? pluginId : pluginName
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
MaterialButton {
|
||||
id: update
|
||||
TextMetrics {
|
||||
id: updateTextSize
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
|
||||
font.capitalization: Font.AllUppercase
|
||||
text: JamiStrings.updatePlugin
|
||||
}
|
||||
visible: false
|
||||
secondary: true
|
||||
preferredWidth: updateTextSize.width
|
||||
text: JamiStrings.updatePlugin
|
||||
fontSize: 15
|
||||
}
|
||||
|
||||
ToggleSwitch {
|
||||
id: loadSwitch
|
||||
Layout.fillHeight: true
|
||||
|
|
|
@ -29,7 +29,6 @@ Rectangle {
|
|||
|
||||
property string activePlugin: ""
|
||||
|
||||
visible: false
|
||||
color: JamiTheme.secondaryBackgroundColor
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -50,6 +49,21 @@ Rectangle {
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: disableAll
|
||||
TextMetrics {
|
||||
id: disableTextSize
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
|
||||
font.capitalization: Font.AllUppercase
|
||||
text: JamiStrings.disableAll
|
||||
}
|
||||
secondary: true
|
||||
preferredWidth: disableTextSize.width
|
||||
text: JamiStrings.disableAll
|
||||
fontSize: 15
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: installButton
|
||||
|
||||
|
@ -88,7 +102,6 @@ Rectangle {
|
|||
id: pluginList
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 0
|
||||
Layout.bottomMargin: 10
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
clip: true
|
||||
|
|
|
@ -178,7 +178,9 @@ Rectangle {
|
|||
"buttonCallBacks": [function () {
|
||||
pluginPreferencesView.visible = false;
|
||||
PluginModel.uninstallPlugin(pluginId);
|
||||
installedPluginsModel.removePlugin(index);
|
||||
PluginListModel.removePlugin(index);
|
||||
var pluginPath = pluginId.split('/');
|
||||
PluginListModel.setVersionStatus(pluginPath[pluginPath.length - 1], PluginStatus.INSTALLABLE);
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -41,12 +41,9 @@ SettingsPageBase {
|
|||
Layout.preferredWidth: root.width
|
||||
spacing: JamiTheme.settingsCategorySpacing
|
||||
}
|
||||
// View of installed plugins
|
||||
PluginListView {
|
||||
id: pluginListView
|
||||
|
||||
visible: PluginAdapter.isEnabled
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.minimumHeight: 0
|
||||
|
|
104
src/app/settingsview/components/PluginStoreListView.qml
Normal file
104
src/app/settingsview/components/PluginStoreListView.qml
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Savoir-faire Linux Inc.
|
||||
* Author: Xavier Jouslin de Noray <xjouslindenoray@savoirfairelinux.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.platform
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import "../../commoncomponents"
|
||||
|
||||
ColumnLayout {
|
||||
function installPlugin() {
|
||||
var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
|
||||
"title": JamiStrings.selectPluginInstall,
|
||||
"fileMode": JamiFileDialog.OpenFile,
|
||||
"folder": StandardPaths.writableLocation(StandardPaths.DownloadLocation),
|
||||
"nameFilters": [JamiStrings.pluginFiles, JamiStrings.allFiles]
|
||||
});
|
||||
dlg.fileAccepted.connect(function (file) {
|
||||
var url = UtilsAdapter.getAbsPath(file.toString());
|
||||
PluginModel.installPlugin(url, true);
|
||||
PluginListModel.addPlugin();
|
||||
});
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 25
|
||||
|
||||
text: JamiStrings.pluginStoreTitle
|
||||
font.pointSize: JamiTheme.headerFontSize
|
||||
font.kerning: true
|
||||
color: JamiTheme.textColor
|
||||
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
MaterialButton {
|
||||
id: installManually
|
||||
|
||||
TextMetrics {
|
||||
id: installManuallyTextSize
|
||||
font.weight: Font.Black
|
||||
font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
|
||||
font.capitalization: Font.Capitalize
|
||||
text: JamiStrings.installManually
|
||||
}
|
||||
secondary: true
|
||||
preferredWidth: installManuallyTextSize.width
|
||||
text: JamiStrings.installManually
|
||||
toolTipText: JamiStrings.installManually
|
||||
fontSize: 15
|
||||
onClicked: installPlugin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: pluginStoreList
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 20
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
clip: true
|
||||
Repeater {
|
||||
model: PluginStoreListModel
|
||||
|
||||
delegate: PluginAvailableDelagate {
|
||||
id: pluginItemDelegate
|
||||
|
||||
width: 350
|
||||
height: 400
|
||||
pluginId: Id
|
||||
pluginTitle: Title
|
||||
pluginIcon: IconPath
|
||||
pluginBackground: Background === '' ? JamiTheme.backgroundColor : Background
|
||||
pluginDescription: Description
|
||||
pluginAuthor: Author
|
||||
pluginShortDescription: ""
|
||||
pluginStatus: Status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,8 +38,10 @@ namespace plugin {
|
|||
*/
|
||||
struct PluginDetails
|
||||
{
|
||||
QString id = "";
|
||||
QString name = "";
|
||||
QString path = "";
|
||||
QString version = "";
|
||||
QString iconPath = "";
|
||||
bool loaded = false;
|
||||
};
|
||||
|
@ -102,6 +104,25 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE bool uninstallPlugin(const QString& rootPath);
|
||||
|
||||
/**
|
||||
* @brief get the plugin path
|
||||
* @param pluginId
|
||||
* @return plugin path
|
||||
*/
|
||||
QString getPluginPath(const QString& pluginId);
|
||||
|
||||
/**
|
||||
* @brief fetch all plugins path and id
|
||||
*
|
||||
*/
|
||||
void setPluginsPath();
|
||||
|
||||
/**
|
||||
* @brief get all plugins id
|
||||
* @return plugins id
|
||||
*/
|
||||
VectorString getPluginsId();
|
||||
|
||||
/**
|
||||
* Load plugin
|
||||
* @return true if plugin was succesfully loaded
|
||||
|
@ -184,6 +205,9 @@ public:
|
|||
Q_SIGNALS:
|
||||
void chatHandlerStatusUpdated(bool isVisible);
|
||||
void modelUpdated();
|
||||
|
||||
private:
|
||||
MapStringString pluginsPath_ = {};
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
|
|
@ -38,13 +38,23 @@
|
|||
// LRC
|
||||
#include "dbus/pluginmanager.h"
|
||||
|
||||
enum PluginInstallStatus {
|
||||
SUCCESS = 0,
|
||||
PLUGIN_ALREADY_INSTALLED = 100,
|
||||
PLUGIN_OLD_VERSION = 200,
|
||||
SIGNATURE_VERIFICATION_FAILED = 300,
|
||||
CERTIFICATE_VERIFICATION_FAILED = 400,
|
||||
INVALID_PLUGIN = 500,
|
||||
} PluginInstallStatus;
|
||||
namespace lrc {
|
||||
|
||||
using namespace api;
|
||||
|
||||
PluginModel::PluginModel()
|
||||
: QObject()
|
||||
{}
|
||||
{
|
||||
setPluginsPath();
|
||||
}
|
||||
|
||||
PluginModel::~PluginModel() {}
|
||||
|
||||
|
@ -87,11 +97,15 @@ PluginModel::getPluginDetails(const QString& path)
|
|||
MapStringString details = PluginManager::instance().getPluginDetails(path);
|
||||
plugin::PluginDetails result;
|
||||
if (!details.empty()) {
|
||||
result.id = details["id"];
|
||||
result.name = details["name"];
|
||||
result.path = path;
|
||||
result.iconPath = details["iconPath"];
|
||||
result.version = details["version"];
|
||||
}
|
||||
if (!pluginsPath_.contains(result.id)) {
|
||||
pluginsPath_[result.id] = path;
|
||||
}
|
||||
|
||||
VectorString loadedPlugins = getLoadedPlugins();
|
||||
if (std::find(loadedPlugins.begin(), loadedPlugins.end(), result.path) != loadedPlugins.end()) {
|
||||
result.loaded = true;
|
||||
|
@ -106,7 +120,27 @@ PluginModel::installPlugin(const QString& jplPath, bool force)
|
|||
if (getPluginsEnabled()) {
|
||||
auto result = PluginManager::instance().installPlugin(jplPath, force);
|
||||
Q_EMIT modelUpdated();
|
||||
return result;
|
||||
if (result != 0) {
|
||||
switch (result) {
|
||||
case PluginInstallStatus::PLUGIN_ALREADY_INSTALLED:
|
||||
qWarning() << "Plugin already installed";
|
||||
break;
|
||||
case PluginInstallStatus::PLUGIN_OLD_VERSION:
|
||||
qWarning() << "Plugin already installed with a newer version";
|
||||
break;
|
||||
case PluginInstallStatus::SIGNATURE_VERIFICATION_FAILED:
|
||||
qWarning() << "Signature verification failed";
|
||||
break;
|
||||
case PluginInstallStatus::CERTIFICATE_VERIFICATION_FAILED:
|
||||
qWarning() << "Certificate verification failed";
|
||||
break;
|
||||
case PluginInstallStatus::INVALID_PLUGIN:
|
||||
qWarning() << "Invalid plugin";
|
||||
break;
|
||||
}
|
||||
}
|
||||
pluginsPath_[getPluginDetails(jplPath).id] = jplPath;
|
||||
return result == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -115,10 +149,37 @@ bool
|
|||
PluginModel::uninstallPlugin(const QString& rootPath)
|
||||
{
|
||||
auto result = PluginManager::instance().uninstallPlugin(rootPath);
|
||||
for (auto plugin : pluginsPath_.keys(rootPath)) {
|
||||
pluginsPath_.remove(plugin);
|
||||
}
|
||||
Q_EMIT modelUpdated();
|
||||
return result;
|
||||
}
|
||||
|
||||
QString
|
||||
PluginModel::getPluginPath(const QString& pluginId)
|
||||
{
|
||||
return pluginsPath_[pluginId];
|
||||
}
|
||||
|
||||
void
|
||||
PluginModel::setPluginsPath()
|
||||
{
|
||||
for (auto plugin : getInstalledPlugins()) {
|
||||
auto details = getPluginDetails(plugin);
|
||||
pluginsPath_[details.name] = details.path;
|
||||
}
|
||||
}
|
||||
|
||||
VectorString
|
||||
PluginModel::getPluginsId()
|
||||
{
|
||||
if (pluginsPath_.empty()) {
|
||||
setPluginsPath();
|
||||
}
|
||||
return pluginsPath_.keys();
|
||||
}
|
||||
|
||||
bool
|
||||
PluginModel::loadPlugin(const QString& path)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue