diff --git a/discover/qml/ApplicationPage.qml b/discover/qml/ApplicationPage.qml index 99353fee..e0b1a55b 100644 --- a/discover/qml/ApplicationPage.qml +++ b/discover/qml/ApplicationPage.qml @@ -1,405 +1,405 @@ /* * 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. */ import QtQuick 2.5 import QtQuick.Controls 1.1 import QtQuick.Controls 2.3 as QQC2 import QtQuick.Window 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.1 as Kirigami import "navigation.js" as Navigation DiscoverPage { id: appInfo property QtObject application: null clip: true background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } ReviewsPage { id: reviewsSheet model: ReviewsModel { id: reviewsModel resource: appInfo.application } } contextualActions: [originsMenuAction] QQC2.ActionGroup { id: sourcesGroup exclusive: true } Kirigami.Action { id: originsMenuAction text: i18n("Sources") visible: children.length>1 readonly property var r0: Instantiator { model: ResourcesProxyModel { id: alternativeResourcesModel allBackends: true resourcesUrl: appInfo.application.url } delegate: QQC2.Action { QQC2.ActionGroup.group: sourcesGroup text: displayOrigin icon.name: sourceIcon checked: appInfo.application == model.application onTriggered: if(index>=0) { var res = model.application console.assert(res) window.stack.pop() Navigation.openApplication(res) } } onObjectAdded: originsMenuAction.children.push(object) } } actions { main: appbutton.action right: Kirigami.Action { - visible: application.isInstalled && application.canExecute + visible: application.isInstalled && application.canExecute && !appbutton.isActive text: application.executeLabel icon.name: "media-playback-start" onTriggered: application.invokeApplication() } } InstallApplicationButton { id: appbutton Layout.rightMargin: Kirigami.Units.smallSpacing application: appInfo.application visible: false } leftPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) rightPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) // Icon, name, caption, screenshots, description and reviews ColumnLayout { spacing: 0 RowLayout { Kirigami.Icon { Layout.preferredHeight: 80 Layout.preferredWidth: 80 source: appInfo.application.icon Layout.rightMargin: Kirigami.Units.smallSpacing * 2 } ColumnLayout { spacing: 0 Kirigami.Heading { level: 1 text: appInfo.application.name lineHeight: 1.0 maximumLineCount: 1 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Text.AlignBottom } RowLayout { spacing: Kirigami.Units.largeSpacing Rating { rating: appInfo.application.rating ? appInfo.application.rating.sortableRating : 0 starSize: summary.font.pointSize } QQC2.Label { text: appInfo.application.rating ? i18np("%1 rating", "%1 ratings", appInfo.application.rating.ratingCount) : i18n("No ratings yet") opacity: 0.5 } } Kirigami.Heading { id: summary level: 4 text: appInfo.application.comment maximumLineCount: 2 lineHeight: lineCount > 1 ? 0.75 : 1.2 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Qt.AlignTop } } Layout.bottomMargin: Kirigami.Units.largeSpacing } ApplicationScreenshots { id: screenshots Layout.fillWidth: true visible: count > 0 resource: appInfo.application QQC2.ScrollBar.horizontal: screenshotsScrollbar } QQC2.ScrollBar { id: screenshotsScrollbar Layout.fillWidth: true } QQC2.Label { Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap text: appInfo.application.longDescription } Kirigami.Heading { Layout.topMargin: Kirigami.Units.largeSpacing text: i18n("What's New") level: 2 visible: changelogLabel.text.length > 0 } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: changelogLabel.text.length > 0 } QQC2.Label { id: changelogLabel Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap Component.onCompleted: appInfo.application.fetchChangelog() Connections { target: appInfo.application onChangelogFetched: { changelogLabel.text = changelog } } } LinkButton { id: addonsButton text: i18n("Addons") visible: addonsView.containsAddons onClicked: addonsView.sheetOpen = true } Kirigami.Heading { Layout.topMargin: Kirigami.Units.largeSpacing text: i18n("Reviews") level: 2 visible: rep.count > 0 } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: rep.count > 0 } Repeater { id: rep model: PaginateModel { sourceModel: reviewsSheet.model pageSize: 3 } delegate: ReviewDelegate { Layout.topMargin: Kirigami.Units.largeSpacing separator: false compact: true Layout.bottomMargin: Kirigami.Units.largeSpacing } } LinkButton { text: appInfo.application.isInstalled? i18n("Be the first to write a review!") : i18n("Install this app and be the first to write a review!") onClicked: reviewsSheet.openReviewDialog() enabled: appInfo.application.isInstalled visible: !commentsButton.visible && reviewsModel.backend && reviewsModel.backend.isResourceSupported(appInfo.application) } LinkButton { id: commentsButton visible: reviewsModel.count > 0 text: i18n("Show all %1 reviews...", reviewsModel.count) onClicked: { reviewsSheet.open() } Layout.bottomMargin: Kirigami.Units.largeSpacing } Repeater { model: application.objects delegate: Loader { property QtObject resource: appInfo.application source: modelData } } Item { height: addonsButton.height width: 5 } // Details/metadata Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 Layout.bottomMargin: Kirigami.Units.largeSpacing } GridLayout { rowSpacing: 0 columns: 2 // Category row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Category:") } QQC2.Label { Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.categoryDisplay } // Version row QQC2.Label { visible: versionLabel.visible Layout.alignment: Qt.AlignRight text: i18n("Version:") } QQC2.Label { readonly property string version: appInfo.application.isInstalled ? appInfo.application.installedVersion : appInfo.application.availableVersion id: versionLabel visible: text.length > 0 Layout.fillWidth: true elide: Text.ElideRight text: version ? version : "" } // Size row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Size:") } QQC2.Label { Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.sizeDescription } // Source row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("Source:") } QQC2.Label { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: appInfo.application.displayOrigin elide: Text.ElideRight } // License row QQC2.Label { Layout.alignment: Qt.AlignRight text: i18n("License:") visible: appInfo.application.license.length>0 } LinkButton { elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft visible: text.length>0 text: appInfo.application.license // tooltip: i18n("See full license terms") onClicked: Qt.openUrlExternally("https://spdx.org/licenses/" + appInfo.application.license + ".html#licenseText") } // Homepage row QQC2.Label { visible: homepageLink.visible Layout.alignment: Qt.AlignRight text: i18n("Homepage:") } LinkButton { id: homepageLink visible: text.length > 0 text: application.homepage onClicked: Qt.openUrlExternally(application.homepage) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "User Guide" row QQC2.Label { visible: docsLink.visible Layout.alignment: Qt.AlignRight text: i18n("User Guide:") } LinkButton { id: docsLink visible: text.length > 0 text: application.helpURL onClicked: Qt.openUrlExternally(helpURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // Donate row QQC2.Label { visible: donationLink.visible Layout.alignment: Qt.AlignRight text: i18n("Donate:") } LinkButton { id: donationLink visible: text.length > 0 text: application.donationURL onClicked: Qt.openUrlExternally(donationURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "Report a Droblem" row QQC2.Label { visible: bugLink.visible Layout.alignment: Qt.AlignRight text: i18n("Report a Problem:") } LinkButton { id: bugLink visible: text.length > 0 text: application.bugURL onClicked: Qt.openUrlExternally(bugURL) elide: Text.ElideRight Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } } } readonly property var addons: AddonsView { id: addonsView application: appInfo.application parent: overlay } } diff --git a/discover/qml/ProgressView.qml b/discover/qml/ProgressView.qml index 1a0ac188..b2c38787 100644 --- a/discover/qml/ProgressView.qml +++ b/discover/qml/ProgressView.qml @@ -1,103 +1,103 @@ import QtQuick 2.1 import QtQuick.Controls 1.1 import QtQuick.Controls 2.1 as QQC2 import QtQuick.Layouts 1.1 import org.kde.discover 2.0 import org.kde.kirigami 2.0 as Kirigami import "navigation.js" as Navigation Kirigami.BasicListItem { id: listItem label: TransactionModel.count ? i18n("Tasks (%1%)", TransactionModel.progress) : i18n("Tasks") visible: TransactionModel.count > 0 background: Item { Rectangle { anchors { fill: parent rightMargin: TransactionModel.count>=1 ? listItem.width*(1-TransactionModel.progress/100) : 0 } color: TransactionModel.count>=1 || listItem.hovered || listItem.highlighted || listItem.pressed || listItem.checked ? listItem.activeBackgroundColor : listItem.backgroundColor opacity: listItem.hovered || listItem.highlighted ? 0.2 : 1 } } property QtObject sheetObject: null onClicked: { sheetObject = sheet.createObject() sheetObject.open() } onVisibleChanged: if (!visible && sheetObject) { sheetObject.close() sheetObject.destroy(100) } readonly property var v3: Component { id: sheet Kirigami.OverlaySheet { contentItem: ListView { spacing: 0 Component { id: listenerComp TransactionListener {} } model: TransactionModel delegate: Kirigami.AbstractListItem { id: del separatorVisible: false onClicked: { if (model.application) { Navigation.clearStack() Navigation.openApplication(model.application) } } readonly property QtObject listener: listenerComp.createObject(del, (model.transaction.resource ? {resource: model.transaction.resource} : {transaction: model.transaction})) ColumnLayout { width: parent.width RowLayout { Layout.fillWidth: true Kirigami.Icon { Layout.fillHeight: true Layout.minimumWidth: height source: model.transaction.icon } QQC2.Label { - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true elide: Text.ElideRight text: listener.isActive ? i18nc("TransactioName - TransactionStatus", "%1 - %2", model.transaction.name, listener.statusText) : model.transaction.name } ToolButton { iconName: "dialog-cancel" visible: listener.isCancellable onClicked: listener.cancel() } ToolButton { iconName: "system-run" visible: model.application != undefined && model.application.isInstalled && !listener.isActive && model.application.canExecute onClicked: { model.application.invokeApplication() model.remove(index) } } } ProgressBar { Layout.fillWidth: true visible: listener.isActive value: listener.progress maximumValue: 100 } } } } } } } diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml index b0215911..fe9aef82 100644 --- a/discover/qml/SourcesPage.qml +++ b/discover/qml/SourcesPage.qml @@ -1,230 +1,228 @@ import QtQuick 2.4 import QtQuick.Controls 1.1 import QtQuick.Controls 2.1 as QQC2 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: "" Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View background: Rectangle { color: Kirigami.Theme.backgroundColor } 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 Component { id: sourceBackendDelegate Kirigami.AbstractListItem { id: backendItem hoverEnabled: false supportsMouseEvents: false readonly property QtObject backend: sourcesBackend readonly property bool isDefault: ResourcesModel.currentApplicationBackend == resourcesBackend RowLayout { Connections { target: backendItem.backend onPassiveMessage: window.showPassiveNotification(message) } anchors { right: parent.right left: parent.left rightMargin: parent.rightPadding leftMargin: parent.leftPadding } Kirigami.Heading { Layout.fillWidth: true text: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName } Button { Layout.rightMargin: Kirigami.Units.smallSpacing iconName: "preferences-other" visible: resourcesBackend && resourcesBackend.hasApplications Component { id: dialogComponent AddSourceDialog { source: backendItem.backend onVisibleChanged: if (!visible) { destroy() } } } menu: 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() } } MenuSeparator { visible: backendActionsInst.count>0 } Instantiator { id: backendActionsInst model: ActionsModel { actions: backendItem.backend ? backendItem.backend.actions : undefined } delegate: MenuItem { action: ActionBridge { action: modelData.action } } onObjectAdded: { settingsMenu.insertItem(index, object) } onObjectRemoved: { object.destroy() } } } } } } } delegate: ConditionalLoader { anchors { right: parent.right left: parent.left } readonly property variant resourcesBackend: model.resourcesBackend readonly property variant sourcesBackend: model.sourcesBackend readonly property variant display: model.display readonly property variant checked: model.checked readonly property variant statusTip: model.statusTip readonly property variant toolTip: model.toolTip readonly property variant sourceId: model.sourceId readonly property variant modelIndex: sourcesView.model.index(index, 0) condition: resourcesBackend != null componentTrue: sourceBackendDelegate componentFalse: sourceDelegate } Component { id: sourceDelegate Kirigami.SwipeListItem { Layout.fillWidth: true enabled: display.length>0 highlighted: ListView.isCurrentItem 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") 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 modelChecked: sourcesView.model.data(modelIndex, Qt.CheckStateRole) checked: modelChecked != Qt.Unchecked enabled: modelChecked !== undefined onClicked: { sourcesView.model.setData(modelIndex, checkedState, Qt.CheckStateRole) } } QQC2.Label { text: display + " - " + 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 } Repeater { id: back model: ResourcesProxyModel { extending: "org.kde.discover.desktop" } - delegate: RowLayout { - visible: !model.application.isInstalled - QQC2.Label { - Layout.fillWidth: true - text: name - } + delegate: Kirigami.BasicListItem { + supportsMouseEvents: false + label: name + icon: model.icon InstallApplicationButton { application: model.application } } } } } } diff --git a/libdiscover/backends/DummyBackend/DummyBackend.cpp b/libdiscover/backends/DummyBackend/DummyBackend.cpp index 79f2fc48..ce3659b3 100644 --- a/libdiscover/backends/DummyBackend/DummyBackend.cpp +++ b/libdiscover/backends/DummyBackend/DummyBackend.cpp @@ -1,172 +1,179 @@ /*************************************************************************** * 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 "DummyBackend.h" #include "DummyResource.h" #include "DummyReviewsBackend.h" #include "DummyTransaction.h" #include "DummySourcesBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include DISCOVER_BACKEND_PLUGIN(DummyBackend) DummyBackend::DummyBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(new DummyReviewsBackend(this)) , m_fetching(true) , m_startElements(120) { QTimer::singleShot(500, this, &DummyBackend::toggleFetching); connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &DummyBackend::updatesCountChanged); populate(QStringLiteral("Dummy")); if (!m_fetching) m_reviews->initialize(); SourcesModel::global()->addSourcesBackend(new DummySourcesBackend(this)); } void DummyBackend::populate(const QString& n) { const int start = m_resources.count(); for(int i=start; isetSize(100+(m_startElements-i)); res->setState(AbstractResource::State(1+(i%3))); m_resources.insert(name.toLower(), res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); } for(int i=start; isetState(AbstractResource::State(1+(i%3))); res->setSize(300+(m_startElements-i)); m_resources.insert(name, res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); } } void DummyBackend::toggleFetching() { m_fetching = !m_fetching; // qDebug() << "fetching..." << m_fetching; emit fetchingChanged(); if (!m_fetching) m_reviews->initialize(); } int DummyBackend::updatesCount() const { return m_updater->updatesCount(); } ResultsStream* DummyBackend::search(const AbstractResourcesBackend::Filters& filter) { QVector ret; - if (!filter.resourceUrl.isEmpty() && filter.resourceUrl.scheme() == QLatin1String("dummy")) + if (!filter.resourceUrl.isEmpty()) return findResourceByPackageName(filter.resourceUrl); else foreach(AbstractResource* r, m_resources) { + if (r->isTechnical() && filter.state != AbstractResource::Upgradeable) { + continue; + } + + if (r->state() < filter.state) + continue; + if(r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) ret += r; } return new ResultsStream(QStringLiteral("DummyStream"), ret); } ResultsStream * DummyBackend::findResourceByPackageName(const QUrl& search) { auto res = search.scheme() == QLatin1String("dummy") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr; if (!res) { return new ResultsStream(QStringLiteral("DummyStream"), {}); } else return new ResultsStream(QStringLiteral("DummyStream"), { res }); } AbstractBackendUpdater* DummyBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* DummyBackend::reviewsBackend() const { return m_reviews; } Transaction* DummyBackend::installApplication(AbstractResource* app, const AddonList& addons) { return new DummyTransaction(qobject_cast(app), addons, Transaction::InstallRole); } Transaction* DummyBackend::installApplication(AbstractResource* app) { return new DummyTransaction(qobject_cast(app), Transaction::InstallRole); } Transaction* DummyBackend::removeApplication(AbstractResource* app) { return new DummyTransaction(qobject_cast(app), Transaction::RemoveRole); } void DummyBackend::checkForUpdates() { if(m_fetching) return; toggleFetching(); populate(QStringLiteral("Moar")); QTimer::singleShot(500, this, &DummyBackend::toggleFetching); qDebug() << "DummyBackend::checkForUpdates"; } AbstractResource * DummyBackend::resourceForFile(const QUrl& path) { DummyResource* res = new DummyResource(path.fileName(), true, this); res->setSize(666); res->setState(AbstractResource::None); m_resources.insert(res->packageName(), res); connect(res, &DummyResource::stateChanged, this, &DummyBackend::updatesCountChanged); return res; } QString DummyBackend::displayName() const { return QStringLiteral("Dummy"); } bool DummyBackend::hasApplications() const { return true; } #include "DummyBackend.moc" diff --git a/libdiscover/backends/DummyBackend/DummyBackend.h b/libdiscover/backends/DummyBackend/DummyBackend.h index 226df7ed..b3a33156 100644 --- a/libdiscover/backends/DummyBackend/DummyBackend.h +++ b/libdiscover/backends/DummyBackend/DummyBackend.h @@ -1,68 +1,68 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef DUMMYBACKEND_H #define DUMMYBACKEND_H #include #include class QAction; class DummyReviewsBackend; class StandardBackendUpdater; class DummyResource; class DummyBackend : public AbstractResourcesBackend { Q_OBJECT Q_PROPERTY(int startElements MEMBER m_startElements) public: explicit DummyBackend(QObject* parent = nullptr); int updatesCount() const override; AbstractBackendUpdater* backendUpdater() const override; AbstractReviewsBackend* reviewsBackend() const override; ResultsStream* search(const AbstractResourcesBackend::Filters & search) override; - ResultsStream * findResourceByPackageName(const QUrl& 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; public Q_SLOTS: void toggleFetching(); private: void populate(const QString& name); QHash m_resources; StandardBackendUpdater* m_updater; DummyReviewsBackend* m_reviews; bool m_fetching; int m_startElements; }; #endif // DUMMYBACKEND_H diff --git a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp index eba06aec..2186045d 100644 --- a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp +++ b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp @@ -1,289 +1,298 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "DummyTest.h" #include "DiscoverBackendsFactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(DummyTest) AbstractResourcesBackend* backendByName(ResourcesModel* m, const QString& name) { QVector backends = m->backends(); foreach(AbstractResourcesBackend* backend, backends) { if(QString::fromLatin1(backend->metaObject()->className()) == name) { return backend; } } return nullptr; } DummyTest::DummyTest(QObject* parent): QObject(parent) { DiscoverBackendsFactory::setRequestedBackends({ QStringLiteral("dummy-backend") }); m_model = new ResourcesModel(QStringLiteral("dummy-backend"), this); m_appBackend = backendByName(m_model, QStringLiteral("DummyBackend")); CategoryModel::global()->populateCategories(); } void DummyTest::initTestCase() { QVERIFY(m_appBackend); while(m_appBackend->isFetching()) { QSignalSpy spy(m_appBackend, &AbstractResourcesBackend::fetchingChanged); QVERIFY(spy.wait()); } } QVector fetchResources(ResultsStream* stream) { QVector ret; QObject::connect(stream, &ResultsStream::resourcesFound, stream, [&ret](const QVector& res) { ret += res; }); QSignalSpy spy(stream, &ResultsStream::destroyed); Q_ASSERT(spy.wait()); return ret; } void DummyTest::testReadData() { const auto resources = fetchResources(m_appBackend->search({})); QCOMPARE(m_appBackend->property("startElements").toInt()*2, resources.size()); QBENCHMARK { for(AbstractResource* res: resources) { QVERIFY(!res->name().isEmpty()); } } } void DummyTest::testProxy() { ResourcesProxyModel pm; QSignalSpy spy(&pm, &ResourcesProxyModel::busyChanged); // QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); pm.setFiltersFromCategory(CategoryModel::global()->rootCategories().first()); pm.componentComplete(); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt()*2, pm.rowCount()); pm.setSearch(QStringLiteral("techie")); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt(), pm.rowCount()); QCOMPARE(pm.subcategories().count(), 7); pm.setSearch(QString()); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt()*2, pm.rowCount()); } void DummyTest::testProxySorting() { ResourcesProxyModel pm; QSignalSpy spy(&pm, &ResourcesProxyModel::busyChanged); // QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); pm.setFiltersFromCategory(CategoryModel::global()->rootCategories().first()); pm.setSortOrder(Qt::DescendingOrder); pm.setSortRole(ResourcesProxyModel::RatingCountRole); pm.componentComplete(); QVERIFY(pm.isBusy()); QVERIFY(spy.wait()); QVERIFY(!pm.isBusy()); QCOMPARE(m_appBackend->property("startElements").toInt()*2, pm.rowCount()); QVariant lastRatingCount; for(int i=0, rc=pm.rowCount(); isearch({})); QCOMPARE(m_appBackend->property("startElements").toInt()*2, resources.count()); //fetches updates, adds new things m_appBackend->checkForUpdates(); QSignalSpy spy(m_model, SIGNAL(allInitialized())); QVERIFY(spy.wait(80000)); auto resources2 = fetchResources(m_appBackend->search({})); QCOMPARE(m_appBackend->property("startElements").toInt()*4, resources2.count()); } void DummyTest::testSort() { ResourcesProxyModel pm; QCollator c; QBENCHMARK_ONCE { pm.setSortRole(ResourcesProxyModel::NameRole); pm.sort(0); QCOMPARE(pm.sortOrder(), Qt::AscendingOrder); QString last; for(int i = 0, count = pm.rowCount(); ifindResourceByPackageName(QUrl(QStringLiteral("dummy://Dummy.1")))); + AbstractResourcesBackend::Filters filter; + filter.resourceUrl = QUrl(QStringLiteral("dummy://Dummy.1")); + + const auto resources = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); ApplicationAddonsModel m; new ModelTest(&m, &m); m.setApplication(res); QCOMPARE(m.rowCount(), res->addonsInformation().count()); QCOMPARE(res->addonsInformation().at(0).isInstalled(), false); QString firstAddonName = m.data(m.index(0,0)).toString(); m.changeState(firstAddonName, true); QVERIFY(m.hasChanges()); m.applyChanges(); QSignalSpy sR(TransactionModel::global(), &TransactionModel::transactionRemoved); QVERIFY(sR.wait()); QVERIFY(!m.hasChanges()); QCOMPARE(m.data(m.index(0,0)).toString(), firstAddonName); QCOMPARE(res->addonsInformation().at(0).name(), firstAddonName); QCOMPARE(res->addonsInformation().at(0).isInstalled(), true); m.changeState(m.data(m.index(1,0)).toString(), true); QVERIFY(m.hasChanges()); for(int i=0, c=m.rowCount(); ifindResourceByPackageName(QUrl(QStringLiteral("dummy://Dummy.1")))); + AbstractResourcesBackend::Filters filter; + filter.resourceUrl = QUrl(QStringLiteral("dummy://Dummy.1")); + + const auto resources = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); ReviewsModel m; new ModelTest(&m, &m); m.setResource(res); m.fetchMore(); QVERIFY(m.rowCount()>0); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::None); m.markUseful(0, true); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::Yes); m.markUseful(0, false); QCOMPARE(ReviewsModel::UserChoice(m.data(m.index(0,0), ReviewsModel::UsefulChoice).toInt()), ReviewsModel::No); - const auto resources2 = fetchResources(m_appBackend->findResourceByPackageName(QUrl(QStringLiteral("dummy://Dummy.1")))); + const auto resources2 = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources2.count(), 1); res = resources2.first(); m.setResource(res); m.fetchMore(); QSignalSpy spy(&m, &ReviewsModel::rowsChanged); QVERIFY(m.rowCount()>0); } void DummyTest::testUpdateModel() { const auto backend = m_model->backends().first(); ResourcesUpdatesModel ruModel; new ModelTest(&ruModel, &ruModel); UpdateModel model; new ModelTest(&model, &model); model.setBackend(&ruModel); QCOMPARE(model.rowCount(), 4*backend->property("startElements").toInt()/3); QCOMPARE(model.hasUpdates(), true); } void DummyTest::testScreenshotsModel() { + AbstractResourcesBackend::Filters filter; + filter.resourceUrl = QUrl(QStringLiteral("dummy://Dummy.1")); + ScreenshotsModel m; new ModelTest(&m, &m); - const auto resources = fetchResources(m_appBackend->findResourceByPackageName(QUrl(QStringLiteral("dummy://Dummy.1")))); + const auto resources = fetchResources(m_appBackend->search(filter)); QCOMPARE(resources.count(), 1); AbstractResource* res = resources.first(); QVERIFY(res); m.setResource(res); QCOMPARE(res, m.resource()); int c=m.rowCount(); for(int i=0; i * * 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) 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_fetching; } 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); 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); bool compareAppFlatpakRef(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, FlatpakInstalledRef *ref) const; void finishInitialization(); 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); void setFetching(bool fetching); QHash m_resources; StandardBackendUpdater *m_updater; FlatpakSourcesBackend *m_sources = nullptr; QSharedPointer m_reviews; bool m_fetching; uint m_refreshAppstreamMetadataJobs; GCancellable *m_cancellable; QVector m_installations; QThreadPool m_threadPool; }; #endif // FLATPAKBACKEND_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp index 153db004..2286f260 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp @@ -1,295 +1,295 @@ /*************************************************************************** * Copyright © 2014 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 "FlatpakSourcesBackend.h" #include "FlatpakResource.h" #include "FlatpakBackend.h" #include #include #include #include #include #include #include #include #include #include #include class FlatpakSourceItem : public QStandardItem { public: FlatpakSourceItem(const QString &text) : QStandardItem(text) { } void setFlatpakInstallation(FlatpakInstallation *installation) { m_installation = installation; } FlatpakInstallation *flatpakInstallation() const { return m_installation; } private: FlatpakInstallation *m_installation; }; FlatpakSourcesBackend::FlatpakSourcesBackend(const QVector &installations, AbstractResourcesBackend * parent) : AbstractSourcesBackend(parent) , m_preferredInstallation(installations.constFirst()) , m_sources(new QStandardItemModel(this)) , m_flathubAction(new QAction(i18n("Add Flathub"), this)) { QHash roles = m_sources->roleNames(); roles.insert(Qt::CheckStateRole, "checked"); roles.insert(Qt::UserRole, "flatpakInstallation"); m_sources->setItemRoleNames(roles); m_flathubAction->setToolTip(QStringLiteral("flathub")); connect(m_flathubAction, &QAction::triggered, this, [this](){ addSource(QStringLiteral("https://flathub.org/repo/flathub.flatpakrepo")); }); for (auto installation : installations) { if (!listRepositories(installation)) { qWarning() << "Failed to list repositories from installation" << installation; } } } FlatpakSourcesBackend::~FlatpakSourcesBackend() { QStringList ids; for (int i = 0, c = m_sources->rowCount(); iitem(i); ids << it->data(IdRole).toString(); } auto conf = KSharedConfig::openConfig(); KConfigGroup group = conf->group("FlatpakSources"); group.writeEntry("Sources", ids); } QAbstractItemModel* FlatpakSourcesBackend::sources() { return m_sources; } bool FlatpakSourcesBackend::addSource(const QString &id) { FlatpakBackend* backend = qobject_cast(parent()); - const QUrl flatpakrepoUrl = QUrl::fromUserInput(id); + const QUrl flatpakrepoUrl(id); if (id.isEmpty() || !flatpakrepoUrl.isValid()) return false; if (flatpakrepoUrl.isLocalFile()) { auto res = backend->addSourceFromFlatpakRepo(flatpakrepoUrl); if (res) backend->installApplication(res); else backend->passiveMessage(i18n("Could not add the source %1", flatpakrepoUrl.toDisplayString())); } else { AbstractResourcesBackend::Filters filter; filter.resourceUrl = flatpakrepoUrl; auto stream = new StoredResultsStream ({backend->search(filter)}); connect(stream, &StoredResultsStream::finished, this, [backend, stream, flatpakrepoUrl]() { const auto res = stream->resources(); if (!res.isEmpty()) { Q_ASSERT(res.count() == 1); backend->installApplication(res.first()); } else { backend->passiveMessage(i18n("Could not add the source %1", flatpakrepoUrl.toDisplayString())); } }); } return true; } QStandardItem * FlatpakSourcesBackend::sourceById(const QString& id) const { QStandardItem* sourceIt = nullptr; for (int i = 0, c = m_sources->rowCount(); iitem(i); if (it->data(IdRole) == id) { sourceIt = it; break; } } return sourceIt; } bool FlatpakSourcesBackend::removeSource(const QString &id) { auto sourceIt = sourceById(id); if (sourceIt) { FlatpakSourceItem *sourceItem = static_cast(sourceIt); g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) error = NULL; if (flatpak_installation_remove_remote(sourceItem->flatpakInstallation(), id.toUtf8().constData(), cancellable, &error)) { m_sources->removeRow(sourceItem->row()); return true; } else { qWarning() << "Failed to remove " << id << " remote repository:" << error->message; return false; } } else { qWarning() << "couldn't find " << id; return false; } return false; } QList FlatpakSourcesBackend::actions() const { return { m_flathubAction }; } bool FlatpakSourcesBackend::listRepositories(FlatpakInstallation* installation) { Q_ASSERT(installation); g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GPtrArray) remotes = flatpak_installation_list_remotes(installation, cancellable, nullptr); if (!remotes) { return false; } for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); if (flatpak_remote_get_noenumerate(remote)) { continue; } addRemote(remote, installation); } return true; } FlatpakRemote * FlatpakSourcesBackend::installSource(FlatpakResource *resource) { g_autoptr(GCancellable) cancellable = g_cancellable_new(); auto remote = flatpak_installation_get_remote_by_name(m_preferredInstallation, resource->flatpakName().toUtf8().constData(), cancellable, nullptr); if (remote) { qWarning() << "Source " << resource->flatpakName() << " already exists"; return nullptr; } remote = flatpak_remote_new(resource->flatpakName().toUtf8().constData()); flatpak_remote_set_url(remote, resource->getMetadata(QStringLiteral("repo-url")).toString().toUtf8().constData()); flatpak_remote_set_noenumerate(remote, false); flatpak_remote_set_title(remote, resource->comment().toUtf8().constData()); const QString gpgKey = resource->getMetadata(QStringLiteral("gpg-key")).toString(); if (!gpgKey.isEmpty()) { gsize dataLen = 0; g_autofree guchar *data = nullptr; g_autoptr(GBytes) bytes = nullptr; data = g_base64_decode(gpgKey.toUtf8().constData(), &dataLen); bytes = g_bytes_new(data, dataLen); flatpak_remote_set_gpg_verify(remote, true); flatpak_remote_set_gpg_key(remote, bytes); } else { flatpak_remote_set_gpg_verify(remote, false); } if (!resource->branch().isEmpty()) { flatpak_remote_set_default_branch(remote, resource->branch().toUtf8().constData()); } if (!flatpak_installation_modify_remote(m_preferredInstallation, remote, cancellable, nullptr)) { qWarning() << "Failed to add source " << resource->flatpakName(); return nullptr; } addRemote(remote, m_preferredInstallation); return remote; } void FlatpakSourcesBackend::addRemote(FlatpakRemote *remote, FlatpakInstallation *installation) { const QString id = QString::fromUtf8(flatpak_remote_get_name(remote)); const QString title = QString::fromUtf8(flatpak_remote_get_title(remote)); const QUrl remoteUrl(QString::fromUtf8(flatpak_remote_get_url(remote))); for(QAction *action: actions()) { if (action->toolTip() == id) { action->setEnabled(false); action->setVisible(false); } } FlatpakSourceItem *it = new FlatpakSourceItem(!title.isEmpty() ? title : id); it->setCheckState(flatpak_remote_get_disabled(remote) ? Qt::Unchecked : Qt::Checked); it->setData(remoteUrl.host(), Qt::ToolTipRole); it->setData(id, IdRole); it->setFlatpakInstallation(installation); int idx = -1; { const auto conf = KSharedConfig::openConfig(); const KConfigGroup group = conf->group("FlatpakSources"); const auto ids = group.readEntry("Sources", QStringList()); const auto ourIdx = ids.indexOf(id); if (ourIdx<0) { //If not present, we put it on top idx = 0; } else { idx=0; for(int c=m_sources->rowCount(); idxitem(idx); const int compIdx = ids.indexOf(compIt->data(IdRole).toString()); if (compIdx >= ourIdx) { break; } } } } m_sources->insertRow(idx, it); if (m_sources->rowCount() == 1) Q_EMIT firstSourceIdChanged(); Q_EMIT lastSourceIdChanged(); } QString FlatpakSourcesBackend::idDescription() { return i18n("Flatpak repository URI (*.flatpakrepo)"); } bool FlatpakSourcesBackend::moveSource(const QString& sourceId, int delta) { const auto row = sourceById(sourceId)->row(); auto prevRow = m_sources->takeRow(row); Q_ASSERT(!prevRow.isEmpty()); const auto destRow = row + (delta>0? delta : delta); m_sources->insertRow(destRow, prevRow); if (destRow == 0 || row == 0) Q_EMIT firstSourceIdChanged(); if (destRow == m_sources->rowCount() - 1 || row == m_sources->rowCount() - 1) Q_EMIT lastSourceIdChanged(); return true; } int FlatpakSourcesBackend::originIndex(const QString& sourceId) const { return sourceById(sourceId)->row(); } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp index 735c1986..e80d4374 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * 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 "FlatpakTransaction.h" #include "FlatpakBackend.h" #include "FlatpakResource.h" #include "FlatpakTransactionJob.h" #include #include extern "C" { #include #include #include } FlatpakTransaction::FlatpakTransaction(FlatpakResource *app, Role role, bool delayStart) : FlatpakTransaction(app, nullptr, role, delayStart) { } FlatpakTransaction::FlatpakTransaction(FlatpakResource *app, FlatpakResource *runtime, Transaction::Role role, bool delayStart) : Transaction(app->backend(), app, role, {}) , m_app(app) , m_runtime(runtime) { setCancellable(true); setStatus(QueuedStatus); if (!delayStart) { QTimer::singleShot(0, this, &FlatpakTransaction::start); } } FlatpakTransaction::~FlatpakTransaction() { for(auto job : m_jobs) { if (!job->isFinished()) { connect(job, &QThread::finished, job, &QObject::deleteLater); } else delete job; } } void FlatpakTransaction::cancel() { Q_ASSERT(m_appJob); foreach (const QPointer &job, m_jobs) { job->cancel(); } setStatus(CancelledStatus); } void FlatpakTransaction::setRuntime(FlatpakResource *runtime) { m_runtime = runtime; } void FlatpakTransaction::start() { - setStatus(DownloadingStatus); + setStatus(CommittingStatus); if (m_runtime) { QPointer job = new FlatpakTransactionJob(m_runtime, {}, role()); connect(job, &FlatpakTransactionJob::finished, this, &FlatpakTransaction::onJobFinished); connect(job, &FlatpakTransactionJob::progressChanged, this, &FlatpakTransaction::onJobProgressChanged); m_jobs << job; processRelatedRefs(m_runtime); } // App job will be added everytime m_appJob = new FlatpakTransactionJob(m_app, {}, role()); connect(m_appJob, &FlatpakTransactionJob::finished, this, &FlatpakTransaction::onJobFinished); connect(m_appJob, &FlatpakTransactionJob::progressChanged, this, &FlatpakTransaction::onJobProgressChanged); m_jobs << m_appJob; processRelatedRefs(m_app); // Now start all the jobs together foreach (const QPointer &job, m_jobs) { job->start(); } } void FlatpakTransaction::processRelatedRefs(FlatpakResource* resource) { g_autoptr(GPtrArray) refs = nullptr; g_autoptr(GError) error = nullptr; g_autoptr(GCancellable) cancellable = g_cancellable_new();; QList additionalResources; g_autofree gchar *ref = g_strdup_printf ("%s/%s/%s/%s", resource->typeAsString().toUtf8().constData(), resource->flatpakName().toUtf8().constData(), resource->arch().toUtf8().constData(), resource->branch().toUtf8().constData()); if (role() == Transaction::Role::InstallRole) { if (resource->state() == AbstractResource::Upgradeable) { refs = flatpak_installation_list_installed_related_refs_sync(resource->installation(), resource->origin().toUtf8().constData(), ref, cancellable, &error); if (error) { qWarning() << "Failed to list installed related refs for update: " << error->message; } } else { refs = flatpak_installation_list_remote_related_refs_sync(resource->installation(), resource->origin().toUtf8().constData(), ref, cancellable, &error); if (error) { qWarning() << "Failed to list related refs for installation: " << error->message; } } } else if (role() == Transaction::Role::RemoveRole) { refs = flatpak_installation_list_installed_related_refs_sync(resource->installation(), resource->origin().toUtf8().constData(), ref, cancellable, &error); if (error) { qWarning() << "Failed to list installed related refs for removal: " << error->message; } } if (refs) { for (uint i = 0; i < refs->len; i++) { FlatpakRef *flatpakRef = FLATPAK_REF(g_ptr_array_index(refs, i)); if (flatpak_related_ref_should_download(FLATPAK_RELATED_REF(flatpakRef))) { QPointer job = new FlatpakTransactionJob(resource, QPair(QString::fromUtf8(flatpak_ref_get_name(flatpakRef)), flatpak_ref_get_kind(flatpakRef)), role()); connect(job, &FlatpakTransactionJob::finished, this, &FlatpakTransaction::onJobFinished); connect(job, &FlatpakTransactionJob::progressChanged, this, &FlatpakTransaction::onJobProgressChanged); // Add to the list of all jobs m_jobs << job; } } } } void FlatpakTransaction::onJobFinished() { FlatpakTransactionJob *job = static_cast(sender()); if (job != m_appJob) { if (!job->result()) { Q_EMIT passiveMessage(job->errorMessage()); } // Mark runtime as installed if (m_runtime && job->app()->flatpakName() == m_runtime->flatpakName() && !job->isRelated() && role() != Transaction::Role::RemoveRole) { if (job->result()) { m_runtime->setState(AbstractResource::Installed); } } } foreach (const QPointer &job, m_jobs) { if (job->isRunning()) { return; } } // No other job is running → finish transaction finishTransaction(); } void FlatpakTransaction::onJobProgressChanged(int progress) { Q_UNUSED(progress); int total = 0; // Count progress from all the jobs foreach (const QPointer &job, m_jobs) { total += job->progress(); } setProgress(total / m_jobs.count()); } void FlatpakTransaction::finishTransaction() { if (m_appJob->result()) { AbstractResource::State newState = AbstractResource::None; switch(role()) { case InstallRole: case ChangeAddonsRole: newState = AbstractResource::Installed; break; case RemoveRole: newState = AbstractResource::None; break; } m_app->setState(newState); setStatus(DoneStatus); } else { setStatus(DoneWithErrorStatus); } } diff --git a/libdiscover/backends/KNSBackend/KNSBackend.h b/libdiscover/backends/KNSBackend/KNSBackend.h index f8a9d854..c459dafc 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.h +++ b/libdiscover/backends/KNSBackend/KNSBackend.h @@ -1,101 +1,101 @@ /*************************************************************************** * 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 KNSBACKEND_H #define KNSBACKEND_H #include #include #include "Transaction/AddonList.h" #include "discovercommon_export.h" class KConfigGroup; class KNSReviews; class KNSResource; class StandardBackendUpdater; namespace KNSCore { class Engine; } class DISCOVERCOMMON_EXPORT KNSBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit KNSBackend(QObject* parent, const QString& iconName, const QString &knsrc); ~KNSBackend() override; Transaction* removeApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; int updatesCount() const override; AbstractReviewsBackend* reviewsBackend() const override; AbstractBackendUpdater* backendUpdater() const override; bool isFetching() const override; ResultsStream* search(const AbstractResourcesBackend::Filters & filter) override; - ResultsStream* findResourceByPackageName(const QUrl & search) override; + ResultsStream* findResourceByPackageName(const QUrl & search); QVector category() const override { return m_rootCategories; } bool isValid() const override; QStringList extends() const override { return m_extends; } QString iconName() const { return m_iconName; } KNSCore::Engine* engine() const { return m_engine; } void checkForUpdates() override {} QString displayName() const override; Q_SIGNALS: void receivedResources(const QVector &resources); void searchFinished(); void startingSearch(); void availableForQueries(); public Q_SLOTS: void receivedEntries(const KNSCore::EntryInternal::List& entries); void statusChanged(const KNSCore::EntryInternal& entry); private: void fetchInstalled(); KNSResource* resourceForEntry(const KNSCore::EntryInternal& entry); void setFetching(bool f); void markInvalid(const QString &message); ResultsStream* searchStream(const QString &searchText); bool m_onePage = false; bool m_responsePending = false; bool m_fetching; bool m_isValid; KNSCore::Engine* m_engine; QHash m_resourcesByName; KNSReviews* const m_reviews; QString m_name; QString m_iconName; StandardBackendUpdater* const m_updater; QStringList m_extends; QStringList m_categories; QVector m_rootCategories; QString m_displayName; }; #endif // KNSBACKEND_H diff --git a/libdiscover/backends/KNSBackend/tests/KNSBackendTest.cpp b/libdiscover/backends/KNSBackend/tests/KNSBackendTest.cpp index 315578db..058c9b06 100644 --- a/libdiscover/backends/KNSBackend/tests/KNSBackendTest.cpp +++ b/libdiscover/backends/KNSBackend/tests/KNSBackendTest.cpp @@ -1,170 +1,166 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "KNSBackendTest.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN( KNSBackendTest ) KNSBackendTest::KNSBackendTest(QObject* parent) : QObject(parent) , m_r(nullptr) { QStandardPaths::setTestModeEnabled(true); ResourcesModel* model = new ResourcesModel(QLatin1String("kns-backend"), this); Q_ASSERT(!model->backends().isEmpty()); auto findTestBackend = [](AbstractResourcesBackend* backend) { return backend->name() == QLatin1String("discover_ktexteditor_codesnippets_core.knsrc"); }; m_backend = kFilter>(model->backends(), findTestBackend).at(0); if (!m_backend->isValid()) { qWarning() << "couldn't run the test"; exit(0); } connect(m_backend->reviewsBackend(), &AbstractReviewsBackend::reviewsReady, this, &KNSBackendTest::reviewsArrived); } QVector KNSBackendTest::getResources(ResultsStream* stream, bool canBeEmpty) { Q_ASSERT(stream); Q_ASSERT(stream->objectName() != QLatin1String("KNS-void")); QSignalSpy spyResources(stream, &ResultsStream::destroyed); QVector resources; connect(stream, &ResultsStream::resourcesFound, this, [&resources](const QVector& res) { resources += res; }); Q_ASSERT(spyResources.wait(10000)); Q_ASSERT(!resources.isEmpty() || canBeEmpty); return resources; } QVector KNSBackendTest::getAllResources(AbstractResourcesBackend* backend) { AbstractResourcesBackend::Filters f; if (CategoryModel::global()->rootCategories().isEmpty()) CategoryModel::global()->populateCategories(); f.category = CategoryModel::global()->rootCategories().constFirst(); return getResources(backend->search(f)); } void KNSBackendTest::testRetrieval() { QVERIFY(m_backend->backendUpdater()); QCOMPARE(m_backend->updatesCount(), m_backend->backendUpdater()->toUpdate().count()); QSignalSpy spy(m_backend, &AbstractResourcesBackend::fetchingChanged); QVERIFY(!m_backend->isFetching() || spy.wait()); const auto resources = getAllResources(m_backend); foreach(AbstractResource* res, resources) { QVERIFY(!res->name().isEmpty()); QVERIFY(!res->categories().isEmpty()); QVERIFY(!res->origin().isEmpty()); QVERIFY(!res->icon().isNull()); // QVERIFY(!res->comment().isEmpty()); // QVERIFY(!res->longDescription().isEmpty()); // QVERIFY(!res->license().isEmpty()); QVERIFY(res->homepage().isValid() && !res->homepage().isEmpty()); QVERIFY(res->state() > AbstractResource::Broken); QVERIFY(res->addonsInformation().isEmpty()); QSignalSpy spy(res, &AbstractResource::screenshotsFetched); res->fetchScreenshots(); QVERIFY(spy.count() || spy.wait()); QSignalSpy spy1(res, &AbstractResource::changelogFetched); res->fetchChangelog(); QVERIFY(spy1.count() || spy1.wait()); } } void KNSBackendTest::testReviews() { const QVector resources = getAllResources(m_backend); AbstractReviewsBackend* rev = m_backend->reviewsBackend(); QVERIFY(!rev->hasCredentials()); foreach(AbstractResource* res, resources) { Rating* r = rev->ratingForApplication(res); QVERIFY(r); QCOMPARE(r->packageName(), res->packageName()); QVERIFY(r->rating()>0 && r->rating()<=10); } auto res = resources.first(); QSignalSpy spy(rev, &AbstractReviewsBackend::reviewsReady); rev->fetchReviews(res); QVERIFY(spy.count() || spy.wait()); } void KNSBackendTest::reviewsArrived(AbstractResource* r, const QVector& revs) { m_r = r; m_revs = revs; } void KNSBackendTest::testResourceByUrl() { - const QUrl url(QStringLiteral("kns://") + m_backend->name() + QStringLiteral("/api.kde-look.org/1136471")); - - auto resources = getResources(m_backend->findResourceByPackageName(url)); + AbstractResourcesBackend::Filters f; + f.resourceUrl = QUrl(QStringLiteral("kns://") + m_backend->name() + QStringLiteral("/api.kde-look.org/1136471")); + const QVector resources = getResources(m_backend->search(f)); const QVector res = kTransform>(resources, [](AbstractResource* res){ return res->url(); }); QCOMPARE(res.count(), 1); - QCOMPARE(url, res.constFirst()); - - AbstractResourcesBackend::Filters f; - f.resourceUrl = url; - const QVector res2 = kTransform>(getResources(m_backend->search(f)), [](AbstractResource* res){ return res->url(); }); - QCOMPARE(res, res2); + QCOMPARE(f.resourceUrl, res.constFirst()); auto resource = resources.constFirst(); QVERIFY(!resource->isInstalled()); //Make sure .qttest is clean before running the test QSignalSpy spy(resource, &AbstractResource::stateChanged); auto b = resource->backend(); b->installApplication(resource); QVERIFY(spy.wait()); b->removeApplication(resource); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 2); QVERIFY(!resource->isInstalled()); } void KNSBackendTest::testResourceByUrlResourcesModel() { - const QUrl url(QStringLiteral("kns://plasmoids.knsrc/store.kde.org/1169537")); //Wrong domain + AbstractResourcesBackend::Filters filter; + filter.resourceUrl = QUrl(QStringLiteral("kns://plasmoids.knsrc/store.kde.org/1169537")); //Wrong domain - auto resources = getResources(ResourcesModel::global()->findResourceByPackageName(url), true); + auto resources = getResources(ResourcesModel::global()->search(filter), true); const QVector res = kTransform>(resources, [](AbstractResource* res){ return res->url(); }); QCOMPARE(res.count(), 0); } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.h b/libdiscover/backends/PackageKitBackend/PackageKitBackend.h index e6252a69..1df20bc0 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.h +++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.h @@ -1,127 +1,127 @@ /*************************************************************************** * 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 PACKAGEKITBACKEND_H #define PACKAGEKITBACKEND_H #include "PackageKitResource.h" #include #include #include #include #include #include #include #include #include class AppPackageKitResource; class PackageKitUpdater; class PKTransaction; class OdrsReviewsBackend; class DISCOVERCOMMON_EXPORT PackageKitBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit PackageKitBackend(QObject* parent = nullptr); ~PackageKitBackend() override; AbstractBackendUpdater* backendUpdater() const override; AbstractReviewsBackend* reviewsBackend() const override; QSet resourcesByPackageName(const QString& name) const; ResultsStream* search(const AbstractResourcesBackend::Filters & search) override; - ResultsStream* findResourceByPackageName(const QUrl& search) override; + ResultsStream* findResourceByPackageName(const QUrl& search); int updatesCount() const override; bool hasSecurityUpdates() const override; Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; Transaction* removeApplication(AbstractResource* app) override; bool isValid() const override { return true; } QSet upgradeablePackages() const; bool isFetching() const override; bool isPackageNameUpgradeable(const PackageKitResource* res) const; QString upgradeablePackageId(const PackageKitResource* res) const; QVector extendedBy(const QString& id) const; void clearPackages(const QStringList &packageNames); void resolvePackages(const QStringList &packageNames); void fetchDetails(const QString& pkgid); AbstractResource * resourceForFile(const QUrl & ) override; void checkForUpdates() override; QString displayName() const override; bool hasApplications() const override { return true; } static QString locateService(const QString &filename); QList componentsById(const QString &id) const; public Q_SLOTS: void reloadPackageList(); void refreshDatabase(); private Q_SLOTS: void getPackagesFinished(); void addPackage(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary, bool arch); void addPackageArch(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary); void addPackageNotArch(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary); void packageDetails(const PackageKit::Details& details); void transactionError(PackageKit::Transaction::Error, const QString& message); void addPackageToUpdate(PackageKit::Transaction::Info, const QString& pkgid, const QString& summary); void getUpdatesFinished(PackageKit::Transaction::Exit,uint); void getUpdatesDetailsFinished(PackageKit::Transaction::Exit,uint); private: template T resourcesByPackageNames(const QStringList& names) const; void fetchUpdates(); void checkDaemonRunning(); void acquireFetching(bool f); void includePackagesToAdd(); void performDetailsFetch(); AppPackageKitResource* addComponent(const AppStream::Component& component, const QStringList& pkgNames); AppStream::Pool m_appdata; PackageKitUpdater* m_updater; QPointer m_refresher; int m_isFetching; QSet m_updatesPackageId; bool m_hasSecurityUpdates = false; QSet m_packagesToAdd; QSet m_packagesToDelete; struct Packages { QHash packages; QHash packageToApp; QHash> extendedBy; void clear() { *this = {}; } }; QTimer m_delayedDetailsFetch; QSet m_packageNamesToFetchDetails; Packages m_packages; QSharedPointer m_reviews; }; #endif // PACKAGEKITBACKEND_H diff --git a/libdiscover/backends/PackageKitBackend/PackageKitNotifier.cpp b/libdiscover/backends/PackageKitBackend/PackageKitNotifier.cpp index a8ff468c..c1a0f722 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitNotifier.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitNotifier.cpp @@ -1,316 +1,320 @@ /*************************************************************************** * Copyright © 2013 Lukas Appelhans * * Copyright © 2015 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 "PackageKitNotifier.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pk-offline-private.h" PackageKitNotifier::PackageKitNotifier(QObject* parent) : BackendNotifierModule(parent) , m_securityUpdates(0) , m_normalUpdates(0) { if (PackageKit::Daemon::global()->isRunning()) { recheckSystemUpdateNeeded(); } connect(PackageKit::Daemon::global(), &PackageKit::Daemon::networkStateChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::updatesChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::isRunningChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::transactionListChanged, this, &PackageKitNotifier::transactionListChanged); //Check if there's packages after 5' QTimer::singleShot(5 * 60 * 1000, this, &PackageKitNotifier::refreshDatabase); QTimer *regularCheck = new QTimer(this); regularCheck->setInterval(24 * 60 * 60 * 1000); //refresh at least once every day connect(regularCheck, &QTimer::timeout, this, &PackageKitNotifier::refreshDatabase); const QString aptconfig = QStandardPaths::findExecutable(QStringLiteral("apt-config")); if (!aptconfig.isEmpty()) { auto process = checkAptVariable(aptconfig, QLatin1String("Apt::Periodic::Update-Package-Lists"), [regularCheck](const QStringRef& value) { bool ok; int time = value.toInt(&ok); if (ok && time > 0) regularCheck->setInterval(time * 60 * 60 * 1000); else qWarning() << "couldn't understand value for timer:" << value; }); connect(process, static_cast(&QProcess::finished), regularCheck, static_cast(&QTimer::start)); } else regularCheck->start(); QTimer::singleShot(3000, this, &PackageKitNotifier::checkOfflineUpdates); m_recheckTimer = new QTimer(this); m_recheckTimer->setInterval(200); m_recheckTimer->setSingleShot(true); connect(m_recheckTimer, &QTimer::timeout, this, &PackageKitNotifier::recheckSystemUpdate); } PackageKitNotifier::~PackageKitNotifier() { } void PackageKitNotifier::checkOfflineUpdates() { if (!QFile::exists(QStringLiteral(PK_OFFLINE_RESULTS_FILENAME))) { return; } qDebug() << "found offline update results at " << PK_OFFLINE_RESULTS_FILENAME; KDesktopFile file(QStringLiteral(PK_OFFLINE_RESULTS_FILENAME)); KConfigGroup group(&file, PK_OFFLINE_RESULTS_GROUP); const bool success = group.readEntry("Success", false); const QString packagesJoined = group.readEntry("Packages"); const auto packages = packagesJoined.splitRef(QLatin1Char(',')); if (!success) { const QString errorDetails = group.readEntry("ErrorDetails"); KNotification *notification = new KNotification(QLatin1String("offlineupdate-failed"), KNotification::Persistent | KNotification::DefaultEvent); notification->setIconName(QStringLiteral("error")); notification->setText(i18n("Offline Updates")); notification->setText(i18n("Failed to update %1 packages\n%2", packages.count(), errorDetails)); notification->setActions(QStringList{QLatin1String("Open Discover")}); connect(notification, &KNotification::action1Activated, this, [] () { QProcess::startDetached(QStringLiteral("plasma-discover")); }); notification->sendEvent(); } else { KNotification *notification = new KNotification(QLatin1String("offlineupdate-successful")); notification->setIconName(QStringLiteral("system-software-update")); notification->setTitle(i18n("Offline Updates")); notification->setText(i18n("Successfully updated %1 packages", packages.count())); notification->setActions(QStringList{QLatin1String("Open Discover")}); connect(notification, &KNotification::action1Activated, this, [] () { QProcess::startDetached(QStringLiteral("plasma-discover")); }); notification->sendEvent(); } } void PackageKitNotifier::recheckSystemUpdateNeeded() { m_recheckTimer->start(); } void PackageKitNotifier::recheckSystemUpdate() { if (PackageKit::Daemon::global()->isRunning()) { PackageKit::Daemon::getUpdates(); } } void PackageKitNotifier::setupGetUpdatesTransaction(PackageKit::Transaction* trans) { qDebug() << "using..." << trans << trans->tid().path(); trans->setProperty("normalUpdates", 0); trans->setProperty("securityUpdates", 0); connect(trans, &PackageKit::Transaction::package, this, &PackageKitNotifier::package); connect(trans, &PackageKit::Transaction::finished, this, &PackageKitNotifier::finished); } void PackageKitNotifier::package(PackageKit::Transaction::Info info, const QString &/*packageID*/, const QString &/*summary*/) { PackageKit::Transaction * trans = qobject_cast(sender()); switch (info) { case PackageKit::Transaction::InfoBlocked: break; //skip, we ignore blocked updates case PackageKit::Transaction::InfoSecurity: trans->setProperty("securityUpdates", trans->property("securityUpdates").toInt()+1); break; default: trans->setProperty("normalUpdates", trans->property("normalUpdates").toInt()+1); break; } } void PackageKitNotifier::finished(PackageKit::Transaction::Exit /*exit*/, uint) { const PackageKit::Transaction * trans = qobject_cast(sender()); const uint normalUpdates = trans->property("normalUpdates").toInt(); const uint securityUpdates = trans->property("securityUpdates").toInt(); const bool changed = normalUpdates != m_normalUpdates || securityUpdates != m_securityUpdates; m_normalUpdates = normalUpdates; m_securityUpdates = securityUpdates; if (changed) { Q_EMIT foundUpdates(); } } uint PackageKitNotifier::securityUpdatesCount() { return m_securityUpdates; } uint PackageKitNotifier::updatesCount() { return m_normalUpdates; } void PackageKitNotifier::onDistroUpgrade(PackageKit::Transaction::DistroUpgrade type, const QString& name, const QString& description) { #ifdef PKQT_1_0 KNotification *notification = new KNotification(QLatin1String("distupgrade-notification"), KNotification::Persistent | KNotification::DefaultEvent); notification->setIconName(QStringLiteral("system-software-update")); notification->setActions(QStringList{QLatin1String("Upgrade")}); notification->setTitle(i18n("Upgrade available")); switch(type) { case PackageKit::Transaction::DistroUpgradeUnknown: case PackageKit::Transaction::DistroUpgradeUnstable: notification->setText(i18n("New unstable version: %1", description)); break; case PackageKit::Transaction::DistroUpgradeStable: notification->setText(i18n("New version: %1", description)); break; } connect(notification, &KNotification::action1Activated, this, [name] () { PackageKit::Daemon::upgradeSystem(name, PackageKit::Transaction::UpgradeKindDefault); }); notification->sendEvent(); +#else + Q_UNUSED(type) + Q_UNUSED(name) + Q_UNUSED(description) #endif } void PackageKitNotifier::refreshDatabase() { if (!m_refresher) { m_refresher = PackageKit::Daemon::refreshCache(false); connect(m_refresher.data(), &PackageKit::Transaction::finished, this, [this]() { recheckSystemUpdateNeeded(); }); } #ifdef PKQT_1_0 if (!m_distUpgrades && (PackageKit::Daemon::roles() & PackageKit::Transaction::RoleUpgradeSystem)) { m_distUpgrades = PackageKit::Daemon::getDistroUpgrades(); connect(m_distUpgrades, &PackageKit::Transaction::distroUpgrade, this, &PackageKitNotifier::onDistroUpgrade); } #endif } QProcess* PackageKitNotifier::checkAptVariable(const QString &aptconfig, const QLatin1String& varname, std::function func) { QProcess* process = new QProcess; process->start(aptconfig, {QStringLiteral("dump")}); connect(process, static_cast(&QProcess::finished), this, [func, process, varname](int code) { if (code != 0) return; QRegularExpression rx(QLatin1Char('^') + varname + QStringLiteral(" \"(.*?)\"$")); QTextStream stream(process); QString line; while (stream.readLineInto(&line)) { const auto match = rx.match(line); if (match.hasMatch()) { func(match.capturedRef(1)); } } }); connect(process, static_cast(&QProcess::finished), process, &QObject::deleteLater); return process; } void PackageKitNotifier::transactionListChanged(const QStringList& tids) { for (const auto &tid: tids) { if (m_transactions.contains(tid)) continue; auto t = new PackageKit::Transaction(QDBusObjectPath(tid)); connect(t, &PackageKit::Transaction::roleChanged, this, [this, t]() { if (t->role() == PackageKit::Transaction::RoleGetUpdates) { setupGetUpdatesTransaction(t); } }); connect(t, &PackageKit::Transaction::requireRestart, this, &PackageKitNotifier::onRequireRestart); connect(t, &PackageKit::Transaction::finished, this, [this, t](){ auto restart = t->property("requireRestart"); if (!restart.isNull()) requireRestartNotification(PackageKit::Transaction::Restart(restart.toInt())); m_transactions.remove(t->tid().path()); t->deleteLater(); }); m_transactions.insert(tid, t); } } void PackageKitNotifier::onRequireRestart(PackageKit::Transaction::Restart type, const QString &packageID) { PackageKit::Transaction* t = qobject_cast(sender()); t->setProperty("requireRestart", qMax(t->property("requireRestart").toInt(), type)); qDebug() << "RESTART" << type << "is required for package" << packageID; } void PackageKitNotifier::requireRestartNotification(PackageKit::Transaction::Restart type) { if (type < PackageKit::Transaction::RestartSession) { return; } KNotification *notification = new KNotification(QLatin1String("notification"), KNotification::Persistent | KNotification::DefaultEvent); notification->setIconName(QStringLiteral("system-software-update")); if (type == PackageKit::Transaction::RestartSystem || type == PackageKit::Transaction::RestartSecuritySystem) { notification->setActions(QStringList{QLatin1String("Restart")}); notification->setTitle(i18n("Restart is required")); notification->setText(i18n("The system needs to be restarted for the updates to take effect.")); } else { notification->setActions(QStringList{QLatin1String("Logout")}); notification->setTitle(i18n("Session restart is required")); notification->setText(i18n("You will need to log out and back in for the update to take effect.")); } connect(notification, &KNotification::action1Activated, this, [type] () { QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"), QDBusConnection::sessionBus()); if (type == PackageKit::Transaction::RestartSystem) { interface.asyncCall(QStringLiteral("logout"), 0, 1, 2); // Options: do not ask again | reboot | force } else { interface.asyncCall(QStringLiteral("logout"), 0, 0, 2); // Options: do not ask again | logout | force } }); notification->sendEvent(); } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp index 5549db56..fe7993dc 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp @@ -1,266 +1,266 @@ /*************************************************************************** * 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 PackageKitResource::PackageKitResource(QString packageName, QString summary, PackageKitBackend* parent) : AbstractResource(parent) , m_summary(std::move(summary)) , m_name(std::move(packageName)) , m_dependenciesCount(0) { setObjectName(m_name); } 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(uint 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()) backend()->resourcesChanged(this, {"size", "homepage", "license"}); } } void PackageKitResource::fetchChangelog() { 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) { QStringList ret; foreach(const QString& pkgid, pkgids) { ret += i18nc("package-name (version)", "%1 (%2)", PackageKit::Daemon::packageName(pkgid), 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 QString& /*changelog*/, PackageKit::Transaction::UpdateState state, const QDateTime& /*issued*/, const QDateTime& /*updated*/) { QString info; addIfNotEmpty(i18n("Reason:"), updateText, info); addIfNotEmpty(i18n("Obsoletes:"), joinPackages(obsoletes), info); addIfNotEmpty(i18n("Updates:"), joinPackages(updates), 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(info); } PackageKitBackend* PackageKitResource::backend() const { return qobject_cast(parent()); } QString PackageKitResource::sizeDescription() { 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"); } diff --git a/libdiscover/backends/SnapBackend/SnapBackend.h b/libdiscover/backends/SnapBackend/SnapBackend.h index ed569c01..44d116f3 100644 --- a/libdiscover/backends/SnapBackend/SnapBackend.h +++ b/libdiscover/backends/SnapBackend/SnapBackend.h @@ -1,71 +1,71 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef SNAPBACKEND_H #define SNAPBACKEND_H #include #include #include #include class QAction; class SnapReviewsBackend; class StandardBackendUpdater; class SnapResource; class SnapBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit SnapBackend(QObject* parent = nullptr); ResultsStream * search(const AbstractResourcesBackend::Filters & search) override; - ResultsStream * findResourceByPackageName(const QUrl& search) override; + ResultsStream * findResourceByPackageName(const QUrl& search); QString displayName() const override; int updatesCount() const override; AbstractBackendUpdater* backendUpdater() const override; AbstractReviewsBackend* reviewsBackend() const override; bool isValid() const override { return m_valid; } 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; } void checkForUpdates() override {} bool hasApplications() const override { return true; } QSnapdClient* client() { return &m_client; } void refreshStates(); private: void setFetching(bool fetching); template ResultsStream* populate(T* snaps); QHash m_resources; StandardBackendUpdater* m_updater; SnapReviewsBackend* m_reviews; bool m_valid = true; bool m_fetching = false; QSnapdClient m_client; }; #endif // SNAPBACKEND_H diff --git a/libdiscover/resources/AbstractResourcesBackend.h b/libdiscover/resources/AbstractResourcesBackend.h index 5cb130be..48ddffd1 100644 --- a/libdiscover/resources/AbstractResourcesBackend.h +++ b/libdiscover/resources/AbstractResourcesBackend.h @@ -1,258 +1,256 @@ /*************************************************************************** * 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 ABSTRACTRESOURCESBACKEND_H #define ABSTRACTRESOURCESBACKEND_H #include #include #include #include "AbstractResource.h" #include "Transaction/AddonList.h" #include "discovercommon_export.h" class Transaction; class Category; class AbstractReviewsBackend; class AbstractBackendUpdater; class DISCOVERCOMMON_EXPORT ResultsStream : public QObject { Q_OBJECT public: ResultsStream(const QString &objectName); /// assumes all the information is in @p resources ResultsStream(const QString &objectName, const QVector& resources); ~ResultsStream() override; void finish(); Q_SIGNALS: void resourcesFound(const QVector& resources); }; /** * \class AbstractResourcesBackend AbstractResourcesBackend.h "AbstractResourcesBackend.h" * * \brief This is the base class of all resource backends. * * For writing basic new resource backends, we need to implement two classes: this and the * AbstractResource one. Basic questions on how to build your plugin with those classes * can be answered by looking at the dummy plugin. * * As this is the base class of a backend, we save all the created resources here and also * accept calls to install and remove applications or to cancel transactions. * * To show resources in Muon, we need to initialize all resources we want to show beforehand, * we should not create resources in the search function. When we reload the resources * (e.g. when initializing), the backend needs change the fetching property throughout the * processs. */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackend : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(AbstractReviewsBackend* reviewsBackend READ reviewsBackend CONSTANT) Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(bool hasApplications READ hasApplications CONSTANT) public: /** * Constructs an AbstractResourcesBackend * @param parent the parent of the class (the object will be deleted when the parent gets deleted) */ explicit AbstractResourcesBackend(QObject* parent = nullptr); /** * @returns true when the backend is in a valid state, which means it is able to work * You must return true here if you want the backend to be loaded. */ virtual bool isValid() const = 0; struct Filters { Category* category = nullptr; AbstractResource::State state = AbstractResource::Broken; QString mimetype; QString search; QString extends; QUrl resourceUrl; QString origin; bool allBackends = false; bool isEmpty() const { return !category && state == AbstractResource::Broken && mimetype.isEmpty() && search.isEmpty() && extends.isEmpty() && resourceUrl.isEmpty() && origin.isEmpty(); } bool shouldFilter(AbstractResource* res) const; void filterJustInCase(QVector& input) const; }; /** * @returns a stream that will provide elements that match the search */ virtual ResultsStream* search(const Filters &search) = 0;//FIXME: Probably provide a standard implementation?! - virtual ResultsStream* findResourceByPackageName(const QUrl &search) = 0;//FIXME: Probably provide a standard implementation?! - /** * @returns the reviews backend of this AbstractResourcesBackend (which handles all ratings and reviews of resources) */ virtual AbstractReviewsBackend* reviewsBackend() const = 0;//FIXME: Have a standard impl which returns 0? /** * @returns the class which is used by muon to update the users system, if you are unsure what to do * just return the StandardBackendUpdater */ virtual AbstractBackendUpdater* backendUpdater() const = 0;//FIXME: Standard impl returning the standard updater? /** * @returns the number of resources for which an update is available, it should only count technical packages */ virtual int updatesCount() const = 0;//FIXME: Probably provide a standard implementation?! /** * @returns whether either of the updates contains a security fix */ virtual bool hasSecurityUpdates() const { return false; } /** * Tells whether the backend is fetching resources */ virtual bool isFetching() const = 0; /** * @returns the appstream ids that this backend extends */ virtual QStringList extends() const; /** @returns the plugin's name */ QString name() const; /** @internal only to be used by the factory */ void setName(const QString& name); virtual QString displayName() const = 0; /** * emits a change for all rating properties */ void emitRatingsReady(); virtual AbstractResource* resourceForFile(const QUrl &/*url*/) { return nullptr; } /** * @returns the root category tree */ virtual QVector category() const { return {}; } virtual bool hasApplications() const { return false; } public Q_SLOTS: /** * This gets called when the backend should install an application. * The AbstractResourcesBackend should create a Transaction object, is returned and * will be included in the TransactionModel * @param app the application to be installed * @param addons the addons which should be installed with the application * @returns the Transaction that keeps track of the installation process */ virtual Transaction* installApplication(AbstractResource *app, const AddonList& addons) = 0; /** * Overloaded function, which simply does the same, except not installing any addons. */ virtual Transaction* installApplication(AbstractResource *app); /** * This gets called when the backend should remove an application. * Like in the installApplication() method, we'll return the Transaction * responsible for the removal. * * @see installApplication * @param app the application to be removed * @returns the Transaction that keeps track of the removal process */ virtual Transaction* removeApplication(AbstractResource *app) = 0; /** * Notifies the backend that the user wants the information to be up to date */ virtual void checkForUpdates() = 0; Q_SIGNALS: /** * Notify of a change in the backend */ void fetchingChanged(); /** * This should be emitted when the number of upgradeable packages changed. */ void updatesCountChanged(); /** * This should be emitted when all data of the backends resources changed. Internally it will emit * a signal in the model to show the view that all data of a certain backend changed. */ void allDataChanged(const QVector &propertyNames); /** * Allows to notify some @p properties in @p resource have changed */ void resourcesChanged(AbstractResource* resource, const QVector &properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); private: QString m_name; }; DISCOVERCOMMON_EXPORT QDebug operator<<(QDebug dbg, const AbstractResourcesBackend::Filters& filters); /** * @internal Workaround because QPluginLoader enforces 1 instance per plugin */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackendFactory : public QObject { Q_OBJECT public: virtual QVector newInstance(QObject* parent, const QString &name) const = 0; }; #define DISCOVER_BACKEND_PLUGIN(ClassName)\ class ClassName##Factory : public AbstractResourcesBackendFactory {\ Q_OBJECT\ Q_PLUGIN_METADATA(IID "org.kde.muon.AbstractResourcesBackendFactory")\ Q_INTERFACES(AbstractResourcesBackendFactory)\ public:\ QVector newInstance(QObject* parent, const QString &name) const override {\ auto c = new ClassName(parent);\ c->setName(name);\ return {c};\ }\ }; Q_DECLARE_INTERFACE( AbstractResourcesBackendFactory, "org.kde.muon.AbstractResourcesBackendFactory" ) #endif // ABSTRACTRESOURCESBACKEND_H diff --git a/libdiscover/resources/ResourcesModel.cpp b/libdiscover/resources/ResourcesModel.cpp index e58c4bea..91e481af 100644 --- a/libdiscover/resources/ResourcesModel.cpp +++ b/libdiscover/resources/ResourcesModel.cpp @@ -1,396 +1,390 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "ResourcesModel.h" #include "AbstractResource.h" #include "resources/AbstractResourcesBackend.h" #include "resources/AbstractBackendUpdater.h" #include #include #include #include #include "Transaction/TransactionModel.h" #include "Category/CategoryModel.h" #include "utils.h" #include #include #include #include #include #include #include #include ResourcesModel *ResourcesModel::s_self = nullptr; ResourcesModel *ResourcesModel::global() { if(!s_self) s_self = new ResourcesModel; return s_self; } ResourcesModel::ResourcesModel(QObject* parent, bool load) : QObject(parent) , m_isFetching(false) , m_initializingBackends(0) , m_currentApplicationBackend(nullptr) { init(load); connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching); connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend); } void ResourcesModel::init(bool load) { Q_ASSERT(!s_self); Q_ASSERT(QCoreApplication::instance()->thread()==QThread::currentThread()); if(load) QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update"))); m_updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates")); m_updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) { m_updateAction->setEnabled(!fetching); }); connect(m_updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); } ResourcesModel::ResourcesModel(const QString& backendName, QObject* parent) : ResourcesModel(parent, false) { s_self = this; registerBackendByName(backendName); } ResourcesModel::~ResourcesModel() { s_self = nullptr; qDeleteAll(m_backends); } void ResourcesModel::addResourcesBackend(AbstractResourcesBackend* backend) { Q_ASSERT(!m_backends.contains(backend)); if(!backend->isValid()) { qWarning() << "Discarding invalid backend" << backend->name(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); return; } m_backends += backend; if(!backend->isFetching()) { if (backend->updatesCount() > 0) emit updatesCountChanged(); } else { m_initializingBackends++; } connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged); connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller); connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged); connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, &ResourcesModel::updatesCountChanged); connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved); connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage); connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching); if(m_initializingBackends==0) emit allInitialized(); else slotFetching(); } void ResourcesModel::callerFetchingChanged() { AbstractResourcesBackend* backend = qobject_cast(sender()); if (!backend->isValid()) { qWarning() << "Discarding invalid backend" << backend->name(); int idx = m_backends.indexOf(backend); Q_ASSERT(idx>=0); m_backends.removeAt(idx); Q_EMIT backendsChanged(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); return; } if(backend->isFetching()) { m_initializingBackends++; slotFetching(); } else { m_initializingBackends--; if(m_initializingBackends==0) emit allInitialized(); else slotFetching(); } } void ResourcesModel::updateCaller(const QVector& properties) { AbstractResourcesBackend* backend = qobject_cast(sender()); Q_EMIT backendDataChanged(backend, properties); } QVector< AbstractResourcesBackend* > ResourcesModel::backends() const { return m_backends; } int ResourcesModel::updatesCount() const { int ret = 0; foreach(AbstractResourcesBackend* backend, m_backends) { ret += backend->updatesCount(); } return ret; } bool ResourcesModel::hasSecurityUpdates() const { bool ret = false; foreach(AbstractResourcesBackend* backend, m_backends) { ret |= backend->hasSecurityUpdates(); } return ret; } void ResourcesModel::installApplication(AbstractResource* app) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app)); } void ResourcesModel::installApplication(AbstractResource* app, const AddonList& addons) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons)); } void ResourcesModel::removeApplication(AbstractResource* app) { TransactionModel::global()->addTransaction(app->backend()->removeApplication(app)); } void ResourcesModel::registerAllBackends() { DiscoverBackendsFactory f; const auto backends = f.allBackends(); if(m_initializingBackends==0 && backends.isEmpty()) { qWarning() << "Couldn't find any backends"; emit allInitialized(); } else { foreach(AbstractResourcesBackend* b, backends) { addResourcesBackend(b); } emit backendsChanged(); } } void ResourcesModel::registerBackendByName(const QString& name) { DiscoverBackendsFactory f; for(auto b : f.backend(name)) addResourcesBackend(b); emit backendsChanged(); } bool ResourcesModel::isFetching() const { return m_isFetching; } void ResourcesModel::slotFetching() { bool newFetching = false; foreach(AbstractResourcesBackend* b, m_backends) { // isFetching should sort of be enough. However, sometimes the backend itself // will still be operating on things, which from a model point of view would // still mean something going on. So, interpret that as fetching as well, for // the purposes of this property. if(b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) { newFetching = true; break; } } if (newFetching != m_isFetching) { m_isFetching = newFetching; Q_EMIT fetchingChanged(m_isFetching); } } bool ResourcesModel::isBusy() const { return TransactionModel::global()->rowCount() > 0; } bool ResourcesModel::isExtended(const QString& id) { bool ret = true; foreach (AbstractResourcesBackend* backend, m_backends) { ret = backend->extends().contains(id); if (ret) break; } return ret; } AggregatedResultsStream::AggregatedResultsStream(const QSet& streams) : ResultsStream(QStringLiteral("AggregatedResultsStream")) { Q_ASSERT(!streams.contains(nullptr)); if (streams.isEmpty()) { qWarning() << "no streams to aggregate!!"; QTimer::singleShot(0, this, &AggregatedResultsStream::clear); } for (auto stream: streams) { connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults); connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::destruction); m_streams << stream; } m_delayedEmission.setInterval(0); connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults); } void AggregatedResultsStream::addResults(const QVector& res) { m_results += res; m_delayedEmission.start(); } void AggregatedResultsStream::emitResults() { if (!m_results.isEmpty()) { Q_EMIT resourcesFound(m_results); m_results.clear(); } m_delayedEmission.setInterval(m_delayedEmission.interval() + 100); m_delayedEmission.stop(); } void AggregatedResultsStream::destruction(QObject* obj) { m_streams.remove(obj); clear(); } void AggregatedResultsStream::clear() { if (m_streams.isEmpty()) { emitResults(); Q_EMIT finished(); deleteLater(); } } -AggregatedResultsStream * ResourcesModel::findResourceByPackageName(const QUrl& search) -{ - auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend* backend){ return backend->findResourceByPackageName(search); }); - return new AggregatedResultsStream(streams); -} - AggregatedResultsStream* ResourcesModel::search(const AbstractResourcesBackend::Filters& search) { if (search.isEmpty()) { return new AggregatedResultsStream ({new ResultsStream(QStringLiteral("emptysearch"), {})}); } auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend* backend){ return backend->search(search); }); return new AggregatedResultsStream(streams); } AbstractResource* ResourcesModel::resourceForFile(const QUrl& file) { AbstractResource* ret = nullptr; foreach(auto backend, m_backends) { ret = backend->resourceForFile(file); if (ret) break; } return ret; } void ResourcesModel::checkForUpdates() { for(auto backend: qAsConst(m_backends)) backend->checkForUpdates(); } QVector ResourcesModel::applicationBackends() const { return kFilter>(m_backends, [](AbstractResourcesBackend* b){ return b->hasApplications(); }); } QVariantList ResourcesModel::applicationBackendsVariant() const { return kTransform(applicationBackends(), [](AbstractResourcesBackend* b) {return QVariant::fromValue(b);}); } AbstractResourcesBackend* ResourcesModel::currentApplicationBackend() const { return m_currentApplicationBackend; } void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool write) { if (backend != m_currentApplicationBackend) { if (write) { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); if (backend) settings.writeEntry("currentApplicationBackend", backend->name()); else settings.deleteEntry("currentApplicationBackend"); } qDebug() << "setting currentApplicationBackend" << backend; m_currentApplicationBackend = backend; Q_EMIT currentApplicationBackendChanged(backend); } } void ResourcesModel::initApplicationsBackend() { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); const QString name = settings.readEntry("currentApplicationBackend", QStringLiteral("packagekit-backend")); const auto backends = applicationBackends(); auto idx = kIndexOf(backends, [name](AbstractResourcesBackend* b) { return b->name() == name; }); if (idx<0) { idx = kIndexOf(backends, [](AbstractResourcesBackend* b) { return b->hasApplications(); }); qDebug() << "falling back applications backend to" << idx; } setCurrentApplicationBackend(backends.value(idx, nullptr), false); } diff --git a/libdiscover/resources/ResourcesModel.h b/libdiscover/resources/ResourcesModel.h index 71befca4..947340e1 100644 --- a/libdiscover/resources/ResourcesModel.h +++ b/libdiscover/resources/ResourcesModel.h @@ -1,130 +1,129 @@ /*************************************************************************** * 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 RESOURCESMODEL_H #define RESOURCESMODEL_H #include #include #include #include "discovercommon_export.h" #include "AbstractResourcesBackend.h" class QAction; class DISCOVERCOMMON_EXPORT AggregatedResultsStream : public ResultsStream { Q_OBJECT public: AggregatedResultsStream(const QSet& streams); Q_SIGNALS: void finished(); private: void addResults(const QVector& res); void emitResults(); void destruction(QObject* obj); void clear(); QSet m_streams; QVector m_results; QTimer m_delayedEmission; }; class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject { Q_OBJECT Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(QVariantList applicationBackends READ applicationBackendsVariant NOTIFY backendsChanged) Q_PROPERTY(AbstractResourcesBackend* currentApplicationBackend READ currentApplicationBackend WRITE setCurrentApplicationBackend NOTIFY currentApplicationBackendChanged) Q_PROPERTY(QAction* updateAction READ updateAction CONSTANT) public: /** This constructor should be only used by unit tests. * @p backendName defines what backend will be loaded when the backend is constructed. */ explicit ResourcesModel(const QString& backendName, QObject* parent = nullptr); static ResourcesModel* global(); ~ResourcesModel() override; QVector< AbstractResourcesBackend* > backends() const; int updatesCount() const; bool hasSecurityUpdates() const; bool isBusy() const; bool isFetching() const; Q_SCRIPTABLE bool isExtended(const QString &id); - AggregatedResultsStream* findResourceByPackageName(const QUrl& search); AggregatedResultsStream* search(const AbstractResourcesBackend::Filters &search); AbstractResource* resourceForFile(const QUrl &/*url*/); void checkForUpdates(); QVariantList applicationBackendsVariant() const; QVector applicationBackends() const; void setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool writeConfig = true); AbstractResourcesBackend* currentApplicationBackend() const; QAction* updateAction() const { return m_updateAction; } public Q_SLOTS: void installApplication(AbstractResource* app, const AddonList& addons); void installApplication(AbstractResource* app); void removeApplication(AbstractResource* app); Q_SIGNALS: void fetchingChanged(bool isFetching); void allInitialized(); void backendsChanged(); void updatesCountChanged(); void backendDataChanged(AbstractResourcesBackend* backend, const QVector& properties); void resourceDataChanged(AbstractResource* resource, const QVector& properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); void currentApplicationBackendChanged(AbstractResourcesBackend* currentApplicationBackend); private Q_SLOTS: void callerFetchingChanged(); void updateCaller(const QVector& properties); void registerAllBackends(); private: ///@p initialize tells if all backends load will be triggered on construction explicit ResourcesModel(QObject* parent=nullptr, bool load = true); void init(bool load); void addResourcesBackend(AbstractResourcesBackend* backend); void registerBackendByName(const QString& name); void initApplicationsBackend(); void slotFetching(); bool m_isFetching; QVector< AbstractResourcesBackend* > m_backends; int m_initializingBackends; QAction* m_updateAction = nullptr; AbstractResourcesBackend* m_currentApplicationBackend; static ResourcesModel* s_self; }; #endif // RESOURCESMODEL_H