diff --git a/libdiscover/backends/FwupdBackend/FwupdBackend.cpp b/libdiscover/backends/FwupdBackend/FwupdBackend.cpp index 466e3915..e5a186d6 100644 --- a/libdiscover/backends/FwupdBackend/FwupdBackend.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdBackend.cpp @@ -1,568 +1,567 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdBackend.h" #include "FwupdResource.h" #include "FwupdTransaction.h" #include "FwupdSourcesBackend.h" #include #include #include #include #include #include #include #include #include DISCOVER_BACKEND_PLUGIN(FwupdBackend) FwupdBackend::FwupdBackend(QObject* parent) : AbstractResourcesBackend(parent) , client(fwupd_client_new()) , m_updater(new StandardBackendUpdater(this)) , m_cancellable(g_cancellable_new()) { connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FwupdBackend::updatesCountChanged); SourcesModel::global()->addSourcesBackend(new FwupdSourcesBackend(this)); QTimer::singleShot(0, this, &FwupdBackend::checkForUpdates); } QMap FwupdBackend::gchecksumToQChryptographicHash() { static QMap map; if (map.isEmpty()) { map.insert(G_CHECKSUM_SHA1,QCryptographicHash::Sha1); map.insert(G_CHECKSUM_SHA256,QCryptographicHash::Sha256); map.insert(G_CHECKSUM_SHA512,QCryptographicHash::Sha512); map.insert(G_CHECKSUM_MD5,QCryptographicHash::Md5); } return map; } FwupdBackend::~FwupdBackend() { g_cancellable_cancel(m_cancellable); if (!m_threadPool.waitForDone(200)) qWarning("Could not stop all fwupd threads"); m_threadPool.clear(); g_object_unref(m_cancellable); g_object_unref(client); } void FwupdBackend::addResourceToList(FwupdResource* res) { res->setParent(this); auto &r = m_resources[res->packageName()]; if (r) { Q_EMIT resourceRemoved(r); delete r; } r = res; Q_ASSERT(m_resources.value(res->packageName()) == res); } FwupdResource * FwupdBackend::createDevice(FwupdDevice *device) { const QString name = QString::fromUtf8(fwupd_device_get_name(device)); FwupdResource* res = new FwupdResource(name, this); const QString deviceID = QString::fromUtf8(fwupd_device_get_id(device)); res->setId(QStringLiteral("org.fwupd.%1.device").arg(QString(deviceID).replace(QLatin1Char('/'),QLatin1Char('_')))); res->setDeviceId(deviceID); res->setDeviceDetails(device); return res; } FwupdResource * FwupdBackend::createRelease(FwupdDevice *device) { FwupdResource* res = createDevice(device); FwupdRelease *release = fwupd_device_get_release_default(device); res->setId(QString::fromUtf8(fwupd_release_get_appstream_id(release))); res->setReleaseDetails(release); /* the same as we have already */ if (qstrcmp(fwupd_device_get_version(device), fwupd_release_get_version(release)) == 0) { qWarning() << "Fwupd Error: same firmware version as installed"; } return res; } void FwupdBackend::addUpdates() { g_autoptr(GError) error = nullptr; g_autoptr(GPtrArray) devices = fwupd_client_get_devices(client, nullptr, &error); if (!devices) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) qDebug() << "Fwupd Info: No Devices Found"; else handleError(error); return; } for(uint i = 0; i < devices->len; i++) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i); if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) continue; if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; g_autoptr(GError) error2 = nullptr; g_autoptr(GPtrArray) rels = fwupd_client_get_upgrades(client, fwupd_device_get_id(device), nullptr, &error2); if (rels) { fwupd_device_add_release(device, (FwupdRelease *)g_ptr_array_index(rels, 0)); auto res = createApp(device); if (!res) { qWarning() << "Fwupd Error: Cannot Create App From Device" << fwupd_device_get_name(device); } else { QString longdescription; for(uint j = 0; j < rels->len; j++) { FwupdRelease *release = (FwupdRelease *)g_ptr_array_index(rels, j); if (!fwupd_release_get_description(release)) continue; longdescription += QStringLiteral("Version %1\n").arg(QString::fromUtf8(fwupd_release_get_version(release))); longdescription += QString::fromUtf8(fwupd_release_get_description(release)) + QLatin1Char('\n'); } res->setDescription(longdescription); addResourceToList(res); } } else { if (!g_error_matches(error2, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { handleError(error2); } } } } QByteArray FwupdBackend::getChecksum(const QString &filename, QCryptographicHash::Algorithm hashAlgorithm) { QFile f(filename); if (!f.open(QFile::ReadOnly)) { qWarning() << "could not open to check" << filename; return {}; } QCryptographicHash hash(hashAlgorithm); if (!hash.addData(&f)) { qWarning() << "could not read to check" << filename; return {}; } return hash.result().toHex(); } FwupdResource* FwupdBackend::createApp(FwupdDevice *device) { FwupdRelease *release = fwupd_device_get_release_default(device); QScopedPointer app(createRelease(device)); if (!app->isLiveUpdatable()) { qWarning() << "Fwupd Error: " << app->name() << "[" << app->id() << "]" << "cannot be updated "; return nullptr; } if (app->id().isNull()) { qWarning() << "Fwupd Error: No id for firmware"; return nullptr; } if (app->availableVersion().isNull()) { qWarning() << "Fwupd Error: No version! for " << app->id(); return nullptr; } GPtrArray *checksums = fwupd_release_get_checksums(release); if (checksums->len == 0) { qWarning() << "Fwupd Error: " << app->name() << "[" << app->id() << "] has no checksums, ignoring as unsafe"; return nullptr; } const QUrl update_uri(QString::fromUtf8(fwupd_release_get_uri(release))); if (!update_uri.isValid()) { qWarning() << "Fwupd Error: No Update URI available for" << app->name() << "[" << app->id() << "]"; return nullptr; } /* Checking for firmware in the cache? */ const QString filename_cache = app->cacheFile(); if (QFile::exists(filename_cache)) { /* Currently LVFS supports SHA1 only*/ const QByteArray checksum_tmp(fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1)); const QByteArray checksum = getChecksum(filename_cache, QCryptographicHash::Sha1); if (checksum_tmp != checksum) { QFile::remove(filename_cache); } } - if (!app->needsReboot()) - app->setState(AbstractResource::Upgradeable); + app->setState(AbstractResource::Upgradeable); return app.take(); } bool FwupdBackend::downloadFile(const QUrl &uri, const QString &filename) { Q_ASSERT(QThread::currentThread() != QCoreApplication::instance()->thread()); QScopedPointer manager(new QNetworkAccessManager); QEventLoop loop; QTimer getTimer; connect(&getTimer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(manager.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); QScopedPointer reply(manager->get(QNetworkRequest(uri))); getTimer.start(600000); // 60 Seconds TimeOout Period loop.exec(); if (!reply) { return false; } else if (QNetworkReply::NoError != reply->error() ) { qWarning() << "error fetching" << uri; return false; } else if (reply->error() == QNetworkReply::NoError) { QFile file(filename); if (file.open(QIODevice::WriteOnly)) { file.write(reply->readAll()); } else { qWarning() << "Fwupd Error: Cannot Open File to write Data" << filename; } } return true; } void FwupdBackend::refreshRemote(FwupdBackend* backend, FwupdRemote* remote, quint64 cacheAge, GCancellable *cancellable) { if (!fwupd_remote_get_filename_cache_sig(remote)) { qWarning() << "Fwupd Error: " << "Remote " << fwupd_remote_get_id(remote) << "has no cache signature!"; return; } /* check cache age */ if (cacheAge > 0) { const quint64 age = fwupd_remote_get_age(remote); if (age < cacheAge) { // qDebug() << "Fwupd Info:" << fwupd_remote_get_id(remote) << "is only" << age << "seconds old, so ignoring refresh! "; return; } } const QString cacheId = QStringLiteral("fwupd/remotes.d/%1").arg(QString::fromUtf8(fwupd_remote_get_id(remote))); const auto basenameSig = QString::fromUtf8(g_path_get_basename(fwupd_remote_get_filename_cache_sig(remote))); const QString filenameSig = cacheFile(cacheId, basenameSig); if (filenameSig.isEmpty()) return; /* download the signature first*/ const QUrl urlSig(QString::fromUtf8(fwupd_remote_get_metadata_uri_sig(remote))); const QString filenameSigTmp(filenameSig + QStringLiteral(".tmp")); if (!downloadFile(urlSig, filenameSigTmp)) { qWarning() << "failed to download" << urlSig; return; } Q_ASSERT(QFile::exists(filenameSigTmp)); const auto checksum = fwupd_remote_get_checksum(remote); const QCryptographicHash::Algorithm hashAlgorithm = gchecksumToQChryptographicHash()[fwupd_checksum_guess_kind(checksum)]; const QByteArray hash = getChecksum(filenameSigTmp, hashAlgorithm); const QByteArray oldHash = getChecksum(filenameSig, hashAlgorithm); if (oldHash == hash) { qDebug() << "remote hasn't changed:" << fwupd_remote_get_id(remote); QFile::remove(filenameSigTmp); return; } QFile::remove(filenameSig); if (!QFile::rename(filenameSigTmp, filenameSig)) { QFile::remove(filenameSigTmp); qWarning() << "Fwupd Error: cannot save remote signature" << filenameSigTmp << "to" << filenameSig; return; } QFile::remove(filenameSigTmp); const auto basename = QString::fromUtf8(g_path_get_basename(fwupd_remote_get_filename_cache(remote))); const QString filename = cacheFile(cacheId, basename); if (filename.isEmpty()) return; qDebug() << "Fwupd Info: saving new firmware metadata to:" << filename; const QUrl url(QString::fromUtf8(fwupd_remote_get_metadata_uri(remote))); if (!downloadFile(url, filename)) { qWarning() << "Fwupd Error: cannot download file:" << filename; return; } g_autoptr(GError) error = nullptr; if (!fwupd_client_update_metadata(backend->client, fwupd_remote_get_id(remote), filename.toUtf8().constData(), filenameSig.toUtf8().constData(), cancellable, &error)) { backend->handleError(error); } } void FwupdBackend::handleError(GError *perror) { //TODO: localise the error message if (perror && !g_error_matches(perror, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE) && !g_error_matches(perror, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { const QString msg = QString::fromUtf8(perror->message); QTimer::singleShot(0, this, [this, msg](){ Q_EMIT passiveMessage(msg); }); qWarning() << "Fwupd Error" << perror->code << perror->message; } // else // qDebug() << "Fwupd skipped" << perror->code << perror->message; } QString FwupdBackend::cacheFile(const QString &kind, const QString &basename) { const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); const QString cacheDirFile = cacheDir.filePath(kind); if (!QFileInfo::exists(cacheDirFile) && !cacheDir.mkpath(kind)) { qWarning() << "Fwupd Error: cannot make cache directory!"; return {}; } return cacheDir.filePath(kind + QLatin1Char('/') + basename); } void FwupdBackend::checkForUpdates() { if (m_fetching) return; auto fw = new QFutureWatcher(this); connect(fw, &QFutureWatcher::finished, this, [this, fw]() { m_fetching = true; emit fetchingChanged(); auto devices = fw->result(); for(uint i = 0; devices && i < devices->len; i++) { FwupdDevice *device = (FwupdDevice *) g_ptr_array_index(devices, i); if (!fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; g_autoptr(GError) error = nullptr; g_autoptr(GPtrArray) releases = fwupd_client_get_releases(client, fwupd_device_get_id(device), nullptr, &error); if (error) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { continue; } handleError(error); } auto res = createDevice(device); for (uint i=0; releases && ilen; ++i) { FwupdRelease *release = (FwupdRelease *)g_ptr_array_index(releases, i); if (res->installedVersion().toUtf8() == fwupd_release_get_version(release)) { res->setReleaseDetails(release); break; } } addResourceToList(res); } g_ptr_array_unref(devices); addUpdates(); m_fetching = false; emit fetchingChanged(); emit initialized(); fw->deleteLater(); }); fw->setFuture(QtConcurrent::run(&m_threadPool, [this] () -> GPtrArray* { const uint cacheAge = (24*60*60); // Check for updates every day g_autoptr(GError) error = nullptr; /* get devices */ GPtrArray* devices = fwupd_client_get_devices(client, m_cancellable, nullptr); g_autoptr(GPtrArray) remotes = fwupd_client_get_remotes(client, m_cancellable, &error); for(uint i = 0; remotes && i < remotes->len; i++) { FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i); if (!fwupd_remote_get_enabled(remote)) continue; if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL) continue; refreshRemote(this, remote, cacheAge, m_cancellable); } return devices; } )); } int FwupdBackend::updatesCount() const { return m_updater->updatesCount(); } ResultsStream* FwupdBackend::search(const AbstractResourcesBackend::Filters& filter) { if (filter.resourceUrl.scheme() == QLatin1String("fwupd")) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.resourceUrl.isEmpty()) { return resourceForFile(filter.resourceUrl); } auto stream = new ResultsStream(QStringLiteral("FwupdStream")); auto f = [this, stream, filter] () { QVector ret; foreach(AbstractResource* r, m_resources) { if (r->state() < filter.state) continue; if (filter.search.isEmpty() || r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) { ret += r; } } if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); stream->finish(); }; if (isFetching()) { connect(this, &FwupdBackend::initialized, stream, f); } else { QTimer::singleShot(0, this, f); } return stream; } ResultsStream * FwupdBackend::findResourceByPackageName(const QUrl& search) { auto res = search.scheme() == QLatin1String("fwupd") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr; if (!res) { return new ResultsStream(QStringLiteral("FwupdStream"), {}); } else return new ResultsStream(QStringLiteral("FwupdStream"), { res }); } AbstractBackendUpdater* FwupdBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* FwupdBackend::reviewsBackend() const { return nullptr; } Transaction* FwupdBackend::installApplication(AbstractResource* app, const AddonList& addons) { Q_ASSERT(addons.isEmpty()); return installApplication(app); } Transaction* FwupdBackend::installApplication(AbstractResource* app) { return new FwupdTransaction(qobject_cast(app), this); } Transaction* FwupdBackend::removeApplication(AbstractResource* /*app*/) { qWarning() << "should not have reached here, it's not possible to uninstall a firmware"; return nullptr; } ResultsStream* FwupdBackend::resourceForFile(const QUrl& path) { g_autoptr(GError) error = nullptr; QMimeDatabase db; QMimeType type = db.mimeTypeForFile(path.fileName()); FwupdResource* app = nullptr; if (type.isValid() && type.inherits(QStringLiteral("application/vnd.ms-cab-compressed"))) { g_autofree gchar *filename = path.fileName().toUtf8().data(); g_autoptr(GPtrArray) devices = fwupd_client_get_details(client, filename, nullptr, &error); if (devices) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, 0); app = createRelease(device); app->setState(AbstractResource::None); for(uint i = 1; i < devices->len; i++) { FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i); FwupdResource* app_ = createRelease(device); app_->setState(AbstractResource::None); } addResourceToList(app); connect(app, &FwupdResource::stateChanged, this, &FwupdBackend::updatesCountChanged); return new ResultsStream(QStringLiteral("FwupdStream-file"), {app}); } else { handleError(error); } } return new ResultsStream(QStringLiteral("FwupdStream-void"), {}); } QString FwupdBackend::displayName() const { return QStringLiteral("Firmware Updates"); } bool FwupdBackend::hasApplications() const { return false; } #include "FwupdBackend.moc" diff --git a/libdiscover/backends/FwupdBackend/FwupdTransaction.cpp b/libdiscover/backends/FwupdBackend/FwupdTransaction.cpp index 533ee14e..1938f8af 100644 --- a/libdiscover/backends/FwupdBackend/FwupdTransaction.cpp +++ b/libdiscover/backends/FwupdBackend/FwupdTransaction.cpp @@ -1,133 +1,139 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2018 Abhijeet Sharma * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FwupdTransaction.h" #include FwupdTransaction::FwupdTransaction(FwupdResource* app, FwupdBackend* backend) : Transaction(backend, app, Transaction::InstallRole, {}) , m_app(app) , m_backend(backend) { setCancellable(true); setStatus(QueuedStatus); Q_ASSERT(!m_app->deviceId().isEmpty()); QTimer::singleShot(0, this, &FwupdTransaction::install); } FwupdTransaction::~FwupdTransaction() = default; void FwupdTransaction::install() { g_autoptr(GError) error = nullptr; if(m_app->isDeviceLocked()) { QString device_id = m_app->deviceId(); if(device_id.isNull()) { qWarning() << "Fwupd Error: No Device ID set, cannot unlock device " << this << m_app->name(); } else if(!fwupd_client_unlock(m_backend->client, device_id.toUtf8().constData(),nullptr, &error)) { m_backend->handleError(error); } setStatus(DoneWithErrorStatus); return; } const QString fileName = m_app->cacheFile(); if(!QFileInfo::exists(fileName)) { const QUrl uri(m_app->updateURI()); setStatus(DownloadingStatus); QNetworkAccessManager *manager = new QNetworkAccessManager(this); auto reply = manager->get(QNetworkRequest(uri)); QFile* file = new QFile(fileName); + if (!file->open(QFile::WriteOnly)) { + qWarning() << "Fwupd Error: Could not open to write" << fileName << uri; + setStatus(DoneWithErrorStatus); + file->deleteLater(); + return; + } connect(reply, &QNetworkReply::finished, this, [this, file, reply](){ file->close(); file->deleteLater(); if(reply->error() != QNetworkReply::NoError) { qWarning() << "Fwupd Error: Could not download" << reply->url() << reply->errorString(); file->remove(); setStatus(DoneWithErrorStatus); } else { fwupdInstall(file->fileName()); } }); connect(reply, &QNetworkReply::readyRead, this, [file, reply](){ file->write(reply->readAll()); }); } else { fwupdInstall(fileName); } } void FwupdTransaction::fwupdInstall(const QString &file) { FwupdInstallFlags install_flags = FWUPD_INSTALL_FLAG_NONE; g_autoptr(GError) error = nullptr; /* only offline supported */ if(m_app->isOnlyOffline()) install_flags = static_cast(install_flags | FWUPD_INSTALL_FLAG_OFFLINE); if(!fwupd_client_install(m_backend->client, m_app->deviceId().toUtf8().constData(), file.toUtf8().constData(), install_flags, nullptr, &error)) { m_backend->handleError(error); setStatus(DoneWithErrorStatus); } else finishTransaction(); } void FwupdTransaction::updateProgress() { setProgress(fwupd_client_get_percentage(m_backend->client)); } void FwupdTransaction::proceed() { finishTransaction(); } void FwupdTransaction::cancel() { setStatus(CancelledStatus); } void FwupdTransaction::finishTransaction() { AbstractResource::State newState; switch(role()) { case InstallRole: case ChangeAddonsRole: newState = AbstractResource::Installed; break; case RemoveRole: newState = AbstractResource::None; break; } m_app->setState(newState); setStatus(DoneStatus); deleteLater(); } diff --git a/libdiscover/backends/KNSBackend/KNSBackend.cpp b/libdiscover/backends/KNSBackend/KNSBackend.cpp index fa5a06e7..72bfa112 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.cpp +++ b/libdiscover/backends/KNSBackend/KNSBackend.cpp @@ -1,593 +1,594 @@ /*************************************************************************** * 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; #if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MINOR>=57 QStringList locations = KNSCore::Engine::configSearchLocations(); #else QStringList locations = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); #endif for (const QString &path: locations) { 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); // This ensures we have something to track when checking after the initialization timeout connect(this, &KNSBackend::initialized, this, [this](){ m_initialized = true; }); // If we have not initialized in 60 seconds, consider this KNS backend invalid QTimer::singleShot(60000, this, [this](){ if(!m_initialized) { markInvalid(i18n("Backend %1 took too long to initialize", m_displayName)); m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); } }); 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(); } const QStringList cats = group.readEntry("Categories", QStringList{}); QVector categories; if (cats.count() > 1) { m_categories += cats; for(const auto &cat: cats) categories << new Category(cat, {}, { {CategoryFilter, cat } }, backendName, {}, {}, true); } QVector topCategories{categories}; for (const auto &cat: categories) { const QString catName = cat->name().append(QLatin1Char('/')); for (const auto& potentialSubCat: categories) { if(potentialSubCat->name().startsWith(catName)) { cat->addSubcategory(potentialSubCat); topCategories.removeOne(potentialSubCat); } } } m_engine = new KNSCore::Engine(this); connect(m_engine, &KNSCore::Engine::signalErrorCode, this, &KNSBackend::signalErrorCode); 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); connect(m_engine, &KNSCore::Engine::signalCategoriesMetadataLoded, this, [categories](const QList< KNSCore::Provider::CategoryMetadata>& categoryMetadatas){ for (const KNSCore::Provider::CategoryMetadata& category : categoryMetadatas) { for (Category* cat : categories) { if (cat->orFilters().count() > 0 && cat->orFilters().first().second == category.name) { cat->setName(category.displayName); break; } } } }); m_engine->setPageSize(100); m_engine->init(m_name); m_hasApplications = group.readEntry("X-Discover-HasApplications", false); if(m_hasApplications) { auto actualCategory = new Category(m_displayName, QStringLiteral("plasma"), filters, backendName, topCategories, QUrl(), false); auto applicationCategory = new Category(i18n("Applications"), QStringLiteral("applications-internet"), filters, backendName, { actualCategory }, QUrl(), false); applicationCategory->setAndFilter({ {CategoryFilter, QLatin1String("Application")} }); m_categories.append(applicationCategory->name()); m_rootCategories = { applicationCategory }; // Make sure we filter out any apps which won't run on the current system architecture QStringList tagFilter = m_engine->tagFilter(); if(QSysInfo::currentCpuArchitecture() == QLatin1String("arm")) { tagFilter << QLatin1String("application##architecture==armhf"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("arm64")) { tagFilter << QLatin1String("application##architecture==arm64"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("i386")) { tagFilter << QLatin1String("application##architecture==x86"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("ia64")) { tagFilter << QLatin1String("application##architecture==x86-64"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("x86_64")) { tagFilter << QLatin1String("application##architecture==x86"); tagFilter << QLatin1String("application##architecture==x86-64"); } m_engine->setTagFilter(tagFilter); } else { 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"), QStringLiteral("wallpaperplugin.knsrc") + QStringLiteral("sddmtheme.knsrc"), QStringLiteral("wallpaperplugin.knsrc"), + QStringLiteral("ksplash.knsrc") }; auto actualCategory = new Category(m_displayName, QStringLiteral("plasma"), filters, backendName, categories, QUrl(), true); const auto topLevelName = knsrcPlasma.contains(fileName)? i18n("Plasma Addons") : i18n("Application Addons"); auto addonsCategory = new Category(topLevelName, QStringLiteral("plasma"), filters, backendName, {actualCategory}, QUrl(), true); m_rootCategories = { addonsCategory }; } } KNSBackend::~KNSBackend() { qDeleteAll(m_rootCategories); } void KNSBackend::markInvalid(const QString &message) { m_rootCategories.clear(); 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) { QStringList categories{name(), m_rootCategories.first()->name()}; const auto cats = m_engine->categoriesMetadata(); const int catIndex = kIndexOf(cats, [&entry](const KNSCore::Provider::CategoryMetadata& cat){ return entry.category() == cat.id; }); if (catIndex > -1) { categories << cats.at(catIndex).name; } if(m_hasApplications) { categories << QLatin1String("Application"); } r = new KNSResource(entry, 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 filtered = kFilter(entries, [](const KNSCore::EntryInternal& entry){ return entry.isValid(); }); const auto resources = kTransform>(filtered, [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); } void KNSBackend::signalErrorCode(const KNSCore::ErrorCode& errorCode, const QString& message, const QVariant& metadata) { QString error = message; qDebug() << "KNS error in" << m_displayName << ":" << errorCode << message << metadata; bool invalidFile = false; switch(errorCode) { case KNSCore::ErrorCode::UnknownError: // This is not supposed to be hit, of course, but any error coming to this point should be non-critical and safely ignored. break; case KNSCore::ErrorCode::NetworkError: // If we have a network error, we need to tell the user about it. This is almost always fatal, so mark invalid and tell the user. error = i18n("Network error in backend %1: %2", m_displayName, metadata.toInt()); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::OcsError: if(metadata.toInt() == 200) { // Too many requests, try again in a couple of minutes - perhaps we can simply postpone it automatically, and give a message? error = i18n("Too many requests sent to the server for backend %1. Please try again in a few minutes.", m_displayName); } else { // Unknown API error, usually something critical, mark as invalid and cry a lot error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; } break; case KNSCore::ErrorCode::ConfigFileError: error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::ProviderError: error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::InstallationError: // This error is handled already, by forwarding the KNS engine's installer error message. break; case KNSCore::ErrorCode::ImageError: // Image fetching errors are not critical as such, but may lead to weird layout issues, might want handling... error = i18n("Could not fetch screenshot for the entry %1 in backend %2", metadata.toList().at(0).toString(), m_displayName); break; default: // Having handled all current error values, we should by all rights never arrive here, but for good order and future safety... error = i18n("Unhandled error in %1 backend. Contact your distributor.", m_displayName); break; } m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); // Setting setFetching to false when we get an error ensures we don't end up in an eternally-fetching state this->setFetching(false); qWarning() << "kns error" << objectName() << error; if (!invalidFile) Q_EMIT passiveMessage(i18n("%1: %2", name(), error)); } 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); 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]() { const auto links = res->linkIds(); for(auto i : links) engine->install(res->entry(), i); }; 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 ((m_hasApplications && !filter.category) // If there is no category defined, we are searching in the root, and should include only application results // If there /is/ a category, make sure we actually are one of those requested before searching || (filter.category && kContains(m_categories, [&filter](const QString& cat) { return filter.category->matchesCategoryName(cat); }))) { 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"