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.
*/
RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, QObject *parent) :
Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), parent),
RepoIndex::AlpmDatabase::AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, uint32 index, QObject *parent) :
Repository(QString::fromLocal8Bit(alpm_db_get_name(dataBase)), index, parent),
m_ptr(dataBase),
m_dbFile(QStringLiteral("%1/sync/%2").arg(dbPath, m_name))
{}

View File

@ -35,8 +35,9 @@ inline QFuture<void> &PackageLoader::future()
class AlpmDatabase : public Repository
{
friend class EmplacePackage;
public:
explicit AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, QObject *parent = nullptr);
explicit AlpmDatabase(alpm_db_t *dataBase, const QString &dbPath, uint32 index = invalidIndex, QObject *parent = nullptr);
PackageLoader *init();
// explicit AlpmDatabase(const QString &dataBaseFile, QObject *parent = nullptr);
@ -112,7 +113,7 @@ inline bool AlpmDatabase::setServers(StringList servers)
}
/*!
* \brief Performs a search using the build in ALPM function.
* \brief Performs a search using the build-in ALPM function.
*/
inline PackageList AlpmDatabase::search(StringList terms) const
{

View File

@ -37,6 +37,13 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue, UserRepository *repositor
m_tarUrl = obj.value(QStringLiteral("URLPath")).toString();
}
/*!
* \brief Creates a new instance where only the name is known.
*/
AurPackage::AurPackage(const QString &name, UserRepository *repository) :
Package(name, repository)
{}
/*!
* \brief Creates a new, empty instance.
* \remarks The only purpose of this c'tor is to use it with restoreFromCacheStream().

View File

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

View File

@ -49,6 +49,8 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
targetNameArg("target-name", "n", "specifies the name of the target archive"),
targetFormatArg("target-format", "e", "specifies the format of the target archive"),
iconThemesArg("icon-packages", "i", "specifies the names of the icon packages to include"),
defaultIconThemeArg("default-icon-theme", string(), "specifies the name of the default icon theme (should be included in --icon-packages)"),
extraPackagesArg("extra-packages", string(), "specifies extra packages to be included"),
shSyntaxArg("sh-syntax", string(), "prints the output using shell syntax: export REPOINDEX_RESULTS=('res1' 'res2' 'res3') or export REPOINDEX_ERROR='some error message'"),
repoArg("repo", string(), "specifies the repository")
{
@ -110,13 +112,19 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) :
iconThemesArg.setCombinable(true);
iconThemesArg.setRequiredValueCount(-1);
iconThemesArg.setValueNames(pkgValueNames);
defaultIconThemeArg.setCombinable(true);
defaultIconThemeArg.setRequiredValueCount(1);
defaultIconThemeArg.setValueNames({"theme name"});
extraPackagesArg.setCombinable(true);
extraPackagesArg.setRequiredValueCount(-1);
extraPackagesArg.setValueNames(pkgValueNames);
shSyntaxArg.setCombinable(true);
repoArg.setRequiredValueCount(1);
repoArg.setValueNames({"repo name"});
serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg});
upgradeLookupArg.setSecondaryArguments({&shSyntaxArg});
buildOrderArg.setSecondaryArguments({&aurArg, &verboseArg, &shSyntaxArg});
mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg});
mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg, &defaultIconThemeArg, &extraPackagesArg});
parser.setMainArguments({&buildOrderArg, &upgradeLookupArg, &serverArg, &mingwBundleArg, &repoindexConfArg, &repoindexConfArg, &helpArg});
}
@ -132,6 +140,7 @@ Config::Config() :
m_alpmRootDir(QStringLiteral("/")),
m_alpmDbPath(QStringLiteral("/var/lib/pacman")),
m_pacmanConfFile(QStringLiteral("/etc/pacman.conf")),
m_cacheDir(QStringLiteral(".")),
m_websocketServerListeningAddr(QHostAddress::LocalHost),
m_websocketServerListeningPort(1234),
m_serverInsecure(false),

View File

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

View File

@ -1,8 +1,8 @@
#include "./manager.h"
#include "./alpmdatabase.h"
#include "./utilities.h"
#include "./list.h"
#include "./config.h"
#include "./alpmdatabase.h"
#include "../network/userrepository.h"
@ -63,11 +63,7 @@ Manager::Manager(const Config &config) :
m_sigLevel(defaultSigLevel),
m_localFileSigLevel(ALPM_SIG_USE_DEFAULT)
{
alpm_errno_t err;
if(!(m_handle = alpm_initialize(config.alpmRootDir().toLocal8Bit().data(), config.alpmDbPath().toLocal8Bit().data(), &err))) {
throw runtime_error(string("Cannot initialize ALPM: ") + alpm_strerror(err));
}
m_localDb = make_unique<AlpmDatabase>(alpm_get_localdb(m_handle), config.alpmDbPath());
initAlpmHandle();
if(config.isAurEnabled()) {
m_userRepo = make_unique<UserRepository>(m_networkAccessManager);
}
@ -81,7 +77,7 @@ Manager::~Manager()
if(m_writeCacheBeforeGone) {
writeCache();
}
alpm_release(m_handle);
cleanupAlpm();
}
/*!
@ -289,9 +285,9 @@ int Manager::parseUsage(const string &usageStr)
}
/*!
* \brief Parses and applies the Pacman configuration. Registers the listed sync databases.
* \brief Registers sync databases listed in the Pacman config file. Also reads the cache dir.
*/
void Manager::applyPacmanConfig()
void Manager::registerDataBasesFromPacmanConfig()
{
// open config file and parse as ini
try {
@ -309,21 +305,21 @@ void Manager::applyPacmanConfig()
// read relevant options
static const string sigLevelKey("SigLevel");
static const string usageKey("Usage");
int globalSigLevel;
try {
const auto &options = config.at("options");
const auto &specifiedArch = lastValue(options, "Architecture");
if(!specifiedArch.empty() && specifiedArch != "auto") {
arch = specifiedArch;
int globalSigLevel = defaultSigLevel;
for(auto &scope : config) {
if(scope.first == "options") {
// iterate through all "config" scopes (just to cover the case that there are multiple "options" scopes)
const auto &options = scope.second;
const auto &specifiedArch = lastValue(options, "Architecture");
if(!specifiedArch.empty() && specifiedArch != "auto") {
arch = specifiedArch;
}
const auto &specifiedDir = lastValue(options, "CacheDir");
if(!specifiedDir.empty()) {
m_pacmanCacheDir = QString::fromStdString(specifiedDir);
}
globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey));
}
const auto &specifiedDir = lastValue(options, "CacheDir");
if(!specifiedDir.empty()) {
m_pacmanCacheDir = QString::fromStdString(specifiedDir);
}
globalSigLevel = parseSigLevel(lastValue(options, sigLevelKey));
} catch(const out_of_range &) {
// no options specified
globalSigLevel = defaultSigLevel;
}
// register sync databases
unordered_map<string, IniFile> includedInis;
@ -347,7 +343,7 @@ void Manager::applyPacmanConfig()
// set usage
if(alpm_db_set_usage(db, static_cast<alpm_db_usage_t>(usage)) == 0) {
if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added database [" << scope.first << "]" << endl;
cerr << shchar << "Added database [" << scope.first << ']' << endl;
}
} else {
if(m_config.isVerbose() || m_config.runServer()) {
@ -379,28 +375,31 @@ void Manager::applyPacmanConfig()
}
}
try {
const auto &includedScope = includedIni.data().at(string());
for(auto range = includedScope.equal_range("Server"); range.first != range.second; ++range.first) {
string url = range.first->second;
findAndReplace<string>(url, "$repo", scope.first);
findAndReplace<string>(url, "$arch", arch);
alpm_db_add_server(db, url.c_str());
if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added server: " << url << endl;
for(auto &scope : includedIni.data()) {
if(scope.first.empty()) {
for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) {
string url = range.first->second;
findAndReplace<string>(url, "$repo", scope.first);
findAndReplace<string>(url, "$arch", arch);
alpm_db_add_server(db, url.c_str());
if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added server: " << url << endl;
}
}
}
}
} catch (const out_of_range &) {
cerr << shchar << "Warning: Included file \"" << path << "\" has no values." << endl;
}
}
auto emplaced = m_syncDbs.emplace(dbName, make_unique<AlpmDatabase>(db, m_config.alpmDbPath()));
// add sync db to internal map
// add sync db to internal map (use as index size + 1 because the local database has index 0)
auto emplaced = m_syncDbs.emplace(dbName, make_unique<AlpmDatabase>(db, m_config.alpmDbPath(), m_syncDbs.size() + 1));
if(usage & ALPM_DB_USAGE_UPGRADE) {
// -> db is used to upgrade local database
localDataBase()->upgradeSources() << emplaced.first->second.get();
}
} else {
cerr << shchar << "Error: Unable to add sync database [" << scope.first << "]" << endl;
cerr << shchar << "Error: Unable to add sync database [" << scope.first << ']' << endl;
}
}
}
@ -411,16 +410,16 @@ void Manager::applyPacmanConfig()
}
/*!
* \brief Applies the repository index configuration.
* \brief Registers sync databases listed in the repository index configuration.
*/
void Manager::applyRepoIndexConfig()
void Manager::registerDatabasesFromRepoIndexConfig()
{
// check whether an entry already exists, if not create a new one
for(const RepoEntry &repoEntry : m_config.repoEntries()) {
AlpmDatabase *syncDb = nullptr;
try {
syncDb = m_syncDbs.at(repoEntry.name()).get();
cerr << shchar << "Applying config for database [" << syncDb->name() << "]" << endl;
cerr << shchar << "Applying config for database [" << syncDb->name() << ']' << endl;
if(!repoEntry.dataBasePath().isEmpty()) {
cerr << shchar << "Warning: Can't use data base path specified in repo index config because the repo \""
<< repoEntry.name() << "\" has already been added from the Pacman config." << endl;
@ -439,13 +438,13 @@ void Manager::applyRepoIndexConfig()
} else {
// TODO: database path
auto *db = alpm_register_syncdb(m_handle, repoEntry.name().toLocal8Bit().data(), static_cast<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) {
syncDb = emplaced.first->second.get();
syncDb->setSourcesDirectory(repoEntry.sourceDir());
syncDb->setPackagesDirectory(repoEntry.packageDir());
if(m_config.isVerbose() || m_config.runServer()) {
cerr << shchar << "Added database [" << repoEntry.name() << "]" << endl;
cerr << shchar << "Added database [" << repoEntry.name() << ']' << endl;
}
}
}
@ -556,7 +555,7 @@ void Manager::unregisterSyncDataBases()
* \brief Returns a list of all sync databases.
* \remarks Sync databases must be registered with parsePacmanConfig() before.
*/
const map<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
}
@ -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 registered sync databases.
*/
const QJsonArray &Manager::basicRepoInfo() const
const QJsonObject &Manager::basicRepoInfo() const
{
if(m_basicRepoInfo.isEmpty()) {
QMutexLocker locker(&m_basicRepoInfoMutex);
if(m_basicRepoInfo.isEmpty()) {
// add local data base
m_basicRepoInfo << localDataBase()->basicInfo();
m_basicRepoInfo.insert(localDataBase()->name(), localDataBase()->basicInfo());
// add sync data bases
for(const auto &syncDb : syncDatabases()) {
// check if the "sync" database is actually used for syncing
auto usage = syncDb.second->usage();
if((usage & ALPM_DB_USAGE_SYNC) || (usage & ALPM_DB_USAGE_INSTALL) || (usage & ALPM_DB_USAGE_UPGRADE)) {
m_basicRepoInfo << syncDb.second->basicInfo();
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
} else {
m_basicRepoInfo << syncDb.second->basicInfo();
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
}
}
// add AUR
if(config().isAurEnabled()) {
m_basicRepoInfo << userRepository()->basicInfo();
if(userRepository()) {
m_basicRepoInfo.insert(userRepository()->name(), userRepository()->basicInfo());
}
}
}
@ -596,28 +595,33 @@ const QJsonArray &Manager::basicRepoInfo() const
/*!
* \brief Returns package information for the specified selection of packages.
*/
const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, bool full) const
const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const
{
QJsonArray pkgInfos;
QJsonArray results;
for(auto i = pkgSelection.constBegin(), end = pkgSelection.constEnd(); i != end; ++i) {
if(auto *repo = repositoryByName(i.key())) {
for(const auto &entry : i.value().toArray()) {
const auto entryObj = entry.toObject();
const auto pkgName = entryObj.value(QStringLiteral("name")).toString();
if(!pkgName.isEmpty()) {
QJsonObject pkgInfo;
QJsonObject res;
if(auto *pkg = repo->packageByName(pkgName)) {
pkgInfo = full ? pkg->fullInfo() : pkg->basicInfo();
if(part & Basics) {
res.insert(QStringLiteral("basics"), pkg->basicInfo());
}
if(part & Details) {
res.insert(QStringLiteral("details"), pkg->detailedInfo());
}
} else {
pkgInfo.insert(QStringLiteral("error"), QStringLiteral("na"));
res.insert(QStringLiteral("error"), QStringLiteral("na"));
}
pkgInfo.insert(QStringLiteral("name"), pkgName);
pkgInfo.insert(QStringLiteral("repo"), repo->name());
res.insert(QStringLiteral("name"), pkgName);
res.insert(QStringLiteral("repo"), repo->name());
const auto index = entryObj.value(QStringLiteral("index"));
if(!index.isNull() && !index.isUndefined()) {
pkgInfo.insert(QStringLiteral("index"), index);
res.insert(QStringLiteral("index"), index);
}
pkgInfos << pkgInfo;
results << res;
}
}
} else {
@ -625,11 +629,11 @@ const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, bool full
QJsonObject errorObj;
errorObj.insert(QStringLiteral("repo"), i.key());
errorObj.insert(QStringLiteral("error"), QStringLiteral("na"));
pkgInfos << errorObj;
results << errorObj;
}
}
return pkgInfos;
return results;
}
/*!
@ -649,6 +653,33 @@ const QJsonArray &Manager::groupInfo() const
return m_groupInfo;
}
/*!
* \brief Initiates the ALPM library handle and databases.
* \remarks Do not call this function, if ALPM is already initiated.
*/
void Manager::initAlpmHandle()
{
alpm_errno_t err;
if(!(m_handle = alpm_initialize(config().alpmRootDir().toLocal8Bit().data(), config().alpmDbPath().toLocal8Bit().data(), &err))) {
throw runtime_error(string("Cannot initialize ALPM: ") + alpm_strerror(err));
}
m_localDb = make_unique<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.
*/
@ -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.
*

View File

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

View File

@ -27,8 +27,9 @@ using namespace Utilities;
const string prefix("mingw-w64-");
MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages) :
m_manager(manager)
MingwBundle::MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages) :
m_manager(manager),
m_extraPackages(extraPackages)
{
cerr << shchar << "Resolving dependencies ..." << endl;
string missing;
@ -36,7 +37,8 @@ MingwBundle::MingwBundle(const Manager &manager, const ApplicationUtilities::Str
for(const auto &pkgName : packages) {
bool found = false;
for(const auto &syncDb : manager.syncDatabases()) {
if(auto *pkg = syncDb.second->packageByName(QString::fromLocal8Bit(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data()))) {
const Dependency dep(QString::fromLocal8Bit(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data()));
if(auto *pkg = syncDb.second->packageProviding(dep)) {
if(missing.empty()) {
decltype(m_packages)::value_type entry(syncDb.second.get(), pkg);
if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) {
@ -120,7 +122,8 @@ enum class RelevantFileType
Translation,
QtTranslation,
QtPlugin,
Icon
IconTheme,
ConfigFile
};
enum class RelevantFileArch
@ -133,12 +136,14 @@ enum class RelevantFileArch
struct RelevantFile
{
RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) :
file(file),
name(file->name()),
data(file->data()),
fileType(type),
arch(arch),
subDir(subDir)
{}
const KArchiveFile *file;
QString name;
QByteArray data;
RelevantFileType fileType;
RelevantFileArch arch;
QString subDir;
@ -191,8 +196,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
if(entryName.endsWith(QLatin1String(".exe")) || entryName.endsWith(QLatin1String(".dll"))) {
if(const auto *entry = binDir->entry(entryName)) {
if(entry->isFile()) {
const auto *binFile = static_cast<const KArchiveFile *>(entry);
pkgFileInfo.relevantFiles.emplace_back(binFile, RelevantFileType::Binary, root.first);
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), RelevantFileType::Binary, root.first);
}
}
}
@ -214,8 +218,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
for(const auto &entryName : categoryDir->entries()) {
if(const auto *pluginEntry = categoryDir->entry(entryName)) {
if(pluginEntry->isFile()) {
const auto *pluginFile = static_cast<const KArchiveFile *>(pluginEntry);
pkgFileInfo.relevantFiles.emplace_back(pluginFile, RelevantFileType::QtPlugin, root.first, categoryDir->name());
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(pluginEntry), RelevantFileType::QtPlugin, root.first, categoryDir->name());
}
}
}
@ -237,8 +240,7 @@ void getFiles(PkgFileInfo &pkgFileInfo)
if(entryName.endsWith(QLatin1String(".qm"))) {
if(const auto *qmEntry = trDir->entry(entryName)) {
if(qmEntry->isFile()) {
const auto *qmFile = static_cast<const KArchiveFile *>(qmEntry);
pkgFileInfo.relevantFiles.emplace_back(qmFile, RelevantFileType::QtTranslation, root.first);
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(qmEntry), RelevantFileType::QtTranslation, root.first);
}
}
}
@ -249,6 +251,34 @@ void getFiles(PkgFileInfo &pkgFileInfo)
const auto *appEntry = shareDir->entry(pkgFileInfo.name);
if(appEntry && appEntry->isDirectory()) {
const auto *appDir = static_cast<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"));
if(trEntry && trEntry->isDirectory()) {
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()) {
const auto *themeEntry = iconsDir->entry(themeName);
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;
// get package files
@ -304,6 +396,15 @@ void MingwBundle::createBundle(const string &targetDir, const string &targetName
}
pkgFiles.emplace_back(entry.second->name().startsWith(QLatin1String("mingw-w64-")) ? entry.second->name().mid(10) : entry.second->name(), pkgFile);
}
for(const auto &pkgFileStdStr : m_extraPackages) {
QString pkgFile = QString::fromLocal8Bit(pkgFileStdStr.data());
if(QFile::exists(pkgFile)) {
const auto pkg = m_manager.packageFromFile(pkgFileStdStr.data()); // do not catch the exception here
pkgFiles.emplace_back(pkg->name().startsWith(QLatin1String("mingw-w64-")) ? pkg->name().mid(10) : pkg->name(), pkgFile);
} else {
throw runtime_error("The specified extra package \"" + pkgFileStdStr + "\" can't be found.");
}
}
// get relevant files from packages
QtConcurrent::blockingMap(pkgFiles, getFiles);
// check whether all packages could be opened
@ -325,59 +426,18 @@ void MingwBundle::createBundle(const string &targetDir, const string &targetName
QJsonDocument pkgList;
pkgList.setArray(pkgArray);
QByteArray pkgListBytes = pkgList.toJson();
// make target archive
static const QString user(QStringLiteral("root"));
static const QString &group = user;
static const pair<RelevantFileArch, QString> roots[] = {
make_pair(RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32")),
make_pair(RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32"))
};
for(const auto &root : roots) {
QString targetPath = qstr(targetDir) % QChar('/') % root.second % QChar('-') % qstr(targetName) % QChar('.') % qstr(targetFormat);
cerr << shchar << "Making archive \"" << targetPath.toLocal8Bit().data() << "\" ..." << endl;
unique_ptr<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.");
}
QByteArray indexFileBytes;
if(!defaultIconTheme.empty()) {
indexFileBytes.reserve(23 + defaultIconTheme.size());
indexFileBytes.append("[Icon Theme]\nInherits=");
indexFileBytes.append(defaultIconTheme.data());
indexFileBytes.append('\n');
}
// make target archive
auto run1 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat)));
auto run2 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat)));
run1.waitForFinished();
run2.waitForFinished();
}
} // namespace PackageManagement

View File

@ -15,15 +15,16 @@ class Manager;
class MingwBundle
{
public:
MingwBundle(const Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages);
MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages);
void createBundle(const std::string &targetDir, const std::string &targetName, const std::string &targetFormat) const;
void createBundle(const std::string &targetDir, const std::string &targetName, const std::string &targetFormat, const std::string &defaultIconTheme) const;
private:
void addDependencies(const Package *pkg);
const Manager &m_manager;
Manager &m_manager;
std::list<std::pair<const AlpmDatabase *, const Package *> > m_packages;
const ApplicationUtilities::StringVector &m_extraPackages;
};
} // namespace PackageManagement

View File

@ -1,4 +1,4 @@
#include "./package.h"
#include "./package.h"
#include "./alpmdatabase.h"
#include "./utilities.h"
#include "./repository.h"
@ -10,6 +10,7 @@
#include <QJsonDocument>
#include <QVariant>
#include <QDataStream>
#include <QStringBuilder>
#include <iostream>
@ -32,6 +33,7 @@ namespace RepoIndex {
Package::Package(const QString &name, Repository *repository) :
m_origin(PackageOrigin::Unknown),
m_repository(repository),
m_timeStamp(DateTime::now()),
m_hasGeneralInfo(false),
m_name(name),
m_requiredByComputed(false),
@ -293,7 +295,7 @@ using namespace Utilities;
*/
QJsonObject Package::basicInfo(bool includeRepoAndName) const
{
QJsonObject info;
QJsonObject info;
if(includeRepoAndName) {
if(repository()) {
put(info, QStringLiteral("repo"), repository()->name());
@ -312,24 +314,11 @@ QJsonObject Package::basicInfo(bool includeRepoAndName) const
/*!
* \brief Returns full information about the package as JSON object.
*/
QJsonObject Package::fullInfo(bool includeRepoAndName) const
QJsonObject Package::detailedInfo() const
{
QJsonObject info;
if(includeRepoAndName) {
if(repository()) {
put(info, QStringLiteral("repo"), repository()->name());
}
put(info, QStringLiteral("name"), name());
}
put(info, QStringLiteral("build_avail"), hasBuildRelatedMetaData());
put(info, QStringLiteral("src_avail"), hasSourceRelatedMetaData());
put(info, QStringLiteral("archs"), architectures());
put(info, QStringLiteral("arch"), buildArchitecture());
put(info, QStringLiteral("ver"), version());
put(info, QStringLiteral("desc"), description());
put(info, QStringLiteral("bdate"), buildDate());
put(info, QStringLiteral("bdate"), buildDate());
put(info, QStringLiteral("flagdate"), outOfDate());
put(info, QStringLiteral("buildAvail"), hasBuildRelatedMetaData());
put(info, QStringLiteral("srcAvail"), hasSourceRelatedMetaData());
put(info, QStringLiteral("idate"), installDate());
put(info, QStringLiteral("isize"), QJsonValue(static_cast<long long int>(installedSize())));
put(info, QStringLiteral("url"), upstreamUrl());
@ -356,7 +345,7 @@ QJsonObject Package::fullInfo(bool includeRepoAndName) const
*/
void Package::writeToCacheStream(QDataStream &out)
{
out << static_cast<qint32>(m_origin);
out << static_cast<qint32>(m_origin) << m_timeStamp;
// general info
out << m_hasGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses
<< m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides
@ -389,6 +378,7 @@ void Package::restoreFromCacheStream(QDataStream &in)
// origin
in >> tmp;
m_origin = static_cast<PackageOrigin>(tmp); // TODO: validate value
in >> m_timeStamp;
// general info
in >> m_hasGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses
>> m_groups >> m_dependencies >> m_optionalDependencies >> m_conflicts >> m_provides
@ -420,6 +410,74 @@ void Package::restoreFromCacheStream(QDataStream &in)
}
}
/*!
* \brief Puts the specified src/pkg info key value pairs; clears current dependencies, provides, ...
* \remarks This method should only be called by the associated repository because it must handle a possible name change.
*/
void Package::putInfo(const QList<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.
*/
@ -472,6 +530,24 @@ PackageVersion::PackageVersion(const QString &versionStr)
}
}
/*!
* \brief Constructs an empty package version.
*/
PackageVersion::PackageVersion()
{}
/*!
* \brief Returns the string representation of the package version: epoch:version-release
*/
QString RepoIndex::PackageVersion::toString() const
{
if(epoch.isEmpty()) {
return version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release);
} else {
return epoch % QChar(':') % version % QChar('-') % (release.isEmpty() ? QStringLiteral("1") : release);
}
}
/*!
* \brief Compares two version parts.
* \returns Returns 1 if part1 is newer then part2, -1 if part2 is newer then part1 and 0 if both parts are equal.
@ -556,5 +632,45 @@ PackageVersionComparsion PackageVersion::compare(const PackageVersion &other) co
return PackageVersionComparsion::Equal;
}
/*!
* \brief Constructs a dependency from the specified string.
* \remarks \a dependency might have version suffix.
*/
Dependency::Dependency(const QString &dependency)
{
int suffixBeg;
if((suffixBeg = dependency.lastIndexOf(QLatin1String(">="))) > 0) {
mode = ALPM_DEP_MOD_GE;
} else if((suffixBeg = dependency.lastIndexOf(QLatin1String("<="))) > 0) {
mode = ALPM_DEP_MOD_LE;
} else if((suffixBeg = dependency.lastIndexOf(QChar('='))) > 0) {
mode = ALPM_DEP_MOD_EQ;
} else if((suffixBeg = dependency.lastIndexOf(QChar('<'))) > 0) {
mode = ALPM_DEP_MOD_LT;
} else if((suffixBeg = dependency.lastIndexOf(QChar('>'))) > 0) {
mode = ALPM_DEP_MOD_GT;
} else {
mode = ALPM_DEP_MOD_ANY;
}
switch(mode) {
case ALPM_DEP_MOD_ANY:
name = dependency;
break;
case ALPM_DEP_MOD_GE:
case ALPM_DEP_MOD_LE:
name = dependency.mid(0, suffixBeg);
version = dependency.mid(suffixBeg + 2);
break;
case ALPM_DEP_MOD_EQ:
case ALPM_DEP_MOD_LT:
case ALPM_DEP_MOD_GT:
name = dependency.mid(0, suffixBeg);
version = dependency.mid(suffixBeg + 1);
break;
default:
;
}
}
}

View File

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

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).
*/
Repository::Repository(const QString &name, QObject *parent) :
Repository::Repository(const QString &name, uint32 index, QObject *parent) :
QObject(parent),
m_index(index),
m_name(name),
m_usage(static_cast<alpm_db_usage_t>(0)),
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));
}
/*!
* \brief Returns suggestions for the specified \a term.
*/
QJsonObject Repository::suggestions(const QString &term) const
{
QJsonArray suggestions;
size_t remainingSuggestions = 20;
for(auto i = packages().lower_bound(term), end = packages().cend(); i != end && remainingSuggestions; ++i, --remainingSuggestions) {
if(i->first.startsWith(term, Qt::CaseInsensitive)) {
suggestions << i->first;
} else {
break;
}
}
QJsonObject res;
res.insert(QStringLiteral("repo"), name());
res.insert(QStringLiteral("res"), suggestions);
return res;
}
QJsonArray Repository::upgradeSourcesJsonArray() const
{
QJsonArray sources;
@ -303,6 +324,18 @@ QJsonArray Repository::packageNamesJsonArray() const
return names;
}
/*!
* \brief Returns an object with the package names of the repository as keys (and empty objects as value).
*/
QJsonObject Repository::packagesObjectSkeleton() const
{
QJsonObject skel;
for(const auto &entry : m_packages) {
skel.insert(entry.first, QJsonValue(QJsonValue::Object));
}
return skel;
}
/*!
* \cond
*/
@ -328,18 +361,26 @@ inline void put(QJsonObject &obj, const QString &key, const QStringList &values)
/*!
* \brief Returns basic information about the repository.
*/
QJsonObject Repository::basicInfo() const
QJsonObject Repository::basicInfo(bool includeName) const
{
QJsonObject info;
put(info, QStringLiteral("name"), name());
if(includeName) {
put(info, QStringLiteral("name"), name());
}
if(index() != invalidIndex) {
info.insert(QStringLiteral("index"), static_cast<int>(index()));
}
put(info, QStringLiteral("desc"), description());
put(info, QStringLiteral("servers"), serverUrls());
put(info, QStringLiteral("usage"), Utilities::usageStrings(usage()));
put(info, QStringLiteral("sigLevel"), Utilities::sigLevelStrings(sigLevel()));
put(info, QStringLiteral("upgradeSources"), upgradeSourcesJsonArray());
put(info, QStringLiteral("packages"), packageNamesJsonArray());
put(info, QStringLiteral("requestRequired"), requestsRequired(PackageDetail::Basics) != PackageDetailAvailability::Immediately);
put(info, QStringLiteral("packages"), packagesObjectSkeleton());
if(requestsRequired(PackageDetail::Basics) == PackageDetailAvailability::Immediately) {
info.insert(QStringLiteral("packageCount"), static_cast<qint64>(m_packages.size()));
}
put(info, QStringLiteral("srcOnly"), isSourceOnly());
put(info, QStringLiteral("pkgOnly"), isPackageOnly());
return info;
}
@ -471,4 +512,100 @@ void Repository::restoreSpecificCacheHeader(QDataStream &in)
Q_UNUSED(in)
}
/*!
* \brief Adds a package parsed from the specified \a srcInfo.
*/
void Repository::addPackagesFromSrcInfo(const QByteArray &srcInfo)
{
enum {
FieldName,
EquationSign,
Pad,
FieldValue
} state = FieldName;
QString currentFieldName;
QString currentFieldValue;
QString packageBase;
QList<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

View File

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

View File

@ -195,7 +195,7 @@ void BuildOrderResolver::printResults(const QStringList &results)
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
addDeps(tasks, task, pkg->dependencies());
} 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 obj;
obj.insert(QStringLiteral("pkg"), package->basicInfo(true));
obj.insert(QStringLiteral("name"), package->name());
if(package->repository()) {
obj.insert(QStringLiteral("repo"), package->repository()->name());
}
obj.insert(QStringLiteral("pkg"), package->basicInfo());
obj.insert(QStringLiteral("curVer"), currentVersion);
return obj;
}
@ -194,10 +198,6 @@ UpgradeLookupJson::UpgradeLookupJson(const Manager &manager, const QJsonObject &
} else {
m_errorsArray << QStringLiteral("Repository \"%1\" can not be found.").arg(toCheckName);
}
// there are errors
QJsonObject results;
results.insert(QStringLiteral("errors"), m_errorsArray);
emit resultsAvailable(request.value(QStringLiteral("what")), request.value(QStringLiteral("id")), results);
deleteLater();
}
@ -231,7 +231,13 @@ void UpgradeLookupJson::processFinished()
if(--m_remainingProcesses == 0) {
// finally make info for orphanded packages
for(const auto *pkg : m_orphanedPackages) {
m_orphanedPackagesArray << pkg->basicInfo(true);
QJsonObject obj;
obj.insert(QStringLiteral("name"), pkg->name());
if(pkg->repository()) {
obj.insert(QStringLiteral("repo"), pkg->repository()->name());
}
obj.insert(QStringLiteral("pkg"), pkg->basicInfo());
m_orphanedPackagesArray << obj;
}
// add results to results QJsonObject
QJsonObject results;
@ -367,7 +373,7 @@ void UpgradeLookupCli::printResults()
Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray);
}
}
emit finished();
QCoreApplication::exit();
}
} // namespace PackageManagement

View File

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

View File

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

View File

@ -1,5 +1,3 @@
# template
TEMPLATE = lib
#dirs
UI_DIR = ./gui
MOC_DIR = ./moc
@ -75,7 +73,11 @@ guiqtwidgets {
DEFINES += GUI_QTWIDGETS
DEFINES += MODEL_UNDO_SUPPORT
}
# configuration for cross compliation with mingw-w64
# Windows stuff: configuration for cross compliation with mingw-w64
win32 {
QMAKE_TARGET_PRODUCT = "$${appname}"
QMAKE_TARGET_COPYRIGHT = "by $${appauthor}"
}
mingw-w64-manualstrip-dll {
QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET); \
$${CROSS_COMPILE}strip --strip-unneeded ./release/lib$(TARGET).a
@ -84,5 +86,7 @@ mingw-w64-manualstrip-exe {
QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET)
}
mingw-w64-noversion {
VERSION = ""
TARGET_EXT = ".dll"
TARGET_VERSION_EXT = ""
CONFIG += skip_target_version_ext
}

View File

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

View File

@ -2,6 +2,7 @@
#include "../alpm/manager.h"
#include "../alpm/upgradelookup.h"
#include "../alpm/suggestionslookup.h"
#include "../alpm/config.h"
#include <QJsonDocument>
@ -16,7 +17,7 @@ using namespace std;
namespace RepoIndex {
Connection::Connection(const Manager &alpmManager, QWebSocket *socket, QObject *parent) :
Connection::Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent) :
QObject(parent),
m_socket(socket),
m_manager(alpmManager),
@ -75,16 +76,37 @@ void Connection::handleQuery(const QJsonObject &obj)
const auto id = obj.value(QStringLiteral("id"));
if(what == QLatin1String("basicrepoinfo")) {
m_repoInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_repoInfoUpdatesRequested);
sendResults(what, id, m_manager.basicRepoInfo());
sendResult(what, id, m_manager.basicRepoInfo());
} else if(what == QLatin1String("basicpkginfo")) {
sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), false));
sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), Manager::Basics));
} else if(what == QLatin1String("pkgdetails")) {
sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), Manager::Details));
} else if(what == QLatin1String("fullpkginfo")) {
sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), true));
// TODO: figure out why QFlags doesn't work, in the mean time, use static_cast workaround
sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), static_cast<Manager::PackageInfoPart>(static_cast<int>(Manager::Basics) | static_cast<int>(Manager::Details))));
} else if(what == QLatin1String("groupinfo")) {
m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested);
sendResults(what, id, m_manager.groupInfo());
} else if(what == QLatin1String("suggestions")) {
auto *suggestionsLookup = new SuggestionsLookup(m_manager, obj);
if(suggestionsLookup->finished()) {
sendResult(what, id, suggestionsLookup->results());
} else if(!suggestionsLookup->errors().isEmpty()) {
QJsonObject results;
results.insert(QStringLiteral("errors"), suggestionsLookup->errors());
sendResult(what, id, results);
} else {
connect(suggestionsLookup, &SuggestionsLookup::resultsAvailable, this, &Connection::sendResult);
}
} else if(what == QLatin1String("upgradelookup")) {
connect(new UpgradeLookupJson(m_manager, obj), &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult);
auto *upgradeLookup = new UpgradeLookupJson(m_manager, obj);
if(upgradeLookup->errors().isEmpty()) {
connect(upgradeLookup, &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult);
} else {
QJsonObject results;
results.insert(QStringLiteral("errors"), upgradeLookup->errors());
sendResult(what, id, results);
}
} else if(what == QLatin1String("ping")) {
sendResult(what, id, QStringLiteral("pong"));
} else {
@ -103,6 +125,17 @@ void Connection::handleCmd(const QJsonObject &obj)
} else {
sendError(QStringLiteral("rejected"), id);
}
} else if(what == QLatin1String("reinitalpm")) {
if(m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Reinit of ALPM databases triggered via web interface." << endl;
m_manager.cleanupAlpm();
m_manager.initAlpmHandle();
m_manager.registerDataBasesFromPacmanConfig();
m_manager.registerDatabasesFromRepoIndexConfig();
m_manager.initAlpmDataBases(true);
} else {
sendError(QStringLiteral("rejected"), id);
}
} else {
sendError(QStringLiteral("unknown command"), id);
}

View File

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

View File

@ -14,7 +14,7 @@ using namespace std;
namespace RepoIndex {
Server::Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent) :
Server::Server(RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent) :
QObject(parent),
m_server(new QWebSocketServer(QStringLiteral("Repository index server"),
config.serverInsecure() ? QWebSocketServer::NonSecureMode : QWebSocketServer::SecureMode,

View File

@ -22,7 +22,7 @@ class Server : public QObject
Q_OBJECT
public:
Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent = nullptr);
Server(RepoIndex::Manager &alpmManager, const RepoIndex::Config &config, QObject *parent = nullptr);
~Server();
signals:
@ -34,7 +34,7 @@ private slots:
private:
QWebSocketServer *m_server;
const RepoIndex::Manager &m_alpmManager;
RepoIndex::Manager &m_alpmManager;
};
}

View File

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

View File

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

View File

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

View File

@ -69,7 +69,15 @@
</div>
</form>
<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>
</form>
</div><!-- /.navbar-collapse -->
@ -96,8 +104,8 @@
</button>
<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><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.upgradeEnabledAll(false); return false;">Remove selection</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.updateEnabledAll(false); return false;">Remove selection</a></li>
</ul>
</span>
</form>

View File

@ -13,16 +13,15 @@
BasicPackageInfo: "basicpkginfo",
FullPackageInfo: "fullpkginfo",
GroupInfo: "groupinfo",
UpgradeLookup: "upgradelookup"
UpgradeLookup: "upgradelookup",
Suggestions: "suggestions"
};
repoindex.isLoopback = function(domain) {
return domain === "localhost" || domain === "127.0.0.1" || domain === "::1";
};
// responsible for the connection to the server
// responsible for the connection to the server; holds all repo/package information returned by the server
var Client = function(url) {
// basic initialization
this.url = url;
@ -191,10 +190,10 @@
this.useBasicRepoInfo(values);
break;
case repoindex.RequestType.BasicPackageInfo:
// the info is used via the registred callbacks only
this.usePackageInfo(values);
break;
case repoindex.RequestType.FullPackageInfo:
// the info is used via the registred callbacks only
this.usePackageInfo(values);
break;
case repoindex.RequestType.GroupInfo:
this.useGroupInfo(values);
@ -202,6 +201,9 @@
case repoindex.RequestType.UpgradeLookup:
// the info is used via the registred callbacks only
break;
case repoindex.RequestType.Suggestions:
this.useSuggestions(values);
break;
default:
pageManager.addError("Server replied unknown results: " + what);
return; // don't invoke callbacks when results are of unknown type
@ -233,14 +235,37 @@
this.scheduleRequest(repoindex.RequestType.GroupInfo, {upgrades: "true"}, callback);
};
this.stopServer = function() {
this.requestSuggestions = function(repoNames, searchTerm, callback) {
this.scheduleRequest(repoindex.RequestType.Suggestions, {repos: repoNames, term: searchTerm});
};
this.requestAurSuggestions = function(term) {
var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.onreadystatechange = function() {
if(xmlhttp.readyState === 4 && xmlhttp.status === 200) {
repoindex.client.useAurSuggestions(JSON.parse(xmlhttp.responseText));
}
};
xmlhttp.open("GET", "http://aur.archlinux.org/rpc.php?type=suggest&arg=" + encodeURIComponent(term), true);
xmlhttp.send();
};
this.sendCmd = function(cmd) {
if(this.isOpen()) {
this.socket.send(JSON.stringify({class: "cmd", what: "stop"}));
this.socket.send(JSON.stringify({class: "cmd", what: cmd}));
} else {
repoindex.pageManager.addError("Not connected to a server.");
}
};
this.stopServer = function() {
this.sendCmd("stop");
};
this.reinitAlpm = function() {
this.sendCmd("reinitalpm");
};
this.checkForUpgrades = function(dbName, syncdbNames, callback) {
var params = {
db: dbName
@ -251,29 +276,102 @@
this.scheduleRequest(repoindex.RequestType.UpgradeLookup, params, callback);
};
// define helper functions to access repo/package information
this.getPackageInfo = function(repoName, packageName) {
var repoInfo = this.repos[repoName];
if(!repoInfo) {
// the repo doesn't exists; might happen when receiving AUR suggestions directly
// -> just create a new, empty repo
this.repo[repoName] = (repoInfo = {name: repoName, packages: {}});
}
var packageInfo = repoInfo.packages[packageName];
if(!packageInfo) {
repoInfo.packages[packageName] = (packageInfo = {repo: repoName});
}
return packageInfo;
};
// define functions to use differend kinds of results
this.useBasicRepoInfo = function(values) {
if(!values) {
this.useBasicRepoInfo = function(value) {
if(!value) {
repoindex.pageManager.addError("Server replied insufficiant basic repo info.");
return false;
}
// upgrade repos and package list
this.repos = {};
this.repos = value;
var repoMgr = repoindex.pageManager.repoManager;
var pkgMgr = repoindex.pageManager.packageManager;
repoMgr.removeEntries();
pkgMgr.removeEntries();
for(var i1 = 0; i1 < values.length; ++i1) {
this.repos[values[i1].name] = values[i1];
var repoEntry = repoMgr.addEntry(values[i1]);
var packages = repoEntry.info.packages;
for(var i2 = 0; i2 < packages.length; ++i2) {
pkgMgr.addEntry(repoEntry, packages[i2]);
var reposInOrder = [];
for(var repoName in value) {
if(value.hasOwnProperty(repoName)) {
reposInOrder.push({name: repoName, info: value[repoName]});
//var repoInfo = value[repoName];
}
}
reposInOrder.sort(function(lhs, rhs) {
if(lhs === rhs) {
return 1;
} else if(lhs.info.index !== undefined) {
if(rhs.info.index !== undefined) {
return lhs.info.index > rhs.info.index;
} else {
return -1;
}
} else if(rhs.info.index !== undefined) {
return 1;
} else {
return lhs.name > rhs.name;
}
});
for(var i = 0; i < reposInOrder.length; ++i) {
var repoName = reposInOrder[i].name;
var repoInfo = reposInOrder[i].info;
var repoEntry = repoMgr.addEntry(repoName, repoInfo);
var packages = repoInfo.packages;
for(var packageName in packages) {
if(value.hasOwnProperty(repoName)) {
var packageInfo = packages[packageName];
pkgMgr.addEntry(repoEntry, packageName, packageInfo);
}
}
}
this.hasBasicRepoInfo = true;
pkgMgr.invalidate();
repoMgr.invalidate();
repoMgr.applyRepoStatusChange();
};
this.usePackageInfo = function(values) {
if(!Array.isArray(values)) {
repoindex.pageManager.addError("Server replied insufficiant package info.");
return false;
}
for(var i = 0; i < values.length; ++i) {
var value = values[i];
var repo = repoindex.client.repos[value.repo];
if(repo) {
var package = repo.packages[value.name];
if(package) {
if(value.basics) {
package.basics = value.basics;
}
if(value.details) {
package.details = value.details;
}
} else {
repoindex.pageManager.addError("Package info replied by server refers to unknown package.");
}
} else {
repoindex.pageManager.addError("Package info replied by server refers to unknown repository.");
}
}
if(value.error) {
repoindex.pageManager.addError("Server replied error in package info: " + value.error);
}
// updating table rows or any other GUI elements is done via callbacks
};
this.useGroupInfo = function(values) {
@ -293,13 +391,80 @@
groupMgr.useRequestedData();
};
this.useSuggestions = function(values) {
if(Array.isArray(values)) {
for(var i1 = 0; i1 < values.length; ++i1) {
var value = values[i1];
var repoName = value.repo;
var suggestions = value.res;
if(Array.isArray(suggestions)) {
if(suggestions.length > 0) {
var repoEntry = repoindex.pageManager.repoManager.entryByName(repoName);
if(repoEntry) {
var pkgMgr = repoindex.pageManager.packageManager;
for(var i2 = 0; i2 < suggestions.length; ++i2) {
var packageName = suggestions[i2];
if(!pkgMgr.entryByName(packageName)) {
var packageInfo = this.getPackageInfo(repoName, packageName);
pkgMgr.addEntry(repoEntry, packageName, packageInfo);
}
}
pkgMgr.invalidate();
} else {
repoindex.pageManager.addError("There is no repo entry for the suggestions replied by the server.");
}
var entries = pkgMgr.entries;
entries.sort(function(lhs, rhs) {
return lhs.name > rhs.name;
});
for(var i = 0; i < entries.length; ++i) {
entries[i].index = i;
}
}
} else {
// TODO: handle error
}
}
} else if(Array.isArray(values.errors)) {
for(var i = 0; i < values.errors.length; ++i) {
repoindex.pageManager.addError("Error replied by server in response of suggestion request: " + value.errors[i]);
}
} else {
repoindex.pageManager.addError("Server replied insufficiant suggestion reply.");
}
};
this.useAurSuggestions = function(suggestions) {
if(Array.isArray(suggestions)) {
if(suggestions.length > 0) {
var aurEntry = repoindex.pageManager.repoManager.entryByName("AUR");
if(aurEntry) {
var pkgMgr = repoindex.pageManager.packageManager;
for(var i = 0; i < suggestions.length; ++i) {
var packageName = suggestions[i];
if(!pkgMgr.entryByName(packageName)) {
var packageInfo = this.getPackageInfo("AUR", packageName);
pkgMgr.addEntry(aurEntry, packageName, packageInfo);
}
}
pkgMgr.invalidate();
} else {
// there is no AUR entry but we requested AUR suggestions
// -> shouldn't happen
}
}
} else {
// TODO: handle error
}
};
};
// create a global client used within the entire document
repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + document.domain + ":1234");
if(!repoindex.isLoopback(document.domain)) {
// hide stop button if server is not on loopback interface
document.getElementById("nav_stop_server").style.display = "none";
document.getElementById("nav_server").style.display = "none";
}
return repoindex;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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