From 8f32dd0da0fdaabd25dc339ceea54e51d22d6d32 Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 19 Aug 2015 02:13:28 +0200 Subject: [PATCH] added previous version to update results; started to work on build order resolver and mingw-w64 installer --- alpm/config.cpp | 67 +++++++++-- alpm/config.h | 72 +++++++++--- alpm/database.cpp | 23 +++- alpm/database.h | 139 ++++++++++++++-------- alpm/manager.cpp | 226 ++++++++++++++++-------------------- alpm/manager.h | 66 +++++++---- alpm/mingwbundle.cpp | 170 +++++++++++++++++++++++++++ alpm/mingwbundle.h | 31 +++++ alpm/package.cpp | 218 ++++++++++++++++++++++------------ alpm/package.h | 59 +++++++++- alpm/resolvebuildorder.cpp | 202 ++++++++++++++++++++++++++++++++ alpm/resolvebuildorder.h | 29 +++++ alpm/updatelookup.cpp | 185 +++++++++++++++++++++++++++++ alpm/updatelookup.h | 145 +++++++++++++++++++++++ main.cpp | 40 +++++-- network/aurquery.cpp | 121 ++++++++++++++----- network/aurquery.h | 43 ++++--- network/connection.cpp | 6 +- network/connection.h | 4 +- network/query.cpp | 12 -- network/query.h | 21 ---- network/server.cpp | 2 +- network/server.h | 4 +- repoindex.conf.js | 74 +++++------- repoindex.pro | 18 +-- web/css/core.css | 4 + web/index.html | 7 +- web/js/entrymanagement.js | 19 +-- web/js/packagemanagement.js | 71 +++++++---- web/js/pagemanagement.js | 6 +- web/js/repomanagement.js | 18 ++- web/js/utils.js | 6 +- 32 files changed, 1604 insertions(+), 504 deletions(-) create mode 100644 alpm/mingwbundle.cpp create mode 100644 alpm/mingwbundle.h create mode 100644 alpm/resolvebuildorder.cpp create mode 100644 alpm/resolvebuildorder.h create mode 100644 alpm/updatelookup.cpp create mode 100644 alpm/updatelookup.h delete mode 100644 network/query.cpp delete mode 100644 network/query.h diff --git a/alpm/config.cpp b/alpm/config.cpp index ba04712..aa0967a 100644 --- a/alpm/config.cpp +++ b/alpm/config.cpp @@ -1,4 +1,5 @@ #include "config.h" +#include "manager.h" #include @@ -25,7 +26,9 @@ namespace PackageManagement { */ ConfigArgs::ConfigArgs(ArgumentParser &parser) : helpArg(parser), + buildOrderArg("build-order", "b", "calculates the build order to build the specified packages"), serverArg("server", "s", "runs a websocket server providing the web interface with information"), + mingwBundleArg("mingw-w64-bundle", "m", "creates an archive with the runtime-relevant files from the specified mingw-w64-packages and their dependencies"), repoindexConfArg("repoindex-conf", "c", "specifies the path of the repo index config file (default is /etc/repoindex.conf"), rootdirArg("root-dir", "r", "specifies the root directory (default is /)"), dbpathArg("db-path", "d", "specifies the pacman database path (default is /var/lib/pacman)"), @@ -34,10 +37,20 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : websocketPortArg("port", "p", "specifies the listening port for the websocket server, default is 1234"), certFileArg("cert-file", string(), "specifies the SSL certificate"), keyFileArg("key-file", string(), "specifies the private SSL key"), - insecureArg("insecure", string(), "forces the server to run in insecure mode") + insecureArg("insecure", string(), "forces the server to run in insecure mode"), + aurArg("aur", "u", "enables/disables AUR queries"), + verboseArg("verbose", "v", "be verbose"), + outputFileArg("output-file", "f", "specifies the output file") { - initializer_list pathValueName = {"path"}; + const initializer_list pathValueName = {"path"}; + const initializer_list pkgValueNames = {"package 1", "package 2", "package 3"}; + buildOrderArg.setDenotesOperation(true); + buildOrderArg.setRequiredValueCount(-1); + buildOrderArg.setValueNames(pkgValueNames); serverArg.setDenotesOperation(true); + mingwBundleArg.setDenotesOperation(true); + mingwBundleArg.setRequiredValueCount(-1); + mingwBundleArg.setValueNames(pkgValueNames); repoindexConfArg.setCombinable(true); repoindexConfArg.setValueNames(pathValueName); repoindexConfArg.setRequiredValueCount(1); @@ -63,8 +76,18 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : keyFileArg.setValueNames(pathValueName); keyFileArg.setRequiredValueCount(1); insecureArg.setCombinable(true); - serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &insecureArg}); - parser.setMainArguments({&serverArg, &repoindexConfArg, &helpArg}); + aurArg.setCombinable(true); + aurArg.setRequiredValueCount(1); + aurArg.setValueNames({"enabled/disabled"}); + verboseArg.setCombinable(true); + outputFileArg.setCombinable(true); + outputFileArg.setRequired(true); + outputFileArg.setRequiredValueCount(1); + outputFileArg.setValueNames({"path"}); + serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &insecureArg, &aurArg}); + buildOrderArg.setSecondaryArguments({&aurArg, &verboseArg}); + mingwBundleArg.setSecondaryArguments({&outputFileArg}); + parser.setMainArguments({&buildOrderArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &helpArg}); } /*! @@ -77,12 +100,15 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : */ Config::Config() : m_alpmRootDir(QStringLiteral("/")), - m_alpmDbPath(QStringLiteral("/var/lib/pacman/")), + m_alpmDbPath(QStringLiteral("/var/lib/pacman")), m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")), m_websocketServerListeningAddr(QHostAddress::LocalHost), m_websocketServerListeningPort(1234), m_serverInsecure(false), - m_reposFromPacmanConf(false) + m_reposFromPacmanConf(false), + m_aurEnabled(true), + m_verbose(false), + m_runServer(false) {} /*! @@ -150,6 +176,8 @@ void Config::loadFromConfigFile(const QString &configFilePath) m_alpmRootDir = alpmObj.value(QStringLiteral("rootDir")).toString(m_alpmRootDir); m_alpmDbPath = alpmObj.value(QStringLiteral("dbPath")).toString(m_alpmDbPath); m_pacmanConfFile = alpmObj.value(QStringLiteral("pacmanConfigFile")).toString(m_pacmanConfFile); + auto aurObj = mainObj.value(QStringLiteral("aur")).toObject(); + m_aurEnabled = aurObj.value(QStringLiteral("enabled")).toBool(m_aurEnabled); auto serverObj = mainObj.value(QStringLiteral("server")).toObject(); assign(m_websocketServerListeningAddr, serverObj.value(QStringLiteral("websocketListeningAddr")).toString()); assign(m_websocketServerListeningPort, serverObj.value(QStringLiteral("websocketListeningPort"))); @@ -164,7 +192,7 @@ void Config::loadFromConfigFile(const QString &configFilePath) } } else { cerr << "Error: Unable to parse config file \"" << configFilePath.toLocal8Bit().data() << "\": " - << error.errorString().toLocal8Bit().data() << endl; + << error.errorString().toLocal8Bit().data() << " at character " << error.offset << endl; } } else { cerr << "Error: Unable to open config file \"" << configFilePath.toLocal8Bit().data() << "\"." << endl; @@ -200,6 +228,16 @@ void Config::loadFromArgs(const ConfigArgs &args) assign(m_alpmRootDir, args.rootdirArg); assign(m_alpmDbPath, args.dbpathArg); assign(m_pacmanConfFile, args.pacmanConfArg); + if(args.aurArg.isPresent()) { + auto val = args.aurArg.values().front(); + if(val == "enabled") { + m_aurEnabled = true; + } else if(val == "disabled") { + m_aurEnabled = false; + } else { + cerr << "Warning: The specified value for the argument --aur-enabled is invalid and will be ignored." << endl; + } + } assign(m_websocketServerListeningPort, args.websocketPortArg); assign(m_serverCertFile, args.certFileArg); assign(m_serverKeyFile, args.keyFileArg); @@ -207,6 +245,8 @@ void Config::loadFromArgs(const ConfigArgs &args) if(args.websocketAddrArg.isPresent()) { assign(m_websocketServerListeningAddr, QString::fromLocal8Bit(args.websocketAddrArg.values().front().data(), args.websocketAddrArg.values().front().size())); } + m_verbose = args.verboseArg.isPresent(); + m_runServer = args.serverArg.isPresent(); } /*! @@ -216,15 +256,22 @@ void RepoEntry::load(const QJsonValue &jsonValue) { auto obj = jsonValue.toObject(); m_name = obj.value(QStringLiteral("name")).toString(m_name); - m_dbPath = obj.value(QStringLiteral("dbpath")).toString(m_dbPath); - m_srcPath = obj.value(QStringLiteral("srcpath")).toString(m_srcPath); - m_pkgPath = obj.value(QStringLiteral("pkgpath")).toString(m_pkgPath); + m_dataBaseFile = obj.value(QStringLiteral("dataBaseFile")).toString(m_dataBaseFile); + m_sourceDir = obj.value(QStringLiteral("sourcesDir")).toString(m_sourceDir); + m_packageDir = obj.value(QStringLiteral("packagesDir")).toString(m_packageDir); for(const auto &server : obj.value(QStringLiteral("servers")).toArray()) { auto str = server.toString(); if(!str.isEmpty()) { m_servers << str; } } + for(const auto &upgradeSources : obj.value(QStringLiteral("upgradeSources")).toArray()) { + auto str = upgradeSources.toString(); + if(!str.isEmpty()) { + m_upgradeSources << str; + } + } + m_sigLevel = Manager::parseSigLevel(obj.value(QStringLiteral("sigLevel")).toString().toLocal8Bit().data()); } } // namespace Alpm diff --git a/alpm/config.h b/alpm/config.h index 7f7ba7e..333b6c9 100644 --- a/alpm/config.h +++ b/alpm/config.h @@ -18,7 +18,9 @@ class ConfigArgs public: ConfigArgs(ApplicationUtilities::ArgumentParser &parser); ApplicationUtilities::HelpArgument helpArg; + ApplicationUtilities::Argument buildOrderArg; ApplicationUtilities::Argument serverArg; + ApplicationUtilities::Argument mingwBundleArg; ApplicationUtilities::Argument repoindexConfArg; ApplicationUtilities::Argument rootdirArg; ApplicationUtilities::Argument dbpathArg; @@ -28,6 +30,9 @@ public: ApplicationUtilities::Argument certFileArg; ApplicationUtilities::Argument keyFileArg; ApplicationUtilities::Argument insecureArg; + ApplicationUtilities::Argument aurArg; + ApplicationUtilities::Argument verboseArg; + ApplicationUtilities::Argument outputFileArg; }; class Config; @@ -36,39 +41,48 @@ class RepoEntry { friend class Config; public: + RepoEntry(); const QString &name() const; - const QString &dbPath() const; - const QString &srcPath() const; - const QString &pkgPath() const; + const QString &dataBasePath() const; + const QString &sourceDir() const; + const QString &packageDir() const; const QStringList &servers() const; + const QStringList &upgradeSources() const; + int sigLevel() const; void load(const QJsonValue &jsonValue); private: QString m_name; - QString m_dbPath; - QString m_srcPath; - QString m_pkgPath; + QString m_dataBaseFile; + QString m_sourceDir; + QString m_packageDir; QStringList m_servers; + QStringList m_upgradeSources; + int m_sigLevel; }; +inline RepoEntry::RepoEntry() : + m_sigLevel(0) +{} + inline const QString &RepoEntry::name() const { return m_name; } -inline const QString &RepoEntry::dbPath() const +inline const QString &RepoEntry::dataBasePath() const { - return m_dbPath; + return m_dataBaseFile; } -inline const QString &RepoEntry::srcPath() const +inline const QString &RepoEntry::sourceDir() const { - return m_srcPath; + return m_sourceDir; } -inline const QString &RepoEntry::pkgPath() const +inline const QString &RepoEntry::packageDir() const { - return m_pkgPath; + return m_packageDir; } inline const QStringList &RepoEntry::servers() const @@ -76,6 +90,16 @@ inline const QStringList &RepoEntry::servers() const return m_servers; } +inline const QStringList &RepoEntry::upgradeSources() const +{ + return m_upgradeSources; +} + +inline int RepoEntry::sigLevel() const +{ + return m_sigLevel; +} + class Config { public: @@ -91,6 +115,9 @@ public: bool serverInsecure() const; bool reposFromPacmanConf() const; const QList &repoEntries() const; + bool isAurEnabled() const; + bool isVerbose() const; + bool runServer() const; void loadFromConfigFile(const QString &args); void loadFromConfigFile(const ConfigArgs &args); @@ -107,9 +134,11 @@ private: QString m_serverKeyFile; bool m_serverInsecure; - bool m_reposFromPacmanConf; QList m_repoEntries; - + bool m_reposFromPacmanConf; + bool m_aurEnabled; + bool m_verbose; + bool m_runServer; }; inline const QString &Config::alpmRootDir() const @@ -162,6 +191,21 @@ inline const QList &Config::repoEntries() const return m_repoEntries; } +inline bool Config::isAurEnabled() const +{ + return m_aurEnabled; +} + +inline bool Config::isVerbose() const +{ + return m_verbose; +} + +inline bool Config::runServer() const +{ + return m_runServer; +} + } // namespace Alpm #endif // ALPM_CONFIG_H diff --git a/alpm/database.cpp b/alpm/database.cpp index 166edf1..788ee16 100644 --- a/alpm/database.cpp +++ b/alpm/database.cpp @@ -1,5 +1,6 @@ #include "database.h" #include "group.h" +#include "updatelookup.h" #include #include @@ -38,6 +39,20 @@ const QJsonArray &AlpmDataBase::serverUrls() const return m_serverUrls; } +/*! + * \brief Adds the specified server URLs. + */ +bool AlpmDataBase::addServerUrls(const QStringList &urls) +{ + m_serverUrls = QJsonArray(); + for(const auto &url : urls) { + if(alpm_db_add_server(m_ptr, url.toLocal8Bit().data()) != 0) { + return false; + } + } + return true; +} + /*! * \brief Returns the packages of the database. */ @@ -95,7 +110,7 @@ QJsonObject AlpmDataBase::groupInfo() const return groupInfo; } -void AlpmDataBase::checkForUpgrades(const QList &syncDbs, UpdateLookupResults &results) const +void AlpmDataBase::checkForUpgrades(const QList &syncDbs, UpdateLookupResults &results) const { if(syncDbs.isEmpty()) { results.noSources = true; @@ -110,13 +125,13 @@ void AlpmDataBase::checkForUpgrades(const QList &syncDbs, case PackageVersionComparsion::Equal: break; // ignore equal packages case PackageVersionComparsion::SoftwareUpgrade: - results.newVersions << syncPkg; + results.newVersions << makeUpdateResult(syncPkg, dbPkg.second.version()); break; case PackageVersionComparsion::PackageUpgradeOnly: - results.newReleases << syncPkg; + results.newReleases << makeUpdateResult(syncPkg, dbPkg.second.version()); break; case PackageVersionComparsion::NewerThenSyncVersion: - results.downgrades << syncPkg; + results.downgrades << makeUpdateResult(syncPkg, dbPkg.second.version()); } orphaned = false; } catch(out_of_range &) { diff --git a/alpm/database.h b/alpm/database.h index fc48559..dbb7ff3 100644 --- a/alpm/database.h +++ b/alpm/database.h @@ -2,7 +2,7 @@ #define ALPM_DATABASE_H #include "list.h" -#include "package.h" +#include "updatelookup.h" #include @@ -13,51 +13,10 @@ namespace PackageManagement { -class UpdateLookupResults -{ -public: - UpdateLookupResults(); - - /*! - * \brief Indicates that there are no upgrade sources available. - */ - bool noSources; - - /*! - * \brief Packages providing a software upgrade (new version). - */ - QList newVersions; - - /*! - * \brief Package upgrades only (new release). - */ - QList newReleases; - - /*! - * \brief Downgrades (older version in sync db). - */ - QList downgrades; - - /*! - * \brief Orphaned packages (could not be found in any of the sync dbs). - */ - QList orphaned; - - QStringList warnings; - QStringList errors; -}; - -/*! - * \brief Constructs new update lookup results. - */ -inline UpdateLookupResults::UpdateLookupResults() : - noSources(false) -{} - class AlpmDataBase { public: - AlpmDataBase(alpm_db_t *dataBase = nullptr); + AlpmDataBase(alpm_db_t *dataBase = nullptr, const QString &dbFile = QString(), const QString &srcDir = QString(), const QString &pkgDir = QString()); // operators operator bool() const; @@ -67,11 +26,17 @@ public: // database properties alpm_db_t *ptr() const; const char *name() const; + const QString &dataBaseFile() const; + const QString &sourcesDirectory() const; + void setSourcesDirectory(const QString &dir); + const QString &packagesDirectory() const; + void setPackagesDirectory(const QString &dir); alpm_siglevel_t sigLevel() const; alpm_db_usage_t usage() const; StringList servers() const; - const QJsonArray &serverUrls() const; bool setServers(StringList servers); + const QJsonArray &serverUrls() const; + bool addServerUrls(const QStringList &urls); alpm_pkg_t *package(const char *name) const; const std::map &packages() const; QStringList packageNames() const; @@ -84,11 +49,16 @@ public: // upgrade lookup const QStringList &upgradeSources() const; QStringList &upgradeSources(); - void checkForUpgrades(const QList &syncDbs, UpdateLookupResults &results) const; + template + void checkForUpgrades(const std::map &syncDbPkgs, UpdateLookupResults &results) const; + void checkForUpgrades(const QList &syncDbs, UpdateLookupResults &results) const; private: alpm_db_t *m_ptr; - QStringList m_updateSources; + QString m_dbFile; + QString m_srcDir; + QString m_pkgDir; + QStringList m_upgradeSources; mutable QJsonArray m_serverUrls; mutable std::map m_packages; mutable QJsonArray m_packageNamesJsonArray; @@ -98,8 +68,11 @@ private: /*! * \brief Creates a new instance wrapping the specified database struct. */ -inline AlpmDataBase::AlpmDataBase(alpm_db_t *dataBase) : - m_ptr(dataBase) +inline AlpmDataBase::AlpmDataBase(alpm_db_t *dataBase, const QString &dbFile, const QString &srcDir, const QString &pkgDir) : + m_ptr(dataBase), + m_dbFile(dbFile), + m_srcDir(srcDir), + m_pkgDir(pkgDir) {} /*! @@ -134,6 +107,46 @@ inline const char *AlpmDataBase::name() const return alpm_db_get_name(m_ptr); } +/*! + * \brief Returns the path of the data base file. + */ +inline const QString &AlpmDataBase::dataBaseFile() const +{ + return m_dbFile; +} + +/*! + * \brief Returns the path of the local sources directory. + */ +inline const QString &AlpmDataBase::sourcesDirectory() const +{ + return m_srcDir; +} + +/*! + * \brief Sets the path of the local sources directory. + */ +inline void AlpmDataBase::setSourcesDirectory(const QString &dir) +{ + m_srcDir = dir; +} + +/*! + * \brief Returns the path of the local packages directory. + */ +inline const QString &AlpmDataBase::packagesDirectory() const +{ + return m_pkgDir; +} + +/*! + * \brief Sets the path of the local packages directory. + */ +inline void AlpmDataBase::setPackagesDirectory(const QString &dir) +{ + m_pkgDir = dir; +} + /*! * \brief Returns the signature level of the database. */ @@ -155,7 +168,7 @@ inline StringList AlpmDataBase::servers() const */ inline bool AlpmDataBase::setServers(StringList servers) { - return alpm_db_set_servers(m_ptr, servers.begin().ptr()) != 0; + return alpm_db_set_servers(m_ptr, servers.begin().ptr()) == 0; } /*! @@ -195,7 +208,7 @@ inline PackageList AlpmDataBase::search(StringList terms) const */ inline const QStringList &AlpmDataBase::upgradeSources() const { - return m_updateSources; + return m_upgradeSources; } /*! @@ -203,7 +216,7 @@ inline const QStringList &AlpmDataBase::upgradeSources() const */ inline QStringList &AlpmDataBase::upgradeSources() { - return m_updateSources; + return m_upgradeSources; } /*! @@ -214,6 +227,30 @@ inline PackageManagement::AlpmDataBase::operator bool() const return m_ptr != nullptr; } +template +void AlpmDataBase::checkForUpgrades(const std::map &syncDbPkgs, UpdateLookupResults &results) const +{ + for(const auto &dbPkg : packages()) { + try { + const auto &syncPkg = syncDbPkgs.at(dbPkg.first); + switch(dbPkg.second.compareVersion(syncPkg)) { + case PackageVersionComparsion::Equal: + break; // ignore equal packages + case PackageVersionComparsion::SoftwareUpgrade: + results.newVersions << makeUpdateResult(syncPkg, dbPkg.second.version()); + break; + case PackageVersionComparsion::PackageUpgradeOnly: + results.newReleases << makeUpdateResult(syncPkg, dbPkg.second.version()); + break; + case PackageVersionComparsion::NewerThenSyncVersion: + results.downgrades << makeUpdateResult(syncPkg, dbPkg.second.version()); + } + } catch(std::out_of_range &) { + results.orphaned << dbPkg.second; + } + } +} + } // namespace Alpm #endif // ALPM_DATABASE_H diff --git a/alpm/manager.cpp b/alpm/manager.cpp index 72699c3..3590d94 100644 --- a/alpm/manager.cpp +++ b/alpm/manager.cpp @@ -8,10 +8,9 @@ #include #include -#include #include #include - +#include #include #include #include @@ -38,8 +37,7 @@ constexpr int defaultSigLevel = ALPM_SIG_PACKAGE | ALPM_SIG_PACKAGE_OPTIONAL | * \param rootdir Specifies the root directory. * \param dbpath Specifies the database directory. */ -Manager::Manager(const Config &config, QObject *parent) : - QObject(parent), +Manager::Manager(const Config &config) : m_config(config), m_sigLevel(defaultSigLevel), m_localFileSigLevel(ALPM_SIG_USE_DEFAULT), @@ -62,7 +60,7 @@ Manager::~Manager() /*! * \brief Returns the package with the specified name from the specified database. */ -AlpmPackage Manager::packageFromSyncDataBase(const QString &dbName, const QString &pkgName) +AlpmPackage Manager::packageFromSyncDataBase(const QString &dbName, const QString &pkgName) const { try { if(dbName == QLatin1String("local")) { @@ -115,7 +113,7 @@ const string &lastValue(const multimap &mm, const string &key) /*! * \brief Parses a "SigLevel" denotation from pacman config file. */ -int parseSigLevel(const string &sigLevelStr = string()) +int Manager::parseSigLevel(const string &sigLevelStr) { int sigLevel = defaultSigLevel; // split sig level denotation into parts @@ -179,7 +177,7 @@ int parseSigLevel(const string &sigLevelStr = string()) /*! * \brief Parses a "Usage" denotation from pacman config file. */ -int parseUsage(const string &usageStr) +int Manager::parseUsage(const string &usageStr) { int usage = 0; const auto parts = splitString >(usageStr, " "); @@ -200,9 +198,9 @@ int parseUsage(const string &usageStr) } /*! - * \brief Parses the Pacman configuration. Registers the listed sync databases. + * \brief Parses and applies the Pacman configuration. Registers the listed sync databases. */ -void Manager::parsePacmanConfig() +void Manager::applyPacmanConfig() { // open config file and parse as ini try { @@ -229,7 +227,7 @@ void Manager::parsePacmanConfig() } const auto &specifiedDir = lastValue(options, "CacheDir"); if(!specifiedDir.empty()) { - m_cacheDir = QString::fromStdString(specifiedDir); + m_pacmanCacheDir = QString::fromStdString(specifiedDir); } globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey)); } catch(const out_of_range &) { @@ -242,9 +240,9 @@ void Manager::parsePacmanConfig() if(scope.first != "options") { // read and validate database name QString dbName = QString::fromLocal8Bit(scope.first.c_str()); - if(dbName == QLatin1String("local")) { + if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) { cerr << "Error: Unable to add database from pacman config: The database name mustn't be \"local\" because this name is reserved for the local database." << endl; - } else if(dbName.startsWith(QLatin1String("aur"))) { + } else if(dbName.startsWith(QLatin1String("aur"), Qt::CaseInsensitive)) { cerr << "Error: Unable to add database from pacman config: The database name mustn't start with \"aur\" because this name is reserved for the Arch Linux User Repository." << endl; } else if(m_syncDbs.count(dbName)) { cerr << "Error: Unable to add database from pacman config: Database names must be unique. Ignoring second occurance of database \"" << scope.first << "\"." << endl; @@ -257,9 +255,13 @@ void Manager::parsePacmanConfig() if(alpm_db_t *db = alpm_register_syncdb(m_handle, scope.first.c_str(), static_cast(sigLevel))) { // set usage if(alpm_db_set_usage(db, static_cast(usage)) == 0) { - cerr << "Added database [" << scope.first << "]" << endl; + if(m_config.isVerbose() || m_config.runServer()) { + cerr << "Added database [" << scope.first << "]" << endl; + } } else { - cerr << "Warning: Added database [" << scope.first << "] but failed to set usage" << endl; + if(m_config.isVerbose() || m_config.runServer()) { + cerr << "Warning: Added database [" << scope.first << "] but failed to set usage" << endl; + } } // add servers for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) { @@ -267,7 +269,9 @@ void Manager::parsePacmanConfig() findAndReplace(url, "$repo", scope.first); findAndReplace(url, "$arch", arch); alpm_db_add_server(db, url.c_str()); - cerr << "Added server: " << url << endl; + if(m_config.isVerbose() || m_config.runServer()) { + cerr << "Added server: " << url << endl; + } } // add included servers for(auto range = scope.second.equal_range("Include"); range.first != range.second; ++range.first) { @@ -290,7 +294,9 @@ void Manager::parsePacmanConfig() findAndReplace(url, "$repo", scope.first); findAndReplace(url, "$arch", arch); alpm_db_add_server(db, url.c_str()); - cerr << "Added server: " << url << endl; + if(m_config.isVerbose() || m_config.runServer()) { + cerr << "Added server: " << url << endl; + } } } catch (const out_of_range &) { cerr << "Warning: Included file \"" << path << "\" has no values." << endl; @@ -301,15 +307,58 @@ void Manager::parsePacmanConfig() // -> db is used to upgrade local database localDataBase().upgradeSources() << dbName; } - m_syncDbs.emplace(dbName, db); + m_syncDbs.emplace(dbName, AlpmDataBase(db, QStringLiteral("%1/sync/%2").arg(m_config.alpmDbPath(), dbName))); } else { - cerr << "Unable to add sync database [" << scope.first << "]" << endl; + cerr << "Error: Unable to add sync database [" << scope.first << "]" << endl; } } } } } catch (const ios_base::failure &) { - throw ios_base::failure("An IO exception occured when parsing the config file."); + throw ios_base::failure("Error: An IO exception occured when parsing the config file."); + } +} + +/*! + * \brief Applies the repository index configuration. + */ +void Manager::applyRepoIndexConfig() +{ + for(const RepoEntry &repoEntry : m_config.repoEntries()) { + AlpmDataBase *syncDb; + try { + syncDb = &m_syncDbs.at(repoEntry.name()); + cerr << "Applying config for database [" << syncDb->name() << "]" << endl; + if(!repoEntry.dataBasePath().isEmpty()) { + cerr << "Warning: Can't use data base path specified in repo index config because the repo \"" + << repoEntry.name().toLocal8Bit().data() << "\" has already been added from the Pacman config." << endl; + } + if(repoEntry.sigLevel()) { + cerr << "Warning: Can't use sig level path specified in repo index config because the repo \"" + << repoEntry.name().toLocal8Bit().data() << "\" has already been added from the Pacman config." << endl; + } + syncDb->setPackagesDirectory(repoEntry.packageDir()); + syncDb->setSourcesDirectory(repoEntry.sourceDir()); + } catch(const out_of_range &) { + if(repoEntry.name().compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) { + cerr << "Error: Unable to add database from repo index config: The database name mustn't be \"local\" because this name is reserved for the local database." << endl; + } else if(repoEntry.name().startsWith(QLatin1String("aur"), Qt::CaseInsensitive)) { + cerr << "Error: Unable to add database from repo index config: The database name mustn't start with \"aur\" because this name is reserved for the Arch Linux User Repository." << endl; + } else { + auto *db = alpm_register_syncdb(m_handle, repoEntry.name().toLocal8Bit().data(), static_cast(repoEntry.sigLevel())); + auto emplaced = m_syncDbs.emplace(repoEntry.name(), AlpmDataBase(db, repoEntry.dataBasePath(), repoEntry.sourceDir(), repoEntry.packageDir())); + if(emplaced.second) { + syncDb = &emplaced.first->second; + if(m_config.isVerbose() || m_config.runServer()) { + cerr << "Added database [" << repoEntry.name().toLocal8Bit().data() << "]" << endl; + } + } + } + } + if(syncDb) { + syncDb->addServerUrls(repoEntry.servers()); + syncDb->upgradeSources() << repoEntry.upgradeSources(); + } } } @@ -329,7 +378,10 @@ void Manager::unregisterSyncDataBases() const AlpmDataBase &Manager::localDataBase() const { if(!m_localDb) { - m_localDb = alpm_get_localdb(m_handle); + QMutexLocker locker(&m_localDbMutex); + if(!m_localDb) { + m_localDb = alpm_get_localdb(m_handle); + } } return m_localDb; } @@ -365,6 +417,7 @@ QJsonObject Manager::basicRepoInfo(AlpmDataBase db, const QString &name, const Q repoInfo.insert(QStringLiteral("servers"), db.serverUrls()); repoInfo.insert(QStringLiteral("usage"), Utilities::usageStrings(db.usage())); repoInfo.insert(QStringLiteral("sigLevel"), Utilities::sigLevelStrings(db.sigLevel())); + repoInfo.insert(QStringLiteral("upgradeSources"), QJsonArray::fromStringList(db.upgradeSources())); repoInfo.insert(QStringLiteral("packages"), db.packageNameJsonArray()); return repoInfo; } @@ -378,15 +431,18 @@ QJsonObject Manager::basicRepoInfo(AlpmDataBase db, const QString &name, const Q const QJsonArray &Manager::basicRepoInfo() const { if(m_basicRepoInfo.isEmpty()) { - m_basicRepoInfo << basicRepoInfo(localDataBase(), QStringLiteral("local"), QStringLiteral("The local database.")); - auto const &syncDbs = syncDataBases(); - for(const auto &syncDb : syncDbs) { - // check if the "sync" database is actually used for syncing - auto usage = syncDb.second.usage(); - if((usage & ALPM_DB_USAGE_SYNC) || (usage & ALPM_DB_USAGE_INSTALL) || (usage & ALPM_DB_USAGE_UPGRADE)) { - m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The sync database »%1«.").arg(syncDb.first)); - } else { - m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The database »%1«.").arg(syncDb.first)); + QMutexLocker locker(&m_basicRepoInfoMutex); + if(m_basicRepoInfo.isEmpty()) { + m_basicRepoInfo << basicRepoInfo(localDataBase(), QStringLiteral("local"), QStringLiteral("The local database.")); + auto const &syncDbs = syncDataBases(); + for(const auto &syncDb : syncDbs) { + // check if the "sync" database is actually used for syncing + auto usage = syncDb.second.usage(); + if((usage & ALPM_DB_USAGE_SYNC) || (usage & ALPM_DB_USAGE_INSTALL) || (usage & ALPM_DB_USAGE_UPGRADE)) { + m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The sync database »%1«.").arg(syncDb.first)); + } else { + m_basicRepoInfo << basicRepoInfo(syncDb.second, syncDb.first, QStringLiteral("The database »%1«.").arg(syncDb.first)); + } } } } @@ -396,7 +452,7 @@ const QJsonArray &Manager::basicRepoInfo() const /*! * \brief Returns package information for the specified selection of packages. */ -const QJsonArray Manager::packageInfo(const QJsonArray &pkgSelection, bool full) +const QJsonArray Manager::packageInfo(const QJsonArray &pkgSelection, bool full) const { QJsonArray pkgInfos; for(const auto &pkgSelJsonVal : pkgSelection) { @@ -426,9 +482,12 @@ const QJsonArray Manager::packageInfo(const QJsonArray &pkgSelection, bool full) const QJsonArray &Manager::groupInfo() const { if(m_groupInfo.empty()) { - m_groupInfo << localDataBase().groupInfo(); - for(const auto &db : m_syncDbs) { - m_groupInfo << db.second.groupInfo(); + QMutexLocker locker(&m_groupInfoMutex); + if(m_groupInfo.empty()) { + m_groupInfo << localDataBase().groupInfo(); + for(const auto &db : m_syncDbs) { + m_groupInfo << db.second.groupInfo(); + } } } return m_groupInfo; @@ -468,103 +527,19 @@ AlpmDataBase &Manager::dataBaseByName(const QString &dbName) * - syncdbs: Array with the names of the databases used as upgrade sources. * If not present, appropriate upgrade sources will be determined automatically. */ -QJsonObject Manager::invokeUpgradeLookup(const QJsonObject &request) const +void Manager::invokeUpgradeLookup(const QJsonObject &request, UpdateLookupCallback callback) const { - QJsonObject result; - QJsonArray errors; - const AlpmDataBase &db = dataBaseByName(request.value(QStringLiteral("db")).toString()); - QJsonValue syncDbsValue = request.value(QStringLiteral("syncdbs")); - if(!db) { - errors << QStringLiteral("Database to be checked not found."); - } else { - QJsonArray warnings; - bool searchAur = request.value(QStringLiteral("aur")).toBool(false); - if(searchAur) { - m_aur.requestPackageInfo(db.packageNames()); - } - const auto results = checkForUpgrades(db, syncDbsValue); - if(searchAur || !results.noSources) { - QJsonArray softwareUpdates; - QJsonArray packageOnlyUpdates; - QJsonArray downgrades; - QJsonArray orphanedPackages; - if(!results.noSources) { - for(const auto pkg : results.newVersions) { - softwareUpdates << pkg.basicInfo(true); - } - for(const auto pkg : results.newReleases) { - packageOnlyUpdates << pkg.basicInfo(true); - } - for(const auto pkg : results.downgrades) { - downgrades << pkg.basicInfo(true); - } - for(const auto pkg : results.orphaned) { - orphanedPackages << pkg.basicInfo(true); - } - } - if(!warnings.isEmpty()) { - result.insert(QStringLiteral("warnings"), warnings); - } - result.insert(QStringLiteral("softwareUpdates"), softwareUpdates); - result.insert(QStringLiteral("packageOnlyUpdates"), packageOnlyUpdates); - result.insert(QStringLiteral("downgrades"), downgrades); - result.insert(QStringLiteral("orphanedPackages"), orphanedPackages); - } else { - errors << QStringLiteral("No update sources associated for database \"%1\".").arg(QString::fromLocal8Bit(db.name())); - } - } - if(!errors.isEmpty()) { - result.insert(QStringLiteral("errors"), errors); - } - return result; -} - -/*! - * \brief Checks the specified database for upgrades. - */ -const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db, const QJsonValue &syncDbsValue) const -{ - UpdateLookupResults results; - const auto &syncDbs = syncDataBases(); - QList syncDbSel; - if(syncDbsValue.type() == QJsonValue::Array) { - for(const auto &syncDbVal : syncDbsValue.toArray()) { - const auto syncDbName = syncDbVal.toString(); - if(syncDbName == QLatin1String("local")) { - syncDbSel << &(localDataBase()); - } else { - try { - syncDbSel << &(syncDbs.at(syncDbName)); - } catch(out_of_range &) { - results.warnings << QStringLiteral("The specified sync database \"%1\" can not be found.").arg(syncDbName); - } - } - } - } else { - for(const auto &syncDbName : db.upgradeSources()) { - if(syncDbName == QLatin1String("local")) { - syncDbSel << &(localDataBase()); - } else { - try { - syncDbSel << &(syncDbs.at(syncDbName)); - } catch(out_of_range &) { - results.warnings << QStringLiteral("The associated upgrade database \"%1\" can not be found.").arg(syncDbName); - } - } - } - } - db.checkForUpgrades(syncDbSel, results); - return results; + new UpdateLookup(*this, request, callback); // this object will delete itself } /*! * \brief Checks the specified database for upgrades. * - * Appropriate upgrade sources will be determined automatically. + * Appropriate upgrade sources will be determined automatically; does not check the AUR. */ -const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db) const +const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db) const { - UpdateLookupResults results; + UpdateLookupResults results; QList syncDbSel; for(const auto &syncDbName : db.upgradeSources()) { try { @@ -577,11 +552,4 @@ const UpdateLookupResults Manager::checkForUpgrades(const AlpmDataBase &db) cons return results; } -//void Manager::addTask() -//{ -// QUuid uuid; -// while(m_tasks.find(uuid = QUuid::createUuid()) != m_tasks.cend()); -// m_tasks.emplace(uuid); -//} - } diff --git a/alpm/manager.h b/alpm/manager.h index 070992c..7941732 100644 --- a/alpm/manager.h +++ b/alpm/manager.h @@ -1,45 +1,52 @@ #ifndef ALPM_MANAGER_H #define ALPM_MANAGER_H -#include "package.h" #include "database.h" +#include "updatelookup.h" #include "network/aurquery.h" #include #include +#include #include -#include -#include +#include #include -#include namespace PackageManagement { class Config; +template class UpdateLookupResults; -class Manager : QObject +class Manager { public: - Manager(const Config &config, QObject *parent = nullptr); + explicit Manager(const Config &config); Manager(const Manager &other) = delete; Manager(Manager &&other) = delete; Manager &operator =(const Manager &other) = delete; ~Manager(); // configuration, signature level, etc + const Config &config() const; int sigLevel() const; void setSigLevel(int sigLevel); int localFileSigLevel() const; void setLocalFileSigLevel(int sigLevel); - const QString &cacheDir() const; - void parsePacmanConfig(); + const QString &pacmanCacheDir() const; + static int parseSigLevel(const std::string &sigLevelStr = std::string()); + static int parseUsage(const std::string &usageStr); + void applyPacmanConfig(); + void applyRepoIndexConfig(); + void unregisterSyncDataBases(); + const AurQuery &aurQuery() const; + AurQuery &aurQuery(); // package lookup - AlpmPackage packageFromSyncDataBase(const QString &dbName, const QString &pkgName); + AlpmPackage packageFromSyncDataBase(const QString &dbName, const QString &pkgName) const; AlpmPackage packageFromSyncDataBases(const char *name) const; AlpmOwnershipPackage packageFromFile(const char *fileName, bool verifyIntegrity = false); bool isPackageIgnored(AlpmPackage package) const; @@ -51,18 +58,14 @@ public: const std::map &syncDataBases() const; const AlpmDataBase &dataBaseByName(const QString &dbName) const; AlpmDataBase &dataBaseByName(const QString &dbName); - const UpdateLookupResults checkForUpgrades(const AlpmDataBase &db) const; - - // asynchronous tasks -// void addTask(); + const UpdateLookupResults checkForUpgrades(const AlpmDataBase &db) const; // JSON serialization, handling JSON requests QJsonObject basicRepoInfo(AlpmDataBase db, const QString &name, const QString &desc = QString()) const; const QJsonArray &basicRepoInfo() const; - const QJsonArray packageInfo(const QJsonArray &pkgSelection, bool full = true); + const QJsonArray packageInfo(const QJsonArray &pkgSelection, bool full = true) const; const QJsonArray &groupInfo() const; - QJsonObject invokeUpgradeLookup(const QJsonObject &request) const; - const UpdateLookupResults checkForUpgrades(const AlpmDataBase &db, const QJsonValue &syncDbsValue) const; + void invokeUpgradeLookup(const QJsonObject &request, UpdateLookupCallback callback) const; private: void cleanup(); @@ -71,16 +74,27 @@ private: alpm_handle_t *m_handle; int m_sigLevel; int m_localFileSigLevel; - QString m_cacheDir; + QString m_pacmanCacheDir; QNetworkAccessManager m_networkAccessManager; AurQuery m_aur; mutable AlpmDataBase m_localDb; + mutable QMutex m_localDbMutex; mutable std::map m_syncDbs; mutable QJsonArray m_basicRepoInfo; - mutable QJsonArray m_packageInfo; + mutable QMutex m_basicRepoInfoMutex; mutable QJsonArray m_groupInfo; + mutable QMutex m_groupInfoMutex; }; +/*! + * \brief Returns the configuration of the manager. + * \remarks The configuration has been specified when constructing the manager. + */ +inline const Config &Manager::config() const +{ + return m_config; +} + /*! * \brief Returns the signature level. * @@ -138,12 +152,22 @@ inline bool Manager::isPackageIgnored(AlpmPackage package) const } /*! - * \brief Returns the cache directory. + * \brief Returns the Pacman cache directory. * \remarks This is read from the Pacman configuration. */ -inline const QString &Manager::cacheDir() const +inline const QString &Manager::pacmanCacheDir() const { - return m_cacheDir; + return m_pacmanCacheDir; +} + +inline const AurQuery &Manager::aurQuery() const +{ + return m_aur; +} + +inline AurQuery &Manager::aurQuery() +{ + return m_aur; } } diff --git a/alpm/mingwbundle.cpp b/alpm/mingwbundle.cpp new file mode 100644 index 0000000..133cd01 --- /dev/null +++ b/alpm/mingwbundle.cpp @@ -0,0 +1,170 @@ +#include "mingwbundle.h" +#include "manager.h" + +#include +#include + +#include + +#include +#include + +#include +#include + +using namespace std; + +namespace PackageManagement { + +const string prefix("mingw-w64-"); + +MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages) : + m_manager(manager) +{ + string missing; + for(const auto &pkgName : packages) { + bool found = false; + for(const auto &syncDb : manager.syncDataBases()) { + if(auto pkg = syncDb.second.package(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data())) { + if(missing.empty()) { + m_packages.emplace_back(syncDb.second, pkg); + } + addDependencies(pkg); + found = true; + break; + } + } + if(!found) { + missing.push_back(' '); + missing.append(pkgName); + } + } + if(!missing.empty()) { + throw runtime_error("The following packages can not be found:" + missing); + } else { + cerr << "Adding the following packages:"; + for(const auto &pkg : m_packages) { + cerr << ' ' << pkg.second.name(); + } + cerr << endl; + } +} + +enum class RelevantFileType +{ + Binary, + Translation +}; + +enum class RelevantFileArch +{ + x86_64, + i686 +}; + +struct RelevantFile +{ + RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch) : + file(file), + fileType(type), + arch(arch) + {} + const KArchiveFile *file; + RelevantFileType fileType; + RelevantFileArch arch; +}; + +struct PkgFileInfo +{ + PkgFileInfo(const QString &path) : path(path), failure(false) {} + QString path; + unique_ptr archive; + list relevantFiles; + bool failure; +}; + +void getFiles(PkgFileInfo &pkgFileInfo) +{ + pkgFileInfo.archive = make_unique(pkgFileInfo.path); + if(pkgFileInfo.archive->open(QIODevice::ReadOnly)) { + static const pair roots[] = { + make_pair(RelevantFileArch::x86_64, QStringLiteral("/usr/x86_64-w64-mingw32")), + make_pair(RelevantFileArch::i686, QStringLiteral("/usr/i686-w64-mingw32")) + }; + for(const auto &root : roots) { + const auto *rootEntry = pkgFileInfo.archive->directory()->entry(root.second); + if(rootEntry && rootEntry->isDirectory()) { + const auto *rootDir = static_cast(rootEntry); + const auto *binEntry = rootDir->entry(QStringLiteral("bin")); + if(binEntry && binEntry->isDirectory()) { + const auto *binDir = static_cast(binEntry); + for(const auto &entryName : binDir->entries()) { + if(entryName.endsWith(QLatin1String(".exe")) || entryName.endsWith(QLatin1String(".dll"))) { + if(const auto *entry = binDir->entry(entryName)) { + if(entry->isFile()) { + const auto *binFile = static_cast(entry); + pkgFileInfo.relevantFiles.emplace_back(binFile, RelevantFileType::Binary, root.first); + cerr << entryName.toLocal8Bit().data() << endl; + } + } + } + } + } + } + + } + + } else { + pkgFileInfo.failure = true; + } +} + +void MingwBundle::createBundle(const string &path) const +{ + list pkgFiles; + for(const auto &entry : m_packages) { + QString pkgFile; + if(!entry.first.packagesDirectory().isEmpty()) { + pkgFile = QStringLiteral("%1/%2").arg(entry.first.packagesDirectory(), QString::fromLocal8Bit(entry.second.fileName())); + } + if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) { + if(!m_manager.pacmanCacheDir().isEmpty()) { + pkgFile = QStringLiteral("%1/%2").arg(m_manager.pacmanCacheDir(), QString::fromLocal8Bit(entry.second.fileName())); + } + if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) { + throw runtime_error("The package file " + string(entry.second.fileName()) + " can't be found."); + // TODO: download package from mirror + } + } + pkgFiles.emplace_back(pkgFile); + } + QtConcurrent::blockingMap(pkgFiles, getFiles); +} + +void MingwBundle::addDependencies(const AlpmPackage &pkg) +{ + string missing; + for(const auto &depInfo : pkg.dependencies()) { + bool found = false; + for(const auto &syncDb : m_manager.syncDataBases()) { + if(auto pkg = syncDb.second.package(depInfo->name)) { + if(missing.empty()) { + m_packages.emplace_back(syncDb.second, pkg); + } + addDependencies(pkg); + found = true; + break; + } + } + if(!found) { + missing.push_back(' '); + missing.append(depInfo->name); + } + } + if(!missing.empty()) { + throw runtime_error("The following dependencies of the " + string(pkg.name()) + " package can not be resolved:" + missing); + } +} + +} // namespace PackageManagement + diff --git a/alpm/mingwbundle.h b/alpm/mingwbundle.h new file mode 100644 index 0000000..be618b3 --- /dev/null +++ b/alpm/mingwbundle.h @@ -0,0 +1,31 @@ +#ifndef PACKAGEMANAGEMENT_MINGWBUNDLE_H +#define PACKAGEMANAGEMENT_MINGWBUNDLE_H + +#include "package.h" +#include "database.h" + +#include + +#include + +namespace PackageManagement { + +class Manager; + +class MingwBundle +{ +public: + MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages); + + void createBundle(const std::string &path) const; + +private: + void addDependencies(const AlpmPackage &pkg); + + const Manager &m_manager; + std::list > m_packages; +}; + +} // namespace PackageManagement + +#endif // PACKAGEMANAGEMENT_MINGWBUNDLE_H diff --git a/alpm/package.cpp b/alpm/package.cpp index 3ec7d91..30a7007 100644 --- a/alpm/package.cpp +++ b/alpm/package.cpp @@ -11,10 +11,93 @@ using namespace ChronoUtilities; namespace PackageManagement { +/*! + * \cond + */ + +inline QString qstr(const char *str) +{ + return QString::fromLocal8Bit(str); +} + +inline QJsonArray qjarry(StringList list) +{ + QJsonArray jsonArray; + for(const char *str : list) { + jsonArray << qstr(str); + } + return jsonArray; +} + +inline QJsonArray qjarry(DependencyList list) +{ + QJsonArray jsonArray; + for(alpm_depend_t *dep : list) { + QJsonObject depObj; + depObj.insert(QStringLiteral("name"), dep->name); + depObj.insert(QStringLiteral("desc"), dep->desc); + depObj.insert(QStringLiteral("ver"), dep->version); + switch(dep->mod) { + case ALPM_DEP_MOD_ANY: + depObj.insert(QStringLiteral("mod"), QStringLiteral("any")); + break; + case ALPM_DEP_MOD_EQ: + depObj.insert(QStringLiteral("mod"), QStringLiteral("eq")); + break; + case ALPM_DEP_MOD_GE: + depObj.insert(QStringLiteral("mod"), QStringLiteral("ge")); + break; + case ALPM_DEP_MOD_LE: + depObj.insert(QStringLiteral("mod"), QStringLiteral("le")); + break; + case ALPM_DEP_MOD_GT: + depObj.insert(QStringLiteral("mod"), QStringLiteral("gt")); + break; + case ALPM_DEP_MOD_LT: + depObj.insert(QStringLiteral("mod"), QStringLiteral("lt")); + break; + } + jsonArray << depObj; + } + return jsonArray; +} + +inline QJsonArray qjarry(_alpm_pkgvalidation_t validation) +{ + QJsonArray jsonArray; + if(validation & 0x1) { + jsonArray << QStringLiteral("none"); + } + if(validation & 0x2) { + jsonArray << QStringLiteral("MD5"); + } + if(validation & 0x4) { + jsonArray << QStringLiteral("SHA256"); + } + if(validation & 0x8) { + jsonArray << QStringLiteral("signature"); + } + if(jsonArray.empty()) { + jsonArray << QStringLiteral("unknown"); + } + return jsonArray; +} + +/*! + * \endcond + */ + /*! * \brief The PackageVersion class helps parsing package versions. */ +/*! + * \brief Constructs a new PackageVersion instance from the specified \a versionStr. + */ +PackageVersion::PackageVersion(const QString &versionStr) : + PackageVersion(versionStr.toLocal8Bit().data()) +{} + /*! * \brief Constructs a new PackageVersion instance from the specified \a versionStr. */ @@ -158,10 +241,12 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue) : QJsonObject obj = aurJsonValue.toObject(); m_id = obj.value(QStringLiteral("ID")).toInt(-1); m_categoryId = obj.value(QStringLiteral("CategoryID")).toInt(-1); + m_name = obj.value(QStringLiteral("Name")).toString(); + m_version = obj.value(QStringLiteral("Version")).toString(); m_description = obj.value(QStringLiteral("Description")).toString(); m_upstreamUrl = obj.value(QStringLiteral("URL")).toString(); m_votes = obj.value(QStringLiteral("NumVotes")).toInt(0); - m_outOfDate = obj.value(QStringLiteral("OutOfDate")).toInt() != 0; + m_outOfDate = DateTime::fromTimeStamp(obj.value(QStringLiteral("OutOfDate")).toInt()); m_maintainer = obj.value(QStringLiteral("Maintainer")).toString(); m_firstSubmitted = DateTime::fromTimeStamp(obj.value(QStringLiteral("FirstSubmitted")).toInt()); m_lastModified = DateTime::fromTimeStamp(obj.value(QStringLiteral("LastModified")).toInt()); @@ -169,87 +254,65 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue) : m_tarUrl = obj.value(QStringLiteral("URLPath")).toString(); } + + +/*! + * \brief Returns basic information about the packages as JSON object. + */ +QJsonObject AurPackage::basicInfo(bool includeRepoAndName) const +{ + QJsonObject packageInfo; + if(includeRepoAndName) { + packageInfo.insert(QStringLiteral("repo"), QStringLiteral("aur")); + packageInfo.insert(QStringLiteral("name"), name()); + } + packageInfo.insert(QStringLiteral("arch"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("ver"), version()); + packageInfo.insert(QStringLiteral("desc"), description()); + packageInfo.insert(QStringLiteral("bdate"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("flagdate"), outOfDate().isNull() ? QStringLiteral("none") : qstr(outOfDate().toString().data())); + return packageInfo; +} + +/*! + * \brief Returns full information about the package as JSON object. + */ +QJsonObject AurPackage::fullInfo(bool includeRepoAndName) const +{ + QJsonObject packageInfo; + if(includeRepoAndName) { + packageInfo.insert(QStringLiteral("repo"), QStringLiteral("aur")); + packageInfo.insert(QStringLiteral("name"), name()); + } + packageInfo.insert(QStringLiteral("arch"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("ver"), version()); + packageInfo.insert(QStringLiteral("desc"), description()); + packageInfo.insert(QStringLiteral("bdate"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("idate"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("isize"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("url"), upstreamUrl()); + packageInfo.insert(QStringLiteral("lic"), license()); + packageInfo.insert(QStringLiteral("grp"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("prov"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("optd"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("deps"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("requ"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("optf"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("conf"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("repl"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("pack"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("expl"), QStringLiteral("n.a.")); + packageInfo.insert(QStringLiteral("scri"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("sig"), QStringLiteral("TODO")); + packageInfo.insert(QStringLiteral("file"), QStringLiteral("n.a.")); + return packageInfo; +} + /*! * \brief The AlpmPackage class helps getting information about ALPM packages. It allows to convert the * information to JSON objects used by the network classes and the web interface. */ -/*! - * \cond - */ - -inline QString qstr(const char *str) -{ - return QString::fromLocal8Bit(str); -} - -inline QJsonArray qjarry(StringList list) -{ - QJsonArray jsonArray; - for(const char *str : list) { - jsonArray << qstr(str); - } - return jsonArray; -} - -inline QJsonArray qjarry(DependencyList list) -{ - QJsonArray jsonArray; - for(alpm_depend_t *dep : list) { - QJsonObject depObj; - depObj.insert(QStringLiteral("name"), dep->name); - depObj.insert(QStringLiteral("desc"), dep->desc); - depObj.insert(QStringLiteral("ver"), dep->version); - switch(dep->mod) { - case ALPM_DEP_MOD_ANY: - depObj.insert(QStringLiteral("mod"), QStringLiteral("any")); - break; - case ALPM_DEP_MOD_EQ: - depObj.insert(QStringLiteral("mod"), QStringLiteral("eq")); - break; - case ALPM_DEP_MOD_GE: - depObj.insert(QStringLiteral("mod"), QStringLiteral("ge")); - break; - case ALPM_DEP_MOD_LE: - depObj.insert(QStringLiteral("mod"), QStringLiteral("le")); - break; - case ALPM_DEP_MOD_GT: - depObj.insert(QStringLiteral("mod"), QStringLiteral("gt")); - break; - case ALPM_DEP_MOD_LT: - depObj.insert(QStringLiteral("mod"), QStringLiteral("lt")); - break; - } - jsonArray << depObj; - } - return jsonArray; -} - -inline QJsonArray qjarry(_alpm_pkgvalidation_t validation) -{ - QJsonArray jsonArray; - if(validation & 0x1) { - jsonArray << QStringLiteral("none"); - } - if(validation & 0x2) { - jsonArray << QStringLiteral("MD5"); - } - if(validation & 0x4) { - jsonArray << QStringLiteral("SHA256"); - } - if(validation & 0x8) { - jsonArray << QStringLiteral("signature"); - } - if(jsonArray.empty()) { - jsonArray << QStringLiteral("unknown"); - } - return jsonArray; -} - -/*! - * \endcond - */ - /*! * \brief Returns basic information about the packages as JSON object. */ @@ -264,6 +327,7 @@ QJsonObject AlpmPackage::basicInfo(bool includeRepoAndName) const packageInfo.insert(QStringLiteral("ver"), qstr(version())); packageInfo.insert(QStringLiteral("desc"), qstr(description())); packageInfo.insert(QStringLiteral("bdate"), qstr(buildDate().toString().c_str())); + packageInfo.insert(QStringLiteral("flagdate"), QStringLiteral("n.a.")); return packageInfo; } diff --git a/alpm/package.h b/alpm/package.h index e707136..5aa59fa 100644 --- a/alpm/package.h +++ b/alpm/package.h @@ -11,6 +11,7 @@ #include #include +#include QT_BEGIN_NAMESPACE class QJsonObject; @@ -43,6 +44,7 @@ enum class PackageVersionPartComparsion class PackageVersion { public: + PackageVersion(const QString &versionStr); PackageVersion(const char *versionStr); static PackageVersionPartComparsion compareParts(const QString &part1, const QString &part2); @@ -60,39 +62,49 @@ public: AurPackage(const QJsonValue &aurJsonValue); bool isValid() const; + bool hasFullInfo() const; int id() const; int categoryId() const; const QString &name() const; + const QString &version() const; + template + PackageVersionComparsion compareVersion(const Package &syncPackage) const; const QString &description() const; const QString &upstreamUrl() const; int votes() const; - bool isOutOfDate() const; + ChronoUtilities::DateTime outOfDate() const; const QString &maintainer() const; ChronoUtilities::DateTime firstSubmitted() const; ChronoUtilities::DateTime lastModified() const; const QString &license() const; const QString &tarUrl() const; + // JSON serialization + QJsonObject basicInfo(bool includeRepoAndName) const; + QJsonObject fullInfo(bool includeRepoAndName = false) const; + private: + bool m_fullInfo; int m_id; int m_categoryId; QString m_name; + QString m_version; QString m_description; QString m_upstreamUrl; int m_votes; - bool m_outOfDate; + ChronoUtilities::DateTime m_outOfDate; QString m_maintainer; ChronoUtilities::DateTime m_firstSubmitted; ChronoUtilities::DateTime m_lastModified; QString m_license; QString m_tarUrl; - }; /*! * \brief Constructs a empty, invalid AUR package. */ inline AurPackage::AurPackage() : + m_fullInfo(false), m_id(-1), m_categoryId(-1), m_votes(-1), @@ -107,6 +119,14 @@ inline bool AurPackage::isValid() const return m_id >= 0; } +/*! + * \brief Returns an indication whether full package info is available. + */ +inline bool AurPackage::hasFullInfo() const +{ + return m_fullInfo; +} + /*! * \brief Returns the ID of the package in the AUR. */ @@ -131,6 +151,20 @@ inline const QString &AurPackage::name() const return m_name; } +/*! + * \brief Returns the version of the package. + */ +inline const QString &AurPackage::version() const +{ + return m_version; +} + +template +inline PackageVersionComparsion AurPackage::compareVersion(const Package &syncPackage) const +{ + return PackageVersion(version()).compare(PackageVersion(syncPackage.version())); +} + /*! * \brief Returns the description of the package. */ @@ -158,7 +192,7 @@ inline int AurPackage::votes() const /*! * \brief Returns wheter the package is flagged as out-of-date. */ -inline bool AurPackage::isOutOfDate() const +inline ChronoUtilities::DateTime AurPackage::outOfDate() const { return m_outOfDate; } @@ -209,12 +243,14 @@ public: AlpmPackage(alpm_pkg_t *package = nullptr); // package properties + const alpm_pkg_t *ptr() const; alpm_pkg_t *ptr(); bool hasInstallScript() const; const char *fileName() const; const char *name() const; const char *version() const; - PackageVersionComparsion compareVersion(const AlpmPackage &syncPackage) const; + template + PackageVersionComparsion compareVersion(const Package &syncPackage) const; alpm_pkgfrom_t origin() const; const char *description() const; const char *upstreamUrl() const; @@ -256,6 +292,11 @@ protected: alpm_pkg_t *m_ptr; }; +inline uint qHash(const AlpmPackage &key) +{ + return qHash(key.ptr()); +} + /*! * \brief Constructs a new package instance for the specified ALPM \a package. */ @@ -263,6 +304,11 @@ inline AlpmPackage::AlpmPackage(alpm_pkg_t *package) : m_ptr(package) {} +inline const alpm_pkg_t *AlpmPackage::ptr() const +{ + return m_ptr; +} + inline alpm_pkg_t *AlpmPackage::ptr() { return m_ptr; @@ -293,7 +339,8 @@ inline const char *AlpmPackage::version() const * * This method distinguishes between software upgrades and package releases. See Alpm::PackageVersionComparsion enum. */ -inline PackageVersionComparsion AlpmPackage::compareVersion(const AlpmPackage &syncPackage) const +template +inline PackageVersionComparsion AlpmPackage::compareVersion(const Package &syncPackage) const { return PackageVersion(version()).compare(PackageVersion(syncPackage.version())); } diff --git a/alpm/resolvebuildorder.cpp b/alpm/resolvebuildorder.cpp new file mode 100644 index 0000000..158fa73 --- /dev/null +++ b/alpm/resolvebuildorder.cpp @@ -0,0 +1,202 @@ +#include "resolvebuildorder.h" + +#include "manager.h" +#include "config.h" + +#include + +#include +#include + +using namespace std; +using namespace ApplicationUtilities; + +namespace PackageManagement { + +class TaskInfo +{ +public: + TaskInfo(QString name, bool onlyDependency = false, const QList &deps = QList()); + + const QString &name() const; + const QList deps() const; + void addDep(TaskInfo *dep); + bool isDone() const; + bool isVisited() const; + bool isOnlyDependency() const; + void add(QList &results); + static void addAll(const QList &tasks, QList &results); + static TaskInfo *find(const QList &tasks, const QString &name); + +private: + QString m_name; + QList m_deps; + bool m_done; + bool m_visited; + bool m_onlyDep; +}; + +inline TaskInfo::TaskInfo(QString name, bool onlyDependency, const QList &deps) : + m_name(name), + m_deps(deps), + m_done(false), + m_visited(false), + m_onlyDep(onlyDependency) +{} + +inline const QString &TaskInfo::name() const +{ + return m_name; +} + +inline const QList TaskInfo::deps() const +{ + return m_deps; +} + +inline void TaskInfo::addDep(TaskInfo *dep) +{ + m_deps << dep; +} + +inline bool TaskInfo::isDone() const +{ + return m_done; +} + +inline bool TaskInfo::isVisited() const +{ + return m_visited; +} + +inline bool TaskInfo::isOnlyDependency() const +{ + return m_onlyDep; +} + +void TaskInfo::add(QList &results) +{ + if(!m_done) { + if(m_visited) { + throw *this; // cyclic dependency + } else { + m_visited = true; + } + for(auto *dep : m_deps) { + dep->add(results); + } + m_done = true; + results << this; + } +} + +void TaskInfo::addAll(const QList &tasks, QList &results) +{ + for(auto *task : tasks) { + if(!task->m_done) { + task->add(results); + } + } +} + +TaskInfo *TaskInfo::find(const QList &tasks, const QString &name) +{ + for(auto *task : tasks) { + if(task->name() == name) { + return task; + } + } + return nullptr; +} + +template +class DestroyList +{ +public: + DestroyList(ListType &list) : + m_list(list) + {} + + ~DestroyList() + { + qDeleteAll(m_list); + } + +private: + ListType &m_list; +}; + +BuildOrderResolver::BuildOrderResolver(const Manager &manager) : + m_manager(manager) +{} + +QStringList BuildOrderResolver::resolve(const StringVector &packages) const +{ + cerr << "Getting package information ..." << endl; + QList tasks; + tasks.reserve(packages.size()); + try { + // add a task for each specified package + for(const auto &pkgName : packages) { + tasks << new TaskInfo(QString::fromLocal8Bit(pkgName.data())); + } + // find specified packages and their dependencies + for(auto *task : tasks) { + addDeps(tasks, task); + } + cerr << "Relevant packages: "; + for(const auto *task : tasks) { + cerr << task->name().toLocal8Bit().data() << ' '; + } + cerr << endl; + // topo sort + QList results; + results.reserve(tasks.size()); + try { + TaskInfo::addAll(tasks, results); + QStringList names; + names.reserve(results.size()); + for(const auto *res : results) { + names << res->name(); + } + return names; + } catch (const TaskInfo &cyclic) { + throw runtime_error("Can't resolve build order; the package " + cyclic.name().toStdString() + " is a cyclic dependency."); + } + } catch(...) { + qDeleteAll(tasks); + throw; + } +} + +void BuildOrderResolver::addDeps(QList &tasks, TaskInfo *task) const +{ + if(const auto pkg = m_manager.packageFromSyncDataBases(task->name().toLocal8Bit().data())) { + for(auto dep : pkg.dependencies()) { + if(auto *depTask = addDep(tasks, dep->name)) { + task->addDep(depTask); + } + } + } else { + stringstream ss; + ss << "The package \"" << task->name().toLocal8Bit().data() << "\" could not be found; TODO: search AUR for package, add AUR deps to the packages we want to build"; + throw runtime_error(ss.str()); + } +} + +TaskInfo *BuildOrderResolver::addDep(QList &tasks, const char *depName) const +{ + if(auto *task = TaskInfo::find(tasks, depName)) { + // we've already added a task for this dependency + return task; + } else { + // create new task + //task = new TaskInfo(QString::fromLocal8Bit(depName)); + //tasks << task; + //return task; + return nullptr; + } +} + +} // namespace PackageManagement + diff --git a/alpm/resolvebuildorder.h b/alpm/resolvebuildorder.h new file mode 100644 index 0000000..3135552 --- /dev/null +++ b/alpm/resolvebuildorder.h @@ -0,0 +1,29 @@ +#ifndef PACKAGEMANAGEMENT_RESOLVEBUILDORDER_H +#define PACKAGEMANAGEMENT_RESOLVEBUILDORDER_H + +#include + +#include +#include + +namespace PackageManagement { + +class Manager; +class TaskInfo; + +class BuildOrderResolver +{ +public: + BuildOrderResolver(const Manager &manager); + + QStringList resolve(const ApplicationUtilities::StringVector &packages) const; + +private: + void addDeps(QList &tasks, TaskInfo *task) const; + TaskInfo *addDep(QList &pkgInfos, const char *depName) const; + const Manager &m_manager; +}; + +} // namespace PackageManagement + +#endif // PACKAGEMANAGEMENT_RESOLVEBUILDORDER_H diff --git a/alpm/updatelookup.cpp b/alpm/updatelookup.cpp new file mode 100644 index 0000000..354ca55 --- /dev/null +++ b/alpm/updatelookup.cpp @@ -0,0 +1,185 @@ +#include "updatelookup.h" +#include "manager.h" +#include "database.h" +#include "config.h" + +#include +#include + +#include + +using namespace std; + +namespace PackageManagement { + +UpdateLookup::UpdateLookup(const Manager &manager, const QJsonObject &request, const UpdateLookupCallback callback, QObject *parent) : + QObject(parent), + m_manager(manager), + m_request(request), + m_callback(callback), + m_db(nullptr), + m_syncDbsWatcher(new QFutureWatcher >(this)), + m_aurWatcher(new QFutureWatcher >(this)), + m_aurReply(nullptr), + m_sourcesAvailable(false) +{ + QJsonArray errors; + const auto dbName = request.value(QStringLiteral("db")).toString(); + try { + m_db = &manager.dataBaseByName(dbName); + } catch (const out_of_range &) { + errors << QStringLiteral("Database \"%1\" can not be found.").arg(dbName); + } + if(errors.isEmpty()) { + // invoke syncdb lookup + connect(m_syncDbsWatcher, &QFutureWatcher >::finished, this, &UpdateLookup::lookupDone); + m_syncDbsWatcher->setFuture(QtConcurrent::run(this, &UpdateLookup::checkSyncDbs, request.value(QStringLiteral("syncdbs")))); + // invoke AUR lookup + if(m_db->upgradeSources().contains(QStringLiteral("aur"), Qt::CaseInsensitive) || request.value(QStringLiteral("aur")).toBool(false)) { + if(manager.config().isAurEnabled()) { + connect(&m_manager.aurQuery(), &AurQuery::packageInfoAvailable, this, &UpdateLookup::aurPackageInfoAvailable); + connect(m_aurWatcher, &QFutureWatcher >::finished, this, &UpdateLookup::lookupDone); + if(!(m_aurReply = m_manager.aurQuery().requestPackageInfo(m_db->packageNames()))) { + aurPackageInfoAvailable(nullptr); + } + } else { + errors << QStringLiteral("The AUR is configured as upgrade source for the database \"%1\" but AUR queries are not enabled.").arg(dbName); + } + } + } else { + QJsonObject results; + results.insert(QStringLiteral("errors"), errors); + callback(move(results)); + deleteLater(); + } +} + +UpdateLookupResults UpdateLookup::checkSyncDbs(const QJsonValue &syncDbsValue) +{ + UpdateLookupResults results; + const auto &syncDbs = m_manager.syncDataBases(); + QList syncDbSel; + if(syncDbsValue.type() == QJsonValue::Array) { + for(const auto &syncDbVal : syncDbsValue.toArray()) { + const auto syncDbName = syncDbVal.toString(); + if(syncDbName == QLatin1String("local")) { + syncDbSel << &(m_manager.localDataBase()); + } else if(syncDbName == QLatin1String("aur")) { + continue; // the AUR is checked separately + } else { + try { + syncDbSel << &(syncDbs.at(syncDbName)); + } catch(out_of_range &) { + results.warnings << QStringLiteral("The specified sync database \"%1\" can not be found.").arg(syncDbName); + } + } + } + } else { + for(const auto &syncDbName : m_db->upgradeSources()) { + if(syncDbName == QLatin1String("local")) { + syncDbSel << &(m_manager.localDataBase()); + } else if(syncDbName == QLatin1String("aur")) { + continue; // the AUR is checked separately + } else { + try { + syncDbSel << &(syncDbs.at(syncDbName)); + } catch(out_of_range &) { + results.warnings << QStringLiteral("The associated upgrade database \"%1\" can not be found.").arg(syncDbName); + } + } + } + } + m_db->checkForUpgrades(syncDbSel, results); + return results; +} + +void UpdateLookup::aurPackageInfoAvailable(const QNetworkReply *reply) +{ + // check whether the package info requested by THIS INSTANCE is available + if(m_aurReply == reply) { + m_aurWatcher->setFuture(QtConcurrent::run(this, &UpdateLookup::checkAur)); + } +} + +UpdateLookupResults UpdateLookup::checkAur() +{ + UpdateLookupResults results; + m_db->checkForUpgrades(m_manager.aurQuery().packages(), results); + return results; +} + +void UpdateLookup::lookupDone() +{ + const auto *sender = this->sender(); + if(sender == static_cast(m_syncDbsWatcher)) { + // add results from syncdb lookup + m_syncDbsResults = m_syncDbsWatcher->result(); + if(!m_syncDbsResults.noSources) { + m_sourcesAvailable = true; + for(const auto pkg : m_syncDbsResults.newVersions) { + m_softwareUpdates << pkg.json(); + } + for(const auto pkg : m_syncDbsResults.newReleases) { + m_packageOnlyUpdates << pkg.json(); + } + for(const auto pkg : m_syncDbsResults.downgrades) { + m_downgrades << pkg.json(); + } + for(const auto &warning : m_syncDbsResults.warnings) { + m_warnings << warning; + } + for(const auto &error : m_syncDbsResults.errors) { + m_errors << error; + } + } + } else if(sender == static_cast(m_aurWatcher)) { + // add results from AUR lookup + m_aurResults = m_aurWatcher->result(); + if(!m_aurResults.noSources) { + m_sourcesAvailable = true; + for(const auto pkg : m_aurResults.newVersions) { + m_softwareUpdates << pkg.json(); + } + for(const auto pkg : m_aurResults.newReleases) { + m_packageOnlyUpdates << pkg.json(); + } + for(const auto pkg : m_aurResults.downgrades) { + m_downgrades << pkg.json(); + } + for(const auto &warning : m_aurResults.warnings) { + m_warnings << warning; + } + for(const auto &error : m_aurResults.errors) { + m_errors << error; + } + } + } + // check whether everything is done + if(m_syncDbsWatcher->isFinished() && (!m_aurReply || m_aurWatcher->isFinished())) { + QJsonObject results; + // determine orphaned packages + for(const auto pkg : m_aurReply ? m_aurResults.orphaned.intersect(m_syncDbsResults.orphaned) : m_syncDbsResults.orphaned) { + m_orphanedPackages << pkg.basicInfo(true); + } + // add results to results QJsonObject + results.insert(QStringLiteral("softwareUpdates"), m_softwareUpdates); + results.insert(QStringLiteral("packageOnlyUpdates"), m_packageOnlyUpdates); + results.insert(QStringLiteral("downgrades"), m_downgrades); + results.insert(QStringLiteral("orphanedPackages"), m_orphanedPackages); + if(!m_sourcesAvailable) { + m_errors << QStringLiteral("No update sources associated for database \"%1\".").arg(QString::fromLocal8Bit(m_db->name())); + } + if(!m_warnings.isEmpty()) { + results.insert(QStringLiteral("warnings"), m_warnings); + } + if(!m_errors.isEmpty()) { + results.insert(QStringLiteral("errors"), m_errors); + } + m_callback(move(results)); + // lookup done, delete this helper object + deleteLater(); + } +} + +} // namespace PackageManagement + diff --git a/alpm/updatelookup.h b/alpm/updatelookup.h new file mode 100644 index 0000000..54eb8fd --- /dev/null +++ b/alpm/updatelookup.h @@ -0,0 +1,145 @@ +#ifndef PACKAGEMANAGEMENT_UPDATELOOKUP_H +#define PACKAGEMANAGEMENT_UPDATELOOKUP_H + +#include "package.h" + +#include +#include +#include +#include + +#include + +QT_FORWARD_DECLARE_CLASS(QNetworkReply) + +namespace PackageManagement { + +class Manager; +class AlpmDataBase; + +typedef std::function UpdateLookupCallback; + +template +class UpdateResult +{ +public: + UpdateResult(const Package &package, const VersionType &previousVersion); + Package package; + VersionType previousVersion; + QJsonObject json() const; +}; + +template +inline UpdateResult::UpdateResult(const Package &package, const VersionType &previousVersion) : + package(package), + previousVersion(previousVersion) +{} + +template +QJsonObject UpdateResult::json() const +{ + QJsonObject obj; + obj.insert(QStringLiteral("pkg"), package.basicInfo(true)); + obj.insert(QStringLiteral("prevVersion"), previousVersion); + return obj; +} + +template +inline UpdateResult makeUpdateResult(const Package &package, const QString &previousVersion) +{ + return UpdateResult(package, previousVersion); +} + +template +inline UpdateResult makeUpdateResult(const Package &package, const char *previousVersion) +{ + return UpdateResult(package, QString::fromLocal8Bit(previousVersion)); +} + + + +template +class UpdateLookupResults +{ +public: + UpdateLookupResults(); + + /*! + * \brief Indicates that there are no upgrade sources available. + */ + bool noSources; + + /*! + * \brief Packages providing a software upgrade (new version). + */ + QList > newVersions; + + /*! + * \brief Package upgrades only (new release). + */ + QList > newReleases; + + /*! + * \brief Downgrades (older version in sync db). + */ + QList > downgrades; + + /*! + * \brief Orphaned packages (could not be found in any of the sync dbs). + */ + QSet orphaned; + + /*! + * \brief Warnings occured when checking for updates. + */ + QStringList warnings; + + /*! + * \brief Errors occured when checking for updates. + */ + QStringList errors; +}; + +/*! + * \brief Constructs new update lookup results. + */ +template +inline UpdateLookupResults::UpdateLookupResults() : + noSources(false) +{} + +class UpdateLookup : public QObject +{ + Q_OBJECT +public: + explicit UpdateLookup(const Manager &manager, const QJsonObject &request, const UpdateLookupCallback callback, QObject *parent = nullptr); + +private slots: + UpdateLookupResults checkSyncDbs(const QJsonValue &syncDbsValue); + void aurPackageInfoAvailable(const QNetworkReply *reply); + UpdateLookupResults checkAur(); + + void lookupDone(); + +private: + const Manager &m_manager; + const QJsonObject m_request; + const UpdateLookupCallback m_callback; + const AlpmDataBase *m_db; + QFutureWatcher > *m_syncDbsWatcher; + QFutureWatcher > *m_aurWatcher; + UpdateLookupResults m_syncDbsResults; + UpdateLookupResults m_aurResults; + QNetworkReply *m_aurReply; + bool m_sourcesAvailable; + QJsonArray m_warnings; + QJsonArray m_errors; + QJsonArray m_softwareUpdates; + QJsonArray m_packageOnlyUpdates; + QJsonArray m_downgrades; + QJsonArray m_orphanedPackages; +}; + +} // namespace PackageManagement + +#endif // PACKAGEMANAGEMENT_UPDATELOOKUP_H diff --git a/main.cpp b/main.cpp index 3c9709b..1059890 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,8 @@ #include "alpm/manager.h" #include "alpm/utilities.h" #include "alpm/config.h" +#include "alpm/resolvebuildorder.h" +#include "alpm/mingwbundle.h" #include "network/server.h" #include @@ -9,6 +11,7 @@ #include #include +#include using namespace std; using namespace ApplicationUtilities; @@ -33,17 +36,34 @@ int main(int argc, char *argv[]) Config config; config.loadFromConfigFile(configArgs); config.loadFromArgs(configArgs); - // run websocket server - if(configArgs.serverArg.isPresent()) { - // setup ALPM - Manager alpmManager(config); - alpmManager.parsePacmanConfig(); - // setup the server + if(find_if(parser.mainArguments().cbegin(), parser.mainArguments().cend(), [&configArgs] (const Argument *arg) { + return arg != &configArgs.helpArg && arg->isPresent(); + }) != parser.mainArguments().cend()) { + // create app QCoreApplication application(argc, argv); - Server server(alpmManager, config); - QObject::connect(&server, &Server::closed, &application, &QCoreApplication::quit); - // run Qt loop - return application.exec(); + // setup ALPM + Manager manager(config); + manager.applyPacmanConfig(); + manager.applyRepoIndexConfig(); + if(configArgs.serverArg.isPresent()) { + // setup the server + Server server(manager, manager.config()); + QObject::connect(&server, &Server::closed, &application, &QCoreApplication::quit); + // run Qt loop + return application.exec(); + } else if(configArgs.buildOrderArg.isPresent()) { + BuildOrderResolver resolver(manager); + const QStringList results = resolver.resolve(configArgs.buildOrderArg.values()); + // print results + cout << "Results: "; + for(const auto &pkgName : results) { + cout << pkgName.toLocal8Bit().data() << ' '; + } + cout << endl; + } else if(configArgs.mingwBundleArg.isPresent()) { + MingwBundle bundle(manager, configArgs.mingwBundleArg.values()); + bundle.createBundle(configArgs.outputFileArg.values().front()); + } } else { cout << "No command line arguments specified. See --help for available commands." << endl; } diff --git a/network/aurquery.cpp b/network/aurquery.cpp index 96f7bcf..c8f5fff 100644 --- a/network/aurquery.cpp +++ b/network/aurquery.cpp @@ -16,53 +16,105 @@ using namespace std; namespace PackageManagement { -QUrl AurQuery::m_aur4RequestUrl = QUrl(QStringLiteral("https://aur.archlinux.org/rpc.php")); +QUrl AurQuery::m_aurRpcUrl = QUrl(QStringLiteral("https://aur.archlinux.org/rpc.php")); + +QUrl AurQuery::m_aurPkgbuildUrl = QUrl(QStringLiteral("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD")); + +const char *requestTypeProp = "type"; +const QString rpcRequestTypeKey(QStringLiteral("type")); +const QString rpcRequestTypeSuggest(QStringLiteral("suggest")); +const QString rpcRequestTypeMultiInfo(QStringLiteral("multiinfo")); +const QString rpcArgKey(QStringLiteral("arg")); +const QString rpcArgArray(QStringLiteral("arg[]")); +const QString pkgbuildRequestType(QString("pkgbuild")); AurQuery::AurQuery(QNetworkAccessManager &networkAccessManager, QObject *parent) : QObject(parent), m_networkAccessManager(networkAccessManager) {} -void AurQuery::requestSuggestions(const QString &phrase) const +QNetworkReply *AurQuery::requestSuggestions(const QString &phrase) const { - auto url = m_aur4RequestUrl; + auto url = m_aurRpcUrl; QUrlQuery query; - query.addQueryItem(QStringLiteral("type"), QStringLiteral("suggest")); - query.addQueryItem(QStringLiteral("arg"), phrase); - //auto *reply = m_networkAccessManager.get(QNetworkRequest(url)); - //connect(reply, &QNetworkReply::finished, this, &AurEnquiry::suggestionsReceived); -} - -void AurQuery::requestPackageInfo(const QStringList &packageNames) const -{ - auto url = m_aur4RequestUrl; - QUrlQuery query; - query.addQueryItem(QStringLiteral("type"), QStringLiteral("multiinfo")); - for(const auto &packageName : packageNames) { - query.addQueryItem(QStringLiteral("arg[]"), packageName); - } + query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeSuggest); + query.addQueryItem(rpcArgKey, phrase); + url.setQuery(query); auto *reply = m_networkAccessManager.get(QNetworkRequest(url)); - reply->setProperty("type", QStringLiteral("packageinfo")); - connect(reply, &QNetworkReply::finished, this, &AurQuery::packageInfoReceived); + reply->setProperty(requestTypeProp, rpcRequestTypeSuggest); + connect(reply, &QNetworkReply::finished, this, &AurQuery::dataReceived); + return reply; } -void AurQuery::packageInfoReceived() +/*! + * \brief Requests package information for the specified packages from the AUR. + * \returns Returns the QNetworkReply used for the request. + * \remarks + * If \a forceUpdate is true, package information which has already been retrieved + * and is still cached is requested again. Otherwise these packages will not be + * requested again. If it turns out, that all packages are already cached, nullptr + * is returned in this case. + */ +QNetworkReply *AurQuery::requestPackageInfo(const QStringList &packageNames, bool forceUpdate) const +{ + QUrlQuery query; + for(const auto &packageName : packageNames) { + if(forceUpdate || !m_packages.count(packageName)) { + query.addQueryItem(rpcArgArray, packageName); + } + } + if(query.isEmpty()) { + return nullptr; + } else { + auto url = m_aurRpcUrl; + query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeMultiInfo); + url.setQuery(query); + auto *reply = m_networkAccessManager.get(QNetworkRequest(url)); + reply->setProperty(requestTypeProp, rpcRequestTypeMultiInfo); + connect(reply, &QNetworkReply::finished, this, &AurQuery::dataReceived); + return reply; + } +} + +QNetworkReply *AurQuery::requestFullPackageInfo(const QString &package, bool forceUpdate) const +{ + try { + const auto &pkg = m_packages.at(package); + if(pkg.hasFullInfo() && !forceUpdate) { + return nullptr; + } + } catch(const out_of_range &) { + } + auto url = m_aurPkgbuildUrl; + QUrlQuery query; + query.addQueryItem(QStringLiteral("h"), package); + url.setQuery(query); + auto *reply = m_networkAccessManager.get(QNetworkRequest(url)); + reply->setProperty(requestTypeProp, pkgbuildRequestType); + connect(reply, &QNetworkReply::finished, this, &AurQuery::dataReceived); + return reply; +} + +void AurQuery::dataReceived() { if(auto *reply = qobject_cast(sender())) { if(reply->error() == QNetworkReply::NoError) { QJsonParseError error; - const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error); + QByteArray data = reply->readAll(); + cerr << "AUR reply: " << data.data() << endl; + const QJsonDocument doc = QJsonDocument::fromJson(data, &error); + //const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error); if(error.error == QJsonParseError::NoError) { - const auto results = doc.object().value(QStringLiteral("results")).toArray(); - for(const auto &result : results) { - AurPackage package(result); - if(!package.name().isEmpty()) { - m_packages.emplace(package.name(), move(package)); - } + const QString requestType = reply->property(requestTypeProp).toString(); + if(requestType == rpcRequestTypeSuggest) { + emit suggestionsAvailable(reply, doc.array()); + } else if(requestType == rpcRequestTypeMultiInfo) { + processPackageInfo(reply, doc.object().value(QStringLiteral("results")).toArray()); + } else { + cerr << "Error: Reply has invalid type. (should never happen)" << endl; } - emit packageInfoAvailable(); } else { - cerr << "Error: Unable to parse JSON received from AUR: " << error.errorString().toLocal8Bit().data() << endl; + cerr << "Error: Unable to parse JSON received from AUR: " << error.errorString().toLocal8Bit().data() << " at character " << error.offset << endl; } } else { cerr << "Error: Unable to request data from AUR: " << reply->errorString().toLocal8Bit().data() << endl; @@ -70,5 +122,16 @@ void AurQuery::packageInfoReceived() } } +void AurQuery::processPackageInfo(const QNetworkReply *reply, const QJsonArray &results) +{ + for(const auto &result : results) { + AurPackage package(result); + if(!package.name().isEmpty()) { + m_packages[package.name()] = move(package); + } + } + emit packageInfoAvailable(reply); +} + } // namespace Alpm diff --git a/network/aurquery.h b/network/aurquery.h index f691691..a9c7473 100644 --- a/network/aurquery.h +++ b/network/aurquery.h @@ -9,49 +9,58 @@ #include QT_FORWARD_DECLARE_CLASS(QNetworkAccessManager) +QT_FORWARD_DECLARE_CLASS(QNetworkReply) namespace PackageManagement { class AurQuery : public QObject { Q_OBJECT - public: AurQuery(QNetworkAccessManager &networkAccessManager, QObject *parent = nullptr); - static const QUrl aur4RequestUrl(); - static void setAur4RequestUrl(const QUrl &aur4RequestUrl); - - void requestSuggestions(const QString &phrase) const; - void requestPackageInfo(const QStringList &packageNames) const; + static const QUrl aurRpcUrl(); + static void setAurRpcUrl(const QUrl &aurRpcUrl); + + QNetworkReply *requestSuggestions(const QString &phrase) const; + QNetworkReply *requestPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; + QNetworkReply *requestFullPackageInfo(const QString &package, bool forceUpdate = false) const; + const std::map &packages() const; std::map &packages(); signals: - void suggestionsAvailable(const QJsonArray &suggestions); - void packageInfoAvailable(); - + void suggestionsAvailable(const QNetworkReply *reply, const QJsonArray &suggestions); + void packageInfoAvailable(const QNetworkReply *reply); private slots: - void packageInfoReceived(); + void dataReceived(); private: + void processPackageInfo(const QNetworkReply *reply, const QJsonArray &results); + QNetworkAccessManager &m_networkAccessManager; std::map m_packages; - - static QUrl m_aurRequstUrl; - static QUrl m_aur4RequestUrl; + + static QUrl m_aurRpcUrl; + static QUrl m_aurPkgbuildUrl; }; -inline const QUrl AurQuery::aur4RequestUrl() +inline const QUrl AurQuery::aurRpcUrl() { - return m_aur4RequestUrl; + return m_aurRpcUrl; } -inline void AurQuery::setAur4RequestUrl(const QUrl &aur4Url) +inline void AurQuery::setAurRpcUrl(const QUrl &aur4Url) { - m_aur4RequestUrl = aur4Url; + m_aurRpcUrl = aur4Url; } +inline const std::map &AurQuery::packages() const +{ + return m_packages; +} + + inline std::map &AurQuery::packages() { return m_packages; diff --git a/network/connection.cpp b/network/connection.cpp index ed0b668..d9847b1 100644 --- a/network/connection.cpp +++ b/network/connection.cpp @@ -13,7 +13,7 @@ using namespace std; namespace Network { -Connection::Connection(PackageManagement::Manager &alpmManager, QWebSocket *socket, QObject *parent) : +Connection::Connection(const PackageManagement::Manager &alpmManager, QWebSocket *socket, QObject *parent) : QObject(parent), m_socket(socket), m_alpmManager(alpmManager), @@ -78,8 +78,8 @@ void Connection::handleQuery(const QJsonObject &obj) m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested); sendResults(what, id, m_alpmManager.groupInfo()); } else if(what == QLatin1String("checkforupdates")) { - m_alpmManager.invokeUpgradeLookup(obj); - //m_alpmManager.invokeUpgradeLookup(obj, bind(&Connection::sendResult, this, what, id, placeholders::_1)); + //sendResult(what, id, m_alpmManager.invokeUpgradeLookup(obj)); + m_alpmManager.invokeUpgradeLookup(obj, bind(&Connection::sendResult, this, what, id, placeholders::_1)); //m_alpmManager.checkForUpgrades(obj, [this] (const QJsonObject &results) { // sendResult(what, id, results); //}; diff --git a/network/connection.h b/network/connection.h index 9753bd2..c8428ef 100644 --- a/network/connection.h +++ b/network/connection.h @@ -18,7 +18,7 @@ class Connection : public QObject Q_OBJECT public: - Connection(PackageManagement::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr); + Connection(const PackageManagement::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr); private slots: void processTextMessage(const QString &message); @@ -33,7 +33,7 @@ private: void handleQuery(const QJsonObject &obj); QWebSocket *m_socket; - PackageManagement::Manager &m_alpmManager; + const PackageManagement::Manager &m_alpmManager; bool m_repoInfoUpdatesRequested; bool m_groupInfoUpdatesRequested; diff --git a/network/query.cpp b/network/query.cpp deleted file mode 100644 index 68e026b..0000000 --- a/network/query.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "query.h" - -namespace Network { - -Query::Query(QObject *parent) : - QObject(parent) -{ - -} - -} // namespace Network - diff --git a/network/query.h b/network/query.h deleted file mode 100644 index abffd6a..0000000 --- a/network/query.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef NETWORK_QUERY_H -#define NETWORK_QUERY_H - -#include - -namespace Network { - -class Query : public QObject -{ -public: - Query(QObject *parent = nullptr); - -private: - QString m_what; - QString m_clientId; - -}; - -} // namespace Network - -#endif // NETWORK_QUERY_H diff --git a/network/server.cpp b/network/server.cpp index 6d733c5..eb31a91 100644 --- a/network/server.cpp +++ b/network/server.cpp @@ -14,7 +14,7 @@ using namespace std; namespace Network { -Server::Server(PackageManagement::Manager &alpmManager, const PackageManagement::Config &config, QObject *parent) : +Server::Server(const PackageManagement::Manager &alpmManager, const PackageManagement::Config &config, QObject *parent) : QObject(parent), m_server(new QWebSocketServer(QStringLiteral("Repository index server"), config.serverInsecure() ? QWebSocketServer::NonSecureMode : QWebSocketServer::SecureMode, diff --git a/network/server.h b/network/server.h index 2569919..9fe41c7 100644 --- a/network/server.h +++ b/network/server.h @@ -22,7 +22,7 @@ class Server : public QObject Q_OBJECT public: - Server(PackageManagement::Manager &alpmManager, const PackageManagement::Config &config, QObject *parent = nullptr); + Server(const PackageManagement::Manager &alpmManager, const PackageManagement::Config &config, QObject *parent = nullptr); ~Server(); signals: @@ -34,7 +34,7 @@ private slots: private: QWebSocketServer *m_server; - PackageManagement::Manager &m_alpmManager; + const PackageManagement::Manager &m_alpmManager; }; } diff --git a/repoindex.conf.js b/repoindex.conf.js index 5a6230b..dde94b5 100644 --- a/repoindex.conf.js +++ b/repoindex.conf.js @@ -1,50 +1,36 @@ -{// ALPM related config - alpm: { - // the root directory for ALPM - //rootDir: "/", +{ + "alpm": { + "rootDir": "/", + "dbPath": "/var/lib/pacman", + "pacmanConfigFile": "/etc/pacman.conf" + }, - // the database directory for ALPM - //dbPath: "/var/lib/pacman/", + "aur": { + "enabled": true + }, - // the pacman config file - //pacmanConfigFile: "/etc/pacman.conf" - }, + "server": { + "listeningPort": 1234, + "certFile": "some.cert", + "keyFile": "some.key", + "insecure": false + }, - // server related config - server: { - // the listening port - //listeningPort: 1234, + "repos": { + "fromPacmanConfig": true, - // the SSL certificate - //certFile: "some.cert", - - // the private SSL key - //keyFile: "some.key", - - // forces the server to run in insecure mode - //insecure: false - } - - // repositories related config - repos: { - // indicates whether the repositories listed in - // the pacman config file should be loaded - //fromPacmanConfig: true, - - // lists additional repos to be loaded - add: [ - /* example repo entry - {name: "reponame", - dbpath: "path/to/database/file", - srcpath: "path/to/local/source/files", - pkgpath: "path/to/local/pkg/files", - servers: [ - "https://some/mirror", - "https://another/mirror - ] - } - */ - ] - } + "add": [ + {"name": "examplerepo", + "dataBaseFile": "path/to/database/file", + "sourcesDir": "path/to/local/source/dir", + "packagesDir": "path/to/local/pkg/dir", + "upgradeSources": ["aur"], + "server": [ + "https://some/mirror", + "https://another/mirror" + ] + } + ] + } } diff --git a/repoindex.pro b/repoindex.pro index 5e0dae5..9339261 100644 --- a/repoindex.pro +++ b/repoindex.pro @@ -12,7 +12,7 @@ TEMPLATE = app CONFIG += console # enables qDebug() -QT += core network websockets +QT += core network websockets concurrent KArchive SOURCES += main.cpp \ alpm/manager.cpp \ @@ -23,8 +23,10 @@ SOURCES += main.cpp \ network/connection.cpp \ alpm/group.cpp \ alpm/config.cpp \ - network/query.cpp \ - network/aurquery.cpp + network/aurquery.cpp \ + alpm/updatelookup.cpp \ + alpm/resolvebuildorder.cpp \ + alpm/mingwbundle.cpp HEADERS += \ alpm/manager.h \ @@ -36,8 +38,10 @@ HEADERS += \ network/connection.h \ alpm/group.h \ alpm/config.h \ - network/query.h \ - network/aurquery.h + network/aurquery.h \ + alpm/updatelookup.h \ + alpm/resolvebuildorder.h \ + alpm/mingwbundle.h DISTFILES += \ README.md \ @@ -58,9 +62,9 @@ DISTFILES += \ # libs and includepath CONFIG(debug, debug|release) { - LIBS += -L../../ -lc++utilitiesd -lalpm + LIBS += -L../../ -lc++utilitiesd -lalpm -lKF5Archive } else { - LIBS += -L../../ -lc++utilities -lalpm + LIBS += -L../../ -lc++utilities -lalpm -lKF5Archive } INCLUDEPATH += ../ diff --git a/web/css/core.css b/web/css/core.css index 2f29691..f44a68a 100644 --- a/web/css/core.css +++ b/web/css/core.css @@ -91,6 +91,10 @@ span.glyphicon { .navbar-fixed-top { border-bottom: 5px solid #337ab7; } +.navbar-form { + padding-left: 0px; + padding-right: 8px; +} /* * Package info diff --git a/web/index.html b/web/index.html index aedb51e..e439ee8 100644 --- a/web/index.html +++ b/web/index.html @@ -36,7 +36,7 @@ - Repository index + @@ -232,8 +232,9 @@ Package count Usage Signature level + Upgrade sources - Updates + Upgrades diff --git a/web/js/entrymanagement.js b/web/js/entrymanagement.js index cdc05a4..429bb2f 100644 --- a/web/js/entrymanagement.js +++ b/web/js/entrymanagement.js @@ -1,5 +1,7 @@ var repoindex = (function(repoindex) { + repoindex.removeFilterButtonHtml = ''; + repoindex.Entry = function(info, enabled) { // basic initialization this.info = info; @@ -82,15 +84,15 @@ // provide functions to apply filter and update UI this.applyFilter = function() { - if(this.customSelection) { - this.filteredEntries = this.customSelection; - } else { + //if(this.customSelection) { + // this.filteredEntries = this.customSelection; + //} else { if((this.filterName || this.filterRepos) && this.filterPred) { - this.filteredEntries = this.entries.filter(this.filterPred, this); + this.filteredEntries = this.relevantEntries().filter(this.filterPred, this); } else { - this.filteredEntries = this.entries; + this.filteredEntries = this.relevantEntries(); } - } + //} // update pagination (the pageSelected method defined above will be invoked here automatically) this.pagination.entryCount = this.filteredEntries.length; this.pagination.update(); @@ -140,17 +142,16 @@ } else { var entryText = this.customSelection ? (this.filteredEntries.length === 1 ? this.customSelectionName : this.customSelectionNamePlural) : (this.filteredEntries.length === 1 ? this.entryName : this.entryNamePlural); if(this.filterDescr) { - var removeFilterButton = ''; if(this.filterName) { return "" + this.filteredEntries.length + " " + entryText + containerText + " " + repoindex.escapeHtml(this.filterDescr) + " "+ repoindex.escapeHtml(this.filterName) + "." - + removeFilterButton; + + repoindex.removeFilterButtonHtml; } else { return "" + this.filteredEntries.length + " " + entryText + containerText + " " + repoindex.escapeHtml(this.filterDescr) + "." - + removeFilterButton; + + repoindex.removeFilterButtonHtml; } } else { return "Showing " diff --git a/web/js/packagemanagement.js b/web/js/packagemanagement.js index 6ee1646..ed66127 100644 --- a/web/js/packagemanagement.js +++ b/web/js/packagemanagement.js @@ -22,13 +22,13 @@ this.initTableRow = function() { var values = [this.info.arch, this.info.repo, this.info.name, this.info.ver, this.info.desc, this.info.bdate, ""]; for(var i = 0; i < 7; ++i) { - this.rowElement.addCell(values[i]); + this.rowElement.addCell(repoindex.makeStr(values[i])); } }; this.initTableRow(); - this.applyBasicInfo = function(info) { + this.applyBasicInfo = function(info, noUpdate) { if(info.repo) this.info.repo = info.repo; if(info.name) this.info.name = info.name; if(info.arch) this.info.arch = info.arch; @@ -36,10 +36,12 @@ if(info.desc) this.info.desc = info.desc; if(info.bdate) this.info.bdate = info.bdate; this.info.basic = true; - this.updateTableRow(); + if(!noUpdate) { + this.updateTableRow(); + } }; - this.applyFullInfo = function(info) { + this.applyFullInfo = function(info, noUpdate) { this.applyBasicInfo(info); if(info.idate) this.info.idate = info.idate; if(info.isize) this.info.isize = info.isize; @@ -60,7 +62,9 @@ if(info.file) this.info.file = info.file; if(info.files) this.info.files = info.files; this.info.full = true; - this.updateTableRow(); + if(!noUpdate) { + this.updateTableRow(); + } }; }; @@ -82,13 +86,13 @@ this.addEntry = function(repoName, packageName) { var packageInfo = { index: this.entries.length, - arch: "", + arch: undefined, repo: repoName, name: packageName, - version: "", - desc: "", - builddate: "", - flagdate: "", + version: undefined, + desc: undefined, + builddate: undefined, + flagdate: undefined, received: false }; this.entries.push(new PackageEntry(packageInfo)); @@ -169,7 +173,7 @@ if(i.url) { repoindex.setLink("pkg_url", i.url, i.url, window.open); } else { - repoindex.setText("pkg_url", "none"); + repoindex.setText("pkg_url", "unknown"); } repoindex.setText("pkg_lic", repoindex.makeArray(i.lic)); repoindex.setPackageNames("pkg_grp", repoindex.pack(i.grp), repoindex.Pages.Groups); @@ -208,7 +212,15 @@ entry.applyFullInfo(info); } } else { - repoindex.pageManager.addError("Error: " + info.error); + var errorMsg; + switch(info.error) { + case "na": + errorMsg = "The server can't find the requested (full) package info." + break; + default: + errorMsg = "The server can't deliver the requested (full) package info." + }; + repoindex.pageManager.addError("Error: " + errorMsg); } } // set properties (again) to actually show applied info @@ -260,8 +272,9 @@ }; this.showMirrorsForIndex = function(entryIndex) { - if(entryIndex >= 0 && entryIndex < this.entries.length) { - var info = this.entries[entryIndex].info; + var entry = this.entryByIndex(entryIndex); + if(entry) { + var info = entry.info; repoindex.setText("title_mirror_selection", "Mirrors for package " + repoindex.escapeHtml(info.name) + "", true); var listMirrorSelection = document.getElementById("list_mirror_selection"); listMirrorSelection.wipeChildren(); @@ -286,19 +299,29 @@ listMirrorSelection.appendChild(liElement); } mirrorsAvailable = 1; - } else if(info.repo === "local") { - mirrorsAvailable = -1; } } - if(mirrorsAvailable === 1) { - repoindex.setText("status_mirror_selection", "Select a mirror:"); - } else if(mirrorsAvailable === -1) { - repoindex.setText("status_mirror_selection", "The package entry belongs to the local database. Hence there are no mirrors available.", true); - } else { - repoindex.setText("status_mirror_selection", "No mirrors available."); - } - $("#dlg_mirror_selection").modal("show"); } + if(mirrorsAvailable !== 1) { + // no mirrors available? + if(info.repo === "local" || info.repo === "LOCAL") { + // no surprise because its an package from the local database + mirrorsAvailable = -1; + } else if(info.repo === "aur" || info.repo === "AUR") { + // no surprise because its an AUR package + mirrorsAvailable = -2; + } + } + if(mirrorsAvailable === 1) { + repoindex.setText("status_mirror_selection", "Select a mirror:"); + } else if(mirrorsAvailable === -1) { + repoindex.setText("status_mirror_selection", "The package belongs to the local database. Hence there are no mirrors available.", true); + } else if(mirrorsAvailable === -2) { + repoindex.setText("status_mirror_selection", "The package is from the Arch Linux User Repository. Hence there are no mirrors available.", true); + } else { + repoindex.setText("status_mirror_selection", "No mirrors available."); + } + $("#dlg_mirror_selection").modal("show"); }; this.showPackageNotFound = function(repo, name) { diff --git a/web/js/pagemanagement.js b/web/js/pagemanagement.js index 4fc58ec..8a13ef9 100644 --- a/web/js/pagemanagement.js +++ b/web/js/pagemanagement.js @@ -234,7 +234,7 @@ }; // provide a function to apply an entered search term - this.applySearchTerm = function(searchTerm, exact) { + this.applySearchTerm = function(searchTerm, exact, reset) { var mgr = undefined; switch(this.currentPage) { case repoindex.Pages.Packages: @@ -250,7 +250,9 @@ ; } if(mgr) { - mgr.customSelection = undefined; + if(reset) { + mgr.customSelection = undefined; + } mgr.filterName = searchTerm; mgr.filterNameExact = exact; mgr.filterDescr = searchTerm ? (exact ? "with the name" : "for the search term") : undefined; diff --git a/web/js/repomanagement.js b/web/js/repomanagement.js index 0ef0c1a..2d253ef 100644 --- a/web/js/repomanagement.js +++ b/web/js/repomanagement.js @@ -175,6 +175,7 @@ repoindex.setText("repo_pkgcount", i.packages.length); repoindex.setText("repo_usage", repoindex.makeArray(i.usage, ", ")); repoindex.setText("repo_siglevel", repoindex.makeArray(i.sigLevel, ", ")); + repoindex.setText("repo_upgrade_sources", repoindex.makeArray(i.upgradeSources, ", ")); var invokeUpdateLookupParams = {repo: i.name, invoke: "updatelookup"}; repoindex.setDownloadButton("repo_checkforupdates", "check for updates", repoindex.makeHash(repoindex.Pages.Repositories, invokeUpdateLookupParams, true), function() { repoindex.pageManager.repoManager.showAvailableUpdates(i.name); @@ -241,7 +242,15 @@ for(var i2 = 0; i2 < updates[i1].entries.length; ++i2) { var newEntry = pkgMgr.createCustomEntry(updates[i1].color); newEntry.info.index = updateEntries.length; - newEntry.applyBasicInfo(updates[i1].entries[i2]); + if(updates[i1].entries[i2].pkg) { + newEntry.applyBasicInfo(updates[i1].entries[i2].pkg, true); + if(updates[i1].entries[i2].prevVersion) { + newEntry.info.ver = updates[i1].entries[i2].prevVersion + " → " + (newEntry.info.ver ? newEntry.info.ver : "?"); + } + newEntry.updateTableRow(); + } else { + newEntry.applyBasicInfo(updates[i1].entries[i2]); + } updateEntries.push(newEntry); } } @@ -251,14 +260,17 @@ } } // show updates via package manager + pkgMgr.filterName = undefined; pkgMgr.customSelection = updateEntries; pkgMgr.customSelectionName = "upgrade"; pkgMgr.customSelectionNamePlural = "upgrades"; var quandityInfo = repoindex.makeQuandityInfo(updates); if(quandityInfo) { - pkgMgr.customSelectionInfoText = "Updates for the repository " + repoindex.escapeHtml(repo) + ": " + quandityInfo; + pkgMgr.customSelectionInfoText = "Updates for the repository " + + repoindex.escapeHtml(repo) + + ": " + quandityInfo + + repoindex.removeFilterButtonHtml; } - pkgMgr.filterName = repo; pkgMgr.filterDescr = "for the repository" pkgMgr.invalidate(); repoindex.pageManager.setPage(repoindex.Pages.Packages); diff --git a/web/js/utils.js b/web/js/utils.js index 38168d5..b798499 100644 --- a/web/js/utils.js +++ b/web/js/utils.js @@ -212,8 +212,8 @@ } }; - repoindex.makeStr = function(str) { - return str ? str : ""; + repoindex.makeStr = function(str, na) { + return str ? str : (na ? na : "unknown"); }; repoindex.makeArray = function(array, separator) { @@ -234,7 +234,7 @@ return (sizeInByte / 1099511627776.0).toFixed(2) + " TiB"; } } else { - return ""; + return "unknown"; } };