diff --git a/discover/DiscoverObject.cpp b/discover/DiscoverObject.cpp index ed872a53..2b313886 100644 --- a/discover/DiscoverObject.cpp +++ b/discover/DiscoverObject.cpp @@ -1,481 +1,483 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DiscoverObject.h" #include "PaginateModel.h" #include "UnityLauncher.h" #include "FeaturedModel.h" #include "CachedNetworkAccessManager.h" #include "DiscoverDeclarativePlugin.h" +#include "DiscoverBackendsFactory.h" // Qt includes #include #include "discover_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include // #include // DiscoverCommon includes #include #include #include #include #include #include #include #include #include #include #include class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: void classBegin() override {} void componentComplete() override { if (dynamicSortFilter()) sort(0); } }; class KIOAccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: KIOAccessManagerFactory() = default; ~KIOAccessManagerFactory() = default; QNetworkAccessManager *create(QObject *parent) override { return new KIO::AccessManager(parent); } }; DiscoverObject::DiscoverObject(CompactMode mode) : QObject() , m_engine(new QQmlApplicationEngine) , m_mode(mode) , m_networkAccessManagerFactory(new CachedNetworkAccessManagerFactory) { setObjectName(QStringLiteral("DiscoverMain")); m_engine->rootContext()->setContextObject(new KLocalizedContext(m_engine)); auto factory = m_engine->networkAccessManagerFactory(); m_engine->setNetworkAccessManagerFactory(nullptr); delete factory; m_engine->setNetworkAccessManagerFactory(new KIOAccessManagerFactory()); qmlRegisterType("org.kde.discover.app", 1, 0, "UnityLauncher"); qmlRegisterType("org.kde.discover.app", 1, 0, "PaginateModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "KConcatenateRowsProxyModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "FeaturedModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "QSortFilterProxyModel"); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterUncreatableType("org.kde.discover.app", 1, 0, "DiscoverMainWindow", QStringLiteral("don't do that")); setupActions(); auto uri = "org.kde.discover"; DiscoverDeclarativePlugin* plugin = new DiscoverDeclarativePlugin; plugin->setParent(this); plugin->initializeEngine(m_engine, uri); plugin->registerTypes(uri); //Here we set up a cache for the screenshots delete m_engine->networkAccessManagerFactory(); m_engine->setNetworkAccessManagerFactory(m_networkAccessManagerFactory.data()); m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this); connect(m_engine, &QQmlApplicationEngine::objectCreated, this, &DiscoverObject::integrateObject); m_engine->load(QUrl(QStringLiteral("qrc:/qml/DiscoverWindow.qml"))); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this](){ const auto objs = m_engine->rootObjects(); for(auto o: objs) delete o; }); auto action = new OneTimeAction( [this]() { if (ResourcesModel::global()->backends().isEmpty()) Q_EMIT openErrorPage(i18n("No Discover back-ends found, please report to your distribution.")); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::allInitialized, action, &OneTimeAction::trigger); } else { action->trigger(); } } DiscoverObject::~DiscoverObject() { delete m_engine; } bool DiscoverObject::isRoot() { return ::getuid() == 0; } QStringList DiscoverObject::modes() const { QStringList ret; QObject* obj = rootObject(); for(int i = obj->metaObject()->propertyOffset(); imetaObject()->propertyCount(); i++) { QMetaProperty p = obj->metaObject()->property(i); QByteArray compName = p.name(); if(compName.startsWith("top") && compName.endsWith("Comp")) { ret += QString::fromLatin1(compName.mid(3, compName.length()-7)); } } return ret; } void DiscoverObject::openMode(const QString& _mode) { QObject* obj = rootObject(); if (!obj) { qCWarning(DISCOVER_LOG) << "could not get the main object"; return; } if(!modes().contains(_mode, Qt::CaseInsensitive)) qCWarning(DISCOVER_LOG) << "unknown mode" << _mode << modes(); QString mode = _mode; mode[0] = mode[0].toUpper(); const QByteArray propertyName = "top"+mode.toLatin1()+"Comp"; const QVariant modeComp = obj->property(propertyName.constData()); if (!modeComp.isValid()) qCWarning(DISCOVER_LOG) << "couldn't open mode" << _mode; else obj->setProperty("currentTopLevel", modeComp); } void DiscoverObject::openMimeType(const QString& mime) { emit listMimeInternal(mime); } void DiscoverObject::openCategory(const QString& category) { setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, category]() { Category* cat = CategoryModel::global()->findCategoryByName(category); if (cat) { emit listCategoryInternal(cat); } else { showPassiveNotification(i18n("Could not find category '%1'", category)); setRootObjectProperty("defaultStartup", false); } } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openLocalPackage(const QUrl& localfile) { if (!QFile::exists(localfile.toLocalFile())) { // showPassiveNotification(i18n("Trying to open unexisting file '%1'", localfile.toString())); qCWarning(DISCOVER_LOG) << "Trying to open unexisting file" << localfile; return; } setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, localfile]() { auto res = ResourcesModel::global()->resourceForFile(localfile); qCDebug(DISCOVER_LOG) << "all initialized..." << res; if (res) { emit openApplicationInternal(res); } else { QMimeDatabase db; auto mime = db.mimeTypeForUrl(localfile); - if (mime.name().startsWith(QLatin1String("application/vnd.flatpak"))) { + auto fIsFlatpakBackend = [](AbstractResourcesBackend* backend) { return backend->metaObject()->className() == QByteArray("FlatpakBackend"); }; + if (mime.name().startsWith(QLatin1String("application/vnd.flatpak")) && !kContains(ResourcesModel::global()->backends(), fIsFlatpakBackend)) { openApplication(QUrl(QLatin1String("appstream://org.kde.discover.flatpak"))); showPassiveNotification(i18n("Cannot interact with flatpak resources without the flatpak backend %1. Please install it first.", localfile.toDisplayString())); } else { setRootObjectProperty("defaultStartup", true); showPassiveNotification(i18n("Couldn't open %1", localfile.toDisplayString())); } } } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openApplication(const QUrl& url) { Q_ASSERT(!url.isEmpty()); setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, url]() { AbstractResourcesBackend::Filters f; f.resourceUrl = url; auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)}); connect(stream, &StoredResultsStream::finished, this, [this, url, stream]() { const auto res = stream->resources(); if (res.count() >= 1) { emit openApplicationInternal(res.first()); } else { setRootObjectProperty("defaultStartup", true); Q_EMIT openErrorPage(i18n("Couldn't open %1", url.toDisplayString())); } }); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::integrateObject(QObject* object) { if (!object) { qCWarning(DISCOVER_LOG) << "Errors when loading the GUI"; QTimer::singleShot(0, QCoreApplication::instance(), [](){ QCoreApplication::instance()->exit(1); }); return; } Q_ASSERT(object == rootObject()); KConfigGroup window(KSharedConfig::openConfig(), "Window"); if (window.hasKey("geometry")) rootObject()->setGeometry(window.readEntry("geometry", QRect())); if (window.hasKey("visibility")) { QWindow::Visibility visibility(QWindow::Visibility(window.readEntry("visibility", QWindow::Windowed))); rootObject()->setVisibility(qMax(visibility, QQuickView::AutomaticVisibility)); } object->installEventFilter(this); connect(object, &QObject::destroyed, qGuiApp, &QCoreApplication::quit); object->setParent(m_engine); connect(qGuiApp, &QGuiApplication::commitDataRequest, this, [this](QSessionManager &sessionManager) { if (ResourcesModel::global()->isBusy()) { Q_EMIT preventedClose(); sessionManager.cancel(); } }); } bool DiscoverObject::eventFilter(QObject * object, QEvent * event) { if (object!=rootObject()) return false; if (event->type() == QEvent::Close) { if (ResourcesModel::global()->isBusy()) { qCWarning(DISCOVER_LOG) << "not closing because there's still pending tasks"; Q_EMIT preventedClose(); return true; } KConfigGroup window(KSharedConfig::openConfig(), "Window"); window.writeEntry("geometry", rootObject()->geometry()); window.writeEntry("visibility", rootObject()->visibility()); // } else if (event->type() == QEvent::ShortcutOverride) { // qCWarning(DISCOVER_LOG) << "Action conflict" << event; } return false; } void DiscoverObject::setupActions() { if (KAuthorized::authorizeAction(QStringLiteral("help_report_bug")) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto mReportBugAction = KStandardAction::reportBug(this, &DiscoverObject::reportBug, this); m_collection[mReportBugAction->objectName()] = mReportBugAction; } if (KAuthorized::authorizeAction(QStringLiteral("help_about_app"))) { auto mAboutAppAction = KStandardAction::aboutApp(this, &DiscoverObject::aboutApplication, this); m_collection[mAboutAppAction->objectName()] = mAboutAppAction; } } QAction * DiscoverObject::action(const QString& name) const { return m_collection.value(name); } QString DiscoverObject::iconName(const QIcon& icon) { return icon.name(); } void DiscoverObject::aboutApplication() { static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::reportBug() { static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::switchApplicationLanguage() { // auto langDialog = new KSwitchLanguageDialog(nullptr); // connect(langDialog, SIGNAL(finished(int)), this, SLOT(dialogFinished())); // langDialog->show(); } void DiscoverObject::setCompactMode(DiscoverObject::CompactMode mode) { if (m_mode != mode) { m_mode = mode; Q_EMIT compactModeChanged(m_mode); } } class DiscoverTestExecutor : public QObject { public: DiscoverTestExecutor(QObject* rootObject, QQmlEngine* engine, const QUrl &url) : QObject(engine) { connect(engine, &QQmlEngine::quit, this, &DiscoverTestExecutor::finish, Qt::QueuedConnection); QQmlComponent* component = new QQmlComponent(engine, url, engine); m_testObject = component->create(engine->rootContext()); if (!m_testObject) { qCWarning(DISCOVER_LOG) << "error loading test" << url << m_testObject << component->errors(); Q_ASSERT(false); } m_testObject->setProperty("appRoot", QVariant::fromValue(rootObject)); connect(engine, &QQmlEngine::warnings, this, &DiscoverTestExecutor::processWarnings); } void processWarnings(const QList &warnings) { foreach(const QQmlError &warning, warnings) { if (warning.url().path().endsWith(QLatin1String("DiscoverTest.qml"))) { qCWarning(DISCOVER_LOG) << "Test failed!" << warnings; qGuiApp->exit(1); } } m_warnings << warnings; } void finish() { //The CI doesn't seem to have icons, remove when it's not an issue anymore m_warnings.erase(std::remove_if(m_warnings.begin(), m_warnings.end(), [](const QQmlError& err) -> bool { return err.description().contains(QLatin1String("QML Image: Failed to get image from provider: image://icon/")); })); if (m_warnings.isEmpty()) qCDebug(DISCOVER_LOG) << "cool no warnings!"; else qCDebug(DISCOVER_LOG) << "test finished succesfully despite" << m_warnings; qGuiApp->exit(m_warnings.count()); } private: QObject* m_testObject; QList m_warnings; }; void DiscoverObject::loadTest(const QUrl& url) { new DiscoverTestExecutor(rootObject(), engine(), url); } QWindow* DiscoverObject::rootObject() const { return qobject_cast(m_engine->rootObjects().at(0)); } void DiscoverObject::setRootObjectProperty(const char* name, const QVariant& value) { auto ro = rootObject(); if (!ro) { qCWarning(DISCOVER_LOG) << "please check your installation"; return; } rootObject()->setProperty(name, value); } void DiscoverObject::showPassiveNotification(const QString& msg) { QTimer::singleShot(100, this, [this, msg](){ QMetaObject::invokeMethod(rootObject(), "showPassiveNotification", Qt::QueuedConnection, Q_ARG(QVariant, msg), Q_ARG(QVariant, {}), Q_ARG(QVariant, {}), Q_ARG(QVariant, {})); }); } void DiscoverObject::copyTextToClipboard(const QString& text) { qGuiApp->clipboard()->setText(text); } #include "DiscoverObject.moc" diff --git a/discover/main.cpp b/discover/main.cpp index 704cc384..aae6274f 100644 --- a/discover/main.cpp +++ b/discover/main.cpp @@ -1,157 +1,157 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // #define QT_QML_DEBUG #include #include #include #include #include #include #include #include "DiscoverObject.h" #include #include "DiscoverVersion.h" #include #include typedef QHash StringCompactMode; Q_GLOBAL_STATIC_WITH_ARGS(StringCompactMode, s_decodeCompactMode, (StringCompactMode { { QLatin1String("auto"), DiscoverObject::Auto }, { QLatin1String("compact"), DiscoverObject::Compact }, { QLatin1String("full"), DiscoverObject::Full } })) QCommandLineParser* createParser() { QCommandLineParser* parser = new QCommandLineParser; parser->addOption(QCommandLineOption(QStringLiteral("application"), i18n("Directly open the specified application by its package name."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("mime"), i18n("Open with a program that can deal with the given mimetype."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("category"), i18n("Display a list of entries with a category."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("mode"), i18n("Open Discover in a said mode. Modes correspond to the toolbar buttons."), QStringLiteral("name"))); parser->addOption(QCommandLineOption(QStringLiteral("listmodes"), i18n("List all the available modes."))); parser->addOption(QCommandLineOption(QStringLiteral("compact"), i18n("Compact Mode (auto/compact/full)."), QStringLiteral("mode"), QStringLiteral("auto"))); parser->addOption(QCommandLineOption(QStringLiteral("local-filename"), i18n("Local package file to install"), QStringLiteral("package"))); parser->addOption(QCommandLineOption(QStringLiteral("listbackends"), i18n("List all the available backends."))); parser->addOption(QCommandLineOption(QStringLiteral("test"), QStringLiteral("Test file"), QStringLiteral("file.qml"))); parser->addPositionalArgument(QStringLiteral("urls"), i18n("Supports appstream: url scheme")); DiscoverBackendsFactory::setupCommandLine(parser); KAboutData::applicationData().setupCommandLine(parser); parser->addHelpOption(); parser->addVersionOption(); return parser; } void processArgs(QCommandLineParser* parser, DiscoverObject* mainWindow) { if(parser->isSet(QStringLiteral("application"))) mainWindow->openApplication(QUrl(parser->value(QStringLiteral("application")))); else if(parser->isSet(QStringLiteral("mime"))) mainWindow->openMimeType(parser->value(QStringLiteral("mime"))); else if(parser->isSet(QStringLiteral("category"))) mainWindow->openCategory(parser->value(QStringLiteral("category"))); if(parser->isSet(QStringLiteral("mode"))) mainWindow->openMode(parser->value(QStringLiteral("mode"))); if(parser->isSet(QStringLiteral("local-filename"))) mainWindow->openLocalPackage(QUrl::fromUserInput(parser->value(QStringLiteral("local-filename")), {}, QUrl::AssumeLocalFile)); foreach(const QString &arg, parser->positionalArguments()) { const QUrl url = QUrl::fromUserInput(arg, {}, QUrl::AssumeLocalFile); if (url.isLocalFile()) mainWindow->openLocalPackage(url); - else if (url.scheme() == QLatin1String("apt") || url.scheme() == QLatin1String("snap")) + else if (url.scheme() == QLatin1String("apt")) Q_EMIT mainWindow->openSearch(url.host()); else mainWindow->openApplication(url); } } int main(int argc, char** argv) { QApplication app(argc, argv); app.setWindowIcon(QIcon::fromTheme(QStringLiteral("plasmadiscover"))); app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KCrash::initialize(); KLocalizedString::setApplicationDomain("plasma-discover"); KAboutData about(QStringLiteral("discover"), i18n("Discover"), version, i18n("An application explorer"), KAboutLicense::GPL, i18n("© 2010-2018 Plasma Development Team")); about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@blue-systems.com")); about.addAuthor(i18n("Jonathan Thomas"), QString(), QStringLiteral("echidnaman@kubuntu.org")); about.setProductName("discover/discover"); KAboutData::setApplicationData(about); DiscoverObject *mainWindow = nullptr; { QScopedPointer parser(createParser()); parser->process(app); about.processCommandLine(parser.data()); DiscoverBackendsFactory::processCommandLine(parser.data(), parser->isSet(QStringLiteral("test"))); if(parser->isSet(QStringLiteral("listbackends"))) { QTextStream(stdout) << i18n("Available backends:\n"); DiscoverBackendsFactory f; foreach(const QString& name, f.allBackendNames(false, true)) QTextStream(stdout) << " * " << name << '\n'; return 0; } if (parser->isSet(QStringLiteral("test"))) { QStandardPaths::setTestModeEnabled(true); } KDBusService* service = new KDBusService(KDBusService::Unique, &app); mainWindow = new DiscoverObject(s_decodeCompactMode->value(parser->value(QStringLiteral("compact")), DiscoverObject::Full)); QObject::connect(&app, &QCoreApplication::aboutToQuit, mainWindow, &DiscoverObject::deleteLater); QObject::connect(service, &KDBusService::activateRequested, mainWindow, [mainWindow](const QStringList &arguments, const QString &/*workingDirectory*/){ if (!mainWindow->rootObject()) QCoreApplication::instance()->quit(); mainWindow->rootObject()->raise(); if (arguments.isEmpty()) return; QScopedPointer parser(createParser()); parser->parse(arguments); processArgs(parser.data(), mainWindow); }); processArgs(parser.data(), mainWindow); if(parser->isSet(QStringLiteral("listmodes"))) { QTextStream(stdout) << i18n("Available modes:\n"); foreach(const QString& mode, mainWindow->modes()) QTextStream(stdout) << " * " << mode << '\n'; delete mainWindow; return 0; } if (parser->isSet(QStringLiteral("test"))) { const QUrl testFile = QUrl::fromUserInput(parser->value(QStringLiteral("test")), {}, QUrl::AssumeLocalFile); Q_ASSERT(!testFile.isEmpty() && testFile.isLocalFile()); mainWindow->loadTest(testFile); } } return app.exec(); } diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml index c96c1f17..3850a1b2 100644 --- a/discover/qml/SourcesPage.qml +++ b/discover/qml/SourcesPage.qml @@ -1,259 +1,259 @@ import QtQuick 2.4 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import org.kde.kirigami 2.2 as Kirigami import "navigation.js" as Navigation DiscoverPage { id: page clip: true title: i18n("Settings") property string search: "" contextualActions: [ KirigamiActionBridge { action: app.action("help_about_app") }, KirigamiActionBridge { action: app.action("help_report_bug") } ] mainItem: ListView { id: sourcesView model: QSortFilterProxyModel { filterRegExp: new RegExp(page.search, 'i') dynamicSortFilter: false //We don't want to sort, as sorting can have some semantics on some backends sourceModel: SourcesModel } currentIndex: -1 section.property: "sourceName" section.delegate: Kirigami.AbstractListItem { id: backendItem hoverEnabled: false supportsMouseEvents: false readonly property QtObject backend: SourcesModel.sourcesBackendByName(section) readonly property QtObject resourcesBackend: backend.resourcesBackend readonly property bool isDefault: ResourcesModel.currentApplicationBackend == resourcesBackend GridLayout { id: sourceTitleLayout Layout.fillHeight: true Connections { target: backendItem.backend onPassiveMessage: window.showPassiveNotification(message) onProceedRequest: { var dialog = sourceProceedDialog.createObject(window, {sourcesBackend: sourcesBackend, title: title, description: description}) dialog.open() } } Kirigami.Heading { Layout.fillWidth: true Layout.leftMargin: Kirigami.Units.smallSpacing Layout.alignment: Qt.AlignVCenter text: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName level: 3 } Instantiator { id: backendActionsInst model: ActionsModel { actions: backendItem.backend ? backendItem.backend.actions : undefined } delegate: Button { parent: sourceTitleLayout Layout.column: 1 text: modelData.text icon.name: app.iconName(modelData.icon) ToolTip.visible: hovered ToolTip.text: modelData.toolTip onClicked: modelData.trigger() } onObjectRemoved: { object.destroy() } } Button { Layout.alignment: Qt.AlignVCenter icon.name: "preferences-other" Layout.column: 2 visible: resourcesBackend && resourcesBackend.hasApplications Component { id: dialogComponent AddSourceDialog { source: backendItem.backend onVisibleChanged: if (!visible) { destroy() } } } id: this onClicked: settingsMenu.popup(this) Menu { id: settingsMenu MenuItem { enabled: !backendItem.isDefault text: i18n("Make default") onTriggered: ResourcesModel.currentApplicationBackend = backendItem.backend.resourcesBackend } MenuItem { text: i18n("Add Source...") visible: backendItem.backend && backendItem.backend.supportsAdding onTriggered: { var addSourceDialog = dialogComponent.createObject(null, {displayName: backendItem.backend.resourcesBackend.displayName }) addSourceDialog.open() } } } } } } Component { id: sourceProceedDialog Kirigami.OverlaySheet { id: sheet showCloseButton: false property QtObject sourcesBackend property alias title: heading.text property alias description: desc.text property bool acted: false ColumnLayout { Kirigami.Heading { id: heading } Label { id: desc Layout.fillWidth: true textFormat: Text.StyledText wrapMode: Text.WordWrap } RowLayout { Layout.alignment: Qt.AlignRight Button { text: i18n("Proceed") icon.name: "dialog-ok" onClicked: { sourcesBackend.proceed() sheet.acted = true sheet.close() } } Button { Layout.alignment: Qt.AlignRight text: i18n("Cancel") icon.name: "dialog-cancel" onClicked: { sourcesBackend.cancel() sheet.acted = true sheet.close() } } } } onSheetOpenChanged: if(!sheetOpen) { sheet.destroy(1000) if (!sheet.acted) sourcesBackend.cancel() } } } delegate: Kirigami.SwipeListItem { Layout.fillWidth: true enabled: display.length>0 highlighted: ListView.isCurrentItem supportsMouseEvents: sourcesBackend.canFilterSources onClicked: Navigation.openApplicationListSource(sourceId) Keys.onReturnPressed: clicked() actions: [ Kirigami.Action { iconName: "go-up" enabled: sourcesBackend.firstSourceId !== sourceId visible: sourcesBackend.canMoveSources onTriggered: { var ret = sourcesBackend.moveSource(sourceId, -1) if (!ret) window.showPassiveNotification(i18n("Failed to increase '%1' preference", display)) } }, Kirigami.Action { iconName: "go-down" enabled: sourcesBackend.lastSourceId !== sourceId visible: sourcesBackend.canMoveSources onTriggered: { var ret = sourcesBackend.moveSource(sourceId, +1) if (!ret) window.showPassiveNotification(i18n("Failed to decrease '%1' preference", display)) } }, Kirigami.Action { iconName: "edit-delete" tooltip: i18n("Delete the origin") visible: sourcesBackend.supportsAdding onTriggered: { var backend = sourcesBackend if (!backend.removeSource(sourceId)) { window.showPassiveNotification(i18n("Failed to remove the source '%1'", display)) } } } ] RowLayout { CheckBox { id: enabledBox readonly property variant idx: sourcesView.model.index(index, 0) readonly property variant modelChecked: sourcesView.model.data(idx, Qt.CheckStateRole) checked: modelChecked != Qt.Unchecked enabled: modelChecked !== undefined onClicked: { - sourcesView.model.setData(idx, checkedState, Qt.CheckStateRole) + sourcesView.model.setData(idx, checkState, Qt.CheckStateRole) } } Label { text: display + (toolTip ? " - " + toolTip + "" : "") elide: Text.ElideRight Layout.fillWidth: true } } } footer: ColumnLayout { id: foot anchors { right: parent.right left: parent.left margins: Kirigami.Units.smallSpacing } Kirigami.Heading { Layout.fillWidth: true text: i18n("Missing Backends") visible: back.count>0 } spacing: 0 Repeater { id: back model: ResourcesProxyModel { extending: "org.kde.discover.desktop" filterMinimumState: false } delegate: Kirigami.BasicListItem { supportsMouseEvents: false label: name icon: model.icon InstallApplicationButton { application: model.application } } } } } } diff --git a/libdiscover/UpdateModel/UpdateModel.cpp b/libdiscover/UpdateModel/UpdateModel.cpp index 77e2fdba..ac890130 100644 --- a/libdiscover/UpdateModel/UpdateModel.cpp +++ b/libdiscover/UpdateModel/UpdateModel.cpp @@ -1,332 +1,333 @@ /*************************************************************************** * Copyright © 2011 Jonathan Thomas * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "UpdateModel.h" // Qt includes #include #include #include "libdiscover_debug.h" // KDE includes #include #include // Own includes #include "UpdateItem.h" #include #include #include UpdateModel::UpdateModel(QObject *parent) : QAbstractListModel(parent) , m_updateSizeTimer(new QTimer(this)) , m_updates(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::fetchingChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::updatesCountChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &UpdateModel::resourceDataChanged); connect(this, &UpdateModel::toUpdateChanged, this, &UpdateModel::updateSizeChanged); m_updateSizeTimer->setInterval(100); m_updateSizeTimer->setSingleShot(true); connect(m_updateSizeTimer, &QTimer::timeout, this, &UpdateModel::updateSizeChanged); } UpdateModel::~UpdateModel() { qDeleteAll(m_updateItems); m_updateItems.clear(); } QHash UpdateModel::roleNames() const { return QAbstractItemModel::roleNames().unite({ { Qt::CheckStateRole, "checked" }, { ResourceProgressRole, "resourceProgress" }, { ResourceRole, "resource" }, { SizeRole, "size" }, { VersionRole, "version" }, { SectionRole, "section" }, { ChangelogRole, "changelog" } } ); } void UpdateModel::setBackend(ResourcesUpdatesModel* updates) { if (m_updates) { disconnect(m_updates, nullptr, this, nullptr); } m_updates = updates; connect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged); connect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed); activityChanged(); } void UpdateModel::resourceHasProgressed(AbstractResource* res, qreal progress) { UpdateItem* item = itemFromResource(res); if (!item) return; item->setProgress(progress); const QModelIndex idx = indexFromItem(item); Q_EMIT dataChanged(idx, idx, { ResourceProgressRole, SectionResourceProgressRole }); } void UpdateModel::activityChanged() { if (m_updates) { if (!m_updates->isProgressing()) { m_updates->prepare(); setResources(m_updates->toUpdate()); for(auto item : qAsConst(m_updateItems)) { item->setProgress(0); } } else setResources(m_updates->toUpdate()); } } QVariant UpdateModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } UpdateItem *item = itemFromIndex(index); switch (role) { case Qt::DisplayRole: return item->name(); case Qt::DecorationRole: return item->icon(); case Qt::CheckStateRole: return item->checked(); case VersionRole: return item->version(); case SizeRole: return KFormat().formatByteSize(item->size()); case ResourceRole: return QVariant::fromValue(item->resource()); case ResourceProgressRole: return item->progress(); case ChangelogRole: return item->changelog(); case SectionRole: { static const QString appUpdatesSection = i18nc("@item:inlistbox", "Application Updates"); static const QString systemUpdateSection = i18nc("@item:inlistbox", "System Updates"); switch(item->section()) { case UpdateItem::ApplicationSection: return appUpdatesSection; case UpdateItem::SystemSection: return systemUpdateSection; } + return {}; } case SectionResourceProgressRole: return (100-item->progress()) + (101 * item->section()); default: break; } return QVariant(); } void UpdateModel::checkResources(const QList& resource, bool checked) { if(checked) m_updates->addResources(resource); else m_updates->removeResources(resource); } Qt::ItemFlags UpdateModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int UpdateModel::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_updateItems.count() : 0; } bool UpdateModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { UpdateItem *item = itemFromIndex(idx); const bool newValue = value.toInt() == Qt::Checked; const QList apps = { item->app() }; checkResources(apps, newValue); Q_ASSERT(idx.data(Qt::CheckStateRole) == value); //When un/checking some backends will decide to add or remove a bunch of packages, so refresh it all auto m = idx.model(); Q_EMIT dataChanged(m->index(0, 0), m->index(m->rowCount()-1, 0), { Qt::CheckStateRole }); Q_EMIT toUpdateChanged(); return true; } return false; } void UpdateModel::fetchChangelog(int row) { UpdateItem *item = itemFromIndex(index(row, 0)); Q_ASSERT(item); if (!item) return; item->app()->fetchChangelog(); } void UpdateModel::integrateChangelog(const QString &changelog) { auto app = qobject_cast(sender()); Q_ASSERT(app); auto item = itemFromResource(app); if (!item) return; item->setChangelog(changelog); const QModelIndex idx = indexFromItem(item); Q_ASSERT(idx.isValid()); emit dataChanged(idx, idx, { ChangelogRole }); } void UpdateModel::setResources(const QList& resources) { if (resources == m_resources) { return; } m_resources = resources; beginResetModel(); qDeleteAll(m_updateItems); m_updateItems.clear(); QVector appItems, systemItems; foreach(AbstractResource* res, resources) { connect(res, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection); UpdateItem *updateItem = new UpdateItem(res); if(!res->isTechnical()) { updateItem->setSection(UpdateItem::ApplicationSection); appItems += updateItem; } else { updateItem->setSection(UpdateItem::SystemSection); systemItems += updateItem; } } const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) { return a->name() < b->name(); }; qSort(appItems.begin(), appItems.end(), sortUpdateItems); qSort(systemItems.begin(), systemItems.end(), sortUpdateItems); m_updateItems = (QVector() << appItems << systemItems); endResetModel(); Q_EMIT hasUpdatesChanged(!resources.isEmpty()); Q_EMIT toUpdateChanged(); } bool UpdateModel::hasUpdates() const { return rowCount() > 0; } ResourcesUpdatesModel* UpdateModel::backend() const { return m_updates; } int UpdateModel::toUpdateCount() const { int ret = 0; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += item->checked() != Qt::Unchecked ? 1 : 0; } return ret; } int UpdateModel::totalUpdatesCount() const { int ret = 0; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } return ret; } UpdateItem * UpdateModel::itemFromResource(AbstractResource* res) { foreach (UpdateItem* item, m_updateItems) { if (item->app() == res) return item; } return nullptr; } QString UpdateModel::updateSize() const { return KFormat().formatByteSize(m_updates->updateSize()); } QModelIndex UpdateModel::indexFromItem(UpdateItem* item) const { return index(m_updateItems.indexOf(item), 0, {}); } UpdateItem * UpdateModel::itemFromIndex(const QModelIndex& index) const { return m_updateItems[index.row()]; } void UpdateModel::resourceDataChanged(AbstractResource* res, const QVector& properties) { auto item = itemFromResource(res); if (!item) return; const auto index = indexFromItem(item); if (properties.contains("state")) Q_EMIT dataChanged(index, index, {SizeRole, VersionRole}); else if (properties.contains("size")) { Q_EMIT dataChanged(index, index, {SizeRole}); m_updateSizeTimer->start(); } } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp index 23e73946..16341639 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp @@ -1,1286 +1,1299 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FlatpakBackend.h" #include "FlatpakFetchDataJob.h" #include "FlatpakResource.h" #include "FlatpakSourcesBackend.h" #include "FlatpakJobTransaction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FlatpakSourcesBackend.h" DISCOVER_BACKEND_PLUGIN(FlatpakBackend) static FlatpakResource::Id idForInstalledRef(FlatpakInstallation *installation, FlatpakInstalledRef *ref) { const FlatpakResource::ResourceType appType = flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime; const QString name = QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))); const QString appId = appType == FlatpakResource::DesktopApp ? QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))) + QStringLiteral(".desktop") : name; const QString arch = QString::fromUtf8(flatpak_ref_get_arch(FLATPAK_REF(ref))); const QString branch = QString::fromUtf8(flatpak_ref_get_branch(FLATPAK_REF(ref))); return { installation, QString::fromUtf8(flatpak_installed_ref_get_origin(ref)), appType, appId, branch, arch }; } FlatpakBackend::FlatpakBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(AppStreamIntegration::global()->reviews()) , m_refreshAppstreamMetadataJobs(0) , m_threadPool(new QThreadPool(this)) { g_autoptr(GError) error = nullptr; m_cancellable = g_cancellable_new(); connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FlatpakBackend::updatesCountChanged); // Load flatpak installation if (!setupFlatpakInstallations(&error)) { qWarning() << "Failed to setup flatpak installations:" << error->message; } else { loadAppsFromAppstreamData(); m_sources = new FlatpakSourcesBackend(m_installations, this); SourcesModel::global()->addSourcesBackend(m_sources); } connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, &FlatpakBackend::announceRatingsReady); } FlatpakBackend::~FlatpakBackend() { g_cancellable_cancel(m_cancellable); + m_threadPool.waitForDone(200); m_threadPool.clear(); for(auto inst : m_installations) g_object_unref(inst); g_object_unref(m_cancellable); } bool FlatpakBackend::isValid() const { return m_sources && !m_installations.isEmpty(); } void FlatpakBackend::announceRatingsReady() { emitRatingsReady(); const auto ids = m_reviews->appstreamIds().toSet(); foreach(AbstractResource* res, m_resources) { if (ids.contains(res->appstreamId())) { res->ratingFetched(); } } } class FlatpakFetchRemoteResourceJob : public QNetworkAccessManager { Q_OBJECT public: FlatpakFetchRemoteResourceJob(const QUrl &url, FlatpakBackend *backend) : QNetworkAccessManager(backend) , m_backend(backend) , m_url(url) { } void start() { auto replyGet = get(QNetworkRequest(m_url)); connect(replyGet, &QNetworkReply::finished, this, [this, replyGet] { const QUrl originalUrl = replyGet->request().url(); if (replyGet->error() != QNetworkReply::NoError) { qWarning() << "couldn't download" << originalUrl << replyGet->errorString(); Q_EMIT jobFinished(false, nullptr); return; } const QUrl fileUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/') + originalUrl.fileName()); auto replyPut = put(QNetworkRequest(fileUrl), replyGet->readAll()); connect(replyPut, &QNetworkReply::finished, this, [this, originalUrl, fileUrl, replyPut]() { if (replyPut->error() == QNetworkReply::NoError) { auto res = m_backend->resourceForFile(fileUrl); if (res) { FlatpakResource *resource = qobject_cast(res); resource->setResourceFile(originalUrl); Q_EMIT jobFinished(true, resource); } else { - qWarning() << "couldn't download" << originalUrl << "into" << fileUrl << replyPut->errorString(); + qWarning() << "couldn't create resource from" << fileUrl.toLocalFile(); Q_EMIT jobFinished(false, nullptr); } + } else { + qWarning() << "couldn't save" << originalUrl << replyPut->errorString(); + Q_EMIT jobFinished(false, nullptr); } }); }); } Q_SIGNALS: void jobFinished(bool success, FlatpakResource *resource); private: FlatpakBackend *m_backend; QUrl m_url; }; FlatpakRemote * FlatpakBackend::getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const { auto remotes = flatpak_installation_list_remotes(installation, m_cancellable, nullptr); if (!remotes) { return nullptr; } const QByteArray comparableUrl = url.toUtf8(); for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); if (comparableUrl == flatpak_remote_get_url(remote)) { return remote; } } return nullptr; } FlatpakInstalledRef * FlatpakBackend::getInstalledRefForApp(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) const { FlatpakInstalledRef *ref = nullptr; g_autoptr(GError) localError = nullptr; if (!flatpakInstallation) { return ref; } const auto type = resource->type() == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME; return flatpak_installation_get_installed_ref(flatpakInstallation, type, resource->flatpakName().toUtf8().constData(), resource->arch().toUtf8().constData(), resource->branch().toUtf8().constData(), m_cancellable, &localError); } FlatpakResource * FlatpakBackend::getAppForInstalledRef(FlatpakInstallation *flatpakInstallation, FlatpakInstalledRef *ref) const { return m_resources.value(idForInstalledRef(flatpakInstallation, ref)); } FlatpakResource * FlatpakBackend::getRuntimeForApp(FlatpakResource *resource) const { FlatpakResource *runtime = nullptr; const auto runtimeInfo = resource->runtime().split(QLatin1Char('/')); if (runtimeInfo.count() != 3) { return runtime; } for(auto it = m_resources.constBegin(), itEnd = m_resources.constEnd(); it!=itEnd; ++it) { const auto id = it.key(); if (id.type == FlatpakResource::Runtime && id.id == runtimeInfo.at(0) && id.branch == runtimeInfo.at(2)) { runtime = *it; break; } } // TODO if runtime wasn't found, create a new one from available info if (!runtime) { qWarning() << "could not find runtime" << runtimeInfo << resource; } return runtime; } FlatpakResource * FlatpakBackend::addAppFromFlatpakBundle(const QUrl &url) { g_autoptr(GBytes) appstreamGz = nullptr; g_autoptr(GError) localError = nullptr; g_autoptr(GFile) file = nullptr; g_autoptr(FlatpakBundleRef) bundleRef = nullptr; AppStream::Component asComponent; file = g_file_new_for_path(url.toLocalFile().toUtf8().constData()); bundleRef = flatpak_bundle_ref_new(file, &localError); if (!bundleRef) { qWarning() << "Failed to load bundle:" << localError->message; return nullptr; } g_autoptr(GBytes) metadata = flatpak_bundle_ref_get_metadata(bundleRef); appstreamGz = flatpak_bundle_ref_get_appstream(bundleRef); if (appstreamGz) { g_autoptr(GZlibDecompressor) decompressor = nullptr; g_autoptr(GInputStream) streamGz = nullptr; g_autoptr(GInputStream) streamData = nullptr; g_autoptr(GBytes) appstream = nullptr; /* decompress data */ decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); streamGz = g_memory_input_stream_new_from_bytes (appstreamGz); if (!streamGz) { return nullptr; } streamData = g_converter_input_stream_new (streamGz, G_CONVERTER (decompressor)); appstream = g_input_stream_read_bytes (streamData, 0x100000, m_cancellable, &localError); if (!appstream) { qWarning() << "Failed to extract appstream metadata from bundle:" << localError->message; return nullptr; } gsize len = 0; gconstpointer data = g_bytes_get_data(appstream, &len); AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parse(QString::fromUtf8((char*)data, len), AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return nullptr; } const QList components = metadata.components(); if (components.size()) { asComponent = AppStream::Component(components.first()); } else { qWarning() << "Failed to parse appstream metadata"; return nullptr; } } else { qWarning() << "No appstream metadata in bundle"; QTemporaryFile tempFile; tempFile.setAutoRemove(false); if (!tempFile.open()) { qWarning() << "Failed to get metadata file"; return nullptr; } gsize len = 0; QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); tempFile.write(metadataContent); tempFile.close(); // Parse the temporary file QSettings setting(tempFile.fileName(), QSettings::NativeFormat); setting.beginGroup(QLatin1String("Application")); asComponent.setName(setting.value(QLatin1String("name")).toString()); tempFile.remove(); } FlatpakResource *resource = new FlatpakResource(asComponent, preferredInstallation(), this); gsize len = 0; QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); if (!updateAppMetadata(resource, metadataContent)) { delete resource; qWarning() << "Failed to update metadata from app bundle"; return nullptr; } g_autoptr(GBytes) iconData = flatpak_bundle_ref_get_icon(bundleRef, 128); if (!iconData) { iconData = flatpak_bundle_ref_get_icon(bundleRef, 64); } if (iconData) { gsize len = 0; char * data = (char *)g_bytes_get_data(iconData, &len); QPixmap pixmap; pixmap.loadFromData(QByteArray(data, len), "PNG"); resource->setBundledIcon(pixmap); } const QString origin = QString::fromUtf8(flatpak_bundle_ref_get_origin(bundleRef)); resource->setDownloadSize(0); resource->setInstalledSize(flatpak_bundle_ref_get_installed_size(bundleRef)); resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::AlreadyKnown); resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::AlreadyKnown); resource->setFlatpakFileType(QStringLiteral("flatpak")); resource->setOrigin(origin.isEmpty() ? i18n("Local bundle") : origin); resource->setResourceFile(url); resource->setState(FlatpakResource::None); resource->setType(FlatpakResource::DesktopApp); addResource(resource); return resource; } FlatpakResource * FlatpakBackend::addAppFromFlatpakRef(const QUrl &url) { QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString refurl = settings.value(QStringLiteral("Flatpak Ref/Url")).toString(); g_autoptr(GError) error = nullptr; g_autoptr(FlatpakRemoteRef) remoteRef = nullptr; { QFile f(url.toLocalFile()); if (!f.open(QFile::ReadOnly | QFile::Text)) { return nullptr; } QByteArray contents = f.readAll(); g_autoptr(GBytes) bytes = g_bytes_new (contents.data(), contents.size()); remoteRef = flatpak_installation_install_ref_file (preferredInstallation(), bytes, m_cancellable, &error); if (!remoteRef) { - qWarning() << "Failed to install ref file:" << error->message; + qWarning() << "Failed to create install ref file:" << error->message; + const auto resources = resourcesByAppstreamName(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); + if (!resources.isEmpty()) { + return qobject_cast(resources.constFirst()); + } return nullptr; } } const auto remoteName = flatpak_remote_ref_get_remote_name(remoteRef); auto ref = FLATPAK_REF(remoteRef); AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Ref/Homepage")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Ref/Description")).toString()); asComponent.setName(settings.value(QStringLiteral("Flatpak Ref/Title")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Ref/Comment")).toString()); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Ref/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), this); resource->setFlatpakFileType(QStringLiteral("flatpakref")); resource->setOrigin(QString::fromUtf8(remoteName)); resource->updateFromRef(ref); QUrl runtimeUrl = QUrl(settings.value(QStringLiteral("Flatpak Ref/RuntimeRepo")).toString()); if (!runtimeUrl.isEmpty()) { auto installation = preferredInstallation(); // We need to fetch metadata to find information about required runtime auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, installation, resource)); connect(fw, &QFutureWatcher::finished, this, [this, installation, resource, fw, runtimeUrl]() { const auto metadata = fw->result(); // Even when we failed to fetch information about runtime we still want to show the application if (metadata.isEmpty()) { onFetchMetadataFinished(installation, resource, metadata); } else { updateAppMetadata(resource, metadata); auto runtime = getRuntimeForApp(resource); if (!runtime || (runtime && !runtime->isInstalled())) { FlatpakFetchRemoteResourceJob *fetchRemoteResource = new FlatpakFetchRemoteResourceJob(runtimeUrl, this); connect(fetchRemoteResource, &FlatpakFetchRemoteResourceJob::jobFinished, this, [this, resource] (bool success, FlatpakResource *repoResource) { if (success) { installApplication(repoResource); } addResource(resource); }); fetchRemoteResource->start(); return; } else { addResource(resource); } } fw->deleteLater(); }); } else { addResource(resource); } return resource; } FlatpakResource * FlatpakBackend::addSourceFromFlatpakRepo(const QUrl &url) { Q_ASSERT(url.isLocalFile()); QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString gpgKey = settings.value(QStringLiteral("Flatpak Repo/GPGKey")).toString(); const QString title = settings.value(QStringLiteral("Flatpak Repo/Title")).toString(); const QString repoUrl = settings.value(QStringLiteral("Flatpak Repo/Url")).toString(); if (gpgKey.isEmpty() || title.isEmpty() || repoUrl.isEmpty()) { return nullptr; } if (gpgKey.startsWith(QStringLiteral("http://")) || gpgKey.startsWith(QStringLiteral("https://"))) { return nullptr; } AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Repo/Homepage")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Repo/Comment")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Repo/Description")).toString()); asComponent.setName(title); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Repo/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), this); // Use metadata only for stuff which are not common for all resources resource->addMetadata(QStringLiteral("gpg-key"), gpgKey); resource->addMetadata(QStringLiteral("repo-url"), repoUrl); resource->setBranch(settings.value(QStringLiteral("Flatpak Repo/DefaultBranch")).toString()); resource->setFlatpakName(url.fileName().remove(QStringLiteral(".flatpakrepo"))); resource->setType(FlatpakResource::Source); auto repo = flatpak_installation_get_remote_by_name(preferredInstallation(), resource->flatpakName().toUtf8().constData(), m_cancellable, nullptr); if (!repo) { resource->setState(AbstractResource::State::None); } else { resource->setState(AbstractResource::State::Installed); } return resource; } void FlatpakBackend::addResource(FlatpakResource *resource) { // Update app with all possible information we have if (!parseMetadataFromAppBundle(resource)) { qWarning() << "Failed to parse metadata from app bundle for" << resource->name(); } auto installation = resource->installation(); updateAppState(installation, resource); // This will update also metadata (required runtime) updateAppSize(installation, resource); m_resources.insert(resource->uniqueId(), resource); } class FlatpakSource { public: FlatpakSource(FlatpakRemote* remote) : m_remote(remote) {} bool isEnabled() const { return !flatpak_remote_get_disabled(m_remote); } QString appstreamDir() const { g_autoptr(GFile) appstreamDir = flatpak_remote_get_appstream_dir(m_remote, nullptr); if (!appstreamDir) { qWarning() << "No appstream dir for" << flatpak_remote_get_name(m_remote); return {}; } return QString::fromUtf8(g_file_get_path(appstreamDir)); } QString name() const { return QString::fromUtf8(flatpak_remote_get_name(m_remote)); } private: FlatpakRemote* m_remote; }; void FlatpakBackend::loadAppsFromAppstreamData() { for (auto installation : qAsConst(m_installations)) { // Load applications from appstream metadata if (!loadAppsFromAppstreamData(installation)) { qWarning() << "Failed to load packages from appstream data from installation" << installation; } } } bool FlatpakBackend::loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); GPtrArray *remotes = flatpak_installation_list_remotes(flatpakInstallation, m_cancellable, nullptr); if (!remotes) { return false; } m_refreshAppstreamMetadataJobs += remotes->len; for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); g_autoptr(GFile) fileTimestamp = flatpak_remote_get_appstream_timestamp(remote, nullptr); QFileInfo fileInfo = QFileInfo(QString::fromUtf8(g_file_get_path(fileTimestamp))); // Refresh appstream metadata in case they have never been refreshed or the cache is older than 6 hours if (!fileInfo.exists() || fileInfo.lastModified().toUTC().secsTo(QDateTime::currentDateTimeUtc()) > 21600) { refreshAppstreamMetadata(flatpakInstallation, remote); } else { integrateRemote(flatpakInstallation, remote); } } return true; } void FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote) { Q_ASSERT(m_refreshAppstreamMetadataJobs != 0); m_refreshAppstreamMetadataJobs--; FlatpakSource source(remote); if (!source.isEnabled() || flatpak_remote_get_noenumerate(remote)) { return; } const QString appstreamDirPath = source.appstreamDir(); const QString appstreamIconsPath = source.appstreamDir() + QLatin1String("/icons/"); const QString appDirFileName = appstreamDirPath + QLatin1String("/appstream.xml.gz"); if (!QFile::exists(appDirFileName)) { qWarning() << "No" << appDirFileName << "appstream metadata found for" << source.name(); return; } auto fw = new QFutureWatcher>(this); fw->setFuture(QtConcurrent::run(&m_threadPool, [appDirFileName]() -> QList { AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parseFile(appDirFileName, AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return {}; } return metadata.components(); })); const auto sourceName = source.name(); acquireFetching(true); connect(fw, &QFutureWatcher>::finished, this, [this, fw, flatpakInstallation, appstreamIconsPath, sourceName]() { const auto components = fw->result(); foreach (const AppStream::Component& appstreamComponent, components) { FlatpakResource *resource = new FlatpakResource(appstreamComponent, flatpakInstallation, this); resource->setIconPath(appstreamIconsPath); resource->setOrigin(sourceName); addResource(resource); } if (!m_refreshAppstreamMetadataJobs) { loadInstalledApps(); checkForUpdates(); } acquireFetching(false); fw->deleteLater(); }); } void FlatpakBackend::loadInstalledApps() { for (auto installation : qAsConst(m_installations)) { // Load installed applications and update existing resources with info from installed application if (!loadInstalledApps(installation)) { qWarning() << "Failed to load installed packages from installation" << installation; } } } bool FlatpakBackend::loadInstalledApps(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); g_autoptr(GError) localError = nullptr; g_autoptr(GPtrArray) refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates:" << localError->message; return false; } const QString pathExports = FlatpakResource::installationPath(flatpakInstallation) + QLatin1String("/exports/"); const QString pathApps = pathExports + QLatin1String("share/applications/"); for (uint i = 0; i < refs->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(refs, i)); if (flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_RUNTIME) { continue; } const auto res = getAppForInstalledRef(flatpakInstallation, ref); if (res) { res->setState(AbstractResource::Installed); continue; } const auto name = QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))); AppStream::Metadata metadata; const QString fnDesktop = pathApps + name + QLatin1String(".desktop"); AppStream::Metadata::MetadataError error = metadata.parseFile(fnDesktop, AppStream::Metadata::FormatKindDesktopEntry); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error << fnDesktop; continue; } auto component = metadata.component(); component.setId(name + QLatin1String(".desktop")); FlatpakResource *resource = new FlatpakResource(component, flatpakInstallation, this); resource->setIconPath(pathExports); resource->setState(AbstractResource::Installed); resource->setOrigin(QString::fromUtf8(flatpak_installed_ref_get_origin(ref))); resource->updateFromRef(FLATPAK_REF(ref)); addResource(resource); } return true; } void FlatpakBackend::loadLocalUpdates(FlatpakInstallation *flatpakInstallation) { g_autoptr(GError) localError = nullptr; g_autoptr(GPtrArray) refs = nullptr; refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates:" << localError->message; return; } for (uint i = 0; i < refs->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(refs, i)); const gchar *latestCommit = flatpak_installed_ref_get_latest_commit(ref); if (!latestCommit) { qWarning() << "Couldn't get latest commit for" << flatpak_ref_get_name(FLATPAK_REF(ref)); continue; } const gchar *commit = flatpak_ref_get_commit(FLATPAK_REF(ref)); if (g_strcmp0(commit, latestCommit) == 0) { continue; } FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } void FlatpakBackend::loadRemoteUpdates(FlatpakInstallation* installation) { auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, [installation, this]() -> GPtrArray * { g_autoptr(GError) localError = nullptr; GPtrArray *refs = flatpak_installation_list_installed_refs_for_update(installation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; } return refs; })); connect(fw, &QFutureWatcher::finished, this, [this, installation, fw](){ auto refs = fw->result(); onFetchUpdatesFinished(installation, refs); fw->deleteLater(); }); } void FlatpakBackend::onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates) { if (!updates) { qWarning() << "could not get updates for" << flatpakInstallation; return; } g_autoptr(GPtrArray) fetchedUpdates = updates; for (uint i = 0; i < fetchedUpdates->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i)); FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } bool FlatpakBackend::parseMetadataFromAppBundle(FlatpakResource *resource) { g_autoptr(FlatpakRef) ref = nullptr; g_autoptr(GError) localError = nullptr; AppStream::Bundle bundle = resource->appstreamComponent().bundle(AppStream::Bundle::KindFlatpak); // Get arch/branch/commit/name from FlatpakRef if (!bundle.isEmpty()) { ref = flatpak_ref_parse(bundle.id().toUtf8().constData(), &localError); if (!ref) { qWarning() << "Failed to parse" << bundle.id() << localError->message; return false; } else { resource->updateFromRef(ref); } } return true; } class FlatpakRefreshAppstreamMetadataJob : public QThread { Q_OBJECT public: FlatpakRefreshAppstreamMetadataJob(FlatpakInstallation *installation, FlatpakRemote *remote) : QThread() , m_cancellable(g_cancellable_new()) , m_installation(installation) , m_remote(remote) { connect(this, &FlatpakRefreshAppstreamMetadataJob::finished, this, &QObject::deleteLater); } ~FlatpakRefreshAppstreamMetadataJob() { g_object_unref(m_cancellable); } void cancel() { g_cancellable_cancel(m_cancellable); } void run() override { g_autoptr(GError) localError = nullptr; #if FLATPAK_CHECK_VERSION(0,9,4) // With Flatpak 0.9.4 we can use flatpak_installation_update_appstream_full_sync() providing progress reporting which we don't use at this moment, but still // better to use newer function in case the previous one gets deprecated if (!flatpak_installation_update_appstream_full_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, nullptr, nullptr, m_cancellable, &localError)) { #else if (!flatpak_installation_update_appstream_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, m_cancellable, &localError)) { #endif qWarning() << "Failed to refresh appstream metadata for " << flatpak_remote_get_name(m_remote) << ": " << (localError ? localError->message : ""); Q_EMIT jobRefreshAppstreamMetadataFailed(); } else { Q_EMIT jobRefreshAppstreamMetadataFinished(m_installation, m_remote); } } Q_SIGNALS: void jobRefreshAppstreamMetadataFailed(); void jobRefreshAppstreamMetadataFinished(FlatpakInstallation *installation, FlatpakRemote *remote); private: GCancellable *m_cancellable; FlatpakInstallation *m_installation; FlatpakRemote *m_remote; }; void FlatpakBackend::refreshAppstreamMetadata(FlatpakInstallation *installation, FlatpakRemote *remote) { FlatpakRefreshAppstreamMetadataJob *job = new FlatpakRefreshAppstreamMetadataJob(installation, remote); connect(job, &FlatpakRefreshAppstreamMetadataJob::jobRefreshAppstreamMetadataFailed, this, [this] () { m_refreshAppstreamMetadataJobs--; }); connect(job, &FlatpakRefreshAppstreamMetadataJob::jobRefreshAppstreamMetadataFinished, this, &FlatpakBackend::integrateRemote); job->start(); } bool FlatpakBackend::setupFlatpakInstallations(GError **error) { if (qEnvironmentVariableIsSet("FLATPAK_TEST_MODE")) { const QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1String("/discover-flatpak-test"); qDebug() << "running flatpak backend on test mode" << path; g_autoptr(GFile) file = g_file_new_for_path(QFile::encodeName(path).constData()); m_installations << flatpak_installation_new_for_path(file, true, m_cancellable, error); return true; } GPtrArray *installations = flatpak_get_system_installations(m_cancellable, error); if (*error) { qWarning() << "Failed to call flatpak_get_system_installations:" << (*error)->message; } for (uint i = 0; installations && i < installations->len; i++) { m_installations << FLATPAK_INSTALLATION(g_ptr_array_index(installations, i)); } auto user = flatpak_installation_new_user(m_cancellable, error); if (user) { m_installations << user; } return !m_installations.isEmpty(); } void FlatpakBackend::updateAppInstalledMetadata(FlatpakInstalledRef *installedRef, FlatpakResource *resource) { // Update the rest resource->updateFromRef(FLATPAK_REF(installedRef)); resource->setInstalledSize(flatpak_installed_ref_get_installed_size(installedRef)); resource->setOrigin(QString::fromUtf8(flatpak_installed_ref_get_origin(installedRef))); if (resource->state() < AbstractResource::Installed) resource->setState(AbstractResource::Installed); } bool FlatpakBackend::updateAppMetadata(FlatpakInstallation* flatpakInstallation, FlatpakResource *resource) { g_autoptr(GFile) installationPath = nullptr; if (resource->type() != FlatpakResource::DesktopApp) { return true; } installationPath = flatpak_installation_get_path(flatpakInstallation); const QString path = QString::fromUtf8(g_file_get_path(installationPath)) + QStringLiteral("/app/%1/%2/%3/active/metadata").arg(resource->flatpakName()).arg(resource->arch()).arg(resource->branch()); if (QFile::exists(path)) { return updateAppMetadata(resource, path); } else { auto fw = new QFutureWatcher(this); fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, flatpakInstallation, resource)); connect(fw, &QFutureWatcher::finished, this, [this, flatpakInstallation, resource, fw]() { const auto metadata = fw->result(); if (!metadata.isEmpty()) onFetchMetadataFinished(flatpakInstallation, resource, metadata); fw->deleteLater(); }); // Return false to indicate we cannot continue (right now used only in updateAppSize()) return false; } } void FlatpakBackend::onFetchMetadataFinished(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, const QByteArray &metadata) { updateAppMetadata(resource, metadata); // Right now we attempt to update metadata for calculating the size so call updateSizeFromRemote() // as it's what we want. In future if there are other reason to update metadata we will need to somehow // distinguish betwen these calls updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppMetadata(FlatpakResource *resource, const QString &path) { // Parse the temporary file QSettings setting(path, QSettings::NativeFormat); setting.beginGroup(QLatin1String("Application")); // Set the runtime in form of name/arch/version which can be later easily parsed resource->setRuntime(setting.value(QLatin1String("runtime")).toString()); // TODO get more information? return true; } bool FlatpakBackend::updateAppMetadata(FlatpakResource *resource, const QByteArray &data) { //We just find the runtime with a regex, QSettings only can read from disk (and so does KConfig) const QRegularExpression rx(QStringLiteral("runtime=(.*)")); const auto match = rx.match(QString::fromUtf8(data)); if (!match.isValid()) { return false; } resource->setRuntime(match.captured(1)); return true; } bool FlatpakBackend::updateAppSize(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Check if the size is already set, we should also distiguish between download and installed size, // right now it doesn't matter whether we get size for installed or not installed app, but if we // start making difference then for not installed app check download and install size separately if (resource->state() == AbstractResource::Installed) { // The size appears to be already set (from updateAppInstalledMetadata() apparently) if (resource->installedSize() > 0) { return true; } } else { if (resource->installedSize() > 0 && resource->downloadSize() > 0) { return true; } } // Check if we know the needed runtime which is needed for calculating the size if (resource->runtime().isEmpty()) { if (!updateAppMetadata(flatpakInstallation, resource)) { return false; } } return updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppSizeFromRemote(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Calculate the runtime size if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { auto runtime = getRuntimeForApp(resource); if (runtime) { // Re-check runtime state if case a new one was created updateAppState(flatpakInstallation, runtime); if (!runtime->isInstalled()) { if (!updateAppSize(flatpakInstallation, runtime)) { qWarning() << "Failed to get runtime size needed for total size of" << resource->name(); return false; } // Set required download size to include runtime size even now, in case we fail to // get the app size (e.g. when installing bundles where download size is 0) resource->setDownloadSize(runtime->downloadSize()); } } } if (resource->state() == AbstractResource::Installed) { g_autoptr(FlatpakInstalledRef) ref = nullptr; ref = getInstalledRefForApp(flatpakInstallation, resource); if (!ref) { qWarning() << "Failed to get installed size of" << resource->name(); return false; } resource->setInstalledSize(flatpak_installed_ref_get_installed_size(ref)); } else { if (resource->origin().isEmpty()) { qWarning() << "Failed to get size of" << resource->name() << " because of missing origin"; return false; } auto futureWatcher = new QFutureWatcher(this); futureWatcher->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchFlatpakSize, flatpakInstallation, resource)); connect(futureWatcher, &QFutureWatcher::finished, this, [this, resource, futureWatcher]() { auto value = futureWatcher->result(); if (value.valid) { onFetchSizeFinished(resource, value.downloadSize, value.installedSize); } else { resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::UnknownOrFailed); resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::UnknownOrFailed); } futureWatcher->deleteLater(); }); } return true; } void FlatpakBackend::onFetchSizeFinished(FlatpakResource *resource, guint64 downloadSize, guint64 installedSize) { FlatpakResource *runtime = nullptr; if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { runtime = getRuntimeForApp(resource); } if (runtime && !runtime->isInstalled()) { resource->setDownloadSize(runtime->downloadSize() + downloadSize); resource->setInstalledSize(installedSize); } else { resource->setDownloadSize(downloadSize); resource->setInstalledSize(installedSize); } } void FlatpakBackend::updateAppState(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { FlatpakInstalledRef *ref = getInstalledRefForApp(flatpakInstallation, resource); if (ref) { // If the app is installed, we can set information about commit, arch etc. updateAppInstalledMetadata(ref, resource); } else { // TODO check if the app is actuall still available resource->setState(AbstractResource::None); } } void FlatpakBackend::acquireFetching(bool f) { if (f) m_isFetching++; else m_isFetching--; if ((!f && m_isFetching==0) || (f && m_isFetching==1)) { emit fetchingChanged(); } if (m_isFetching==0) Q_EMIT initialized(); } int FlatpakBackend::updatesCount() const { return m_updater->updatesCount(); } -bool FlatpakBackend::flatpakResourceLessThan(AbstractResource* l, AbstractResource* r) +bool FlatpakBackend::flatpakResourceLessThan(AbstractResource* l, AbstractResource* r) const { return (l->isInstalled() != r->isInstalled()) ? l->isInstalled() : (l->origin() != r->origin()) ? m_sources->originIndex(l->origin()) < m_sources->originIndex(r->origin()) : l < r; } ResultsStream * FlatpakBackend::search(const AbstractResourcesBackend::Filters &filter) { if (filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakrepo")) || filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakref"))) { auto stream = new ResultsStream(QStringLiteral("FlatpakStream-http-")+filter.resourceUrl.fileName()); FlatpakFetchRemoteResourceJob *fetchResourceJob = new FlatpakFetchRemoteResourceJob(filter.resourceUrl, this); connect(fetchResourceJob, &FlatpakFetchRemoteResourceJob::jobFinished, this, [fetchResourceJob, stream] (bool success, FlatpakResource *resource) { if (success) { stream->resourcesFound({resource}); } stream->finish(); fetchResourceJob->deleteLater(); }); fetchResourceJob->start(); return stream; } else if(filter.resourceUrl.scheme() == QLatin1String("appstream")) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.resourceUrl.isEmpty() || !filter.extends.isEmpty()) return new ResultsStream(QStringLiteral("FlatpakStream-void"), {}); auto stream = new ResultsStream(QStringLiteral("FlatpakStream")); auto f = [this, stream, filter] () { QVector ret; foreach(AbstractResource* r, m_resources) { if (r->isTechnical() && filter.state != AbstractResource::Upgradeable) { continue; } if (r->state() < filter.state) continue; if (filter.search.isEmpty() || r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) { ret += r; } } auto f = [this](AbstractResource* l, AbstractResource* r) { return flatpakResourceLessThan(l,r); }; std::sort(ret.begin(), ret.end(), f); if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); stream->finish(); }; if (isFetching()) { connect(this, &FlatpakBackend::initialized, stream, f); } else { QTimer::singleShot(0, this, f); } return stream; } +QVector FlatpakBackend::resourcesByAppstreamName(const QString& name) const +{ + QVector resources; + foreach(FlatpakResource* res, m_resources) { + if (QString::compare(res->appstreamId(), name, Qt::CaseInsensitive)==0 || QString::compare(res->appstreamId(), name + QLatin1String(".desktop"), Qt::CaseInsensitive)==0) + resources << res; + } + auto f = [this](AbstractResource* l, AbstractResource* r) { return flatpakResourceLessThan(l, r); }; + std::sort(resources.begin(), resources.end(), f); + return resources; +} + ResultsStream * FlatpakBackend::findResourceByPackageName(const QUrl &url) { if (url.scheme() == QLatin1String("appstream")) { if (url.host().isEmpty()) passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { auto stream = new ResultsStream(QStringLiteral("FlatpakStream")); auto f = [this, stream, url] () { - QVector resources; - foreach(FlatpakResource* res, m_resources) { - if (QString::compare(res->appstreamId(), url.host(), Qt::CaseInsensitive)==0) - resources << res; - } - auto f = [this](AbstractResource* l, AbstractResource* r) { return flatpakResourceLessThan(l,r); }; - std::sort(resources.begin(), resources.end(), f); - + const auto resources = resourcesByAppstreamName(url.host()); if (!resources.isEmpty()) Q_EMIT stream->resourcesFound(resources); stream->finish(); }; if (isFetching()) { connect(this, &FlatpakBackend::initialized, stream, f); } else { QTimer::singleShot(0, this, f); } return stream; } } return new ResultsStream(QStringLiteral("FlatpakStream-packageName-void"), {}); } AbstractBackendUpdater * FlatpakBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend * FlatpakBackend::reviewsBackend() const { return m_reviews.data(); } Transaction* FlatpakBackend::installApplication(AbstractResource *app, const AddonList &addons) { Q_UNUSED(addons); FlatpakResource *resource = qobject_cast(app); if (resource->type() == FlatpakResource::Source) { // Let source backend handle this FlatpakRemote *remote = m_sources->installSource(resource); if (remote) { resource->setState(AbstractResource::Installed); m_refreshAppstreamMetadataJobs++; // Make sure we update appstream metadata first // FIXME we have to let flatpak to return the remote as the one created by FlatpakSourcesBackend will not have appstream directory refreshAppstreamMetadata(preferredInstallation(), flatpak_installation_get_remote_by_name(preferredInstallation(), flatpak_remote_get_name(remote), nullptr, nullptr)); } return nullptr; } FlatpakJobTransaction *transaction = nullptr; FlatpakInstallation *installation = resource->installation(); if (resource->propertyState(FlatpakResource::RequiredRuntime) == FlatpakResource::NotKnownYet && resource->type() == FlatpakResource::DesktopApp) { transaction = new FlatpakJobTransaction(resource, Transaction::InstallRole, true); connect(resource, &FlatpakResource::propertyStateChanged, [resource, transaction, this] (FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState state) { if (kind != FlatpakResource::RequiredRuntime) { return; } if (state == FlatpakResource::AlreadyKnown) { FlatpakResource *runtime = getRuntimeForApp(resource); if (runtime && !runtime->isInstalled()) { transaction->setRuntime(runtime); } } transaction->start(); }); } else { FlatpakResource *runtime = getRuntimeForApp(resource); if (runtime && !runtime->isInstalled()) { transaction = new FlatpakJobTransaction(resource, runtime, Transaction::InstallRole); } else { transaction = new FlatpakJobTransaction(resource, Transaction::InstallRole); } } connect(transaction, &FlatpakJobTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppState(installation, resource); } }); return transaction; } Transaction* FlatpakBackend::installApplication(AbstractResource *app) { return installApplication(app, {}); } Transaction* FlatpakBackend::removeApplication(AbstractResource *app) { FlatpakResource *resource = qobject_cast(app); if (resource->type() == FlatpakResource::Source) { // Let source backend handle this if (m_sources->removeSource(resource->flatpakName())) { resource->setState(AbstractResource::None); } return nullptr; } FlatpakInstallation *installation = resource->installation(); FlatpakJobTransaction *transaction = new FlatpakJobTransaction(resource, Transaction::RemoveRole); connect(transaction, &FlatpakJobTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppSize(installation, resource); } }); return transaction; } void FlatpakBackend::checkForUpdates() { for (auto installation : qAsConst(m_installations)) { // Load local updates, comparing current and latest commit loadLocalUpdates(installation); // Load updates from remote repositories loadRemoteUpdates(installation); } } AbstractResource * FlatpakBackend::resourceForFile(const QUrl &url) { if (!url.isLocalFile()) { return nullptr; } FlatpakResource *resource = nullptr; if (url.path().endsWith(QLatin1String(".flatpak"))) { resource = addAppFromFlatpakBundle(url); } else if (url.path().endsWith(QLatin1String(".flatpakref"))) { resource = addAppFromFlatpakRef(url); } else if (url.path().endsWith(QLatin1String(".flatpakrepo"))) { resource = addSourceFromFlatpakRepo(url); } return resource; } QString FlatpakBackend::displayName() const { return QStringLiteral("Flatpak"); } #include "FlatpakBackend.moc" diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.h b/libdiscover/backends/FlatpakBackend/FlatpakBackend.h index d9ddf2d9..3c5aa697 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.h @@ -1,119 +1,120 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef FLATPAKBACKEND_H #define FLATPAKBACKEND_H #include "FlatpakResource.h" #include #include #include #include #include extern "C" { #include } class QAction; class FlatpakSourcesBackend; class StandardBackendUpdater; class OdrsReviewsBackend; class FlatpakBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit FlatpakBackend(QObject *parent = nullptr); ~FlatpakBackend(); int updatesCount() const override; AbstractBackendUpdater * backendUpdater() const override; AbstractReviewsBackend * reviewsBackend() const override; ResultsStream * search(const AbstractResourcesBackend::Filters & search) override; ResultsStream * findResourceByPackageName(const QUrl &search); QList resources() const { return m_resources.values(); } bool isValid() const override; Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; Transaction* removeApplication(AbstractResource* app) override; bool isFetching() const override { return m_isFetching>0; } AbstractResource * resourceForFile(const QUrl & ) override; void checkForUpdates() override; QString displayName() const override; bool hasApplications() const override { return true; } FlatpakResource * addSourceFromFlatpakRepo(const QUrl &url); private Q_SLOTS: void onFetchMetadataFinished(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, const QByteArray &metadata); void onFetchSizeFinished(FlatpakResource *resource, guint64 downloadSize, guint64 installedSize); void onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates); Q_SIGNALS: //for tests void initialized(); private: - bool flatpakResourceLessThan(AbstractResource* l, AbstractResource* r); + bool flatpakResourceLessThan(AbstractResource* l, AbstractResource* r) const; void announceRatingsReady(); FlatpakInstallation * preferredInstallation() const { return m_installations.constFirst(); } void integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote); FlatpakRemote * getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const; FlatpakInstalledRef * getInstalledRefForApp(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) const; FlatpakResource * getAppForInstalledRef(FlatpakInstallation *flatpakInstallation, FlatpakInstalledRef *ref) const; FlatpakResource * getRuntimeForApp(FlatpakResource *resource) const; FlatpakResource * addAppFromFlatpakBundle(const QUrl &url); FlatpakResource * addAppFromFlatpakRef(const QUrl &url); void addResource(FlatpakResource *resource); void loadAppsFromAppstreamData(); bool loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation); void loadInstalledApps(); bool loadInstalledApps(FlatpakInstallation *flatpakInstallation); void loadLocalUpdates(FlatpakInstallation *flatpakInstallation); void loadRemoteUpdates(FlatpakInstallation *flatpakInstallation); bool parseMetadataFromAppBundle(FlatpakResource *resource); void refreshAppstreamMetadata(FlatpakInstallation *installation, FlatpakRemote *remote); bool setupFlatpakInstallations(GError **error); void updateAppInstalledMetadata(FlatpakInstalledRef *installedRef, FlatpakResource *resource); bool updateAppMetadata(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); bool updateAppMetadata(FlatpakResource *resource, const QByteArray &data); bool updateAppMetadata(FlatpakResource *resource, const QString &path); bool updateAppSize(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); bool updateAppSizeFromRemote(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); void updateAppState(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); + QVector resourcesByAppstreamName(const QString &name) const; void acquireFetching(bool f); QHash m_resources; StandardBackendUpdater *m_updater; FlatpakSourcesBackend *m_sources = nullptr; QSharedPointer m_reviews; uint m_isFetching = 0; uint m_refreshAppstreamMetadataJobs; GCancellable *m_cancellable; QVector m_installations; QThreadPool m_threadPool; }; #endif // FLATPAKBACKEND_H diff --git a/libdiscover/backends/FwupdBackend/CMakeLists.txt b/libdiscover/backends/FwupdBackend/CMakeLists.txt index 6dd9fa35..7c92343f 100644 --- a/libdiscover/backends/FwupdBackend/CMakeLists.txt +++ b/libdiscover/backends/FwupdBackend/CMakeLists.txt @@ -1,17 +1,17 @@ add_definitions( -DPROJECT_NAME=${PROJECT_NAME} -DPROJECT_VERSION=${PROJECT_VERSION}) set(fwupd-backend_SRCS FwupdResource.cpp FwupdBackend.cpp FwupdTransaction.cpp FwupdSourcesBackend.cpp ) find_package(GObject) include_directories(${LIBFWUPD_INCLUDE_DIRS}) add_library(fwupd-backend MODULE ${fwupd-backend_SRCS}) -target_link_libraries(fwupd-backend Qt5::Core Qt5::Widgets KF5::CoreAddons KF5::ConfigCore Discover::Common LIBFWUPD ${GObject}) +target_link_libraries(fwupd-backend Qt5::Core Qt5::Widgets Qt5::Concurrent KF5::CoreAddons KF5::ConfigCore Discover::Common LIBFWUPD ${GObject}) install(TARGETS fwupd-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover) diff --git a/libdiscover/backends/FwupdBackend/FwupdBackend.cpp b/libdiscover/backends/FwupdBackend/FwupdBackend.cpp index e7788339..8f9a8754 100644 --- a/libdiscover/backends/FwupdBackend/FwupdBackend.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdBackend.cpp @@ -1,620 +1,623 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdBackend.h" #include "FwupdResource.h" #include "FwupdTransaction.h" #include "FwupdSourcesBackend.h" #include #include #include +#include #include #include #include #include #include DISCOVER_BACKEND_PLUGIN(FwupdBackend) FwupdBackend::FwupdBackend(QObject* parent) : AbstractResourcesBackend(parent) , client(fwupd_client_new()) , m_updater(new StandardBackendUpdater(this)) { connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FwupdBackend::updatesCountChanged); SourcesModel::global()->addSourcesBackend(new FwupdSourcesBackend(this)); QTimer::singleShot(0, this, &FwupdBackend::checkForUpdates); } QMap FwupdBackend::gchecksumToQChryptographicHash() { static QMap map; if (map.isEmpty()) { map.insert(G_CHECKSUM_SHA1,QCryptographicHash::Sha1); map.insert(G_CHECKSUM_SHA256,QCryptographicHash::Sha256); map.insert(G_CHECKSUM_SHA512,QCryptographicHash::Sha512); map.insert(G_CHECKSUM_MD5,QCryptographicHash::Md5); } return map; } FwupdBackend::~FwupdBackend() { g_object_unref(client); } QString FwupdBackend::buildDeviceID(FwupdDevice* device) { QString DeviceID = QString::fromUtf8(fwupd_device_get_id(device)); DeviceID.replace(QLatin1Char('/'),QLatin1Char('_')); return QStringLiteral("org.fwupd.%1.device").arg(DeviceID); } void FwupdBackend::addResourceToList(FwupdResource* res) { - m_resources.insert(res->packageName().toLower(), res); + res->setParent(this); + auto &r = m_resources[res->packageName().toLower()]; + if (r) { + Q_EMIT resourceRemoved(r); + delete r; + } + r = res; + Q_ASSERT(m_resources.value(res->packageName().toLower()) == res); } FwupdResource * FwupdBackend::createDevice(FwupdDevice *device) { const QString name = QString::fromUtf8(fwupd_device_get_name(device)); - FwupdResource* res = new FwupdResource(name, true, this); + FwupdResource* res = new FwupdResource(name, nullptr); res->setId(buildDeviceID(device)); - if (fwupd_device_get_icons(device)->len >= 1) - res->setIconName(QString::fromUtf8((const gchar *)g_ptr_array_index(fwupd_device_get_icons(device),0)));// Check wether given icon exists or not! setDeviceDetails(res, device); return res; } FwupdResource * FwupdBackend::createRelease(FwupdDevice *device) { FwupdRelease *release = fwupd_device_get_release_default(device); const QString name = QString::fromUtf8(fwupd_release_get_name(release)); - FwupdResource* res = new FwupdResource(name, true, this); + FwupdResource* res = new FwupdResource(name, this); res->setDeviceID(QString::fromUtf8(fwupd_device_get_id(device))); setReleaseDetails(res, release); setDeviceDetails(res, device); res->setId(QString::fromUtf8(fwupd_release_get_appstream_id(release))); /* the same as we have already */ if (qstrcmp(fwupd_device_get_version(device), fwupd_release_get_version(release)) == 0) { qWarning() << "Fwupd Error: same firmware version as installed"; } return res; } void FwupdBackend::setReleaseDetails(FwupdResource *res, FwupdRelease *release) { + res->setOrigin(QString::fromUtf8(fwupd_release_get_remote_id(release))); res->setSummary(QString::fromUtf8(fwupd_release_get_summary(release))); res->setVendor(QString::fromUtf8(fwupd_release_get_vendor(release))); res->setSize(fwupd_release_get_size(release)); res->setVersion(QString::fromUtf8(fwupd_release_get_version(release))); res->setDescription(QString::fromUtf8((fwupd_release_get_description(release)))); res->setHomePage(QUrl(QString::fromUtf8(fwupd_release_get_homepage(release)))); res->setLicense(QString::fromUtf8(fwupd_release_get_license(release))); res->m_updateURI = QString::fromUtf8(fwupd_release_get_uri(release)); } + void FwupdBackend::setDeviceDetails(FwupdResource *res, FwupdDevice *dev) { res->isLiveUpdatable = fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); res->isOnlyOffline = fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_ONLY_OFFLINE); res->needsReboot = fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); res->isDeviceRemoval = !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_INTERNAL); res->needsBootLoader = fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); GPtrArray *guids = fwupd_device_get_guids(dev); if (guids->len > 0) { QString guidStr = QString::fromUtf8((char *)g_ptr_array_index(guids, 0)); for(uint i = 1; i < guids->len; i++) { guidStr += QLatin1Char(',') + QString::fromUtf8((char *)g_ptr_array_index(guids, i)); } res->guidString = guidStr; } if (fwupd_device_get_name(dev)) { QString vendorDesc = QString::fromUtf8(fwupd_device_get_name(dev)); const QString vendorName = QString::fromUtf8(fwupd_device_get_vendor(dev)); if (!vendorDesc.startsWith(vendorName)) vendorDesc = vendorName + QLatin1Char(' ') + vendorDesc; res->setName(vendorDesc); } res->setSummary(QString::fromUtf8(fwupd_device_get_summary(dev))); res->setVendor(QString::fromUtf8(fwupd_device_get_vendor(dev))); res->setReleaseDate((QDateTime::fromSecsSinceEpoch(fwupd_device_get_created(dev))).date()); res->setVersion(QString::fromUtf8(fwupd_device_get_version(dev))); res->setDescription(QString::fromUtf8((fwupd_device_get_description(dev)))); - res->setIconName(QString::fromUtf8("device-notifier")); -} -void FwupdBackend::populate() -{ - /* get devices */ - g_autoptr(GPtrArray) devices = fwupd_client_get_devices(client, nullptr, nullptr); - - if (devices) { - for(uint i = 0; i < devices->len; i++) { - FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i); - - addResourceToList(createDevice(device)); - } - } + if (fwupd_device_get_icons(dev)->len >= 1) + res->setIconName(QString::fromUtf8((const gchar *)g_ptr_array_index(fwupd_device_get_icons(dev), 0)));// Check wether given icon exists or not! + else + res->setIconName(QString::fromUtf8("device-notifier")); } void FwupdBackend::addUpdates() { g_autoptr(GError) error = nullptr; g_autoptr(GPtrArray) devices = fwupd_client_get_devices(client, nullptr, &error); if (!devices) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { qDebug() << "Fwupd Info: No Devices Found"; handleError(&error); } return; } for(uint i = 0; i < devices->len; i++) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i); if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /*Device is Locked Needs Unlocking*/ if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) { auto res = createDevice(device); res->setIsDeviceLocked(true); addResourceToList(res); connect(res, &FwupdResource::stateChanged, this, &FwupdBackend::updatesCountChanged); continue; } if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; g_autoptr(GError) error2 = nullptr; g_autoptr(GPtrArray) rels = fwupd_client_get_upgrades(client, fwupd_device_get_id(device), nullptr, &error2); if (rels) { fwupd_device_add_release(device, (FwupdRelease *)g_ptr_array_index(rels, 0)); auto res = createApp(device); if (!res) { qWarning() << "Fwupd Error: Cannot Create App From Device" << fwupd_device_get_name(device); } else { QString longdescription; for(uint j = 0; j < rels->len; j++) { FwupdRelease *release = (FwupdRelease *)g_ptr_array_index(rels, j); if (!fwupd_release_get_description(release)) continue; longdescription += QStringLiteral("Version %1\n").arg(QString::fromUtf8(fwupd_release_get_version(release))); longdescription += QString::fromUtf8(fwupd_release_get_description(release)) + QLatin1Char('\n'); } res->setDescription(longdescription); addResourceToList(res); } } else { if (!g_error_matches(error2, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { handleError(&error2); } } } } void FwupdBackend::addHistoricalUpdates() { g_autoptr(GError) error = nullptr; g_autoptr(FwupdDevice) device = fwupd_client_get_results(client, FWUPD_DEVICE_ID_ANY, nullptr, &error); if (!device) { handleError(&error); } else { FwupdResource* res = createRelease(device); if (!res) qWarning() << "Fwupd Error: Cannot Make App for" << fwupd_device_get_name(device); else { addResourceToList(res); } } } QByteArray FwupdBackend::getChecksum(const QString &filename, QCryptographicHash::Algorithm hashAlgorithm) { QFile f(filename); if (!f.open(QFile::ReadOnly)) return {}; QCryptographicHash hash(hashAlgorithm); if (!hash.addData(&f)) return {}; return hash.result().toHex(); } FwupdResource* FwupdBackend::createApp(FwupdDevice *device) { FwupdRelease *release = fwupd_device_get_release_default(device); GPtrArray *checksums; FwupdResource* app = createRelease(device); /* update unsupported */ if (!app->isLiveUpdatable) { qWarning() << "Fwupd Error: " << app->m_name << "[" << app->m_id << "]" << "cannot be updated "; return nullptr; } /* Important Attributes missing */ if (app->m_id.isNull()) { qWarning() << "Fwupd Error: No id for firmware"; return nullptr; } if (app->m_version.isNull()) { qWarning() << "Fwupd Error: No version! for " << app->m_id; return nullptr; } checksums = fwupd_release_get_checksums(release); if (checksums->len == 0) { qWarning() << "Fwupd Error: " << app->m_name << "[" << app->m_id << "] has no checksums, ignoring as unsafe"; return nullptr; } const QUrl update_uri(QString::fromUtf8(fwupd_release_get_uri(release))); if (!update_uri.isValid()) { qWarning() << "Fwupd Error: No Update URI available for" << app->m_name << "[" << app->m_id << "]"; return nullptr; } /* Checking for firmware in the cache? */ const QString filename_cache = cacheFile(QStringLiteral("fwupd"), QFileInfo(update_uri.path()).fileName()); Q_ASSERT(!filename_cache.isEmpty()); const QByteArray checksum_tmp = QByteArray(fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1)); /* Currently LVFS supports SHA1 only*/ if (checksum_tmp.isNull()) { qWarning() << "Fwupd Error: No valid checksum for" << filename_cache; } const QByteArray checksum = getChecksum(filename_cache, QCryptographicHash::Sha1); if (checksum_tmp != checksum) { qWarning() << "Fwupd Error: " << filename_cache << " does not match checksum, expected" << checksum_tmp << "got" << checksum; QFile::remove(filename_cache); return nullptr; } /* link file to application and return its reference */ app->m_file = filename_cache; if (!app->needsReboot) app->setState(AbstractResource::Upgradeable); return app; } bool FwupdBackend::downloadFile(const QUrl &uri, const QString &filename) { - QScopedPointer manager(new QNetworkAccessManager(this)); + QScopedPointer manager(new QNetworkAccessManager); QEventLoop loop; QTimer getTimer; connect(&getTimer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(manager.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); QNetworkReply *reply = manager->get(QNetworkRequest(uri)); getTimer.start(600000); // 60 Seconds TimeOout Period loop.exec(); if (!reply) { return false; } else if (QNetworkReply::NoError != reply->error() ) { qWarning() << "error fetching" << uri; return false; } else if (reply->error() == QNetworkReply::NoError) { QFile file(filename); if (file.open(QIODevice::WriteOnly)) { file.write(reply->readAll()); } else { qWarning() << "Fwupd Error: Cannot Open File to write Data" << filename; } } reply->deleteLater(); return true; } -void FwupdBackend::refreshRemotes(uint cacheAge) -{ - g_autoptr(GError) error = nullptr; - g_autoptr(GPtrArray) remotes = fwupd_client_get_remotes(client, nullptr, &error); - if (!remotes) - return; - - for(uint i = 0; i < remotes->len; i++) - { - FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i); - if (!fwupd_remote_get_enabled(remote)) - continue; - - if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL) - continue; - - refreshRemote(remote, cacheAge); - } -} - -void FwupdBackend::refreshRemote(FwupdRemote* remote, uint cacheAge) +void FwupdBackend::refreshRemote(FwupdBackend* backend, FwupdRemote* remote, uint cacheAge) { if (!fwupd_remote_get_filename_cache_sig(remote)) { qWarning() << "Fwupd Error: " << "Remote " << fwupd_remote_get_id(remote) << "has no cache signature!"; return; } /* check cache age */ if (cacheAge > 0) { quint64 age = fwupd_remote_get_age(remote); uint tmp = age < std::numeric_limits::max() ? (uint) age : std::numeric_limits::max(); if (tmp < cacheAge) { // qDebug() << "Fwupd Info:" << fwupd_remote_get_id(remote) << "is only" << tmp << "seconds old, so ignoring refresh! "; return; } } const QString cacheId = QStringLiteral("fwupd/remotes.d/%1").arg(QString::fromUtf8(fwupd_remote_get_id(remote))); const auto basenameSig = QString::fromUtf8(g_path_get_basename(fwupd_remote_get_filename_cache_sig(remote))); const QString filenameSig = cacheFile(cacheId, basenameSig); if (filenameSig.isEmpty()) return; /* download the signature first*/ const QUrl urlSig(QString::fromUtf8(fwupd_remote_get_metadata_uri_sig(remote))); const QString filenameSigTmp(filenameSig + QStringLiteral(".tmp")); if (!downloadFile(urlSig, filenameSigTmp)) { qWarning() << "failed to download" << urlSig; return; } Q_ASSERT(QFile::exists(filenameSigTmp)); const auto checksum = fwupd_remote_get_checksum(remote); const QCryptographicHash::Algorithm hashAlgorithm = gchecksumToQChryptographicHash()[fwupd_checksum_guess_kind(checksum)]; const QByteArray hash = getChecksum(filenameSigTmp, hashAlgorithm); - if (QByteArray(checksum) != hash) { - qDebug() << "Fwupd Error: signature of remote is wrong" << urlSig << "expected:" << checksum << "got" << hash; - QFile::remove(filenameSigTmp); - return; - } - const QByteArray oldHash = getChecksum(filenameSig, hashAlgorithm); if (oldHash == hash) { qDebug() << "remote hasn't changed:" << fwupd_remote_get_id(remote); QFile::remove(filenameSigTmp); return; } QFile::remove(filenameSig); if (!QFile::rename(filenameSigTmp, filenameSig)) { QFile::remove(filenameSigTmp); qWarning() << "Fwupd Error: cannot save remote signature" << filenameSigTmp << "to" << filenameSig; return; } QFile::remove(filenameSigTmp); const auto basename = QString::fromUtf8(g_path_get_basename(fwupd_remote_get_filename_cache(remote))); const QString filename = cacheFile(cacheId, basename); if (filename.isEmpty()) return; qDebug() << "Fwupd Info: saving new firmware metadata to:" << filename; const QUrl url(QString::fromUtf8(fwupd_remote_get_metadata_uri(remote))); if (!downloadFile(url, filename)) { - qWarning() << "Fwupd Error: cannot download file : " << filename; + qWarning() << "Fwupd Error: cannot download file:" << filename; return; } g_autoptr(GError) error = nullptr; - if (!fwupd_client_update_metadata(client, fwupd_remote_get_id(remote), filename.toUtf8().constData(), filenameSig.toUtf8().constData(), nullptr, &error)) + if (!fwupd_client_update_metadata(backend->client, fwupd_remote_get_id(remote), filename.toUtf8().constData(), filenameSig.toUtf8().constData(), nullptr, &error)) { - handleError(&error); + backend->handleError(&error); } } void FwupdBackend::handleError(GError **perror) { //TODO: localise the error message - if ((*perror)->code != FWUPD_ERROR_NOTHING_TO_DO) - Q_EMIT passiveMessage(QString::fromUtf8((*perror)->message)); + if ((*perror)->code != FWUPD_ERROR_NOTHING_TO_DO) { + const QString msg = QString::fromUtf8((*perror)->message); + QTimer::singleShot(0, this, [this, msg](){ + Q_EMIT passiveMessage(msg); + }); + } qWarning() << "Fwupd Error" << (*perror)->code << (*perror)->message; } QString FwupdBackend::cacheFile(const QString &kind, const QString &basename) { const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); const QString cacheDirFile = cacheDir.filePath(kind); if (!QFileInfo::exists(cacheDirFile) && !cacheDir.mkpath(kind)) { qWarning() << "Fwupd Error: cannot make cache directory!"; return {}; } return cacheDir.filePath(kind + QLatin1Char('/') + basename); } -void FwupdBackend::refresh() +void FwupdBackend::checkForUpdates() { if (m_fetching) return; - m_fetching = true; - emit fetchingChanged(); + auto fw = new QFutureWatcher(this); + fw->setFuture(QtConcurrent::run([this] () -> GPtrArray* + { + const uint cacheAge = (24*60*60); // Check for updates every day + g_autoptr(GError) error = nullptr; + + /* get devices */ + GPtrArray* devices = fwupd_client_get_devices(client, nullptr, nullptr); + + + g_autoptr(GPtrArray) remotes = fwupd_client_get_remotes(client, nullptr, &error); + for(uint i = 0; remotes && i < remotes->len; i++) + { + FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i); + if (!fwupd_remote_get_enabled(remote)) + continue; + + if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL) + continue; + + refreshRemote(this, remote, cacheAge); + } + return devices; + } + )); + connect(fw, &QFutureWatcher::finished, this, [this, fw]() { + m_fetching = true; + emit fetchingChanged(); - refreshRemotes(24*60*60); // Check for updates every day - addUpdates(); - addHistoricalUpdates(); + auto devices = fw->result(); + for(uint i = 0; devices && i < devices->len; i++) { + FwupdDevice *device = (FwupdDevice *) g_ptr_array_index(devices, i); - m_fetching = false; - emit fetchingChanged(); + addResourceToList(createDevice(device)); + } + g_ptr_array_unref(devices); + + + addUpdates(); + addHistoricalUpdates(); + + m_fetching = false; + emit fetchingChanged(); + fw->deleteLater(); + }); } int FwupdBackend::updatesCount() const { return m_updater->updatesCount(); } ResultsStream* FwupdBackend::search(const AbstractResourcesBackend::Filters& filter) { if (filter.resourceUrl.scheme() == QLatin1String("fwupd")) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.resourceUrl.isEmpty()) { return new ResultsStream(QStringLiteral("FwupdStream-void"), {}); } QVector ret; foreach(AbstractResource* r, m_resources) { - if (r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) + if (filter.search.isEmpty() || r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) ret += r; } return new ResultsStream(QStringLiteral("FwupdStream"), ret); } ResultsStream * FwupdBackend::findResourceByPackageName(const QUrl& search) { auto res = search.scheme() == QLatin1String("fwupd") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr; if (!res) { return new ResultsStream(QStringLiteral("FwupdStream"), {}); } else return new ResultsStream(QStringLiteral("FwupdStream"), { res }); } AbstractBackendUpdater* FwupdBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* FwupdBackend::reviewsBackend() const { return nullptr; } Transaction* FwupdBackend::installApplication(AbstractResource* app, const AddonList& addons) { Q_ASSERT(addons.isEmpty()); return installApplication(app); } Transaction* FwupdBackend::installApplication(AbstractResource* app) { return new FwupdTransaction(qobject_cast(app), this); } Transaction* FwupdBackend::removeApplication(AbstractResource* /*app*/) { qWarning() << "should not have reached here, it's not possible to uninstall a firmware"; return nullptr; } -void FwupdBackend::checkForUpdates() -{ - if (m_fetching) - return; - - populate(); - refresh(); -} - AbstractResource * FwupdBackend::resourceForFile(const QUrl& path) { g_autoptr(GError) error = nullptr; QMimeDatabase db; QMimeType type = db.mimeTypeForFile(path.fileName()); FwupdResource* app = nullptr; if (type.isValid() && type.inherits(QStringLiteral("application/vnd.ms-cab-compressed"))) { g_autofree gchar *filename = path.fileName().toUtf8().data(); g_autoptr(GPtrArray) devices = fwupd_client_get_details(client, filename, nullptr, &error); if (devices) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, 0); app = createRelease(device); app->setState(AbstractResource::None); for(uint i = 1; i < devices->len; i++) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i); FwupdResource* app_ = createRelease(device); app_->setState(AbstractResource::None); } addResourceToList(app); connect(app, &FwupdResource::stateChanged, this, &FwupdBackend::updatesCountChanged); } else { handleError(&error); } } return app; } QString FwupdBackend::displayName() const { return QStringLiteral("Firmware Updates"); } bool FwupdBackend::hasApplications() const { return false; } #include "FwupdBackend.moc" diff --git a/libdiscover/backends/FwupdBackend/FwupdBackend.h b/libdiscover/backends/FwupdBackend/FwupdBackend.h index 0f561545..b392c277 100644 --- a/libdiscover/backends/FwupdBackend/FwupdBackend.h +++ b/libdiscover/backends/FwupdBackend/FwupdBackend.h @@ -1,113 +1,107 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef FWUPDBACKEND_H #define FWUPDBACKEND_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include class QAction; class StandardBackendUpdater; class FwupdResource; class FwupdBackend : public AbstractResourcesBackend { Q_OBJECT Q_PROPERTY(int startElements MEMBER m_startElements) Q_ENUMS(Modes) public: explicit FwupdBackend(QObject* parent = nullptr); ~FwupdBackend(); int updatesCount() const override; AbstractBackendUpdater* backendUpdater() const override; AbstractReviewsBackend* reviewsBackend() const override; ResultsStream* search(const AbstractResourcesBackend::Filters & search) override; ResultsStream * findResourceByPackageName(const QUrl& search) ; QHash resources() const { return m_resources; } bool isValid() const override { return true; } // No external file dependencies that could cause runtime errors Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; Transaction* removeApplication(AbstractResource* app) override; bool isFetching() const override { return m_fetching; } AbstractResource * resourceForFile(const QUrl & ) override; void checkForUpdates() override; QString displayName() const override; bool hasApplications() const override; FwupdClient *client; + void handleError(GError **perror); - bool downloadFile(const QUrl &uri, const QString &filename); - void refreshRemotes(uint cacheAge); - void refreshRemote(FwupdRemote *remote, uint cacheAge); - QString cacheFile(const QString &kind, const QString &baseName); - FwupdResource * createDevice(FwupdDevice *device); +private: + void refreshRemotes(); FwupdResource * createRelease(FwupdDevice *device); FwupdResource * createApp(FwupdDevice *device); - QByteArray getChecksum(const QString &filename, QCryptographicHash::Algorithm hashAlgorithm); - QString buildDeviceID(FwupdDevice* device); void addUpdates(); void addResourceToList(FwupdResource *res); void addHistoricalUpdates(); - void setReleaseDetails(FwupdResource *res, FwupdRelease *release); - void setDeviceDetails(FwupdResource *res, FwupdDevice *device); - void handleError(GError **perror); + static void setReleaseDetails(FwupdResource *res, FwupdRelease *release); QSet getAllUpdates(); - QString getAppName(QString ID); - QMap gchecksumToQChryptographicHash(); - -public Q_SLOTS: - void refresh(); - -private: - void populate(); + static QMap gchecksumToQChryptographicHash(); + static QString cacheFile(const QString &kind, const QString &baseName); + static void refreshRemote(FwupdBackend* backend, FwupdRemote *remote, uint cacheAge); + static QByteArray getChecksum(const QString &filename, QCryptographicHash::Algorithm hashAlgorithm); + static bool downloadFile(const QUrl &uri, const QString &filename); + static QString buildDeviceID(FwupdDevice* device); + static void setDeviceDetails(FwupdResource *res, FwupdDevice *device); + static FwupdResource * createDevice(FwupdDevice *device); QHash m_resources; StandardBackendUpdater* m_updater; bool m_fetching = false; int m_startElements; QList m_toUpdate; }; #endif // FWUPDBACKEND_H diff --git a/libdiscover/backends/FwupdBackend/FwupdResource.cpp b/libdiscover/backends/FwupdBackend/FwupdResource.cpp index b9839cb4..2a753425 100644 --- a/libdiscover/backends/FwupdBackend/FwupdResource.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdResource.cpp @@ -1,194 +1,193 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdResource.h" #include #include #include #include -FwupdResource::FwupdResource(QString name, bool isTechnical, AbstractResourcesBackend* parent) +FwupdResource::FwupdResource(QString name, AbstractResourcesBackend* parent) : AbstractResource(parent) , m_name(std::move(name)) , m_state(State::Broken) - , m_isTechnical(isTechnical) { setObjectName(m_name); } QList FwupdResource::addonsInformation() { return m_addons; } QString FwupdResource::availableVersion() const { return m_version; } QStringList FwupdResource::allResourceNames() const { return { m_name }; } QStringList FwupdResource::categories() { return m_categories; } QString FwupdResource::comment() { return m_summary; } int FwupdResource::size() { return m_size; } QUrl FwupdResource::homepage() { return m_homepage; } QUrl FwupdResource::helpURL() { - return m_homepage; + return {}; } QUrl FwupdResource::bugURL() { - return m_homepage; + return {}; } QUrl FwupdResource::donationURL() { - return m_homepage; + return {}; } QVariant FwupdResource::icon() const { return m_iconName; } QString FwupdResource::installedVersion() const { return m_version; } QString FwupdResource::license() { return m_license; } QString FwupdResource::longDescription() { return m_description; } QString FwupdResource::name() const { return m_name; } QString FwupdResource::vendor() const { return m_vendor; } QString FwupdResource::origin() const { - return m_homepage.toString(); + return m_origin; } QString FwupdResource::packageName() const { return m_name; } QString FwupdResource::section() { return QStringLiteral("Firmware Updates"); } AbstractResource::State FwupdResource::state() { return m_state; } void FwupdResource::fetchChangelog() { QString log = longDescription(); log.replace(QLatin1Char('\n'), QLatin1String("
")); emit changelogFetched(log); } void FwupdResource::setState(AbstractResource::State state) { if(m_state != state) { m_state = state; emit stateChanged(); } } void FwupdResource::setAddons(const AddonList& addons) { Q_FOREACH(const QString& toInstall, addons.addonsToInstall()) { setAddonInstalled(toInstall, true); } Q_FOREACH(const QString& toRemove, addons.addonsToRemove()) { setAddonInstalled(toRemove, false); } } void FwupdResource::setAddonInstalled(const QString& addon, bool installed) { for(auto & elem : m_addons) { if(elem.name() == addon) { elem.setInstalled(installed); } } } void FwupdResource::invokeApplication() const { qWarning() << "Not Launchable"; } QUrl FwupdResource::url() const { return m_homepage; } QString FwupdResource::executeLabel() const { return i18n("Not Invokable"); } diff --git a/libdiscover/backends/FwupdBackend/FwupdResource.h b/libdiscover/backends/FwupdBackend/FwupdResource.h index b34f8a62..6edc067d 100644 --- a/libdiscover/backends/FwupdBackend/FwupdResource.h +++ b/libdiscover/backends/FwupdBackend/FwupdResource.h @@ -1,116 +1,117 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef FWUPDRESOURCE_H #define FWUPDRESOURCE_H #include "FwupdBackend.h" #include #include class AddonList; class FwupdResource : public AbstractResource { Q_OBJECT public: - explicit FwupdResource(QString name, bool isTechnical, AbstractResourcesBackend* parent); + explicit FwupdResource(QString name, AbstractResourcesBackend* parent); QList addonsInformation() override; QString section() override; QString origin() const override; QString longDescription() override; QString availableVersion() const override; QString installedVersion() const override; QString license() override; int size() override; QUrl homepage() override; QUrl helpURL() override; QUrl bugURL() override; QUrl donationURL() override; QStringList categories() override; AbstractResource::State state() override; QVariant icon() const override; QString comment() override; QString name() const override; QString packageName() const override; QString vendor() const; - bool isTechnical() const override { return m_isTechnical; } + bool isTechnical() const override { return true; } bool canExecute() const override { return false; } void invokeApplication() const override; void fetchChangelog() override; QUrl url() const override; QString executeLabel() const override; void setState(State state); void setSize(int size) { m_size = size; } void setAddons(const AddonList& addons); void setId(const QString &id){m_id = id;} void setName(const QString &name){ m_name = name;} void setSummary(const QString &summary){ m_summary = summary;} void setDescription(const QString &description){ m_description = description;} void setVersion(const QString &version){ m_version = version;} void setVendor(const QString &vendor){ m_vendor = vendor;} void setHomePage(const QUrl &homepage){ m_homepage = homepage;} void setLicense(const QString &license){ m_license = license;} void setIconName(const QString &iconName){ m_iconName = iconName;} void setReleaseDate(const QDate &date){ m_releaseDate = date;} + void setOrigin(const QString &origin){ m_origin = origin;} virtual QStringList allResourceNames() const; void setIsDeviceLocked(bool status){ isDeviceLocked = status;} void setDeviceID(const QString &deviceID){ m_deviceID = deviceID;} void setUpdateURI(const QString &updateURI){m_updateURI = updateURI;} void setAddonInstalled(const QString& addon, bool installed); QString sourceIcon() const override { return m_iconName; } QDate releaseDate() const override { return m_releaseDate; } public: QString m_id; QString m_name; QString m_summary; QString m_description; QString m_version; QString m_vendor; QStringList m_categories; QString m_license; QDate m_releaseDate; AbstractResource::State m_state; QUrl m_homepage; QString m_iconName; QList m_addons; - bool m_isTechnical; int m_size; QString m_deviceID; QString m_updateURI; QString m_file; bool isDeviceLocked = false; // True if device is locked! bool isOnlyOffline = false; // True if only offline updates bool isLiveUpdatable = false; // True if device is live updatable bool needsReboot = false; // True if device needs Reboot bool isDeviceRemoval = false; //True if device is Removal bool needsBootLoader = false; //True if BootLoader Required QString guidString; + QString m_origin; }; #endif // FWUPDRESOURCE_H diff --git a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp index 2303d4ad..771a7a4d 100644 --- a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp @@ -1,156 +1,156 @@ /*************************************************************************** * Copyright © 2014 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdSourcesBackend.h" #include #include #include class FwupdSourcesModel : public QStandardItemModel { Q_OBJECT public: FwupdSourcesModel(FwupdSourcesBackend* backend) : QStandardItemModel(backend) , m_backend(backend) {} QHash roleNames() const override { auto roles = QStandardItemModel::roleNames(); roles[Qt::CheckStateRole] = "checked"; return roles; } bool setData(const QModelIndex & index, const QVariant & value, int role) override { auto item = itemFromIndex(index); if(!item) return false; - remote = fwupd_client_get_remote_by_id(m_backend->backend->client,item->data(AbstractSourcesBackend::IdRole).toString().toUtf8().constData(),nullptr,nullptr); + remote = fwupd_client_get_remote_by_id(m_backend->backend->client, item->data(AbstractSourcesBackend::IdRole).toString().toUtf8().constData(),nullptr,nullptr); status = fwupd_remote_get_enabled(remote); switch(role) { case Qt::CheckStateRole: { if((value.toInt() == Qt::Checked) ) { #if FWUPD_CHECK_VERSION(1,0,7) m_backend->eulaRequired(QString::fromUtf8(fwupd_remote_get_title(remote)),QString::fromUtf8(fwupd_remote_get_agreement(remote))); #endif connect(m_backend,&FwupdSourcesBackend::proceed,this, [=]() { if(fwupd_client_modify_remote(m_backend->backend->client,fwupd_remote_get_id(remote),QString(QLatin1String("Enabled")).toUtf8().constData(),(QString(QLatin1String("true")).toUtf8().constData()),nullptr,nullptr)) item->setData(value, role); } ); connect(m_backend,&FwupdSourcesBackend::cancel,this, [=]() { item->setCheckState(Qt::Unchecked); Q_EMIT dataChanged(index,index,{}); return false; } ); } else if(value.toInt() == Qt::Unchecked) { if(fwupd_client_modify_remote(m_backend->backend->client,fwupd_remote_get_id(remote),QString(QLatin1String("Enabled")).toUtf8().constData(),(QString(QLatin1String("false")).toUtf8().constData()),nullptr,nullptr)) item->setData(value, role); } } } Q_EMIT dataChanged(index,index,{}); return true; } private: FwupdSourcesBackend* m_backend; FwupdRemote* remote; bool status; }; FwupdSourcesBackend::FwupdSourcesBackend(AbstractResourcesBackend * parent) : AbstractSourcesBackend(parent) , backend(qobject_cast(parent)) , m_sources(new FwupdSourcesModel(this)) { populateSources(); } void FwupdSourcesBackend::populateSources() { /* find all remotes */ g_autoptr(GPtrArray) remotes = fwupd_client_get_remotes(backend->client,nullptr,nullptr); if(remotes != nullptr) { for(uint i = 0; i < remotes->len; i++) { FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i); if(fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL) continue; const QString id = QString::fromUtf8(fwupd_remote_get_id(remote)); if(id.isEmpty()) continue; bool status = !fwupd_remote_get_enabled(remote); QStandardItem* it = new QStandardItem(id); it->setData(id, AbstractSourcesBackend::IdRole); it->setData(QVariant(QString::fromUtf8(fwupd_remote_get_title(remote))), Qt::ToolTipRole); it->setCheckable(true); it->setCheckState(status ? Qt::Unchecked : Qt::Checked); m_sources->appendRow(it); } } } QAbstractItemModel* FwupdSourcesBackend::sources() { return m_sources; } void FwupdSourcesBackend::eulaRequired( const QString& remoteName, const QString& licenseAgreement) { Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The remote %1 require that you accept their license:\n %2", remoteName, licenseAgreement)); } bool FwupdSourcesBackend::addSource(const QString& id) { qWarning() << "Fwupd Error: Custom Addition of Sources Not Allowed" << "Remote-ID" << id; return false; } bool FwupdSourcesBackend::removeSource(const QString& id) { qWarning() << "Fwupd Error: Removal of Sources Not Allowed" << "Remote-ID" << id; return false; } QList FwupdSourcesBackend::actions() const { return {} ; } #include "FwupdSourcesBackend.moc" diff --git a/libdiscover/backends/PackageKitBackend/PKTransaction.cpp b/libdiscover/backends/PackageKitBackend/PKTransaction.cpp index d86b9300..44b98a87 100644 --- a/libdiscover/backends/PackageKitBackend/PKTransaction.cpp +++ b/libdiscover/backends/PackageKitBackend/PKTransaction.cpp @@ -1,290 +1,293 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PKTransaction.h" #include "PackageKitBackend.h" #include "PackageKitResource.h" #include "PackageKitMessages.h" #include "utils.h" #include "LocalFilePKResource.h" #include #include #include #include #include #include #include PKTransaction::PKTransaction(const QVector& apps, Transaction::Role role) : Transaction(apps.first(), apps.first(), role) , m_apps(apps) { Q_ASSERT(!apps.contains(nullptr)); foreach(auto r, apps) { PackageKitResource* res = qobject_cast(r); m_pkgnames.unite(res->allPackageNames().toSet()); } QTimer::singleShot(0, this, &PKTransaction::start); } static QStringList packageIds(const QVector& res, std::function func) { QStringList ret; foreach(auto r, res) { ret += func(qobject_cast(r)); } ret.removeDuplicates(); return ret; } void PKTransaction::start() { trigger(PackageKit::Transaction::TransactionFlagSimulate); } void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags) { if (m_trans) m_trans->deleteLater(); m_newPackageStates.clear(); if (m_apps.size() == 1 && qobject_cast(m_apps.at(0))) { auto app = qobject_cast(m_apps.at(0)); m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags); - connect(m_trans.data(), &PackageKit::Transaction::finished, this, [app](PackageKit::Transaction::Exit status) { - if (status == PackageKit::Transaction::ExitSuccess) { + connect(m_trans.data(), &PackageKit::Transaction::finished, this, [this, app](PackageKit::Transaction::Exit status) { + const bool simulate = m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate; + if (!simulate && status == PackageKit::Transaction::ExitSuccess) { app->markInstalled(); } }); } else switch (role()) { case Transaction::ChangeAddonsRole: case Transaction::InstallRole: m_trans = PackageKit::Daemon::installPackages(packageIds(m_apps, [](PackageKitResource* r){return r->availablePackageId(); }), flags); break; case Transaction::RemoveRole: //see bug #315063 m_trans = PackageKit::Daemon::removePackages(packageIds(m_apps, [](PackageKitResource* r){return r->installedPackageId(); }), true /*allowDeps*/, false, flags); break; }; Q_ASSERT(m_trans); // connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, [this]() { qDebug() << "state..." << m_trans->status(); }); connect(m_trans.data(), &PackageKit::Transaction::package, this, &PKTransaction::packageResolved); connect(m_trans.data(), &PackageKit::Transaction::finished, this, &PKTransaction::cleanup); connect(m_trans.data(), &PackageKit::Transaction::errorCode, this, &PKTransaction::errorFound); connect(m_trans.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PKTransaction::mediaChange); connect(m_trans.data(), &PackageKit::Transaction::requireRestart, this, &PKTransaction::requireRestart); connect(m_trans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PKTransaction::repoSignatureRequired); connect(m_trans.data(), &PackageKit::Transaction::percentageChanged, this, &PKTransaction::progressChanged); connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, &PKTransaction::statusChanged); connect(m_trans.data(), &PackageKit::Transaction::eulaRequired, this, &PKTransaction::eulaRequired); connect(m_trans.data(), &PackageKit::Transaction::allowCancelChanged, this, &PKTransaction::cancellableChanged); connect(m_trans.data(), &PackageKit::Transaction::speedChanged, this, [this]() { setDownloadSpeed(m_trans->speed()); }); setCancellable(m_trans->allowCancel()); } void PKTransaction::statusChanged() { setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus); progressChanged(); } int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage); void PKTransaction::progressChanged() { auto percent = m_trans->percentage(); if (percent == 101) { qWarning() << "percentage cannot be calculated"; percent = 50; } const auto processedPercentage = percentageWithStatus(m_trans->status(), qBound(0, percent, 100)); if (processedPercentage >= 0) setProgress(processedPercentage); } void PKTransaction::cancellableChanged() { setCancellable(m_trans->allowCancel()); } void PKTransaction::cancel() { if (!m_trans) { setStatus(CancelledStatus); } else if (m_trans->allowCancel()) { m_trans->cancel(); } else { qWarning() << "trying to cancel a non-cancellable transaction: " << resource()->name(); } } void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime) { Q_UNUSED(runtime) const bool cancel = !m_proceedFunctions.isEmpty() || exit == PackageKit::Transaction::ExitCancelled; - const bool failed = exit == PackageKit::Transaction::ExitFailed; + const bool failed = exit == PackageKit::Transaction::ExitFailed || exit == PackageKit::Transaction::ExitUnknown; const bool simulate = m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate; disconnect(m_trans, nullptr, this, nullptr); m_trans = nullptr; const auto backend = qobject_cast(resource()->backend()); if (!cancel && !failed && simulate) { auto packagesToRemove = m_newPackageStates.value(PackageKit::Transaction::InfoRemoving); QMutableListIterator i(packagesToRemove); QSet removedResources; while (i.hasNext()) { const auto pkgname = PackageKit::Daemon::packageName(i.next()); removedResources.unite(backend->resourcesByPackageName(pkgname)); if (m_pkgnames.contains(pkgname)) { i.remove(); } } removedResources.subtract(kVectorToSet(m_apps)); if (!packagesToRemove.isEmpty() || !removedResources.isEmpty()) { QString msg = QStringLiteral("
  • ") + PackageKitResource::joinPackages(packagesToRemove, QStringLiteral("
  • "), {}); if (!removedResources.isEmpty()) { const QStringList removedResourcesStr = kTransform(removedResources, [](AbstractResource* a) { return a->name(); }); msg += QLatin1Char('\n'); msg += removedResourcesStr.join(QStringLiteral("
  • ")); } msg += QStringLiteral("
"); Q_EMIT proceedRequest(i18n("Confirm package removal"), i18np("This action will also remove the following package:\n%2", "This action will also remove the following packages:\n%2", packagesToRemove.count(), msg)); } else { proceed(); } return; } if (failed && m_newPackageStates.isEmpty()) m_newPackageStates.insert(PackageKit::Transaction::InfoAvailable, kTransform(m_apps, [](AbstractResource* res) { return res->packageName(); })); this->submitResolve(); if (failed) setStatus(Transaction::DoneWithErrorStatus); - else + else if (cancel) setStatus(Transaction::CancelledStatus); + else + setStatus(Transaction::DoneStatus); } void PKTransaction::processProceedFunction() { auto t = m_proceedFunctions.takeFirst()(); connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) { if (status != PackageKit::Transaction::Exit::ExitSuccess) { qWarning() << "transaction failed" << sender() << status; cancel(); return; } if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { start(); } }); } void PKTransaction::proceed() { if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { trigger(PackageKit::Transaction::TransactionFlagOnlyTrusted); } } void PKTransaction::packageResolved(PackageKit::Transaction::Info info, const QString& packageId) { m_newPackageStates[info].append(packageId); } void PKTransaction::submitResolve() { QStringList needResolving; foreach(const auto &pkgids, m_newPackageStates) { foreach(const auto &pkgid, pkgids) { needResolving += PackageKit::Daemon::packageName(pkgid); } } if (!needResolving.isEmpty()) { needResolving.removeDuplicates(); const auto backend = qobject_cast(resource()->backend()); backend->clearPackages(needResolving); backend->resolvePackages(needResolving); } } PackageKit::Transaction* PKTransaction::transaction() { return m_trans; } void PKTransaction::eulaRequired(const QString& eulaID, const QString& packageID, const QString& vendor, const QString& licenseAgreement) { m_proceedFunctions << [eulaID](){ return PackageKit::Daemon::acceptEula(eulaID); }; Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The package %1 and its vendor %2 require that you accept their license:\n %3", PackageKit::Daemon::packageName(packageID), vendor, licenseAgreement)); } void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString& error) { if (err == PackageKit::Transaction::ErrorNoLicenseAgreement) return; qWarning() << "PackageKit error:" << err << PackageKitMessages::errorMessage(err) << error; Q_EMIT passiveMessage(PackageKitMessages::errorMessage(err)); } void PKTransaction::mediaChange(PackageKit::Transaction::MediaType media, const QString& type, const QString& text) { Q_UNUSED(media) Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text)); } void PKTransaction::requireRestart(PackageKit::Transaction::Restart restart, const QString& pkgid) { Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid)); } void PKTransaction::repoSignatureRequired(const QString& packageID, const QString& repoName, const QString& keyUrl, const QString& keyUserid, const QString& keyId, const QString& keyFingerprint, const QString& keyTimestamp, PackageKit::Transaction::SigType type) { Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName), i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n", keyUrl, keyUserid, keyFingerprint, keyTimestamp)); m_proceedFunctions << [type, keyId, packageID](){ return PackageKit::Daemon::installSignature(type, keyId, packageID); }; } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp index cfba3164..6d63793d 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp @@ -1,299 +1,304 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * Copyright © 2013 Lukas Appelhans * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PackageKitResource.h" #include "PackageKitBackend.h" #include "PackageKitMessages.h" #include #include #include #include #include const QStringList PackageKitResource::m_objects({ QStringLiteral("qrc:/qml/DependenciesButton.qml") }); PackageKitResource::PackageKitResource(QString packageName, QString summary, PackageKitBackend* parent) : AbstractResource(parent) , m_summary(std::move(summary)) , m_name(std::move(packageName)) { setObjectName(m_name); connect(this, &PackageKitResource::dependenciesFound, this, [this](const QJsonObject& obj) { setDependenciesCount(obj.size()); }); } QString PackageKitResource::name() const { return m_name; } QString PackageKitResource::packageName() const { return m_name; } QStringList PackageKitResource::allPackageNames() const { return { m_name }; } QString PackageKitResource::availablePackageId() const { //First we check if it's upgradeable and use this version to display const QString pkgid = backend()->upgradeablePackageId(this); if (!pkgid.isEmpty()) return pkgid; QMap::const_iterator it = m_packages.constFind(PackageKit::Transaction::InfoAvailable); if (it != m_packages.constEnd()) return it->last(); return installedPackageId(); } QString PackageKitResource::installedPackageId() const { const auto installed = m_packages[PackageKit::Transaction::InfoInstalled]; return installed.isEmpty() ? QString() : installed.last(); } QString PackageKitResource::comment() { return m_summary; } QString PackageKitResource::longDescription() { fetchDetails(); return m_details.description(); } QUrl PackageKitResource::homepage() { fetchDetails(); return QUrl(m_details.url()); } QVariant PackageKitResource::icon() const { return QStringLiteral("applications-other"); } QString PackageKitResource::license() { fetchDetails(); return m_details.license().isEmpty() ? i18n("Unknown") : m_details.license(); } QList PackageKitResource::addonsInformation() { return QList(); } QString PackageKitResource::availableVersion() const { return PackageKit::Daemon::packageVersion(availablePackageId()); } QString PackageKitResource::installedVersion() const { return PackageKit::Daemon::packageVersion(installedPackageId()); } int PackageKitResource::size() { fetchDetails(); return m_details.size(); } QString PackageKitResource::origin() const { auto pkgid = availablePackageId(); return PackageKit::Daemon::packageData(pkgid); } QString PackageKitResource::section() { return QString(); } AbstractResource::State PackageKitResource::state() { if (backend()->isPackageNameUpgradeable(this)) return Upgradeable; else if(m_packages.contains(PackageKit::Transaction::InfoInstalled)) return Installed; else if(m_packages.contains(PackageKit::Transaction::InfoAvailable)) return None; else return Broken; } void PackageKitResource::addPackageId(PackageKit::Transaction::Info info, const QString &packageId, bool arch) { if (arch) m_packages[info].append(packageId); else m_packages[info].prepend(packageId); emit stateChanged(); } QStringList PackageKitResource::categories() { return { QStringLiteral("Unknown") }; } bool PackageKitResource::isTechnical() const { return true; } void PackageKitResource::fetchDetails() { const QString pkgid = availablePackageId(); if (!m_details.isEmpty() || pkgid.isEmpty()) return; m_details.insert(QStringLiteral("fetching"), true);//we add an entry so it's not re-fetched. backend()->fetchDetails(pkgid); } void PackageKitResource::failedFetchingDetails(PackageKit::Transaction::Error, const QString& msg) { qWarning() << "error fetching details" << msg; } void PackageKitResource::setDependenciesCount(int deps) { if (deps != m_dependenciesCount) { m_dependenciesCount = deps; Q_EMIT sizeChanged(); } } void PackageKitResource::setDetails(const PackageKit::Details & details) { const bool ourDetails = details.packageId() == availablePackageId(); if (!ourDetails) return; if (m_details != details) { m_details = details; emit stateChanged(); if (!backend()->isFetching()) Q_EMIT backend()->resourcesChanged(this, {"size", "homepage", "license"}); } } void PackageKitResource::fetchChangelog() { + const auto pkgid = availablePackageId(); + if (pkgid.isEmpty()) { + connect(this, &PackageKitResource::stateChanged, this, &PackageKitResource::fetchChangelog); + return; + } PackageKit::Transaction* t = PackageKit::Daemon::getUpdateDetail(availablePackageId()); connect(t, &PackageKit::Transaction::updateDetail, this, &PackageKitResource::updateDetail); connect(t, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error err, const QString & error) { qWarning() << "error fetching updates:" << err << error; emit changelogFetched(QString()); }); } static void addIfNotEmpty(const QString& title, const QString& content, QString& where) { if (!content.isEmpty()) where += QStringLiteral("

") + title + QStringLiteral(" ") + QString(content).replace(QStringLiteral("\n"), QStringLiteral("
")) + QStringLiteral("

"); } QString PackageKitResource::joinPackages(const QStringList& pkgids, const QString &_sep, const QString &shadowPackage) { QStringList ret; foreach(const QString& pkgid, pkgids) { const auto pkgname = PackageKit::Daemon::packageName(pkgid); if (pkgname == shadowPackage) ret += PackageKit::Daemon::packageVersion(pkgid); else ret += i18nc("package-name (version)", "%1 (%2)", pkgname, PackageKit::Daemon::packageVersion(pkgid)); } const QString sep = _sep.isEmpty() ? i18nc("comma separating package names", ", ") : _sep; return ret.join(sep); } static QStringList urlToLinks(const QStringList& urls) { QStringList ret; foreach(const QString& in, urls) ret += QStringLiteral("%1").arg(in); return ret; } void PackageKitResource::updateDetail(const QString& packageID, const QStringList& updates, const QStringList& obsoletes, const QStringList& vendorUrls, const QStringList& /*bugzillaUrls*/, const QStringList& /*cveUrls*/, PackageKit::Transaction::Restart restart, const QString& updateText, const QString& /*changelog*/, PackageKit::Transaction::UpdateState state, const QDateTime& /*issued*/, const QDateTime& /*updated*/) { const auto name = PackageKit::Daemon::packageName(packageID); QString info; addIfNotEmpty(i18n("Current Version:"), joinPackages(updates, {}, name), info); addIfNotEmpty(i18n("Obsoletes:"), joinPackages(obsoletes, {}, name), info); addIfNotEmpty(i18n("New Version:"), updateText, info); addIfNotEmpty(i18n("Update State:"), PackageKitMessages::updateStateMessage(state), info); addIfNotEmpty(i18n("Restart:"), PackageKitMessages::restartMessage(restart), info); if (!vendorUrls.isEmpty()) addIfNotEmpty(i18n("Vendor:"), urlToLinks(vendorUrls).join(QStringLiteral(", ")), info); emit changelogFetched(changelog() + info); } PackageKitBackend* PackageKitResource::backend() const { return qobject_cast(parent()); } QString PackageKitResource::sizeDescription() { if (m_dependenciesCount < 0) { fetchDetails(); fetchDependencies(); } if (m_dependenciesCount <= 0) return AbstractResource::sizeDescription(); else return i18np("%2 (plus %1 dependency)", "%2 (plus %1 dependencies)", m_dependenciesCount, AbstractResource::sizeDescription()); } QString PackageKitResource::sourceIcon() const { return QStringLiteral("package-available"); } void PackageKitResource::fetchDependencies() { const auto id = availablePackageId(); if (id.isEmpty()) return; m_dependenciesCount = 0; QSharedPointer packageDependencies(new QJsonObject); auto trans = PackageKit::Daemon::installPackage(id, PackageKit::Transaction::TransactionFlagSimulate); connect(trans, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error, const QString& message) { qWarning() << "Transaction error: " << message << sender(); }); connect(trans, &PackageKit::Transaction::package, this, [packageDependencies](PackageKit::Transaction::Info /*info*/, const QString &packageID, const QString &summary) { (*packageDependencies)[PackageKit::Daemon::packageName(packageID)] = summary ; }); connect(trans, &PackageKit::Transaction::finished, this, [this, packageDependencies](PackageKit::Transaction::Exit /*status*/) { Q_EMIT dependenciesFound(*packageDependencies); }); } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp index cac97f9c..0cc1be13 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp @@ -1,423 +1,429 @@ /*************************************************************************** * Copyright © 2013 Lukas Appelhans * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PackageKitUpdater.h" #include "PackageKitMessages.h" #include #ifdef PKQT_1_0 #include #endif #include #include #include #include #include #include int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage) { const auto was = percentage; if (status != PackageKit::Transaction::StatusUnknown) { static const QMap statuses = { { PackageKit::Transaction::Status::StatusDownload, 0 }, { PackageKit::Transaction::Status::StatusInstall, 1}, { PackageKit::Transaction::Status::StatusRemove, 1}, { PackageKit::Transaction::Status::StatusUpdate, 1} }; const auto idx = statuses.value(status, -1); if (idx < 0) { qDebug() << "Status not present" << status << "among" << statuses .keys() << percentage; return -1; } percentage = (idx * 100 + percentage) / 2 /*the maximum in statuses*/; } qDebug() << "reporting progress with status:" << status << percentage << was; return percentage; } PackageKitUpdater::PackageKitUpdater(PackageKitBackend * parent) : AbstractBackendUpdater(parent), m_transaction(nullptr), m_backend(parent), m_isCancelable(false), m_isProgressing(false), m_percentage(0), m_lastUpdate() { fetchLastUpdateTime(); } PackageKitUpdater::~PackageKitUpdater() { } void PackageKitUpdater::prepare() { Q_ASSERT(!m_transaction); m_toUpgrade = m_backend->upgradeablePackages(); m_allUpgradeable = m_toUpgrade; } void PackageKitUpdater::setupTransaction(PackageKit::Transaction::TransactionFlags flags) { m_packagesModified.clear(); auto pkgs = involvedPackages(m_toUpgrade).toList(); pkgs.sort(); m_transaction = PackageKit::Daemon::updatePackages(pkgs, flags); m_isCancelable = m_transaction->allowCancel(); connect(m_transaction.data(), &PackageKit::Transaction::finished, this, &PackageKitUpdater::finished); connect(m_transaction.data(), &PackageKit::Transaction::package, this, &PackageKitUpdater::packageResolved); connect(m_transaction.data(), &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound); connect(m_transaction.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PackageKitUpdater::mediaChange); connect(m_transaction.data(), &PackageKit::Transaction::requireRestart, this, &PackageKitUpdater::requireRestart); connect(m_transaction.data(), &PackageKit::Transaction::eulaRequired, this, &PackageKitUpdater::eulaRequired); connect(m_transaction.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PackageKitUpdater::repoSignatureRequired); connect(m_transaction.data(), &PackageKit::Transaction::allowCancelChanged, this, &PackageKitUpdater::cancellableChanged); connect(m_transaction.data(), &PackageKit::Transaction::percentageChanged, this, &PackageKitUpdater::percentageChanged); connect(m_transaction.data(), &PackageKit::Transaction::itemProgress, this, &PackageKitUpdater::itemProgress); connect(m_transaction.data(), &PackageKit::Transaction::speedChanged, this, [this] { Q_EMIT downloadSpeedChanged(downloadSpeed()); }); } QSet PackageKitUpdater::packagesForPackageId(const QSet& pkgids) const { QSet packages; packages.reserve(pkgids.size()); foreach(const QString& pkgid, pkgids) { packages += PackageKit::Daemon::packageName(pkgid); } QSet ret; foreach (AbstractResource * res, m_allUpgradeable) { PackageKitResource* pres = qobject_cast(res); if (packages.contains(pres->allPackageNames().toSet())) { ret.insert(res); } } return ret; } QSet PackageKitUpdater::involvedPackages(const QSet& packages) const { QSet packageIds; packageIds.reserve(packages.size()); foreach (AbstractResource * res, packages) { PackageKitResource * app = qobject_cast(res); QString pkgid = m_backend->upgradeablePackageId(app); + + if (pkgid.isEmpty()) { + qWarning() << "no upgradeablePackageId for" << app; + continue; + } + packageIds.insert(pkgid); } return packageIds; } void PackageKitUpdater::processProceedFunction() { auto t = m_proceedFunctions.takeFirst()(); connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) { if (status != PackageKit::Transaction::Exit::ExitSuccess) { qWarning() << "transaction failed" << sender() << status; cancel(); return; } if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { start(); } }); } void PackageKitUpdater::proceed() { if (!m_proceedFunctions.isEmpty()) processProceedFunction(); #ifdef PKQT_1_0 else if (useOfflineUpdates()) setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted | PackageKit::Transaction::TransactionFlagOnlyDownload); #endif else setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted); } bool PackageKitUpdater::useOfflineUpdates() const { if (qEnvironmentVariableIsSet("PK_OFFLINE_UPDATE")) return true; KConfigGroup group(KSharedConfig::openConfig(), "Software"); return group.readEntry("UseOfflineUpdates", false); } void PackageKitUpdater::setUseOfflineUpdates(bool use) { KConfigGroup group(KSharedConfig::openConfig(), "Software"); group.writeEntry("UseOfflineUpdates", use); } void PackageKitUpdater::start() { Q_ASSERT(!isProgressing()); setupTransaction(PackageKit::Transaction::TransactionFlagSimulate); setProgressing(true); } void PackageKitUpdater::finished(PackageKit::Transaction::Exit exit, uint /*time*/) { // qDebug() << "update finished!" << exit << time; if (!m_proceedFunctions.isEmpty()) return; const bool cancel = exit == PackageKit::Transaction::ExitCancelled; const bool simulate = m_transaction->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate; disconnect(m_transaction, nullptr, this, nullptr); m_transaction = nullptr; if (!cancel && simulate) { const auto toremove = m_packagesModified.value(PackageKit::Transaction::InfoRemoving); if (!toremove.isEmpty()) { const auto toinstall = QStringList() << m_packagesModified.value(PackageKit::Transaction::InfoInstalling) << m_packagesModified.value(PackageKit::Transaction::InfoUpdating); Q_EMIT proceedRequest(i18n("Packages to remove"), i18n("The following packages will be removed by the update:\n
  • %1
\nin order to install:\n
  • %2
", PackageKitResource::joinPackages(toremove, QStringLiteral("
  • "), {}), PackageKitResource::joinPackages(toinstall, QStringLiteral("
  • "), {}) )); } else { proceed(); } return; } setProgressing(false); m_backend->fetchUpdates(); fetchLastUpdateTime(); if (useOfflineUpdates()) { #ifdef PKQT_1_0 PackageKit::Daemon::global()->offline()->trigger(PackageKit::Offline::ActionReboot); Q_EMIT passiveMessage(i18n("Please restart the computer to finish the installation")); #else qWarning() << "PK_OFFLINE_UPDATE is set but discover was built against an old version of PackageKitQt that didn't support offline updates"; #endif } } void PackageKitUpdater::cancellableChanged() { if (m_isCancelable != m_transaction->allowCancel()) { m_isCancelable = m_transaction->allowCancel(); emit cancelableChanged(m_isCancelable); } } void PackageKitUpdater::percentageChanged() { const auto actualPercentage = percentageWithStatus(m_transaction->status(), m_transaction->percentage()); if (actualPercentage >= 0 && m_percentage != actualPercentage) { m_percentage = actualPercentage; emit progressChanged(m_percentage); } } bool PackageKitUpdater::hasUpdates() const { return m_backend->updatesCount() > 0; } qreal PackageKitUpdater::progress() const { return m_percentage; } void PackageKitUpdater::removeResources(const QList& apps) { QSet pkgs = involvedPackages(apps.toSet()); m_toUpgrade.subtract(packagesForPackageId(pkgs)); } void PackageKitUpdater::addResources(const QList& apps) { QSet pkgs = involvedPackages(apps.toSet()); m_toUpgrade.unite(packagesForPackageId(pkgs)); } QList PackageKitUpdater::toUpdate() const { return m_toUpgrade.toList(); } bool PackageKitUpdater::isMarked(AbstractResource* res) const { return m_toUpgrade.contains(res); } QDateTime PackageKitUpdater::lastUpdate() const { return m_lastUpdate; } bool PackageKitUpdater::isCancelable() const { return m_isCancelable; } bool PackageKitUpdater::isProgressing() const { return m_isProgressing; } void PackageKitUpdater::cancel() { if (m_transaction) m_transaction->cancel(); else setProgressing(false); } void PackageKitUpdater::errorFound(PackageKit::Transaction::Error err, const QString& error) { if (err == PackageKit::Transaction::ErrorNoLicenseAgreement) return; Q_EMIT passiveMessage(QStringLiteral("%1\n%2").arg(PackageKitMessages::errorMessage(err), error)); qWarning() << "Error happened" << err << error; } void PackageKitUpdater::mediaChange(PackageKit::Transaction::MediaType media, const QString& type, const QString& text) { Q_UNUSED(media) Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text)); } void PackageKitUpdater::requireRestart(PackageKit::Transaction::Restart restart, const QString& pkgid) { Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid)); } void PackageKitUpdater::eulaRequired(const QString& eulaID, const QString& packageID, const QString& vendor, const QString& licenseAgreement) { m_proceedFunctions << [eulaID](){ return PackageKit::Daemon::acceptEula(eulaID); }; Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The package %1 and its vendor %2 require that you accept their license:\n %3", PackageKit::Daemon::packageName(packageID), vendor, licenseAgreement)); } void PackageKitUpdater::setProgressing(bool progressing) { if (m_isProgressing != progressing) { m_isProgressing = progressing; emit progressingChanged(m_isProgressing); } } void PackageKitUpdater::fetchLastUpdateTime() { QDBusPendingReply transaction = PackageKit::Daemon::global()->getTimeSinceAction(PackageKit::Transaction::RoleGetUpdates); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(transaction, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PackageKitUpdater::lastUpdateTimeReceived); } void PackageKitUpdater::lastUpdateTimeReceived(QDBusPendingCallWatcher* w) { QDBusPendingReply reply = w->reply(); if (reply.isError()) { qWarning() << "Error when fetching the last update time" << reply.error(); } else { m_lastUpdate = QDateTime::currentDateTime().addSecs(-int(reply.value())); } w->deleteLater(); } void PackageKitUpdater::itemProgress(const QString& itemID, PackageKit::Transaction::Status status, uint percentage) { auto res = packagesForPackageId({itemID}); const auto actualPercentage = percentageWithStatus(status, percentage); if (actualPercentage<0) return; foreach(auto r, res) { Q_EMIT resourceProgressed(r, actualPercentage); } } void PackageKitUpdater::fetchChangelog() const { QStringList pkgids; foreach(AbstractResource* res, m_allUpgradeable) { pkgids += static_cast(res)->availablePackageId(); } Q_ASSERT(!pkgids.isEmpty()); PackageKit::Transaction* t = PackageKit::Daemon::getUpdatesDetails(pkgids); connect(t, &PackageKit::Transaction::updateDetail, this, &PackageKitUpdater::updateDetail); connect(t, &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound); } void PackageKitUpdater::updateDetail(const QString& packageID, const QStringList& updates, const QStringList& obsoletes, const QStringList& vendorUrls, const QStringList& bugzillaUrls, const QStringList& cveUrls, PackageKit::Transaction::Restart restart, const QString& updateText, const QString& changelog, PackageKit::Transaction::UpdateState state, const QDateTime& issued, const QDateTime& updated) { auto res = packagesForPackageId({packageID}); foreach(auto r, res) { static_cast(r)->updateDetail(packageID, updates, obsoletes, vendorUrls, bugzillaUrls, cveUrls, restart, updateText, changelog, state, issued, updated); } } void PackageKitUpdater::packageResolved(PackageKit::Transaction::Info info, const QString& packageId) { m_packagesModified[info] << packageId; } void PackageKitUpdater::repoSignatureRequired(const QString& packageID, const QString& repoName, const QString& keyUrl, const QString& keyUserid, const QString& keyId, const QString& keyFingerprint, const QString& keyTimestamp, PackageKit::Transaction::SigType type) { Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName), i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n", keyUrl, keyUserid, keyFingerprint, keyTimestamp)); m_proceedFunctions << [type, keyId, packageID](){ return PackageKit::Daemon::installSignature(type, keyId, packageID); }; } double PackageKitUpdater::updateSize() const { double ret = 0.; QSet donePkgs; for (AbstractResource * res : m_toUpgrade) { PackageKitResource * app = qobject_cast(res); QString pkgid = m_backend->upgradeablePackageId(app); if (!donePkgs.contains(pkgid)) { donePkgs.insert(pkgid); ret += app->size(); } } return ret; } quint64 PackageKitUpdater::downloadSpeed() const { return m_transaction ? m_transaction->speed() : 0; } diff --git a/libdiscover/backends/SnapBackend/SnapResource.cpp b/libdiscover/backends/SnapBackend/SnapResource.cpp index 8a4702aa..44fe82f2 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.cpp +++ b/libdiscover/backends/SnapBackend/SnapResource.cpp @@ -1,445 +1,447 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "SnapResource.h" #include "SnapBackend.h" #include #include #include #include #include #include #include #include QDebug operator<<(QDebug debug, const QSnapdPlug& plug) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdPlug("; debug.nospace() << "name:" << plug.name() << ','; debug.nospace() << "snap:" << plug.snap() << ','; debug.nospace() << "label:" << plug.label() << ','; debug.nospace() << "interface:" << plug.interface() << ','; debug.nospace() << "connectionCount:" << plug.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot& slot) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdSlot("; debug.nospace() << "name:" << slot.name() << ','; debug.nospace() << "label:" << slot.label() << ','; debug.nospace() << "snap:" << slot.snap() << ','; debug.nospace() << "interface:" << slot.interface() << ','; debug.nospace() << "connectionCount:" << slot.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdPlug* plug) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *plug; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot* slot) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *slot; return debug; } const QStringList SnapResource::m_objects({ QStringLiteral("qrc:/qml/PermissionsButton.qml") #ifdef SNAP_CHANNELS , QStringLiteral("qrc:/qml/ChannelsButton.qml") #endif }); SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* backend) : AbstractResource(backend) , m_state(state) , m_snap(snap) { setObjectName(snap->name()); } QSnapdClient * SnapResource::client() const { auto backend = qobject_cast(parent()); return backend->client(); } QString SnapResource::availableVersion() const { return installedVersion(); } QStringList SnapResource::categories() { return { QStringLiteral("Application") }; } QString SnapResource::comment() { return m_snap->summary(); } int SnapResource::size() { // return isInstalled() ? m_snap->installedSize() : m_snap->downloadSize(); return m_snap->downloadSize(); } QVariant SnapResource::icon() const { if (m_icon.isNull()) { m_icon = [this]() -> QVariant { const auto iconPath = m_snap->icon(); if (iconPath.isEmpty()) return QStringLiteral("package-x-generic"); if (!iconPath.startsWith(QLatin1Char('/'))) return QUrl(iconPath); auto req = client()->getIcon(packageName()); connect(req, &QSnapdGetIconRequest::complete, this, &SnapResource::gotIcon); req->runAsync(); return {}; }(); } return m_icon; } void SnapResource::gotIcon() { auto req = qobject_cast(sender()); if (req->error()) { qWarning() << "icon error" << req->errorString(); return; } auto icon = req->icon(); QBuffer buffer; buffer.setData(icon->data()); QImageReader reader(&buffer); auto theIcon = QVariant::fromValue(reader.read()); if (theIcon != m_icon) { m_icon = theIcon; iconChanged(); } } QString SnapResource::installedVersion() const { return m_snap->version(); } QString SnapResource::license() { return m_snap->license(); } QString SnapResource::longDescription() { return m_snap->description(); } QString SnapResource::name() const { return m_snap->title().isEmpty() ? m_snap->name() : m_snap->title(); } QString SnapResource::origin() const { return QStringLiteral("snappy:") + m_snap->channel(); } QString SnapResource::packageName() const { return m_snap->name(); } QString SnapResource::section() { return QStringLiteral("snap"); } AbstractResource::State SnapResource::state() { return m_state; } void SnapResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void SnapResource::fetchChangelog() { QString log; emit changelogFetched(log); } void SnapResource::fetchScreenshots() { QList screenshots; for(int i = 0, c = m_snap->screenshotCount(); i screenshot(m_snap->screenshot(i)); screenshots << QUrl(screenshot->url()); } Q_EMIT screenshotsFetched(screenshots, screenshots); } void SnapResource::invokeApplication() const { QProcess::startDetached(QStringLiteral("snap"), {QStringLiteral("run"), packageName()}); } bool SnapResource::isTechnical() const { return m_snap->snapType() != QLatin1String("app"); } void SnapResource::setSnap(const QSharedPointer& snap) { Q_ASSERT(snap->name() == m_snap->name()); if (m_snap == snap) return; const bool newSize = m_snap->installedSize() != snap->installedSize() || m_snap->downloadSize() != snap->downloadSize(); m_snap = snap; if (newSize) Q_EMIT sizeChanged(); Q_EMIT newSnap(); } QDate SnapResource::releaseDate() const { return {}; } class PlugsModel : public QStandardItemModel { public: enum Roles { PlugNameRole = Qt::UserRole + 1, SlotSnapRole, SlotNameRole }; PlugsModel(SnapResource* res, SnapBackend* backend, QObject* parent) : QStandardItemModel(parent) , m_res(res) , m_backend(backend) { setItemRoleNames(roleNames().unite( { {Qt::CheckStateRole, "checked"} } )); auto req = backend->client()->getInterfaces(); req->runSync(); QHash> slotsForInterface; for (int i = 0; islotCount(); ++i) { const auto slot = req->slot(i); slot->setParent(this); slotsForInterface[slot->interface()].append(slot); } const auto snap = m_res->snap(); for (int i = 0; iplugCount(); ++i) { const QScopedPointer plug(req->plug(i)); if (plug->snap() == snap->name()) { if (plug->interface() == QLatin1String("content")) continue; for (auto slot: slotsForInterface[plug->interface()]) { auto item = new QStandardItem; if (plug->label().isEmpty()) item->setText(plug->name()); else item->setText(i18n("%1 - %2", plug->name(), plug->label())); // qDebug() << "xxx" << plug->name() << plug->label() << plug->interface() << slot->snap() << "slot:" << slot->name() << slot->snap() << slot->interface() << slot->label(); item->setCheckable(true); item->setCheckState(plug->connectionCount()>0 ? Qt::Checked : Qt::Unchecked); item->setData(plug->name(), PlugNameRole); item->setData(slot->snap(), SlotSnapRole); item->setData(slot->name(), SlotNameRole); appendRow(item); } } } } private: bool setData(const QModelIndex & index, const QVariant & value, int role) override { if (role != Qt::CheckStateRole) return QStandardItemModel::setData(index, value, role); auto item = itemFromIndex(index); Q_ASSERT(item); const QString plugName = item->data(PlugNameRole).toString(); const QString slotSnap = item->data(SlotSnapRole).toString(); const QString slotName = item->data(SlotNameRole).toString(); QSnapdRequest* req; const auto snap = m_res->snap(); if (item->checkState() == Qt::Checked) { - req = m_backend->client()->connectInterface(snap->name(), plugName, slotSnap, slotName); - } else { req = m_backend->client()->disconnectInterface(snap->name(), plugName, slotSnap, slotName); + } else { + req = m_backend->client()->connectInterface(snap->name(), plugName, slotSnap, slotName); } req->runSync(); if (req->error()) { qWarning() << "snapd error" << req->errorString(); + m_res->backend()->passiveMessage(req->errorString()); } return req->error() == QSnapdRequest::NoError; } SnapResource* const m_res; SnapBackend* const m_backend; }; QAbstractItemModel* SnapResource::plugs(QObject* p) { if (!isInstalled()) return new QStandardItemModel(p); return new PlugsModel(this, qobject_cast(parent()), p); } QString SnapResource::appstreamId() const { const QStringList ids #if defined(SNAP_COMMON_IDS) = m_snap->commonIds() #endif ; return ids.isEmpty() ? QLatin1String("com.snap.") + m_snap->name() : ids.first(); } QString SnapResource::channel() const { auto req = client()->listOne(packageName()); req->runSync(); return req->error() ? QString() : req->snap()->trackingChannel(); } void SnapResource::setChannel(const QString& channelName) { #ifdef SNAP_CHANNELS Q_ASSERT(isInstalled()); auto request = client()->switchChannel(m_snap->name(), channelName); const auto currentChannel = channel(); auto dest = new CallOnDestroy([this, currentChannel]() { const auto newChannel = channel(); if (newChannel != currentChannel) { Q_EMIT channelChanged(newChannel); } }); request->runAsync(); connect(request, &QSnapdRequest::complete, dest, &QObject::deleteLater); #endif } void SnapResource::refreshSnap() { auto request = client()->find(QSnapdClient::FindFlag::MatchName, m_snap->name()); connect(request, &QSnapdRequest::complete, this, [this, request](){ if (request->error()) { qWarning() << "error" << request->error() << ": " << request->errorString(); return; } Q_ASSERT(request->snapCount() == 1); setSnap(QSharedPointer(request->snap(0))); }); request->runAsync(); } #ifdef SNAP_CHANNELS class Channels : public QObject { Q_OBJECT Q_PROPERTY(QList channels READ channels NOTIFY channelsChanged) public: Channels(SnapResource* res, QObject* parent) : QObject(parent), m_res(res) { if (res->snap()->channelCount() == 0) res->refreshSnap(); else refreshChannels(); connect(res, &SnapResource::newSnap, this, &Channels::refreshChannels); } void refreshChannels() { qDeleteAll(m_channels); + m_channels.clear(); auto s = m_res->snap(); for(int i=0, c=s->channelCount(); ichannel(i); channel->setParent(this); m_channels << channel; } Q_EMIT channelsChanged(); } QList channels() const { return m_channels; } Q_SIGNALS: void channelsChanged(); private: QList m_channels; SnapResource* const m_res; }; #endif QObject * SnapResource::channels(QObject* parent) { #ifdef SNAP_CHANNELS return new Channels(this, parent); #else return nullptr; #endif } #include "SnapResource.moc" diff --git a/libdiscover/utils.h b/libdiscover/utils.h index d041ad6b..07d3072f 100644 --- a/libdiscover/utils.h +++ b/libdiscover/utils.h @@ -1,116 +1,120 @@ /*************************************************************************** * Copyright ?? 2012 Aleix Pol Gonzalez * * * * 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 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef UTILS_H #define UTILS_H #include #include #include class OneTimeAction : public QObject { public: OneTimeAction(std::function func, QObject* parent) : QObject(parent), m_function(func) {} void trigger() { m_function(); deleteLater(); } private: std::function m_function; }; template static T kTransform(const Q &input, _UnaryOperation op) { T ret; ret.reserve(input.size()); for(const auto& v : input) { ret += op(v); } return ret; } template static T kFilter(const Q &input, _UnaryOperation op) { T ret; for(const auto& v : input) { if (op(v)) ret += v; } return ret; } template static int kIndexOf(const Q& list, W func) { int i = 0; for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) { if (func(*it)) return i; ++i; } return -1; } +template +static bool kContains(const Q& list, W func) +{ return kIndexOf(list, func) != -1; } + template static QVector kSetToVector(const QSet & set) { QVector ret; ret.reserve(set.size()); for(auto &x: set) ret.append(x); return ret; } template static QSet kVectorToSet(const QVector & set) { QSet ret; ret.reserve(set.size()); for(auto &x: set) ret.insert(x); return ret; } class ElapsedDebug : private QElapsedTimer { public: ElapsedDebug(const QString &name = QStringLiteral("")) : m_name(name) { start(); } ~ElapsedDebug() { qDebug("elapsed %s: %lld!", m_name.toUtf8().constData(), elapsed()); } void step(const QString &step) { qDebug("step %s(%s): %lld!", m_name.toUtf8().constData(), qPrintable(step), elapsed()); } QString m_name; }; class CallOnDestroy : public QObject { public: CallOnDestroy(std::function f) : m_func(std::move(f)) {} ~CallOnDestroy() { m_func(); } private: std::function m_func; }; #endif