diff --git a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp index 771a7a4d..e0f51bf0 100644 --- a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp @@ -1,156 +1,157 @@ /*************************************************************************** * Copyright © 2014 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdSourcesBackend.h" #include #include #include class FwupdSourcesModel : public QStandardItemModel { Q_OBJECT public: FwupdSourcesModel(FwupdSourcesBackend* backend) : QStandardItemModel(backend) , m_backend(backend) {} QHash roleNames() const override { auto roles = QStandardItemModel::roleNames(); roles[Qt::CheckStateRole] = "checked"; return roles; } bool setData(const QModelIndex & index, const QVariant & value, int role) override { auto item = itemFromIndex(index); if(!item) return false; remote = fwupd_client_get_remote_by_id(m_backend->backend->client, item->data(AbstractSourcesBackend::IdRole).toString().toUtf8().constData(),nullptr,nullptr); status = fwupd_remote_get_enabled(remote); switch(role) { case Qt::CheckStateRole: { if((value.toInt() == Qt::Checked) ) { - #if FWUPD_CHECK_VERSION(1,0,7) - m_backend->eulaRequired(QString::fromUtf8(fwupd_remote_get_title(remote)),QString::fromUtf8(fwupd_remote_get_agreement(remote))); - #endif - connect(m_backend,&FwupdSourcesBackend::proceed,this, - [=]() - { - if(fwupd_client_modify_remote(m_backend->backend->client,fwupd_remote_get_id(remote),QString(QLatin1String("Enabled")).toUtf8().constData(),(QString(QLatin1String("true")).toUtf8().constData()),nullptr,nullptr)) - item->setData(value, role); - } - ); + auto proceedFunction = [=]() + { + if(fwupd_client_modify_remote(m_backend->backend->client, fwupd_remote_get_id(remote), "Enabled", "true", nullptr, nullptr)) + item->setData(value, role); + }; +#if FWUPD_CHECK_VERSION(1,0,7) + m_backend->eulaRequired(QString::fromUtf8(fwupd_remote_get_title(remote)),QString::fromUtf8(fwupd_remote_get_agreement(remote))); +#else + proceedFunction(); +#endif + connect(m_backend,&FwupdSourcesBackend::proceed,this, proceedFunction); connect(m_backend,&FwupdSourcesBackend::cancel,this, [=]() { item->setCheckState(Qt::Unchecked); Q_EMIT dataChanged(index,index,{}); return false; } ); } else if(value.toInt() == Qt::Unchecked) { if(fwupd_client_modify_remote(m_backend->backend->client,fwupd_remote_get_id(remote),QString(QLatin1String("Enabled")).toUtf8().constData(),(QString(QLatin1String("false")).toUtf8().constData()),nullptr,nullptr)) item->setData(value, role); } } } Q_EMIT dataChanged(index,index,{}); return true; } private: FwupdSourcesBackend* m_backend; FwupdRemote* remote; bool status; }; FwupdSourcesBackend::FwupdSourcesBackend(AbstractResourcesBackend * parent) : AbstractSourcesBackend(parent) , backend(qobject_cast(parent)) , m_sources(new FwupdSourcesModel(this)) { populateSources(); } void FwupdSourcesBackend::populateSources() { /* find all remotes */ g_autoptr(GPtrArray) remotes = fwupd_client_get_remotes(backend->client,nullptr,nullptr); if(remotes != nullptr) { for(uint i = 0; i < remotes->len; i++) { FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i); if(fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL) continue; const QString id = QString::fromUtf8(fwupd_remote_get_id(remote)); if(id.isEmpty()) continue; bool status = !fwupd_remote_get_enabled(remote); QStandardItem* it = new QStandardItem(id); it->setData(id, AbstractSourcesBackend::IdRole); it->setData(QVariant(QString::fromUtf8(fwupd_remote_get_title(remote))), Qt::ToolTipRole); it->setCheckable(true); it->setCheckState(status ? Qt::Unchecked : Qt::Checked); m_sources->appendRow(it); } } } QAbstractItemModel* FwupdSourcesBackend::sources() { return m_sources; } void FwupdSourcesBackend::eulaRequired( const QString& remoteName, const QString& licenseAgreement) { Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The remote %1 require that you accept their license:\n %2", remoteName, licenseAgreement)); } bool FwupdSourcesBackend::addSource(const QString& id) { qWarning() << "Fwupd Error: Custom Addition of Sources Not Allowed" << "Remote-ID" << id; return false; } bool FwupdSourcesBackend::removeSource(const QString& id) { qWarning() << "Fwupd Error: Removal of Sources Not Allowed" << "Remote-ID" << id; return false; } QList FwupdSourcesBackend::actions() const { return {} ; } #include "FwupdSourcesBackend.moc" diff --git a/libdiscover/backends/KNSBackend/KNSBackend.cpp b/libdiscover/backends/KNSBackend/KNSBackend.cpp index 77de5fd9..a28b7592 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.cpp +++ b/libdiscover/backends/KNSBackend/KNSBackend.cpp @@ -1,461 +1,468 @@ /*************************************************************************** * 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 . * ***************************************************************************/ // Qt includes #include #include #include #include #include #include // Attica includes #include #include // KDE includes #include #include #include #include #include #include // DiscoverCommon includes #include "Transaction/Transaction.h" #include "Transaction/TransactionModel.h" #include "Category/Category.h" // Own includes #include "KNSBackend.h" #include "KNSResource.h" #include "KNSReviews.h" #include #include "utils.h" class KNSBackendFactory : public AbstractResourcesBackendFactory { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.muon.AbstractResourcesBackendFactory") Q_INTERFACES(AbstractResourcesBackendFactory) public: KNSBackendFactory() { connect(KNSCore::QuestionManager::instance(), &KNSCore::QuestionManager::askQuestion, this, [](KNSCore::Question* q) { qWarning() << q->question() << q->questionType(); q->setResponse(KNSCore::Question::InvalidResponse); }); } QVector newInstance(QObject* parent, const QString &/*name*/) const override { QVector ret; for (const QString &path: QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation)) { QDirIterator dirIt(path, {QStringLiteral("*.knsrc")}, QDir::Files); for(; dirIt.hasNext(); ) { dirIt.next(); auto bk = new KNSBackend(parent, QStringLiteral("plasma"), dirIt.filePath()); if (bk->isValid()) ret += bk; else delete bk; } } return ret; } }; Q_DECLARE_METATYPE(KNSCore::EntryInternal) KNSBackend::KNSBackend(QObject* parent, const QString& iconName, const QString &knsrc) : AbstractResourcesBackend(parent) , m_fetching(false) , m_isValid(true) , m_reviews(new KNSReviews(this)) , m_name(knsrc) , m_iconName(iconName) , m_updater(new StandardBackendUpdater(this)) { const QString fileName = QFileInfo(m_name).fileName(); setName(fileName); setObjectName(knsrc); const KConfig conf(m_name); if (!conf.hasGroup("KNewStuff3")) { markInvalid(QStringLiteral("Config group not found! Check your KNS3 installation.")); return; } m_categories = QStringList{ fileName }; const KConfigGroup group = conf.group("KNewStuff3"); m_extends = group.readEntry("Extends", QStringList()); m_reviews->setProviderUrl(QUrl(group.readEntry("ProvidersUrl", QString()))); setFetching(true); m_engine = new KNSCore::Engine(this); m_engine->init(m_name); m_engine->setPageSize(100); // Setting setFetching to false when we get an error ensures we don't end up in an eternally-fetching state connect(m_engine, &KNSCore::Engine::signalError, this, [this](const QString &_error) { QString error = _error; bool invalidFile = false; if(error == QLatin1Literal("All categories are missing")) { markInvalid(error); error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); invalidFile = true; } m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); this->setFetching(false); qWarning() << "kns error" << objectName() << error; if (!invalidFile) Q_EMIT passiveMessage(i18n("%1: %2", name(), error)); }); connect(m_engine, &KNSCore::Engine::signalEntriesLoaded, this, &KNSBackend::receivedEntries, Qt::QueuedConnection); connect(m_engine, &KNSCore::Engine::signalEntryChanged, this, &KNSBackend::statusChanged, Qt::QueuedConnection); connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, this, &KNSBackend::statusChanged); connect(m_engine, &KNSCore::Engine::signalProvidersLoaded, this, &KNSBackend::fetchInstalled); const QVector> filters = { {CategoryFilter, fileName } }; const QSet backendName = { name() }; m_displayName = group.readEntry("Name", QString()); if (m_displayName.isEmpty()) { m_displayName = fileName.mid(0, fileName.indexOf(QLatin1Char('.'))); m_displayName[0] = m_displayName[0].toUpper(); } static const QSet knsrcPlasma = { QStringLiteral("aurorae.knsrc"), QStringLiteral("icons.knsrc"), QStringLiteral("kfontinst.knsrc"), QStringLiteral("lookandfeel.knsrc"), QStringLiteral("plasma-themes.knsrc"), QStringLiteral("plasmoids.knsrc"), QStringLiteral("wallpaper.knsrc"), QStringLiteral("xcursor.knsrc"), QStringLiteral("cgcgtk3.knsrc"), QStringLiteral("cgcicon.knsrc"), QStringLiteral("cgctheme.knsrc"), //GTK integration QStringLiteral("kwinswitcher.knsrc"), QStringLiteral("kwineffect.knsrc"), QStringLiteral("kwinscripts.knsrc"), //KWin QStringLiteral("comic.knsrc"), QStringLiteral("colorschemes.knsrc"), QStringLiteral("emoticons.knsrc"), QStringLiteral("plymouth.knsrc"), QStringLiteral("sddmtheme.knsrc") }; auto actualCategory = new Category(m_displayName, QStringLiteral("plasma"), filters, backendName, {}, QUrl(), true); const auto topLevelName = knsrcPlasma.contains(fileName)? i18n("Plasma Addons") : i18n("Application Addons"); const QUrl decoration(knsrcPlasma.contains(fileName)? QStringLiteral("https://c2.staticflickr.com/4/3148/3042248532_20bd2e38f4_b.jpg") : QStringLiteral("https://c2.staticflickr.com/8/7067/6847903539_d9324dcd19_o.jpg")); auto addonsCategory = new Category(topLevelName, QStringLiteral("plasma"), filters, backendName, {actualCategory}, decoration, true); m_rootCategories = { addonsCategory }; } KNSBackend::~KNSBackend() { qDeleteAll(m_rootCategories); } void KNSBackend::markInvalid(const QString &message) { qWarning() << "invalid kns backend!" << m_name << "because:" << message; m_isValid = false; setFetching(false); Q_EMIT initialized(); } void KNSBackend::fetchInstalled() { auto search = new OneTimeAction([this]() { Q_EMIT startingSearch(); m_onePage = true; m_responsePending = true; m_engine->checkForInstalled(); }, this); if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, search, &OneTimeAction::trigger, Qt::QueuedConnection); } else { search->trigger(); } } void KNSBackend::setFetching(bool f) { if(m_fetching!=f) { m_fetching = f; emit fetchingChanged(); if (!m_fetching) { Q_EMIT initialized(); } } } bool KNSBackend::isValid() const { return m_isValid; } KNSResource* KNSBackend::resourceForEntry(const KNSCore::EntryInternal& entry) { KNSResource* r = static_cast(m_resourcesByName.value(entry.uniqueId())); if (!r) { r = new KNSResource(entry, m_categories, this); m_resourcesByName.insert(entry.uniqueId(), r); } else { r->setEntry(entry); } return r; } void KNSBackend::receivedEntries(const KNSCore::EntryInternal::List& entries) { m_responsePending = false; const auto resources = kTransform>(entries, [this](const KNSCore::EntryInternal& entry){ return resourceForEntry(entry); }); if (!resources.isEmpty()) { Q_EMIT receivedResources(resources); } else { Q_EMIT searchFinished(); Q_EMIT availableForQueries(); setFetching(false); return; } // qDebug() << "received" << objectName() << this << m_resourcesByName.count(); if (m_onePage) { Q_EMIT availableForQueries(); setFetching(false); } } void KNSBackend::fetchMore() { if (m_responsePending) return; // We _have_ to set this first. If we do not, we may run into a situation where the // data request will conclude immediately, causing m_responsePending to remain true // for perpetuity as the slots will be called before the function returns. m_responsePending = true; m_engine->requestMoreData(); } void KNSBackend::statusChanged(const KNSCore::EntryInternal& entry) { resourceForEntry(entry); } class KNSTransaction : public Transaction { public: KNSTransaction(QObject* parent, KNSResource* res, Transaction::Role role) : Transaction(parent, res, role) , m_id(res->entry().uniqueId()) { setCancellable(false); auto manager = res->knsBackend()->engine(); connect(manager, &KNSCore::Engine::signalEntryChanged, this, &KNSTransaction::anEntryChanged); TransactionModel::global()->addTransaction(this); - QTimer::singleShot(0, this, [this, res]() { - for(auto i : res->linkIds()) { - auto engine = res->knsBackend()->engine(); - if (this->role() == InstallRole) + std::function actionFunction; + auto engine = res->knsBackend()->engine(); + if(role == RemoveRole) + actionFunction = [res, engine]() { + engine->uninstall(res->entry()); + }; + else if (res->linkIds().isEmpty()) + actionFunction = [res, engine]() { + engine->install(res->entry()); + }; + else + actionFunction = [res, engine]() { + for(auto i : res->linkIds()) engine->install(res->entry(), i); - else if(this->role() == RemoveRole) - engine->uninstall(res->entry()); - } - }); + }; + QTimer::singleShot(0, res, actionFunction); } void anEntryChanged(const KNSCore::EntryInternal& entry) { if (entry.uniqueId() == m_id) { switch (entry.status()) { case KNS3::Entry::Invalid: qWarning() << "invalid status for" << entry.uniqueId() << entry.status(); break; case KNS3::Entry::Installing: case KNS3::Entry::Updating: setStatus(CommittingStatus); break; case KNS3::Entry::Downloadable: case KNS3::Entry::Installed: case KNS3::Entry::Deleted: case KNS3::Entry::Updateable: if (status() != DoneStatus) { setStatus(DoneStatus); } break; } } } void cancel() override {} private: const QString m_id; }; Transaction* KNSBackend::removeApplication(AbstractResource* app) { auto res = qobject_cast(app); return new KNSTransaction(this, res, Transaction::RemoveRole); } Transaction* KNSBackend::installApplication(AbstractResource* app) { auto res = qobject_cast(app); return new KNSTransaction(this, res, Transaction::InstallRole); } Transaction* KNSBackend::installApplication(AbstractResource* app, const AddonList& /*addons*/) { return installApplication(app); } int KNSBackend::updatesCount() const { return m_updater->updatesCount(); } AbstractReviewsBackend* KNSBackend::reviewsBackend() const { return m_reviews; } static ResultsStream* voidStream() { return new ResultsStream(QStringLiteral("KNS-void"), {}); } ResultsStream* KNSBackend::search(const AbstractResourcesBackend::Filters& filter) { if (!m_isValid || (!filter.resourceUrl.isEmpty() && filter.resourceUrl.scheme() != QLatin1String("kns")) || !filter.mimetype.isEmpty()) return voidStream(); if (filter.resourceUrl.scheme() == QLatin1String("kns")) { return findResourceByPackageName(filter.resourceUrl); } else if (filter.state >= AbstractResource::Installed) { auto stream = new ResultsStream(QStringLiteral("KNS-installed")); const auto start = [this, stream, filter]() { if (m_isValid) { auto filterFunction = [&filter](AbstractResource* r) { return r->state()>=filter.state && (r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)); }; const auto ret = kFilter>(m_resourcesByName, filterFunction); if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); } stream->finish(); }; if (isFetching()) { connect(this, &KNSBackend::initialized, stream, start); } else { QTimer::singleShot(0, stream, start); } return stream; } else if ((!filter.category && !filter.search.isEmpty()) || (filter.category && filter.category->matchesCategoryName(m_categories.constFirst()))) { auto r = new ResultsStream(QStringLiteral("KNS-search-")+name()); searchStream(r, filter.search); return r; } return voidStream(); } void KNSBackend::searchStream(ResultsStream* stream, const QString &searchText) { Q_EMIT startingSearch(); auto start = [this, stream, searchText]() { Q_ASSERT(!isFetching()); if (!m_isValid) { stream->finish(); return; } // No need to explicitly launch a search, setting the search term already does that for us m_engine->setSearchTerm(searchText); m_onePage = false; m_responsePending = true; connect(stream, &ResultsStream::fetchMore, this, &KNSBackend::fetchMore); connect(this, &KNSBackend::receivedResources, stream, &ResultsStream::resourcesFound); connect(this, &KNSBackend::searchFinished, stream, &ResultsStream::finish); connect(this, &KNSBackend::startingSearch, stream, &ResultsStream::finish); }; if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, stream, start, Qt::QueuedConnection); } else if (isFetching()) { connect(this, &KNSBackend::initialized, stream, start); } else { QTimer::singleShot(0, stream, start); } } ResultsStream * KNSBackend::findResourceByPackageName(const QUrl& search) { if (search.scheme() != QLatin1String("kns") || search.host() != name()) return voidStream(); const auto pathParts = search.path().split(QLatin1Char('/'), QString::SkipEmptyParts); if (pathParts.size() != 2) { Q_EMIT passiveMessage(i18n("Wrong KNewStuff URI: %1", search.toString())); return voidStream(); } const auto providerid = pathParts.at(0); const auto entryid = pathParts.at(1); auto stream = new ResultsStream(QStringLiteral("KNS-byname-")+entryid); auto start = [this, entryid, stream, providerid]() { m_responsePending = true; m_engine->fetchEntryById(entryid); m_onePage = false; connect(m_engine, &KNSCore::Engine::signalError, stream, &ResultsStream::finish); connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, stream, [this, stream, entryid, providerid](const KNSCore::EntryInternal &entry) { if (entry.uniqueId() == entryid && providerid == QUrl(entry.providerId()).host()) { Q_EMIT stream->resourcesFound({resourceForEntry(entry)}); } else qWarning() << "found invalid" << entryid << entry.uniqueId() << providerid << QUrl(entry.providerId()).host(); m_responsePending = false; QTimer::singleShot(0, this, &KNSBackend::availableForQueries); stream->finish(); }); }; if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, stream, start); } else { start(); } return stream; } bool KNSBackend::isFetching() const { return m_fetching; } AbstractBackendUpdater* KNSBackend::backendUpdater() const { return m_updater; } QString KNSBackend::displayName() const { return QStringLiteral("KNewStuff"); } #include "KNSBackend.moc" diff --git a/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp index aa4baa00..ec9d148d 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp @@ -1,183 +1,182 @@ /*************************************************************************** * Copyright © 2016 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 "PackageKitSourcesBackend.h" #include #include #include #include #include #include #include #include #include #include #include "PackageKitBackend.h" #include "config-paths.h" class PKSourcesModel : public QStandardItemModel { public: PKSourcesModel(PackageKitSourcesBackend* backend) : QStandardItemModel(backend) , m_backend(backend) {} QHash roleNames() const override { auto roles = QStandardItemModel::roleNames(); roles[Qt::CheckStateRole] = "checked"; return roles; } bool setData(const QModelIndex & index, const QVariant & value, int role) override { auto item = itemFromIndex(index); if (!item) return false; switch(role) { case Qt::CheckStateRole: { auto transaction = PackageKit::Daemon::global()->repoEnable(item->data(AbstractSourcesBackend::IdRole).toString(), value.toInt() == Qt::Checked); connect(transaction, &PackageKit::Transaction::errorCode, m_backend, &PackageKitSourcesBackend::transactionError); return true; } } item->setData(value, role); return true; } private: PackageKitSourcesBackend* m_backend; }; static QAction* createActionForService(const QString &servicePath, QObject* parent) { QAction* action = new QAction(parent); KDesktopFile parser(servicePath); action->setIcon(QIcon::fromTheme(parser.readIcon())); action->setText(parser.readName()); action->setToolTip(parser.readComment()); QObject::connect(action, &QAction::triggered, action, [servicePath](){ bool b = QProcess::startDetached(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/discover/runservice"), {servicePath}); if (!b) qWarning() << "Could not start" << servicePath; }); return action; } PackageKitSourcesBackend::PackageKitSourcesBackend(AbstractResourcesBackend* parent) : AbstractSourcesBackend(parent) , m_sources(new PKSourcesModel(this)) { connect(PackageKit::Daemon::global(), &PackageKit::Daemon::repoListChanged, this, &PackageKitSourcesBackend::resetSources); resetSources(); // Kubuntu-based auto addNativeSourcesManager = [this](const QString &file){ auto service = PackageKitBackend::locateService(file); if (!service.isEmpty()) m_actions += createActionForService(service, this); }; //New Ubuntu addNativeSourcesManager(QStringLiteral("software-properties-qt.desktop")); //Old Ubuntu addNativeSourcesManager(QStringLiteral("software-properties-kde.desktop")); //OpenSuse addNativeSourcesManager(QStringLiteral("YaST2/sw_source.desktop")); } QString PackageKitSourcesBackend::idDescription() { return i18n("Repository URL:"); } QStandardItem* PackageKitSourcesBackend::findItemForId(const QString &id) const { for(int i=0, c=m_sources->rowCount(); iitem(i); if (it->data(AbstractSourcesBackend::IdRole).toString() == id) return it; } return nullptr; } void PackageKitSourcesBackend::addRepositoryDetails(const QString &id, const QString &description, bool enabled) { bool add = false; QStandardItem* item = findItemForId(id); if (!item) { - QString desc = description; + item = new QStandardItem(description); if (PackageKit::Daemon::backendName() == QLatin1String("aptcc")) { QRegularExpression exp(QStringLiteral("^/etc/apt/sources.list.d/(.+?).list:.*")); auto matchIt = exp.globalMatch(id); if (matchIt.hasNext()) { auto match = matchIt.next(); - desc += QStringLiteral(" - ") + match.captured(1); + item->setData(match.captured(1), Qt::ToolTipRole); } } - item = new QStandardItem(desc); add = true; } item->setData(id, IdRole); item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); if (add) m_sources->appendRow(item); } QAbstractItemModel * PackageKitSourcesBackend::sources() { return m_sources; } bool PackageKitSourcesBackend::addSource(const QString& /*id*/) { return false; } bool PackageKitSourcesBackend::removeSource(const QString& id) { auto transaction = PackageKit::Daemon::global()->repoRemove(id, false); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitSourcesBackend::transactionError); return false; } QList PackageKitSourcesBackend::actions() const { return m_actions; } void PackageKitSourcesBackend::resetSources() { m_sources->clear(); auto transaction = PackageKit::Daemon::global()->getRepoList(); connect(transaction, &PackageKit::Transaction::repoDetail, this, &PackageKitSourcesBackend::addRepositoryDetails); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitSourcesBackend::transactionError); } void PackageKitSourcesBackend::transactionError(PackageKit::Transaction::Error error, const QString& message) { Q_EMIT passiveMessage(message); qWarning() << "Transaction error: " << error << message << sender(); } diff --git a/libdiscover/backends/SnapBackend/SnapBackend.cpp b/libdiscover/backends/SnapBackend/SnapBackend.cpp index f8ff673b..fa7a96f3 100644 --- a/libdiscover/backends/SnapBackend/SnapBackend.cpp +++ b/libdiscover/backends/SnapBackend/SnapBackend.cpp @@ -1,214 +1,216 @@ /*************************************************************************** * 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 "SnapBackend.h" #include "SnapTransaction.h" #include "SnapResource.h" #include "SnapReviewsBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" DISCOVER_BACKEND_PLUGIN(SnapBackend) class SnapSourcesBackend : public AbstractSourcesBackend { public: explicit SnapSourcesBackend(AbstractResourcesBackend * parent) : AbstractSourcesBackend(parent), m_model(new QStandardItemModel(this)) { - m_model->appendRow(new QStandardItem(i18n("Snap"))); + auto it = new QStandardItem(i18n("Snap")); + it->setData(QStringLiteral("Snap"), IdRole); + m_model->appendRow(it); } QAbstractItemModel* sources() override { return m_model; } bool addSource(const QString& /*id*/) override { return false; } bool removeSource(const QString& /*id*/) override { return false;} QString idDescription() override { return QStringLiteral("Snap"); } QList actions() const override { return {}; } bool supportsAdding() const override { return false; } bool canMoveSources() const override { return false; } private: QStandardItemModel* const m_model; }; SnapBackend::SnapBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(new SnapReviewsBackend(this)) { { auto request = m_client.connect(); request->runSync(); m_valid = request->error() == QSnapdRequest::NoError; if (!m_valid) { qWarning() << "snap problem at initialize:" << request->errorString(); return; } } connect(m_reviews, &SnapReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); //make sure we populate the installed resources first refreshStates(); SourcesModel::global()->addSourcesBackend(new SnapSourcesBackend(this)); } int SnapBackend::updatesCount() const { return m_updater->updatesCount(); } static ResultsStream* voidStream() { return new ResultsStream(QStringLiteral("Snap-void"), {}); } ResultsStream * SnapBackend::search(const AbstractResourcesBackend::Filters& filters) { if (!filters.extends.isEmpty()) { return voidStream(); } else if (!filters.resourceUrl.isEmpty()) { return findResourceByPackageName(filters.resourceUrl); } else if (filters.category && filters.category->isAddons()) { return voidStream(); - } else if (filters.state >= AbstractResource::Installed) { + } else if (filters.state >= AbstractResource::Installed || filters.origin == QLatin1String("Snap")) { return populate(m_client.list()); } else if (!filters.search.isEmpty()) { return populate(m_client.find(QSnapdClient::FindFlag::None, filters.search)); } return voidStream(); } ResultsStream * SnapBackend::findResourceByPackageName(const QUrl& search) { return search.scheme() == QLatin1String("snap") ? populate(m_client.find(QSnapdClient::MatchName, search.host())) : search.scheme() == QLatin1String("appstream") ? new ResultsStream(QLatin1String("snap-appstreamurl"), kFilter>(m_resources, [search](SnapResource* res){ return res->appstreamId() == search.host(); } )) : voidStream(); } template ResultsStream* SnapBackend::populate(T* job) { auto stream = new ResultsStream(QStringLiteral("Snap-populate")); connect(job, &QSnapdFindRequest::complete, stream, [stream, this, job]() { if (job->error()) { qDebug() << "error:" << job->error() << job->errorString(); stream->finish(); return; } QVector ret; QSet resources; resources.reserve(job->snapCount()); for (int i=0, c=job->snapCount(); i snap(job->snap(i)); const auto snapname = snap->name(); SnapResource* res = m_resources.value(snapname); if (!res) { res = new SnapResource(snap, AbstractResource::None, this); Q_ASSERT(res->packageName() == snapname); resources += res; } else { res->setSnap(snap); } ret += res; } foreach(SnapResource* res, resources) m_resources[res->packageName()] = res; if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); stream->finish(); }); job->runAsync(); return stream; } void SnapBackend::setFetching(bool fetching) { if (m_fetching != fetching) { m_fetching = fetching; Q_EMIT fetchingChanged(); } else { qWarning() << "fetching already on state" << fetching; } } AbstractBackendUpdater* SnapBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* SnapBackend::reviewsBackend() const { return m_reviews; } Transaction* SnapBackend::installApplication(AbstractResource* app, const AddonList& addons) { Q_ASSERT(addons.isEmpty()); return installApplication(app); } Transaction* SnapBackend::installApplication(AbstractResource* _app) { auto app = qobject_cast(_app); return new SnapTransaction(&m_client, app, Transaction::InstallRole, AbstractResource::Installed); } Transaction* SnapBackend::removeApplication(AbstractResource* _app) { auto app = qobject_cast(_app); return new SnapTransaction(&m_client, app, Transaction::RemoveRole, AbstractResource::None); } QString SnapBackend::displayName() const { return QStringLiteral("Snap"); } void SnapBackend::refreshStates() { auto ret = new StoredResultsStream({populate(m_client.list())}); connect(ret, &StoredResultsStream::finished, this, [this, ret](){ for (auto res: qAsConst(m_resources)) { if (ret->resources().contains(res)) res->setState(AbstractResource::Installed); else res->setState(AbstractResource::None); } }); } #include "SnapBackend.moc" diff --git a/libdiscover/backends/SnapBackend/SnapResource.cpp b/libdiscover/backends/SnapBackend/SnapResource.cpp index 44fe82f2..34baad4d 100644 --- a/libdiscover/backends/SnapBackend/SnapResource.cpp +++ b/libdiscover/backends/SnapBackend/SnapResource.cpp @@ -1,447 +1,447 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "SnapResource.h" #include "SnapBackend.h" #include #include #include #include #include #include #include #include QDebug operator<<(QDebug debug, const QSnapdPlug& plug) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdPlug("; debug.nospace() << "name:" << plug.name() << ','; debug.nospace() << "snap:" << plug.snap() << ','; debug.nospace() << "label:" << plug.label() << ','; debug.nospace() << "interface:" << plug.interface() << ','; debug.nospace() << "connectionCount:" << plug.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot& slot) { QDebugStateSaver saver(debug); debug.nospace() << "QSnapdSlot("; debug.nospace() << "name:" << slot.name() << ','; debug.nospace() << "label:" << slot.label() << ','; debug.nospace() << "snap:" << slot.snap() << ','; debug.nospace() << "interface:" << slot.interface() << ','; debug.nospace() << "connectionCount:" << slot.connectionCount(); debug.nospace() << ')'; return debug; } QDebug operator<<(QDebug debug, const QSnapdPlug* plug) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *plug; return debug; } QDebug operator<<(QDebug debug, const QSnapdSlot* slot) { QDebugStateSaver saver(debug); debug.nospace() << "*" << *slot; return debug; } const QStringList SnapResource::m_objects({ QStringLiteral("qrc:/qml/PermissionsButton.qml") #ifdef SNAP_CHANNELS , QStringLiteral("qrc:/qml/ChannelsButton.qml") #endif }); SnapResource::SnapResource(QSharedPointer snap, AbstractResource::State state, SnapBackend* backend) : AbstractResource(backend) , m_state(state) , m_snap(snap) { setObjectName(snap->name()); } QSnapdClient * SnapResource::client() const { auto backend = qobject_cast(parent()); return backend->client(); } QString SnapResource::availableVersion() const { return installedVersion(); } QStringList SnapResource::categories() { return { QStringLiteral("Application") }; } QString SnapResource::comment() { return m_snap->summary(); } int SnapResource::size() { // return isInstalled() ? m_snap->installedSize() : m_snap->downloadSize(); return m_snap->downloadSize(); } QVariant SnapResource::icon() const { if (m_icon.isNull()) { m_icon = [this]() -> QVariant { const auto iconPath = m_snap->icon(); if (iconPath.isEmpty()) return QStringLiteral("package-x-generic"); if (!iconPath.startsWith(QLatin1Char('/'))) return QUrl(iconPath); auto req = client()->getIcon(packageName()); connect(req, &QSnapdGetIconRequest::complete, this, &SnapResource::gotIcon); req->runAsync(); return {}; }(); } return m_icon; } void SnapResource::gotIcon() { auto req = qobject_cast(sender()); if (req->error()) { qWarning() << "icon error" << req->errorString(); return; } auto icon = req->icon(); QBuffer buffer; buffer.setData(icon->data()); QImageReader reader(&buffer); auto theIcon = QVariant::fromValue(reader.read()); if (theIcon != m_icon) { m_icon = theIcon; iconChanged(); } } QString SnapResource::installedVersion() const { return m_snap->version(); } QString SnapResource::license() { return m_snap->license(); } QString SnapResource::longDescription() { return m_snap->description(); } QString SnapResource::name() const { return m_snap->title().isEmpty() ? m_snap->name() : m_snap->title(); } QString SnapResource::origin() const { - return QStringLiteral("snappy:") + m_snap->channel(); + return QStringLiteral("Snap"); } QString SnapResource::packageName() const { return m_snap->name(); } QString SnapResource::section() { return QStringLiteral("snap"); } AbstractResource::State SnapResource::state() { return m_state; } void SnapResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void SnapResource::fetchChangelog() { QString log; emit changelogFetched(log); } void SnapResource::fetchScreenshots() { QList screenshots; for(int i = 0, c = m_snap->screenshotCount(); i screenshot(m_snap->screenshot(i)); screenshots << QUrl(screenshot->url()); } Q_EMIT screenshotsFetched(screenshots, screenshots); } void SnapResource::invokeApplication() const { QProcess::startDetached(QStringLiteral("snap"), {QStringLiteral("run"), packageName()}); } bool SnapResource::isTechnical() const { return m_snap->snapType() != QLatin1String("app"); } void SnapResource::setSnap(const QSharedPointer& snap) { Q_ASSERT(snap->name() == m_snap->name()); if (m_snap == snap) return; const bool newSize = m_snap->installedSize() != snap->installedSize() || m_snap->downloadSize() != snap->downloadSize(); m_snap = snap; if (newSize) Q_EMIT sizeChanged(); Q_EMIT newSnap(); } QDate SnapResource::releaseDate() const { return {}; } class PlugsModel : public QStandardItemModel { public: enum Roles { PlugNameRole = Qt::UserRole + 1, SlotSnapRole, SlotNameRole }; PlugsModel(SnapResource* res, SnapBackend* backend, QObject* parent) : QStandardItemModel(parent) , m_res(res) , m_backend(backend) { setItemRoleNames(roleNames().unite( { {Qt::CheckStateRole, "checked"} } )); auto req = backend->client()->getInterfaces(); req->runSync(); QHash> slotsForInterface; for (int i = 0; islotCount(); ++i) { const auto slot = req->slot(i); slot->setParent(this); slotsForInterface[slot->interface()].append(slot); } const auto snap = m_res->snap(); for (int i = 0; iplugCount(); ++i) { const QScopedPointer plug(req->plug(i)); if (plug->snap() == snap->name()) { if (plug->interface() == QLatin1String("content")) continue; for (auto slot: slotsForInterface[plug->interface()]) { auto item = new QStandardItem; if (plug->label().isEmpty()) item->setText(plug->name()); else item->setText(i18n("%1 - %2", plug->name(), plug->label())); // qDebug() << "xxx" << plug->name() << plug->label() << plug->interface() << slot->snap() << "slot:" << slot->name() << slot->snap() << slot->interface() << slot->label(); item->setCheckable(true); item->setCheckState(plug->connectionCount()>0 ? Qt::Checked : Qt::Unchecked); item->setData(plug->name(), PlugNameRole); item->setData(slot->snap(), SlotSnapRole); item->setData(slot->name(), SlotNameRole); appendRow(item); } } } } private: bool setData(const QModelIndex & index, const QVariant & value, int role) override { if (role != Qt::CheckStateRole) return QStandardItemModel::setData(index, value, role); auto item = itemFromIndex(index); Q_ASSERT(item); const QString plugName = item->data(PlugNameRole).toString(); const QString slotSnap = item->data(SlotSnapRole).toString(); const QString slotName = item->data(SlotNameRole).toString(); QSnapdRequest* req; const auto snap = m_res->snap(); if (item->checkState() == Qt::Checked) { req = m_backend->client()->disconnectInterface(snap->name(), plugName, slotSnap, slotName); } else { req = m_backend->client()->connectInterface(snap->name(), plugName, slotSnap, slotName); } req->runSync(); if (req->error()) { qWarning() << "snapd error" << req->errorString(); m_res->backend()->passiveMessage(req->errorString()); } return req->error() == QSnapdRequest::NoError; } SnapResource* const m_res; SnapBackend* const m_backend; }; QAbstractItemModel* SnapResource::plugs(QObject* p) { if (!isInstalled()) return new QStandardItemModel(p); return new PlugsModel(this, qobject_cast(parent()), p); } QString SnapResource::appstreamId() const { const QStringList ids #if defined(SNAP_COMMON_IDS) = m_snap->commonIds() #endif ; return ids.isEmpty() ? QLatin1String("com.snap.") + m_snap->name() : ids.first(); } QString SnapResource::channel() const { auto req = client()->listOne(packageName()); req->runSync(); return req->error() ? QString() : req->snap()->trackingChannel(); } void SnapResource::setChannel(const QString& channelName) { #ifdef SNAP_CHANNELS Q_ASSERT(isInstalled()); auto request = client()->switchChannel(m_snap->name(), channelName); const auto currentChannel = channel(); auto dest = new CallOnDestroy([this, currentChannel]() { const auto newChannel = channel(); if (newChannel != currentChannel) { Q_EMIT channelChanged(newChannel); } }); request->runAsync(); connect(request, &QSnapdRequest::complete, dest, &QObject::deleteLater); #endif } void SnapResource::refreshSnap() { auto request = client()->find(QSnapdClient::FindFlag::MatchName, m_snap->name()); connect(request, &QSnapdRequest::complete, this, [this, request](){ if (request->error()) { qWarning() << "error" << request->error() << ": " << request->errorString(); return; } Q_ASSERT(request->snapCount() == 1); setSnap(QSharedPointer(request->snap(0))); }); request->runAsync(); } #ifdef SNAP_CHANNELS class Channels : public QObject { Q_OBJECT Q_PROPERTY(QList channels READ channels NOTIFY channelsChanged) public: Channels(SnapResource* res, QObject* parent) : QObject(parent), m_res(res) { if (res->snap()->channelCount() == 0) res->refreshSnap(); else refreshChannels(); connect(res, &SnapResource::newSnap, this, &Channels::refreshChannels); } void refreshChannels() { qDeleteAll(m_channels); m_channels.clear(); auto s = m_res->snap(); for(int i=0, c=s->channelCount(); ichannel(i); channel->setParent(this); m_channels << channel; } Q_EMIT channelsChanged(); } QList channels() const { return m_channels; } Q_SIGNALS: void channelsChanged(); private: QList m_channels; SnapResource* const m_res; }; #endif QObject * SnapResource::channels(QObject* parent) { #ifdef SNAP_CHANNELS return new Channels(this, parent); #else return nullptr; #endif } #include "SnapResource.moc" diff --git a/libdiscover/resources/ResourcesUpdatesModel.cpp b/libdiscover/resources/ResourcesUpdatesModel.cpp index 4004dc95..3e9f3821 100644 --- a/libdiscover/resources/ResourcesUpdatesModel.cpp +++ b/libdiscover/resources/ResourcesUpdatesModel.cpp @@ -1,277 +1,284 @@ /*************************************************************************** * 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 "ResourcesUpdatesModel.h" #include #include #include "ResourcesModel.h" #include "AbstractBackendUpdater.h" #include "AbstractResource.h" #include "utils.h" #include #include "libdiscover_debug.h" #include #include class UpdateTransaction : public Transaction { Q_OBJECT public: UpdateTransaction(ResourcesUpdatesModel* /*parent*/, const QVector &updaters) : Transaction(nullptr, nullptr, Transaction::InstallRole) , m_allUpdaters(updaters) { bool cancelable = false; foreach(auto updater, m_allUpdaters) { connect(updater, &AbstractBackendUpdater::progressingChanged, this, &UpdateTransaction::slotProgressingChanged); connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &UpdateTransaction::slotDownloadSpeedChanged); connect(updater, &AbstractBackendUpdater::progressChanged, this, &UpdateTransaction::slotUpdateProgress); connect(updater, &AbstractBackendUpdater::proceedRequest, this, &UpdateTransaction::processProceedRequest); connect(updater, &AbstractBackendUpdater::cancelableChanged, this, [this](bool cancelable){ if (cancelable) setCancellable(true); }); cancelable |= updater->isCancelable(); } setCancellable(cancelable); } void processProceedRequest(const QString &title, const QString& message) { m_updatersWaitingForFeedback += qobject_cast(sender()); Q_EMIT proceedRequest(title, message); } void cancel() override { QVector toCancel = m_updatersWaitingForFeedback.isEmpty() ? m_allUpdaters : m_updatersWaitingForFeedback; foreach(auto updater, toCancel) { updater->cancel(); } } void proceed() override { m_updatersWaitingForFeedback.takeFirst()->proceed(); } bool isProgressing() const { bool progressing = false; foreach(AbstractBackendUpdater* upd, m_allUpdaters) { progressing |= upd->isProgressing(); } return progressing; } void slotProgressingChanged() { - if (status() < DoneStatus && !isProgressing()) { + if (status() > SetupStatus && status() < DoneStatus && !isProgressing()) { setStatus(Transaction::DoneStatus); Q_EMIT finished(); deleteLater(); } } void slotUpdateProgress() { qreal total = 0; foreach(AbstractBackendUpdater* updater, m_allUpdaters) { total += updater->progress(); } setProgress(total / m_allUpdaters.count()); } void slotDownloadSpeedChanged() { quint64 total = 0; foreach(AbstractBackendUpdater* updater, m_allUpdaters) { total += updater->downloadSpeed(); } setDownloadSpeed(total); } QVariant icon() const override { return QStringLiteral("update-low"); } QString name() const override { return i18n("Update"); } Q_SIGNALS: void finished(); private: QVector m_updatersWaitingForFeedback; const QVector m_allUpdaters; }; ResourcesUpdatesModel::ResourcesUpdatesModel(QObject* parent) : QStandardItemModel(parent) , m_lastIsProgressing(false) , m_transaction(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesUpdatesModel::init); init(); } void ResourcesUpdatesModel::init() { const QVector backends = ResourcesModel::global()->backends(); m_lastIsProgressing = false; foreach(AbstractResourcesBackend* b, backends) { AbstractBackendUpdater* updater = b->backendUpdater(); if(updater && !m_updaters.contains(updater)) { connect(updater, &AbstractBackendUpdater::statusMessageChanged, this, &ResourcesUpdatesModel::message); connect(updater, &AbstractBackendUpdater::statusDetailChanged, this, &ResourcesUpdatesModel::message); connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &ResourcesUpdatesModel::downloadSpeedChanged); connect(updater, &AbstractBackendUpdater::resourceProgressed, this, &ResourcesUpdatesModel::resourceProgressed); connect(updater, &AbstractBackendUpdater::passiveMessage, this, &ResourcesUpdatesModel::passiveMessage); connect(updater, &AbstractBackendUpdater::destroyed, this, &ResourcesUpdatesModel::updaterDestroyed); m_updaters += updater; m_lastIsProgressing |= updater->isProgressing(); } } auto tm = TransactionModel::global(); foreach(auto t, tm->transactions()) { auto updateTransaction = qobject_cast(t); if (updateTransaction) { setTransaction(updateTransaction); } } } void ResourcesUpdatesModel::updaterDestroyed(QObject* obj) { m_updaters.removeAll(static_cast(obj)); } void ResourcesUpdatesModel::message(const QString& msg) { if(msg.isEmpty()) return; appendRow(new QStandardItem(msg)); } void ResourcesUpdatesModel::prepare() { if(isProgressing()) { qCWarning(LIBDISCOVER_LOG) << "trying to set up a running instance"; return; } foreach(AbstractBackendUpdater* upd, m_updaters) { upd->prepare(); } } void ResourcesUpdatesModel::updateAll() { if (!m_updaters.isEmpty()) { delete m_transaction; const auto updaters = kFilter>(m_updaters, [](AbstractBackendUpdater* u) {return u->hasUpdates(); }); if (updaters.isEmpty()) { return; } m_transaction = new UpdateTransaction(this, updaters); + m_transaction->setStatus(Transaction::SetupStatus); setTransaction(m_transaction); TransactionModel::global()->addTransaction(m_transaction); Q_FOREACH (AbstractBackendUpdater* upd, updaters) { QMetaObject::invokeMethod(upd, "start", Qt::QueuedConnection); } + + QMetaObject::invokeMethod(this, [this](){ + m_transaction->setStatus(Transaction::QueuedStatus); + m_transaction->slotProgressingChanged(); + }, Qt::QueuedConnection); + } } bool ResourcesUpdatesModel::isProgressing() const { return m_transaction && m_transaction->status() < Transaction::DoneStatus; } QList ResourcesUpdatesModel::toUpdate() const { QList ret; foreach(AbstractBackendUpdater* upd, m_updaters) { ret += upd->toUpdate(); } return ret; } void ResourcesUpdatesModel::addResources(const QList& resources) { QHash > sortedResources; foreach(AbstractResource* res, resources) { sortedResources[res->backend()] += res; } for(auto it=sortedResources.constBegin(), itEnd=sortedResources.constEnd(); it!=itEnd; ++it) { it.key()->backendUpdater()->addResources(*it); } } void ResourcesUpdatesModel::removeResources(const QList< AbstractResource* >& resources) { QHash > sortedResources; foreach(AbstractResource* res, resources) { sortedResources[res->backend()] += res; } for(auto it=sortedResources.constBegin(), itEnd=sortedResources.constEnd(); it!=itEnd; ++it) { it.key()->backendUpdater()->removeResources(*it); } } QDateTime ResourcesUpdatesModel::lastUpdate() const { QDateTime ret; foreach(AbstractBackendUpdater* upd, m_updaters) { QDateTime current = upd->lastUpdate(); if(!ret.isValid() || (current.isValid() && current>ret)) { ret = current; } } return ret; } double ResourcesUpdatesModel::updateSize() const { double ret = 0.; for(AbstractBackendUpdater* upd: m_updaters) { ret += upd->updateSize(); } return ret; } qint64 ResourcesUpdatesModel::secsToLastUpdate() const { return lastUpdate().secsTo(QDateTime::currentDateTime()); } void ResourcesUpdatesModel::setTransaction(UpdateTransaction* transaction) { m_transaction = transaction; connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::finished); connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::progressingChanged); Q_EMIT progressingChanged(); } Transaction* ResourcesUpdatesModel::transaction() const { return m_transaction.data(); } #include "ResourcesUpdatesModel.moc" diff --git a/libdiscover/resources/StandardBackendUpdater.cpp b/libdiscover/resources/StandardBackendUpdater.cpp index d1f3a189..79739e16 100644 --- a/libdiscover/resources/StandardBackendUpdater.cpp +++ b/libdiscover/resources/StandardBackendUpdater.cpp @@ -1,255 +1,253 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "ResourcesModel.h" #include #include #include #include #include "libdiscover_debug.h" #include #include StandardBackendUpdater::StandardBackendUpdater(AbstractResourcesBackend* parent) : AbstractBackendUpdater(parent) , m_backend(parent) , m_settingUp(false) , m_progress(0) , m_lastUpdate(QDateTime()) { connect(m_backend, &AbstractResourcesBackend::fetchingChanged, this, &StandardBackendUpdater::refreshUpdateable); connect(m_backend, &AbstractResourcesBackend::resourcesChanged, this, &StandardBackendUpdater::resourcesChanged); connect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, [this](AbstractResource* resource){ m_upgradeable.remove(resource); m_toUpgrade.remove(resource); }); connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &StandardBackendUpdater::transactionRemoved); connect(TransactionModel::global(), &TransactionModel::transactionAdded, this, &StandardBackendUpdater::transactionAdded); m_timer.setSingleShot(true); m_timer.setInterval(10); connect(&m_timer, &QTimer::timeout, this, &StandardBackendUpdater::refreshUpdateable); } void StandardBackendUpdater::resourcesChanged(AbstractResource* res, const QVector& props) { if (props.contains("state") && (res->state() == AbstractResource::Upgradeable || m_upgradeable.contains(res))) m_timer.start(); } bool StandardBackendUpdater::hasUpdates() const { return !m_upgradeable.isEmpty(); } void StandardBackendUpdater::start() { m_settingUp = true; emit progressingChanged(true); setProgress(0); auto upgradeList = m_toUpgrade.toList(); qSort(upgradeList.begin(), upgradeList.end(), [](const AbstractResource* a, const AbstractResource* b){ return a->name() < b->name(); }); const bool couldCancel = m_canCancel; foreach(AbstractResource* res, upgradeList) { m_pendingResources += res; auto t = m_backend->installApplication(res); t->setVisible(false); t->setProperty("updater", QVariant::fromValue(this)); connect(t, &Transaction::downloadSpeedChanged, this, [this](){ Q_EMIT downloadSpeedChanged(downloadSpeed()); }); connect(this, &StandardBackendUpdater::cancelTransaction, t, &Transaction::cancel); TransactionModel::global()->addTransaction(t); m_canCancel |= t->isCancellable(); } if (m_canCancel != couldCancel) { Q_EMIT cancelableChanged(m_canCancel); } m_settingUp = false; - if (m_pendingResources.isEmpty()) - emit progressingChanged(false); if(m_pendingResources.isEmpty()) { cleanup(); } else { setProgress(1); } } void StandardBackendUpdater::cancel() { Q_EMIT cancelTransaction(); } void StandardBackendUpdater::transactionAdded(Transaction* newTransaction) { if (!m_pendingResources.contains(newTransaction->resource())) return; connect(newTransaction, &Transaction::progressChanged, this, &StandardBackendUpdater::transactionProgressChanged); } void StandardBackendUpdater::transactionProgressChanged(int percentage) { Transaction* t = qobject_cast(sender()); Q_EMIT resourceProgressed(t->resource(), percentage); } void StandardBackendUpdater::transactionRemoved(Transaction* t) { const bool fromOurBackend = t->resource() && t->resource()->backend()==m_backend; if (!fromOurBackend) { return; } const bool found = fromOurBackend && m_pendingResources.remove(t->resource()); if(found && !m_settingUp) { qreal p = 1-(qreal(m_pendingResources.size())/m_toUpgrade.size()); setProgress(100*p); if(m_pendingResources.isEmpty()) { cleanup(); } } refreshUpdateable(); } void StandardBackendUpdater::refreshUpdateable() { if (m_backend->isFetching() || !m_backend->isValid()) { return; } if (isProgressing()) { m_timer.start(1000); return; } m_settingUp = true; Q_EMIT progressingChanged(true); AbstractResourcesBackend::Filters f; f.state = AbstractResource::Upgradeable; m_upgradeable.clear(); auto r = m_backend->search(f); connect(r, &ResultsStream::resourcesFound, this, [this](const QVector &resources){ for(auto res : resources) if (res->state() == AbstractResource::Upgradeable) m_upgradeable.insert(res); }); connect(r, &ResultsStream::destroyed, this, [this](){ m_settingUp = false; Q_EMIT updatesCountChanged(updatesCount()); Q_EMIT progressingChanged(false); }); } qreal StandardBackendUpdater::progress() const { return m_progress; } void StandardBackendUpdater::setProgress(qreal p) { if(p>m_progress || p<0) { m_progress = p; emit progressChanged(p); } } void StandardBackendUpdater::prepare() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade = m_upgradeable; } int StandardBackendUpdater::updatesCount() const { return m_upgradeable.count(); } void StandardBackendUpdater::addResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); m_toUpgrade += apps.toSet(); } void StandardBackendUpdater::removeResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); Q_ASSERT(m_toUpgrade.contains(apps.toSet())); m_toUpgrade -= apps.toSet(); } void StandardBackendUpdater::cleanup() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade.clear(); refreshUpdateable(); emit progressingChanged(false); } QList StandardBackendUpdater::toUpdate() const { return m_toUpgrade.toList(); } bool StandardBackendUpdater::isMarked(AbstractResource* res) const { return m_toUpgrade.contains(res); } QDateTime StandardBackendUpdater::lastUpdate() const { return m_lastUpdate; } bool StandardBackendUpdater::isCancelable() const { return m_canCancel; } bool StandardBackendUpdater::isProgressing() const { return m_settingUp || !m_pendingResources.isEmpty(); } double StandardBackendUpdater::updateSize() const { double ret = 0.; for(AbstractResource* res: m_toUpgrade) { ret += res->size(); } return ret; } quint64 StandardBackendUpdater::downloadSpeed() const { quint64 ret = 0; for(Transaction* t: TransactionModel::global()->transactions()) { if (t->property("updater").value() == this) ret += t->downloadSpeed(); } return ret; } diff --git a/notifier/DiscoverNotifier.cpp b/notifier/DiscoverNotifier.cpp index 035d98b2..4b7af0a2 100644 --- a/notifier/DiscoverNotifier.cpp +++ b/notifier/DiscoverNotifier.cpp @@ -1,216 +1,216 @@ /*************************************************************************** * Copyright © 2014 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 "DiscoverNotifier.h" #include "BackendNotifierFactory.h" #include #include #include #include #include #include #include #include #include DiscoverNotifier::DiscoverNotifier(QObject * parent) : QObject(parent) , m_verbose(false) { configurationChanged(); m_backends = BackendNotifierFactory().allBackends(); foreach(BackendNotifierModule* module, m_backends) { connect(module, &BackendNotifierModule::foundUpdates, this, &DiscoverNotifier::updateStatusNotifier); connect(module, &BackendNotifierModule::needsRebootChanged, this, [this]() { if (!m_needsReboot) { m_needsReboot = true; showRebootNotification(); Q_EMIT updatesChanged(); Q_EMIT needsRebootChanged(true); } }); } connect(&m_timer, &QTimer::timeout, this, &DiscoverNotifier::showUpdatesNotification); m_timer.setSingleShot(true); m_timer.setInterval(1000); updateStatusNotifier(); } DiscoverNotifier::~DiscoverNotifier() = default; void DiscoverNotifier::configurationChanged() { KConfig notifierConfig(QStringLiteral("plasma-discover-notifierrc"), KConfig::NoGlobals); KConfigGroup notifyTypeGroup(¬ifierConfig, "NotificationType"); m_verbose = notifyTypeGroup.readEntry("Verbose", false); } void DiscoverNotifier::showDiscover() { KRun::runCommand(QStringLiteral("plasma-discover"), nullptr); } void DiscoverNotifier::showDiscoverUpdates() { KRun::runCommand(QStringLiteral("plasma-discover --mode update"), nullptr); } void DiscoverNotifier::showUpdatesNotification() { if (state()==NoUpdates) { //it's not very helpful to notify that everyting is in order return; } auto e = KNotification::event(QStringLiteral("Update"), message(), extendedMessage(), iconName(), nullptr, KNotification::CloseOnTimeout, QStringLiteral("discoverabstractnotifier")); const QString name = i18n("Update"); e->setDefaultAction(name); e->setActions({name}); - connect(e, QOverload::of(&KNotification::activated), this, &DiscoverNotifier::showDiscover); + connect(e, QOverload::of(&KNotification::activated), this, &DiscoverNotifier::showDiscoverUpdates); } void DiscoverNotifier::updateStatusNotifier() { uint securityCount = 0; for (BackendNotifierModule* module: m_backends) securityCount += module->securityUpdatesCount(); uint count = securityCount; foreach(BackendNotifierModule* module, m_backends) count += module->updatesCount(); if (m_count == count && m_securityCount == securityCount) return; if (state() != NoUpdates && count > m_count) { m_timer.start(); } m_securityCount = securityCount; m_count = count; emit updatesChanged(); } DiscoverNotifier::State DiscoverNotifier::state() const { if (m_needsReboot) return RebootRequired; else if (m_securityCount) return SecurityUpdates; else if (m_count) return NormalUpdates; else return NoUpdates; } QString DiscoverNotifier::iconName() const { switch(state()) { case SecurityUpdates: return QStringLiteral("update-high"); case NormalUpdates: return QStringLiteral("update-low"); case NoUpdates: return QStringLiteral("update-none"); case RebootRequired: return QStringLiteral("system-reboot"); } return QString(); } QString DiscoverNotifier::message() const { switch(state()) { case SecurityUpdates: return i18n("Security updates available"); case NormalUpdates: return i18n("Updates available"); case NoUpdates: return i18n("System up to date"); case RebootRequired: return i18n("Computer needs to restart"); } return QString(); } QString DiscoverNotifier::extendedMessage() const { if (m_count > 0 && m_securityCount > 0) { QString allUpdates = i18ncp("First part of '%1, %2'", "1 package to update", "%1 packages to update", m_count); QString securityUpdates = i18ncp("Second part of '%1, %2'", "of which 1 is security update", "of which %1 are security updates", m_securityCount); return i18nc("%1 is '%1 packages to update' and %2 is 'of which %1 is security updates'", "%1, %2", allUpdates, securityUpdates); } else if (m_count > 0) { return i18np("1 package to update", "%1 packages to update", m_count); } else if (m_securityCount > 0) { return i18np("1 security update", "%1 security updates", m_securityCount); } else { return i18n("No packages to update"); } } void DiscoverNotifier::recheckSystemUpdateNeeded() { foreach(BackendNotifierModule* module, m_backends) module->recheckSystemUpdateNeeded(); } uint DiscoverNotifier::securityUpdatesCount() const { return m_securityCount; } uint DiscoverNotifier::updatesCount() const { return m_count; } QStringList DiscoverNotifier::loadedModules() const { QStringList ret; for(BackendNotifierModule* module : m_backends) ret += QString::fromLatin1(module->metaObject()->className()); return ret; } void DiscoverNotifier::showRebootNotification() { KNotification *notification = new KNotification(QLatin1String("notification"), KNotification::Persistent | KNotification::DefaultEvent); notification->setIconName(QStringLiteral("system-software-update")); 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.")); connect(notification, &KNotification::action1Activated, this, &DiscoverNotifier::reboot); notification->sendEvent(); } void DiscoverNotifier::reboot() { QDBusInterface interface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"), QDBusConnection::sessionBus()); interface.asyncCall(QStringLiteral("logout"), 0, 1, 2); // Options: do not ask again | reboot | force }