#include "./manager.h" #include "./utilities.h" #include "./config.h" #include "./alpmdatabase.h" #include "../network/userrepository.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(); } } /*! * \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(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(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(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(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 &mm, const string &key) { static const string defaultValue; const auto i = find_if(mm.crbegin(), mm.crend(), [&key] (const pair &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 >(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 >(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 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(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(url, "$repo", scope.first); findAndReplace(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(url, "$repo", scope.first); findAndReplace(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(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 loaders; loaders.reserve(m_syncDbMap.size() + (localDatabase() ? 1 : 0)); if(localDatabase()) { loaders << static_cast(localDatabase()->init()); } for(auto &syncDb : m_syncDbs) { loaders << static_cast(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 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(); 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(); 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 &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(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")); } }