1118 lines
38 KiB
C++
1118 lines
38 KiB
C++
#include "./package.h"
|
|
#include "./alpmdatabase.h"
|
|
#include "./utilities.h"
|
|
#include "./repository.h"
|
|
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QVariant>
|
|
#include <QDataStream>
|
|
#include <QStringBuilder>
|
|
|
|
#include <iostream>
|
|
#include <functional>
|
|
|
|
using namespace std;
|
|
using namespace std::placeholders;
|
|
using namespace ChronoUtilities;
|
|
|
|
namespace RepoIndex {
|
|
|
|
/*!
|
|
* \class The Package class holds meta information about an
|
|
* Arch Linux package.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new package instance.
|
|
*
|
|
* Since it is intenced to use the Package class as base class only,
|
|
* this constructor is protected.
|
|
*/
|
|
Package::Package(const QString &name, Repository *repository) :
|
|
m_origin(PackageOrigin::Unknown),
|
|
m_repository(repository),
|
|
m_hasGeneralInfo(false),
|
|
m_hasAllGeneralInfo(false),
|
|
m_name(name),
|
|
m_requiredByComputed(false),
|
|
m_hasInstallScript(false),
|
|
m_hasBuildRelatedMetaData(false),
|
|
m_hasInstallRelatedMetaData(false),
|
|
m_validationMethods(PackageValidation::Unknown),
|
|
m_installReason(InstallStatus::None),
|
|
m_id(-1),
|
|
m_categoryId(-1),
|
|
m_votes(-1)
|
|
{
|
|
// initialization must be done in derived class
|
|
}
|
|
|
|
/*!
|
|
* \brief Writes the package-type-specific cache header.
|
|
*/
|
|
void Package::writeSpecificCacheHeader(QDataStream &out)
|
|
{
|
|
Q_UNUSED(out)
|
|
}
|
|
|
|
/*!
|
|
* \brief Restores the package-type-specific cache header.
|
|
*/
|
|
void Package::restoreSpecificCacheHeader(QDataStream &in)
|
|
{
|
|
Q_UNUSED(in)
|
|
}
|
|
|
|
/*!
|
|
* \brief Destroys the package.
|
|
*/
|
|
Package::~Package()
|
|
{}
|
|
|
|
/*!
|
|
* \brief Computes required-by and optional-for fields.
|
|
*
|
|
* Sources the specified \a relevantRepositores for packages depending on this package.
|
|
*/
|
|
void Package::computeRequiredBy(const QList<Repository *> &relevantRepositories)
|
|
{
|
|
if(m_requiredByComputed) {
|
|
m_requiredBy.clear();
|
|
m_optionalFor.clear();
|
|
}
|
|
for(const Repository *repo : relevantRepositories) {
|
|
for(const auto &pkgEntry : repo->packages()) {
|
|
for(const auto &dep : pkgEntry.second->dependencies()) {
|
|
if(dep.name == m_name) {
|
|
m_requiredBy << pkgEntry.first;
|
|
break;
|
|
}
|
|
}
|
|
for(const auto &dep : pkgEntry.second->optionalDependencies()) {
|
|
if(dep.name == m_name) {
|
|
m_optionalFor << pkgEntry.first;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_requiredByComputed = true;
|
|
}
|
|
|
|
/*!
|
|
* \brief Checks whether the specified \a name and the specified \a version match the specified \a dependency.
|
|
*/
|
|
bool Package::matches(const QString &name, const QString &version, const Dependency &dependency)
|
|
{
|
|
if(name == dependency.name) {
|
|
if(dependency.version.isEmpty()) {
|
|
return true;
|
|
} else {
|
|
PackageVersionComparsion cmp;
|
|
switch(dependency.mode) {
|
|
case DependencyMode::Any:
|
|
return true;
|
|
case DependencyMode::Equal:
|
|
return PackageVersion(version).compare(PackageVersion(dependency.version)) == PackageVersionComparsion::Equal;
|
|
case DependencyMode::GreatherEqual:
|
|
return (cmp = PackageVersion(version).compare(PackageVersion(dependency.version))) == PackageVersionComparsion::Equal || cmp == PackageVersionComparsion::NewerThenSyncVersion;
|
|
case DependencyMode::LessEqual:
|
|
return (cmp = PackageVersion(version).compare(PackageVersion(dependency.version))) == PackageVersionComparsion::Equal || cmp == PackageVersionComparsion::PackageUpgradeOnly || cmp == PackageVersionComparsion::SoftwareUpgrade;
|
|
case DependencyMode::GreatherThen:
|
|
return PackageVersion(version).compare(PackageVersion(dependency.version)) == PackageVersionComparsion::NewerThenSyncVersion;
|
|
case DependencyMode::LessThen:
|
|
return (cmp = PackageVersion(version).compare(PackageVersion(dependency.version))) == PackageVersionComparsion::PackageUpgradeOnly || cmp == PackageVersionComparsion::SoftwareUpgrade;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Checks whether the package matches the specified \a dependency.
|
|
*/
|
|
bool Package::matches(const Dependency &dependency)
|
|
{
|
|
// check whether package matches "directly"
|
|
if(matches(name(), version(), dependency)) {
|
|
return true;
|
|
} else {
|
|
// check whether at least one of the provides matches
|
|
for(const auto &provide : provides()) {
|
|
if(matches(provide.name, provide.version, dependency)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \cond
|
|
*/
|
|
|
|
namespace Utilities {
|
|
|
|
inline void put(QJsonObject &obj, const QString &key, const QJsonValue &value)
|
|
{
|
|
obj.insert(key, value);
|
|
}
|
|
|
|
inline void put(QJsonObject &obj, const QString &key, const DateTime dateTime)
|
|
{
|
|
if(!dateTime.isNull()) {
|
|
put(obj, key, QString::fromLocal8Bit(dateTime.toString().data()));
|
|
}
|
|
}
|
|
|
|
inline void put(QJsonObject &obj, const QString &key, const QStringList &values)
|
|
{
|
|
put(obj, key, QJsonArray::fromStringList(values));
|
|
}
|
|
|
|
void put(QJsonObject &obj, const QString &key, const QList<Dependency> &dependencies)
|
|
{
|
|
QJsonArray jsonArray;
|
|
for(const auto &dependency : dependencies) {
|
|
jsonArray << dependency.toJson();
|
|
}
|
|
put(obj, key, jsonArray);
|
|
}
|
|
|
|
QDataStream &operator <<(QDataStream &out, const QList<Dependency> dependencies)
|
|
{
|
|
out << static_cast<quint32>(dependencies.count());
|
|
for(const auto &dependency : dependencies) {
|
|
out << dependency.name << dependency.version << static_cast<quint32>(dependency.mode);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
QDataStream &operator >>(QDataStream &in, QList<Dependency> &dependencies)
|
|
{
|
|
quint32 size;
|
|
in >> size;
|
|
for(quint32 i = 0; i < size; ++i) {
|
|
QString name;
|
|
in >> name;
|
|
QString version;
|
|
in >> version;
|
|
quint32 mode;
|
|
in >> mode;
|
|
dependencies << Dependency(name, version, static_cast<DependencyMode>(mode));
|
|
}
|
|
return in;
|
|
}
|
|
|
|
inline QDataStream &operator <<(QDataStream &out, const DateTime dateTime)
|
|
{
|
|
return out << static_cast<quint64>(dateTime.totalTicks());
|
|
}
|
|
|
|
inline QDataStream &operator >>(QDataStream &in, DateTime &dateTime)
|
|
{
|
|
quint64 ticks;
|
|
in >> ticks;
|
|
dateTime = DateTime(ticks);
|
|
return in;
|
|
}
|
|
|
|
QDataStream &operator <<(QDataStream &out, const map<QString, QByteArray> fileMap)
|
|
{
|
|
out << static_cast<quint32>(fileMap.size());
|
|
for(const auto &entry : fileMap) {
|
|
out << entry.first << entry.second;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
QDataStream &operator >>(QDataStream &in, map<QString, QByteArray> &fileMap)
|
|
{
|
|
quint32 size;
|
|
in >> size;
|
|
for(quint32 i = 0; i < size; ++i) {
|
|
QString path;
|
|
in >> path;
|
|
QByteArray data;
|
|
in >> data;
|
|
fileMap.emplace(path, data);
|
|
}
|
|
return in;
|
|
}
|
|
|
|
QDataStream &operator <<(QDataStream &out, const QJsonArray &jsonArray)
|
|
{
|
|
QJsonDocument doc;
|
|
doc.setArray(jsonArray);
|
|
out << doc.toBinaryData();
|
|
return out;
|
|
}
|
|
|
|
QDataStream &operator >>(QDataStream &in, QJsonArray &jsonArray)
|
|
{
|
|
QByteArray data;
|
|
in >> data;
|
|
QJsonDocument doc = QJsonDocument::fromBinaryData(data);
|
|
jsonArray = doc.array();
|
|
return in;
|
|
}
|
|
|
|
}
|
|
|
|
/*!
|
|
* \endcond
|
|
*/
|
|
|
|
using namespace Utilities;
|
|
|
|
/*!
|
|
* \brief Returns basic information about the packages as JSON object.
|
|
*/
|
|
QJsonObject Package::basicInfo(bool includeRepoAndName) const
|
|
{
|
|
QJsonObject info;
|
|
if(includeRepoAndName) {
|
|
if(repository()) {
|
|
put(info, QStringLiteral("repo"), repository()->name());
|
|
}
|
|
put(info, QStringLiteral("name"), name());
|
|
}
|
|
put(info, QStringLiteral("ver"), version());
|
|
put(info, QStringLiteral("desc"), description());
|
|
put(info, QStringLiteral("fdate"), outOfDate());
|
|
put(info, QStringLiteral("arch"), buildArchitecture());
|
|
put(info, QStringLiteral("bdate"), buildDate());
|
|
put(info, QStringLiteral("archs"), architectures());
|
|
return info;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns full information about the package as JSON object.
|
|
*/
|
|
QJsonObject Package::detailedInfo() const
|
|
{
|
|
QJsonObject info;
|
|
put(info, QStringLiteral("iav"), hasInstallRelatedMetaData());
|
|
put(info, QStringLiteral("bav"), hasBuildRelatedMetaData());
|
|
put(info, QStringLiteral("sav"), hasSourceRelatedMetaData());
|
|
put(info, QStringLiteral("idate"), installDate());
|
|
put(info, QStringLiteral("isize"), QJsonValue(static_cast<long long int>(installedSize())));
|
|
put(info, QStringLiteral("csize"), QJsonValue(static_cast<long long int>(packageSize())));
|
|
put(info, QStringLiteral("url"), upstreamUrl());
|
|
put(info, QStringLiteral("lic"), licenses());
|
|
put(info, QStringLiteral("grp"), groups());
|
|
put(info, QStringLiteral("prov"), provides());
|
|
put(info, QStringLiteral("deps"), dependencies());
|
|
put(info, QStringLiteral("optd"), optionalDependencies());
|
|
put(info, QStringLiteral("mkd"), makeDependencies());
|
|
put(info, QStringLiteral("chkd"), checkDependencies());
|
|
if(isRequiredByComputed()) {
|
|
put(info, QStringLiteral("requ"), requiredBy());
|
|
put(info, QStringLiteral("optf"), optionalFor());
|
|
}
|
|
put(info, QStringLiteral("conf"), conflicts());
|
|
put(info, QStringLiteral("repl"), replaces());
|
|
put(info, QStringLiteral("pack"), packager());
|
|
put(info, QStringLiteral("expl"), QJsonValue(installReason() == InstallStatus::Explicit));
|
|
put(info, QStringLiteral("scri"), QJsonValue(hasInstallScript()));
|
|
put(info, QStringLiteral("sig"), Utilities::validationMethodsStrings(validationMethods()));
|
|
put(info, QStringLiteral("file"), fileName());
|
|
put(info, QStringLiteral("files"), files());
|
|
put(info, QStringLiteral("fsub"), firstSubmitted());
|
|
put(info, QStringLiteral("lmod"), lastModified());
|
|
if(!maintainer().isEmpty()) {
|
|
put(info, QStringLiteral("main"), maintainer());
|
|
}
|
|
if(!tarUrl().isEmpty()) {
|
|
put(info, QStringLiteral("srctar"), tarUrl());
|
|
}
|
|
if(votes() >= 0) {
|
|
put(info, QStringLiteral("votes"), votes());
|
|
}
|
|
return info;
|
|
}
|
|
|
|
QJsonObject Package::simpleInfo() const
|
|
{
|
|
QJsonObject info;
|
|
put(info, QStringLiteral("name"), name());
|
|
put(info, QStringLiteral("ver"), version());
|
|
put(info, QStringLiteral("desc"), description());
|
|
put(info, QStringLiteral("flagdate"), outOfDate());
|
|
put(info, QStringLiteral("arch"), buildArchitecture());
|
|
put(info, QStringLiteral("bdate"), buildDate());
|
|
put(info, QStringLiteral("url"), upstreamUrl());
|
|
put(info, QStringLiteral("lic"), licenses());
|
|
put(info, QStringLiteral("packer"), packager());
|
|
return info;
|
|
}
|
|
|
|
/*!
|
|
* \brief Writes the package contents to the specified data stream.
|
|
*/
|
|
void Package::writeToCacheStream(QDataStream &out)
|
|
{
|
|
out << static_cast<qint32>(m_origin) << m_timeStamp;
|
|
// general info
|
|
out << m_hasGeneralInfo << m_hasAllGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses
|
|
<< m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides
|
|
<< m_replaces << m_requiredByComputed << m_requiredBy << m_optionalFor << m_hasInstallScript;
|
|
// build related meta data
|
|
out << m_hasBuildRelatedMetaData << m_fileName << m_files << m_buildDate << m_packager
|
|
<< m_md5 << m_sha256 << m_buildArchitecture << m_packageSize << m_makeDependencies;
|
|
// installation related meta data
|
|
out << m_hasInstallRelatedMetaData << m_installDate << m_installedSize << m_backupFiles
|
|
<< static_cast<qint32>(m_validationMethods) << static_cast<qint32>(m_installReason);
|
|
// source related meta data
|
|
out << m_hasSourceRelatedMetaData << m_baseName << m_architectures << m_id
|
|
<< m_categoryId << m_votes << m_outOfDate
|
|
<< m_maintainer << m_firstSubmitted << m_lastModified << m_tarUrl << m_sourceFiles;
|
|
// write specific header
|
|
auto headerStart = out.device()->pos();
|
|
out.device()->seek(headerStart + 4);
|
|
writeSpecificCacheHeader(out);
|
|
auto headerEnd = out.device()->pos();
|
|
out.device()->seek(headerStart);
|
|
out << static_cast<quint32>(headerEnd - headerStart - 4);
|
|
out.device()->seek(headerEnd);
|
|
// no extended header
|
|
out << static_cast<quint32>(0);
|
|
}
|
|
|
|
void Package::restoreFromCacheStream(QDataStream &in)
|
|
{
|
|
qint32 tmp;
|
|
// origin
|
|
in >> tmp;
|
|
m_origin = static_cast<PackageOrigin>(tmp); // TODO: validate value
|
|
in >> m_timeStamp;
|
|
// general info
|
|
in >> m_hasGeneralInfo >> m_hasAllGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses
|
|
>> m_groups >> m_dependencies >> m_optionalDependencies >> m_conflicts >> m_provides
|
|
>> m_replaces >> m_requiredByComputed >> m_requiredBy >> m_optionalFor >> m_hasInstallScript;
|
|
// build related meta data
|
|
in >> m_hasBuildRelatedMetaData >> m_fileName >> m_files >> m_buildDate >> m_packager
|
|
>> m_md5 >> m_sha256 >> m_buildArchitecture >> m_packageSize >> m_makeDependencies;
|
|
// installation related meta data
|
|
in >> m_hasInstallRelatedMetaData >> m_installDate >> m_installedSize >> m_backupFiles;
|
|
in >> tmp;
|
|
m_validationMethods = static_cast<PackageValidation>(tmp); // TODO: validate value
|
|
in >> tmp;
|
|
m_installReason = static_cast<InstallStatus>(tmp); // TODO: validate value
|
|
// source related meta data
|
|
in >> m_hasSourceRelatedMetaData >> m_baseName >> m_architectures >> m_id
|
|
>> m_categoryId >> m_votes >> m_outOfDate
|
|
>> m_maintainer >> m_firstSubmitted >> m_lastModified >> m_tarUrl >> m_sourceFiles;
|
|
// specific header
|
|
quint32 headerSize;
|
|
in >> headerSize;
|
|
quint64 headerEnd = in.device()->pos() + headerSize;
|
|
restoreSpecificCacheHeader(in);
|
|
in.device()->seek(headerEnd);
|
|
if(in.status() == QDataStream::Ok) {
|
|
// skip extended header
|
|
in >> headerSize;
|
|
quint64 headerEnd = in.device()->pos() + headerSize;
|
|
in.device()->seek(headerEnd);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Puts the specified .SRCINFO/.PKGINFO key value pairs; clears current values before inserting new values.
|
|
* \remarks
|
|
* - This method should only be called by the associated repository because the associated repository might
|
|
* has to handle a possible name change.
|
|
* - Actual parsing of .SRCINFO/.PKGINFO is done in Repository::addPackagesFromSrcInfo() because one info file
|
|
* applies to multiple packages in case of split packages.
|
|
*/
|
|
void Package::putInfo(const QList<QPair<QString, QString> > &baseInfo, const QList<QPair<QString, QString> > &pkgInfo, bool includesSourceRelatedMetaData, bool includesBuildRelatedMetaData)
|
|
{
|
|
// clear current values
|
|
m_baseName.clear();
|
|
m_description.clear();
|
|
m_upstreamUrl.clear();
|
|
m_packager.clear();
|
|
m_licenses.clear();
|
|
m_dependencies.clear();
|
|
m_optionalDependencies.clear();
|
|
m_conflicts.clear();
|
|
m_provides.clear();
|
|
m_replaces.clear();
|
|
if(includesBuildRelatedMetaData) {
|
|
m_buildArchitecture.clear();
|
|
}
|
|
if(includesSourceRelatedMetaData) {
|
|
m_architectures.clear();
|
|
m_makeDependencies.clear();
|
|
m_checkDependencies.clear();
|
|
m_installedSize = 0;
|
|
m_buildDate = DateTime();
|
|
m_packager.clear();
|
|
}
|
|
|
|
// prevent overwriting these crucial fields with empty values
|
|
QString name;
|
|
PackageVersion version;
|
|
|
|
// read specified key value pairs
|
|
const auto infos = {baseInfo, pkgInfo};
|
|
for(const auto &info : infos) {
|
|
for(const auto &pair : info) {
|
|
const auto &field = pair.first;
|
|
if(field == QLatin1String("pkgbase")) {
|
|
m_baseName.clear();
|
|
} else if(field == QLatin1String("pkgname")) {
|
|
name.clear();
|
|
} else if(field == QLatin1String("epoch")) {
|
|
version.epoch.clear();
|
|
} else if(field == QLatin1String("pkgver")) {
|
|
version.version.clear();
|
|
} else if(field == QLatin1String("pkgrel")) {
|
|
version.release.clear();
|
|
} else if(field == QLatin1String("pkgdesc")) {
|
|
m_description.clear();
|
|
} else if(field == QLatin1String("url")) {
|
|
m_upstreamUrl.clear();
|
|
} else if(field == QLatin1String("arch")) {
|
|
if(includesSourceRelatedMetaData) {
|
|
m_architectures.clear();
|
|
}
|
|
if(includesBuildRelatedMetaData) {
|
|
m_buildArchitecture.clear();
|
|
}
|
|
} else if(field == QLatin1String("license")) {
|
|
m_licenses.clear();
|
|
} else if(field == QLatin1String("depends")) {
|
|
m_dependencies.clear();
|
|
} else if(field == QLatin1String("makedepends")) {
|
|
m_makeDependencies.clear();
|
|
} else if(field == QLatin1String("checkdepends")) {
|
|
m_checkDependencies.clear();
|
|
} else if(field == QLatin1String("optdepends")) {
|
|
m_optionalDependencies.clear();
|
|
} else if(field == QLatin1String("conflicts")) {
|
|
m_conflicts.clear();
|
|
} else if(field == QLatin1String("provides")) {
|
|
m_provides.clear();
|
|
} else if(field == QLatin1String("replaces")) {
|
|
m_replaces.clear();
|
|
} else if(field == QLatin1String("source")) {
|
|
// currently not used
|
|
} else if(field == QLatin1String("size")) {
|
|
m_installedSize = 0;
|
|
} else if(field == QLatin1String("builddate")) {
|
|
m_buildDate = DateTime();
|
|
} else if(field == QLatin1String("packager")) {
|
|
m_packager.clear();
|
|
}
|
|
}
|
|
for(const auto &pair : info) {
|
|
const auto &field = pair.first;
|
|
const auto &value = pair.second;
|
|
if(field == QLatin1String("pkgbase")) {
|
|
m_baseName = value;
|
|
} else if(field == QLatin1String("pkgname")) {
|
|
name = value;
|
|
} else if(field == QLatin1String("epoch")) {
|
|
version.epoch = value;
|
|
} else if(field == QLatin1String("pkgver")) {
|
|
version.version = value;
|
|
} else if(field == QLatin1String("pkgrel")) {
|
|
version.release = value;
|
|
} else if(field == QLatin1String("pkgdesc")) {
|
|
m_description = value;
|
|
} else if(field == QLatin1String("url")) {
|
|
m_upstreamUrl = value;
|
|
} else if(field == QLatin1String("arch")) {
|
|
if(includesSourceRelatedMetaData) {
|
|
m_architectures << value;
|
|
}
|
|
if(includesBuildRelatedMetaData) {
|
|
m_buildArchitecture = value;
|
|
}
|
|
} else if(field == QLatin1String("license")) {
|
|
m_licenses << value;
|
|
} else if(field == QLatin1String("depends")) {
|
|
m_dependencies << Dependency(value);
|
|
} else if(field == QLatin1String("makedepends")) {
|
|
m_makeDependencies << Dependency(value);
|
|
} else if(field == QLatin1String("checkdepends")) {
|
|
m_checkDependencies << Dependency(value);
|
|
} else if(field == QLatin1String("optdepends")) {
|
|
m_optionalDependencies << Dependency(value);
|
|
} else if(field == QLatin1String("conflicts")) {
|
|
m_conflicts << Dependency(value);
|
|
} else if(field == QLatin1String("provides")) {
|
|
m_provides << Dependency(value);
|
|
} else if(field == QLatin1String("replaces")) {
|
|
m_replaces << Dependency(value);
|
|
} else if(field == QLatin1String("source")) {
|
|
// currently not used
|
|
} else if(field == QLatin1String("size")) {
|
|
m_installedSize = value.toUInt();
|
|
} else if(field == QLatin1String("builddate")) {
|
|
m_buildDate = DateTime::fromTimeStampGmt(value.toUInt());
|
|
} else if(field == QLatin1String("packager")) {
|
|
m_packager = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure crucial information is still present (use old values rather than no values)
|
|
if(!name.isEmpty()) {
|
|
m_name = name;
|
|
}
|
|
if(!version.version.isEmpty()) {
|
|
m_version = version.toString();
|
|
}
|
|
// use the name as base name if the base name hasn't been specified explicitely
|
|
if(includesSourceRelatedMetaData && m_baseName.isEmpty()) {
|
|
m_baseName = m_name;
|
|
}
|
|
|
|
// consider general information as complete
|
|
m_hasGeneralInfo = m_hasAllGeneralInfo = true;
|
|
m_hasSourceRelatedMetaData = includesSourceRelatedMetaData;
|
|
m_hasBuildRelatedMetaData = includesBuildRelatedMetaData;
|
|
}
|
|
|
|
/*!
|
|
* \cond
|
|
*/
|
|
|
|
#define PUT_DEPS(lhs, rhs) \
|
|
lhs.reserve(rhs.size()); \
|
|
for(const QString &dep : rhs) { \
|
|
lhs << Dependency(dep); \
|
|
} \
|
|
|
|
const map<QString, void(Package::*)(const QStringList &)> Package::m_descMap {
|
|
{QStringLiteral("NAME"), &Package::setName},
|
|
{QStringLiteral("VERSION"), &Package::setVersion},
|
|
{QStringLiteral("DESC"), &Package::setDescription},
|
|
{QStringLiteral("URL"), &Package::setUrl},
|
|
{QStringLiteral("ARCH"), &Package::setArch},
|
|
{QStringLiteral("LICENSE"), &Package::setLicenses},
|
|
{QStringLiteral("DEPENDS"), &Package::setDepends},
|
|
{QStringLiteral("MAKEDEPENDS"), &Package::setMakeDepends},
|
|
{QStringLiteral("CHECKDEPENDS"), &Package::setCheckDepends},
|
|
{QStringLiteral("OPTDEPENDS"), &Package::setOptDepends},
|
|
{QStringLiteral("CONFLICTS"), &Package::setConflicts},
|
|
{QStringLiteral("PROVIDES"), &Package::setProvides},
|
|
{QStringLiteral("REPLACES"), &Package::setReplaces},
|
|
{QStringLiteral("BUILDDATE"), &Package::setBuildDate},
|
|
{QStringLiteral("INSTALLDATE"), &Package::setInstallDate},
|
|
{QStringLiteral("ISIZE"), &Package::setInstalledSize},
|
|
{QStringLiteral("SIZE"), &Package::setInstalledSize},
|
|
{QStringLiteral("CSIZE"), &Package::setPackageSize},
|
|
{QStringLiteral("PACKAGER"), &Package::setPackager},
|
|
{QStringLiteral("MD5SUM"), &Package::setMd5},
|
|
{QStringLiteral("SHA256SUM"), &Package::setSha256},
|
|
{QStringLiteral("PGPSIG"), &Package::setPgpSignature},
|
|
{QStringLiteral("FILES"), &Package::setFiles},
|
|
{QStringLiteral("REASON"), &Package::setInstallReason},
|
|
{QStringLiteral("VALIDATION"), &Package::setValidation},
|
|
{QStringLiteral("GROUPS"), &Package::setGroups},
|
|
{QStringLiteral("FILENAME"), &Package::setFileName}
|
|
};
|
|
|
|
void Package::setName(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty() && !values.back().isEmpty()) {
|
|
m_name = values.back();
|
|
}
|
|
}
|
|
|
|
void Package::setVersion(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty() && !values.back().isEmpty()) {
|
|
m_version = values.back();
|
|
}
|
|
}
|
|
void Package::setDescription(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_description = values.back();
|
|
}
|
|
}
|
|
void Package::setUrl(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_upstreamUrl = values.back();
|
|
}
|
|
}
|
|
void Package::setArch(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_buildArchitecture = values.back();
|
|
}
|
|
}
|
|
void Package::setLicenses(const QStringList &values)
|
|
{
|
|
m_licenses = values;
|
|
}
|
|
void Package::setDepends(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_dependencies, values);
|
|
}
|
|
void Package::setMakeDepends(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_makeDependencies, values);
|
|
}
|
|
void Package::setCheckDepends(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_checkDependencies, values);
|
|
}
|
|
void Package::setOptDepends(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_optionalDependencies, values);
|
|
}
|
|
void Package::setConflicts(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_conflicts, values);
|
|
}
|
|
void Package::setProvides(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_provides, values);
|
|
}
|
|
void Package::setReplaces(const QStringList &values)
|
|
{
|
|
PUT_DEPS(m_replaces, values);
|
|
}
|
|
void Package::setBuildDate(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_buildDate = DateTime::fromTimeStampGmt(values.back().toUInt());
|
|
}
|
|
}
|
|
void Package::setInstallDate(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_installDate = DateTime::fromTimeStampGmt(values.back().toUInt());
|
|
}
|
|
}
|
|
void Package::setInstalledSize(const QStringList &values)
|
|
{
|
|
if(!values.empty()) {
|
|
m_installedSize = values.back().toUInt();
|
|
m_hasInstallRelatedMetaData = true;
|
|
}
|
|
}
|
|
void Package::setPackageSize(const QStringList &values)
|
|
{
|
|
if(!values.empty()) {
|
|
m_packageSize = values.back().toUInt();
|
|
}
|
|
}
|
|
void Package::setPackager(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_packager = values.back();
|
|
}
|
|
}
|
|
void Package::setMd5(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
if(!(m_md5 = values.back()).isEmpty()) {
|
|
m_validationMethods |= PackageValidation::Md5Sum;
|
|
} else {
|
|
m_validationMethods &= ~PackageValidation::Md5Sum;
|
|
}
|
|
}
|
|
}
|
|
void Package::setSha256(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
if(!(m_sha256 = values.back()).isEmpty()) {
|
|
m_validationMethods |= PackageValidation::Sha256Sum;
|
|
} else {
|
|
m_validationMethods &= ~PackageValidation::Sha256Sum;
|
|
}
|
|
}
|
|
}
|
|
void Package::setPgpSignature(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
if(!(m_pgpSignature = values.back()).isEmpty()) {
|
|
m_validationMethods |= PackageValidation::PgpSignature;
|
|
} else {
|
|
m_validationMethods &= ~PackageValidation::PgpSignature;
|
|
}
|
|
}
|
|
}
|
|
void Package::setFiles(const QStringList &values)
|
|
{
|
|
m_files = QJsonArray();
|
|
for(const QString &value : values) {
|
|
QJsonObject fileObj;
|
|
fileObj.insert(QStringLiteral("name"), value);
|
|
m_files << fileObj;
|
|
}
|
|
}
|
|
void Package::setValidation(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
for(const QString &value : values) {
|
|
if(value == QLatin1String("md5")) {
|
|
m_validationMethods = m_validationMethods | PackageValidation::Md5Sum;
|
|
} else if(value == QLatin1String("sha256")) {
|
|
m_validationMethods = m_validationMethods | PackageValidation::Sha256Sum;
|
|
} else if(value == QLatin1String("pgp")) {
|
|
m_validationMethods = m_validationMethods | PackageValidation::PgpSignature;
|
|
} else {
|
|
// TODO: error handling (imporant?)
|
|
}
|
|
}
|
|
}
|
|
m_hasInstallRelatedMetaData = true;
|
|
}
|
|
void Package::setGroups(const QStringList &values)
|
|
{
|
|
m_groups = values;
|
|
}
|
|
void Package::setFileName(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_fileName = values.back();
|
|
}
|
|
}
|
|
void Package::setInstallReason(const QStringList &values)
|
|
{
|
|
if(!values.isEmpty()) {
|
|
m_installReason = values.back().toUInt() == 1 ? InstallStatus::AsDependency : InstallStatus::Explicit;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \endcond
|
|
*/
|
|
|
|
/*!
|
|
* \brief Puts the specified desc/depends/files key value(s) pairs; clears current values before inserting new values.
|
|
* \remarks
|
|
* - This method should only be called by the associated repository because the associated repository might
|
|
* has to handle a possible name change.
|
|
* - Actual parsing of desc/depends/files is done in Repository::addPackagesFromSrcInfo() because one info file
|
|
* applies to multiple packages in case of split packages.
|
|
*/
|
|
void Package::putDescription(const QString &name, const QList<QPair<QString, QStringList> > &description, PackageOrigin origin)
|
|
{
|
|
// set name
|
|
if(!name.isEmpty()) {
|
|
m_name = name;
|
|
}
|
|
|
|
// clear current meta data
|
|
m_origin = origin;
|
|
m_hasInstallRelatedMetaData = origin == PackageOrigin::LocalDb;
|
|
m_fileName.clear();
|
|
m_description.clear();
|
|
m_upstreamUrl.clear();
|
|
m_buildArchitecture.clear();
|
|
m_licenses.clear();
|
|
m_dependencies.clear();
|
|
m_makeDependencies.clear();
|
|
m_checkDependencies.clear();
|
|
m_optionalDependencies.clear();
|
|
m_conflicts.clear();
|
|
m_provides.clear();
|
|
m_replaces.clear();
|
|
m_buildDate = m_installDate = DateTime();
|
|
m_packageSize = m_installedSize = 0;
|
|
m_files = QJsonArray();
|
|
m_md5.clear();
|
|
m_sha256.clear();
|
|
m_installReason = origin == PackageOrigin::LocalDb ? InstallStatus::Explicit : InstallStatus::None;
|
|
m_validationMethods = PackageValidation::Unknown;
|
|
|
|
// asign fields
|
|
for(const auto &pair : description) {
|
|
try {
|
|
(this->*m_descMap.at(pair.first))(pair.second);
|
|
} catch(const out_of_range &) {
|
|
}
|
|
}
|
|
|
|
// description provides source related meta data, too (except pkgbase, TODO: special flag required?)
|
|
m_hasBuildRelatedMetaData = m_hasSourceRelatedMetaData = true;
|
|
}
|
|
|
|
/*!
|
|
* \brief Adds a source file with the specified \a path and \a data.
|
|
*/
|
|
void Package::putSourceFile(const QString &path, const QByteArray &data)
|
|
{
|
|
m_sourceFiles.insert(make_pair(path, data));
|
|
}
|
|
|
|
/*!
|
|
* \class PackageVersion
|
|
* \brief The PackageVersion class helps parsing package versions.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new PackageVersion instance from the specified \a versionStr.
|
|
*/
|
|
PackageVersion::PackageVersion(const QString &versionStr)
|
|
{
|
|
// determine start offsets of version and release
|
|
const ushort *str = versionStr.utf16(), *versionBeg = nullptr, *releaseBeg = nullptr;
|
|
for(const auto *i = str; ; ++i) {
|
|
switch(*i) {
|
|
case 0:
|
|
goto terminationFound;
|
|
case ':':
|
|
if(!versionBeg && !releaseBeg) {
|
|
versionBeg = i + 1;
|
|
}
|
|
break;
|
|
case '-':
|
|
releaseBeg = i + 1;
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
terminationFound:
|
|
if(versionBeg) {
|
|
// epoch present
|
|
epoch = QString::fromUtf16(str, versionBeg - str - 1);
|
|
if(releaseBeg) {
|
|
// release present
|
|
version = QString::fromUtf16(versionBeg, releaseBeg - versionBeg - 1);
|
|
release = QString::fromUtf16(releaseBeg);
|
|
} else {
|
|
version = QString::fromUtf16(versionBeg);
|
|
release = QStringLiteral("1");
|
|
}
|
|
} else {
|
|
// epoch not present
|
|
epoch = QStringLiteral("0");
|
|
if(releaseBeg) {
|
|
// release present
|
|
version = QString::fromUtf16(str, releaseBeg - str - 1);
|
|
release = QString::fromUtf16(releaseBeg);
|
|
} else {
|
|
version = QString::fromUtf16(str);
|
|
release = QStringLiteral("1");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Constructs an empty package version.
|
|
*/
|
|
PackageVersion::PackageVersion()
|
|
{}
|
|
|
|
/*!
|
|
* \brief Returns the string representation of the package version: epoch:version-release
|
|
*/
|
|
QString RepoIndex::PackageVersion::toString() const
|
|
{
|
|
if(epoch.isEmpty() || epoch == QLatin1String("0")) {
|
|
return version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release);
|
|
} else {
|
|
return epoch % QChar(':') % version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Compares two version parts.
|
|
*/
|
|
PackageVersionPartComparsion PackageVersion::compareParts(const QString &part1, const QString &part2)
|
|
{
|
|
static const QRegExp nonAlphanumericPattern(QStringLiteral("[^a-zA-Z\\d:öäüÖÄÜß]"));
|
|
int part1Pos = 0, part2Pos = 0, part1End, part2End;
|
|
while(true) {
|
|
// determine current segments
|
|
part1End = part1.indexOf(nonAlphanumericPattern, part1Pos);
|
|
part2End = part2.indexOf(nonAlphanumericPattern, part2Pos);
|
|
auto segment1 = part1.midRef(part1Pos, part1End >= 0 ? part1End - part1Pos : -1);
|
|
auto segment2 = part2.midRef(part2Pos, part2End >= 0 ? part2End - part2Pos : -1);
|
|
// compare current segments
|
|
int digit1 = segment1.size();
|
|
int digit2 = segment2.size();
|
|
const auto *i1 = segment1.cbegin(), *e1 = segment1.cend();
|
|
const auto *i2 = segment2.cbegin(), *e2 = segment2.cend();
|
|
// trim leading zeros
|
|
for(; i1 < e1 && *i1 == QLatin1Char('0'); ++i1, --digit1);
|
|
for(; i2 < e2 && *i2 == QLatin1Char('0'); ++i2, --digit2);
|
|
if(digit1 > digit2) {
|
|
// segment 1 has more digits -> newer
|
|
return PackageVersionPartComparsion::Newer;
|
|
} else if (digit2 > digit1) {
|
|
// segment 2 has more digits -> newer
|
|
return PackageVersionPartComparsion::Older;
|
|
} else {
|
|
for(; i1 < e1 && i2 < e2; ++i1, ++i2) {
|
|
if(*i1 > *i2) {
|
|
// segment 1 digit has higher value -> newer
|
|
return PackageVersionPartComparsion::Newer;
|
|
} else if(*i2 > *i1) {
|
|
// segment 2 digit has higher value -> newer
|
|
return PackageVersionPartComparsion::Older;
|
|
}
|
|
}
|
|
}
|
|
// this segment is equal, look for the next segment
|
|
if(part1End >= 0 && part2End >= 0) {
|
|
// there is another segment in both parts
|
|
part1Pos = part1End + 1;
|
|
part2Pos = part2End + 1;
|
|
} else if(part1End >= 0) {
|
|
// only part 1 has another segment -> it is more specific and hence considered newer
|
|
return PackageVersionPartComparsion::Newer;
|
|
} else if(part2End >= 0) {
|
|
// only part 2 has another segment -> it is more specific and hence considered newer
|
|
return PackageVersionPartComparsion::Older;
|
|
} else {
|
|
// none of the parts has another segment -> parts are equal
|
|
return PackageVersionPartComparsion::Equal;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Compares this version with another version.
|
|
*
|
|
* This method distinguishes between software upgrades and package releases. See Alpm::PackageVersionComparsion enum.
|
|
*/
|
|
PackageVersionComparsion PackageVersion::compare(const PackageVersion &other) const
|
|
{
|
|
// check whether epoch differs
|
|
if(!epoch.isEmpty() || !other.epoch.isEmpty()) {
|
|
switch(compareParts(other.epoch, epoch)) {
|
|
case PackageVersionPartComparsion::Newer: return PackageVersionComparsion::SoftwareUpgrade;
|
|
case PackageVersionPartComparsion::Older: return PackageVersionComparsion::NewerThenSyncVersion;
|
|
case PackageVersionPartComparsion::Equal: ;
|
|
}
|
|
}
|
|
// check whether upstream version differs
|
|
switch(compareParts(other.version, version)) {
|
|
case PackageVersionPartComparsion::Newer: return PackageVersionComparsion::SoftwareUpgrade;
|
|
case PackageVersionPartComparsion::Older: return PackageVersionComparsion::NewerThenSyncVersion;
|
|
case PackageVersionPartComparsion::Equal: ;
|
|
}
|
|
// check whether package version differs
|
|
if(release.isEmpty() && !other.release.isEmpty()) {
|
|
// only consider package release if both versions specify it (otherwise consider packages equal)
|
|
switch(compareParts(other.release, release)) {
|
|
case PackageVersionPartComparsion::Newer: return PackageVersionComparsion::PackageUpgradeOnly;
|
|
case PackageVersionPartComparsion::Older: return PackageVersionComparsion::NewerThenSyncVersion;
|
|
case PackageVersionPartComparsion::Equal: ;
|
|
}
|
|
}
|
|
// no difference -> equal
|
|
return PackageVersionComparsion::Equal;
|
|
}
|
|
|
|
/*!
|
|
* \brief Constructs a dependency from the specified string.
|
|
* \remarks \a dependency might have version suffix.
|
|
*/
|
|
Dependency::Dependency(const QString &dependency)
|
|
{
|
|
int descrBeg = dependency.lastIndexOf(QChar(':'));
|
|
QStringRef actualDependency;
|
|
if(descrBeg > 0) {
|
|
description = dependency.midRef(descrBeg + 1).trimmed().toString();
|
|
actualDependency = dependency.midRef(0, descrBeg);
|
|
} else {
|
|
actualDependency = QStringRef(&dependency);
|
|
}
|
|
int suffixBeg;
|
|
if((suffixBeg = actualDependency.lastIndexOf(QLatin1String(">="))) > 0) {
|
|
mode = DependencyMode::GreatherEqual;
|
|
} else if((suffixBeg = actualDependency.lastIndexOf(QLatin1String("<="))) > 0) {
|
|
mode = DependencyMode::LessEqual;
|
|
} else if((suffixBeg = actualDependency.lastIndexOf(QChar('='))) > 0) {
|
|
mode = DependencyMode::Equal;
|
|
} else if((suffixBeg = actualDependency.lastIndexOf(QChar('<'))) > 0) {
|
|
mode = DependencyMode::LessThen;
|
|
} else if((suffixBeg = actualDependency.lastIndexOf(QChar('>'))) > 0) {
|
|
mode = DependencyMode::GreatherThen;
|
|
} else {
|
|
mode = DependencyMode::Any;
|
|
}
|
|
switch(mode) {
|
|
case DependencyMode::Any:
|
|
name = actualDependency.toString();
|
|
break;
|
|
case DependencyMode::GreatherEqual:
|
|
case DependencyMode::LessEqual:
|
|
name = actualDependency.mid(0, suffixBeg).toString();
|
|
version = actualDependency.mid(suffixBeg + 2).toString();
|
|
break;
|
|
case DependencyMode::Equal:
|
|
case DependencyMode::LessThen:
|
|
case DependencyMode::GreatherThen:
|
|
name = actualDependency.mid(0, suffixBeg).toString();
|
|
version = actualDependency.mid(suffixBeg + 1).toString();
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
QString Dependency::toString() const
|
|
{
|
|
switch(mode) {
|
|
case DependencyMode::Equal:
|
|
return name % QChar('=') % version;
|
|
case DependencyMode::GreatherEqual:
|
|
return name % QChar('>') % QChar('=') % version;
|
|
case DependencyMode::LessEqual:
|
|
return name % QChar('<') % QChar('=') % version;
|
|
case DependencyMode::GreatherThen:
|
|
return name % QChar('>') % version;
|
|
case DependencyMode::LessThen:
|
|
return name % QChar('<') % version;
|
|
default:
|
|
return name;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns a JSON object for the current instance.
|
|
*/
|
|
QJsonObject Dependency::toJson() const
|
|
{
|
|
QJsonObject obj;
|
|
obj.insert(QStringLiteral("name"), name);
|
|
obj.insert(QStringLiteral("ver"), version);
|
|
switch(mode) {
|
|
case DependencyMode::Any:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("any"));
|
|
break;
|
|
case DependencyMode::Equal:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("eq"));
|
|
break;
|
|
case DependencyMode::GreatherEqual:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("ge"));
|
|
break;
|
|
case DependencyMode::LessEqual:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("le"));
|
|
break;
|
|
case DependencyMode::GreatherThen:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("gt"));
|
|
break;
|
|
case DependencyMode::LessThen:
|
|
obj.insert(QStringLiteral("mod"), QStringLiteral("lt"));
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
if(!description.isEmpty()) {
|
|
obj.insert(QStringLiteral("desc"), description);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
}
|
|
|