repoindex/alpm/upgradelookup.cpp

414 lines
15 KiB
C++

#include "./upgradelookup.h"
#include "./manager.h"
#include "./alpmdatabase.h"
#include "./config.h"
#include "./utilities.h"
#include <QtConcurrent/QtConcurrent>
#include <assert.h>
#include <iostream>
using namespace std;
namespace RepoIndex {
using namespace Utilities;
/*!
* \class UpgradeResult
* \brief The UpgradeResult class wraps and upgrade/downgrade package and the current version.
*/
/*!
* \brief Returns a JSON object for the current instance.
*/
QJsonObject UpgradeResult::toJson() const
{
QJsonObject obj;
obj.insert(QStringLiteral("name"), package->name());
if(package->repository()) {
obj.insert(QStringLiteral("repo"), package->repository()->name());
}
obj.insert(QStringLiteral("pkg"), package->basicInfo());
obj.insert(QStringLiteral("curVer"), currentVersion);
return obj;
}
/*!
* \class UpgradeLookupProcess
* \brief The UpgradeLookupProcess class performs an async upgrade lookup for a particular upgrade source.
*/
/*!
* \brief Constructs a new upgrade lookup process. The upgrade lookup process is started immediately.
* \remarks \a upgradeLookup and \a upgradeSource musted be locked by caller
*/
UpgradeLookupProcess::UpgradeLookupProcess(UpgradeLookup *upgradeLookup, Repository *upgradeSource) :
QObject(upgradeLookup),
m_toCheck(upgradeLookup->toCheck()),
m_upgradeSource(upgradeSource),
m_reply(nullptr),
m_watcher(new QFutureWatcher<void>(this))
{
connect(this, &UpgradeLookupProcess::finished, upgradeLookup, &UpgradeLookup::processFinished);
requestSources();
}
/*!
* \brief Returns the results. Results are available, after the finished() signal has been emitted.
*/
inline const UpgradeLookupResults &UpgradeLookupProcess::results() const
{
return m_results;
}
/*!
* \brief Internally called to request the sources.
*/
void UpgradeLookupProcess::requestSources()
{
// ensure the repository to check and the upgrade source are both not busy
if(m_toCheck->isBusy()) {
connect(m_toCheck, &Repository::available, this, &UpgradeLookupProcess::requestSources);
} else {
disconnect(m_toCheck, nullptr, this, nullptr);
}
if(m_upgradeSource->isBusy()) {
connect(m_upgradeSource, &Repository::available, this, &UpgradeLookupProcess::requestSources);
} else {
disconnect(m_upgradeSource, nullptr, this, nullptr);
}
// request sources if required
switch(m_upgradeSource->requestsRequired()) {
case PackageDetailAvailability::Request:
m_reply = m_upgradeSource->requestPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::FullRequest:
m_reply = m_upgradeSource->requestFullPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::Never:
m_results.errors << QStringLiteral("Repository \"%1\" does not provide the required information.").arg(m_upgradeSource->name());
emit finished();
return;
case PackageDetailAvailability::Immediately:
break;
}
if(m_reply) {
// a request is required -> wait until results are available
m_reply->setParent(this);
connect(m_reply, &PackageReply::resultsAvailable, this, &UpgradeLookupProcess::sourceReady);
} else {
// no request required -> call sourceReady immidiately
sourceReady();
}
}
/*!
* \brief Internally called when the upgrade source is ready.
*/
void UpgradeLookupProcess::sourceReady()
{
// if a request was required, check whether an error occured
if(m_reply && !m_reply->error().isEmpty()) {
m_results.errors << m_reply->error();
emit finished();
} else {
// invoke the actual upgrade lookup
connect(m_watcher, &QFutureWatcher<void>::finished, this, &UpgradeLookupProcess::finished);
m_watcher->setFuture(QtConcurrent::run(this, &UpgradeLookupProcess::checkUpgrades));
}
}
/*!
* \brief Internally called to perform the upgrade lookup.
*/
void UpgradeLookupProcess::checkUpgrades()
{
QReadLocker toCheckLocker(m_toCheck->lock()), srcLocker(m_upgradeSource->lock());
m_toCheck->checkForUpgrades(m_results, QList<Repository *>() << m_upgradeSource);
}
/*!
* \class UpgradeLookup
* \brief The UpgradeLookup class performs an async upgrade lookup for using multiple upgrade sources.
*/
/*!
* \brief Constructs a new upgrade lookup (protected since this is a pure virtual class).
*/
UpgradeLookup::UpgradeLookup(QObject *parent) :
QObject(parent),
m_toCheck(nullptr),
m_remainingProcesses(0),
m_firstFinished(false)
{}
/*!
* \fn UpgradeLookup::processFinished()
* \brief Internally called when the finished() signal of an UpgradeLookupProcess instance is emitted.
*/
/*!
* \class UpgradeLookupJson
* \remarks The object deletes itself after the lookup is done (hence it must be created using new).
* \brief The UpgradeLookupJson class performs an async upgrade lookup for using multiple upgrade sources.
*
* The request and the results are in JSON.
*
* Example request:
* {
* db: "repo", repository to be checked
* syncdbs: ["extra", "community"] upgrade sources to be used
* }
*
* Example result:
* {
* softwareUpdates: [ software upgrades
* {pkg: ..., basic info of package providing upgrade
* curVer: current version
* ],
* packageOnlyUpdates: [ package-only upgrades
* {pkg: ..., basic info of package providing upgrade
* curVer: current version
* ],
* downgrades: [ downgrades
* {pkg: ..., basic info of package providing downgrade
* curVer: current version
* ]
* orphanedPackages: [ ... ] basic info of orphaned packages
* warnings: [ ... ] warnings
* errors: [ ... ] errors
* }
*/
/*!
* \fn UpgradeLookupJson::resultsAvailable()
* \brief Emitted when all results are availabe (also in the error case).
*/
/*!
* \brief Constructs a new upgrade lookup for the specified \a request using the specified \a manager.
*/
UpgradeLookupJson::UpgradeLookupJson(Manager &manager, const QJsonObject &request, QObject *parent) :
UpgradeLookup(parent),
m_request(request)
{
const auto toCheckName = request.value(QStringLiteral("db")).toString();
if((m_toCheck = manager.repositoryByName(toCheckName))) {
QReadLocker toCheckLocker(m_toCheck->lock());
// construct upgrade lookup processes
const auto syncDbsArray = request.value(QStringLiteral("syncdbs")).toArray();
if(syncDbsArray.isEmpty()) {
for(auto *src : m_toCheck->upgradeSources()) {
QReadLocker srcLocker(src->lock());
new UpgradeLookupProcess(this, src);
++m_remainingProcesses;
}
} else {
for(const auto &syncDbValue : syncDbsArray) {
const auto syncDbName = syncDbValue.toString();
if(auto *src = manager.repositoryByName(syncDbName)) {
QReadLocker srcLocker(src->lock());
new UpgradeLookupProcess(this, src);
++m_remainingProcesses;
} else {
m_warningsArray << QStringLiteral("The specified upgrade source \"%1\" can not be found.").arg(syncDbName);
}
}
}
// check whether any processes could be constructed
if(!m_remainingProcesses) {
m_errorsArray << QStringLiteral("No upgrade sources associated for repository \"%1\".").arg(m_toCheck->name());
} else {
return; // no errors so far
}
} else {
m_errorsArray << QStringLiteral("Repository \"%1\" can not be found.").arg(toCheckName);
}
deleteLater();
}
void UpgradeLookupJson::processFinished()
{
#ifdef DEBUG_BUILD
assert(m_remainingProcesses);
#endif
// add results
const auto &results = static_cast<UpgradeLookupProcess *>(sender())->results();
for(const auto &res : results.newVersions) {
m_softwareUpgradesArray << res.toJson();
}
for(const auto &res : results.newReleases) {
m_packageOnlyUpgradesArray << res.toJson();
}
for(const auto &res : results.downgrades) {
m_downgradesArray << res.toJson();
}
for(const auto &warning : results.warnings) {
m_warningsArray << warning;
}
for(const auto &error : results.errors) {
m_errorsArray << error;
}
if(m_firstFinished) {
m_orphanedPackages = m_orphanedPackages.intersect(results.orphaned);
} else {
m_firstFinished = true;
m_orphanedPackages = results.orphaned;
}
// check whether all processes are finished
if(--m_remainingProcesses == 0) {
// finally make info for orphanded packages
for(const auto *pkg : m_orphanedPackages) {
QJsonObject obj;
obj.insert(QStringLiteral("name"), pkg->name());
if(pkg->repository()) {
obj.insert(QStringLiteral("repo"), pkg->repository()->name());
}
obj.insert(QStringLiteral("pkg"), pkg->basicInfo());
m_orphanedPackagesArray << obj;
}
// add results to results QJsonObject
QJsonObject results;
results.insert(QStringLiteral("softwareUpgrades"), m_softwareUpgradesArray);
results.insert(QStringLiteral("packageOnlyUpgrades"), m_packageOnlyUpgradesArray);
results.insert(QStringLiteral("downgrades"), m_downgradesArray);
results.insert(QStringLiteral("orphanedPackages"), m_orphanedPackagesArray);
if(!m_warningsArray.isEmpty()) {
results.insert(QStringLiteral("warnings"), m_warningsArray);
}
if(!m_errorsArray.isEmpty()) {
results.insert(QStringLiteral("errors"), m_errorsArray);
}
emit resultsAvailable(m_request.value(QStringLiteral("what")), m_request.value(QStringLiteral("id")), results);
// lookup done, delete this helper object
deleteLater();
}
}
/*!
* \class UpgradeLookupCli
* \brief The UpgradeLookupJson class performs an async upgrade lookup for using multiple upgrade sources.
*
* The repository to be checked is specified as string. Results are printed to cerr/cout.
*/
/*!
* \brief Constructs a new upgrade lookup for the specified \a db using the specified \a manager.
*/
UpgradeLookupCli::UpgradeLookupCli(Manager &manager, const string &repo, QObject *parent) :
UpgradeLookup(parent)
{
cerr << shchar << "Checking upgrades for \"" << repo << "\" ..." << endl;
const auto toCheckName = qstr(repo);
if((m_toCheck = manager.repositoryByName(toCheckName))) {
QReadLocker toCheckLocker(m_toCheck->lock());
// construct upgrade lookup processes
for(auto *src : m_toCheck->upgradeSources()) {
QReadLocker srcLocker(src->lock());
new UpgradeLookupProcess(this, src);
++m_remainingProcesses;
}
// check whether any processes could be constructed
if(!m_remainingProcesses) {
m_errorsArray << QStringLiteral("No upgrade sources associated for repository \"%1\".").arg(m_toCheck->name());
} else {
return; // no errors so far
}
} else {
m_errorsArray << QStringLiteral("Repository \"%1\" can not be found.").arg(toCheckName);
}
printResults(); // print errors
}
void UpgradeLookupCli::processFinished()
{
#ifdef DEBUG_BUILD
assert(m_remainingProcesses);
#endif
// add results
const auto &results = static_cast<UpgradeLookupProcess *>(sender())->results();
m_softwareUpgradesArray.reserve(m_softwareUpgradesArray.size() + results.newVersions.size());
m_packageOnlyUpgradesArray.reserve(m_packageOnlyUpgradesArray.size() + results.newReleases.size());
m_downgradesArray.reserve(m_downgradesArray.size() + results.downgrades.size());
m_warningsArray.reserve(m_warningsArray.size() + results.warnings.size());
m_errorsArray.reserve(m_errorsArray.size() + results.errors.size());
for(const auto &res : results.newVersions) {
m_softwareUpgradesArray << res.package->name();
}
for(const auto &res : results.newReleases) {
m_packageOnlyUpgradesArray << res.package->name();
}
for(const auto &res : results.downgrades) {
m_downgradesArray << res.package->name();
}
for(const auto &warning : results.warnings) {
m_warningsArray << warning;
}
for(const auto &error : results.errors) {
m_errorsArray << error;
}
if(m_firstFinished) {
m_orphanedPackages = m_orphanedPackages.intersect(results.orphaned);
} else {
m_firstFinished = true;
m_orphanedPackages = results.orphaned;
}
// check whether all processes are finished
if(--m_remainingProcesses == 0) {
// finally make info for orphanded packages
m_orphanedPackagesArray.reserve(m_orphanedPackages.size());
for(const auto *pkg : m_orphanedPackages) {
m_orphanedPackagesArray << pkg->name();
}
printResults();
}
}
void UpgradeLookupCli::printResults()
{
if(useShSyntax) {
if(!m_errorsArray.isEmpty()) {
Utilities::printBashArray(cerr, "REPOINDEX_ERRORS", m_errorsArray);
}
if(!m_warningsArray.isEmpty()) {
Utilities::printBashArray(cerr, "REPOINDEX_WARNINGS", m_warningsArray);
}
if(!m_softwareUpgradesArray.isEmpty()) {
Utilities::printBashArray(cout, "REPOINDEX_RESULTS_SOFTWARE_UPGRADES", m_softwareUpgradesArray);
}
if(!m_packageOnlyUpgradesArray.isEmpty()) {
Utilities::printBashArray(cout, "REPOINDEX_RESULTS_PACKAGE_UPGRADES", m_packageOnlyUpgradesArray);
}
if(!m_downgradesArray.isEmpty()) {
Utilities::printBashArray(cout, "REPOINDEX_RESULTS_DOWNGRADES", m_downgradesArray);
}
if(!m_orphanedPackagesArray.isEmpty()) {
Utilities::printBashArray(cout, "REPOINDEX_RESULTS_ORPHANED", m_orphanedPackagesArray);
}
} else {
if(!m_errorsArray.isEmpty()) {
Utilities::printValues(cerr, "Errors", m_errorsArray);
}
if(!m_warningsArray.isEmpty()) {
Utilities::printValues(cerr, "Warnings", m_warningsArray);
}
if(!m_softwareUpgradesArray.isEmpty()) {
Utilities::printValues(cout, "Software upgrades", m_softwareUpgradesArray);
}
if(!m_packageOnlyUpgradesArray.isEmpty()) {
Utilities::printValues(cout, "Package upgrades", m_packageOnlyUpgradesArray);
}
if(!m_downgradesArray.isEmpty()) {
Utilities::printValues(cout, "Downgrades", m_downgradesArray);
}
if(!m_orphanedPackagesArray.isEmpty()) {
Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray);
}
}
QCoreApplication::exit();
}
} // namespace PackageManagement