#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 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) : m_config(config), m_writeCacheBeforeGone(true), m_sigLevel(defaultSigLevel), m_localFileSigLevel(SignatureLevel::UseDefault) { addLocalDatabase(); if(config.isAurEnabled()) { m_userRepo = make_unique(m_networkAccessManager); } } /*! * \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); cerr << shchar << "Added [" << dbName << "]" << endl; if(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 (const ios_base::failure &) { cerr << shchar << "Error: An IO exception occured when parsing the included file \"" << path << "\"." << endl; } } try { for(auto &scope : includedIni.data()) { if(scope.first.empty()) { for(auto range = scope.second.equal_range("Server"); range.first != range.second; ++range.first) { string url = range.first->second; findAndReplace(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 (const ios_base::failure &) { throw ios_base::failure("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 = nullptr; try { syncDb = m_syncDbMap.at(repoEntry.name()); 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()); } catch(const out_of_range &) { 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 { // 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)); 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.servers(); } } // 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(bool computeRequiredBy) { { // call the init method if(m_config.isVerbose() || m_config.runServer()) { cerr << "Initializing ALPM databases ... "; cerr.flush(); } QList loaders; loaders.reserve(m_syncDbMap.size() + 1); loaders << localDataBase()->init(); for(auto &syncDbEntry : m_syncDbMap) { loaders << syncDbEntry.second->init(); } for(auto *loader : loaders) { if(loader) { loader->future().waitForFinished(); delete loader; } } 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 > 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 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()) { 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; } } } 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; } } } } void Manager::cleanCache() { // currently clear only AUR cache if(userRepository()) { userRepository()->cleanOutdatedPackages(); } } void Manager::wipeCache() { // currently wipe only AUR cache if(userRepository()) { userRepository()->wipePackages(); } } void Manager::maintainCache() { cleanCache(); // TODO: write cache only when modified writeCache(); } /*! * \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 } /*! * \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 { 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()); } // 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()); } else { m_basicRepoInfo.insert(syncDb.first, syncDb.second->basicInfo()); } } // add AUR if(userRepository()) { 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. */ const QJsonArray Manager::packageInfo(const QJsonObject &pkgSelection, PackageInfoPart part) const { QJsonArray results; for(auto i = pkgSelection.constBegin(), end = pkgSelection.constEnd(); i != end; ++i) { if(auto *repo = repositoryByName(i.key())) { for(const auto &entry : i.value().toArray()) { const auto entryObj = entry.toObject(); const auto pkgName = entryObj.value(QStringLiteral("name")).toString(); if(!pkgName.isEmpty()) { QJsonObject res; res.insert(QStringLiteral("name"), pkgName); res.insert(QStringLiteral("repo"), repo->name()); 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(part & Details) { res.insert(QStringLiteral("details"), pkg->detailedInfo()); } } else { res.insert(QStringLiteral("error"), QStringLiteral("na")); } 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; } } return results; } /*! * \brief Returns group information for the local database and all registred sync databases. */ const QJsonArray &Manager::groupInfo() const { if(m_groupInfo.empty()) { QMutexLocker locker(&m_groupInfoMutex); if(m_groupInfo.empty()) { m_groupInfo << localDataBase()->groupInfo(); for(const auto &db : m_syncDbMap) { m_groupInfo << db.second->groupInfo(); } } } return m_groupInfo; } /*! * \brief Add the local database. */ void Manager::addLocalDatabase() { m_localDb = make_unique(QStringLiteral("local"), config().alpmDbPath() % QStringLiteral("/local"), RepositoryUsage::None, SignatureLevel::UseDefault, 0); } /*! * \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(config().isAurEnabled() && (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(config().isAurEnabled() && (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; } } /*! * \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")); } }