added suggestions lookup, added src info parser

This commit is contained in:
Martchus 2015-09-21 22:16:19 +02:00
parent 0d20332542
commit 715633c96e
37 changed files with 1169 additions and 484 deletions

View File

@ -58,8 +58,8 @@ PackageLoader::PackageLoader(AlpmDatabase *db)
/*! /*!
* \brief Creates a new instance wrapping the specified database struct. * \brief Creates a new instance wrapping the specified database struct.
*/ */
RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, QObject *parent) : RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, uint32 index, QObject *parent) :
Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), parent), Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), index, parent),
m_ptr(dataBase), m_ptr(dataBase),
m_dbFile(QStringLiteral("%1/sync/%2").arg(dbPath, m_name)) m_dbFile(QStringLiteral("%1/sync/%2").arg(dbPath, m_name))
{} {}

View File

@ -35,8 +35,9 @@ inline QFuture<void> &PackageLoader::future()
class AlpmDatabase : public Repository class AlpmDatabase : public Repository
{ {
friend class EmplacePackage; friend class EmplacePackage;
public: 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(); PackageLoader *init();
// explicit AlpmDatabase(const QString &dataBaseFile, QObject *parent = nullptr); // 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 inline PackageList AlpmDatabase::search(StringList terms) const
{ {

View File

@ -37,6 +37,13 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue, UserRepository *repositor
m_tarUrl = obj.value(QStringLiteral("URLPath")).toString(); 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. * \brief Creates a new, empty instance.
* \remarks The only purpose of this c'tor is to use it with restoreFromCacheStream(). * \remarks The only purpose of this c'tor is to use it with restoreFromCacheStream().

View File

@ -10,8 +10,9 @@ class UserRepository;
class AurPackage : public Package class AurPackage : public Package
{ {
public: public:
AurPackage(const QJsonValue &aurJsonValue, UserRepository *repository); explicit AurPackage(const QJsonValue &aurJsonValue, UserRepository *repository);
AurPackage(UserRepository *repository); explicit AurPackage(const QString &name, UserRepository *repository);
explicit AurPackage(UserRepository *repository);
}; };
} // namespace PackageManagement } // namespace PackageManagement

View File

@ -49,6 +49,8 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
targetNameArg("target-name", "n", "specifies the name of the target archive"), targetNameArg("target-name", "n", "specifies the name of the target archive"),
targetFormatArg("target-format", "e", "specifies the format 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"), 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'"), 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") repoArg("repo", string(), "specifies the repository")
{ {
@ -110,13 +112,19 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
iconThemesArg.setCombinable(true); iconThemesArg.setCombinable(true);
iconThemesArg.setRequiredValueCount(-1); iconThemesArg.setRequiredValueCount(-1);
iconThemesArg.setValueNames(pkgValueNames); 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); shSyntaxArg.setCombinable(true);
repoArg.setRequiredValueCount(1); repoArg.setRequiredValueCount(1);
repoArg.setValueNames({"repo name"}); repoArg.setValueNames({"repo name"});
serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg}); serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg});
upgradeLookupArg.setSecondaryArguments({&shSyntaxArg}); upgradeLookupArg.setSecondaryArguments({&shSyntaxArg});
buildOrderArg.setSecondaryArguments({&aurArg, &verboseArg, &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}); parser.setMainArguments({&buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &helpArg});
} }
@ -132,6 +140,7 @@ Config::Config() :
m_alpmRootDir(QStringLiteral("/")), m_alpmRootDir(QStringLiteral("/")),
m_alpmDbPath(QStringLiteral("/var/lib/pacman")), m_alpmDbPath(QStringLiteral("/var/lib/pacman")),
m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")), m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")),
m_cacheDir(QStringLiteral(".")),
m_websocketServerListeningAddr(QHostAddress::LocalHost), m_websocketServerListeningAddr(QHostAddress::LocalHost),
m_websocketServerListeningPort(1234), m_websocketServerListeningPort(1234),
m_serverInsecure(false), m_serverInsecure(false),

View File

@ -40,6 +40,8 @@ public:
ApplicationUtilities::Argument targetNameArg; ApplicationUtilities::Argument targetNameArg;
ApplicationUtilities::Argument targetFormatArg; ApplicationUtilities::Argument targetFormatArg;
ApplicationUtilities::Argument iconThemesArg; ApplicationUtilities::Argument iconThemesArg;
ApplicationUtilities::Argument defaultIconThemeArg;
ApplicationUtilities::Argument extraPackagesArg;
ApplicationUtilities::Argument shSyntaxArg; ApplicationUtilities::Argument shSyntaxArg;
ApplicationUtilities::Argument repoArg; ApplicationUtilities::Argument repoArg;
}; };

View File

@ -1,8 +1,8 @@
#include "./manager.h" #include "./manager.h"
#include "./alpmdatabase.h"
#include "./utilities.h" #include "./utilities.h"
#include "./list.h" #include "./list.h"
#include "./config.h" #include "./config.h"
#include "./alpmdatabase.h"
#include "../network/userrepository.h" #include "../network/userrepository.h"
@ -63,11 +63,7 @@ Manager::Manager(const Config &config) :
m_sigLevel(defaultSigLevel), m_sigLevel(defaultSigLevel),
m_localFileSigLevel(ALPM_SIG_USE_DEFAULT) m_localFileSigLevel(ALPM_SIG_USE_DEFAULT)
{ {
alpm_errno_t err; initAlpmHandle();
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<AlpmDatabase>(alpm_get_localdb(m_handle), config.alpmDbPath());
if(config.isAurEnabled()) { if(config.isAurEnabled()) {
m_userRepo = make_unique<UserRepository>(m_networkAccessManager); m_userRepo = make_unique<UserRepository>(m_networkAccessManager);
} }
@ -81,7 +77,7 @@ Manager::~Manager()
if(m_writeCacheBeforeGone) { if(m_writeCacheBeforeGone) {
writeCache(); 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 // open config file and parse as ini
try { try {
@ -309,21 +305,21 @@ void Manager::applyPacmanConfig()
// read relevant options // read relevant options
static const string sigLevelKey("SigLevel"); static const string sigLevelKey("SigLevel");
static const string usageKey("Usage"); static const string usageKey("Usage");
int globalSigLevel; int globalSigLevel = defaultSigLevel;
try { for(auto &scope : config) {
const auto &options = config.at("options"); if(scope.first == "options") {
const auto &specifiedArch = lastValue(options, "Architecture"); // iterate through all "config" scopes (just to cover the case that there are multiple "options" scopes)
if(!specifiedArch.empty() && specifiedArch != "auto") { const auto &options = scope.second;
arch = specifiedArch; 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 // register sync databases
unordered_map<string, IniFile> includedInis; unordered_map<string, IniFile> includedInis;
@ -347,7 +343,7 @@ void Manager::applyPacmanConfig()
// set usage // set usage
if(alpm_db_set_usage(db, static_cast<alpm_db_usage_t>(usage)) == 0) { if(alpm_db_set_usage(db, static_cast<alpm_db_usage_t>(usage)) == 0) {
if(m_config.isVerbose() || m_config.runServer()) { if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added database [" << scope.first << "]" << endl; cerr << shchar << "Added database [" << scope.first << ']' << endl;
} }
} else { } else {
if(m_config.isVerbose() || m_config.runServer()) { if(m_config.isVerbose() || m_config.runServer()) {
@ -379,28 +375,31 @@ void Manager::applyPacmanConfig()
} }
} }
try { try {
const auto &includedScope = includedIni.data().at(string()); for(auto &scope : includedIni.data()) {
for(auto range = includedScope.equal_range("Server"); range.first != range.second; ++range.first) { if(scope.first.empty()) {
string url = range.first->second; for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) {
findAndReplace<string>(url, "$repo", scope.first); string url = range.first->second;
findAndReplace<string>(url, "$arch", arch); findAndReplace<string>(url, "$repo", scope.first);
alpm_db_add_server(db, url.c_str()); findAndReplace<string>(url, "$arch", arch);
if(m_config.isVerbose() || m_config.runServer()) { alpm_db_add_server(db, url.c_str());
cerr << shchar << "Added server: " << url << endl; if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added server: " << url << endl;
}
}
} }
} }
} catch (const out_of_range &) { } catch (const out_of_range &) {
cerr << shchar << "Warning: Included file \"" << path << "\" has no values." << endl; cerr << shchar << "Warning: Included file \"" << path << "\" has no values." << endl;
} }
} }
auto emplaced = m_syncDbs.emplace(dbName, make_unique<AlpmDatabase>(db, m_config.alpmDbPath())); // add sync db to internal map (use as index size + 1 because the local database has index 0)
// add sync db to internal map auto emplaced = m_syncDbs.emplace(dbName, make_unique<AlpmDatabase>(db, m_config.alpmDbPath(), m_syncDbs.size() + 1));
if(usage & ALPM_DB_USAGE_UPGRADE) { if(usage & ALPM_DB_USAGE_UPGRADE) {
// -> db is used to upgrade local database // -> db is used to upgrade local database
localDataBase()->upgradeSources() << emplaced.first->second.get(); localDataBase()->upgradeSources() << emplaced.first->second.get();
} }
} else { } 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 // check whether an entry already exists, if not create a new one
for(const RepoEntry &repoEntry : m_config.repoEntries()) { for(const RepoEntry &repoEntry : m_config.repoEntries()) {
AlpmDatabase *syncDb = nullptr; AlpmDatabase *syncDb = nullptr;
try { try {
syncDb = m_syncDbs.at(repoEntry.name()).get(); 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()) { if(!repoEntry.dataBasePath().isEmpty()) {
cerr << shchar << "Warning: Can't use data base path specified in repo index config because the repo \"" 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; << repoEntry.name() << "\" has already been added from the Pacman config." << endl;
@ -439,13 +438,13 @@ void Manager::applyRepoIndexConfig()
} else { } else {
// TODO: database path // TODO: database path
auto *db = alpm_register_syncdb(m_handle, repoEntry.name().toLocal8Bit().data(), static_cast<alpm_siglevel_t>(repoEntry.sigLevel())); auto *db = alpm_register_syncdb(m_handle, repoEntry.name().toLocal8Bit().data(), static_cast<alpm_siglevel_t>(repoEntry.sigLevel()));
auto emplaced = m_syncDbs.emplace(repoEntry.name(), make_unique<AlpmDatabase>(db, m_config.alpmDbPath())); auto emplaced = m_syncDbs.emplace(repoEntry.name(), make_unique<AlpmDatabase>(db, m_config.alpmDbPath(), m_syncDbs.size() + 1));
if(emplaced.second) { if(emplaced.second) {
syncDb = emplaced.first->second.get(); syncDb = emplaced.first->second.get();
syncDb->setSourcesDirectory(repoEntry.sourceDir()); syncDb->setSourcesDirectory(repoEntry.sourceDir());
syncDb->setPackagesDirectory(repoEntry.packageDir()); syncDb->setPackagesDirectory(repoEntry.packageDir());
if(m_config.isVerbose() || m_config.runServer()) { 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. * \brief Returns a list of all sync databases.
* \remarks Sync databases must be registered with parsePacmanConfig() before. * \remarks Sync databases must be registered with parsePacmanConfig() before.
*/ */
const map<QString, unique_ptr<AlpmDatabase> > &Manager::syncDatabases() const const std::map<QString, std::unique_ptr<AlpmDatabase> > &Manager::syncDatabases() const
{ {
return m_syncDbs; // m_syncDbs has been filled when the databases were registered return m_syncDbs; // m_syncDbs has been filled when the databases were registered
} }
@ -567,26 +566,26 @@ const map<QString, unique_ptr<AlpmDatabase> > &Manager::syncDatabases() const
* The results include the local database ("local") and the names of * The results include the local database ("local") and the names of
* the registered sync databases. * the registered sync databases.
*/ */
const QJsonArray &Manager::basicRepoInfo() const const QJsonObject &Manager::basicRepoInfo() const
{ {
if(m_basicRepoInfo.isEmpty()) { if(m_basicRepoInfo.isEmpty()) {
QMutexLocker locker(&m_basicRepoInfoMutex); QMutexLocker locker(&m_basicRepoInfoMutex);
if(m_basicRepoInfo.isEmpty()) { if(m_basicRepoInfo.isEmpty()) {
// add local data base // add local data base
m_basicRepoInfo << localDataBase()->basicInfo(); m_basicRepoInfo.insert(localDataBase()->name(), localDataBase()->basicInfo());
// add sync data bases // add sync data bases
for(const auto &syncDb : syncDatabases()) { for(const auto &syncDb : syncDatabases()) {
// check if the "sync" database is actually used for syncing // check if the "sync" database is actually used for syncing
auto usage = syncDb.second->usage(); auto usage = syncDb.second->usage();
if((usage & ALPM_DB_USAGE_SYNC) || (usage & ALPM_DB_USAGE_INSTALL) || (usage & ALPM_DB_USAGE_UPGRADE)) { 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 { } else {
m_basicRepoInfo << syncDb.second->basicInfo(); m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
} }
} }
// add AUR // add AUR
if(config().isAurEnabled()) { if(userRepository()) {
m_basicRepoInfo << userRepository()->basicInfo(); 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. * \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) { for(auto i = pkgSelection.constBegin(), end = pkgSelection.constEnd(); i != end; ++i) {
if(auto *repo = repositoryByName(i.key())) { if(auto *repo = repositoryByName(i.key())) {
for(const auto &entry : i.value().toArray()) { for(const auto &entry : i.value().toArray()) {
const auto entryObj = entry.toObject(); const auto entryObj = entry.toObject();
const auto pkgName = entryObj.value(QStringLiteral("name")).toString(); const auto pkgName = entryObj.value(QStringLiteral("name")).toString();
if(!pkgName.isEmpty()) { if(!pkgName.isEmpty()) {
QJsonObject pkgInfo; QJsonObject res;
if(auto *pkg = repo->packageByName(pkgName)) { 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 { } else {
pkgInfo.insert(QStringLiteral("error"), QStringLiteral("na")); res.insert(QStringLiteral("error"), QStringLiteral("na"));
} }
pkgInfo.insert(QStringLiteral("name"), pkgName); res.insert(QStringLiteral("name"), pkgName);
pkgInfo.insert(QStringLiteral("repo"), repo->name()); res.insert(QStringLiteral("repo"), repo->name());
const auto index = entryObj.value(QStringLiteral("index")); const auto index = entryObj.value(QStringLiteral("index"));
if(!index.isNull() && !index.isUndefined()) { if(!index.isNull() && !index.isUndefined()) {
pkgInfo.insert(QStringLiteral("index"), index); res.insert(QStringLiteral("index"), index);
} }
pkgInfos << pkgInfo; results << res;
} }
} }
} else { } else {
@ -625,11 +629,11 @@ const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, bool full
QJsonObject errorObj; QJsonObject errorObj;
errorObj.insert(QStringLiteral("repo"), i.key()); errorObj.insert(QStringLiteral("repo"), i.key());
errorObj.insert(QStringLiteral("error"), QStringLiteral("na")); 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; 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<AlpmDatabase>(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. * \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<const Repository *> Manager::repositories() const
{
QList<const Repository *> 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<Repository *> Manager::repositories()
{
QList<Repository *> 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. * \brief Checks the specified database for upgrades.
* *

View File

@ -23,6 +23,13 @@ class AlpmDatabase;
class Manager class Manager
{ {
public: public:
enum PackageInfoPart {
None = 0x0,
Basics = 0x1,
Details = 0x2,
};
Q_DECLARE_FLAGS(PackageInfoParts, PackageInfoPart)
explicit Manager(const Config &config); explicit Manager(const Config &config);
Manager(const Manager &other) = delete; Manager(const Manager &other) = delete;
Manager(Manager &&other) = delete; Manager(Manager &&other) = delete;
@ -40,8 +47,10 @@ public:
const QString &pacmanCacheDir() const; const QString &pacmanCacheDir() const;
static int parseSigLevel(const std::string &sigLevelStr = std::string()); static int parseSigLevel(const std::string &sigLevelStr = std::string());
static int parseUsage(const std::string &usageStr); static int parseUsage(const std::string &usageStr);
void applyPacmanConfig(); void initAlpmHandle();
void applyRepoIndexConfig(); void cleanupAlpm();
void registerDataBasesFromPacmanConfig();
void registerDatabasesFromRepoIndexConfig();
void initAlpmDataBases(bool computeRequiredBy); void initAlpmDataBases(bool computeRequiredBy);
void writeCache(); void writeCache();
void restoreCache(); void restoreCache();
@ -69,19 +78,15 @@ public:
Repository *repositoryByName(const QString &name); Repository *repositoryByName(const QString &name);
const UserRepository *userRepository() const; const UserRepository *userRepository() const;
UserRepository *userRepository(); UserRepository *userRepository();
QList<const Repository *> repositories() const;
QList<Repository *> repositories();
const UpgradeLookupResults checkForUpgrades(AlpmDatabase *db) const; const UpgradeLookupResults checkForUpgrades(AlpmDatabase *db) const;
// JSON serialization, handling JSON requests // JSON serialization, handling JSON requests
const QJsonObject basicRepoInfo(const Repository *packageSource) const; const QJsonObject basicRepoInfo(const Repository *packageSource) const;
const QJsonArray &basicRepoInfo() const; const QJsonObject &basicRepoInfo() const;
const QJsonArray packageInfo(const QJsonObject &pkgSelection, bool full = true) const; const QJsonArray packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const;
const QJsonArray &groupInfo() const; const QJsonArray &groupInfo() const;
private: private:
void cleanup();
const Config &m_config; const Config &m_config;
bool m_writeCacheBeforeGone; bool m_writeCacheBeforeGone;
alpm_handle_t *m_handle; alpm_handle_t *m_handle;
@ -92,12 +97,14 @@ private:
std::unique_ptr<UserRepository> m_userRepo; std::unique_ptr<UserRepository> m_userRepo;
std::unique_ptr<AlpmDatabase> m_localDb; std::unique_ptr<AlpmDatabase> m_localDb;
std::map<QString, std::unique_ptr<AlpmDatabase> > m_syncDbs; std::map<QString, std::unique_ptr<AlpmDatabase> > m_syncDbs;
mutable QJsonArray m_basicRepoInfo; mutable QJsonObject m_basicRepoInfo;
mutable QMutex m_basicRepoInfoMutex; mutable QMutex m_basicRepoInfoMutex;
mutable QJsonArray m_groupInfo; mutable QJsonArray m_groupInfo;
mutable QMutex m_groupInfoMutex; mutable QMutex m_groupInfoMutex;
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(Manager::PackageInfoParts)
/*! /*!
* \brief Returns the configuration of the manager. * \brief Returns the configuration of the manager.
* \remarks The configuration has been specified when constructing the manager. * \remarks The configuration has been specified when constructing the manager.

View File

@ -27,8 +27,9 @@ using namespace Utilities;
const string prefix("mingw-w64-"); const string prefix("mingw-w64-");
MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages) : MingwBundle::MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages) :
m_manager(manager) m_manager(manager),
m_extraPackages(extraPackages)
{ {
cerr << shchar << "Resolving dependencies ..." << endl; cerr << shchar << "Resolving dependencies ..." << endl;
string missing; string missing;
@ -36,7 +37,8 @@ MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::Str
for(const auto &pkgName : packages) { for(const auto &pkgName : packages) {
bool found = false; bool found = false;
for(const auto &syncDb : manager.syncDatabases()) { 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()) { if(missing.empty()) {
decltype(m_packages)::value_type entry(syncDb.second.get(), pkg); decltype(m_packages)::value_type entry(syncDb.second.get(), pkg);
if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) { if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) {
@ -120,7 +122,8 @@ enum class RelevantFileType
Translation, Translation,
QtTranslation, QtTranslation,
QtPlugin, QtPlugin,
Icon IconTheme,
ConfigFile
}; };
enum class RelevantFileArch enum class RelevantFileArch
@ -133,12 +136,14 @@ enum class RelevantFileArch
struct RelevantFile struct RelevantFile
{ {
RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) : RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) :
file(file), name(file->name()),
data(file->data()),
fileType(type), fileType(type),
arch(arch), arch(arch),
subDir(subDir) subDir(subDir)
{} {}
const KArchiveFile *file; QString name;
QByteArray data;
RelevantFileType fileType; RelevantFileType fileType;
RelevantFileArch arch; RelevantFileArch arch;
QString subDir; QString subDir;
@ -191,8 +196,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
if(entryName.endsWith(QLatin1String(".exe")) || entryName.endsWith(QLatin1String(".dll"))) { if(entryName.endsWith(QLatin1String(".exe")) || entryName.endsWith(QLatin1String(".dll"))) {
if(const auto *entry = binDir->entry(entryName)) { if(const auto *entry = binDir->entry(entryName)) {
if(entry->isFile()) { if(entry->isFile()) {
const auto *binFile = static_cast<const KArchiveFile *>(entry); pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), RelevantFileType::Binary, root.first);
pkgFileInfo.relevantFiles.emplace_back(binFile, RelevantFileType::Binary, root.first);
} }
} }
} }
@ -214,8 +218,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
for(const auto &entryName : categoryDir->entries()) { for(const auto &entryName : categoryDir->entries()) {
if(const auto *pluginEntry = categoryDir->entry(entryName)) { if(const auto *pluginEntry = categoryDir->entry(entryName)) {
if(pluginEntry->isFile()) { if(pluginEntry->isFile()) {
const auto *pluginFile = static_cast<const KArchiveFile *>(pluginEntry); pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(pluginEntry), RelevantFileType::QtPlugin, root.first, categoryDir->name());
pkgFileInfo.relevantFiles.emplace_back(pluginFile, RelevantFileType::QtPlugin, root.first, categoryDir->name());
} }
} }
} }
@ -237,8 +240,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
if(entryName.endsWith(QLatin1String(".qm"))) { if(entryName.endsWith(QLatin1String(".qm"))) {
if(const auto *qmEntry = trDir->entry(entryName)) { if(const auto *qmEntry = trDir->entry(entryName)) {
if(qmEntry->isFile()) { if(qmEntry->isFile()) {
const auto *qmFile = static_cast<const KArchiveFile *>(qmEntry); pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(qmEntry), RelevantFileType::QtTranslation, root.first);
pkgFileInfo.relevantFiles.emplace_back(qmFile, RelevantFileType::QtTranslation, root.first);
} }
} }
} }
@ -249,6 +251,34 @@ void getFiles(PkgFileInfo &pkgFileInfo)
const auto *appEntry = shareDir->entry(pkgFileInfo.name); const auto *appEntry = shareDir->entry(pkgFileInfo.name);
if(appEntry && appEntry->isDirectory()) { if(appEntry && appEntry->isDirectory()) {
const auto *appDir = static_cast<const KArchiveDirectory *>(appEntry); const auto *appDir = static_cast<const KArchiveDirectory *>(appEntry);
for(const auto &entryName : appDir->entries()) {
const auto *entry = appDir->entry(entryName);
if(entry->isFile()) {
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), RelevantFileType::ConfigFile, root.first);
} else {
const auto subDir = static_cast<const KArchiveDirectory *>(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<const KArchiveFile *>(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<const KArchiveFile *>(configEntry), RelevantFileType::ConfigFile, root.first, subDir->name());
}
}
}
}
}
}
const auto *trEntry = appDir->entry(QStringLiteral("translations")); const auto *trEntry = appDir->entry(QStringLiteral("translations"));
if(trEntry && trEntry->isDirectory()) { if(trEntry && trEntry->isDirectory()) {
const auto trDir = static_cast<const KArchiveDirectory *>(trEntry); const auto trDir = static_cast<const KArchiveDirectory *>(trEntry);
@ -263,6 +293,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
} }
} }
} }
} }
} }
} }
@ -274,7 +305,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
for(const auto &themeName : iconsDir->entries()) { for(const auto &themeName : iconsDir->entries()) {
const auto *themeEntry = iconsDir->entry(themeName); const auto *themeEntry = iconsDir->entry(themeName);
if(themeEntry && themeEntry->isDirectory()) { if(themeEntry && themeEntry->isDirectory()) {
addEntries(pkgFileInfo, RelevantFileType::Icon, static_cast<const KArchiveDirectory *>(themeEntry)); addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast<const KArchiveDirectory *>(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<PkgFileInfo> &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<KArchive> targetArchive;
if(targetFormat == "7z") {
targetArchive = make_unique<K7Zip>(targetPath);
} else if(targetFormat == "zip") {
targetArchive = make_unique<KZip>(targetPath);
} else if(ConversionUtilities::startsWith<string>(targetFormat, "tar")) {
targetArchive = make_unique<KTar>(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; cerr << shchar << "Gathering relevant files ..." << endl;
// get package files // 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); 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 // get relevant files from packages
QtConcurrent::blockingMap(pkgFiles, getFiles); QtConcurrent::blockingMap(pkgFiles, getFiles);
// check whether all packages could be opened // check whether all packages could be opened
@ -325,59 +426,18 @@ void MingwBundle::createBundle(const string &targetDir, const string &targetName
QJsonDocument pkgList; QJsonDocument pkgList;
pkgList.setArray(pkgArray); pkgList.setArray(pkgArray);
QByteArray pkgListBytes = pkgList.toJson(); QByteArray pkgListBytes = pkgList.toJson();
// make target archive QByteArray indexFileBytes;
static const QString user(QStringLiteral("root")); if(!defaultIconTheme.empty()) {
static const QString &group = user; indexFileBytes.reserve(23 + defaultIconTheme.size());
static const pair<RelevantFileArch, QString> roots[] = { indexFileBytes.append("[Icon Theme]\nInherits=");
make_pair(RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32")), indexFileBytes.append(defaultIconTheme.data());
make_pair(RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32")) indexFileBytes.append('\n');
};
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<KArchive> targetArchive;
if(targetFormat == "7z") {
targetArchive = make_unique<K7Zip>(targetPath);
} else if(targetFormat == "zip") {
targetArchive = make_unique<KZip>(targetPath);
} else if(ConversionUtilities::startsWith<string>(targetFormat, "tar")) {
targetArchive = make_unique<KTar>(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.");
}
} }
// 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 } // namespace PackageManagement

View File

@ -15,15 +15,16 @@ class Manager;
class MingwBundle class MingwBundle
{ {
public: 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: private:
void addDependencies(const Package *pkg); void addDependencies(const Package *pkg);
const Manager &m_manager; Manager &m_manager;
std::list<std::pair<const AlpmDatabase *, const Package *> > m_packages; std::list<std::pair<const AlpmDatabase *, const Package *> > m_packages;
const ApplicationUtilities::StringVector &m_extraPackages;
}; };
} // namespace PackageManagement } // namespace PackageManagement

View File

@ -1,4 +1,4 @@
#include "./package.h" #include "./package.h"
#include "./alpmdatabase.h" #include "./alpmdatabase.h"
#include "./utilities.h" #include "./utilities.h"
#include "./repository.h" #include "./repository.h"
@ -10,6 +10,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QVariant> #include <QVariant>
#include <QDataStream> #include <QDataStream>
#include <QStringBuilder>
#include <iostream> #include <iostream>
@ -32,6 +33,7 @@ namespace RepoIndex {
Package::Package(const QString &name, Repository *repository) : Package::Package(const QString &name, Repository *repository) :
m_origin(PackageOrigin::Unknown), m_origin(PackageOrigin::Unknown),
m_repository(repository), m_repository(repository),
m_timeStamp(DateTime::now()),
m_hasGeneralInfo(false), m_hasGeneralInfo(false),
m_name(name), m_name(name),
m_requiredByComputed(false), m_requiredByComputed(false),
@ -293,7 +295,7 @@ using namespace Utilities;
*/ */
QJsonObject Package::basicInfo(bool includeRepoAndName) const QJsonObject Package::basicInfo(bool includeRepoAndName) const
{ {
QJsonObject info; QJsonObject info;
if(includeRepoAndName) { if(includeRepoAndName) {
if(repository()) { if(repository()) {
put(info, QStringLiteral("repo"), repository()->name()); 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. * \brief Returns full information about the package as JSON object.
*/ */
QJsonObject Package::fullInfo(bool includeRepoAndName) const QJsonObject Package::detailedInfo() const
{ {
QJsonObject info; QJsonObject info;
if(includeRepoAndName) { put(info, QStringLiteral("buildAvail"), hasBuildRelatedMetaData());
if(repository()) { put(info, QStringLiteral("srcAvail"), hasSourceRelatedMetaData());
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("idate"), installDate()); put(info, QStringLiteral("idate"), installDate());
put(info, QStringLiteral("isize"), QJsonValue(static_cast<long long int>(installedSize()))); put(info, QStringLiteral("isize"), QJsonValue(static_cast<long long int>(installedSize())));
put(info, QStringLiteral("url"), upstreamUrl()); put(info, QStringLiteral("url"), upstreamUrl());
@ -356,7 +345,7 @@ QJsonObject Package::fullInfo(bool includeRepoAndName) const
*/ */
void Package::writeToCacheStream(QDataStream &out) void Package::writeToCacheStream(QDataStream &out)
{ {
out << static_cast<qint32>(m_origin); out << static_cast<qint32>(m_origin) << m_timeStamp;
// general info // general info
out << m_hasGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses out << m_hasGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses
<< m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides << m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides
@ -389,6 +378,7 @@ void Package::restoreFromCacheStream(QDataStream &in)
// origin // origin
in >> tmp; in >> tmp;
m_origin = static_cast<PackageOrigin>(tmp); // TODO: validate value m_origin = static_cast<PackageOrigin>(tmp); // TODO: validate value
in >> m_timeStamp;
// general info // general info
in >> m_hasGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses in >> m_hasGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses
>> m_groups >> m_dependencies >> m_optionalDependencies >> m_conflicts >> m_provides >> 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<QPair<QString, QString> > &baseInfo, const QList<QPair<QString, QString> > &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. * \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. * \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. * \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; 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:
;
}
}
} }

View File

@ -64,9 +64,11 @@ class PackageVersion
{ {
public: public:
explicit PackageVersion(const QString &versionStr); explicit PackageVersion(const QString &versionStr);
explicit PackageVersion();
static PackageVersionPartComparsion compareParts(const QString &part1, const QString &part2); static PackageVersionPartComparsion compareParts(const QString &part1, const QString &part2);
PackageVersionComparsion compare(const PackageVersion &other) const; PackageVersionComparsion compare(const PackageVersion &other) const;
QString toString() const;
QString epoch; QString epoch;
QString version; QString version;
@ -76,7 +78,8 @@ public:
class Dependency class Dependency
{ {
public: 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 name;
QString version; QString version;
_alpm_depmod_t mode; _alpm_depmod_t mode;
@ -98,6 +101,7 @@ public:
// general package meta data // general package meta data
PackageOrigin origin() const; PackageOrigin origin() const;
Repository *repository() const; Repository *repository() const;
ChronoUtilities::DateTime timeStamp() const;
bool hasGeneralInfo() const; bool hasGeneralInfo() const;
const QString &name() const; const QString &name() const;
const QString &version() const; const QString &version() const;
@ -127,6 +131,7 @@ public:
const QString &buildArchitecture() const; const QString &buildArchitecture() const;
uint32 packageSize() const; uint32 packageSize() const;
const QList<Dependency> &makeDependencies() const; const QList<Dependency> &makeDependencies() const;
const QList<Dependency> &checkDependencies() const;
// installation related meta data // installation related meta data
bool hasInstallRelatedMetaData() const; bool hasInstallRelatedMetaData() const;
@ -158,12 +163,15 @@ public:
// JSON serialization // JSON serialization
QJsonObject basicInfo(bool includeRepoAndName = false) const; QJsonObject basicInfo(bool includeRepoAndName = false) const;
QJsonObject fullInfo(bool includeRepoAndName = false) const; QJsonObject detailedInfo() const;
// caching // caching
void writeToCacheStream(QDataStream &out); void writeToCacheStream(QDataStream &out);
void restoreFromCacheStream(QDataStream &in); void restoreFromCacheStream(QDataStream &in);
// parsing src/pkg info
void putInfo(const QList<QPair<QString, QString> > &baseInfo, const QList<QPair<QString, QString> > &pkgInfo);
protected: protected:
explicit Package(const QString &name, Repository *repository); explicit Package(const QString &name, Repository *repository);
virtual void writeSpecificCacheHeader(QDataStream &out); virtual void writeSpecificCacheHeader(QDataStream &out);
@ -171,6 +179,7 @@ protected:
PackageOrigin m_origin; PackageOrigin m_origin;
Repository *m_repository; Repository *m_repository;
ChronoUtilities::DateTime m_timeStamp;
// general package meta data // general package meta data
bool m_hasGeneralInfo; bool m_hasGeneralInfo;
@ -201,6 +210,7 @@ protected:
QString m_buildArchitecture; QString m_buildArchitecture;
uint32 m_packageSize; uint32 m_packageSize;
QList<Dependency> m_makeDependencies; QList<Dependency> m_makeDependencies;
QList<Dependency> m_checkDependencies;
// installation related meta data // installation related meta data
bool m_hasInstallRelatedMetaData; bool m_hasInstallRelatedMetaData;
@ -242,6 +252,14 @@ inline Repository *Package::repository() const
return m_repository; 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. * \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<Dependency> &Package::makeDependencies() const inline const QList<Dependency> &Package::makeDependencies() const
{ {
return m_makeDependencies; return m_makeDependencies;
} }
/*!
* \brief Returns dependencies required to run tests when making the package.
*/
inline const QList<Dependency> &Package::checkDependencies() const
{
return m_checkDependencies;
}
/*! /*!
* \brief Returns whether install-related meta data is available. * \brief Returns whether install-related meta data is available.
* *

View File

@ -73,8 +73,9 @@ SuggestionsReply *Repository::requestSuggestions(const QString &) const
/*! /*!
* \brief Constructs a new repository (protected since this is a pure virtual class). * \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), QObject(parent),
m_index(index),
m_name(name), m_name(name),
m_usage(static_cast<alpm_db_usage_t>(0)), m_usage(static_cast<alpm_db_usage_t>(0)),
m_sigLevel(static_cast<alpm_siglevel_t>(ALPM_SIGSTATUS_INVALID)) m_sigLevel(static_cast<alpm_siglevel_t>(ALPM_SIGSTATUS_INVALID))
@ -251,6 +252,26 @@ QFuture<void> Repository::computeRequiredBy(Manager &manager, bool forceUpdate)
return QtConcurrent::map(m_packages, ComputeRequired(manager, 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 Repository::upgradeSourcesJsonArray() const
{ {
QJsonArray sources; QJsonArray sources;
@ -303,6 +324,18 @@ QJsonArray Repository::packageNamesJsonArray() const
return names; 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 * \cond
*/ */
@ -328,18 +361,26 @@ inline void put(QJsonObject &obj, const QString &key, const QStringList &values)
/*! /*!
* \brief Returns basic information about the repository. * \brief Returns basic information about the repository.
*/ */
QJsonObject Repository::basicInfo() const QJsonObject Repository::basicInfo(bool includeName) const
{ {
QJsonObject info; QJsonObject info;
put(info, QStringLiteral("name"), name()); if(includeName) {
put(info, QStringLiteral("name"), name());
}
if(index() != invalidIndex) {
info.insert(QStringLiteral("index"), static_cast<int>(index()));
}
put(info, QStringLiteral("desc"), description()); put(info, QStringLiteral("desc"), description());
put(info, QStringLiteral("servers"), serverUrls()); put(info, QStringLiteral("servers"), serverUrls());
put(info, QStringLiteral("usage"), Utilities::usageStrings(usage())); put(info, QStringLiteral("usage"), Utilities::usageStrings(usage()));
put(info, QStringLiteral("sigLevel"), Utilities::sigLevelStrings(sigLevel())); put(info, QStringLiteral("sigLevel"), Utilities::sigLevelStrings(sigLevel()));
put(info, QStringLiteral("upgradeSources"), upgradeSourcesJsonArray()); put(info, QStringLiteral("upgradeSources"), upgradeSourcesJsonArray());
put(info, QStringLiteral("packages"), packageNamesJsonArray()); put(info, QStringLiteral("packages"), packagesObjectSkeleton());
put(info, QStringLiteral("requestRequired"), requestsRequired(PackageDetail::Basics) != PackageDetailAvailability::Immediately); if(requestsRequired(PackageDetail::Basics) == PackageDetailAvailability::Immediately) {
info.insert(QStringLiteral("packageCount"), static_cast<qint64>(m_packages.size()));
}
put(info, QStringLiteral("srcOnly"), isSourceOnly()); put(info, QStringLiteral("srcOnly"), isSourceOnly());
put(info, QStringLiteral("pkgOnly"), isPackageOnly());
return info; return info;
} }
@ -471,4 +512,100 @@ void Repository::restoreSpecificCacheHeader(QDataStream &in)
Q_UNUSED(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<QPair<QString, QString> > baseInfo;
QList<QPair<QString, QString> > 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<QString, QString>(currentFieldName, currentFieldValue);
} else {
baseInfo << QPair<QString, QString>(currentFieldName, currentFieldValue);
}
currentFieldName.clear();
currentFieldValue.clear();
break;
default:
currentFieldValue.append(c);
}
break;
}
}
if(currentPackage) {
currentPackage->putInfo(baseInfo, packageInfo);
}
}
} // namespace PackageManagement } // namespace PackageManagement

View File

@ -6,6 +6,7 @@
#include <QObject> #include <QObject>
#include <QFuture> #include <QFuture>
#include <QJsonObject>
#include <memory> #include <memory>
#include <functional> #include <functional>
@ -71,22 +72,20 @@ class SuggestionsReply : public Reply
{ {
Q_OBJECT Q_OBJECT
public: public:
SuggestionsReply(QNetworkReply *networkReply); SuggestionsReply(QNetworkReply *networkReply, const QString &term, Repository *repo);
const QJsonArray &suggestions() const; QJsonObject suggestions() const;
protected: protected:
QJsonArray m_suggestions; QString m_term;
Repository *m_repo;
}; };
inline SuggestionsReply::SuggestionsReply(QNetworkReply *networkReply) : inline SuggestionsReply::SuggestionsReply(QNetworkReply *networkReply, const QString &term, Repository *repo) :
Reply(networkReply) 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. * \brief The RepositoryType enum specifies the type of a repository object.
*/ */
@ -130,6 +129,7 @@ public:
virtual RepositoryType type() const = 0; virtual RepositoryType type() const = 0;
// general meta data // general meta data
uint32 index() const;
const QString &name() const; const QString &name() const;
const QString &description() const; const QString &description() const;
const std::map<QString, std::unique_ptr<Package> > &packages() const; const std::map<QString, std::unique_ptr<Package> > &packages() const;
@ -137,6 +137,7 @@ public:
const QStringList packageNames() const; const QStringList packageNames() const;
alpm_db_usage_t usage() const; alpm_db_usage_t usage() const;
bool isSourceOnly() const; bool isSourceOnly() const;
bool isPackageOnly() const;
std::map<QString, QList<Package *> > &groups(); std::map<QString, QList<Package *> > &groups();
const std::map<QString, QList<Package *> > &groups() const; const std::map<QString, QList<Package *> > &groups() const;
const QStringList &serverUrls() const; const QStringList &serverUrls() const;
@ -156,6 +157,7 @@ public:
QList<const Package *> packagesProviding(const Dependency &dependency) const; QList<const Package *> packagesProviding(const Dependency &dependency) const;
QList<Package *> packageByFilter(std::function<bool (const Package *)> pred); QList<Package *> packageByFilter(std::function<bool (const Package *)> pred);
QFuture<void> computeRequiredBy(Manager &manager, bool forceUpdate = false); QFuture<void> computeRequiredBy(Manager &manager, bool forceUpdate = false);
QJsonObject suggestions(const QString &term) const;
// upgrade lookup // upgrade lookup
const QList<const Repository *> &upgradeSources() const; const QList<const Repository *> &upgradeSources() const;
@ -172,7 +174,8 @@ public:
// JSON serialization // JSON serialization
QJsonArray packageNamesJsonArray() const; QJsonArray packageNamesJsonArray() const;
QJsonObject basicInfo() const; QJsonObject packagesObjectSkeleton() const;
QJsonObject basicInfo(bool includeName = false) const;
QJsonObject groupInfo() const; QJsonObject groupInfo() const;
// caching // caching
@ -183,9 +186,16 @@ public:
virtual std::unique_ptr<Package> emptyPackage(); virtual std::unique_ptr<Package> emptyPackage();
virtual void restoreSpecificCacheHeader(QDataStream &in); virtual void restoreSpecificCacheHeader(QDataStream &in);
protected: // parsing src/pkg info
explicit Repository(const QString &name, QObject *parent = nullptr); void addPackagesFromSrcInfo(const QByteArray &srcInfo);
static const uint32 invalidIndex = static_cast<uint32>(-1);
protected:
explicit Repository(const QString &name, uint32 index = invalidIndex, QObject *parent = nullptr);
protected:
uint32 m_index;
QString m_name; QString m_name;
QString m_description; QString m_description;
std::map<QString, std::unique_ptr<Package> > m_packages; std::map<QString, std::unique_ptr<Package> > m_packages;
@ -198,6 +208,24 @@ protected:
QString m_pkgDir; 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. * \brief Returns the name.
*/ */
@ -222,6 +250,14 @@ inline bool Repository::isSourceOnly() const
return requestsRequired(PackageDetail::PackageInfo) == PackageDetailAvailability::Never; 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. * \brief Returns the packages.
*/ */

View File

@ -195,7 +195,7 @@ void BuildOrderResolver::printResults(const QStringList &results)
void BuildOrderResolver::addDeps(QList<TaskInfo *> &tasks, TaskInfo *task) const void BuildOrderResolver::addDeps(QList<TaskInfo *> &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 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()); addDeps(tasks, task, pkg->dependencies());
} else { } else {

View File

@ -0,0 +1,56 @@
#include "./suggestionslookup.h"
#include "./manager.h"
#include "./repository.h"
#include <QJsonObject>
#include <QJsonArray>
#include <assert.h>
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<SuggestionsReply *>(sender());
m_results << reply->suggestions();
reply->deleteLater();
if(!--m_remainingReplies) {
emit resultsAvailable(QStringLiteral("suggestions"), m_id, m_results);
deleteLater();
}
}
} // namespace RepoIndex

51
alpm/suggestionslookup.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef REPOINDEX_SUGGESTIONSLOOKUP_H
#define REPOINDEX_SUGGESTIONSLOOKUP_H
#include <QObject>
#include <QJsonArray>
#include <QJsonValue>
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

View File

@ -26,7 +26,11 @@ using namespace Utilities;
QJsonObject UpgradeResult::json() const QJsonObject UpgradeResult::json() const
{ {
QJsonObject obj; 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); obj.insert(QStringLiteral("curVer"), currentVersion);
return obj; return obj;
} }
@ -194,10 +198,6 @@ UpgradeLookupJson::UpgradeLookupJson(const Manager &manager, const QJsonObject &
} else { } else {
m_errorsArray << QStringLiteral("Repository \"%1\" can not be found.").arg(toCheckName); 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(); deleteLater();
} }
@ -231,7 +231,13 @@ void UpgradeLookupJson::processFinished()
if(--m_remainingProcesses == 0) { if(--m_remainingProcesses == 0) {
// finally make info for orphanded packages // finally make info for orphanded packages
for(const auto *pkg : m_orphanedPackages) { 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 // add results to results QJsonObject
QJsonObject results; QJsonObject results;
@ -367,7 +373,7 @@ void UpgradeLookupCli::printResults()
Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray); Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray);
} }
} }
emit finished(); QCoreApplication::exit();
} }
} // namespace PackageManagement } // namespace PackageManagement

View File

@ -135,6 +135,7 @@ class UpgradeLookupJson : public UpgradeLookup
Q_OBJECT Q_OBJECT
public: public:
explicit UpgradeLookupJson(const Manager &manager, const QJsonObject &request, QObject *parent = nullptr); explicit UpgradeLookupJson(const Manager &manager, const QJsonObject &request, QObject *parent = nullptr);
const QJsonArray &errors() const;
signals: signals:
void resultsAvailable(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value); void resultsAvailable(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value);
@ -152,6 +153,11 @@ private:
QJsonArray m_orphanedPackagesArray; QJsonArray m_orphanedPackagesArray;
}; };
inline const QJsonArray &UpgradeLookupJson::errors() const
{
return m_errorsArray;
}
class UpgradeLookupCli : public UpgradeLookup class UpgradeLookupCli : public UpgradeLookup
{ {
Q_OBJECT Q_OBJECT
@ -160,9 +166,6 @@ public:
explicit UpgradeLookupCli(const Manager &manager, const std::string &repo, QObject *parent = nullptr); explicit UpgradeLookupCli(const Manager &manager, const std::string &repo, QObject *parent = nullptr);
const Repository *toCheck() const; const Repository *toCheck() const;
signals:
void finished();
private slots: private slots:
void processFinished(); void processFinished();

View File

@ -3,6 +3,7 @@
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QStringList> #include <QStringList>
#include <QFileInfo>
#include <iostream> #include <iostream>

View File

@ -1,5 +1,3 @@
# template
TEMPLATE = lib
#dirs #dirs
UI_DIR = ./gui UI_DIR = ./gui
MOC_DIR = ./moc MOC_DIR = ./moc
@ -75,7 +73,11 @@ guiqtwidgets {
DEFINES += GUI_QTWIDGETS DEFINES += GUI_QTWIDGETS
DEFINES += MODEL_UNDO_SUPPORT 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 { mingw-w64-manualstrip-dll {
QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET); \ QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET); \
$${CROSS_COMPILE}strip --strip-unneeded ./release/lib$(TARGET).a $${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) QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET)
} }
mingw-w64-noversion { mingw-w64-noversion {
VERSION = "" TARGET_EXT = ".dll"
TARGET_VERSION_EXT = ""
CONFIG += skip_target_version_ext
} }

View File

@ -52,10 +52,11 @@ int main(int argc, char *argv[])
QCoreApplication application(argc, argv); QCoreApplication application(argc, argv);
// setup manager // setup manager
Manager manager(config); Manager manager(config);
manager.applyPacmanConfig();
manager.applyRepoIndexConfig();
cerr << shchar << "Loading databases ..." << endl; cerr << shchar << "Loading databases ..." << endl;
manager.registerDataBasesFromPacmanConfig();
manager.registerDatabasesFromRepoIndexConfig();
manager.initAlpmDataBases(configArgs.serverArg.isPresent()); manager.initAlpmDataBases(configArgs.serverArg.isPresent());
cerr << shchar << "Restoring cache ..." << endl;
manager.restoreCache(); manager.restoreCache();
if(configArgs.serverArg.isPresent()) { if(configArgs.serverArg.isPresent()) {
// setup the server // setup the server
@ -67,13 +68,13 @@ int main(int argc, char *argv[])
BuildOrderResolver resolver(manager); BuildOrderResolver resolver(manager);
BuildOrderResolver::printResults(resolver.resolve(configArgs.buildOrderArg.values())); BuildOrderResolver::printResults(resolver.resolve(configArgs.buildOrderArg.values()));
} else if(configArgs.mingwBundleArg.isPresent()) { } 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("."), bundle.createBundle(configArgs.targetDirArg.isPresent() ? configArgs.targetDirArg.values().front() : string("."),
configArgs.targetNameArg.values().front(), 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()) { } else if(configArgs.upgradeLookupArg.isPresent()) {
UpgradeLookupCli upgradeLookup(manager, configArgs.upgradeLookupArg.values().front()); UpgradeLookupCli upgradeLookup(manager, configArgs.upgradeLookupArg.values().front());
QObject::connect(&upgradeLookup, &UpgradeLookupCli::finished, &application, &QCoreApplication::quit);
return application.exec(); return application.exec();
} }
} else if(!configArgs.helpArg.isPresent()) { } else if(!configArgs.helpArg.isPresent()) {

View File

@ -2,6 +2,7 @@
#include "../alpm/manager.h" #include "../alpm/manager.h"
#include "../alpm/upgradelookup.h" #include "../alpm/upgradelookup.h"
#include "../alpm/suggestionslookup.h"
#include "../alpm/config.h" #include "../alpm/config.h"
#include <QJsonDocument> #include <QJsonDocument>
@ -16,7 +17,7 @@ using namespace std;
namespace RepoIndex { namespace RepoIndex {
Connection::Connection(const Manager &alpmManager, QWebSocket *socket, QObject *parent) : Connection::Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent) :
QObject(parent), QObject(parent),
m_socket(socket), m_socket(socket),
m_manager(alpmManager), m_manager(alpmManager),
@ -75,16 +76,37 @@ void Connection::handleQuery(const QJsonObject &obj)
const auto id = obj.value(QStringLiteral("id")); const auto id = obj.value(QStringLiteral("id"));
if(what == QLatin1String("basicrepoinfo")) { if(what == QLatin1String("basicrepoinfo")) {
m_repoInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_repoInfoUpdatesRequested); 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")) { } 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")) { } 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<Manager::PackageInfoPart>(static_cast<int>(Manager::Basics) | static_cast<int>(Manager::Details))));
} else if(what == QLatin1String("groupinfo")) { } else if(what == QLatin1String("groupinfo")) {
m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested); m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested);
sendResults(what, id, m_manager.groupInfo()); 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")) { } 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")) { } else if(what == QLatin1String("ping")) {
sendResult(what, id, QStringLiteral("pong")); sendResult(what, id, QStringLiteral("pong"));
} else { } else {
@ -103,6 +125,17 @@ void Connection::handleCmd(const QJsonObject &obj)
} else { } else {
sendError(QStringLiteral("rejected"), id); 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 { } else {
sendError(QStringLiteral("unknown command"), id); sendError(QStringLiteral("unknown command"), id);
} }

View File

@ -17,7 +17,7 @@ class Connection : public QObject
Q_OBJECT Q_OBJECT
public: public:
Connection(const RepoIndex::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr); Connection(RepoIndex::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr);
private slots: private slots:
void processTextMessage(const QString &message); void processTextMessage(const QString &message);
@ -33,7 +33,7 @@ private:
void handleCmd(const QJsonObject &obj); void handleCmd(const QJsonObject &obj);
QWebSocket *m_socket; QWebSocket *m_socket;
const RepoIndex::Manager &m_manager; RepoIndex::Manager &m_manager;
bool m_repoInfoUpdatesRequested; bool m_repoInfoUpdatesRequested;
bool m_groupInfoUpdatesRequested; bool m_groupInfoUpdatesRequested;

View File

@ -14,7 +14,7 @@ using namespace std;
namespace RepoIndex { 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), QObject(parent),
m_server(new QWebSocketServer(QStringLiteral("Repository index server"), m_server(new QWebSocketServer(QStringLiteral("Repository index server"),
config.serverInsecure() ? QWebSocketServer::NonSecureMode : QWebSocketServer::SecureMode, config.serverInsecure() ? QWebSocketServer::NonSecureMode : QWebSocketServer::SecureMode,

View File

@ -22,7 +22,7 @@ class Server : public QObject
Q_OBJECT Q_OBJECT
public: 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(); ~Server();
signals: signals:
@ -34,7 +34,7 @@ private slots:
private: private:
QWebSocketServer *m_server; QWebSocketServer *m_server;
const RepoIndex::Manager &m_alpmManager; RepoIndex::Manager &m_alpmManager;
}; };
} }

View File

@ -72,8 +72,8 @@ void AurFullPackageReply::processData()
// TODO // TODO
} }
AurSuggestionsReply::AurSuggestionsReply(QNetworkReply *networkReply) : AurSuggestionsReply::AurSuggestionsReply(QNetworkReply *networkReply, const QString &term, UserRepository *repo) :
SuggestionsReply(networkReply) SuggestionsReply(networkReply, term, repo)
{} {}
void AurSuggestionsReply::processData() void AurSuggestionsReply::processData()
@ -86,7 +86,16 @@ void AurSuggestionsReply::processData()
//const QJsonDocument doc = QJsonDocument::fromJson(data, &error); //const QJsonDocument doc = QJsonDocument::fromJson(data, &error);
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error); const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
if(error.error == QJsonParseError::NoError) { 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<AurPackage>(suggestionString, static_cast<UserRepository *>(m_repo));
}
}
}
} else { } else {
m_error = QStringLiteral("Error: Unable to parse JSON received from AUR: ") % error.errorString() % QStringLiteral(" at character ") % QString::number(error.offset); 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) : UserRepository::UserRepository(QNetworkAccessManager &networkAccessManager, QObject *parent) :
Repository(QStringLiteral("AUR"), parent), Repository(QStringLiteral("AUR"), invalidIndex, parent),
m_networkAccessManager(networkAccessManager) m_networkAccessManager(networkAccessManager)
{ {
m_description = QStringLiteral("Arch User Repository"); 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; size_t remainingSuggestions = 20;
QUrlQuery query; for(auto i = packages().lower_bound(term), end = packages().cend(); i != end && remainingSuggestions && i->first.startsWith(term, Qt::CaseInsensitive); ++i, --remainingSuggestions);
query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeSuggest); if(remainingSuggestions) {
query.addQueryItem(rpcArgKey, phrase); auto url = m_aurRpcUrl;
url.setQuery(query); QUrlQuery query;
return new AurSuggestionsReply(m_networkAccessManager.get(QNetworkRequest(url))); query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeSuggest);
query.addQueryItem(rpcArgKey, term);
url.setQuery(query);
return new AurSuggestionsReply(m_networkAccessManager.get(QNetworkRequest(url)), term, const_cast<UserRepository *>(this));
} else {
return nullptr;
}
} }
AurPackageReply *UserRepository::requestPackageInfo(const QStringList &packageNames, bool forceUpdate) const AurPackageReply *UserRepository::requestPackageInfo(const QStringList &packageNames, bool forceUpdate) const

View File

@ -47,7 +47,7 @@ class AurSuggestionsReply : public SuggestionsReply
{ {
Q_OBJECT Q_OBJECT
public: public:
AurSuggestionsReply(QNetworkReply *networkReply); AurSuggestionsReply(QNetworkReply *networkReply, const QString &term, UserRepository *repo);
private slots: private slots:
void processData(); void processData();
@ -65,7 +65,7 @@ public:
static void setAurRpcUrl(const QUrl &aurRpcUrl); static void setAurRpcUrl(const QUrl &aurRpcUrl);
PackageDetailAvailability requestsRequired(PackageDetail packageDetail) const; 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; AurPackageReply *requestPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const;
AurFullPackageReply *requestFullPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; AurFullPackageReply *requestFullPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const;

View File

@ -2,6 +2,7 @@ projectname = repoindex
appname = "Repository Index" appname = "Repository Index"
appauthor = Martchus appauthor = Martchus
appurl = "https://github.com/$${appauthor}/$${projectname}" appurl = "https://github.com/$${appauthor}/$${projectname}"
QMAKE_TARGET_DESCRIPTION = "Provides a web interface to browse Arch Linux package repositories."
VERSION = 1.0.0 VERSION = 1.0.0
# include ../../common.pri when building as part of a subdirs project; otherwise include general.pri # 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/aurpackage.cpp \
alpm/alpmdatabase.cpp \ alpm/alpmdatabase.cpp \
alpm/repository.cpp \ alpm/repository.cpp \
alpm/upgradelookup.cpp alpm/upgradelookup.cpp \
alpm/suggestionslookup.cpp
HEADERS += \ HEADERS += \
alpm/manager.h \ alpm/manager.h \
@ -50,7 +52,8 @@ HEADERS += \
alpm/aurpackage.h \ alpm/aurpackage.h \
alpm/alpmdatabase.h \ alpm/alpmdatabase.h \
alpm/repository.h \ alpm/repository.h \
alpm/upgradelookup.h alpm/upgradelookup.h \
alpm/suggestionslookup.h
DISTFILES += \ DISTFILES += \
README.md \ README.md \

View File

@ -69,7 +69,15 @@
</div> </div>
</form> </form>
<form class="navbar-form navbar-right"> <form class="navbar-form navbar-right">
<button id="nav_stop_server" class="btn btn-danger" onclick="repoindex.client.stopServer();">Stop server</button> <div class="btn-group" id="nav_server">
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Server <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#" onclick="repoindex.client.stopServer(); return false;">Stop</a></li>
<li><a href="#" onclick="repoindex.client.reinitAlpm(); return false;">Reinit ALPM</a></li>
</ul>
</div>
<button id="nav_connect" class="btn btn-danger" onclick="repoindex.client.init();"><span class="glyphicon glyphicon glyphicon-refresh" aria-hidden="true" id="connection_glyphicon"></span> <span id="connection_status">Disconnected</span></button> <button id="nav_connect" class="btn btn-danger" onclick="repoindex.client.init();"><span class="glyphicon glyphicon glyphicon-refresh" aria-hidden="true" id="connection_glyphicon"></span> <span id="connection_status">Disconnected</span></button>
</form> </form>
</div><!-- /.navbar-collapse --> </div><!-- /.navbar-collapse -->
@ -96,8 +104,8 @@
</button> </button>
<ul class="dropdown-menu" aria-labelledby="repo_buttons_settings_dropdown"> <ul class="dropdown-menu" aria-labelledby="repo_buttons_settings_dropdown">
<li><input type="checkbox" name="reposButtonsExclusive" id="repos_buttons_exclusive"><label for="repos_buttons_exclusive">Exclusive selection</label></li> <li><input type="checkbox" name="reposButtonsExclusive" id="repos_buttons_exclusive"><label for="repos_buttons_exclusive">Exclusive selection</label></li>
<li><a href="#" onclick="repoindex.pageManager.repoManager.buttonContainerExclusiveButton.checked = false; repoindex.pageManager.repoManager.upgradeEnabledAll(true); return false;">Select all</a></li> <li><a href="#" onclick="repoindex.pageManager.repoManager.buttonContainerExclusiveButton.checked = false; repoindex.pageManager.repoManager.updateEnabledAll(true); return false;">Select all</a></li>
<li><a href="#" onclick="repoindex.pageManager.repoManager.upgradeEnabledAll(false); return false;">Remove selection</a></li> <li><a href="#" onclick="repoindex.pageManager.repoManager.updateEnabledAll(false); return false;">Remove selection</a></li>
</ul> </ul>
</span> </span>
</form> </form>

View File

@ -13,16 +13,15 @@
BasicPackageInfo: "basicpkginfo", BasicPackageInfo: "basicpkginfo",
FullPackageInfo: "fullpkginfo", FullPackageInfo: "fullpkginfo",
GroupInfo: "groupinfo", GroupInfo: "groupinfo",
UpgradeLookup: "upgradelookup" UpgradeLookup: "upgradelookup",
Suggestions: "suggestions"
}; };
repoindex.isLoopback = function(domain) { repoindex.isLoopback = function(domain) {
return domain === "localhost" || domain === "127.0.0.1" || domain === "::1"; return domain === "localhost" || domain === "127.0.0.1" || domain === "::1";
}; };
// responsible for the connection to the server; holds all repo/package information returned by the server
// responsible for the connection to the server
var Client = function(url) { var Client = function(url) {
// basic initialization // basic initialization
this.url = url; this.url = url;
@ -191,10 +190,10 @@
this.useBasicRepoInfo(values); this.useBasicRepoInfo(values);
break; break;
case repoindex.RequestType.BasicPackageInfo: case repoindex.RequestType.BasicPackageInfo:
// the info is used via the registred callbacks only this.usePackageInfo(values);
break; break;
case repoindex.RequestType.FullPackageInfo: case repoindex.RequestType.FullPackageInfo:
// the info is used via the registred callbacks only this.usePackageInfo(values);
break; break;
case repoindex.RequestType.GroupInfo: case repoindex.RequestType.GroupInfo:
this.useGroupInfo(values); this.useGroupInfo(values);
@ -202,6 +201,9 @@
case repoindex.RequestType.UpgradeLookup: case repoindex.RequestType.UpgradeLookup:
// the info is used via the registred callbacks only // the info is used via the registred callbacks only
break; break;
case repoindex.RequestType.Suggestions:
this.useSuggestions(values);
break;
default: default:
pageManager.addError("Server replied unknown results: " + what); pageManager.addError("Server replied unknown results: " + what);
return; // don't invoke callbacks when results are of unknown type return; // don't invoke callbacks when results are of unknown type
@ -233,14 +235,37 @@
this.scheduleRequest(repoindex.RequestType.GroupInfo, {upgrades: "true"}, callback); 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()) { if(this.isOpen()) {
this.socket.send(JSON.stringify({class: "cmd", what: "stop"})); this.socket.send(JSON.stringify({class: "cmd", what: cmd}));
} else { } else {
repoindex.pageManager.addError("Not connected to a server."); 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) { this.checkForUpgrades = function(dbName, syncdbNames, callback) {
var params = { var params = {
db: dbName db: dbName
@ -251,29 +276,102 @@
this.scheduleRequest(repoindex.RequestType.UpgradeLookup, params, callback); 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 // define functions to use differend kinds of results
this.useBasicRepoInfo = function(values) { this.useBasicRepoInfo = function(value) {
if(!values) { if(!value) {
repoindex.pageManager.addError("Server replied insufficiant basic repo info."); repoindex.pageManager.addError("Server replied insufficiant basic repo info.");
return false; return false;
} }
// upgrade repos and package list // upgrade repos and package list
this.repos = {}; this.repos = value;
var repoMgr = repoindex.pageManager.repoManager; var repoMgr = repoindex.pageManager.repoManager;
var pkgMgr = repoindex.pageManager.packageManager; var pkgMgr = repoindex.pageManager.packageManager;
repoMgr.removeEntries(); repoMgr.removeEntries();
pkgMgr.removeEntries(); pkgMgr.removeEntries();
for(var i1 = 0; i1 < values.length; ++i1) { var reposInOrder = [];
this.repos[values[i1].name] = values[i1]; for(var repoName in value) {
var repoEntry = repoMgr.addEntry(values[i1]); if(value.hasOwnProperty(repoName)) {
var packages = repoEntry.info.packages; reposInOrder.push({name: repoName, info: value[repoName]});
for(var i2 = 0; i2 < packages.length; ++i2) { //var repoInfo = value[repoName];
pkgMgr.addEntry(repoEntry, packages[i2]);
} }
} }
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; this.hasBasicRepoInfo = true;
pkgMgr.invalidate(); pkgMgr.invalidate();
repoMgr.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) { this.useGroupInfo = function(values) {
@ -293,13 +391,80 @@
groupMgr.useRequestedData(); 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 // create a global client used within the entire document
repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + document.domain + ":1234"); repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + document.domain + ":1234");
if(!repoindex.isLoopback(document.domain)) { if(!repoindex.isLoopback(document.domain)) {
// hide stop button if server is not on loopback interface // 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; return repoindex;

View File

@ -22,7 +22,7 @@
this.appendChild(cellElement); this.appendChild(cellElement);
}; };
this.initTableRow = function() {}; this.initTableRow = function() {};
this.upgradeTableRow = function() { this.updateTableRow = function() {
this.rowElement.wipeChildren(); this.rowElement.wipeChildren();
this.initTableRow(); this.initTableRow();
}; };
@ -43,7 +43,7 @@
this.rowElement.style.display = "table-row"; this.rowElement.style.display = "table-row";
}; };
this.upgradeEnabled = function() {}; this.updateEnabled = function() {};
}; };
repoindex.EntryManager = function(EntryType, entryContainer, pagination) { repoindex.EntryManager = function(EntryType, entryContainer, pagination) {
@ -52,7 +52,7 @@
this.entryContainer = entryContainer; this.entryContainer = entryContainer;
this.entries = []; this.entries = [];
this.pageName = undefined; this.pageName = undefined;
this.upgradeRequired = false; this.updateRequired = false;
// init pagination // init pagination
if((this.pagination = pagination)) { if((this.pagination = pagination)) {
@ -79,7 +79,7 @@
// define default filter predicate // define default filter predicate
this.filterPred = function(entry) { 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)); && (!this.filterRepos || this.filterRepos.contains(entry.info.repo));
}; };
@ -96,7 +96,7 @@
//} //}
// upgrade pagination (the pageSelected method defined above will be invoked here automatically) // upgrade pagination (the pageSelected method defined above will be invoked here automatically)
this.pagination.entryCount = this.filteredEntries.length; this.pagination.entryCount = this.filteredEntries.length;
this.pagination.upgrade(); this.pagination.update();
}; };
this.removeFilter = function() { this.removeFilter = function() {
@ -108,15 +108,15 @@
this.refreshInfo = function() {}; this.refreshInfo = function() {};
this.invalidate = function() { this.invalidate = function() {
this.upgradeRequired = true; this.updateRequired = true;
this.upgrade(); this.update();
}; };
this.upgrade = function() { this.update = function() {
if(this.upgradeRequired && (!this.pageName || this.pageName === repoindex.pageManager.currentPage)) { if(this.updateRequired && (!this.pageName || this.pageName === repoindex.pageManager.currentPage)) {
this.applyFilter(); this.applyFilter();
this.infoBox.innerHTML = this.infoText(); this.infoBox.innerHTML = this.infoText();
this.upgradeRequired = false; this.updateRequired = false;
} }
}; };
@ -126,9 +126,9 @@
this.entryContainer.wipeChildren(); this.entryContainer.wipeChildren();
}; };
this.upgradeEnabledForAll = function(enabled) { this.updateEnabledForAll = function(enabled) {
for(var i = 0; i < this.entries.length; ++i) { 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) { if(this.customSelection) {
for(var i = 0; i < this.customSelection.length; ++i) { for(var i = 0; i < this.customSelection.length; ++i) {
var entry = this.customSelection[i]; var entry = this.customSelection[i];
if(entry.info && entry.info.name === entryName) { if(entry.name === entryName) {
return entry; return entry;
} }
} }
} else { } else {
for(var i = 0; i < this.entries.length; ++i) { for(var i = 0; i < this.entries.length; ++i) {
var entry = this.entries[i]; var entry = this.entries[i];
if(entry.info && entry.info.name === entryName) { if(entry.name === entryName) {
return entry; return entry;
} }
} }

View File

@ -3,11 +3,11 @@
var GroupEntry = {}; var GroupEntry = {};
GroupEntry.prototype = new repoindex.Entry(); GroupEntry.prototype = new repoindex.Entry();
GroupEntry.prototype.constructor = GroupEntry; GroupEntry.prototype.constructor = GroupEntry;
GroupEntry = function(groupInfo) { GroupEntry = function(groupName, groupInfo) {
repoindex.Entry.prototype.constructor.call(this, groupInfo); repoindex.Entry.prototype.constructor.call(this, groupName, groupInfo);
this.initTableRow = function() { this.initTableRow = function() {
this.rowElement.addCell(this.info.name); this.rowElement.addCell(this.name);
this.rowElement.addCell(this.info.repo); this.rowElement.addCell(this.info.repo);
var packagesCellElement = document.createElement("td"); var packagesCellElement = document.createElement("td");
repoindex.setPackageNames(packagesCellElement, this.info.packages); repoindex.setPackageNames(packagesCellElement, this.info.packages);
@ -30,13 +30,13 @@
this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity; this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity;
this.addEntry = function(repoName, groupName, packages) { this.addEntry = function(repoName, groupName, packages) {
var groupInfo = { var entry = new GroupEntry(groupName, {
index: this.entries.length,
repo: repoName, repo: repoName,
name: groupName, name: groupName,
packages: packages packages: packages
}; });
this.entries.push(new GroupEntry(groupInfo)); entry.index = this.entries.length;
this.entries.push(entry);
}; };
// handle a page selection // handle a page selection

View File

@ -8,67 +8,30 @@
var PackageEntry = {}; var PackageEntry = {};
PackageEntry.prototype = new repoindex.Entry(); PackageEntry.prototype = new repoindex.Entry();
PackageEntry.prototype.constructor = PackageEntry; PackageEntry.prototype.constructor = PackageEntry;
PackageEntry = function(repoEntry, packageInfo, color) { PackageEntry = function(repoEntry, packageName, packageInfo, color) {
this.repoEntry = repoEntry; // might be undefined this.repoEntry = repoEntry; // might be undefined
repoindex.Entry.prototype.constructor.call(this, packageInfo); repoindex.Entry.prototype.constructor.call(this, packageName, packageInfo);
// init row element // init row element
if(color) { if(color) {
this.rowElement.style.backgroundColor = color; this.rowElement.style.backgroundColor = color;
} }
this.rowElement.onclick = function() { this.rowElement.onclick = function() {
repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.info.index); repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.index);
}; };
this.initTableRow = function() { this.initTableRow = function() {
var basics = this.info.basics ? this.info.basics : {};
var srcOnly = this.repoEntry && this.repoEntry.info.srcOnly; 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) { for(var i = 0; i < 7; ++i) {
this.rowElement.addCell(repoindex.makeStr(values[i])); this.rowElement.addCell(repoindex.makeStr(values[i]));
} }
}; };
this.initTableRow(); 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 = {}; repoindex.PackageEntryManager = {};
@ -82,18 +45,16 @@
this.containerNamePlural = "repositories"; this.containerNamePlural = "repositories";
this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity; this.getContainerQuantity = repoindex.entryManagerGetRepoQuantity;
this.createCustomEntry = function(color) { this.createCustomEntry = function(repoEntry, packageName, packageInfo, color) {
return new PackageEntry(undefined, {}, color); return new PackageEntry(repoEntry, packageName, packageInfo, color);
}; };
this.addEntry = function(repoEntry, packageName) { this.addEntry = function(repoEntry, packageName, packageInfo) {
var packageInfo = { packageInfo.repo = repoEntry.name;
index: this.entries.length, packageInfo.name = packageName;
repo: repoEntry.info.name, packageInfo.received = false;
name: packageName, var entry = new PackageEntry(repoEntry, packageName, packageInfo);
received: false entry.index = this.entries.length;
};
var entry = new PackageEntry(repoEntry, packageInfo);
this.entries.push(entry); this.entries.push(entry);
return entry; return entry;
}; };
@ -121,24 +82,13 @@
entriesRequired = true; entriesRequired = true;
} }
}, mgr.filteredEntries.length); }, mgr.filteredEntries.length);
var updateTableRows = function() {
pageElement.forRange(function(i) {
mgr.filteredEntries[i].updateTableRow();
}, mgr.filteredEntries.length);
};
if(entriesRequired) { if(entriesRequired) {
var pkgEntries = repoindex.pageManager.packageManager.relevantEntries(); repoindex.client.requestBasicPackagesInfo(packageSelection, updateTableRows);
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);
} }
} }
}; };
@ -170,80 +120,56 @@
// check whether specified entry index is valid // check whether specified entry index is valid
var entry = this.entryByIndex(entryIndex); var entry = this.entryByIndex(entryIndex);
if(entry) { if(entry) {
var i = entry.info;
// show properties in "Package info" box // show properties in "Package info" box
var setProperties = function() { var setProperties = function() {
// -> basic package info // -> basic package info
repoindex.setPackageNames("pkg_name", [i.name]); var basics = entry.info.basics ? entry.info.basics : {};
repoindex.setText("pkg_repo", repoindex.makeStr(i.repo)); repoindex.setPackageNames("pkg_name", [entry.name]);
repoindex.setText("pkg_ver", repoindex.makeStr(i.ver)); repoindex.setText("pkg_repo", repoindex.makeStr(entry.info.repo));
repoindex.setText("pkg_desc", repoindex.makeStr(i.desc)); repoindex.setText("pkg_ver", repoindex.makeStr(basics.ver));
repoindex.setText("pkg_arch", repoindex.makeStr(i.arch)); repoindex.setText("pkg_desc", repoindex.makeStr(basics.desc));
repoindex.setText("pkg_bdate", repoindex.makeStr(i.bdate)); repoindex.setText("pkg_arch", repoindex.makeStr(basics.arch));
repoindex.setText("pkg_bdate", repoindex.makeStr(basics.bdate));
// -> full package info // -> full package info
if(i.url) { var details = entry.info.details ? entry.info.details : {};
repoindex.setLink("pkg_url", i.url, i.url, window.open); if(details.url) {
repoindex.setLink("pkg_url", details.url, details.url, window.open);
} else { } else {
repoindex.setText("pkg_url", "unknown"); repoindex.setText("pkg_url", "unknown");
} }
repoindex.setText("pkg_lic", repoindex.makeArray(i.lic)); repoindex.setText("pkg_lic", repoindex.makeArray(details.lic));
repoindex.setPackageNames("pkg_grp", repoindex.pack(i.grp), repoindex.Pages.Groups); repoindex.setPackageNames("pkg_grp", repoindex.pack(details.grp), repoindex.Pages.Groups);
repoindex.setPackageNames("pkg_prov", repoindex.pkgNamesFromDeps(i.prov)); repoindex.setPackageNames("pkg_prov", repoindex.pkgNamesFromDeps(details.prov));
repoindex.setPackageNames("pkg_deps", repoindex.pkgNamesFromDeps(i.deps)); repoindex.setPackageNames("pkg_deps", repoindex.pkgNamesFromDeps(details.deps));
repoindex.setPackageNames("pkg_optd", repoindex.pkgNamesFromDeps(i.optd)); repoindex.setPackageNames("pkg_optd", repoindex.pkgNamesFromDeps(details.optd));
repoindex.setPackageNames("pkg_requ", repoindex.pack(i.requ)); repoindex.setPackageNames("pkg_requ", repoindex.pack(details.requ));
repoindex.setPackageNames("pkg_conf", repoindex.pkgNamesFromDeps(i.conf)); repoindex.setPackageNames("pkg_conf", repoindex.pkgNamesFromDeps(details.conf));
repoindex.setPackageNames("pkg_repl", repoindex.pkgNamesFromDeps(i.repl)); repoindex.setPackageNames("pkg_repl", repoindex.pkgNamesFromDeps(details.repl));
repoindex.setText("pkg_isize", repoindex.makeDataSize(i.isize)); repoindex.setText("pkg_isize", repoindex.makeDataSize(details.isize));
repoindex.setText("pkg_pack", repoindex.makeStr(i.pack)); repoindex.setText("pkg_pack", repoindex.makeStr(details.pack));
repoindex.setText("pkg_idate", repoindex.makeStr(i.idate)); repoindex.setText("pkg_idate", repoindex.makeStr(details.idate));
repoindex.setText("pkg_expl", i.repo === "local" ? (i.expl ? "explicitly installed" : "installed as dependency") : "-"); repoindex.setText("pkg_expl", entry.info.repo === "local" ? (details.expl ? "explicitly installed" : "installed as dependency") : "n/a");
repoindex.setText("pkg_scri", repoindex.makeBool(i.scri)); repoindex.setText("pkg_scri", repoindex.makeBool(details.scri));
repoindex.setText("pkg_sig", repoindex.makeArray(i.sig)); repoindex.setText("pkg_sig", repoindex.makeArray(details.sig));
repoindex.setTree("pkg_files", repoindex.makeTree(i.files)); repoindex.setTree("pkg_files", repoindex.makeTree(details.files));
// -> upgrade download buttons // -> 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.setDownloadButton("pkg_down", "package", repoindex.makeHash(repoindex.Pages.Packages, downloadPkgParams, true), function() {
repoindex.pageManager.denoteHash(repoindex.Pages.Packages, downloadPkgParams); repoindex.pageManager.denoteHash(repoindex.Pages.Packages, downloadPkgParams);
repoindex.pageManager.packageManager.showMirrorsForIndex(entryIndex); 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)); repoindex.setDownloadButton("src_down", "source", repoindex.makeHash(repoindex.Pages.Packages, downloadSrcParams, true));
}; };
setProperties(); setProperties();
// use full package info (callback) if(!entry.info.basics || !entry.info.details) {
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) {
// don't have the full package info yet -> request full package info // don't have the full package info yet -> request full package info
var packageSelection = {}; var packageSelection = {};
packageSelection[i.repo] = [{index: entryIndex, name: i.name}]; packageSelection[entry.info.repo] = [{index: entryIndex, name: entry.name}];
repoindex.client.requestFullPackagesInfo(packageSelection, useFullPackageInfo); repoindex.client.requestFullPackagesInfo(packageSelection, setProperties);
} }
// set currentInfo (the pageManager needs this value to upgrade the hash) // 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 // ensures, that the "Package Info" box (with the properties just set) is shown
repoindex.pageManager.showPackageInfo(true); repoindex.pageManager.showPackageInfo(true);
} }
@ -252,23 +178,22 @@
this.showMirrors = function(repo, name) { this.showMirrors = function(repo, name) {
var determineEntry = function() { var determineEntry = function() {
var res = repoindex.pageManager.packageManager.entries.filter(function(entry) { 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? // entry exists?
if(res.length > 0) { if(res.length > 0) {
// yes -> full package info available? // yes -> full package info available?
var i = res[0].info; var entry = res[0];
var showEntry = function() { 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 // yes -> show entry instantly
showEntry(); showEntry();
} else { } else {
// no -> request full info, use callback to show entry when info becomes available // 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 = {}; 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); repoindex.client.requestFullPackagesInfo(packageSelection, showEntry);
} }
} else { } else {
@ -290,11 +215,11 @@
var entry = this.entryByIndex(entryIndex); var entry = this.entryByIndex(entryIndex);
if(entry) { if(entry) {
var info = entry.info; var info = entry.info;
repoindex.setText("title_mirror_selection", "Mirrors for package <i>" + repoindex.escapeHtml(info.name) + "</i>", true); repoindex.setText("title_mirror_selection", "Mirrors for package <i>" + repoindex.escapeHtml(entry.name) + "</i>", true);
var listMirrorSelection = document.getElementById("list_mirror_selection"); var listMirrorSelection = document.getElementById("list_mirror_selection");
listMirrorSelection.wipeChildren(); listMirrorSelection.wipeChildren();
var repoEntries = repoindex.pageManager.repoManager.entries.filter(function(entry) { var repoEntries = repoindex.pageManager.repoManager.entries.filter(function(entry) {
return entry.info.name === info.repo; return entry.name === info.repo;
}); });
var mirrorsAvailable = 0; var mirrorsAvailable = 0;
if(repoEntries.length > 0) { if(repoEntries.length > 0) {
@ -303,7 +228,7 @@
for(var i = 0; i < mirrors.length; ++i) { for(var i = 0; i < mirrors.length; ++i) {
var liElement = document.createElement("li"); var liElement = document.createElement("li");
var aElement = document.createElement("a"); var aElement = document.createElement("a");
var url = mirrors[i] + "/" + info.file; var url = mirrors[i] + "/" + info.details.file;
aElement.appendChild(document.createTextNode(url)); aElement.appendChild(document.createTextNode(url));
aElement.href = url; aElement.href = url;
aElement.onclick = function() { aElement.onclick = function() {

View File

@ -126,7 +126,7 @@
switch(pageName) { switch(pageName) {
case repoindex.Pages.Packages: case repoindex.Pages.Packages:
this.repoManager.buttonRow.style.display = "block"; this.repoManager.buttonRow.style.display = "block";
this.packageManager.upgrade(); this.packageManager.update();
if(params && params.repo && params.pkg) { if(params && params.repo && params.pkg) {
if(params.down) { if(params.down) {
switch(params.down) { switch(params.down) {
@ -149,7 +149,7 @@
if(!repoindex.client.hasGroupInfo) { if(!repoindex.client.hasGroupInfo) {
repoindex.client.requestGroupInfo(); repoindex.client.requestGroupInfo();
} }
this.groupManager.upgrade(); this.groupManager.update();
break; break;
case repoindex.Pages.Repositories: case repoindex.Pages.Repositories:
this.repoManager.buttonRow.style.display = "none"; this.repoManager.buttonRow.style.display = "none";
@ -243,6 +243,9 @@
this.currentManager.filterNameExact = exact; this.currentManager.filterNameExact = exact;
this.currentManager.filterDescr = searchTerm ? (exact ? "with the name" : "for the search term") : undefined; this.currentManager.filterDescr = searchTerm ? (exact ? "with the name" : "for the search term") : undefined;
this.currentManager.invalidate(true); this.currentManager.invalidate(true);
if(searchTerm && !this.currentManager.customSelection && this.repoManager.entryByName("AUR").enabled) {
repoindex.client.requestSuggestions(["AUR"], searchTerm);
}
} }
}; };

View File

@ -93,9 +93,9 @@
// provide a select function for the page element (will be called when the page element // provide a select function for the page element (will be called when the page element
// is clicked) // is clicked)
pageElement.select = function() { 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 pagination = this.pagination;
var upgradeVisibility = function(index, visible) { var updateVisibility = function(index, visible) {
var lowest = 0; var lowest = 0;
var highest = pagination.elements.length - 1; var highest = pagination.elements.length - 1;
var low = index - pagination.visibleElementsBeforeAfter; var low = index - pagination.visibleElementsBeforeAfter;
@ -112,13 +112,13 @@
} }
} }
if(pagination.currentElement) { if(pagination.currentElement) {
upgradeVisibility(pagination.currentElement.pageIndex, false); updateVisibility(pagination.currentElement.pageIndex, false);
pagination.currentElement.setActive(false); pagination.currentElement.setActive(false);
} }
upgradeVisibility(this.pageIndex, true); updateVisibility(this.pageIndex, true);
this.setActive(true); this.setActive(true);
// upgrade status of the containing pagination object // update status of the containing pagination object
pagination.currentElement = this; pagination.currentElement = this;
pagination.previousElement.setEnabled(this !== pagination.elements.first()); pagination.previousElement.setEnabled(this !== pagination.elements.first());
pagination.nextElement.setEnabled(this !== pagination.elements.last()); pagination.nextElement.setEnabled(this !== pagination.elements.last());
@ -186,8 +186,8 @@
this.currentElement = undefined; this.currentElement = undefined;
}; };
// provide a function to upgrade the pagination (after properties such as the entryCount have been changed) // provide a function to update the pagination (after properties such as the entryCount have been changed)
this.upgrade = function() { this.update = function() {
var requiredPages = Math.ceil(this.entryCount / this.entriesPerPage); var requiredPages = Math.ceil(this.entryCount / this.entriesPerPage);
var currentPages = this.elements.length; var currentPages = this.elements.length;
if(requiredPages < currentPages) { if(requiredPages < currentPages) {

View File

@ -5,20 +5,20 @@
RepoEntry.prototype.constructor = RepoEntry; RepoEntry.prototype.constructor = RepoEntry;
RepoEntry = function(repoName, repoInfo, enabled) { RepoEntry = function(repoName, repoInfo, enabled) {
if(enabled === undefined) { 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); repoindex.Entry.prototype.constructor.call(this, repoName, repoInfo, enabled);
this.info.currentServer = 0; this.info.currentServer = 0;
this.rowElement.onclick = function() { this.rowElement.onclick = function() {
repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.info.index); repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.index);
}; };
this.initTableRow = function() { 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.desc);
//this.rowElement.addCell(this.info.servers && this.info.currentServer < this.info.servers.length ? this.info.servers[this.info.currentServer] : "none");
}; };
this.initTableRow(); this.initTableRow();
@ -31,23 +31,23 @@
this.link.repo = this; this.link.repo = this;
this.link.onclick = function() { this.link.onclick = function() {
if(repoindex.pageManager.repoManager.buttonContainerExclusiveButton.checked) { if(repoindex.pageManager.repoManager.buttonContainerExclusiveButton.checked) {
repoindex.pageManager.repoManager.upgradeEnabledAll(false); repoindex.pageManager.repoManager.updateEnabledAll(false);
} }
this.repo.toggleEnabled(); this.repo.toggleEnabled();
return false; return false;
}; };
this.link.appendChild(document.createTextNode(repoInfo.name)); this.link.appendChild(document.createTextNode(repoName));
this.link.title = repoInfo.desc; this.link.title = repoInfo.desc;
// use Bootstrap tooltip // use Bootstrap tooltip
this.link.setAttribute("data-placement", "bottom"); this.link.setAttribute("data-placement", "bottom");
$(this.link).tooltip(); $(this.link).tooltip();
if(!repoInfo.requestRequired) { if(repoInfo.packageCount) {
// create badge with package count // create badge with package count
var span = document.createElement("span"); var span = document.createElement("span");
span.className = "badge"; span.className = "badge";
span.appendChild(document.createTextNode(repoInfo.packages.length)); span.appendChild(document.createTextNode(repoInfo.packageCount));
this.link.appendChild(document.createTextNode(" ")); this.link.appendChild(document.createTextNode(" "));
this.link.appendChild(span); this.link.appendChild(span);
} }
@ -65,15 +65,17 @@
// provide a function to toggle enabled/disabled // provide a function to toggle enabled/disabled
this.upgradeEnabled = function(enabled) { this.updateEnabled = function(enabled, noNotify) {
if(this.enabled !== enabled) { if(this.enabled !== enabled) {
this.link.className = (this.enabled = enabled) ? "btn btn-primary" : "btn btn-default"; this.link.className = (this.enabled = enabled) ? "btn btn-primary" : "btn btn-default";
this.statusChanged(this); if(!noNotify) {
this.statusChanged(this);
}
} }
}; };
this.toggleEnabled = function() { this.toggleEnabled = function() {
this.upgradeEnabled(!this.enabled); this.updateEnabled(!this.enabled);
}; };
}; };
@ -98,7 +100,7 @@
var repoFilter = []; var repoFilter = [];
for(var i = 0, end = repoEntries.length; i < end; ++i) { for(var i = 0, end = repoEntries.length; i < end; ++i) {
if(repoEntries[i].enabled) { if(repoEntries[i].enabled) {
repoFilter.push(repoEntries[i].info.name); repoFilter.push(repoEntries[i].name);
} }
} }
pageMgr.packageManager.filterRepos = pageMgr.groupManager.filterRepos = repoFilter; pageMgr.packageManager.filterRepos = pageMgr.groupManager.filterRepos = repoFilter;
@ -107,12 +109,12 @@
}; };
// provide a function to add repo entries // provide a function to add repo entries
this.addEntry = function(repoInfo) { this.addEntry = function(repoName, repoInfo) {
var entry = new RepoEntry(repoInfo); var entry = new RepoEntry(repoName, repoInfo);
entry.statusChanged = this.applyRepoStatusChange; entry.statusChanged = this.applyRepoStatusChange;
entry.pageManager = this; entry.pageManager = this;
entry.repoEntryManager = this; entry.repoEntryManager = this;
entry.info.index = this.entries.length; entry.index = this.entries.length;
this.entries.push(entry); this.entries.push(entry);
return entry; return entry;
}; };
@ -125,9 +127,12 @@
this.baseRemoveEntries(); this.baseRemoveEntries();
}; };
this.upgradeEnabledAll = function(enabled) { this.updateEnabledAll = function(enabled, noNotify) {
for(var i = 0; i < this.entries.length; ++i) { 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) { this.showRepoInfo = function(repo) {
var determineEntry = function() { var determineEntry = function() {
var res = repoindex.pageManager.repoManager.entries.filter(function(entry) { var res = repoindex.pageManager.repoManager.entries.filter(function(entry) {
return entry.info.name === repo; return entry.name === repo;
}); });
// entry exists? // entry exists?
if(res.length > 0) { if(res.length > 0) {
// yes -> full package info available? // yes -> full package info available?
repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].info.index); repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].index);
} else { } else {
// no -> show error // no -> show error
repoindex.pageManager.repoManager.showRepoNotFound(repo); repoindex.pageManager.repoManager.showRepoNotFound(repo);
@ -175,21 +180,21 @@
this.showRepoInfoForIndex = function(entryIndex) { this.showRepoInfoForIndex = function(entryIndex) {
var entry = this.entryByIndex(entryIndex); var entry = this.entryByIndex(entryIndex);
if(entry) { if(entry) {
var i = entry.info; var info = entry.info;
repoindex.setText("repo_name", i.name); repoindex.setText("repo_name", entry.name);
repoindex.setText("repo_desc", i.desc); repoindex.setText("repo_desc", info.desc);
repoindex.setText("repo_pkgcount", i.requestRequired ? "unknown" : i.packages.length); repoindex.setText("repo_pkgcount", repoindex.makeStr(info.packageCount));
repoindex.setText("repo_usage", repoindex.makeArray(i.usage, ", ")); repoindex.setText("repo_usage", repoindex.makeArray(info.usage, ", "));
repoindex.setText("repo_siglevel", repoindex.makeArray(i.sigLevel, ", ")); repoindex.setText("repo_siglevel", repoindex.makeArray(info.sigLevel, ", "));
repoindex.setText("repo_source_only", repoindex.makeBool(i.srcOnly)); repoindex.setText("repo_source_only", repoindex.makeBool(info.srcOnly));
repoindex.setText("repo_upgrade_sources", repoindex.makeArray(i.upgradeSources, ", ")); repoindex.setText("repo_upgrade_sources", repoindex.makeArray(info.upgradeSources, ", "));
var invokeUpgradeLookupParams = {repo: i.name, invoke: "upgradelookup"}; var invokeUpgradeLookupParams = {repo: entry.name, invoke: "upgradelookup"};
repoindex.setDownloadButton("repo_checkforupgrades", "check for upgrades", repoindex.makeHash(repoindex.Pages.Repositories, invokeUpgradeLookupParams, true), function() { 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); repoindex.pageManager.denoteHash(repoindex.Pages.Repositories, invokeUpgradeLookupParams);
}, "refresh"); }, "refresh");
// set currentInfo (the pageManager needs this value to upgrade the hash) // 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 // ensures, that the "Package Info" box (with the properties just set) is shown
repoindex.pageManager.showRepoInfo(); repoindex.pageManager.showRepoInfo();
} }
@ -250,21 +255,21 @@
// iterate through all kinds of "upgrades" declared above // iterate through all kinds of "upgrades" declared above
for(var i2 = 0; i2 < upgrades[i1].entries.length; ++i2) { for(var i2 = 0; i2 < upgrades[i1].entries.length; ++i2) {
// iterate through all entries which have been assigned to the current upgrade type // iterate through all entries which have been assigned to the current upgrade type
var newEntry = pkgMgr.createCustomEntry(upgrades[i1].color); var upgradeEntry = upgrades[i1].entries[i2];
newEntry.info.index = upgradeEntries.length; if(upgradeEntry.pkg) {
if(upgrades[i1].entries[i2].pkg) { var packageInfo = repoindex.client.getPackageInfo(upgradeEntry.repo, upgradeEntry.name);
newEntry.applyBasicInfo(upgrades[i1].entries[i2].pkg, true); packageInfo.basics = upgradeEntry.pkg;
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 : "?");
}
// find associated repo entry // find associated repo entry
if((newEntry.repoEntry = repoMgr.entryByName(newEntry.info.repo))) { var repoEntry;
newEntry.repoEntry.upgradeEnabled(true); // ensure repo is enabled 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 { } else {
newEntry.applyBasicInfo(upgrades[i1].entries[i2]); repoindex.pageManager.addError("Upgrade entry replied by server does not include package info.");
} }
upgradeEntries.push(newEntry); upgradeEntries.push(newEntry);
} }