From 715633c96e4cb40328a896ae443e06100a364859 Mon Sep 17 00:00:00 2001 From: Martchus Date: Mon, 21 Sep 2015 22:16:19 +0200 Subject: [PATCH] added suggestions lookup, added src info parser --- alpm/alpmdatabase.cpp | 4 +- alpm/alpmdatabase.h | 5 +- alpm/aurpackage.cpp | 7 ++ alpm/aurpackage.h | 5 +- alpm/config.cpp | 11 +- alpm/config.h | 2 + alpm/manager.cpp | 175 ++++++++++++++++--------------- alpm/manager.h | 25 +++-- alpm/mingwbundle.cpp | 192 ++++++++++++++++++++++------------ alpm/mingwbundle.h | 7 +- alpm/package.cpp | 154 +++++++++++++++++++++++---- alpm/package.h | 32 +++++- alpm/repository.cpp | 147 +++++++++++++++++++++++++- alpm/repository.h | 62 ++++++++--- alpm/resolvebuildorder.cpp | 2 +- alpm/suggestionslookup.cpp | 56 ++++++++++ alpm/suggestionslookup.h | 51 +++++++++ alpm/upgradelookup.cpp | 20 ++-- alpm/upgradelookup.h | 9 +- alpm/utilities.cpp | 1 + general.pri | 12 ++- main.cpp | 11 +- network/connection.cpp | 43 +++++++- network/connection.h | 4 +- network/server.cpp | 2 +- network/server.h | 4 +- network/userrepository.cpp | 37 +++++-- network/userrepository.h | 4 +- repoindex.pro | 7 +- web/index.html | 14 ++- web/js/client.js | 201 ++++++++++++++++++++++++++++++++---- web/js/entrymanagement.js | 28 ++--- web/js/groupmanagement.js | 14 +-- web/js/packagemanagement.js | 195 +++++++++++----------------------- web/js/pagemanagement.js | 7 +- web/js/pagination.js | 14 +-- web/js/repomanagement.js | 89 ++++++++-------- 37 files changed, 1169 insertions(+), 484 deletions(-) create mode 100644 alpm/suggestionslookup.cpp create mode 100644 alpm/suggestionslookup.h diff --git a/alpm/alpmdatabase.cpp b/alpm/alpmdatabase.cpp index bb445c2..20135b6 100644 --- a/alpm/alpmdatabase.cpp +++ b/alpm/alpmdatabase.cpp @@ -58,8 +58,8 @@ PackageLoader::PackageLoader(AlpmDatabase *db) /*! * \brief Creates a new instance wrapping the specified database struct. */ -RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, QObject *parent) : - Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), parent), +RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, uint32 index, QObject *parent) : + Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), index, parent), m_ptr(dataBase), m_dbFile(QStringLiteral("%1/sync/%2").arg(dbPath, m_name)) {} diff --git a/alpm/alpmdatabase.h b/alpm/alpmdatabase.h index 376dec3..593eafe 100644 --- a/alpm/alpmdatabase.h +++ b/alpm/alpmdatabase.h @@ -35,8 +35,9 @@ inline QFuture &PackageLoader::future() class AlpmDatabase : public Repository { friend class EmplacePackage; + public: - explicit AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, QObject *parent = nullptr); + explicit AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, uint32 index = invalidIndex, QObject *parent = nullptr); PackageLoader *init(); // explicit AlpmDatabase(const QString &dataBaseFile, QObject *parent = nullptr); @@ -112,7 +113,7 @@ inline bool AlpmDatabase::setServers(StringList servers) } /*! - * \brief Performs a search using the build in ALPM function. + * \brief Performs a search using the build-in ALPM function. */ inline PackageList AlpmDatabase::search(StringList terms) const { diff --git a/alpm/aurpackage.cpp b/alpm/aurpackage.cpp index 9a9332f..33ac23e 100644 --- a/alpm/aurpackage.cpp +++ b/alpm/aurpackage.cpp @@ -37,6 +37,13 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue, UserRepository *repositor m_tarUrl = obj.value(QStringLiteral("URLPath")).toString(); } +/*! + * \brief Creates a new instance where only the name is known. + */ +AurPackage::AurPackage(const QString &name, UserRepository *repository) : + Package(name, repository) +{} + /*! * \brief Creates a new, empty instance. * \remarks The only purpose of this c'tor is to use it with restoreFromCacheStream(). diff --git a/alpm/aurpackage.h b/alpm/aurpackage.h index e4e2857..20d56dd 100644 --- a/alpm/aurpackage.h +++ b/alpm/aurpackage.h @@ -10,8 +10,9 @@ class UserRepository; class AurPackage : public Package { public: - AurPackage(const QJsonValue &aurJsonValue, UserRepository *repository); - AurPackage(UserRepository *repository); + explicit AurPackage(const QJsonValue &aurJsonValue, UserRepository *repository); + explicit AurPackage(const QString &name, UserRepository *repository); + explicit AurPackage(UserRepository *repository); }; } // namespace PackageManagement diff --git a/alpm/config.cpp b/alpm/config.cpp index 85a0fa1..e165930 100644 --- a/alpm/config.cpp +++ b/alpm/config.cpp @@ -49,6 +49,8 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : targetNameArg("target-name", "n", "specifies the name of the target archive"), targetFormatArg("target-format", "e", "specifies the format of the target archive"), iconThemesArg("icon-packages", "i", "specifies the names of the icon packages to include"), + defaultIconThemeArg("default-icon-theme", string(), "specifies the name of the default icon theme (should be included in --icon-packages)"), + extraPackagesArg("extra-packages", string(), "specifies extra packages to be included"), shSyntaxArg("sh-syntax", string(), "prints the output using shell syntax: export REPOINDEX_RESULTS=('res1' 'res2' 'res3') or export REPOINDEX_ERROR='some error message'"), repoArg("repo", string(), "specifies the repository") { @@ -110,13 +112,19 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : iconThemesArg.setCombinable(true); iconThemesArg.setRequiredValueCount(-1); iconThemesArg.setValueNames(pkgValueNames); + defaultIconThemeArg.setCombinable(true); + defaultIconThemeArg.setRequiredValueCount(1); + defaultIconThemeArg.setValueNames({"theme name"}); + extraPackagesArg.setCombinable(true); + extraPackagesArg.setRequiredValueCount(-1); + extraPackagesArg.setValueNames(pkgValueNames); shSyntaxArg.setCombinable(true); repoArg.setRequiredValueCount(1); repoArg.setValueNames({"repo name"}); serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg}); upgradeLookupArg.setSecondaryArguments({&shSyntaxArg}); buildOrderArg.setSecondaryArguments({&aurArg, &verboseArg, &shSyntaxArg}); - mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg}); + mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg, &defaultIconThemeArg, &extraPackagesArg}); parser.setMainArguments({&buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &helpArg}); } @@ -132,6 +140,7 @@ Config::Config() : m_alpmRootDir(QStringLiteral("/")), m_alpmDbPath(QStringLiteral("/var/lib/pacman")), m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")), + m_cacheDir(QStringLiteral(".")), m_websocketServerListeningAddr(QHostAddress::LocalHost), m_websocketServerListeningPort(1234), m_serverInsecure(false), diff --git a/alpm/config.h b/alpm/config.h index b9bfd0d..7790d04 100644 --- a/alpm/config.h +++ b/alpm/config.h @@ -40,6 +40,8 @@ public: ApplicationUtilities::Argument targetNameArg; ApplicationUtilities::Argument targetFormatArg; ApplicationUtilities::Argument iconThemesArg; + ApplicationUtilities::Argument defaultIconThemeArg; + ApplicationUtilities::Argument extraPackagesArg; ApplicationUtilities::Argument shSyntaxArg; ApplicationUtilities::Argument repoArg; }; diff --git a/alpm/manager.cpp b/alpm/manager.cpp index fe76511..ce8c656 100644 --- a/alpm/manager.cpp +++ b/alpm/manager.cpp @@ -1,8 +1,8 @@ #include "./manager.h" -#include "./alpmdatabase.h" #include "./utilities.h" #include "./list.h" #include "./config.h" +#include "./alpmdatabase.h" #include "../network/userrepository.h" @@ -63,11 +63,7 @@ Manager::Manager(const Config &config) : m_sigLevel(defaultSigLevel), m_localFileSigLevel(ALPM_SIG_USE_DEFAULT) { - alpm_errno_t err; - if(!(m_handle = alpm_initialize(config.alpmRootDir().toLocal8Bit().data(), config.alpmDbPath().toLocal8Bit().data(), &err))) { - throw runtime_error(string("Cannot initialize ALPM: ") + alpm_strerror(err)); - } - m_localDb = make_unique(alpm_get_localdb(m_handle), config.alpmDbPath()); + initAlpmHandle(); if(config.isAurEnabled()) { m_userRepo = make_unique(m_networkAccessManager); } @@ -81,7 +77,7 @@ Manager::~Manager() if(m_writeCacheBeforeGone) { writeCache(); } - alpm_release(m_handle); + cleanupAlpm(); } /*! @@ -289,9 +285,9 @@ int Manager::parseUsage(const string &usageStr) } /*! - * \brief Parses and applies the Pacman configuration. Registers the listed sync databases. + * \brief Registers sync databases listed in the Pacman config file. Also reads the cache dir. */ -void Manager::applyPacmanConfig() +void Manager::registerDataBasesFromPacmanConfig() { // open config file and parse as ini try { @@ -309,21 +305,21 @@ void Manager::applyPacmanConfig() // read relevant options static const string sigLevelKey("SigLevel"); static const string usageKey("Usage"); - int globalSigLevel; - try { - const auto &options = config.at("options"); - const auto &specifiedArch = lastValue(options, "Architecture"); - if(!specifiedArch.empty() && specifiedArch != "auto") { - arch = specifiedArch; + int globalSigLevel = defaultSigLevel; + for(auto &scope : config) { + if(scope.first == "options") { + // iterate through all "config" scopes (just to cover the case that there are multiple "options" scopes) + const auto &options = scope.second; + const auto &specifiedArch = lastValue(options, "Architecture"); + if(!specifiedArch.empty() && specifiedArch != "auto") { + arch = specifiedArch; + } + const auto &specifiedDir = lastValue(options, "CacheDir"); + if(!specifiedDir.empty()) { + m_pacmanCacheDir = QString::fromStdString(specifiedDir); + } + globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey)); } - const auto &specifiedDir = lastValue(options, "CacheDir"); - if(!specifiedDir.empty()) { - m_pacmanCacheDir = QString::fromStdString(specifiedDir); - } - globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey)); - } catch(const out_of_range &) { - // no options specified - globalSigLevel = defaultSigLevel; } // register sync databases unordered_map includedInis; @@ -347,7 +343,7 @@ void Manager::applyPacmanConfig() // set usage if(alpm_db_set_usage(db, static_cast(usage)) == 0) { if(m_config.isVerbose() || m_config.runServer()) { - cerr << shchar << "Added database [" << scope.first << "]" << endl; + cerr << shchar << "Added database [" << scope.first << ']' << endl; } } else { if(m_config.isVerbose() || m_config.runServer()) { @@ -379,28 +375,31 @@ void Manager::applyPacmanConfig() } } try { - const auto &includedScope = includedIni.data().at(string()); - for(auto range = includedScope.equal_range("Server"); range.first != range.second; ++range.first) { - string url = range.first->second; - findAndReplace(url, "$repo", scope.first); - findAndReplace(url, "$arch", arch); - alpm_db_add_server(db, url.c_str()); - if(m_config.isVerbose() || m_config.runServer()) { - cerr << shchar << "Added server: " << url << endl; + for(auto &scope : includedIni.data()) { + if(scope.first.empty()) { + for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) { + string url = range.first->second; + findAndReplace(url, "$repo", scope.first); + findAndReplace(url, "$arch", arch); + alpm_db_add_server(db, url.c_str()); + if(m_config.isVerbose() || m_config.runServer()) { + cerr << shchar << "Added server: " << url << endl; + } + } } } } catch (const out_of_range &) { cerr << shchar << "Warning: Included file \"" << path << "\" has no values." << endl; } } - auto emplaced = m_syncDbs.emplace(dbName, make_unique(db, m_config.alpmDbPath())); - // add sync db to internal map + // add sync db to internal map (use as index size + 1 because the local database has index 0) + auto emplaced = m_syncDbs.emplace(dbName, make_unique(db, m_config.alpmDbPath(), m_syncDbs.size() + 1)); if(usage & ALPM_DB_USAGE_UPGRADE) { // -> db is used to upgrade local database localDataBase()->upgradeSources() << emplaced.first->second.get(); } } else { - cerr << shchar << "Error: Unable to add sync database [" << scope.first << "]" << endl; + cerr << shchar << "Error: Unable to add sync database [" << scope.first << ']' << endl; } } } @@ -411,16 +410,16 @@ void Manager::applyPacmanConfig() } /*! - * \brief Applies the repository index configuration. + * \brief Registers sync databases listed in the repository index configuration. */ -void Manager::applyRepoIndexConfig() +void Manager::registerDatabasesFromRepoIndexConfig() { // check whether an entry already exists, if not create a new one for(const RepoEntry &repoEntry : m_config.repoEntries()) { AlpmDatabase *syncDb = nullptr; try { syncDb = m_syncDbs.at(repoEntry.name()).get(); - cerr << shchar << "Applying config for database [" << syncDb->name() << "]" << endl; + cerr << shchar << "Applying config for database [" << syncDb->name() << ']' << endl; if(!repoEntry.dataBasePath().isEmpty()) { cerr << shchar << "Warning: Can't use data base path specified in repo index config because the repo \"" << repoEntry.name() << "\" has already been added from the Pacman config." << endl; @@ -439,13 +438,13 @@ void Manager::applyRepoIndexConfig() } else { // TODO: database path auto *db = alpm_register_syncdb(m_handle, repoEntry.name().toLocal8Bit().data(), static_cast(repoEntry.sigLevel())); - auto emplaced = m_syncDbs.emplace(repoEntry.name(), make_unique(db, m_config.alpmDbPath())); + auto emplaced = m_syncDbs.emplace(repoEntry.name(), make_unique(db, m_config.alpmDbPath(), m_syncDbs.size() + 1)); if(emplaced.second) { syncDb = emplaced.first->second.get(); syncDb->setSourcesDirectory(repoEntry.sourceDir()); syncDb->setPackagesDirectory(repoEntry.packageDir()); if(m_config.isVerbose() || m_config.runServer()) { - cerr << shchar << "Added database [" << repoEntry.name() << "]" << endl; + cerr << shchar << "Added database [" << repoEntry.name() << ']' << endl; } } } @@ -556,7 +555,7 @@ void Manager::unregisterSyncDataBases() * \brief Returns a list of all sync databases. * \remarks Sync databases must be registered with parsePacmanConfig() before. */ -const map > &Manager::syncDatabases() const +const std::map > &Manager::syncDatabases() const { return m_syncDbs; // m_syncDbs has been filled when the databases were registered } @@ -567,26 +566,26 @@ const map > &Manager::syncDatabases() const * The results include the local database ("local") and the names of * the registered sync databases. */ -const QJsonArray &Manager::basicRepoInfo() const +const QJsonObject &Manager::basicRepoInfo() const { if(m_basicRepoInfo.isEmpty()) { QMutexLocker locker(&m_basicRepoInfoMutex); if(m_basicRepoInfo.isEmpty()) { // add local data base - m_basicRepoInfo << localDataBase()->basicInfo(); + m_basicRepoInfo.insert(localDataBase()->name(), localDataBase()->basicInfo()); // add sync data bases for(const auto &syncDb : syncDatabases()) { // 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 << syncDb.second->basicInfo(); + m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo()); } else { - m_basicRepoInfo << syncDb.second->basicInfo(); + m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo()); } } // add AUR - if(config().isAurEnabled()) { - m_basicRepoInfo << userRepository()->basicInfo(); + if(userRepository()) { + m_basicRepoInfo.insert(userRepository()->name(), userRepository()->basicInfo()); } } } @@ -596,28 +595,33 @@ const QJsonArray &Manager::basicRepoInfo() const /*! * \brief Returns package information for the specified selection of packages. */ -const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, bool full) const +const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const { - QJsonArray pkgInfos; + QJsonArray results; for(auto i = pkgSelection.constBegin(), end = pkgSelection.constEnd(); i != end; ++i) { if(auto *repo = repositoryByName(i.key())) { for(const auto &entry : i.value().toArray()) { const auto entryObj = entry.toObject(); const auto pkgName = entryObj.value(QStringLiteral("name")).toString(); if(!pkgName.isEmpty()) { - QJsonObject pkgInfo; + QJsonObject res; if(auto *pkg = repo->packageByName(pkgName)) { - pkgInfo = full ? pkg->fullInfo() : pkg->basicInfo(); + if(part & Basics) { + res.insert(QStringLiteral("basics"), pkg->basicInfo()); + } + if(part & Details) { + res.insert(QStringLiteral("details"), pkg->detailedInfo()); + } } else { - pkgInfo.insert(QStringLiteral("error"), QStringLiteral("na")); + res.insert(QStringLiteral("error"), QStringLiteral("na")); } - pkgInfo.insert(QStringLiteral("name"), pkgName); - pkgInfo.insert(QStringLiteral("repo"), repo->name()); + res.insert(QStringLiteral("name"), pkgName); + res.insert(QStringLiteral("repo"), repo->name()); const auto index = entryObj.value(QStringLiteral("index")); if(!index.isNull() && !index.isUndefined()) { - pkgInfo.insert(QStringLiteral("index"), index); + res.insert(QStringLiteral("index"), index); } - pkgInfos << pkgInfo; + results << res; } } } else { @@ -625,11 +629,11 @@ const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, bool full QJsonObject errorObj; errorObj.insert(QStringLiteral("repo"), i.key()); errorObj.insert(QStringLiteral("error"), QStringLiteral("na")); - pkgInfos << errorObj; + results << errorObj; } } - return pkgInfos; + return results; } /*! @@ -649,6 +653,33 @@ const QJsonArray &Manager::groupInfo() const return m_groupInfo; } +/*! + * \brief Initiates the ALPM library handle and databases. + * \remarks Do not call this function, if ALPM is already initiated. + */ +void Manager::initAlpmHandle() +{ + alpm_errno_t err; + if(!(m_handle = alpm_initialize(config().alpmRootDir().toLocal8Bit().data(), config().alpmDbPath().toLocal8Bit().data(), &err))) { + throw runtime_error(string("Cannot initialize ALPM: ") + alpm_strerror(err)); + } + m_localDb = make_unique(alpm_get_localdb(m_handle), config().alpmDbPath(), 0); +} + +/*! + * \brief Frees the ALPM library handle and databases. + * \remarks Do not call any ALPM related methods except initAlpmHandle() to reinit ALPM. + */ +void Manager::cleanupAlpm() +{ + if(m_handle) { + alpm_release(m_handle); + m_handle = 0; + m_localDb.reset(); + m_syncDbs.clear(); + } +} + /*! * \brief Returns the ALPM database with the specified name. */ @@ -717,34 +748,6 @@ Repository *Manager::repositoryByName(const QString &name) } } -/*! - * \brief Returns a list of all repositories excluding the local database. - */ -QList Manager::repositories() const -{ - QList repos; - repos.reserve(m_syncDbs.size() + 1); - for(const auto &dbEntry : m_syncDbs) { - repos << dbEntry.second.get(); - } - repos << m_userRepo.get(); - return repos; -} - -/*! - * \brief Returns a list of all repositories excluding the local database. - */ -QList Manager::repositories() -{ - QList repos; - repos.reserve(m_syncDbs.size() + 1); - for(auto &dbEntry : m_syncDbs) { - repos << dbEntry.second.get(); - } - repos << m_userRepo.get(); - return repos; -} - /*! * \brief Checks the specified database for upgrades. * diff --git a/alpm/manager.h b/alpm/manager.h index ba3bd36..32fb91b 100644 --- a/alpm/manager.h +++ b/alpm/manager.h @@ -23,6 +23,13 @@ class AlpmDatabase; class Manager { public: + enum PackageInfoPart { + None = 0x0, + Basics = 0x1, + Details = 0x2, + }; + Q_DECLARE_FLAGS(PackageInfoParts, PackageInfoPart) + explicit Manager(const Config &config); Manager(const Manager &other) = delete; Manager(Manager &&other) = delete; @@ -40,8 +47,10 @@ public: 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 initAlpmHandle(); + void cleanupAlpm(); + void registerDataBasesFromPacmanConfig(); + void registerDatabasesFromRepoIndexConfig(); void initAlpmDataBases(bool computeRequiredBy); void writeCache(); void restoreCache(); @@ -69,19 +78,15 @@ public: Repository *repositoryByName(const QString &name); const UserRepository *userRepository() const; UserRepository *userRepository(); - QList repositories() const; - QList repositories(); const UpgradeLookupResults checkForUpgrades(AlpmDatabase *db) const; // JSON serialization, handling JSON requests const QJsonObject basicRepoInfo(const Repository *packageSource) const; - const QJsonArray &basicRepoInfo() const; - const QJsonArray packageInfo(const QJsonObject &pkgSelection, bool full = true) const; + const QJsonObject &basicRepoInfo() const; + const QJsonArray packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const; const QJsonArray &groupInfo() const; private: - void cleanup(); - const Config &m_config; bool m_writeCacheBeforeGone; alpm_handle_t *m_handle; @@ -92,12 +97,14 @@ private: std::unique_ptr m_userRepo; std::unique_ptr m_localDb; std::map > m_syncDbs; - mutable QJsonArray m_basicRepoInfo; + mutable QJsonObject m_basicRepoInfo; mutable QMutex m_basicRepoInfoMutex; mutable QJsonArray m_groupInfo; mutable QMutex m_groupInfoMutex; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Manager::PackageInfoParts) + /*! * \brief Returns the configuration of the manager. * \remarks The configuration has been specified when constructing the manager. diff --git a/alpm/mingwbundle.cpp b/alpm/mingwbundle.cpp index 4ad2421..c5fef9d 100644 --- a/alpm/mingwbundle.cpp +++ b/alpm/mingwbundle.cpp @@ -27,8 +27,9 @@ using namespace Utilities; const string prefix("mingw-w64-"); -MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages) : - m_manager(manager) +MingwBundle::MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages) : + m_manager(manager), + m_extraPackages(extraPackages) { cerr << shchar << "Resolving dependencies ..." << endl; string missing; @@ -36,7 +37,8 @@ MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::Str for(const auto &pkgName : packages) { bool found = false; for(const auto &syncDb : manager.syncDatabases()) { - if(auto *pkg = syncDb.second->packageByName(QString::fromLocal8Bit(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data()))) { + const Dependency dep(QString::fromLocal8Bit(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data())); + if(auto *pkg = syncDb.second->packageProviding(dep)) { if(missing.empty()) { decltype(m_packages)::value_type entry(syncDb.second.get(), pkg); if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) { @@ -120,7 +122,8 @@ enum class RelevantFileType Translation, QtTranslation, QtPlugin, - Icon + IconTheme, + ConfigFile }; enum class RelevantFileArch @@ -133,12 +136,14 @@ enum class RelevantFileArch struct RelevantFile { RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) : - file(file), + name(file->name()), + data(file->data()), fileType(type), arch(arch), subDir(subDir) {} - const KArchiveFile *file; + QString name; + QByteArray data; RelevantFileType fileType; RelevantFileArch arch; QString subDir; @@ -191,8 +196,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) 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); + pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), RelevantFileType::Binary, root.first); } } } @@ -214,8 +218,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) for(const auto &entryName : categoryDir->entries()) { if(const auto *pluginEntry = categoryDir->entry(entryName)) { if(pluginEntry->isFile()) { - const auto *pluginFile = static_cast(pluginEntry); - pkgFileInfo.relevantFiles.emplace_back(pluginFile, RelevantFileType::QtPlugin, root.first, categoryDir->name()); + pkgFileInfo.relevantFiles.emplace_back(static_cast(pluginEntry), RelevantFileType::QtPlugin, root.first, categoryDir->name()); } } } @@ -237,8 +240,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) if(entryName.endsWith(QLatin1String(".qm"))) { if(const auto *qmEntry = trDir->entry(entryName)) { if(qmEntry->isFile()) { - const auto *qmFile = static_cast(qmEntry); - pkgFileInfo.relevantFiles.emplace_back(qmFile, RelevantFileType::QtTranslation, root.first); + pkgFileInfo.relevantFiles.emplace_back(static_cast(qmEntry), RelevantFileType::QtTranslation, root.first); } } } @@ -249,6 +251,34 @@ void getFiles(PkgFileInfo &pkgFileInfo) const auto *appEntry = shareDir->entry(pkgFileInfo.name); if(appEntry && appEntry->isDirectory()) { const auto *appDir = static_cast(appEntry); + for(const auto &entryName : appDir->entries()) { + const auto *entry = appDir->entry(entryName); + if(entry->isFile()) { + pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), RelevantFileType::ConfigFile, root.first); + } else { + const auto subDir = static_cast(entry); + if(entryName == QLatin1String("translations")) { + for(const auto &entryName : subDir->entries()) { + if(entryName.endsWith(QLatin1String(".qm"))) { + if(const auto *qmEntry = subDir->entry(entryName)) { + if(qmEntry->isFile()) { + pkgFileInfo.relevantFiles.emplace_back(static_cast(qmEntry), RelevantFileType::Translation, root.first); + } + } + } + } + } else { + for(const auto &entryName : subDir->entries()) { + if(const auto *configEntry = subDir->entry(entryName)) { + if(configEntry->isFile()) { + pkgFileInfo.relevantFiles.emplace_back(static_cast(configEntry), RelevantFileType::ConfigFile, root.first, subDir->name()); + } + } + } + } + } + + } const auto *trEntry = appDir->entry(QStringLiteral("translations")); if(trEntry && trEntry->isDirectory()) { const auto trDir = static_cast(trEntry); @@ -263,6 +293,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } } + } } } @@ -274,7 +305,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) for(const auto &themeName : iconsDir->entries()) { const auto *themeEntry = iconsDir->entry(themeName); if(themeEntry && themeEntry->isDirectory()) { - addEntries(pkgFileInfo, RelevantFileType::Icon, static_cast(themeEntry)); + addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast(themeEntry)); } } } @@ -283,7 +314,68 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } -void MingwBundle::createBundle(const string &targetDir, const string &targetName, const string &targetFormat) const +void makeArchive(const list &pkgFiles, const QByteArray &pkgList, const QByteArray &indexFile, RelevantFileArch arch, const QString &root, const string &targetDir, const string &targetName, const string &targetFormat) +{ + QString targetPath = qstr(targetDir) % QChar('/') % root % QChar('-') % qstr(targetName) % QChar('.') % qstr(targetFormat); + cerr << shchar << "Making archive \"" << targetPath.toLocal8Bit().data() << "\" ..." << endl; + unique_ptr targetArchive; + if(targetFormat == "7z") { + targetArchive = make_unique(targetPath); + } else if(targetFormat == "zip") { + targetArchive = make_unique(targetPath); + } else if(ConversionUtilities::startsWith(targetFormat, "tar")) { + targetArchive = make_unique(targetPath); + } else { + throw runtime_error("Specified archive format \"" + targetFormat + "\" is unknown."); + } + if(targetArchive->open(QIODevice::WriteOnly)) { + // add package list + if(!pkgList.isEmpty()) { + targetArchive->writeFile(root % QStringLiteral("/var/lib/repoindex/packages.list"), pkgList, 0100644); + } + // set default icon theme + if(!indexFile.isEmpty()) { + targetArchive->writeFile(root % QStringLiteral("/share/icons/default/index.theme"), indexFile, 0100644); + } + // add relevant files from packages + for(const auto &pkgFile : pkgFiles) { + for(const RelevantFile &relevantFile : pkgFile.relevantFiles) { + if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == arch) { + switch(relevantFile.fileType) { + case RelevantFileType::Binary: + targetArchive->writeFile(root % QStringLiteral("/bin/") % relevantFile.name, relevantFile.data, 0100755); + break; + case RelevantFileType::Translation: + targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QStringLiteral("/translations/") % relevantFile.name, relevantFile.data, 0100644); + break; + case RelevantFileType::QtTranslation: + targetArchive->writeFile(root % QStringLiteral("/share/qt/translations/") % relevantFile.name, relevantFile.data, 0100644); + break; + case RelevantFileType::QtPlugin: + targetArchive->writeFile(root % QStringLiteral("/bin/") % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100755); + break; + case RelevantFileType::IconTheme: + targetArchive->writeFile(root % QStringLiteral("/share/icons/") % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100644); + break; + case RelevantFileType::ConfigFile: + if(relevantFile.subDir.isEmpty()) { + targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QChar('/') % relevantFile.name, relevantFile.data, 0100644); + } else { + targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QChar('/') % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100644); + } + break; + } + } + } + } + } else if(targetArchive->device()) { + cerr << shchar << "Error: Unable to open target archive: " << targetArchive->device()->errorString().toLocal8Bit().data() << endl; + } else { + cerr << shchar << "Error: Unable to open target archive." << endl; + } +} + +void MingwBundle::createBundle(const string &targetDir, const string &targetName, const string &targetFormat, const string &defaultIconTheme) const { cerr << shchar << "Gathering relevant files ..." << endl; // get package files @@ -304,6 +396,15 @@ void MingwBundle::createBundle(const string &targetDir, const string &targetName } pkgFiles.emplace_back(entry.second->name().startsWith(QLatin1String("mingw-w64-")) ? entry.second->name().mid(10) : entry.second->name(), pkgFile); } + for(const auto &pkgFileStdStr : m_extraPackages) { + QString pkgFile = QString::fromLocal8Bit(pkgFileStdStr.data()); + if(QFile::exists(pkgFile)) { + const auto pkg = m_manager.packageFromFile(pkgFileStdStr.data()); // do not catch the exception here + pkgFiles.emplace_back(pkg->name().startsWith(QLatin1String("mingw-w64-")) ? pkg->name().mid(10) : pkg->name(), pkgFile); + } else { + throw runtime_error("The specified extra package \"" + pkgFileStdStr + "\" can't be found."); + } + } // get relevant files from packages QtConcurrent::blockingMap(pkgFiles, getFiles); // check whether all packages could be opened @@ -325,59 +426,18 @@ void MingwBundle::createBundle(const string &targetDir, const string &targetName QJsonDocument pkgList; pkgList.setArray(pkgArray); QByteArray pkgListBytes = pkgList.toJson(); - // make target archive - static const QString user(QStringLiteral("root")); - static const QString &group = user; - static const pair roots[] = { - make_pair(RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32")), - make_pair(RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32")) - }; - for(const auto &root : roots) { - QString targetPath = qstr(targetDir) % QChar('/') % root.second % QChar('-') % qstr(targetName) % QChar('.') % qstr(targetFormat); - cerr << shchar << "Making archive \"" << targetPath.toLocal8Bit().data() << "\" ..." << endl; - unique_ptr targetArchive; - if(targetFormat == "7z") { - targetArchive = make_unique(targetPath); - } else if(targetFormat == "zip") { - targetArchive = make_unique(targetPath); - } else if(ConversionUtilities::startsWith(targetFormat, "tar")) { - targetArchive = make_unique(targetPath); - } else { - throw runtime_error("Specified archive format \"" + targetFormat + "\" is unknown."); - } - if(targetArchive->open(QIODevice::WriteOnly)) { - // add package list - targetArchive->writeFile(root.second % QStringLiteral("/var/lib/repoindex/packages.list"), pkgListBytes, 0100644, user, group); - // add relevant files from packages - for(const auto &pkgFile : pkgFiles) { - for(const RelevantFile &relevantFile : pkgFile.relevantFiles) { - if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == root.first) { - switch(relevantFile.fileType) { - case RelevantFileType::Binary: - targetArchive->writeFile(root.second % QStringLiteral("/bin/") % relevantFile.file->name(), relevantFile.file->data(), 0100755, user, group); - break; - case RelevantFileType::Translation: - targetArchive->writeFile(root.second % QStringLiteral("/share/") % pkgFile.name % QStringLiteral("/translations/") % relevantFile.file->name(), relevantFile.file->data(), 0100644, user, group); - break; - case RelevantFileType::QtTranslation: - targetArchive->writeFile(root.second % QStringLiteral("/share/qt/translations/") % relevantFile.file->name(), relevantFile.file->data(), 0100644, user, group); - break; - case RelevantFileType::QtPlugin: - targetArchive->writeFile(root.second % QStringLiteral("/bin/") % relevantFile.subDir % QChar('/') % relevantFile.file->name(), relevantFile.file->data(), 0100755, user, group); - break; - case RelevantFileType::Icon: - targetArchive->writeFile(root.second % QStringLiteral("/share/icons/") % relevantFile.subDir % QChar('/') % relevantFile.file->name(), relevantFile.file->data(), 0100644, user, group); - break; - } - } - } - } - } else if(targetArchive->device()) { - throw runtime_error("Unable to open target archive: " + string(targetArchive->device()->errorString().toLocal8Bit().data())); - } else { - throw runtime_error("Unable to open target archive."); - } + QByteArray indexFileBytes; + if(!defaultIconTheme.empty()) { + indexFileBytes.reserve(23 + defaultIconTheme.size()); + indexFileBytes.append("[Icon Theme]\nInherits="); + indexFileBytes.append(defaultIconTheme.data()); + indexFileBytes.append('\n'); } + // make target archive + auto run1 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat))); + auto run2 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat))); + run1.waitForFinished(); + run2.waitForFinished(); } } // namespace PackageManagement diff --git a/alpm/mingwbundle.h b/alpm/mingwbundle.h index ac48ef3..ee21f30 100644 --- a/alpm/mingwbundle.h +++ b/alpm/mingwbundle.h @@ -15,15 +15,16 @@ class Manager; class MingwBundle { public: - MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages); + MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages); - void createBundle(const std::string &targetDir, const std::string &targetName, const std::string &targetFormat) const; + void createBundle(const std::string &targetDir, const std::string &targetName, const std::string &targetFormat, const std::string &defaultIconTheme) const; private: void addDependencies(const Package *pkg); - const Manager &m_manager; + Manager &m_manager; std::list > m_packages; + const ApplicationUtilities::StringVector &m_extraPackages; }; } // namespace PackageManagement diff --git a/alpm/package.cpp b/alpm/package.cpp index 8d154e2..5db17eb 100644 --- a/alpm/package.cpp +++ b/alpm/package.cpp @@ -1,4 +1,4 @@ -#include "./package.h" + #include "./package.h" #include "./alpmdatabase.h" #include "./utilities.h" #include "./repository.h" @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -32,6 +33,7 @@ namespace RepoIndex { Package::Package(const QString &name, Repository *repository) : m_origin(PackageOrigin::Unknown), m_repository(repository), + m_timeStamp(DateTime::now()), m_hasGeneralInfo(false), m_name(name), m_requiredByComputed(false), @@ -293,7 +295,7 @@ using namespace Utilities; */ QJsonObject Package::basicInfo(bool includeRepoAndName) const { - QJsonObject info; + QJsonObject info; if(includeRepoAndName) { if(repository()) { put(info, QStringLiteral("repo"), repository()->name()); @@ -312,24 +314,11 @@ QJsonObject Package::basicInfo(bool includeRepoAndName) const /*! * \brief Returns full information about the package as JSON object. */ -QJsonObject Package::fullInfo(bool includeRepoAndName) const +QJsonObject Package::detailedInfo() const { QJsonObject info; - if(includeRepoAndName) { - if(repository()) { - put(info, QStringLiteral("repo"), repository()->name()); - } - put(info, QStringLiteral("name"), name()); - } - put(info, QStringLiteral("build_avail"), hasBuildRelatedMetaData()); - put(info, QStringLiteral("src_avail"), hasSourceRelatedMetaData()); - put(info, QStringLiteral("archs"), architectures()); - put(info, QStringLiteral("arch"), buildArchitecture()); - put(info, QStringLiteral("ver"), version()); - put(info, QStringLiteral("desc"), description()); - put(info, QStringLiteral("bdate"), buildDate()); - put(info, QStringLiteral("bdate"), buildDate()); - put(info, QStringLiteral("flagdate"), outOfDate()); + put(info, QStringLiteral("buildAvail"), hasBuildRelatedMetaData()); + put(info, QStringLiteral("srcAvail"), hasSourceRelatedMetaData()); put(info, QStringLiteral("idate"), installDate()); put(info, QStringLiteral("isize"), QJsonValue(static_cast(installedSize()))); put(info, QStringLiteral("url"), upstreamUrl()); @@ -356,7 +345,7 @@ QJsonObject Package::fullInfo(bool includeRepoAndName) const */ void Package::writeToCacheStream(QDataStream &out) { - out << static_cast(m_origin); + out << static_cast(m_origin) << m_timeStamp; // general info out << m_hasGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses << m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides @@ -389,6 +378,7 @@ void Package::restoreFromCacheStream(QDataStream &in) // origin in >> tmp; m_origin = static_cast(tmp); // TODO: validate value + in >> m_timeStamp; // general info in >> m_hasGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses >> m_groups >> m_dependencies >> m_optionalDependencies >> m_conflicts >> m_provides @@ -420,6 +410,74 @@ void Package::restoreFromCacheStream(QDataStream &in) } } +/*! + * \brief Puts the specified src/pkg info key value pairs; clears current dependencies, provides, ... + * \remarks This method should only be called by the associated repository because it must handle a possible name change. + */ +void Package::putInfo(const QList > &baseInfo, const QList > &pkgInfo) +{ + // clear current values + m_licenses.clear(); + m_dependencies.clear(); + m_makeDependencies.clear(); + m_checkDependencies.clear(); + m_optionalDependencies.clear(); + m_conflicts.clear(); + m_provides.clear(); + m_replaces.clear(); + // read specified key value pairs + PackageVersion version; + QStringList archs; + const auto infos = {baseInfo, pkgInfo}; + for(const auto &info : infos) { + for(const auto &pair : info) { + const auto &field = pair.first; + const auto &value = pair.second; + if(field == QLatin1String("pkgbase")) { + m_baseName = value; + } else if(field == QLatin1String("pkgname")) { + m_name = value; + } else if(field == QLatin1String("epoch")) { + version.epoch = value; + } else if(field == QLatin1String("pkgver")) { + version.version = value; + } else if(field == QLatin1String("pkgrel")) { + version.release = value; + } else if(field == QLatin1String("pkgdesc")) { + m_description = value; + } else if(field == QLatin1String("url")) { + m_upstreamUrl = value; + } else if(field == QLatin1String("arch")) { + archs << value; + } else if(field == QLatin1String("license")) { + m_licenses << value; + } else if(field == QLatin1String("depends")) { + m_dependencies << Dependency(value); + } else if(field == QLatin1String("makedepends")) { + m_makeDependencies << Dependency(value); + } else if(field == QLatin1String("checkdepends")) { + m_checkDependencies << Dependency(value); + } else if(field == QLatin1String("optdepends")) { + m_optionalDependencies << Dependency(value); + } else if(field == QLatin1String("conflicts")) { + m_conflicts << Dependency(value); + } else if(field == QLatin1String("provides")) { + m_provides << Dependency(value); + } else if(field == QLatin1String("replaces")) { + m_replaces << Dependency(value); + } else if(field == QLatin1String("source")) { + // currently not used + } + } + } + if(!version.version.isEmpty()) { + m_version = version.toString(); + } + if(!archs.isEmpty()) { + m_architectures.swap(archs); + } +} + /*! * \brief The PackageVersion class helps parsing package versions. */ @@ -472,6 +530,24 @@ PackageVersion::PackageVersion(const QString &versionStr) } } +/*! + * \brief Constructs an empty package version. + */ +PackageVersion::PackageVersion() +{} + +/*! + * \brief Returns the string representation of the package version: epoch:version-release + */ +QString RepoIndex::PackageVersion::toString() const +{ + if(epoch.isEmpty()) { + return version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release); + } else { + return epoch % QChar(':') % version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release); + } +} + /*! * \brief Compares two version parts. * \returns Returns 1 if part1 is newer then part2, -1 if part2 is newer then part1 and 0 if both parts are equal. @@ -556,5 +632,45 @@ PackageVersionComparsion PackageVersion::compare(const PackageVersion &other) co return PackageVersionComparsion::Equal; } +/*! + * \brief Constructs a dependency from the specified string. + * \remarks \a dependency might have version suffix. + */ +Dependency::Dependency(const QString &dependency) +{ + int suffixBeg; + if((suffixBeg = dependency.lastIndexOf(QLatin1String(">="))) > 0) { + mode = ALPM_DEP_MOD_GE; + } else if((suffixBeg = dependency.lastIndexOf(QLatin1String("<="))) > 0) { + mode = ALPM_DEP_MOD_LE; + } else if((suffixBeg = dependency.lastIndexOf(QChar('='))) > 0) { + mode = ALPM_DEP_MOD_EQ; + } else if((suffixBeg = dependency.lastIndexOf(QChar('<'))) > 0) { + mode = ALPM_DEP_MOD_LT; + } else if((suffixBeg = dependency.lastIndexOf(QChar('>'))) > 0) { + mode = ALPM_DEP_MOD_GT; + } else { + mode = ALPM_DEP_MOD_ANY; + } + switch(mode) { + case ALPM_DEP_MOD_ANY: + name = dependency; + break; + case ALPM_DEP_MOD_GE: + case ALPM_DEP_MOD_LE: + name = dependency.mid(0, suffixBeg); + version = dependency.mid(suffixBeg + 2); + break; + case ALPM_DEP_MOD_EQ: + case ALPM_DEP_MOD_LT: + case ALPM_DEP_MOD_GT: + name = dependency.mid(0, suffixBeg); + version = dependency.mid(suffixBeg + 1); + break; + default: + ; + } +} + } diff --git a/alpm/package.h b/alpm/package.h index d3d3d84..6550bc5 100644 --- a/alpm/package.h +++ b/alpm/package.h @@ -64,9 +64,11 @@ class PackageVersion { public: explicit PackageVersion(const QString &versionStr); + explicit PackageVersion(); static PackageVersionPartComparsion compareParts(const QString &part1, const QString &part2); PackageVersionComparsion compare(const PackageVersion &other) const; + QString toString() const; QString epoch; QString version; @@ -76,7 +78,8 @@ public: class Dependency { public: - explicit Dependency(const QString &name, const QString &version = QString(), _alpm_depmod_t mode = ALPM_DEP_MOD_ANY); + explicit Dependency(const QString &name, const QString &version, _alpm_depmod_t mode = ALPM_DEP_MOD_ANY); + explicit Dependency(const QString &dependency); QString name; QString version; _alpm_depmod_t mode; @@ -98,6 +101,7 @@ public: // general package meta data PackageOrigin origin() const; Repository *repository() const; + ChronoUtilities::DateTime timeStamp() const; bool hasGeneralInfo() const; const QString &name() const; const QString &version() const; @@ -127,6 +131,7 @@ public: const QString &buildArchitecture() const; uint32 packageSize() const; const QList &makeDependencies() const; + const QList &checkDependencies() const; // installation related meta data bool hasInstallRelatedMetaData() const; @@ -158,12 +163,15 @@ public: // JSON serialization QJsonObject basicInfo(bool includeRepoAndName = false) const; - QJsonObject fullInfo(bool includeRepoAndName = false) const; + QJsonObject detailedInfo() const; // caching void writeToCacheStream(QDataStream &out); void restoreFromCacheStream(QDataStream &in); + // parsing src/pkg info + void putInfo(const QList > &baseInfo, const QList > &pkgInfo); + protected: explicit Package(const QString &name, Repository *repository); virtual void writeSpecificCacheHeader(QDataStream &out); @@ -171,6 +179,7 @@ protected: PackageOrigin m_origin; Repository *m_repository; + ChronoUtilities::DateTime m_timeStamp; // general package meta data bool m_hasGeneralInfo; @@ -201,6 +210,7 @@ protected: QString m_buildArchitecture; uint32 m_packageSize; QList m_makeDependencies; + QList m_checkDependencies; // installation related meta data bool m_hasInstallRelatedMetaData; @@ -242,6 +252,14 @@ inline Repository *Package::repository() const return m_repository; } +/*! + * \brief Returns the package's timestamp. + */ +inline ChronoUtilities::DateTime Package::timeStamp() const +{ + return m_timeStamp; +} + /*! * \brief Returns whether general information is available for the package. */ @@ -446,13 +464,21 @@ inline uint32 Package::packageSize() const } /*! - * \brief Returns make dependencies. + * \brief Returns dependencies required to make the package. */ inline const QList &Package::makeDependencies() const { return m_makeDependencies; } +/*! + * \brief Returns dependencies required to run tests when making the package. + */ +inline const QList &Package::checkDependencies() const +{ + return m_checkDependencies; +} + /*! * \brief Returns whether install-related meta data is available. * diff --git a/alpm/repository.cpp b/alpm/repository.cpp index da1ce7d..ab2df41 100644 --- a/alpm/repository.cpp +++ b/alpm/repository.cpp @@ -73,8 +73,9 @@ SuggestionsReply *Repository::requestSuggestions(const QString &) const /*! * \brief Constructs a new repository (protected since this is a pure virtual class). */ -Repository::Repository(const QString &name, QObject *parent) : +Repository::Repository(const QString &name, uint32 index, QObject *parent) : QObject(parent), + m_index(index), m_name(name), m_usage(static_cast(0)), m_sigLevel(static_cast(ALPM_SIGSTATUS_INVALID)) @@ -251,6 +252,26 @@ QFuture Repository::computeRequiredBy(Manager &manager, bool forceUpdate) return QtConcurrent::map(m_packages, ComputeRequired(manager, forceUpdate)); } +/*! + * \brief Returns suggestions for the specified \a term. + */ +QJsonObject Repository::suggestions(const QString &term) const +{ + QJsonArray suggestions; + size_t remainingSuggestions = 20; + for(auto i = packages().lower_bound(term), end = packages().cend(); i != end && remainingSuggestions; ++i, --remainingSuggestions) { + if(i->first.startsWith(term, Qt::CaseInsensitive)) { + suggestions << i->first; + } else { + break; + } + } + QJsonObject res; + res.insert(QStringLiteral("repo"), name()); + res.insert(QStringLiteral("res"), suggestions); + return res; +} + QJsonArray Repository::upgradeSourcesJsonArray() const { QJsonArray sources; @@ -303,6 +324,18 @@ QJsonArray Repository::packageNamesJsonArray() const return names; } +/*! + * \brief Returns an object with the package names of the repository as keys (and empty objects as value). + */ +QJsonObject Repository::packagesObjectSkeleton() const +{ + QJsonObject skel; + for(const auto &entry : m_packages) { + skel.insert(entry.first, QJsonValue(QJsonValue::Object)); + } + return skel; +} + /*! * \cond */ @@ -328,18 +361,26 @@ inline void put(QJsonObject &obj, const QString &key, const QStringList &values) /*! * \brief Returns basic information about the repository. */ -QJsonObject Repository::basicInfo() const +QJsonObject Repository::basicInfo(bool includeName) const { QJsonObject info; - put(info, QStringLiteral("name"), name()); + if(includeName) { + put(info, QStringLiteral("name"), name()); + } + if(index() != invalidIndex) { + info.insert(QStringLiteral("index"), static_cast(index())); + } put(info, QStringLiteral("desc"), description()); put(info, QStringLiteral("servers"), serverUrls()); put(info, QStringLiteral("usage"), Utilities::usageStrings(usage())); put(info, QStringLiteral("sigLevel"), Utilities::sigLevelStrings(sigLevel())); put(info, QStringLiteral("upgradeSources"), upgradeSourcesJsonArray()); - put(info, QStringLiteral("packages"), packageNamesJsonArray()); - put(info, QStringLiteral("requestRequired"), requestsRequired(PackageDetail::Basics) != PackageDetailAvailability::Immediately); + put(info, QStringLiteral("packages"), packagesObjectSkeleton()); + if(requestsRequired(PackageDetail::Basics) == PackageDetailAvailability::Immediately) { + info.insert(QStringLiteral("packageCount"), static_cast(m_packages.size())); + } put(info, QStringLiteral("srcOnly"), isSourceOnly()); + put(info, QStringLiteral("pkgOnly"), isPackageOnly()); return info; } @@ -471,4 +512,100 @@ void Repository::restoreSpecificCacheHeader(QDataStream &in) Q_UNUSED(in) } +/*! + * \brief Adds a package parsed from the specified \a srcInfo. + */ +void Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo) +{ + enum { + FieldName, + EquationSign, + Pad, + FieldValue + } state = FieldName; + QString currentFieldName; + QString currentFieldValue; + QString packageBase; + QList > baseInfo; + QList > packageInfo; + Package *currentPackage = nullptr; + for(char c : srcInfo) { + switch(state) { + case FieldName: + switch(c) { + case ' ': + if(!currentFieldName.isEmpty()) { + state = EquationSign; + } + break; + case '\n': case '\r': + if(!currentFieldName.isEmpty()) { + // TODO: handle error - field name contains newline character + } + break; + default: + currentFieldName.append(c); + } + break; + case EquationSign: + switch(c) { + case '=': + state = Pad; + break; + default: + ;// TODO: handle error - no equation sign after pad + } + break; + case Pad: + switch(c) { + case ' ': + state = FieldValue; + break; + default: + ;// TODO: handle error - no pad after equation sign + } + break; + case FieldValue: + switch(c) { + case '\n': case '\r': + state = FieldName; + if(currentFieldName == QLatin1String("pkgbase")) { + // pkgbase + packageBase = currentFieldValue; + } else if(currentFieldName == QLatin1String("pkgname")) { + // next package + if(packageBase.isEmpty()) { + // TODO: handle error - pkgbase must be present + } else { + if(currentPackage) { + currentPackage->putInfo(baseInfo, packageInfo); + } + auto &pkg = m_packages[currentFieldValue]; + if(!pkg) { + pkg = emptyPackage(); + } + currentPackage = pkg.get(); + packageInfo.clear(); + } + } + if(currentPackage) { + packageInfo << QPair(currentFieldName, currentFieldValue); + } else { + baseInfo << QPair(currentFieldName, currentFieldValue); + } + currentFieldName.clear(); + currentFieldValue.clear(); + break; + default: + currentFieldValue.append(c); + } + break; + } + } + if(currentPackage) { + currentPackage->putInfo(baseInfo, packageInfo); + } +} + + } // namespace PackageManagement diff --git a/alpm/repository.h b/alpm/repository.h index 9a9e7ab..cd66bbf 100644 --- a/alpm/repository.h +++ b/alpm/repository.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -71,22 +72,20 @@ class SuggestionsReply : public Reply { Q_OBJECT public: - SuggestionsReply(QNetworkReply *networkReply); - const QJsonArray &suggestions() const; + SuggestionsReply(QNetworkReply *networkReply, const QString &term, Repository *repo); + QJsonObject suggestions() const; protected: - QJsonArray m_suggestions; + QString m_term; + Repository *m_repo; }; -inline SuggestionsReply::SuggestionsReply(QNetworkReply *networkReply) : - Reply(networkReply) +inline SuggestionsReply::SuggestionsReply(QNetworkReply *networkReply, const QString &term, Repository *repo) : + Reply(networkReply), + m_term(term), + m_repo(repo) {} -inline const QJsonArray &SuggestionsReply::suggestions() const -{ - return m_suggestions; -} - /*! * \brief The RepositoryType enum specifies the type of a repository object. */ @@ -130,6 +129,7 @@ public: virtual RepositoryType type() const = 0; // general meta data + uint32 index() const; const QString &name() const; const QString &description() const; const std::map > &packages() const; @@ -137,6 +137,7 @@ public: const QStringList packageNames() const; alpm_db_usage_t usage() const; bool isSourceOnly() const; + bool isPackageOnly() const; std::map > &groups(); const std::map > &groups() const; const QStringList &serverUrls() const; @@ -156,6 +157,7 @@ public: QList packagesProviding(const Dependency &dependency) const; QList packageByFilter(std::function pred); QFuture computeRequiredBy(Manager &manager, bool forceUpdate = false); + QJsonObject suggestions(const QString &term) const; // upgrade lookup const QList &upgradeSources() const; @@ -172,7 +174,8 @@ public: // JSON serialization QJsonArray packageNamesJsonArray() const; - QJsonObject basicInfo() const; + QJsonObject packagesObjectSkeleton() const; + QJsonObject basicInfo(bool includeName = false) const; QJsonObject groupInfo() const; // caching @@ -183,9 +186,16 @@ public: virtual std::unique_ptr emptyPackage(); virtual void restoreSpecificCacheHeader(QDataStream &in); -protected: - explicit Repository(const QString &name, QObject *parent = nullptr); + // parsing src/pkg info + void addPackagesFromSrcInfo(const QByteArray &srcInfo); + static const uint32 invalidIndex = static_cast(-1); + +protected: + explicit Repository(const QString &name, uint32 index = invalidIndex, QObject *parent = nullptr); + +protected: + uint32 m_index; QString m_name; QString m_description; std::map > m_packages; @@ -198,6 +208,24 @@ protected: QString m_pkgDir; }; +/*! + * \brief Returns the suggestions. + */ +inline QJsonObject SuggestionsReply::suggestions() const +{ + return m_repo->suggestions(m_term); +} + +/*! + * \brief Returns the index of the repository. + * + * The index is used to sort the repositories by their occurance the configuration files. + */ +inline uint32 Repository::index() const +{ + return m_index; +} + /*! * \brief Returns the name. */ @@ -222,6 +250,14 @@ inline bool Repository::isSourceOnly() const return requestsRequired(PackageDetail::PackageInfo) == PackageDetailAvailability::Never; } +/*! + * \brief Returns whether the repository only has built packages but no sources. + */ +inline bool Repository::isPackageOnly() const +{ + return requestsRequired(PackageDetail::SourceInfo) == PackageDetailAvailability::Never; +} + /*! * \brief Returns the packages. */ diff --git a/alpm/resolvebuildorder.cpp b/alpm/resolvebuildorder.cpp index f9b2c2d..ce4ba41 100644 --- a/alpm/resolvebuildorder.cpp +++ b/alpm/resolvebuildorder.cpp @@ -195,7 +195,7 @@ void BuildOrderResolver::printResults(const QStringList &results) void BuildOrderResolver::addDeps(QList &tasks, TaskInfo *task) const { - if(const auto pkg = m_manager.packageProviding(Dependency(task->name()))) { + if(const auto pkg = m_manager.packageProviding(Dependency(task->name(), QString()))) { task->setName(pkg->name()); // update the name to ensure we have the acutal package name and not just a "provides" name addDeps(tasks, task, pkg->dependencies()); } else { diff --git a/alpm/suggestionslookup.cpp b/alpm/suggestionslookup.cpp new file mode 100644 index 0000000..90123a1 --- /dev/null +++ b/alpm/suggestionslookup.cpp @@ -0,0 +1,56 @@ +#include "./suggestionslookup.h" +#include "./manager.h" +#include "./repository.h" + +#include +#include + +#include + +namespace RepoIndex { + +SuggestionsLookup::SuggestionsLookup(Manager &manager, const QJsonObject &request) : + m_remainingReplies(0) +{ + m_id = request.value(QStringLiteral("id")); + const auto searchTerm = request.value(QStringLiteral("term")).toString(); + if(searchTerm.isEmpty()) { + m_errors << QStringLiteral("No search term specified."); + } + const auto repos = request.value(QStringLiteral("repos")).toArray(); + if(repos.isEmpty()) { + m_errors << QStringLiteral("No repositories specified."); + } + if(m_errors.isEmpty()) { + for(const auto &repoName : repos) { + if(const Repository *repo = manager.repositoryByName(repoName.toString())) { + if(const auto *reply = repo->requestSuggestions(searchTerm)) { + connect(reply, &SuggestionsReply::resultsAvailable, this, &SuggestionsLookup::addResults); + ++m_remainingReplies; + } else { + m_results << repo->suggestions(searchTerm); + } + + } else { + m_errors << QStringLiteral("The specified repository \"%1\" does not exist.").arg(repoName.toString()); + } + } + return; + } + deleteLater(); +} + +void SuggestionsLookup::addResults() +{ + assert(m_remainingReplies); + auto *reply = static_cast(sender()); + m_results << reply->suggestions(); + reply->deleteLater(); + if(!--m_remainingReplies) { + emit resultsAvailable(QStringLiteral("suggestions"), m_id, m_results); + deleteLater(); + } +} + +} // namespace RepoIndex + diff --git a/alpm/suggestionslookup.h b/alpm/suggestionslookup.h new file mode 100644 index 0000000..203cc89 --- /dev/null +++ b/alpm/suggestionslookup.h @@ -0,0 +1,51 @@ +#ifndef REPOINDEX_SUGGESTIONSLOOKUP_H +#define REPOINDEX_SUGGESTIONSLOOKUP_H + +#include +#include +#include + +namespace RepoIndex { + +class Manager; + +class SuggestionsLookup : public QObject +{ + Q_OBJECT +public: + SuggestionsLookup(Manager &manager, const QJsonObject &request); + const QJsonArray &errors() const; + const QJsonArray &results() const; + bool finished() const; + +signals: + void resultsAvailable(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value); + +private slots: + void addResults(); + +private: + unsigned int m_remainingReplies; + QJsonValue m_id; + QJsonArray m_errors; + QJsonArray m_results; +}; + +inline const QJsonArray &SuggestionsLookup::errors() const +{ + return m_errors; +} + +inline const QJsonArray &SuggestionsLookup::results() const +{ + return m_results; +} + +inline bool SuggestionsLookup::finished() const +{ + return !m_remainingReplies && m_errors.isEmpty(); +} + +} // namespace RepoIndex + +#endif // REPOINDEX_SUGGESTIONSLOOKUP_H diff --git a/alpm/upgradelookup.cpp b/alpm/upgradelookup.cpp index a8926ec..a0266f8 100644 --- a/alpm/upgradelookup.cpp +++ b/alpm/upgradelookup.cpp @@ -26,7 +26,11 @@ using namespace Utilities; QJsonObject UpgradeResult::json() const { QJsonObject obj; - obj.insert(QStringLiteral("pkg"), package->basicInfo(true)); + obj.insert(QStringLiteral("name"), package->name()); + if(package->repository()) { + obj.insert(QStringLiteral("repo"), package->repository()->name()); + } + obj.insert(QStringLiteral("pkg"), package->basicInfo()); obj.insert(QStringLiteral("curVer"), currentVersion); return obj; } @@ -194,10 +198,6 @@ UpgradeLookupJson::UpgradeLookupJson(const Manager &manager, const QJsonObject & } else { m_errorsArray << QStringLiteral("Repository \"%1\" can not be found.").arg(toCheckName); } - // there are errors - QJsonObject results; - results.insert(QStringLiteral("errors"), m_errorsArray); - emit resultsAvailable(request.value(QStringLiteral("what")), request.value(QStringLiteral("id")), results); deleteLater(); } @@ -231,7 +231,13 @@ void UpgradeLookupJson::processFinished() if(--m_remainingProcesses == 0) { // finally make info for orphanded packages for(const auto *pkg : m_orphanedPackages) { - m_orphanedPackagesArray << pkg->basicInfo(true); + QJsonObject obj; + obj.insert(QStringLiteral("name"), pkg->name()); + if(pkg->repository()) { + obj.insert(QStringLiteral("repo"), pkg->repository()->name()); + } + obj.insert(QStringLiteral("pkg"), pkg->basicInfo()); + m_orphanedPackagesArray << obj; } // add results to results QJsonObject QJsonObject results; @@ -367,7 +373,7 @@ void UpgradeLookupCli::printResults() Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray); } } - emit finished(); + QCoreApplication::exit(); } } // namespace PackageManagement diff --git a/alpm/upgradelookup.h b/alpm/upgradelookup.h index ea91b02..55390b3 100644 --- a/alpm/upgradelookup.h +++ b/alpm/upgradelookup.h @@ -135,6 +135,7 @@ class UpgradeLookupJson : public UpgradeLookup Q_OBJECT public: explicit UpgradeLookupJson(const Manager &manager, const QJsonObject &request, QObject *parent = nullptr); + const QJsonArray &errors() const; signals: void resultsAvailable(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value); @@ -152,6 +153,11 @@ private: QJsonArray m_orphanedPackagesArray; }; +inline const QJsonArray &UpgradeLookupJson::errors() const +{ + return m_errorsArray; +} + class UpgradeLookupCli : public UpgradeLookup { Q_OBJECT @@ -160,9 +166,6 @@ public: explicit UpgradeLookupCli(const Manager &manager, const std::string &repo, QObject *parent = nullptr); const Repository *toCheck() const; -signals: - void finished(); - private slots: void processFinished(); diff --git a/alpm/utilities.cpp b/alpm/utilities.cpp index dcad57b..f4ec049 100644 --- a/alpm/utilities.cpp +++ b/alpm/utilities.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include diff --git a/general.pri b/general.pri index 9b062bc..60bf445 100644 --- a/general.pri +++ b/general.pri @@ -1,5 +1,3 @@ -# template -TEMPLATE = lib #dirs UI_DIR = ./gui MOC_DIR = ./moc @@ -75,7 +73,11 @@ guiqtwidgets { DEFINES += GUI_QTWIDGETS DEFINES += MODEL_UNDO_SUPPORT } -# configuration for cross compliation with mingw-w64 +# Windows stuff: configuration for cross compliation with mingw-w64 +win32 { + QMAKE_TARGET_PRODUCT = "$${appname}" + QMAKE_TARGET_COPYRIGHT = "by $${appauthor}" +} mingw-w64-manualstrip-dll { QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET); \ $${CROSS_COMPILE}strip --strip-unneeded ./release/lib$(TARGET).a @@ -84,5 +86,7 @@ mingw-w64-manualstrip-exe { QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET) } mingw-w64-noversion { - VERSION = "" + TARGET_EXT = ".dll" + TARGET_VERSION_EXT = "" + CONFIG += skip_target_version_ext } diff --git a/main.cpp b/main.cpp index e2d4129..78a8fa5 100644 --- a/main.cpp +++ b/main.cpp @@ -52,10 +52,11 @@ int main(int argc, char *argv[]) QCoreApplication application(argc, argv); // setup manager Manager manager(config); - manager.applyPacmanConfig(); - manager.applyRepoIndexConfig(); cerr << shchar << "Loading databases ..." << endl; + manager.registerDataBasesFromPacmanConfig(); + manager.registerDatabasesFromRepoIndexConfig(); manager.initAlpmDataBases(configArgs.serverArg.isPresent()); + cerr << shchar << "Restoring cache ..." << endl; manager.restoreCache(); if(configArgs.serverArg.isPresent()) { // setup the server @@ -67,13 +68,13 @@ int main(int argc, char *argv[]) BuildOrderResolver resolver(manager); BuildOrderResolver::printResults(resolver.resolve(configArgs.buildOrderArg.values())); } else if(configArgs.mingwBundleArg.isPresent()) { - MingwBundle bundle(manager, configArgs.mingwBundleArg.values(), configArgs.iconThemesArg.values()); + MingwBundle bundle(manager, configArgs.mingwBundleArg.values(), configArgs.iconThemesArg.values(), configArgs.extraPackagesArg.values()); bundle.createBundle(configArgs.targetDirArg.isPresent() ? configArgs.targetDirArg.values().front() : string("."), configArgs.targetNameArg.values().front(), - configArgs.targetFormatArg.isPresent() ? configArgs.targetFormatArg.values().front() : string("zip")); + configArgs.targetFormatArg.isPresent() ? configArgs.targetFormatArg.values().front() : string("zip"), + configArgs.defaultIconThemeArg.isPresent() ? configArgs.defaultIconThemeArg.values().front() : string()); } else if(configArgs.upgradeLookupArg.isPresent()) { UpgradeLookupCli upgradeLookup(manager, configArgs.upgradeLookupArg.values().front()); - QObject::connect(&upgradeLookup, &UpgradeLookupCli::finished, &application, &QCoreApplication::quit); return application.exec(); } } else if(!configArgs.helpArg.isPresent()) { diff --git a/network/connection.cpp b/network/connection.cpp index fa5caef..549c745 100644 --- a/network/connection.cpp +++ b/network/connection.cpp @@ -2,6 +2,7 @@ #include "../alpm/manager.h" #include "../alpm/upgradelookup.h" +#include "../alpm/suggestionslookup.h" #include "../alpm/config.h" #include @@ -16,7 +17,7 @@ using namespace std; namespace RepoIndex { -Connection::Connection(const Manager &alpmManager, QWebSocket *socket, QObject *parent) : +Connection::Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent) : QObject(parent), m_socket(socket), m_manager(alpmManager), @@ -75,16 +76,37 @@ void Connection::handleQuery(const QJsonObject &obj) const auto id = obj.value(QStringLiteral("id")); if(what == QLatin1String("basicrepoinfo")) { m_repoInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_repoInfoUpdatesRequested); - sendResults(what, id, m_manager.basicRepoInfo()); + sendResult(what, id, m_manager.basicRepoInfo()); } else if(what == QLatin1String("basicpkginfo")) { - sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), false)); + sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), Manager::Basics)); + } else if(what == QLatin1String("pkgdetails")) { + sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), Manager::Details)); } else if(what == QLatin1String("fullpkginfo")) { - sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), true)); + // TODO: figure out why QFlags doesn't work, in the mean time, use static_cast workaround + sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), static_cast(static_cast(Manager::Basics) | static_cast(Manager::Details)))); } else if(what == QLatin1String("groupinfo")) { m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested); sendResults(what, id, m_manager.groupInfo()); + } else if(what == QLatin1String("suggestions")) { + auto *suggestionsLookup = new SuggestionsLookup(m_manager, obj); + if(suggestionsLookup->finished()) { + sendResult(what, id, suggestionsLookup->results()); + } else if(!suggestionsLookup->errors().isEmpty()) { + QJsonObject results; + results.insert(QStringLiteral("errors"), suggestionsLookup->errors()); + sendResult(what, id, results); + } else { + connect(suggestionsLookup, &SuggestionsLookup::resultsAvailable, this, &Connection::sendResult); + } } else if(what == QLatin1String("upgradelookup")) { - connect(new UpgradeLookupJson(m_manager, obj), &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult); + auto *upgradeLookup = new UpgradeLookupJson(m_manager, obj); + if(upgradeLookup->errors().isEmpty()) { + connect(upgradeLookup, &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult); + } else { + QJsonObject results; + results.insert(QStringLiteral("errors"), upgradeLookup->errors()); + sendResult(what, id, results); + } } else if(what == QLatin1String("ping")) { sendResult(what, id, QStringLiteral("pong")); } else { @@ -103,6 +125,17 @@ void Connection::handleCmd(const QJsonObject &obj) } else { sendError(QStringLiteral("rejected"), id); } + } else if(what == QLatin1String("reinitalpm")) { + if(m_socket->peerAddress().isLoopback()) { + cerr << shchar << "Info: Reinit of ALPM databases triggered via web interface." << endl; + m_manager.cleanupAlpm(); + m_manager.initAlpmHandle(); + m_manager.registerDataBasesFromPacmanConfig(); + m_manager.registerDatabasesFromRepoIndexConfig(); + m_manager.initAlpmDataBases(true); + } else { + sendError(QStringLiteral("rejected"), id); + } } else { sendError(QStringLiteral("unknown command"), id); } diff --git a/network/connection.h b/network/connection.h index 52f182d..f5bb3af 100644 --- a/network/connection.h +++ b/network/connection.h @@ -17,7 +17,7 @@ class Connection : public QObject Q_OBJECT public: - Connection(const RepoIndex::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr); + Connection(RepoIndex::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr); private slots: void processTextMessage(const QString &message); @@ -33,7 +33,7 @@ private: void handleCmd(const QJsonObject &obj); QWebSocket *m_socket; - const RepoIndex::Manager &m_manager; + RepoIndex::Manager &m_manager; bool m_repoInfoUpdatesRequested; bool m_groupInfoUpdatesRequested; diff --git a/network/server.cpp b/network/server.cpp index c980a8b..960d98b 100644 --- a/network/server.cpp +++ b/network/server.cpp @@ -14,7 +14,7 @@ using namespace std; namespace RepoIndex { -Server::Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent) : +Server::Server(RepoIndex::Manager &alpmManager, const RepoIndex::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 3ca5fd3..165ac96 100644 --- a/network/server.h +++ b/network/server.h @@ -22,7 +22,7 @@ class Server : public QObject Q_OBJECT public: - Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent = nullptr); + Server(RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent = nullptr); ~Server(); signals: @@ -34,7 +34,7 @@ private slots: private: QWebSocketServer *m_server; - const RepoIndex::Manager &m_alpmManager; + RepoIndex::Manager &m_alpmManager; }; } diff --git a/network/userrepository.cpp b/network/userrepository.cpp index 7e70e58..340c8e1 100644 --- a/network/userrepository.cpp +++ b/network/userrepository.cpp @@ -72,8 +72,8 @@ void AurFullPackageReply::processData() // TODO } -AurSuggestionsReply::AurSuggestionsReply(QNetworkReply *networkReply) : - SuggestionsReply(networkReply) +AurSuggestionsReply::AurSuggestionsReply(QNetworkReply *networkReply, const QString &term, UserRepository *repo) : + SuggestionsReply(networkReply, term, repo) {} void AurSuggestionsReply::processData() @@ -86,7 +86,16 @@ void AurSuggestionsReply::processData() //const QJsonDocument doc = QJsonDocument::fromJson(data, &error); const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error); if(error.error == QJsonParseError::NoError) { - m_suggestions = doc.array(); + auto &packages = m_repo->packages(); + for(const auto &suggestion : doc.array()) { + const auto suggestionString = suggestion.toString(); + if(!suggestionString.isEmpty()) { + auto &package = packages[suggestionString]; + if(!package) { + package = make_unique(suggestionString, static_cast(m_repo)); + } + } + } } else { m_error = QStringLiteral("Error: Unable to parse JSON received from AUR: ") % error.errorString() % QStringLiteral(" at character ") % QString::number(error.offset); } @@ -97,7 +106,7 @@ void AurSuggestionsReply::processData() } UserRepository::UserRepository(QNetworkAccessManager &networkAccessManager, QObject *parent) : - Repository(QStringLiteral("AUR"), parent), + Repository(QStringLiteral("AUR"), invalidIndex, parent), m_networkAccessManager(networkAccessManager) { m_description = QStringLiteral("Arch User Repository"); @@ -122,14 +131,20 @@ PackageDetailAvailability UserRepository::requestsRequired(PackageDetail package } } -AurSuggestionsReply *UserRepository::requestSuggestions(const QString &phrase) const +AurSuggestionsReply *UserRepository::requestSuggestions(const QString &term) const { - auto url = m_aurRpcUrl; - QUrlQuery query; - query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeSuggest); - query.addQueryItem(rpcArgKey, phrase); - url.setQuery(query); - return new AurSuggestionsReply(m_networkAccessManager.get(QNetworkRequest(url))); + size_t remainingSuggestions = 20; + for(auto i = packages().lower_bound(term), end = packages().cend(); i != end && remainingSuggestions && i->first.startsWith(term, Qt::CaseInsensitive); ++i, --remainingSuggestions); + if(remainingSuggestions) { + auto url = m_aurRpcUrl; + QUrlQuery query; + query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeSuggest); + query.addQueryItem(rpcArgKey, term); + url.setQuery(query); + return new AurSuggestionsReply(m_networkAccessManager.get(QNetworkRequest(url)), term, const_cast(this)); + } else { + return nullptr; + } } AurPackageReply *UserRepository::requestPackageInfo(const QStringList &packageNames, bool forceUpdate) const diff --git a/network/userrepository.h b/network/userrepository.h index c5acaa3..7fea8d0 100644 --- a/network/userrepository.h +++ b/network/userrepository.h @@ -47,7 +47,7 @@ class AurSuggestionsReply : public SuggestionsReply { Q_OBJECT public: - AurSuggestionsReply(QNetworkReply *networkReply); + AurSuggestionsReply(QNetworkReply *networkReply, const QString &term, UserRepository *repo); private slots: void processData(); @@ -65,7 +65,7 @@ public: static void setAurRpcUrl(const QUrl &aurRpcUrl); PackageDetailAvailability requestsRequired(PackageDetail packageDetail) const; - AurSuggestionsReply *requestSuggestions(const QString &phrase) const; + AurSuggestionsReply *requestSuggestions(const QString &term) const; AurPackageReply *requestPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; AurFullPackageReply *requestFullPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; diff --git a/repoindex.pro b/repoindex.pro index 8557136..4c34eb9 100644 --- a/repoindex.pro +++ b/repoindex.pro @@ -2,6 +2,7 @@ projectname = repoindex appname = "Repository Index" appauthor = Martchus appurl = "https://github.com/$${appauthor}/$${projectname}" +QMAKE_TARGET_DESCRIPTION = "Provides a web interface to browse Arch Linux package repositories." VERSION = 1.0.0 # include ../../common.pri when building as part of a subdirs project; otherwise include general.pri @@ -32,7 +33,8 @@ SOURCES += main.cpp \ alpm/aurpackage.cpp \ alpm/alpmdatabase.cpp \ alpm/repository.cpp \ - alpm/upgradelookup.cpp + alpm/upgradelookup.cpp \ + alpm/suggestionslookup.cpp HEADERS += \ alpm/manager.h \ @@ -50,7 +52,8 @@ HEADERS += \ alpm/aurpackage.h \ alpm/alpmdatabase.h \ alpm/repository.h \ - alpm/upgradelookup.h + alpm/upgradelookup.h \ + alpm/suggestionslookup.h DISTFILES += \ README.md \ diff --git a/web/index.html b/web/index.html index 35cc9c6..3a337fd 100644 --- a/web/index.html +++ b/web/index.html @@ -69,7 +69,15 @@ @@ -96,8 +104,8 @@ diff --git a/web/js/client.js b/web/js/client.js index 147395f..1f6c3a5 100644 --- a/web/js/client.js +++ b/web/js/client.js @@ -13,16 +13,15 @@ BasicPackageInfo: "basicpkginfo", FullPackageInfo: "fullpkginfo", GroupInfo: "groupinfo", - UpgradeLookup: "upgradelookup" + UpgradeLookup: "upgradelookup", + Suggestions: "suggestions" }; repoindex.isLoopback = function(domain) { return domain === "localhost" || domain === "127.0.0.1" || domain === "::1"; }; - - - // responsible for the connection to the server + // responsible for the connection to the server; holds all repo/package information returned by the server var Client = function(url) { // basic initialization this.url = url; @@ -191,10 +190,10 @@ this.useBasicRepoInfo(values); break; case repoindex.RequestType.BasicPackageInfo: - // the info is used via the registred callbacks only + this.usePackageInfo(values); break; case repoindex.RequestType.FullPackageInfo: - // the info is used via the registred callbacks only + this.usePackageInfo(values); break; case repoindex.RequestType.GroupInfo: this.useGroupInfo(values); @@ -202,6 +201,9 @@ case repoindex.RequestType.UpgradeLookup: // the info is used via the registred callbacks only break; + case repoindex.RequestType.Suggestions: + this.useSuggestions(values); + break; default: pageManager.addError("Server replied unknown results: " + what); return; // don't invoke callbacks when results are of unknown type @@ -233,14 +235,37 @@ this.scheduleRequest(repoindex.RequestType.GroupInfo, {upgrades: "true"}, callback); }; - this.stopServer = function() { + this.requestSuggestions = function(repoNames, searchTerm, callback) { + this.scheduleRequest(repoindex.RequestType.Suggestions, {repos: repoNames, term: searchTerm}); + }; + + this.requestAurSuggestions = function(term) { + var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); + xmlhttp.onreadystatechange = function() { + if(xmlhttp.readyState === 4 && xmlhttp.status === 200) { + repoindex.client.useAurSuggestions(JSON.parse(xmlhttp.responseText)); + } + }; + xmlhttp.open("GET", "http://aur.archlinux.org/rpc.php?type=suggest&arg=" + encodeURIComponent(term), true); + xmlhttp.send(); + }; + + this.sendCmd = function(cmd) { if(this.isOpen()) { - this.socket.send(JSON.stringify({class: "cmd", what: "stop"})); + this.socket.send(JSON.stringify({class: "cmd", what: cmd})); } else { repoindex.pageManager.addError("Not connected to a server."); } }; + this.stopServer = function() { + this.sendCmd("stop"); + }; + + this.reinitAlpm = function() { + this.sendCmd("reinitalpm"); + }; + this.checkForUpgrades = function(dbName, syncdbNames, callback) { var params = { db: dbName @@ -251,29 +276,102 @@ this.scheduleRequest(repoindex.RequestType.UpgradeLookup, params, callback); }; + // define helper functions to access repo/package information + this.getPackageInfo = function(repoName, packageName) { + var repoInfo = this.repos[repoName]; + if(!repoInfo) { + // the repo doesn't exists; might happen when receiving AUR suggestions directly + // -> just create a new, empty repo + this.repo[repoName] = (repoInfo = {name: repoName, packages: {}}); + } + var packageInfo = repoInfo.packages[packageName]; + if(!packageInfo) { + repoInfo.packages[packageName] = (packageInfo = {repo: repoName}); + } + return packageInfo; + }; + // define functions to use differend kinds of results - this.useBasicRepoInfo = function(values) { - if(!values) { + this.useBasicRepoInfo = function(value) { + if(!value) { repoindex.pageManager.addError("Server replied insufficiant basic repo info."); return false; } // upgrade repos and package list - this.repos = {}; + this.repos = value; var repoMgr = repoindex.pageManager.repoManager; var pkgMgr = repoindex.pageManager.packageManager; repoMgr.removeEntries(); pkgMgr.removeEntries(); - for(var i1 = 0; i1 < values.length; ++i1) { - this.repos[values[i1].name] = values[i1]; - var repoEntry = repoMgr.addEntry(values[i1]); - var packages = repoEntry.info.packages; - for(var i2 = 0; i2 < packages.length; ++i2) { - pkgMgr.addEntry(repoEntry, packages[i2]); + var reposInOrder = []; + for(var repoName in value) { + if(value.hasOwnProperty(repoName)) { + reposInOrder.push({name: repoName, info: value[repoName]}); + //var repoInfo = value[repoName]; } } + reposInOrder.sort(function(lhs, rhs) { + if(lhs === rhs) { + return 1; + } else if(lhs.info.index !== undefined) { + if(rhs.info.index !== undefined) { + return lhs.info.index > rhs.info.index; + } else { + return -1; + } + } else if(rhs.info.index !== undefined) { + return 1; + } else { + return lhs.name > rhs.name; + } + }); + for(var i = 0; i < reposInOrder.length; ++i) { + var repoName = reposInOrder[i].name; + var repoInfo = reposInOrder[i].info; + var repoEntry = repoMgr.addEntry(repoName, repoInfo); + var packages = repoInfo.packages; + for(var packageName in packages) { + if(value.hasOwnProperty(repoName)) { + var packageInfo = packages[packageName]; + pkgMgr.addEntry(repoEntry, packageName, packageInfo); + } + } + } + this.hasBasicRepoInfo = true; pkgMgr.invalidate(); repoMgr.invalidate(); + repoMgr.applyRepoStatusChange(); + }; + + this.usePackageInfo = function(values) { + if(!Array.isArray(values)) { + repoindex.pageManager.addError("Server replied insufficiant package info."); + return false; + } + for(var i = 0; i < values.length; ++i) { + var value = values[i]; + var repo = repoindex.client.repos[value.repo]; + if(repo) { + var package = repo.packages[value.name]; + if(package) { + if(value.basics) { + package.basics = value.basics; + } + if(value.details) { + package.details = value.details; + } + } else { + repoindex.pageManager.addError("Package info replied by server refers to unknown package."); + } + } else { + repoindex.pageManager.addError("Package info replied by server refers to unknown repository."); + } + } + if(value.error) { + repoindex.pageManager.addError("Server replied error in package info: " + value.error); + } + // updating table rows or any other GUI elements is done via callbacks }; this.useGroupInfo = function(values) { @@ -293,13 +391,80 @@ groupMgr.useRequestedData(); }; + this.useSuggestions = function(values) { + if(Array.isArray(values)) { + for(var i1 = 0; i1 < values.length; ++i1) { + var value = values[i1]; + var repoName = value.repo; + var suggestions = value.res; + if(Array.isArray(suggestions)) { + if(suggestions.length > 0) { + var repoEntry = repoindex.pageManager.repoManager.entryByName(repoName); + if(repoEntry) { + var pkgMgr = repoindex.pageManager.packageManager; + for(var i2 = 0; i2 < suggestions.length; ++i2) { + var packageName = suggestions[i2]; + if(!pkgMgr.entryByName(packageName)) { + var packageInfo = this.getPackageInfo(repoName, packageName); + pkgMgr.addEntry(repoEntry, packageName, packageInfo); + } + } + pkgMgr.invalidate(); + } else { + repoindex.pageManager.addError("There is no repo entry for the suggestions replied by the server."); + } + var entries = pkgMgr.entries; + entries.sort(function(lhs, rhs) { + return lhs.name > rhs.name; + }); + for(var i = 0; i < entries.length; ++i) { + entries[i].index = i; + } + } + } else { + // TODO: handle error + } + } + } else if(Array.isArray(values.errors)) { + for(var i = 0; i < values.errors.length; ++i) { + repoindex.pageManager.addError("Error replied by server in response of suggestion request: " + value.errors[i]); + } + } else { + repoindex.pageManager.addError("Server replied insufficiant suggestion reply."); + } + }; + + this.useAurSuggestions = function(suggestions) { + if(Array.isArray(suggestions)) { + if(suggestions.length > 0) { + var aurEntry = repoindex.pageManager.repoManager.entryByName("AUR"); + if(aurEntry) { + var pkgMgr = repoindex.pageManager.packageManager; + for(var i = 0; i < suggestions.length; ++i) { + var packageName = suggestions[i]; + if(!pkgMgr.entryByName(packageName)) { + var packageInfo = this.getPackageInfo("AUR", packageName); + pkgMgr.addEntry(aurEntry, packageName, packageInfo); + } + } + pkgMgr.invalidate(); + } else { + // there is no AUR entry but we requested AUR suggestions + // -> shouldn't happen + } + } + } else { + // TODO: handle error + } + }; + }; // create a global client used within the entire document repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + document.domain + ":1234"); if(!repoindex.isLoopback(document.domain)) { // hide stop button if server is not on loopback interface - document.getElementById("nav_stop_server").style.display = "none"; + document.getElementById("nav_server").style.display = "none"; } return repoindex; diff --git a/web/js/entrymanagement.js b/web/js/entrymanagement.js index e4775eb..aceb6fb 100644 --- a/web/js/entrymanagement.js +++ b/web/js/entrymanagement.js @@ -22,7 +22,7 @@ this.appendChild(cellElement); }; this.initTableRow = function() {}; - this.upgradeTableRow = function() { + this.updateTableRow = function() { this.rowElement.wipeChildren(); this.initTableRow(); }; @@ -43,7 +43,7 @@ this.rowElement.style.display = "table-row"; }; - this.upgradeEnabled = function() {}; + this.updateEnabled = function() {}; }; repoindex.EntryManager = function(EntryType, entryContainer, pagination) { @@ -52,7 +52,7 @@ this.entryContainer = entryContainer; this.entries = []; this.pageName = undefined; - this.upgradeRequired = false; + this.updateRequired = false; // init pagination if((this.pagination = pagination)) { @@ -79,7 +79,7 @@ // define default filter predicate this.filterPred = function(entry) { - return (!this.filterName || (this.filterNameExact ? entry.info.name === this.filterName : entry.info.name.contains(this.filterName))) + return (!this.filterName || (this.filterNameExact ? entry.name === this.filterName : entry.name.contains(this.filterName))) && (!this.filterRepos || this.filterRepos.contains(entry.info.repo)); }; @@ -96,7 +96,7 @@ //} // upgrade pagination (the pageSelected method defined above will be invoked here automatically) this.pagination.entryCount = this.filteredEntries.length; - this.pagination.upgrade(); + this.pagination.update(); }; this.removeFilter = function() { @@ -108,15 +108,15 @@ this.refreshInfo = function() {}; this.invalidate = function() { - this.upgradeRequired = true; - this.upgrade(); + this.updateRequired = true; + this.update(); }; - this.upgrade = function() { - if(this.upgradeRequired && (!this.pageName || this.pageName === repoindex.pageManager.currentPage)) { + this.update = function() { + if(this.updateRequired && (!this.pageName || this.pageName === repoindex.pageManager.currentPage)) { this.applyFilter(); this.infoBox.innerHTML = this.infoText(); - this.upgradeRequired = false; + this.updateRequired = false; } }; @@ -126,9 +126,9 @@ this.entryContainer.wipeChildren(); }; - this.upgradeEnabledForAll = function(enabled) { + this.updateEnabledForAll = function(enabled) { for(var i = 0; i < this.entries.length; ++i) { - this.entries[i].upgradeEnabled(enabled); + this.entries[i].updateEnabled(enabled); } }; @@ -185,14 +185,14 @@ if(this.customSelection) { for(var i = 0; i < this.customSelection.length; ++i) { var entry = this.customSelection[i]; - if(entry.info && entry.info.name === entryName) { + if(entry.name === entryName) { return entry; } } } else { for(var i = 0; i < this.entries.length; ++i) { var entry = this.entries[i]; - if(entry.info && entry.info.name === entryName) { + if(entry.name === entryName) { return entry; } } diff --git a/web/js/groupmanagement.js b/web/js/groupmanagement.js index dea1d74..846230b 100644 --- a/web/js/groupmanagement.js +++ b/web/js/groupmanagement.js @@ -3,11 +3,11 @@ var GroupEntry = {}; GroupEntry.prototype = new repoindex.Entry(); GroupEntry.prototype.constructor = GroupEntry; - GroupEntry = function(groupInfo) { - repoindex.Entry.prototype.constructor.call(this, groupInfo); + GroupEntry = function(groupName, groupInfo) { + repoindex.Entry.prototype.constructor.call(this, groupName, groupInfo); this.initTableRow = function() { - this.rowElement.addCell(this.info.name); + this.rowElement.addCell(this.name); this.rowElement.addCell(this.info.repo); var packagesCellElement = document.createElement("td"); repoindex.setPackageNames(packagesCellElement, this.info.packages); @@ -30,13 +30,13 @@ this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity; this.addEntry = function(repoName, groupName, packages) { - var groupInfo = { - index: this.entries.length, + var entry = new GroupEntry(groupName, { repo: repoName, name: groupName, packages: packages - }; - this.entries.push(new GroupEntry(groupInfo)); + }); + entry.index = this.entries.length; + this.entries.push(entry); }; // handle a page selection diff --git a/web/js/packagemanagement.js b/web/js/packagemanagement.js index be23b2f..e89a30b 100644 --- a/web/js/packagemanagement.js +++ b/web/js/packagemanagement.js @@ -8,67 +8,30 @@ var PackageEntry = {}; PackageEntry.prototype = new repoindex.Entry(); PackageEntry.prototype.constructor = PackageEntry; - PackageEntry = function(repoEntry, packageInfo, color) { + PackageEntry = function(repoEntry, packageName, packageInfo, color) { this.repoEntry = repoEntry; // might be undefined - repoindex.Entry.prototype.constructor.call(this, packageInfo); + repoindex.Entry.prototype.constructor.call(this, packageName, packageInfo); // init row element if(color) { this.rowElement.style.backgroundColor = color; } this.rowElement.onclick = function() { - repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.info.index); + repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.index); }; this.initTableRow = function() { + var basics = this.info.basics ? this.info.basics : {}; var srcOnly = this.repoEntry && this.repoEntry.info.srcOnly; - var values = [srcOnly ? "n/a" : this.info.arch, this.info.repo, this.info.name, this.info.ver, this.info.desc, srcOnly ? "n/a" : this.info.bdate, this.info.flagdate]; + var pkgOnly = this.repoEntry && this.repoEntry.info.pkgOnly; + var version = this.curVer ? (this.curVer + " → " + (basics.ver ? basics.ver : "?")) : basics.ver; + var values = [srcOnly ? "n/a" : basics.arch, this.info.repo, this.name, version, basics.desc, srcOnly ? "n/a" : basics.bdate, pkgOnly ? "n/a" : basics.flagdate]; for(var i = 0; i < 7; ++i) { this.rowElement.addCell(repoindex.makeStr(values[i])); } }; this.initTableRow(); - - this.applyBasicInfo = function(info, noUpgrade) { - if(info.repo) this.info.repo = info.repo; - if(info.name) this.info.name = info.name; - if(info.arch) this.info.arch = info.arch; - if(info.ver) this.info.ver = info.ver; - if(info.desc) this.info.desc = info.desc; - if(info.bdate) this.info.bdate = info.bdate; - if(info.flagdate) this.info.flagdate = info.flagdate; - this.info.basic = true; - if(!noUpgrade) { - this.upgradeTableRow(); - } - }; - - this.applyFullInfo = function(info, noUpgrade) { - this.applyBasicInfo(info); - if(info.idate) this.info.idate = info.idate; - if(info.isize) this.info.isize = info.isize; - if(info.url) this.info.url = info.url; - if(info.isize) this.info.isize = info.isize; - if(info.lic) this.info.lic = info.lic; - if(info.grp) this.info.grp = info.grp; - if(info.prov) this.info.prov = info.prov; - if(info.optd) this.info.optd = info.optd; - if(info.deps) this.info.deps = info.deps; - if(info.requ) this.info.requ = info.requ; - if(info.conf) this.info.conf = info.conf; - if(info.repl) this.info.repl = info.repl; - if(info.pack) this.info.pack = info.pack; - if(info.expl) this.info.expl = info.expl; - if(info.scri) this.info.scri = info.scri; - if(info.sig) this.info.sig = info.sig; - if(info.file) this.info.file = info.file; - if(info.files) this.info.files = info.files; - this.info.full = true; - if(!noUpgrade) { - this.upgradeTableRow(); - } - }; }; repoindex.PackageEntryManager = {}; @@ -82,18 +45,16 @@ this.containerNamePlural = "repositories"; this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity; - this.createCustomEntry = function(color) { - return new PackageEntry(undefined, {}, color); + this.createCustomEntry = function(repoEntry, packageName, packageInfo, color) { + return new PackageEntry(repoEntry, packageName, packageInfo, color); }; - this.addEntry = function(repoEntry, packageName) { - var packageInfo = { - index: this.entries.length, - repo: repoEntry.info.name, - name: packageName, - received: false - }; - var entry = new PackageEntry(repoEntry, packageInfo); + this.addEntry = function(repoEntry, packageName, packageInfo) { + packageInfo.repo = repoEntry.name; + packageInfo.name = packageName; + packageInfo.received = false; + var entry = new PackageEntry(repoEntry, packageName, packageInfo); + entry.index = this.entries.length; this.entries.push(entry); return entry; }; @@ -121,24 +82,13 @@ entriesRequired = true; } }, mgr.filteredEntries.length); + var updateTableRows = function() { + pageElement.forRange(function(i) { + mgr.filteredEntries[i].updateTableRow(); + }, mgr.filteredEntries.length); + }; if(entriesRequired) { - var pkgEntries = repoindex.pageManager.packageManager.relevantEntries(); - var useBasicPackageInfo = function(values) { - if(Array.isArray(values)) { - for(var i = 0; i < values.length; ++i) { - var info = values[i]; - if(!info.error) { - if(info.index >= 0 && info.index < pkgEntries.length) { - var entry = pkgEntries[info.index]; - entry.applyBasicInfo(info); - } - } - } - } else { - repoindex.pageManager.addError("Basic package info returned by the server is invalid."); - } - }; - repoindex.client.requestBasicPackagesInfo(packageSelection, useBasicPackageInfo); + repoindex.client.requestBasicPackagesInfo(packageSelection, updateTableRows); } } }; @@ -170,80 +120,56 @@ // check whether specified entry index is valid var entry = this.entryByIndex(entryIndex); if(entry) { - var i = entry.info; // show properties in "Package info" box var setProperties = function() { // -> basic package info - repoindex.setPackageNames("pkg_name", [i.name]); - repoindex.setText("pkg_repo", repoindex.makeStr(i.repo)); - repoindex.setText("pkg_ver", repoindex.makeStr(i.ver)); - repoindex.setText("pkg_desc", repoindex.makeStr(i.desc)); - repoindex.setText("pkg_arch", repoindex.makeStr(i.arch)); - repoindex.setText("pkg_bdate", repoindex.makeStr(i.bdate)); + var basics = entry.info.basics ? entry.info.basics : {}; + repoindex.setPackageNames("pkg_name", [entry.name]); + repoindex.setText("pkg_repo", repoindex.makeStr(entry.info.repo)); + repoindex.setText("pkg_ver", repoindex.makeStr(basics.ver)); + repoindex.setText("pkg_desc", repoindex.makeStr(basics.desc)); + repoindex.setText("pkg_arch", repoindex.makeStr(basics.arch)); + repoindex.setText("pkg_bdate", repoindex.makeStr(basics.bdate)); // -> full package info - if(i.url) { - repoindex.setLink("pkg_url", i.url, i.url, window.open); + var details = entry.info.details ? entry.info.details : {}; + if(details.url) { + repoindex.setLink("pkg_url", details.url, details.url, window.open); } else { repoindex.setText("pkg_url", "unknown"); } - repoindex.setText("pkg_lic", repoindex.makeArray(i.lic)); - repoindex.setPackageNames("pkg_grp", repoindex.pack(i.grp), repoindex.Pages.Groups); - repoindex.setPackageNames("pkg_prov", repoindex.pkgNamesFromDeps(i.prov)); - repoindex.setPackageNames("pkg_deps", repoindex.pkgNamesFromDeps(i.deps)); - repoindex.setPackageNames("pkg_optd", repoindex.pkgNamesFromDeps(i.optd)); - repoindex.setPackageNames("pkg_requ", repoindex.pack(i.requ)); - repoindex.setPackageNames("pkg_conf", repoindex.pkgNamesFromDeps(i.conf)); - repoindex.setPackageNames("pkg_repl", repoindex.pkgNamesFromDeps(i.repl)); - repoindex.setText("pkg_isize", repoindex.makeDataSize(i.isize)); - repoindex.setText("pkg_pack", repoindex.makeStr(i.pack)); - repoindex.setText("pkg_idate", repoindex.makeStr(i.idate)); - repoindex.setText("pkg_expl", i.repo === "local" ? (i.expl ? "explicitly installed" : "installed as dependency") : "-"); - repoindex.setText("pkg_scri", repoindex.makeBool(i.scri)); - repoindex.setText("pkg_sig", repoindex.makeArray(i.sig)); - repoindex.setTree("pkg_files", repoindex.makeTree(i.files)); + repoindex.setText("pkg_lic", repoindex.makeArray(details.lic)); + repoindex.setPackageNames("pkg_grp", repoindex.pack(details.grp), repoindex.Pages.Groups); + repoindex.setPackageNames("pkg_prov", repoindex.pkgNamesFromDeps(details.prov)); + repoindex.setPackageNames("pkg_deps", repoindex.pkgNamesFromDeps(details.deps)); + repoindex.setPackageNames("pkg_optd", repoindex.pkgNamesFromDeps(details.optd)); + repoindex.setPackageNames("pkg_requ", repoindex.pack(details.requ)); + repoindex.setPackageNames("pkg_conf", repoindex.pkgNamesFromDeps(details.conf)); + repoindex.setPackageNames("pkg_repl", repoindex.pkgNamesFromDeps(details.repl)); + repoindex.setText("pkg_isize", repoindex.makeDataSize(details.isize)); + repoindex.setText("pkg_pack", repoindex.makeStr(details.pack)); + repoindex.setText("pkg_idate", repoindex.makeStr(details.idate)); + repoindex.setText("pkg_expl", entry.info.repo === "local" ? (details.expl ? "explicitly installed" : "installed as dependency") : "n/a"); + repoindex.setText("pkg_scri", repoindex.makeBool(details.scri)); + repoindex.setText("pkg_sig", repoindex.makeArray(details.sig)); + repoindex.setTree("pkg_files", repoindex.makeTree(details.files)); // -> upgrade download buttons - var downloadPkgParams = {repo: i.repo, pkg: i.name, down: "pkg"}; + var downloadPkgParams = {repo: entry.info.repo, pkg: entry.name, down: "pkg"}; repoindex.setDownloadButton("pkg_down", "package", repoindex.makeHash(repoindex.Pages.Packages, downloadPkgParams, true), function() { repoindex.pageManager.denoteHash(repoindex.Pages.Packages, downloadPkgParams); repoindex.pageManager.packageManager.showMirrorsForIndex(entryIndex); }); - var downloadSrcParams = {repo: i.repo, pkg: i.name, down: "src"}; + var downloadSrcParams = {repo: entry.info.repo, pkg: entry.name, down: "src"}; repoindex.setDownloadButton("src_down", "source", repoindex.makeHash(repoindex.Pages.Packages, downloadSrcParams, true)); }; setProperties(); - // use full package info (callback) - var pkgEntries = this.relevantEntries(); - var useFullPackageInfo = function(values) { - // apply full info for all entries - for(var i = 0; i < values.length; ++i) { - var info = values[i]; - if(!info.error) { - if(info.index >= 0 && info.index < pkgEntries.length) { - pkgEntries[info.index].applyFullInfo(info); - } - // set properties (again) to actually show applied info - setProperties(); - } else { - 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); - } - } - }; - if(!i.full) { + if(!entry.info.basics || !entry.info.details) { // don't have the full package info yet -> request full package info var packageSelection = {}; - packageSelection[i.repo] = [{index: entryIndex, name: i.name}]; - repoindex.client.requestFullPackagesInfo(packageSelection, useFullPackageInfo); + packageSelection[entry.info.repo] = [{index: entryIndex, name: entry.name}]; + repoindex.client.requestFullPackagesInfo(packageSelection, setProperties); } // set currentInfo (the pageManager needs this value to upgrade the hash) - this.currentInfo = i; + this.currentInfo = entry.info; // ensures, that the "Package Info" box (with the properties just set) is shown repoindex.pageManager.showPackageInfo(true); } @@ -252,23 +178,22 @@ this.showMirrors = function(repo, name) { var determineEntry = function() { var res = repoindex.pageManager.packageManager.entries.filter(function(entry) { - return entry.info.repo === repo && entry.info.name === name; + return entry.info.repo === repo && entry.name === name; }); // entry exists? if(res.length > 0) { // yes -> full package info available? - var i = res[0].info; + var entry = res[0]; var showEntry = function() { - repoindex.pageManager.packageManager.showMirrorsForIndex(i.index); + repoindex.pageManager.packageManager.showMirrorsForIndex(entry.index); }; - if(i.full) { + if(entry.info.details) { // yes -> show entry instantly showEntry(); } else { // no -> request full info, use callback to show entry when info becomes available - //repoindex.client.requestFullPackagesInfo([{index: i.index, repo: i.repo, name: i.name}], showEntry()); var packageSelection = {}; - packageSelection[i.repo] = [{index: entryIndex, name: i.name}]; + packageSelection[entry.info.repo] = [{index: entry.index, name: entry.name}]; repoindex.client.requestFullPackagesInfo(packageSelection, showEntry); } } else { @@ -290,11 +215,11 @@ var entry = this.entryByIndex(entryIndex); if(entry) { var info = entry.info; - repoindex.setText("title_mirror_selection", "Mirrors for package " + repoindex.escapeHtml(info.name) + "", true); + repoindex.setText("title_mirror_selection", "Mirrors for package " + repoindex.escapeHtml(entry.name) + "", true); var listMirrorSelection = document.getElementById("list_mirror_selection"); listMirrorSelection.wipeChildren(); var repoEntries = repoindex.pageManager.repoManager.entries.filter(function(entry) { - return entry.info.name === info.repo; + return entry.name === info.repo; }); var mirrorsAvailable = 0; if(repoEntries.length > 0) { @@ -303,7 +228,7 @@ for(var i = 0; i < mirrors.length; ++i) { var liElement = document.createElement("li"); var aElement = document.createElement("a"); - var url = mirrors[i] + "/" + info.file; + var url = mirrors[i] + "/" + info.details.file; aElement.appendChild(document.createTextNode(url)); aElement.href = url; aElement.onclick = function() { diff --git a/web/js/pagemanagement.js b/web/js/pagemanagement.js index c245a82..6d79495 100644 --- a/web/js/pagemanagement.js +++ b/web/js/pagemanagement.js @@ -126,7 +126,7 @@ switch(pageName) { case repoindex.Pages.Packages: this.repoManager.buttonRow.style.display = "block"; - this.packageManager.upgrade(); + this.packageManager.update(); if(params && params.repo && params.pkg) { if(params.down) { switch(params.down) { @@ -149,7 +149,7 @@ if(!repoindex.client.hasGroupInfo) { repoindex.client.requestGroupInfo(); } - this.groupManager.upgrade(); + this.groupManager.update(); break; case repoindex.Pages.Repositories: this.repoManager.buttonRow.style.display = "none"; @@ -243,6 +243,9 @@ this.currentManager.filterNameExact = exact; this.currentManager.filterDescr = searchTerm ? (exact ? "with the name" : "for the search term") : undefined; this.currentManager.invalidate(true); + if(searchTerm && !this.currentManager.customSelection && this.repoManager.entryByName("AUR").enabled) { + repoindex.client.requestSuggestions(["AUR"], searchTerm); + } } }; diff --git a/web/js/pagination.js b/web/js/pagination.js index 08931c6..00be643 100644 --- a/web/js/pagination.js +++ b/web/js/pagination.js @@ -93,9 +93,9 @@ // provide a select function for the page element (will be called when the page element // is clicked) pageElement.select = function() { - // upgrade active/inactive status and visibility of all page elements + // update active/inactive status and visibility of all page elements var pagination = this.pagination; - var upgradeVisibility = function(index, visible) { + var updateVisibility = function(index, visible) { var lowest = 0; var highest = pagination.elements.length - 1; var low = index - pagination.visibleElementsBeforeAfter; @@ -112,13 +112,13 @@ } } if(pagination.currentElement) { - upgradeVisibility(pagination.currentElement.pageIndex, false); + updateVisibility(pagination.currentElement.pageIndex, false); pagination.currentElement.setActive(false); } - upgradeVisibility(this.pageIndex, true); + updateVisibility(this.pageIndex, true); this.setActive(true); - // upgrade status of the containing pagination object + // update status of the containing pagination object pagination.currentElement = this; pagination.previousElement.setEnabled(this !== pagination.elements.first()); pagination.nextElement.setEnabled(this !== pagination.elements.last()); @@ -186,8 +186,8 @@ this.currentElement = undefined; }; - // provide a function to upgrade the pagination (after properties such as the entryCount have been changed) - this.upgrade = function() { + // provide a function to update the pagination (after properties such as the entryCount have been changed) + this.update = function() { var requiredPages = Math.ceil(this.entryCount / this.entriesPerPage); var currentPages = this.elements.length; if(requiredPages < currentPages) { diff --git a/web/js/repomanagement.js b/web/js/repomanagement.js index 06d6329..22512c9 100644 --- a/web/js/repomanagement.js +++ b/web/js/repomanagement.js @@ -5,20 +5,20 @@ RepoEntry.prototype.constructor = RepoEntry; RepoEntry = function(repoName, repoInfo, enabled) { if(enabled === undefined) { - enabled = !repoInfo.requestRequired; + // per default enable all repos with a fix number of packages + enabled = repoInfo.packageCount; } repoindex.Entry.prototype.constructor.call(this, repoName, repoInfo, enabled); this.info.currentServer = 0; this.rowElement.onclick = function() { - repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.info.index); + repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.index); }; this.initTableRow = function() { - this.rowElement.addCell(this.info.name); + this.rowElement.addCell(this.name); this.rowElement.addCell(this.info.desc); - //this.rowElement.addCell(this.info.servers && this.info.currentServer < this.info.servers.length ? this.info.servers[this.info.currentServer] : "none"); }; this.initTableRow(); @@ -31,23 +31,23 @@ this.link.repo = this; this.link.onclick = function() { if(repoindex.pageManager.repoManager.buttonContainerExclusiveButton.checked) { - repoindex.pageManager.repoManager.upgradeEnabledAll(false); + repoindex.pageManager.repoManager.updateEnabledAll(false); } this.repo.toggleEnabled(); return false; }; - this.link.appendChild(document.createTextNode(repoInfo.name)); + this.link.appendChild(document.createTextNode(repoName)); this.link.title = repoInfo.desc; // use Bootstrap tooltip this.link.setAttribute("data-placement", "bottom"); $(this.link).tooltip(); - if(!repoInfo.requestRequired) { + if(repoInfo.packageCount) { // create badge with package count var span = document.createElement("span"); span.className = "badge"; - span.appendChild(document.createTextNode(repoInfo.packages.length)); + span.appendChild(document.createTextNode(repoInfo.packageCount)); this.link.appendChild(document.createTextNode(" ")); this.link.appendChild(span); } @@ -65,15 +65,17 @@ // provide a function to toggle enabled/disabled - this.upgradeEnabled = function(enabled) { + this.updateEnabled = function(enabled, noNotify) { if(this.enabled !== enabled) { this.link.className = (this.enabled = enabled) ? "btn btn-primary" : "btn btn-default"; - this.statusChanged(this); + if(!noNotify) { + this.statusChanged(this); + } } }; this.toggleEnabled = function() { - this.upgradeEnabled(!this.enabled); + this.updateEnabled(!this.enabled); }; }; @@ -98,7 +100,7 @@ var repoFilter = []; for(var i = 0, end = repoEntries.length; i < end; ++i) { if(repoEntries[i].enabled) { - repoFilter.push(repoEntries[i].info.name); + repoFilter.push(repoEntries[i].name); } } pageMgr.packageManager.filterRepos = pageMgr.groupManager.filterRepos = repoFilter; @@ -107,12 +109,12 @@ }; // provide a function to add repo entries - this.addEntry = function(repoInfo) { - var entry = new RepoEntry(repoInfo); + this.addEntry = function(repoName, repoInfo) { + var entry = new RepoEntry(repoName, repoInfo); entry.statusChanged = this.applyRepoStatusChange; entry.pageManager = this; entry.repoEntryManager = this; - entry.info.index = this.entries.length; + entry.index = this.entries.length; this.entries.push(entry); return entry; }; @@ -125,9 +127,12 @@ this.baseRemoveEntries(); }; - this.upgradeEnabledAll = function(enabled) { + this.updateEnabledAll = function(enabled, noNotify) { for(var i = 0; i < this.entries.length; ++i) { - this.entries[i].upgradeEnabled(enabled); + this.entries[i].updateEnabled(enabled, true); + } + if(!noNotify) { + this.applyRepoStatusChange(); } }; @@ -151,12 +156,12 @@ this.showRepoInfo = function(repo) { var determineEntry = function() { var res = repoindex.pageManager.repoManager.entries.filter(function(entry) { - return entry.info.name === repo; + return entry.name === repo; }); // entry exists? if(res.length > 0) { // yes -> full package info available? - repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].info.index); + repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].index); } else { // no -> show error repoindex.pageManager.repoManager.showRepoNotFound(repo); @@ -175,21 +180,21 @@ this.showRepoInfoForIndex = function(entryIndex) { var entry = this.entryByIndex(entryIndex); if(entry) { - var i = entry.info; - repoindex.setText("repo_name", i.name); - repoindex.setText("repo_desc", i.desc); - repoindex.setText("repo_pkgcount", i.requestRequired ? "unknown" : i.packages.length); - repoindex.setText("repo_usage", repoindex.makeArray(i.usage, ", ")); - repoindex.setText("repo_siglevel", repoindex.makeArray(i.sigLevel, ", ")); - repoindex.setText("repo_source_only", repoindex.makeBool(i.srcOnly)); - repoindex.setText("repo_upgrade_sources", repoindex.makeArray(i.upgradeSources, ", ")); - var invokeUpgradeLookupParams = {repo: i.name, invoke: "upgradelookup"}; + var info = entry.info; + repoindex.setText("repo_name", entry.name); + repoindex.setText("repo_desc", info.desc); + repoindex.setText("repo_pkgcount", repoindex.makeStr(info.packageCount)); + repoindex.setText("repo_usage", repoindex.makeArray(info.usage, ", ")); + repoindex.setText("repo_siglevel", repoindex.makeArray(info.sigLevel, ", ")); + repoindex.setText("repo_source_only", repoindex.makeBool(info.srcOnly)); + repoindex.setText("repo_upgrade_sources", repoindex.makeArray(info.upgradeSources, ", ")); + var invokeUpgradeLookupParams = {repo: entry.name, invoke: "upgradelookup"}; repoindex.setDownloadButton("repo_checkforupgrades", "check for upgrades", repoindex.makeHash(repoindex.Pages.Repositories, invokeUpgradeLookupParams, true), function() { - repoindex.pageManager.repoManager.showAvailableUpgrades(i.name); + repoindex.pageManager.repoManager.showAvailableUpgrades(entry.name); repoindex.pageManager.denoteHash(repoindex.Pages.Repositories, invokeUpgradeLookupParams); }, "refresh"); // set currentInfo (the pageManager needs this value to upgrade the hash) - this.currentInfo = i; + this.currentInfo = info; // ensures, that the "Package Info" box (with the properties just set) is shown repoindex.pageManager.showRepoInfo(); } @@ -250,21 +255,21 @@ // iterate through all kinds of "upgrades" declared above for(var i2 = 0; i2 < upgrades[i1].entries.length; ++i2) { // iterate through all entries which have been assigned to the current upgrade type - var newEntry = pkgMgr.createCustomEntry(upgrades[i1].color); - newEntry.info.index = upgradeEntries.length; - if(upgrades[i1].entries[i2].pkg) { - newEntry.applyBasicInfo(upgrades[i1].entries[i2].pkg, true); - if(upgrades[i1].entries[i2].curVer) { - // add current version to upgrade version - newEntry.info.ver = upgrades[i1].entries[i2].curVer + " → " + (newEntry.info.ver ? newEntry.info.ver : "?"); - } + var upgradeEntry = upgrades[i1].entries[i2]; + if(upgradeEntry.pkg) { + var packageInfo = repoindex.client.getPackageInfo(upgradeEntry.repo, upgradeEntry.name); + packageInfo.basics = upgradeEntry.pkg; // find associated repo entry - if((newEntry.repoEntry = repoMgr.entryByName(newEntry.info.repo))) { - newEntry.repoEntry.upgradeEnabled(true); // ensure repo is enabled + var repoEntry; + if((repoEntry = repoMgr.entryByName(packageInfo.repo))) { + repoEntry.updateEnabled(true); // ensure repo is enabled } - newEntry.upgradeTableRow(); + var newEntry = pkgMgr.createCustomEntry(repoEntry, upgradeEntry.name, packageInfo, upgrades[i1].color); + newEntry.curVer = upgradeEntry.curVer; + newEntry.index = upgradeEntries.length; + newEntry.updateTableRow(); } else { - newEntry.applyBasicInfo(upgrades[i1].entries[i2]); + repoindex.pageManager.addError("Upgrade entry replied by server does not include package info."); } upgradeEntries.push(newEntry); }