1053 lines
39 KiB
C++
1053 lines
39 KiB
C++
#include "./manager.h"
|
|
#include "./utilities.h"
|
|
#include "./config.h"
|
|
#include "./alpmdatabase.h"
|
|
|
|
#include "../network/userrepository.h"
|
|
|
|
#include <c++utilities/io/inifile.h>
|
|
#include <c++utilities/io/catchiofailure.h>
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
#include <c++utilities/misc/memory.h>
|
|
|
|
#include <QList>
|
|
#include <QSysInfo>
|
|
#include <QMutexLocker>
|
|
#include <QStringBuilder>
|
|
#include <QFile>
|
|
#include <QDataStream>
|
|
#include <QTimer>
|
|
#include <QFileInfo>
|
|
#include <QtConcurrent>
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
using namespace std;
|
|
using namespace IoUtilities;
|
|
using namespace ConversionUtilities;
|
|
|
|
namespace RepoIndex {
|
|
|
|
/*!
|
|
* \cond
|
|
*/
|
|
|
|
constexpr auto defaultSigLevel = SignatureLevel::Package | SignatureLevel::PackageOptional
|
|
| SignatureLevel::Database | SignatureLevel::DatabaseOptional;
|
|
|
|
inline ostream &operator <<(ostream &stream, const QString &str)
|
|
{
|
|
stream << str.toLocal8Bit().data();
|
|
return stream;
|
|
}
|
|
|
|
/*!
|
|
* \endcond
|
|
*/
|
|
|
|
/*!
|
|
* \brief The Manager class helps accessing ALPM.
|
|
*
|
|
* - It queries the ALPM for database and package information.
|
|
* - It serializes the information as JSON objects used by the network classes and the web interface.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Creates a new manager class; initializes a new ALPM handle.
|
|
* \param rootdir Specifies the root directory.
|
|
* \param dbpath Specifies the database directory.
|
|
*/
|
|
Manager::Manager(const Config &config, QObject *parent) :
|
|
QObject(parent),
|
|
m_config(config),
|
|
m_writeCacheBeforeGone(true),
|
|
m_sigLevel(defaultSigLevel),
|
|
m_localFileSigLevel(SignatureLevel::UseDefault)
|
|
{
|
|
if(config.isLocalDatabaseEnabled()) {
|
|
addLocalDatabase();
|
|
}
|
|
if(config.isAurEnabled()) {
|
|
m_userRepo = make_unique<UserRepository>();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Releases the associated ALPM handle.
|
|
*/
|
|
Manager::~Manager()
|
|
{
|
|
if(m_writeCacheBeforeGone) {
|
|
writeCache();
|
|
}
|
|
removeAllDatabases();
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package with the specified name from the specified database.
|
|
*/
|
|
AlpmPackage *Manager::packageFromDatabase(const QString &dbName, const QString &pkgName)
|
|
{
|
|
if(auto *db = databaseByName(dbName)) {
|
|
return static_cast<AlpmPackage *>(db->packageByName(pkgName));
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package with the specified name from the specified database.
|
|
*/
|
|
const AlpmPackage *Manager::packageFromDatabase(const QString &dbName, const QString &pkgName) const
|
|
{
|
|
if(const auto *db = databaseByName(dbName)) {
|
|
return static_cast<const AlpmPackage *>(db->packageByName(pkgName));
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package with the specified \a name from one of the sync databases.
|
|
*/
|
|
AlpmPackage *Manager::packageFromSyncDatabases(const QString &pkgName)
|
|
{
|
|
for(const auto &dbEntry : m_syncDbs) {
|
|
if(auto *pkg = dbEntry->packageByName(pkgName)) {
|
|
return static_cast<AlpmPackage *>(pkg);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package with the specified \a name from one of the sync databases.
|
|
*/
|
|
const AlpmPackage *Manager::packageFromSyncDatabases(const QString &pkgName) const
|
|
{
|
|
for(const auto &dbEntry : m_syncDbs) {
|
|
if(const auto *pkg = dbEntry->packageByName(pkgName)) {
|
|
return static_cast<const AlpmPackage *>(pkg);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package satisfiing the specified dependency from one of the available package sources (excluding the local database
|
|
* and sources requirering requests such as the AUR).
|
|
*/
|
|
Package *Manager::packageProviding(const Dependency &dependency)
|
|
{
|
|
for(auto &dbEntry : m_syncDbs) {
|
|
QReadLocker locker(dbEntry->lock());
|
|
if(auto *pkg = dbEntry->packageProviding(dependency)) {
|
|
return pkg;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the first package satisfiing the specified dependency from one of the available package sources (excluding the local database
|
|
* and sources requirering requests such as the AUR).
|
|
*/
|
|
const Package *Manager::packageProviding(const Dependency &dependency) const
|
|
{
|
|
for(const auto &dbEntry : m_syncDbs) {
|
|
QReadLocker locker(dbEntry->lock());
|
|
if(const auto *pkg = dbEntry->packageProviding(dependency)) {
|
|
return pkg;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the last value with the specified \a key in the specified multimap.
|
|
*/
|
|
const string &lastValue(const multimap<string, string> &mm, const string &key)
|
|
{
|
|
static const string defaultValue;
|
|
const auto i = find_if(mm.crbegin(), mm.crend(), [&key] (const pair<string, string> &i) {
|
|
return i.first == key;
|
|
});
|
|
return i != mm.crend() ? i->second : defaultValue;
|
|
}
|
|
|
|
/*!
|
|
* \brief Parses a "SigLevel" denotation from pacman config file.
|
|
*/
|
|
SignatureLevel Manager::parseSigLevel(const string &sigLevelStr)
|
|
{
|
|
SignatureLevel sigLevel = defaultSigLevel;
|
|
// split sig level denotation into parts
|
|
const auto parts = splitString<list<string> >(sigLevelStr, " ");
|
|
for(const auto &part : parts) {
|
|
// determine whether part affect packages, databases or both
|
|
bool package = true, db = true;
|
|
const char *partStart = part.data();
|
|
if(!strncmp(partStart, "Package", 7)) {
|
|
db = false; // package only part
|
|
partStart += 7;
|
|
} else if(!strncmp(partStart, "Database", 8)) {
|
|
package = false; // db only part
|
|
partStart += 8;
|
|
}
|
|
// set sig level according part
|
|
if(!strcmp(partStart, "Never")) {
|
|
if(package) {
|
|
sigLevel &= ~SignatureLevel::Package;
|
|
}
|
|
if(db) {
|
|
sigLevel &= ~SignatureLevel::Database;
|
|
}
|
|
} else if(!strcmp(partStart, "Optional")) {
|
|
if(package) {
|
|
sigLevel |= SignatureLevel::Package | SignatureLevel::PackageOptional;
|
|
}
|
|
if(db) {
|
|
sigLevel |= SignatureLevel::Database | SignatureLevel::DatabaseOptional;
|
|
}
|
|
} else if(!strcmp(partStart, "Required")) {
|
|
if(package) {
|
|
sigLevel |= SignatureLevel::Package;
|
|
sigLevel &= ~SignatureLevel::PackageOptional;
|
|
}
|
|
if(db) {
|
|
sigLevel |= SignatureLevel::Database;
|
|
sigLevel &= ~SignatureLevel::DatabaseOptional;
|
|
}
|
|
} else if(!strcmp(partStart, "TrustedOnly")) {
|
|
if(package) {
|
|
sigLevel &= ~(SignatureLevel::PackageMarginalOk | SignatureLevel::PackageUnknownOk);
|
|
}
|
|
if(db) {
|
|
sigLevel &= ~(SignatureLevel::DatabaseMarginalOk | SignatureLevel::DatabaseUnknownOk);
|
|
}
|
|
} else if(!strcmp(partStart, "TrustAll")) {
|
|
if(package) {
|
|
sigLevel |= SignatureLevel::PackageMarginalOk | SignatureLevel::PackageUnknownOk;
|
|
}
|
|
if(db) {
|
|
sigLevel |= SignatureLevel::DatabaseMarginalOk | SignatureLevel::DatabaseUnknownOk;
|
|
}
|
|
} else {
|
|
cerr << shchar << "Warning: Invalid value \"" << part << "\" for \"SigLevel\" in pacman config file will be ignored." << endl;
|
|
}
|
|
}
|
|
return sigLevel;
|
|
}
|
|
|
|
/*!
|
|
* \brief Parses a "Usage" denotation from pacman config file.
|
|
*/
|
|
RepositoryUsage Manager::parseUsage(const string &usageStr)
|
|
{
|
|
RepositoryUsage usage = RepositoryUsage::None;
|
|
const auto parts = splitString<list<string> >(usageStr, " ", EmptyPartsTreat::Omit);
|
|
for(const auto &part : parts) {
|
|
if(part == "Sync") {
|
|
usage |= RepositoryUsage::Sync;
|
|
} else if(part == "Search") {
|
|
usage |= RepositoryUsage::Search;
|
|
} else if(part == "Install") {
|
|
usage |= RepositoryUsage::Install;
|
|
} else if(part == "Upgrade") {
|
|
usage |= RepositoryUsage::Upgrade;
|
|
} else {
|
|
cerr << shchar << "Warning: Invalid value \"" << part << "\" for \"Usage\" in pacman config file will be ignored." << endl;
|
|
}
|
|
}
|
|
return usage != RepositoryUsage::None ? usage : RepositoryUsage::All;
|
|
}
|
|
|
|
/*!
|
|
* \brief Adds sync databases listed in the Pacman config file. Also reads the cache dir.
|
|
*/
|
|
void Manager::addDataBasesFromPacmanConfig()
|
|
{
|
|
// open config file and parse as ini
|
|
try {
|
|
IniFile configIni;
|
|
{
|
|
fstream configFile;
|
|
configFile.exceptions(ios_base::failbit | ios_base::badbit);
|
|
configFile.open(m_config.pacmanConfFile().toLocal8Bit().data(), ios_base::in);
|
|
configIni.parse(configFile);
|
|
}
|
|
// determine current cpu archtitecture (required for server URLs)
|
|
static const string sysArch(QSysInfo::currentCpuArchitecture().toStdString());
|
|
string arch(sysArch);
|
|
const auto &config = configIni.data();
|
|
// read relevant options
|
|
static const string sigLevelKey("SigLevel");
|
|
static const string usageKey("Usage");
|
|
SignatureLevel 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));
|
|
}
|
|
}
|
|
// register sync databases
|
|
unordered_map<string, IniFile> includedInis;
|
|
for(const auto &scope : config) {
|
|
if(scope.first != "options") {
|
|
// read and validate database name
|
|
QString dbName = QString::fromLocal8Bit(scope.first.c_str());
|
|
if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
|
|
cerr << shchar << "Error: Unable to add database from pacman config: The database name mustn't be \"local\" because this name is reserved for the local database." << endl;
|
|
} else if(dbName.startsWith(QLatin1String("aur"), 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;
|
|
} else {
|
|
// read sig level and usage
|
|
const auto &sigLevelStr = lastValue(scope.second, sigLevelKey);
|
|
SignatureLevel sigLevel = sigLevelStr.empty() ? globalSigLevel : parseSigLevel(sigLevelStr);
|
|
RepositoryUsage usage = parseUsage(lastValue(scope.second, usageKey));
|
|
|
|
// 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>(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(localDatabase() && usage & RepositoryUsage::Upgrade) {
|
|
// -> db is used to upgrade local database
|
|
localDatabase()->upgradeSources() << emplacedDb;
|
|
}
|
|
|
|
// add servers
|
|
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);
|
|
emplacedDb->serverUrls() << Utilities::qstr(url);
|
|
if(m_config.isVerbose() || m_config.runServer()) {
|
|
cerr << shchar << " Added server: " << url << endl;
|
|
}
|
|
}
|
|
|
|
// add included servers / parse mirror list
|
|
for(auto range = scope.second.equal_range("Include"); range.first != range.second; ++range.first) {
|
|
const auto &path = range.first->second;
|
|
auto &includedIni = includedInis[path];
|
|
if(includedIni.data().empty()) {
|
|
try {
|
|
fstream includedFile;
|
|
includedFile.exceptions(ios_base::failbit | ios_base::badbit);
|
|
includedFile.open(path, ios_base::in);
|
|
includedIni.parse(includedFile);
|
|
} catch(...) {
|
|
catchIoFailure();
|
|
cerr << shchar << "Error: An IO exception occured when parsing the included file \"" << path << "\"." << endl;
|
|
}
|
|
}
|
|
try {
|
|
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);
|
|
emplacedDb->serverUrls() << Utilities::qstr(url);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch(...) {
|
|
catchIoFailure();
|
|
throwIoFailure("Error: An IO exception occured when parsing the config file.");
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Adds sync databases listed in the repository index configuration.
|
|
*/
|
|
void Manager::addDatabasesFromRepoIndexConfig()
|
|
{
|
|
// check whether an entry already exists, if not create a new one
|
|
for(const RepoEntry &repoEntry : m_config.repoEntries()) {
|
|
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());
|
|
}
|
|
if(repoEntry.sigLevel() != SignatureLevel::UseDefault) {
|
|
syncDb->setSigLevel(repoEntry.sigLevel());
|
|
}
|
|
syncDb->setPackagesDirectory(repoEntry.packageDir());
|
|
syncDb->setSourcesDirectory(repoEntry.sourceDir());
|
|
if(!repoEntry.maxDatabaseAge().isNull()) {
|
|
syncDb->setMaxPackageAge(repoEntry.maxDatabaseAge());
|
|
}
|
|
} else {
|
|
if(repoEntry.isIgnored()) {
|
|
continue;
|
|
}
|
|
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;
|
|
if(repoEntry.databasePath().isEmpty()) {
|
|
// no path specified -> use defaults
|
|
dbPath = findDatabasePath(repoEntry.name(), !repoEntry.maxDatabaseAge().isNull(), true);
|
|
} else {
|
|
dbPath = repoEntry.databasePath();
|
|
}
|
|
|
|
// 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));
|
|
connectRepository(syncDb = m_syncDbs.back().get());
|
|
m_syncDbMap.emplace(repoEntry.name(), syncDb);
|
|
|
|
syncDb->setSourcesDirectory(repoEntry.sourceDir());
|
|
syncDb->setPackagesDirectory(repoEntry.packageDir());
|
|
if(!repoEntry.maxDatabaseAge().isNull()) {
|
|
syncDb->setMaxPackageAge(repoEntry.maxDatabaseAge());
|
|
}
|
|
if(m_config.isVerbose() || m_config.runServer()) {
|
|
cerr << shchar << "Added database [" << repoEntry.name() << ']' << endl;
|
|
}
|
|
}
|
|
}
|
|
if(syncDb) {
|
|
syncDb->serverUrls() << repoEntry.server();
|
|
}
|
|
}
|
|
|
|
// add upgrade sources
|
|
for(const RepoEntry &repoEntry : m_config.repoEntries()) {
|
|
try {
|
|
auto &upgradeSources = m_syncDbMap.at(repoEntry.name())->upgradeSources();
|
|
for(const auto &upgradeSourceName : repoEntry.upgradeSources()) {
|
|
if(auto *source = repositoryByName(upgradeSourceName)) {
|
|
upgradeSources << source;
|
|
} else {
|
|
cerr << shchar << "Warning: The specified upgrade source \"" << upgradeSourceName << "\" can not be found and will be ignored." << endl;
|
|
}
|
|
}
|
|
} catch(const out_of_range &) {
|
|
// entry should have been added before
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Initiates all ALPM data bases.
|
|
* \remarks Must be called, after all relevant sync databases have been added (eg. via applyPacmanConfig()).
|
|
*/
|
|
void Manager::initAlpmDataBases()
|
|
{
|
|
// connect computeRequiredBy()
|
|
if(m_config.runServer()) {
|
|
if(localDatabase()) {
|
|
QObject::connect(localDatabase(), &AlpmDatabase::initialized, bind(&Manager::computeRequiredBy, this, localDatabase()));
|
|
}
|
|
for(auto &syncDb : m_syncDbs) {
|
|
QObject::connect(syncDb.get(), &AlpmDatabase::initialized, bind(&Manager::computeRequiredBy, this, syncDb.get()));
|
|
}
|
|
} else {
|
|
if(localDatabase()) {
|
|
QObject::connect(localDatabase(), &AlpmDatabase::initialized, this, &Manager::emitUpdatesAvailable);
|
|
}
|
|
for(auto &syncDb : m_syncDbs) {
|
|
QObject::connect(syncDb.get(), &AlpmDatabase::initialized, this, &Manager::emitUpdatesAvailable);
|
|
}
|
|
}
|
|
|
|
// 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 &syncDb : m_syncDbs) {
|
|
loaders << static_cast<AlpmPackageLoader *>(syncDb->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; the repo has ownership
|
|
}
|
|
}
|
|
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) {
|
|
if(syncDb->isBusy()) {
|
|
syncDb->asSoonAsPossible(bind(&Manager::computeRequiredBy, this, repo));
|
|
return;
|
|
}
|
|
relevantDbs << syncDb.get();
|
|
}
|
|
}
|
|
repo->computeRequiredBy(relevantDbs);
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns whether automatic cache maintenance is enabled.
|
|
* \remarks If enabled, the cache will be cleaned and saved frequently.
|
|
*/
|
|
bool Manager::isAutoCacheMaintenanceEnabled() const
|
|
{
|
|
return m_cacheTimer && m_cacheTimer->isActive();
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets whether automatic cache maintenacne is enabled.
|
|
* \sa isAutoCacheMaintenanceEnabled()
|
|
*/
|
|
void Manager::setAutoCacheMaintenanceEnabled(bool enabled)
|
|
{
|
|
if(isAutoCacheMaintenanceEnabled() != enabled) {
|
|
if(enabled) {
|
|
if(!m_cacheTimer) {
|
|
m_cacheTimer = make_unique<QTimer>();
|
|
m_cacheTimer->setInterval(5 * 60 * 1000);
|
|
QObject::connect(m_cacheTimer.get(), &QTimer::timeout, bind(&Manager::maintainCache, this));
|
|
}
|
|
m_cacheTimer->start();
|
|
} else {
|
|
m_cacheTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Writes the cache for all repositories where caching makes sense.
|
|
*/
|
|
void Manager::writeCache()
|
|
{
|
|
// could iterate through all repos and check isCachingUseful() but
|
|
// currently its just the AUR which is needed to be cached
|
|
if(userRepository()) {
|
|
QDir().mkpath(config().cacheDir());
|
|
QFile file(config().cacheDir() % QChar('/') % userRepository()->name() % QStringLiteral(".cache"));
|
|
if(file.open(QFileDevice::WriteOnly)) {
|
|
QDataStream stream(&file);
|
|
userRepository()->writeToCacheStream(stream);
|
|
// if warnings/errors occur, these will be printed directly by writeToCacheStream()
|
|
} else {
|
|
cerr << shchar << "Warning: Unable to write cache file for the AUR." << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Restores the cache for all repositories where caching makes sense.
|
|
*/
|
|
void Manager::restoreCache()
|
|
{
|
|
// could iterate through all repos and check isCachingUseful() but
|
|
// currently its just the AUR which is needed to be cached
|
|
if(userRepository()) {
|
|
QFile file(config().cacheDir() % QChar('/') % userRepository()->name() % QStringLiteral(".cache"));
|
|
if(file.exists()) {
|
|
if(file.open(QFileDevice::ReadOnly)) {
|
|
QDataStream stream(&file);
|
|
userRepository()->restoreFromCacheStream(stream);
|
|
// if warnings/errors occur, these will be printed directly by restoreFromCacheStream()
|
|
} else {
|
|
cerr << shchar << "Warning: Unable to open cache file for the AUR." << endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Removes outdated packages from the cache.
|
|
* \remarks Currently only the AUR cache is affected.
|
|
*/
|
|
void Manager::cleanCache()
|
|
{
|
|
if(userRepository()) {
|
|
userRepository()->cleanOutdatedPackages();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Removes all cached packages.
|
|
* \remarks Currently only the AUR cache is affected.
|
|
*/
|
|
void Manager::wipeCache()
|
|
{
|
|
if(userRepository()) {
|
|
userRepository()->wipePackages();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Cleans and writes the cache.
|
|
* \remarks Automatically called if automatic cache maintenance is enabled.
|
|
*/
|
|
void Manager::maintainCache()
|
|
{
|
|
cleanCache();
|
|
// TODO: write cache only when modified
|
|
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(isAutoUpdateEnabled() != 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()) {
|
|
QReadLocker locker(localDatabase()->lock());
|
|
if(localDatabase()->hasOutdatedPackages()) {
|
|
locker.unlock();
|
|
localDatabase()->init();
|
|
}
|
|
}
|
|
for(auto &syncDb : m_syncDbs) {
|
|
QReadLocker locker(syncDb->lock());
|
|
if(syncDb->hasOutdatedPackages()) {
|
|
locker.unlock();
|
|
syncDb->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.
|
|
*/
|
|
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.
|
|
*/
|
|
const QJsonObject &Manager::basicRepoInfo() const
|
|
{
|
|
QMutexLocker locker(&m_basicRepoInfoMutex);
|
|
if(m_basicRepoInfo.isEmpty()) {
|
|
if(m_basicRepoInfo.isEmpty()) {
|
|
// add local data base
|
|
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()) {
|
|
if(syncDb.second->isBusy()) {
|
|
m_basicRepoInfo.insert(syncDb.first, QStringLiteral("incomplete"));
|
|
} else {
|
|
// 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()) {
|
|
if(userRepository()->isBusy()) {
|
|
m_basicRepoInfo.insert(QStringLiteral("aur"), QStringLiteral("incomplete"));
|
|
} else {
|
|
QReadLocker locker(userRepository()->lock());
|
|
m_basicRepoInfo.insert(userRepository()->name(), userRepository()->basicInfo());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return m_basicRepoInfo;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns package information for the specified selection of packages.
|
|
* \remarks Does not request any information and hence will only return information
|
|
* which does not need to be requested or has been requested yet.
|
|
*/
|
|
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())) {
|
|
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(!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"));
|
|
}
|
|
results << res;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// specified repository can not be found
|
|
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()) {
|
|
if(m_groupInfo.empty()) {
|
|
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) {
|
|
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());
|
|
}
|
|
|
|
/*!
|
|
* \brief Removes all ALPM databases.
|
|
*/
|
|
void Manager::removeAllDatabases()
|
|
{
|
|
m_localDb.reset();
|
|
m_syncDbs.clear();
|
|
m_syncDbMap.clear();
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the ALPM database with the specified name.
|
|
*/
|
|
const AlpmDatabase *Manager::databaseByName(const QString &dbName) const
|
|
{
|
|
if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
|
|
return localDatabase();
|
|
} else {
|
|
try {
|
|
return m_syncDbMap.at(dbName);
|
|
} catch(const out_of_range &) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the ALPM database with the specified name.
|
|
*/
|
|
AlpmDatabase *Manager::databaseByName(const QString &dbName)
|
|
{
|
|
if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
|
|
return localDatabase();
|
|
} else {
|
|
try {
|
|
return m_syncDbMap.at(dbName);
|
|
} catch(const out_of_range &) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the package source with the specified name.
|
|
*/
|
|
const Repository *Manager::repositoryByName(const QString &name) const
|
|
{
|
|
if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
|
|
return localDatabase();
|
|
} else if(name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0) {
|
|
return userRepository();
|
|
} else {
|
|
try {
|
|
return m_syncDbMap.at(name);
|
|
} catch(const out_of_range &) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the package source with the specified name.
|
|
*/
|
|
Repository *Manager::repositoryByName(const QString &name)
|
|
{
|
|
if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
|
|
return localDatabase();
|
|
} else if(name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0) {
|
|
return userRepository();
|
|
} else {
|
|
try {
|
|
return m_syncDbMap.at(name);
|
|
} catch(const out_of_range &) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Finds the default path for the database with the specified \a name.
|
|
* \remarks
|
|
* - If the database couldn't be located and \a printError is true
|
|
* an error message is printed to cerr.
|
|
* - If \a updatesRequired is false using the pacman sync dbs is not considered.
|
|
* - This is used if the database path hasn't been specified explicitely.
|
|
*/
|
|
QString Manager::findDatabasePath(const QString &name, bool updatesRequired, bool printError) const
|
|
{
|
|
QFileInfo dbPathRegular(m_config.storageDir() % QStringLiteral("/sync/") % name % QStringLiteral(".db"));
|
|
QFileInfo dbPathWithFiles(m_config.storageDir() % QStringLiteral("/sync/") % name % QStringLiteral(".files"));
|
|
if(dbPathWithFiles.isFile() && (!dbPathRegular.isFile() || dbPathWithFiles.lastModified() > dbPathRegular.lastModified())) {
|
|
return dbPathWithFiles.absoluteFilePath();
|
|
} else if(dbPathRegular.isFile()) {
|
|
return dbPathRegular.absoluteFilePath();
|
|
} else if(!updatesRequired) {
|
|
// can't find database file in storage directory and database should not be updated automatically
|
|
// -> it might be possible to use the databases from pacman
|
|
QFileInfo pacmanDbPathRegular(m_config.alpmDbPath() % QStringLiteral("/sync/") % name % QStringLiteral(".db"));
|
|
QFileInfo pacmanDbPathWithFiles(m_config.alpmDbPath() % QStringLiteral("/sync/") % name % QStringLiteral(".files"));
|
|
if(pacmanDbPathWithFiles.isFile() && (!pacmanDbPathRegular.isFile() || pacmanDbPathWithFiles.lastModified() > pacmanDbPathRegular.lastModified())) {
|
|
return pacmanDbPathWithFiles.absoluteFilePath();
|
|
} else if(pacmanDbPathRegular.isFile()) {
|
|
return pacmanDbPathRegular.absoluteFilePath();
|
|
}
|
|
}
|
|
if(printError) {
|
|
cerr << shchar << "Error: Unable to locate database file for [" << name.toLocal8Bit().data() << "]" << endl;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns a database path for the database with the specified \a name.
|
|
*/
|
|
QString Manager::proposedDatabasePath(const QString &name, bool files) const
|
|
{
|
|
return m_config.storageDir() % QStringLiteral("/sync/") % name % (files ? QStringLiteral(".files") : QStringLiteral(".db"));
|
|
}
|
|
|
|
}
|