fixed bugs

This commit is contained in:
Martchus 2016-02-25 22:53:33 +01:00
parent 2dd0294bb7
commit 920eddbeed
29 changed files with 921 additions and 378 deletions

4
.gitignore vendored
View File

@ -39,3 +39,7 @@ Makefile*
# documentation
/doc
# testing
/testing
/cert

View File

@ -52,7 +52,7 @@ private:
const PackageOrigin m_origin;
};
void AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions)
DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions)
{
QFileInfo pathInfo(databasePath());
if(pathInfo.isDir()) {
@ -71,7 +71,7 @@ void AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &
if(descFile.open(QFile::ReadOnly)) {
descData << descFile.readAll();
} else {
// TODO: error handling (can't open pkg file)
return DatabaseError::UnableToOpenDescFile;
}
}
if(!descData.isEmpty()) {
@ -79,7 +79,7 @@ void AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &
}
dbDir.cdUp();
} else {
// TODO: error handling (can't enter pkg dir)
return DatabaseError::UnableToEnterDirectory;
}
}
} else if(pathInfo.isFile()) {
@ -101,7 +101,7 @@ void AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &
if(descEntry->isFile()) {
descData << static_cast<const KArchiveFile *>(descEntry)->data();
} else {
// there shouldn't be any subdirs
// there shouldn't be any subdirs anyways
}
}
}
@ -109,22 +109,25 @@ void AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &
descriptions << qMakePair(pkgDirName, descData);
}
} else {
// there shouldn't be any files
// there shouldn't be any files anyways
}
}
}
} else {
// TODO: error handling (can't open sync db file)
return DatabaseError::UnableToOpenArchive;
}
} else {
// TODO: error handling
return DatabaseError::NotFound;
}
return DatabaseError::NoError;
}
AlpmPackageLoader::AlpmPackageLoader(AlpmDatabase *repository, PackageOrigin origin)
AlpmPackageLoader::AlpmPackageLoader(AlpmDatabase *repository, PackageOrigin origin) :
m_db(repository)
{
repository->loadDescriptions(m_descriptions);
m_future = QtConcurrent::map(m_descriptions, LoadPackage(repository, origin));
if((m_error = repository->loadDescriptions(m_descriptions)) == DatabaseError::NoError) {
m_future = QtConcurrent::map(m_descriptions, LoadPackage(repository, origin));
}
}
/*!
@ -138,7 +141,7 @@ AlpmDatabase::AlpmDatabase(const QString &name, const QString &dbPath, Repositor
m_sigLevel = sigLevel;
}
AlpmPackageLoader *AlpmDatabase::init()
AlpmPackageLoader *AlpmDatabase::internalInit()
{
// set description, determine origin
PackageOrigin origin;
@ -157,15 +160,16 @@ AlpmPackageLoader *AlpmDatabase::init()
// wipe current packages
wipePackages();
// initialization of packages is done concurrently via AlpmPackageLoader: ~ 4 sec
// initialization of packages is done concurrently via AlpmPackageLoader
return new AlpmPackageLoader(this, origin);
// without concurrency: ~ 12 sec
// without concurrency
//QList<QPair<QString, QList<QByteArray> > > descriptions;
//loadDescriptions(descriptions);
//for(const auto &description : descriptions) {
// addPackageFromDescription(description.first, description.second, origin);
//}
//emit initialized();
//return nullptr;
}
@ -209,8 +213,9 @@ QNetworkRequest AlpmDatabase::filesDatabaseRequest()
*/
void AlpmDatabase::downloadDatabase(const QString &targetDir, bool filesDatabase)
{
QWriteLocker locker(lock());
if(serverUrls().isEmpty()) {
return;
return; // no server URLs available
}
cerr << shchar << "Downloading " << (filesDatabase ? "files" : "regular") << " database for [" << name().toLocal8Bit().data() << "] from mirror " << serverUrls().front().toLocal8Bit().data() << " ..." << endl;
QNetworkReply *reply = networkAccessManager().get(filesDatabase ? filesDatabaseRequest() : regularDatabaseRequest());
@ -248,28 +253,42 @@ std::unique_ptr<Package> AlpmDatabase::emptyPackage()
void AlpmDatabase::databaseDownloadFinished()
{
auto *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
bool filesDatabase = reply->property("filesDatabase").toBool();
QReadLocker locker(lock());
if(reply->error() == QNetworkReply::NoError) {
QString newDatabasePath;
cerr << "Downloaded database file for [" << name().toLocal8Bit().data() << "] successfully." << endl;
QString newDatabasePath = m_downloadTargetDir % QChar('/') % name() % (filesDatabase ? QStringLiteral(".files") : QStringLiteral(".db"));
newDatabasePath = m_downloadTargetDir % QChar('/') % name() % (filesDatabase ? QStringLiteral(".files") : QStringLiteral(".db"));
if(QFile::exists(newDatabasePath)) {
QString backupFile(newDatabasePath + QStringLiteral(".bak"));
QFile::remove(backupFile);
if(!QFile::rename(newDatabasePath, backupFile)) {
cerr << "An IO error occured when storing database file for [" << name().toLocal8Bit().data() << "]: Unable to rename present database file." << endl;
reply = nullptr;
return;
}
}
locker.unlock();
QFile outputFile(newDatabasePath);
if(outputFile.open(QFile::WriteOnly) && outputFile.write(reply->readAll())) {
outputFile.close();
m_dbPath = newDatabasePath;
init();
{
QWriteLocker locker(lock());
m_dbPath = newDatabasePath;
}
initAsSoonAsPossible();
} else {
locker.relock();
cerr << "An IO error occured when storing database file for [" << name().toLocal8Bit().data() << "]: Unable to create/write output file." << endl;
}
} else {
cerr << "An error occured when dwonloading database file for [" << name().toLocal8Bit().data() << "]: " << reply->errorString().toLocal8Bit().data() << endl;
if(filesDatabase && reply->error() == QNetworkReply::ContentNotFoundError) {
cerr << "-> Attempting to download regular database file instead of files database file." << endl;
locker.unlock();
downloadDatabase(m_downloadTargetDir, false);
}
}
}

View File

@ -17,21 +17,53 @@ class AlpmPackage;
class AlpmDatabase;
class LoadPackage;
enum class DatabaseError
{
NoError,
NotFound,
NoAccess,
UnableToOpenArchive,
UnableToOpenDescFile,
UnableToEnterDirectory
};
class AlpmPackageLoader : public PackageLoader
{
public:
AlpmPackageLoader(AlpmDatabase *db, PackageOrigin origin);
AlpmDatabase *database() const;
DatabaseError error() const;
private:
AlpmDatabase *const m_db;
DatabaseError m_error;
QList<QPair<QString, QList<QByteArray> > > m_descriptions;
};
/*!
* \brief Returns the associated database.
*/
inline AlpmDatabase *AlpmPackageLoader::database() const
{
return m_db;
}
/*!
* \brief Returns the error status.
*/
inline DatabaseError AlpmPackageLoader::error() const
{
return m_error;
}
class AlpmDatabase : public Repository
{
Q_OBJECT
friend class AlpmPackageLoader;
public:
explicit AlpmDatabase(const QString &name, const QString &dbPath, RepositoryUsage usage, SignatureLevel sigLevel, uint32 index = invalidIndex, QObject *parent = nullptr);
AlpmPackageLoader *init();
AlpmPackageLoader *internalInit();
RepositoryType type() const;
PackageDetailAvailability requestsRequired(PackageDetail packageDetail) const;
@ -44,9 +76,6 @@ public:
void downloadDatabase(const QString &targetDir, bool filesDatabase = true);
void refresh(const QString &targetDir);
signals:
void initiated();
protected:
std::unique_ptr<Package> emptyPackage();
@ -54,7 +83,7 @@ private slots:
void databaseDownloadFinished();
private:
void loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions);
DatabaseError loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions);
QNetworkRequest regularDatabaseRequest();
QNetworkRequest filesDatabaseRequest();

View File

@ -157,7 +157,8 @@ Config::Config() :
m_websocketServerListeningAddr(QHostAddress::LocalHost),
m_websocketServerListeningPort(1234),
m_serverInsecure(false),
m_reposFromPacmanConf(false),
m_localEnabled(true),
m_reposFromPacmanConfEnabled(false),
m_aurEnabled(true),
m_verbose(false),
m_runServer(false)
@ -252,7 +253,8 @@ void Config::loadFromConfigFile(const QString &configFilePath)
m_serverKeyFile = serverObj.value(QStringLiteral("keyFile")).toString(m_serverKeyFile);
m_serverInsecure = serverObj.value(QStringLiteral("insecure")).toBool(m_serverInsecure);
auto reposObj = mainObj.value(QStringLiteral("repos")).toObject();
m_reposFromPacmanConf = serverObj.value(QStringLiteral("fromPacmanConfig")).toBool(m_reposFromPacmanConf);
m_localEnabled = reposObj.value(QStringLiteral("localEnabled")).toBool(m_localEnabled);
m_reposFromPacmanConfEnabled = reposObj.value(QStringLiteral("fromPacmanConfig")).toBool(m_reposFromPacmanConfEnabled);
for(const auto &repo : reposObj.value(QStringLiteral("add")).toArray()) {
m_repoEntries << RepoEntry();
m_repoEntries.back().load(repo);
@ -323,7 +325,8 @@ void Config::loadFromArgs(const ConfigArgs &args)
}
RepoEntry::RepoEntry() :
m_sigLevel(SignatureLevel::UseDefault)
m_sigLevel(SignatureLevel::UseDefault),
m_ignored(false)
{}
/*!
@ -353,6 +356,7 @@ void RepoEntry::load(const QJsonValue &jsonValue)
m_sigLevel = Manager::parseSigLevel(sigLevelValue.toString().toLocal8Bit().data());
}
m_maxDatabaseAge = TimeSpan::fromSeconds(obj.value(QStringLiteral("maxAge")).toDouble());
m_ignored = obj.value(QStringLiteral("ignored")).toBool(m_ignored);
}
} // namespace Alpm

View File

@ -68,6 +68,7 @@ public:
const QStringList &upgradeSources() const;
SignatureLevel sigLevel() const;
ChronoUtilities::TimeSpan maxDatabaseAge() const;
bool isIgnored() const;
void load(const QJsonValue &jsonValue);
private:
@ -79,6 +80,7 @@ private:
QStringList m_upgradeSources;
SignatureLevel m_sigLevel;
ChronoUtilities::TimeSpan m_maxDatabaseAge;
bool m_ignored;
};
inline const QString &RepoEntry::name() const
@ -121,6 +123,11 @@ inline ChronoUtilities::TimeSpan RepoEntry::maxDatabaseAge() const
return m_maxDatabaseAge;
}
inline bool RepoEntry::isIgnored() const
{
return m_ignored;
}
class Config
{
public:
@ -136,7 +143,8 @@ public:
const QString &serverCertFile() const;
const QString &serverKeyFile() const;
bool serverInsecure() const;
bool reposFromPacmanConf() const;
bool isLocalDatabaseEnabled() const;
bool areReposFromPacmanConfEnabled() const;
const QList<RepoEntry> &repoEntries() const;
bool isAurEnabled() const;
bool isVerbose() const;
@ -161,7 +169,8 @@ private:
bool m_serverInsecure;
QList<RepoEntry> m_repoEntries;
bool m_reposFromPacmanConf;
bool m_localEnabled;
bool m_reposFromPacmanConfEnabled;
bool m_aurEnabled;
bool m_verbose;
bool m_runServer;
@ -217,9 +226,14 @@ inline bool Config::serverInsecure() const
return m_serverInsecure;
}
inline bool Config::reposFromPacmanConf() const
inline bool Config::isLocalDatabaseEnabled() const
{
return m_reposFromPacmanConf;
return m_localEnabled;
}
inline bool Config::areReposFromPacmanConfEnabled() const
{
return m_reposFromPacmanConfEnabled;
}
inline const QList<RepoEntry> &Config::repoEntries() const

View File

@ -23,6 +23,7 @@
#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <functional>
using namespace std;
using namespace IoUtilities;
@ -59,13 +60,16 @@ inline ostream &operator <<(ostream &stream, const QString &str)
* \param rootdir Specifies the root directory.
* \param dbpath Specifies the database directory.
*/
Manager::Manager(const Config &config) :
Manager::Manager(const Config &config, QObject *parent) :
QObject(parent),
m_config(config),
m_writeCacheBeforeGone(true),
m_sigLevel(defaultSigLevel),
m_localFileSigLevel(SignatureLevel::UseDefault)
{
addLocalDatabase();
if(config.isLocalDatabaseEnabled()) {
addLocalDatabase();
}
if(config.isAurEnabled()) {
m_userRepo = make_unique<UserRepository>();
}
@ -309,7 +313,7 @@ void Manager::addDataBasesFromPacmanConfig()
} else if(dbName.startsWith(QLatin1String("aur"), Qt::CaseInsensitive)) {
cerr << shchar << "Error: Unable to add database from pacman config: The database name mustn't start with \"aur\" because this name is reserved for the Arch Linux User Repository." << endl;
} else if(m_syncDbMap.count(dbName)) {
cerr << shchar << "Error: Unable to add database from pacman config: Database names must be unique. Ignoring second occurance of database \"" << scope.first << "\"." << endl;
cerr << shchar << "Error: Unable to add database from pacman config: Database names must be unique. Ignoring second occurance of database [" << scope.first << "]." << endl;
} else {
// read sig level and usage
const auto &sigLevelStr = lastValue(scope.second, sigLevelKey);
@ -320,10 +324,11 @@ void Manager::addDataBasesFromPacmanConfig()
m_syncDbs.emplace_back(make_unique<AlpmDatabase>(dbName, findDatabasePath(dbName, false, false), usage, sigLevel, m_syncDbs.size() + 1));
AlpmDatabase *emplacedDb = m_syncDbs.back().get();
m_syncDbMap.emplace(dbName, emplacedDb);
connectRepository(emplacedDb);
cerr << shchar << "Added [" << dbName << "]" << endl;
if(usage & RepositoryUsage::Upgrade) {
if(localDatabase() && usage & RepositoryUsage::Upgrade) {
// -> db is used to upgrade local database
localDataBase()->upgradeSources() << emplacedDb;
localDatabase()->upgradeSources() << emplacedDb;
}
// add servers
@ -352,9 +357,9 @@ void Manager::addDataBasesFromPacmanConfig()
}
}
try {
for(auto &scope : includedIni.data()) {
if(scope.first.empty()) {
for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) {
for(auto &nestedScope : includedIni.data()) {
if(nestedScope.first.empty()) {
for(auto range = nestedScope.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);
@ -384,9 +389,18 @@ void Manager::addDatabasesFromRepoIndexConfig()
{
// 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_syncDbMap.at(repoEntry.name());
AlpmDatabase *syncDb;
if(m_localDb && repoEntry.name() == QLatin1String("local")) {
syncDb = m_localDb.get();
} else {
try {
syncDb = m_syncDbMap.at(repoEntry.name());
} catch(const out_of_range &) {
syncDb = nullptr;
}
}
if(syncDb) {
cerr << shchar << "Applying config for database [" << syncDb->name() << ']' << endl;
if(!repoEntry.databasePath().isEmpty()) {
syncDb->setDatabasePath(repoEntry.databasePath());
@ -396,12 +410,21 @@ void Manager::addDatabasesFromRepoIndexConfig()
}
syncDb->setPackagesDirectory(repoEntry.packageDir());
syncDb->setSourcesDirectory(repoEntry.sourceDir());
} catch(const out_of_range &) {
if(!repoEntry.maxDatabaseAge().isNull()) {
syncDb->setMaxPackageAge(repoEntry.maxDatabaseAge());
}
} else {
if(repoEntry.name().compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
cerr << shchar << "Error: Unable to add database from repo index config: The database name mustn't be \"local\" because this name is reserved for the local database." << endl;
} else if(repoEntry.name().startsWith(QLatin1String("aur"), Qt::CaseInsensitive)) {
cerr << shchar << "Error: Unable to add database from repo index config: The database name mustn't start with \"aur\" because this name is reserved for the Arch Linux User Repository." << endl;
} else {
// repo name mustn't be empty
if(repoEntry.name().isEmpty()) {
cerr << shchar << "Warning: Ignoring empty repository entry in configuration file." << endl;
continue;
}
// determine path of db file
// -> currently just use the file from pacman dir, TODO: download syncdata base
QString dbPath;
@ -414,7 +437,7 @@ void Manager::addDatabasesFromRepoIndexConfig()
// add sync db to internal map (use as index size + 1 because the local database has index 0)
m_syncDbs.emplace_back(make_unique<AlpmDatabase>(repoEntry.name(), dbPath, RepositoryUsage::None, repoEntry.sigLevel(), m_syncDbs.size() + 1));
syncDb = m_syncDbs.back().get();
connectRepository(syncDb = m_syncDbs.back().get());
m_syncDbMap.emplace(repoEntry.name(), syncDb);
syncDb->setSourcesDirectory(repoEntry.sourceDir());
@ -453,55 +476,81 @@ void Manager::addDatabasesFromRepoIndexConfig()
* \brief Initiates all ALPM data bases.
* \remarks Must be called, after all relevant sync databases have been added (eg. via applyPacmanConfig()).
*/
void Manager::initAlpmDataBases(bool computeRequiredBy)
void Manager::initAlpmDataBases()
{
{
// call the init method
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "Initializing ALPM databases ... ";
cerr.flush();
// connect computeRequiredBy()
if(m_config.runServer()) {
if(localDatabase()) {
QObject::connect(localDatabase(), &AlpmDatabase::initialized, bind(&Manager::computeRequiredBy, this, localDatabase()));
}
QList<PackageLoader *> loaders;
loaders.reserve(m_syncDbMap.size() + 1);
loaders << localDataBase()->init();
for(auto &syncDbEntry : m_syncDbMap) {
loaders << syncDbEntry.second->init();
QObject::connect(syncDbEntry.second, &AlpmDatabase::initialized, bind(&Manager::computeRequiredBy, this, syncDbEntry.second));
}
for(auto *loader : loaders) {
if(loader) {
loader->future().waitForFinished();
delete loader;
}
// call the init method
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "Initializing ALPM databases ... ";
cerr.flush();
}
QList<AlpmPackageLoader *> loaders;
loaders.reserve(m_syncDbMap.size() + (localDatabase() ? 1 : 0));
if(localDatabase()) {
loaders << static_cast<AlpmPackageLoader *>(localDatabase()->init());
}
for(auto &syncDbEntry : m_syncDbMap) {
loaders << static_cast<AlpmPackageLoader *>(syncDbEntry.second->init());
}
for(auto *loader : loaders) {
if(loader) {
loader->future().waitForFinished();
switch(loader->error()) {
case DatabaseError::NoError:
break;
case DatabaseError::NotFound:
if(loader->database()->serverUrls().isEmpty()) {
// there are no server URLs associated -> print error
cerr << endl << shchar << "Unable to locate database file for ALPM database [" << loader->database()->name().toLocal8Bit().data() << "]" << endl;
} else {
// try to download the database from server
loader->database()->downloadDatabase(m_config.storageDir() + QStringLiteral("/sync"));
}
break;
default:
cerr << endl << shchar << "Unable to initialize ALPM database [" << loader->database()->name().toLocal8Bit().data() << "]" << endl;
// TODO: print the cause of the problem
}
delete loader;
}
for(auto &syncDbEntry : m_syncDbMap) {
syncDbEntry.second->updateGroups();
}
}
for(auto &syncDbEntry : m_syncDbMap) {
syncDbEntry.second->updateGroups();
}
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "DONE" << endl;
}
}
// compute required-by and optional-for
if(computeRequiredBy) {
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "Calculating required-by/optional-for ... ";
cerr.flush();
}
QList<QFuture<void> > futures;
futures.reserve(m_syncDbMap.size() + 1);
futures << localDataBase()->computeRequiredBy(*this);
for(auto &syncDbEntry : m_syncDbMap) {
futures << syncDbEntry.second->computeRequiredBy(*this);
}
for(auto &future : futures) {
future.waitForFinished();
}
if(m_config.isVerbose() || m_config.runServer()) {
cerr << "DONE" << endl;
/*!
* \brief Computes required-by and optional-for fields for the packages of the specified \a repo.
* \remarks
* - The computing is performed asynchronously so this method will return immidiately.
* - Do not call this method if the specified \a repo is busy.
*/
void Manager::computeRequiredBy(Repository *repo)
{
// find relevant databases
QList<Repository *> relevantDbs;
if(repo == localDatabase()) {
relevantDbs.reserve(1);
relevantDbs << repo;
} else {
relevantDbs.reserve(m_syncDbs.size());
for(auto &syncDb : m_syncDbs) {
relevantDbs << syncDb.get();
}
}
repo->computeRequiredBy(relevantDbs);
}
/*!
@ -552,6 +601,9 @@ void Manager::writeCache()
}
}
/*!
* \brief Restores the cache for all repositories where caching makes sense.
*/
void Manager::restoreCache()
{
// could iterate through all repos and check isCachingUseful() but
@ -570,22 +622,32 @@ void Manager::restoreCache()
}
}
/*!
* \brief Removes outdated packages from the cache.
* \remarks Currently only the AUR cache is affected.
*/
void Manager::cleanCache()
{
// currently clear only AUR cache
if(userRepository()) {
userRepository()->cleanOutdatedPackages();
}
}
/*!
* \brief Removes all cached packages.
* \remarks Currently only the AUR cache is affected.
*/
void Manager::wipeCache()
{
// currently wipe only AUR cache
if(userRepository()) {
userRepository()->wipePackages();
}
}
/*!
* \brief Cleans and writes the cache.
* \remarks Automatically called if automatic cache maintenance is enabled.
*/
void Manager::maintainCache()
{
cleanCache();
@ -593,6 +655,65 @@ void Manager::maintainCache()
writeCache();
}
/*!
* \brief Returns whether automatic updates are enabled.
* \remarks If enabled, the ALPM databases will be updated frequently.
*/
bool Manager::isAutoUpdateEnabled() const
{
return m_updateTimer && m_updateTimer->isActive();
}
/*!
* \brief Sets whether automatic updates are enabled.
* \sa isAutoUpdateEnabled()
*/
void Manager::setAutoUpdateEnabled(bool enabled)
{
if(isAutoCacheMaintenanceEnabled() != enabled) {
if(enabled) {
if(!m_updateTimer) {
m_updateTimer = make_unique<QTimer>();
m_updateTimer->setInterval(5 * 60 * 1000);
QObject::connect(m_updateTimer.get(), &QTimer::timeout, bind(&Manager::updateAlpmDatabases, this));
}
m_updateTimer->start();
} else {
m_updateTimer->stop();
}
}
}
/*!
* \brief Triggers updating ALPM databases with outdated packages.
*/
void Manager::updateAlpmDatabases()
{
if(localDatabase()) {
if(localDatabase()->hasOutdatedPackages()) {
localDatabase()->init();
}
}
for(auto &syncDbEntry : m_syncDbMap) {
if(syncDbEntry.second->hasOutdatedPackages()) {
syncDbEntry.second->refresh(m_config.storageDir() + QStringLiteral("/sync"));
}
}
}
/*!
* \brief Triggers updating all ALPM databases (regardless whether packages are outdated or not).
*/
void Manager::forceUpdateAlpmDatabases()
{
if(localDatabase()) {
localDatabase()->init();
}
for(auto &syncDbEntry : m_syncDbMap) {
syncDbEntry.second->refresh(m_config.storageDir() + QStringLiteral("/sync"));
}
}
/*!
* \brief Returns a list of all sync databases.
* \remarks Sync databases must be registered with parsePacmanConfig() before.
@ -602,37 +723,48 @@ const std::map<QString, AlpmDatabase *> &Manager::syncDatabases() const
return m_syncDbMap; // m_syncDbs has been filled when the databases were registered
}
QJsonObject emptyJsonObject;
/*!
* \brief Returns basic information about all repositories known to the manager.
*
* The results include the local database ("local") and the names of
* the registered sync databases.
*/
const QJsonObject &Manager::basicRepoInfo() const
{
QMutexLocker locker(&m_basicRepoInfoMutex);
if(m_basicRepoInfo.isEmpty()) {
QMutexLocker locker(&m_basicRepoInfoMutex);
if(m_basicRepoInfo.isEmpty()) {
// add local data base
{
QReadLocker locker(localDataBase()->lock());
m_basicRepoInfo.insert(localDataBase()->name(), localDataBase()->basicInfo());
if(localDatabase()) {
if(localDatabase()->isBusy()) {
m_basicRepoInfo.insert(QStringLiteral("local"), QStringLiteral("incomplete"));
} else {
QReadLocker locker(localDatabase()->lock());
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
QReadLocker locker(syncDb.second->lock());
auto usage = syncDb.second->usage();
if((usage & RepositoryUsage::Sync) || (usage & RepositoryUsage::Install) || (usage & RepositoryUsage::Upgrade)) {
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
if(syncDb.second->isBusy()) {
m_basicRepoInfo.insert(syncDb.first, QStringLiteral("incomplete"));
} else {
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
// check if the "sync" database is actually used for syncing
QReadLocker locker(syncDb.second->lock());
auto usage = syncDb.second->usage();
if((usage & RepositoryUsage::Sync) || (usage & RepositoryUsage::Install) || (usage & RepositoryUsage::Upgrade)) {
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
} else {
m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo());
}
}
}
// add AUR
if(userRepository()) {
QReadLocker locker(userRepository()->lock());
m_basicRepoInfo.insert(userRepository()->name(), userRepository()->basicInfo());
if(userRepository()->isBusy()) {
m_basicRepoInfo.insert(QStringLiteral("aur"), QStringLiteral("incomplete"));
} else {
QReadLocker locker(userRepository()->lock());
m_basicRepoInfo.insert(userRepository()->name(), userRepository()->basicInfo());
}
}
}
}
@ -644,70 +776,139 @@ const QJsonObject &Manager::basicRepoInfo() const
* \remarks Does not request any information and hence will only return information
* which does not need to be requested or has been requested yet.
*/
const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const
QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const
{
QJsonArray results;
for(auto i = pkgSelection.constBegin(), end = pkgSelection.constEnd(); i != end; ++i) {
QJsonObject res;
res.insert(QStringLiteral("repo"), i.key());
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 res;
res.insert(QStringLiteral("name"), pkgName);
res.insert(QStringLiteral("repo"), repo->name());
if(repo->isBusy()) {
// specified repository is busy
res.insert(QStringLiteral("error"), QStringLiteral("busy"));
results << res;
} else {
QReadLocker locker(repo->lock());
for(const auto &entry : i.value().toArray()) {
const auto entryObj = entry.toObject();
const auto pkgName = entryObj.value(QStringLiteral("name")).toString();
const auto index = entryObj.value(QStringLiteral("index"));
if(!index.isNull() && !index.isUndefined()) {
res.insert(QStringLiteral("index"), index);
}
if(auto *pkg = repo->packageByName(pkgName)) {
if(part & Basics) {
res.insert(QStringLiteral("basics"), pkg->basicInfo());
if(!pkgName.isEmpty()) {
res.insert(QStringLiteral("name"), pkgName);
if(auto *pkg = repo->packageByName(pkgName)) {
if(part & Basics) {
res.insert(QStringLiteral("basics"), pkg->basicInfo());
}
if(part & Details) {
res.insert(QStringLiteral("details"), pkg->detailedInfo());
}
} else {
res.insert(QStringLiteral("error"), QStringLiteral("na"));
}
if(part & Details) {
res.insert(QStringLiteral("details"), pkg->detailedInfo());
}
} else {
res.insert(QStringLiteral("error"), QStringLiteral("na"));
results << res;
}
results << res;
}
}
} else {
// specified repository can not be found
QJsonObject errorObj;
errorObj.insert(QStringLiteral("repo"), i.key());
errorObj.insert(QStringLiteral("error"), QStringLiteral("na"));
results << errorObj;
res.insert(QStringLiteral("error"), QStringLiteral("na"));
results << res;
}
}
return results;
}
QJsonObject incompleteGroupInfo(const QString &repo)
{
QJsonObject busyObject;
busyObject.insert(QStringLiteral("repo"), QStringLiteral("local"));
busyObject.insert(QStringLiteral("incomplete"), true);
return busyObject;
}
/*!
* \brief Returns group information for the local database and all registred sync databases.
*/
const QJsonArray &Manager::groupInfo() const
{
QMutexLocker locker(&m_groupInfoMutex);
if(m_groupInfo.empty()) {
QMutexLocker locker(&m_groupInfoMutex);
if(m_groupInfo.empty()) {
m_groupInfo << localDataBase()->groupInfo();
if(localDatabase()) {
if(localDatabase()->isBusy()) {
m_groupInfo << incompleteGroupInfo(QStringLiteral("local"));
} else {
QReadLocker locker(localDatabase()->lock());
m_groupInfo << localDatabase()->groupInfo();
}
}
for(const auto &db : m_syncDbMap) {
m_groupInfo << db.second->groupInfo();
if(db.second->isBusy()) {
m_groupInfo << incompleteGroupInfo(db.first);
} else {
QReadLocker locker(db.second->lock());
m_groupInfo << db.second->groupInfo();
}
}
}
}
return m_groupInfo;
}
/*!
* \brief Internally called to invalidate cached JSON serialization.
*
* This method is called after a repository has been initialized or the
* required-by computition has finished.
*/
void Manager::invalidateCachedJsonSerialization()
{
m_basicRepoInfo = QJsonObject();
m_groupInfo = QJsonArray();
}
/*!
* \brief Internally called to emit the updatesAvailable() signal.
*
* Emits the signal only if no repository is busy.
*/
void Manager::emitUpdatesAvailable()
{
if(localDatabase() && localDatabase()->isBusy()) {
return;
}
if(userRepository() && userRepository()->isBusy()) {
return;
}
for(const auto &syncDb : m_syncDbs) {
if(syncDb->isBusy()) {
return;
}
}
emit updatesAvailable();
}
/*!
* \brief Internally called after a repository has been added to connect
* the available() signal.
*/
void Manager::connectRepository(Repository *repo)
{
connect(repo, &Repository::available, this, &Manager::invalidateCachedJsonSerialization);
connect(repo, &Repository::requiredByComputed, this, &Manager::emitUpdatesAvailable);
}
/*!
* \brief Add the local database.
*/
void Manager::addLocalDatabase()
{
m_localDb = make_unique<AlpmDatabase>(QStringLiteral("local"), config().alpmDbPath() % QStringLiteral("/local"), RepositoryUsage::None, SignatureLevel::UseDefault, 0);
connectRepository(m_localDb.get());
}
/*!
@ -726,7 +927,7 @@ void Manager::removeAllDatabases()
const AlpmDatabase *Manager::databaseByName(const QString &dbName) const
{
if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
return localDataBase();
return localDatabase();
} else {
try {
return m_syncDbMap.at(dbName);
@ -742,7 +943,7 @@ const AlpmDatabase *Manager::databaseByName(const QString &dbName) const
AlpmDatabase *Manager::databaseByName(const QString &dbName)
{
if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
return localDataBase();
return localDatabase();
} else {
try {
return m_syncDbMap.at(dbName);
@ -758,8 +959,8 @@ AlpmDatabase *Manager::databaseByName(const QString &dbName)
const Repository *Manager::repositoryByName(const QString &name) const
{
if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
return localDataBase();
} else if(config().isAurEnabled() && (name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0)) {
return localDatabase();
} else if(name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0) {
return userRepository();
} else {
try {
@ -776,8 +977,8 @@ const Repository *Manager::repositoryByName(const QString &name) const
Repository *Manager::repositoryByName(const QString &name)
{
if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
return localDataBase();
} else if(config().isAurEnabled() && (name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0)) {
return localDatabase();
} else if(name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0) {
return userRepository();
} else {
try {

View File

@ -7,6 +7,8 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QMutex>
#include <QAtomicInteger>
#include <QObject>
#include <map>
#include <memory>
@ -20,8 +22,10 @@ class UserRepository;
class AlpmDatabase;
enum class RepositoryUsage;
class Manager
class Manager : public QObject
{
Q_OBJECT
public:
enum PackageInfoPart {
None = 0x0,
@ -30,10 +34,7 @@ public:
};
Q_DECLARE_FLAGS(PackageInfoParts, PackageInfoPart)
explicit Manager(const Config &config);
Manager(const Manager &other) = delete;
Manager(Manager &&other) = delete;
Manager &operator =(const Manager &other) = delete;
explicit Manager(const Config &config, QObject *parent = nullptr);
~Manager();
// configuration, signature level, etc
@ -49,7 +50,8 @@ public:
void removeAllDatabases();
void addDataBasesFromPacmanConfig();
void addDatabasesFromRepoIndexConfig();
void initAlpmDataBases(bool computeRequiredBy);
void initAlpmDataBases();
void computeRequiredBy(Repository *repo);
// caching
bool writeCacheBeforeGone() const;
@ -62,7 +64,11 @@ public:
void wipeCache();
void maintainCache();
// refreshing
// updating
bool isAutoUpdateEnabled() const;
void setAutoUpdateEnabled(bool enabled);
void updateAlpmDatabases();
void forceUpdateAlpmDatabases();
// package lookup
AlpmPackage *packageFromDatabase(const QString &dbName, const QString &pkgName);
@ -73,8 +79,8 @@ public:
const Package *packageProviding(const Dependency &dependency) const;
// repository lookup
const AlpmDatabase *localDataBase() const;
AlpmDatabase *localDataBase();
const AlpmDatabase *localDatabase() const;
AlpmDatabase *localDatabase();
const std::map<QString, AlpmDatabase *> &syncDatabases() const;
const AlpmDatabase *databaseByName(const QString &dbName) const;
AlpmDatabase *databaseByName(const QString &dbName);
@ -86,15 +92,25 @@ public:
QString proposedDatabasePath(const QString &name, bool files) const;
// JSON serialization, handling JSON requests
const QJsonObject basicRepoInfo(const Repository *packageSource) const;
const QJsonObject &basicRepoInfo() const;
const QJsonArray packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const;
QJsonArray packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const;
const QJsonArray &groupInfo() const;
signals:
void updatesAvailable();
private slots:
void invalidateCachedJsonSerialization();
void emitUpdatesAvailable();
private:
void connectRepository(Repository *repo);
private:
const Config &m_config;
bool m_writeCacheBeforeGone;
std::unique_ptr<QTimer> m_cacheTimer;
std::unique_ptr<QTimer> m_updateTimer;
SignatureLevel m_sigLevel;
SignatureLevel m_localFileSigLevel;
QString m_pacmanCacheDir;
@ -201,7 +217,7 @@ inline UserRepository *Manager::userRepository()
/*!
* \brief Returns the local data base.
*/
inline const AlpmDatabase *Manager::localDataBase() const
inline const AlpmDatabase *Manager::localDatabase() const
{
return m_localDb.get();
}
@ -209,7 +225,7 @@ inline const AlpmDatabase *Manager::localDataBase() const
/*!
* \brief Returns the local data base.
*/
inline AlpmDatabase *Manager::localDataBase()
inline AlpmDatabase *Manager::localDatabase()
{
return m_localDb.get();
}

View File

@ -2,7 +2,6 @@
#include "./alpmdatabase.h"
#include "./utilities.h"
#include "./repository.h"
#include "./manager.h"
#include <QJsonObject>
#include <QJsonValue>
@ -75,18 +74,18 @@ Package::~Package()
{}
/*!
* \brief Computes required-by and optional-for.
* \brief Computes required-by and optional-for fields.
*
* Sources the specified \a relevantRepositores for packages depending on this package.
*/
void Package::computeRequiredBy(Manager &manager)
void Package::computeRequiredBy(const QList<Repository *> &relevantRepositories)
{
if(m_requiredByComputed) {
m_requiredBy.clear();
m_optionalFor.clear();
}
switch(origin()) {
case PackageOrigin::File:
case PackageOrigin::LocalDb:
for(const auto &pkgEntry : manager.localDataBase()->packages()) {
for(const Repository *repo : relevantRepositories) {
for(const auto &pkgEntry : repo->packages()) {
for(const auto &dep : pkgEntry.second->dependencies()) {
if(dep.name == m_name) {
m_requiredBy << pkgEntry.first;
@ -100,27 +99,6 @@ void Package::computeRequiredBy(Manager &manager)
}
}
}
break;
case PackageOrigin::SyncDb:
for(const auto &dbEntry : manager.syncDatabases()) {
for(const auto &pkgEntry : dbEntry.second->packages()) {
for(const auto &dep : pkgEntry.second->dependencies()) {
if(dep.name == m_name) {
m_requiredBy << pkgEntry.first;
break;
}
}
for(const auto &dep : pkgEntry.second->optionalDependencies()) {
if(dep.name == m_name) {
m_optionalFor << pkgEntry.first;
break;
}
}
}
}
break;
default:
; // can not compute this for packages from other sources
}
m_requiredByComputed = true;
}
@ -320,9 +298,9 @@ QJsonObject Package::basicInfo(bool includeRepoAndName) const
QJsonObject Package::detailedInfo() const
{
QJsonObject info;
put(info, QStringLiteral("installAvail"), hasInstallRelatedMetaData());
put(info, QStringLiteral("buildAvail"), hasBuildRelatedMetaData());
put(info, QStringLiteral("srcAvail"), hasSourceRelatedMetaData());
put(info, QStringLiteral("iav"), hasInstallRelatedMetaData());
put(info, QStringLiteral("bav"), hasBuildRelatedMetaData());
put(info, QStringLiteral("sav"), hasSourceRelatedMetaData());
put(info, QStringLiteral("idate"), installDate());
put(info, QStringLiteral("isize"), QJsonValue(static_cast<long long int>(installedSize())));
put(info, QStringLiteral("csize"), QJsonValue(static_cast<long long int>(packageSize())));
@ -334,8 +312,10 @@ QJsonObject Package::detailedInfo() const
put(info, QStringLiteral("optd"), optionalDependencies());
put(info, QStringLiteral("mkd"), makeDependencies());
put(info, QStringLiteral("chkd"), checkDependencies());
put(info, QStringLiteral("requ"), requiredBy());
put(info, QStringLiteral("optf"), optionalFor());
if(isRequiredByComputed()) {
put(info, QStringLiteral("requ"), requiredBy());
put(info, QStringLiteral("optf"), optionalFor());
}
put(info, QStringLiteral("conf"), conflicts());
put(info, QStringLiteral("repl"), replaces());
put(info, QStringLiteral("pack"), packager());
@ -344,6 +324,17 @@ QJsonObject Package::detailedInfo() const
put(info, QStringLiteral("sig"), Utilities::validationMethodsStrings(validationMethods()));
put(info, QStringLiteral("file"), fileName());
put(info, QStringLiteral("files"), files());
put(info, QStringLiteral("fsub"), firstSubmitted());
put(info, QStringLiteral("lmod"), lastModified());
if(!maintainer().isEmpty()) {
put(info, QStringLiteral("main"), maintainer());
}
if(!tarUrl().isEmpty()) {
put(info, QStringLiteral("srctar"), tarUrl());
}
if(votes() >= 0) {
put(info, QStringLiteral("votes"), votes());
}
return info;
}
@ -623,6 +614,7 @@ const map<QString, void(Package::*)(const QStringList &)> Package::m_descMap {
{QStringLiteral("PACKAGER"), &Package::setPackager},
{QStringLiteral("MD5SUM"), &Package::setMd5},
{QStringLiteral("SHA256SUM"), &Package::setSha256},
{QStringLiteral("PGPSIG"), &Package::setPgpSignature},
{QStringLiteral("FILES"), &Package::setFiles},
{QStringLiteral("REASON"), &Package::setInstallReason},
{QStringLiteral("VALIDATION"), &Package::setValidation},
@ -727,13 +719,31 @@ void Package::setPackager(const QStringList &values)
void Package::setMd5(const QStringList &values)
{
if(!values.isEmpty()) {
m_md5 = values.back();
if(!(m_md5 = values.back()).isEmpty()) {
m_validationMethods |= PackageValidation::Md5Sum;
} else {
m_validationMethods &= ~PackageValidation::Md5Sum;
}
}
}
void Package::setSha256(const QStringList &values)
{
if(!values.isEmpty()) {
m_sha256 = values.back();
if(!(m_sha256 = values.back()).isEmpty()) {
m_validationMethods |= PackageValidation::Sha256Sum;
} else {
m_validationMethods &= ~PackageValidation::Sha256Sum;
}
}
}
void Package::setPgpSignature(const QStringList &values)
{
if(!values.isEmpty()) {
if(!(m_pgpSignature = values.back()).isEmpty()) {
m_validationMethods |= PackageValidation::PgpSignature;
} else {
m_validationMethods &= ~PackageValidation::PgpSignature;
}
}
}
void Package::setFiles(const QStringList &values)
@ -754,7 +764,7 @@ void Package::setValidation(const QStringList &values)
} else if(value == QLatin1String("sha256")) {
m_validationMethods = m_validationMethods | PackageValidation::Sha256Sum;
} else if(value == QLatin1String("pgp")) {
m_validationMethods = m_validationMethods | PackageValidation::Signature;
m_validationMethods = m_validationMethods | PackageValidation::PgpSignature;
} else {
// TODO: error handling (imporant?)
}

View File

@ -52,8 +52,7 @@ enum class PackageOrigin
};
/*!
* \brief The InstallStatus enum specifies whether a package has been installed explicitely
* or as dependency.
* \brief The InstallStatus enum specifies whether a package has been installed explicitely or as dependency.
*/
enum class InstallStatus
{
@ -71,12 +70,12 @@ enum class PackageValidation {
None = (1 << 0),
Md5Sum = (1 << 1),
Sha256Sum = (1 << 2),
Signature = (1 << 3)
PgpSignature = (1 << 3)
};
constexpr PackageValidation operator |(PackageValidation lhs, PackageValidation rhs)
{
return static_cast<PackageValidation>(static_cast<int>(lhs) & static_cast<int>(rhs));
return static_cast<PackageValidation>(static_cast<int>(lhs) | static_cast<int>(rhs));
}
constexpr bool operator &(PackageValidation lhs, PackageValidation rhs)
@ -84,6 +83,23 @@ constexpr bool operator &(PackageValidation lhs, PackageValidation rhs)
return (static_cast<int>(lhs) & static_cast<int>(rhs)) != 0;
}
constexpr int operator ~(PackageValidation lhs)
{
return ~static_cast<int>(lhs);
}
inline PackageValidation &operator |=(PackageValidation &lhs, PackageValidation rhs)
{
lhs = static_cast<PackageValidation>(static_cast<int>(lhs) | static_cast<int>(rhs));
return lhs;
}
inline PackageValidation &operator &=(PackageValidation &lhs, int rhs)
{
lhs = static_cast<PackageValidation>(static_cast<int>(lhs) & rhs);
return lhs;
}
/*!
* \brief The SignatureLevel enum specifies PGP signature verification options.
*/
@ -227,7 +243,7 @@ public:
const QList<Dependency> &provides() const;
const QList<Dependency> &replaces() const;
bool isRequiredByComputed() const;
void computeRequiredBy(Manager &manager);
void computeRequiredBy(const QList<Repository *> &relevantRepositories);
const QStringList &requiredBy() const;
QStringList &requiredBy();
const QStringList &optionalFor() const;
@ -328,6 +344,7 @@ protected:
QString m_packager;
QString m_md5;
QString m_sha256;
QString m_pgpSignature;
QString m_buildArchitecture;
uint32 m_packageSize;
QList<Dependency> m_makeDependencies;
@ -377,6 +394,7 @@ protected:
void setPackager(const QStringList &values);
void setMd5(const QStringList &values);
void setSha256(const QStringList &values);
void setPgpSignature(const QStringList &values);
void setFiles(const QStringList &values);
void setValidation(const QStringList &values);
void setGroups(const QStringList &values);

View File

@ -65,7 +65,9 @@ Package *PackageFinder::packageProviding(const Dependency &dependency)
void PackageFinder::addResults()
{
#ifdef DEBUG_BUILD
assert(m_remainingReplies);
#endif
// add results
auto *reply = static_cast<PackageReply *>(sender());
auto *repo = reply->repository();

View File

@ -9,6 +9,7 @@ namespace RepoIndex {
PackageInfoLookup::PackageInfoLookup(Manager &manager, const QJsonObject &request, QObject *parent) :
PackageLookup(parent),
m_manager(manager),
m_what(request.value(QStringLiteral("what")).toString()),
m_part(Manager::None)
{
@ -26,6 +27,7 @@ PackageInfoLookup::PackageInfoLookup(Manager &manager, const QJsonObject &reques
return;
}
m_packageSelection = request.value(QStringLiteral("sel")).toObject();
m_repos.reserve(m_packageSelection.size());
for(auto i = m_packageSelection.constBegin(), end = m_packageSelection.constEnd(); i != end; ++i) {
if(auto *repo = manager.repositoryByName(i.key())) {
QStringList packagesToBeRequested;
@ -37,6 +39,33 @@ PackageInfoLookup::PackageInfoLookup(Manager &manager, const QJsonObject &reques
}
}
if(!packagesToBeRequested.isEmpty()) {
m_repos << qMakePair(repo, packagesToBeRequested);
}
} else {
// specified repository can not be found
QJsonObject errorObj;
errorObj.insert(QStringLiteral("repo"), i.key());
errorObj.insert(QStringLiteral("error"), QStringLiteral("na"));
m_results << errorObj;
}
}
performLookup();
}
void PackageInfoLookup::performLookup()
{
for(auto &entry : m_repos) {
if(Repository *repo = entry.first) {
const QStringList &packagesToBeRequested = entry.second;
if(repo->isBusy()) {
// repo is busy -> try again when available
connect(repo, &Repository::available, this, &PackageInfoLookup::performLookup);
} else {
// disconnect to ensure the lookup isn't done twice
disconnect(repo, nullptr, this, nullptr);
// this repo can be skipped when this method is called again because other repos where busy
entry.first = nullptr;
// request package info
QReadLocker locker(repo->lock());
if(const auto *reply = (m_part & Manager::Details ? repo->requestFullPackageInfo(packagesToBeRequested) : repo->requestPackageInfo(packagesToBeRequested))) {
connect(reply, &PackageReply::resultsAvailable, this, &PackageInfoLookup::addResultsFromReply);
@ -46,13 +75,7 @@ PackageInfoLookup::PackageInfoLookup(Manager &manager, const QJsonObject &reques
addResultsDirectly(packagesToBeRequested, repo);
}
}
} else {
// specified repository can not be found
QJsonObject errorObj;
errorObj.insert(QStringLiteral("repo"), i.key());
errorObj.insert(QStringLiteral("error"), QStringLiteral("na"));
m_results << errorObj;
}
} // else: repo already processed
}
}
@ -90,7 +113,9 @@ void PackageInfoLookup::addResultsDirectly(const QStringList &packageNames, cons
void PackageInfoLookup::addResultsFromReply()
{
#ifdef DEBUG_BUILD
assert(m_remainingReplies);
#endif
auto *reply = static_cast<PackageReply *>(sender());
reply->deleteLater();
if(reply->error().isEmpty()) {

View File

@ -13,13 +13,16 @@ public:
explicit PackageInfoLookup(Manager &manager, const QJsonObject &request, QObject *parent = nullptr);
private slots:
void performLookup();
void addResultsDirectly(const QStringList &packageNames, const Repository *repo);
void addResultsFromReply();
private:
Manager &m_manager;
const QString m_what;
Manager::PackageInfoParts m_part;
QJsonObject m_packageSelection;
QList<QPair<Repository *, QStringList> > m_repos;
QList<Package *> m_packages;
};

View File

@ -46,7 +46,9 @@ Reply::Reply(const QList<QNetworkReply *> networkReplies) :
*/
void Reply::replyFinished()
{
#ifdef DEBUG_BUILD
assert(m_remainingReplies);
#endif
processData(static_cast<QNetworkReply *>(sender()));
if(!--m_remainingReplies) {
emit resultsAvailable();
@ -84,12 +86,56 @@ void Repository::updateGroups()
/*!
* \brief Initializes the repository.
* \remarks
* - The repository mustn't be busy if this method is called.
* - Does not restore cache. For restoring cache see restoreFromCacheStream().
* - Performs asynchronously and hence returns immidiately. Returns a PackageLoader
* object which QFuture can be used to wait for initializing to finish.
* - Might return nullptr if initialization is tivial.
* object which QFuture can be used to wait until the initialization is finished.
* - Alternatively the available() and initialized() signals can be used.
* - Might return nullptr if initialization is tivial. In this case the available
* and initialized() signals are not emitted.
* - The returned future might be not running indicating the process
* has already finished. In this case the available and initialized() signals are not emitted.
* - Locks the repository for write access. Flags the repository as busy.
*/
PackageLoader *Repository::init()
{
addBusyFlag();
QWriteLocker locker(lock());
if(PackageLoader *loader = internalInit()) {
if(loader->future().isRunning()) {
auto watcher = new QFutureWatcher<void>;
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::removeBusyFlag);
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::initialized);
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater);
watcher->setFuture(loader->future());
}
return loader;
} else {
return nullptr;
}
}
void Repository::initAsSoonAsPossible()
{
if(isBusy()) {
auto connection = make_shared<QMetaObject::Connection>();
*connection = connect(this, &Repository::available, [connection, this] {
disconnect(*connection);
init();
});
} else {
init();
}
}
/*!
* \brief This method can must overriden when subclassing to initialize the repository.
* \remarks
* - Mustn't emit any signals.
* - The repository is already locked when this method is called. Hence mustn't lock the repository.
* \sa init()
*/
PackageLoader *Repository::internalInit()
{
return nullptr;
}
@ -237,23 +283,47 @@ QList<Package *> Repository::packageByFilter(std::function<bool (const Package *
* \cond
*/
class Blocker
{
public:
Blocker(const QList<Repository *> &relevantRepos)
{
m_blockedRepos.reserve(relevantRepos.size());
for(Repository *repo : relevantRepos) {
if(repo->lock()->tryLockForWrite()) {
m_blockedRepos << repo;
}
}
}
~Blocker()
{
for(Repository *repo : m_blockedRepos) {
repo->lock()->unlock();
}
}
private:
QList<Repository *> m_blockedRepos;
};
class ComputeRequired
{
public:
ComputeRequired(Manager &manager, bool forceUpdate) :
m_manager(manager),
ComputeRequired(const QList<Repository *> &relevantRepos, bool forceUpdate) :
m_relevantRepos(relevantRepos),
m_forceUpdate(forceUpdate)
{}
void operator () (const pair<const QString, unique_ptr<Package> > &packageEntry)
{
if(m_forceUpdate || !packageEntry.second->isRequiredByComputed()) {
packageEntry.second->computeRequiredBy(m_manager);
packageEntry.second->computeRequiredBy(m_relevantRepos);
}
}
private:
Manager &m_manager;
const QList<Repository *> m_relevantRepos;
bool m_forceUpdate;
};
@ -263,11 +333,24 @@ private:
/*!
* \brief Computes required-by and optional-for for all packages.
* \remarks Computition is done async.
*
* Sources the packages of all \a relevantRepositories for packages depending on the packages of this repository.
*
* \remarks
* - Computation is done async.
* - The repository mustn't be busy. Flags the repository as busy.
* - \a relevantRepositories might contain the current instance.
* - The available() and requiredByComputed() signals are emitted after computition has finished.
*/
QFuture<void> Repository::computeRequiredBy(Manager &manager, bool forceUpdate)
QFuture<void> Repository::computeRequiredBy(const QList<Repository *> &relevantRepositories, bool forceUpdate)
{
return QtConcurrent::map(m_packages, ComputeRequired(manager, forceUpdate));
addBusyFlag(); // flag repository as busy
auto *watcher = new QFutureWatcher<void>;
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::removeBusyFlag);
connect(watcher, &QFutureWatcher<void>::finished, this, &Repository::requiredByComputed);
connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater);
watcher->setFuture(QtConcurrent::map(m_packages, ComputeRequired(relevantRepositories, forceUpdate)));
return watcher->future();
}
/*!
@ -771,6 +854,8 @@ void Repository::parseDescriptions(const QList<QByteArray> &descriptions, QStrin
// put last field
if(!currentFieldName.isEmpty()) {
fields << QPair<QString, QStringList>(currentFieldName, currentFieldValues);
currentFieldName.clear();
currentFieldValues.clear();
}
}
}
@ -944,4 +1029,21 @@ Package *Repository::addPackageFromDescription(QString name, const QList<QByteAr
return pkgRawPtr;
}
/*!
* \brief Internally called to add the busy flag.
*/
void Repository::addBusyFlag()
{
m_isBusy.store(1);
}
/*!
* \brief Internally called to remove the busy flag.
*/
void Repository::removeBusyFlag()
{
m_isBusy.store(0);
emit available();
}
} // namespace PackageManagement

View File

@ -200,6 +200,7 @@ public:
virtual RepositoryType type() const = 0;
// general meta data
bool isBusy() const;
uint32 index() const;
const QString &name() const;
const QString &description() const;
@ -218,7 +219,10 @@ public:
void setSigLevel(SignatureLevel sigLevel);
// gathering data
virtual PackageLoader *init();
public:
PackageLoader *init();
void initAsSoonAsPossible();
virtual PackageLoader *internalInit();
virtual PackageDetailAvailability requestsRequired(PackageDetail packageDetail = PackageDetail::Basics) const;
virtual SuggestionsReply *requestSuggestions(const QString &phrase);
virtual PackageReply *requestPackageInfo(const QStringList &packageNames, bool forceUpdate = false);
@ -231,7 +235,7 @@ public:
Package *packageProviding(const Dependency &dependency);
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);
QFuture<void> computeRequiredBy(const QList<Repository *> &relevantRepositories, bool forceUpdate = false);
QJsonObject suggestions(const QString &term) const;
// upgrade lookup
@ -277,10 +281,29 @@ public:
static const uint32 invalidIndex = static_cast<uint32>(-1);
signals:
/*!
* \brief Emitted after initialization has finished.
*/
void initialized();
/*!
* \brief Emitted after required-by computation has finished.
*/
void requiredByComputed();
/*!
* \brief Indicates the repository is not busy anymore; emitted after either initialization or required-by computation has finished.
*/
void available();
protected slots:
void addBusyFlag();
void removeBusyFlag();
protected:
explicit Repository(const QString &name, uint32 index = invalidIndex, QObject *parent = nullptr);
protected:
uint32 m_index;
QString m_name;
QString m_description;
@ -293,6 +316,7 @@ protected:
QList<Repository *> m_upgradeSources;
QString m_srcDir;
QString m_pkgDir;
QAtomicInteger<byte> m_isBusy;
private:
QReadWriteLock m_lock;
@ -306,6 +330,18 @@ inline QJsonObject SuggestionsReply::suggestions() const
return m_repo->suggestions(m_term);
}
/*!
* \brief Returns an indication whether the repository is busy (either initializing or computing required-by).
*
* In this case the repository shouldn't be touched until the available() signal is emitted.
*
* \remarks This method is thread-safe.
*/
inline bool Repository::isBusy() const
{
return m_isBusy.load() != 0;
}
/*!
* \brief Returns the index of the repository.
*

View File

@ -10,11 +10,11 @@
namespace RepoIndex {
SuggestionsLookup::SuggestionsLookup(Manager &manager, const QJsonObject &request, QObject *parent) :
PackageLookup(parent)
PackageLookup(parent),
m_searchTerm(request.value(QStringLiteral("term")).toString())
{
m_id = request.value(QStringLiteral("id"));
const auto searchTerm = request.value(QStringLiteral("term")).toString();
if(searchTerm.isEmpty()) {
if(m_searchTerm.isEmpty()) {
m_errors << QStringLiteral("No search term specified.");
}
const auto repos = request.value(QStringLiteral("repos")).toArray();
@ -22,15 +22,10 @@ SuggestionsLookup::SuggestionsLookup(Manager &manager, const QJsonObject &reques
m_errors << QStringLiteral("No repositories specified.");
}
if(m_errors.isEmpty()) {
m_repos.reserve(repos.size());
for(const auto &repoName : repos) {
if(auto *repo = manager.repositoryByName(repoName.toString())) {
QReadLocker locker(repo->lock());
if(const auto *reply = repo->requestSuggestions(searchTerm)) {
connect(reply, &SuggestionsReply::resultsAvailable, this, &SuggestionsLookup::addResults);
++m_remainingReplies;
} else {
m_results << repo->suggestions(searchTerm);
}
m_repos << repo;
} else {
m_errors << QStringLiteral("The specified repository \"%1\" does not exist.").arg(repoName.toString());
}
@ -40,9 +35,36 @@ SuggestionsLookup::SuggestionsLookup(Manager &manager, const QJsonObject &reques
deleteLater();
}
void SuggestionsLookup::performLookup()
{
for(Repository *&repo : m_repos) {
if(repo) {
if(repo->isBusy()) {
// repo is busy -> try again when available
connect(repo, &Repository::available, this, &SuggestionsLookup::performLookup);
} else {
// disconnect to ensure the lookup isn't done twice
disconnect(repo, nullptr, this, nullptr);
// request suggestions
QReadLocker locker(repo->lock());
if(const auto *reply = repo->requestSuggestions(m_searchTerm)) {
connect(reply, &SuggestionsReply::resultsAvailable, this, &SuggestionsLookup::addResults);
++m_remainingReplies;
} else {
m_results << repo->suggestions(m_searchTerm);
}
// this repo can be skipped when this method is called again because other repos where busy
repo = nullptr;
}
} // else: repo already processed
}
}
void SuggestionsLookup::addResults()
{
#ifdef DEBUG_BUILD
assert(m_remainingReplies);
#endif
auto *reply = static_cast<SuggestionsReply *>(sender());
{
QReadLocker locker(reply->repository()->lock());

View File

@ -6,6 +6,7 @@
namespace RepoIndex {
class Manager;
class Repository;
class SuggestionsLookup : public PackageLookup
{
@ -14,7 +15,12 @@ public:
SuggestionsLookup(Manager &manager, const QJsonObject &request, QObject *parent = nullptr);
private slots:
void performLookup();
void addResults();
private:
QList<Repository *> m_repos;
const QString m_searchTerm;
};
} // namespace RepoIndex

View File

@ -52,28 +52,7 @@ UpgradeLookupProcess::UpgradeLookupProcess(UpgradeLookup *upgradeLookup, Reposit
m_watcher(new QFutureWatcher<void>(this))
{
connect(this, &UpgradeLookupProcess::finished, upgradeLookup, &UpgradeLookup::processFinished);
{
switch(m_upgradeSource->requestsRequired()) {
case PackageDetailAvailability::Request:
m_reply = m_upgradeSource->requestPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::FullRequest:
m_reply = m_upgradeSource->requestFullPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::Never:
m_results.errors << QStringLiteral("Repository \"%1\" does not provide the required information.").arg(m_upgradeSource->name());
emit finished();
return;
case PackageDetailAvailability::Immediately:
break;
}
}
if(m_reply) {
m_reply->setParent(this);
connect(m_reply, &PackageReply::resultsAvailable, this, &UpgradeLookupProcess::sourceReady);
} else {
sourceReady();
}
requestSources();
}
/*!
@ -84,16 +63,58 @@ inline const UpgradeLookupResults &UpgradeLookupProcess::results() const
return m_results;
}
/*!
* \brief Internally called to request the sources.
*/
void UpgradeLookupProcess::requestSources()
{
// ensure the repository to check and the upgrade source are both not busy
if(m_toCheck->isBusy()) {
connect(m_toCheck, &Repository::available, this, &UpgradeLookupProcess::requestSources);
} else {
disconnect(m_toCheck, nullptr, this, nullptr);
}
if(m_upgradeSource->isBusy()) {
connect(m_upgradeSource, &Repository::available, this, &UpgradeLookupProcess::requestSources);
} else {
disconnect(m_upgradeSource, nullptr, this, nullptr);
}
// request sources if required
switch(m_upgradeSource->requestsRequired()) {
case PackageDetailAvailability::Request:
m_reply = m_upgradeSource->requestPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::FullRequest:
m_reply = m_upgradeSource->requestFullPackageInfo(m_toCheck->packageNames());
break;
case PackageDetailAvailability::Never:
m_results.errors << QStringLiteral("Repository \"%1\" does not provide the required information.").arg(m_upgradeSource->name());
emit finished();
return;
case PackageDetailAvailability::Immediately:
break;
}
if(m_reply) {
// a request is required -> wait until results are available
m_reply->setParent(this);
connect(m_reply, &PackageReply::resultsAvailable, this, &UpgradeLookupProcess::sourceReady);
} else {
// no request required -> call sourceReady immidiately
sourceReady();
}
}
/*!
* \brief Internally called when the upgrade source is ready.
*/
void UpgradeLookupProcess::sourceReady()
{
// if a request was required, check whether there occured an error
// if a request was required, check whether an error occured
if(m_reply && !m_reply->error().isEmpty()) {
m_results.errors << m_reply->error();
emit finished();
} else {
// invoke the actual upgrade lookup
connect(m_watcher, &QFutureWatcher<void>::finished, this, &UpgradeLookupProcess::finished);
m_watcher->setFuture(QtConcurrent::run(this, &UpgradeLookupProcess::checkUpgrades));
}
@ -210,7 +231,9 @@ UpgradeLookupJson::UpgradeLookupJson(Manager &manager, const QJsonObject &reques
void UpgradeLookupJson::processFinished()
{
#ifdef DEBUG_BUILD
assert(m_remainingProcesses);
#endif
// add results
const auto &results = static_cast<UpgradeLookupProcess *>(sender())->results();
for(const auto &res : results.newVersions) {
@ -301,7 +324,9 @@ UpgradeLookupCli::UpgradeLookupCli(Manager &manager, const string &repo, QObject
void UpgradeLookupCli::processFinished()
{
#ifdef DEBUG_BUILD
assert(m_remainingProcesses);
#endif
// add results
const auto &results = static_cast<UpgradeLookupProcess *>(sender())->results();
m_softwareUpgradesArray.reserve(m_softwareUpgradesArray.size() + results.newVersions.size());

View File

@ -91,6 +91,7 @@ signals:
void finished();
private slots:
void requestSources();
void sourceReady();
void checkUpgrades();

View File

@ -112,8 +112,8 @@ QJsonArray validationMethodsStrings(PackageValidation validationMethods)
if(validationMethods & PackageValidation::Sha256Sum) {
jsonArray << QStringLiteral("SHA256");
}
if(validationMethods & PackageValidation::Signature) {
jsonArray << QStringLiteral("signature");
if(validationMethods & PackageValidation::PgpSignature) {
jsonArray << QStringLiteral("PGP signature");
}
//if(jsonArray.empty()) {
// jsonArray << QStringLiteral("unknown");

View File

@ -1,103 +0,0 @@
# specify build directories for moc, object and rcc files
MOC_DIR = ./moc
OBJECTS_DIR = ./obj
RCC_DIR = ./res
# compiler flags: enable C++11
QMAKE_CXXFLAGS += -std=c++11
QMAKE_LFLAGS += -std=c++11
# disable new ABI (can't catch ios_base::failure with new ABI)
DEFINES += _GLIBCXX_USE_CXX11_ABI=0
# variables to check target architecture
win32-g++:QMAKE_TARGET.arch = $$QMAKE_HOST.arch
win32-g++-32:QMAKE_TARGET.arch = x86
win32-g++-64:QMAKE_TARGET.arch = x86_64
linux-g++:QMAKE_TARGET.arch = $$QMAKE_HOST.arch
linux-g++-32:QMAKE_TARGET.arch = x86
linux-g++-64:QMAKE_TARGET.arch = x86_64
# determine and print target prefix
targetprefix = $$(TARGET_PREFIX)
message("Using target prefix \"$${targetprefix}\".")
# print install root
message("Using install root \"$$(INSTALL_ROOT)\".")
# set target
CONFIG(debug, debug|release) {
TARGET = $${targetprefix}$${projectname}d
} else {
TARGET = $${targetprefix}$${projectname}
}
# add defines for meta data
DEFINES += "APP_METADATA_AVAIL"
DEFINES += "'PROJECT_NAME=\"$${projectname}\"'"
DEFINES += "'APP_NAME=\"$${appname}\"'"
DEFINES += "'APP_AUTHOR=\"$${appauthor}\"'"
DEFINES += "'APP_URL=\"$${appurl}\"'"
DEFINES += "'APP_VERSION=\"$${VERSION}\"'"
# configure Qt modules and defines
mobile {
DEFINES += CONFIG_MOBILE
} else:desktop {
DEFINES += CONFIG_DESKTOP
} else:android {
CONFIG += mobile
DEFINES += CONFIG_MOBILE
} else {
CONFIG += desktop
DEFINES += CONFIG_DESKTOP
}
no-gui {
QT -= gui
DEFINES += GUI_NONE
guiqtquick || guiqtwidgets {
error("Can not use no-gui with guiqtquick or guiqtwidgets.")
} else {
message("Configured for no GUI support.")
}
} else {
QT += gui
mobile {
CONFIG += guiqtquick
}
desktop {
CONFIG += guiqtwidgets
}
}
guiqtquick {
message("Configured for Qt Quick GUI support.")
QT += quick
CONFIG(debug, debug|release) {
CONFIG += qml_debug
}
DEFINES += GUI_QTQUICK
}
guiqtwidgets {
message("Configured for Qt widgets GUI support.")
QT += widgets
DEFINES += GUI_QTWIDGETS
DEFINES += MODEL_UNDO_SUPPORT
}
# 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
}
mingw-w64-manualstrip-exe {
QMAKE_POST_LINK=$${CROSS_COMPILE}strip --strip-unneeded ./release/$(TARGET)
}
mingw-w64-noversion {
TARGET_EXT = ".dll"
TARGET_VERSION_EXT = ""
CONFIG += skip_target_version_ext
}

View File

@ -15,6 +15,8 @@
#include <iostream>
#include <algorithm>
#include <QReadWriteLock>
using namespace std;
using namespace ApplicationUtilities;
using namespace RepoIndex;
@ -53,9 +55,11 @@ int main(int argc, char *argv[])
// setup manager
Manager manager(config);
cerr << shchar << "Loading databases ..." << endl;
manager.addDataBasesFromPacmanConfig();
if(config.areReposFromPacmanConfEnabled()) {
manager.addDataBasesFromPacmanConfig();
}
manager.addDatabasesFromRepoIndexConfig();
manager.initAlpmDataBases(configArgs.serverArg.isPresent());
manager.initAlpmDataBases();
cerr << shchar << "Restoring cache ... ";
manager.restoreCache();
cerr << shchar << "DONE" << endl;
@ -64,6 +68,7 @@ int main(int argc, char *argv[])
// setup the server
Server server(manager, manager.config());
manager.setAutoCacheMaintenanceEnabled(true);
manager.setAutoUpdateEnabled(true);
QObject::connect(&server, &Server::closed, &application, &QCoreApplication::quit);
return application.exec();
} else if(configArgs.buildOrderArg.isPresent()) {

View File

@ -18,10 +18,10 @@ using namespace std;
namespace RepoIndex {
Connection::Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent) :
Connection::Connection(Manager &manager, QWebSocket *socket, QObject *parent) :
QObject(parent),
m_socket(socket),
m_manager(alpmManager),
m_manager(manager),
m_repoInfoUpdatesRequested(false),
m_groupInfoUpdatesRequested(false)
{
@ -29,6 +29,7 @@ Connection::Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent
connect(socket, &QWebSocket::textMessageReceived, this, &Connection::processTextMessage);
connect(socket, &QWebSocket::binaryMessageReceived, this, &Connection::processBinaryMessage);
connect(socket, &QWebSocket::disconnected, this, &Connection::socketDisconnected);
connect(&manager, &Manager::updatesAvailable, this, &Connection::updatedAvailable);
}
void Connection::sendJson(const QJsonObject &obj)
@ -59,7 +60,7 @@ void Connection::sendResult(const QJsonValue &what, const QJsonValue &id, const
sendJson(response);
}
void Connection::sendResults(const QJsonValue &what, const QJsonValue &id, const QJsonArray &values)
void Connection::sendResults(const QJsonValue &what, const QJsonValue &id, const QJsonValue &values)
{
QJsonObject response;
response.insert(QStringLiteral("class"), QStringLiteral("results"));
@ -71,6 +72,16 @@ void Connection::sendResults(const QJsonValue &what, const QJsonValue &id, const
sendJson(response);
}
void Connection::updatedAvailable()
{
if(m_repoInfoUpdatesRequested) {
sendResult(QStringLiteral("basicrepoinfo"), QJsonValue(), m_manager.basicRepoInfo());
}
if(m_groupInfoUpdatesRequested) {
sendResults(QStringLiteral("groupinfo"), QJsonValue(), m_manager.groupInfo());
}
}
template<class Lookup, typename... Args>
void Connection::performLookup(const QJsonObject &request, Args &&...args)
{
@ -129,12 +140,23 @@ void Connection::handleCmd(const QJsonObject &obj)
}
} else if(what == QLatin1String("reinitalpm")) {
if(m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Reinit of ALPM databases triggered via web interface." << endl;
cerr << shchar << "Info: Re-initializing ALPM databases (triggered via web interface) ..." << endl;
m_manager.removeAllDatabases();
m_manager.addLocalDatabase();
m_manager.addDataBasesFromPacmanConfig();
if(m_manager.config().isLocalDatabaseEnabled()) {
m_manager.addLocalDatabase();
}
if(m_manager.config().areReposFromPacmanConfEnabled()) {
m_manager.addDataBasesFromPacmanConfig();
}
m_manager.addDatabasesFromRepoIndexConfig();
m_manager.initAlpmDataBases(true);
m_manager.initAlpmDataBases();
} else {
sendError(QStringLiteral("rejected"), id);
}
} else if(what == QLatin1String("updatealpm")) {
if(m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Forcing update of all ALPM databases (triggered via web interface) ..." << endl;
m_manager.forceUpdateAlpmDatabases();
} else {
sendError(QStringLiteral("rejected"), id);
}

View File

@ -17,14 +17,15 @@ class Connection : public QObject
Q_OBJECT
public:
Connection(RepoIndex::Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr);
Connection(Manager &alpmManager, QWebSocket *socket, QObject *parent = nullptr);
private slots:
void processTextMessage(const QString &message);
void processBinaryMessage(const QByteArray &message);
void socketDisconnected();
void sendResult(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value);
void sendResults(const QJsonValue &what, const QJsonValue &id, const QJsonArray &values);
void sendResults(const QJsonValue &what, const QJsonValue &id, const QJsonValue &values);
void updatedAvailable();
private:
void sendJson(const QJsonObject &obj);
@ -39,6 +40,8 @@ private:
bool m_groupInfoUpdatesRequested;
};
}
#endif // CONNECTION_H

View File

@ -6,6 +6,7 @@
},
"cacheDir": ".",
"storageDir": ".",
"aur": {
"enabled": true
@ -19,6 +20,7 @@
},
"repos": {
"localEnabled": true,
"fromPacmanConfig": true,
"add": [
@ -30,13 +32,22 @@
"https://localhost/repo/arch/$repo/os/$arch"
]
},
{"name": "local", "maxAge": 3600},
{"name": "core", "maxAge": 28800},
{"name": "extra", "maxAge": 28800},
{"name": "community", "maxAge": 28800},
{"name": "multilib", "maxAge": 28800},
{"name": "ownstuff-testing",
"sourcesDir": "path/to/local/source/dir",
"packagesDir": "/run/media/devel/repo/arch/ownstuff-testing/os/x86_64",
"upgradeSources": ["aur"],
"server": [
"ignore": true,
"sourcesDir": "path/to/local/source/dir",
"packagesDir": "/run/media/devel/repo/arch/ownstuff-testing/os/x86_64",
"upgradeSources": ["aur"],
"server": [
"https://localhost/repo/arch/$repo/os/$arch"
]
]
}
]
}

View File

@ -44,7 +44,7 @@
<li id="nav_packages" class="active"><a href="#packages">Packages <span class="sr-only">(current)</span></a></li>
<li id="nav_groups"><a href="#groups">Groups</a></li>
<li id="nav_repositories"><a href="#repositories">Repositories</a></li>
<li id="nav_settings"><a id="link_settings" href="#" onclick="return false;" aria-label="Settings" data-toggle="popover" data-placement="bottom" title="Settings" data-content="TODO"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a></li>
<!--<li id="nav_settings"><a id="link_settings" href="#" onclick="return false;" aria-label="Settings" data-toggle="popover" data-placement="bottom" title="Settings" data-content="TODO"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a></li>-->
<li id="nav_about"><a id="link_settings" href="#" onclick="$('#dlg_about').modal('show'); return false;"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></a></li>
</ul>
<form class="navbar-form navbar-left" target="#" onsubmit="repoindex.pageManager.applySearchTerm(this.searchtermInput.value, this.searchtermExact.checked, true); return false;">
@ -75,7 +75,8 @@
</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>
<li><a href="#" onclick="repoindex.client.reinitAlpm(); return false;">Reinit ALPM databases</a></li>
<li><a href="#" onclick="repoindex.client.updateAlpm(); return false;">Update ALPM databases</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>

View File

@ -14,7 +14,8 @@
FullPackageInfo: "fullpkginfo",
GroupInfo: "groupinfo",
UpgradeLookup: "upgradelookup",
Suggestions: "suggestions"
Suggestions: "suggestions",
Postponed: "postponed"
};
repoindex.isLoopback = function(domain) {
@ -183,7 +184,7 @@
repoindex.pageManager.addError("Server replied \"" + what + "\" with insufficiant data.");
} else {
var request = this.requestById(id);
if(request && request.sent) {
if(!id || (request && request.sent)) {
// use the received information
switch(what) {
case repoindex.RequestType.BasicRepoInfo:
@ -204,13 +205,22 @@
case repoindex.RequestType.Suggestions:
this.useSuggestions(values);
break;
case repoindex.RequestType.Postponed:
// server is busy, but will send results later
pageManager.addError("The server is busy.");
// -> do not call the callbacks already
request = undefined;
break;
default:
pageManager.addError("Server replied unknown results: " + what);
return; // don't invoke callbacks when results are of unknown type
}
// also invoke callbacks registred for this request
for(var i = 0; i < request.callbacks.length; ++i) {
request.callbacks[i](values);
if(request) {
// also invoke callbacks registred for this request
for(var i = 0; i < request.callbacks.length; ++i) {
request.callbacks[i](values);
}
// TODO: remove request object from list of sent requests
}
} else {
repoindex.pageManager.addError("Server replied \"" + what + "\" with unknown ID.");
@ -220,11 +230,11 @@
// define functions to perform several requests
this.requestBasicRepoInfo = function(callback) {
this.scheduleRequest(repoindex.RequestType.BasicRepoInfo, {upgrades: "true"}, callback);
this.scheduleRequest(repoindex.RequestType.BasicRepoInfo, {updates: true}, callback);
};
this.requestBasicPackagesInfo = function(packageSelection, callback) {
this.scheduleRequest(repoindex.RequestType.BasicPackageInfo, {sel: packageSelection}, callback);
this.scheduleRequest(repoindex.RequestType.BasicPackageInfo, {sel: packageSelection, updates: true}, callback);
};
this.requestFullPackagesInfo = function(packageSelection, callback) {
@ -232,7 +242,7 @@
};
this.requestGroupInfo = function(callback) {
this.scheduleRequest(repoindex.RequestType.GroupInfo, {upgrades: "true"}, callback);
this.scheduleRequest(repoindex.RequestType.GroupInfo, {updates: true}, callback);
};
this.requestSuggestions = function(repoNames, searchTerm, callback) {
@ -266,6 +276,10 @@
this.sendCmd("reinitalpm");
};
this.updateAlpm = function() {
this.sendCmd("updatealpm");
};
this.checkForUpgrades = function(dbName, syncdbNames, callback) {
var params = {
db: dbName
@ -303,11 +317,16 @@
var pkgMgr = repoindex.pageManager.packageManager;
repoMgr.removeEntries();
pkgMgr.removeEntries();
var incomplete = false;
var reposInOrder = [];
for(var repoName in value) {
if(value.hasOwnProperty(repoName)) {
reposInOrder.push({name: repoName, info: value[repoName]});
//var repoInfo = value[repoName];
var info = value[repoName];
if(info.incomplete) {
incomplete = true;
} else {
reposInOrder.push({name: repoName, info: info});
}
}
}
reposInOrder.sort(function(lhs, rhs) {
@ -337,7 +356,9 @@
}
}
}
if(incomplete) {
repoindex.pageManager.addError("Server replied incomplete repository info: Server is busy.");
}
this.hasBasicRepoInfo = true;
pkgMgr.invalidate();
repoMgr.invalidate();
@ -369,7 +390,24 @@
}
}
if(value.error) {
repoindex.pageManager.addError("Server replied error in package info: " + value.error);
switch(value.error) {
case "na":
if(value.repo) {
if(value.name) {
repoindex.pageManager.addError("Server replied error: The package " + value.name + " doesn't exist in the repository " + value.repo + ".");
} else {
repoindex.pageManager.addError("Server replied error: The repository " + value.repo + " doesn't exist.");
}
} else {
repoindex.pageManager.addError("Server replied error: The requested info is not available.");
}
break;
case "busy":
repoindex.pageManager.addError("Server replied error in package info: The server is busy.");
break;
default:
repoindex.pageManager.addError("Server replied unknown error code in package info: " + value.error);
}
}
// updating table rows or any other GUI elements is done via callbacks
};
@ -377,9 +415,13 @@
this.useGroupInfo = function(values) {
var groupMgr = repoindex.pageManager.groupManager;
groupMgr.removeEntries();
var incomplete = false;
var groupEntries = groupMgr.entries;
for(var i1 = 0; i1 < values.length; ++i1) {
var info = values[i1];
if(info.incomplete) {
incomplete = true;
}
if(info.repo && info.groups) {
for(var i2 = 0; i2 < info.groups.length; ++i2) {
var group = info.groups[i2];
@ -387,6 +429,9 @@
}
}
}
if(incomplete) {
repoindex.pageManager.addError("Server replied incomplete group info: Server is busy.");
}
this.hasGroupInfo = true;
groupMgr.useRequestedData();
};

View File

@ -32,7 +32,7 @@
};
this.remove = function() {
this.rowElement.parent.removeChild(this.rowElement);
this.rowElement.parentNode.removeChild(this.rowElement);
};
this.hide = function() {

View File

@ -171,11 +171,13 @@
if(details.chkd && details.chkd.length) {
repoindex.addPackageNames(tb, "Check deps", repoindex.pkgNamesFromDeps(details.chkd));
}
repoindex.addPackageNames(tb, "Required by", details.requ);
repoindex.addPackageNames(tb, "Optional for", details.optf);
if(details.requ || details.optf) {
repoindex.addPackageNames(tb, "Required by", details.requ);
repoindex.addPackageNames(tb, "Optional for", details.optf);
}
repoindex.addPackageNames(tb, "Conflicts with", repoindex.pkgNamesFromDeps(details.conf));
repoindex.addPackageNames(tb, "Replaces", repoindex.pkgNamesFromDeps(details.repl));
if(details.buildAvail) {
if(details.bav) {
if(entry.info.repo !== "local") { // local repo does no provide package size
repoindex.addField(tb, "Package size", repoindex.makeDataSize(details.csize));
}
@ -187,14 +189,31 @@
repoindex.addField(tb, "Install reason", details.expl ? "explicitly installed" : "installed as dependency");
}
repoindex.addField(tb, "Install script", repoindex.makeBool(details.scri));
repoindex.addField(tb, "Validated by", repoindex.makeArray(details.sig));
repoindex.addField(tb, "Validation methods", repoindex.makeArray(details.sig, ", "));
repoindex.setTree(repoindex.addField(tb, "Package files"), repoindex.makeTree(details.files));
}
if(details.sav) {
if(details.main) {
repoindex.addField(tb, "Maintainer", repoindex.makeStr(details.main));
}
if(basics.flagdate) {
repoindex.addField(tb, "Out-of-date", repoindex.makeStr(basics.flagdate));
}
if(details.fsub) {
repoindex.addField(tb, "First submitted", repoindex.makeStr(details.fsub));
}
if(details.lmod) {
repoindex.addField(tb, "Last modified", repoindex.makeStr(details.lmod));
}
if(details.votes) {
repoindex.addField(tb, "Votes", repoindex.makeStr(details.votes));
}
}
// -> update download buttons
if(details.buildAvail || details.srcAvail) {
if(details.bav || details.sav) {
var downloadElement = repoindex.addField(tb, "Download");
var spanElement;
if(details.buildAvail) {
if(details.bav) {
spanElement = document.createElement("span");
var downloadPkgParams = {repo: entry.info.repo, pkg: entry.name, down: "pkg"};
repoindex.setDownloadButton(spanElement, "package", repoindex.makeHash(repoindex.Pages.Packages, downloadPkgParams, true), function() {
@ -203,10 +222,13 @@
});
downloadElement.appendChild(spanElement);
}
if(details.srcAvail) {
if(details.srctar && typeof details.srctar === "string") {
spanElement = document.createElement("span");
var downloadSrcParams = {repo: entry.info.repo, pkg: entry.name, down: "src"};
repoindex.setDownloadButton(spanElement, "source", repoindex.makeHash(repoindex.Pages.Packages, downloadSrcParams, true));
repoindex.setDownloadButton(spanElement, "source", repoindex.makeHash(repoindex.Pages.Packages, downloadSrcParams, true), function() {
repoindex.pageManager.denoteHash(repoindex.Pages.Packages, downloadSrcParams);
window.open("https://aur.archlinux.org" + details.srctar);
});
downloadElement.appendChild(spanElement);
}
}

View File

@ -5,8 +5,8 @@
RepoEntry.prototype.constructor = RepoEntry;
RepoEntry = function(repoName, repoInfo, enabled) {
if(enabled === undefined) {
// per default enable all repos with a fix number of packages
enabled = repoInfo.packageCount;
// per default enable all repos with a fix number of packages except the local database
enabled = repoInfo.packageCount && repoName !== "local";
}
repoindex.Entry.prototype.constructor.call(this, repoName, repoInfo, enabled);
@ -57,7 +57,7 @@
};
this.link.remove = function() {
this.parent.removeChild(this);
this.parentNode.removeChild(this);
};
};
this.createLink();