diff --git a/.gitignore b/.gitignore index c22aaeb..42c1099 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ Makefile* # documentation /doc + +# testing +/testing +/cert diff --git a/alpm/alpmdatabase.cpp b/alpm/alpmdatabase.cpp index 1420839..de5a0af 100644 --- a/alpm/alpmdatabase.cpp +++ b/alpm/alpmdatabase.cpp @@ -52,7 +52,7 @@ private: const PackageOrigin m_origin; }; -void AlpmDatabase::loadDescriptions(QList > > &descriptions) +DatabaseError AlpmDatabase::loadDescriptions(QList > > &descriptions) { QFileInfo pathInfo(databasePath()); if(pathInfo.isDir()) { @@ -71,7 +71,7 @@ void AlpmDatabase::loadDescriptions(QList > > & 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 > > & } 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 > > & if(descEntry->isFile()) { descData << static_cast(descEntry)->data(); } else { - // there shouldn't be any subdirs + // there shouldn't be any subdirs anyways } } } @@ -109,22 +109,25 @@ void AlpmDatabase::loadDescriptions(QList > > & 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 > > 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 AlpmDatabase::emptyPackage() void AlpmDatabase::databaseDownloadFinished() { auto *reply = static_cast(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); + } } } diff --git a/alpm/alpmdatabase.h b/alpm/alpmdatabase.h index d3ce720..ad8dfcd 100644 --- a/alpm/alpmdatabase.h +++ b/alpm/alpmdatabase.h @@ -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 > > 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 emptyPackage(); @@ -54,7 +83,7 @@ private slots: void databaseDownloadFinished(); private: - void loadDescriptions(QList > > &descriptions); + DatabaseError loadDescriptions(QList > > &descriptions); QNetworkRequest regularDatabaseRequest(); QNetworkRequest filesDatabaseRequest(); diff --git a/alpm/config.cpp b/alpm/config.cpp index 95b19d5..957c64d 100644 --- a/alpm/config.cpp +++ b/alpm/config.cpp @@ -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 diff --git a/alpm/config.h b/alpm/config.h index 0dbdd47..ea0e0d5 100644 --- a/alpm/config.h +++ b/alpm/config.h @@ -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 &repoEntries() const; bool isAurEnabled() const; bool isVerbose() const; @@ -161,7 +169,8 @@ private: bool m_serverInsecure; QList 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 &Config::repoEntries() const diff --git a/alpm/manager.cpp b/alpm/manager.cpp index d0cd12d..b97fd8b 100644 --- a/alpm/manager.cpp +++ b/alpm/manager.cpp @@ -23,6 +23,7 @@ #include #include #include +#include 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(); } @@ -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(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(url, "$repo", scope.first); findAndReplace(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(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 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 loaders; + loaders.reserve(m_syncDbMap.size() + (localDatabase() ? 1 : 0)); + if(localDatabase()) { + loaders << static_cast(localDatabase()->init()); + } + for(auto &syncDbEntry : m_syncDbMap) { + loaders << static_cast(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 > 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 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(); + 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 &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(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 { diff --git a/alpm/manager.h b/alpm/manager.h index c954954..55e69ef 100644 --- a/alpm/manager.h +++ b/alpm/manager.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -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 &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 m_cacheTimer; + std::unique_ptr 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(); } diff --git a/alpm/package.cpp b/alpm/package.cpp index 501d8d0..cc72ffd 100644 --- a/alpm/package.cpp +++ b/alpm/package.cpp @@ -2,7 +2,6 @@ #include "./alpmdatabase.h" #include "./utilities.h" #include "./repository.h" -#include "./manager.h" #include #include @@ -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 &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(installedSize()))); put(info, QStringLiteral("csize"), QJsonValue(static_cast(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 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?) } diff --git a/alpm/package.h b/alpm/package.h index 5fa61d8..8ce5839 100644 --- a/alpm/package.h +++ b/alpm/package.h @@ -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(static_cast(lhs) & static_cast(rhs)); + return static_cast(static_cast(lhs) | static_cast(rhs)); } constexpr bool operator &(PackageValidation lhs, PackageValidation rhs) @@ -84,6 +83,23 @@ constexpr bool operator &(PackageValidation lhs, PackageValidation rhs) return (static_cast(lhs) & static_cast(rhs)) != 0; } +constexpr int operator ~(PackageValidation lhs) +{ + return ~static_cast(lhs); +} + +inline PackageValidation &operator |=(PackageValidation &lhs, PackageValidation rhs) +{ + lhs = static_cast(static_cast(lhs) | static_cast(rhs)); + return lhs; +} + +inline PackageValidation &operator &=(PackageValidation &lhs, int rhs) +{ + lhs = static_cast(static_cast(lhs) & rhs); + return lhs; +} + /*! * \brief The SignatureLevel enum specifies PGP signature verification options. */ @@ -227,7 +243,7 @@ public: const QList &provides() const; const QList &replaces() const; bool isRequiredByComputed() const; - void computeRequiredBy(Manager &manager); + void computeRequiredBy(const QList &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 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); diff --git a/alpm/packagefinder.cpp b/alpm/packagefinder.cpp index 22264ef..8900fcb 100644 --- a/alpm/packagefinder.cpp +++ b/alpm/packagefinder.cpp @@ -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(sender()); auto *repo = reply->repository(); diff --git a/alpm/packageinfolookup.cpp b/alpm/packageinfolookup.cpp index 23739e7..778e861 100644 --- a/alpm/packageinfolookup.cpp +++ b/alpm/packageinfolookup.cpp @@ -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(sender()); reply->deleteLater(); if(reply->error().isEmpty()) { diff --git a/alpm/packageinfolookup.h b/alpm/packageinfolookup.h index ae3683e..89c8be2 100644 --- a/alpm/packageinfolookup.h +++ b/alpm/packageinfolookup.h @@ -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 > m_repos; QList m_packages; }; diff --git a/alpm/repository.cpp b/alpm/repository.cpp index 1d8c808..eaee9d7 100644 --- a/alpm/repository.cpp +++ b/alpm/repository.cpp @@ -46,7 +46,9 @@ Reply::Reply(const QList networkReplies) : */ void Reply::replyFinished() { +#ifdef DEBUG_BUILD assert(m_remainingReplies); +#endif processData(static_cast(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; + connect(watcher, &QFutureWatcher::finished, this, &Repository::removeBusyFlag); + connect(watcher, &QFutureWatcher::finished, this, &Repository::initialized); + connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater); + watcher->setFuture(loader->future()); + } + return loader; + } else { + return nullptr; + } +} + +void Repository::initAsSoonAsPossible() +{ + if(isBusy()) { + auto connection = make_shared(); + *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 Repository::packageByFilter(std::function &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 m_blockedRepos; +}; + class ComputeRequired { public: - ComputeRequired(Manager &manager, bool forceUpdate) : - m_manager(manager), + ComputeRequired(const QList &relevantRepos, bool forceUpdate) : + m_relevantRepos(relevantRepos), m_forceUpdate(forceUpdate) {} void operator () (const pair > &packageEntry) { if(m_forceUpdate || !packageEntry.second->isRequiredByComputed()) { - packageEntry.second->computeRequiredBy(m_manager); + packageEntry.second->computeRequiredBy(m_relevantRepos); } } private: - Manager &m_manager; + const QList 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 Repository::computeRequiredBy(Manager &manager, bool forceUpdate) +QFuture Repository::computeRequiredBy(const QList &relevantRepositories, bool forceUpdate) { - return QtConcurrent::map(m_packages, ComputeRequired(manager, forceUpdate)); + addBusyFlag(); // flag repository as busy + auto *watcher = new QFutureWatcher; + connect(watcher, &QFutureWatcher::finished, this, &Repository::removeBusyFlag); + connect(watcher, &QFutureWatcher::finished, this, &Repository::requiredByComputed); + connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater); + watcher->setFuture(QtConcurrent::map(m_packages, ComputeRequired(relevantRepositories, forceUpdate))); + return watcher->future(); } /*! @@ -771,6 +854,8 @@ void Repository::parseDescriptions(const QList &descriptions, QStrin // put last field if(!currentFieldName.isEmpty()) { fields << QPair(currentFieldName, currentFieldValues); + currentFieldName.clear(); + currentFieldValues.clear(); } } } @@ -944,4 +1029,21 @@ Package *Repository::addPackageFromDescription(QString name, const QList packagesProviding(const Dependency &dependency) const; QList packageByFilter(std::function pred); - QFuture computeRequiredBy(Manager &manager, bool forceUpdate = false); + QFuture computeRequiredBy(const QList &relevantRepositories, bool forceUpdate = false); QJsonObject suggestions(const QString &term) const; // upgrade lookup @@ -277,10 +281,29 @@ public: static const uint32 invalidIndex = static_cast(-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 m_upgradeSources; QString m_srcDir; QString m_pkgDir; + QAtomicInteger 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. * diff --git a/alpm/suggestionslookup.cpp b/alpm/suggestionslookup.cpp index 37a742f..93ca49d 100644 --- a/alpm/suggestionslookup.cpp +++ b/alpm/suggestionslookup.cpp @@ -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(sender()); { QReadLocker locker(reply->repository()->lock()); diff --git a/alpm/suggestionslookup.h b/alpm/suggestionslookup.h index a0b829d..6027d0d 100644 --- a/alpm/suggestionslookup.h +++ b/alpm/suggestionslookup.h @@ -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 m_repos; + const QString m_searchTerm; }; } // namespace RepoIndex diff --git a/alpm/upgradelookup.cpp b/alpm/upgradelookup.cpp index 222104b..f2c4fce 100644 --- a/alpm/upgradelookup.cpp +++ b/alpm/upgradelookup.cpp @@ -52,28 +52,7 @@ UpgradeLookupProcess::UpgradeLookupProcess(UpgradeLookup *upgradeLookup, Reposit m_watcher(new QFutureWatcher(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::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(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(sender())->results(); m_softwareUpgradesArray.reserve(m_softwareUpgradesArray.size() + results.newVersions.size()); diff --git a/alpm/upgradelookup.h b/alpm/upgradelookup.h index 53475fc..14b2b67 100644 --- a/alpm/upgradelookup.h +++ b/alpm/upgradelookup.h @@ -91,6 +91,7 @@ signals: void finished(); private slots: + void requestSources(); void sourceReady(); void checkUpgrades(); diff --git a/alpm/utilities.cpp b/alpm/utilities.cpp index 2d9c1e4..7ff7ab0 100644 --- a/alpm/utilities.cpp +++ b/alpm/utilities.cpp @@ -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"); diff --git a/general.pri b/general.pri deleted file mode 100644 index f417d7d..0000000 --- a/general.pri +++ /dev/null @@ -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 -} diff --git a/main.cpp b/main.cpp index 0bc22b5..68611c9 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,8 @@ #include #include +#include + 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()) { diff --git a/network/connection.cpp b/network/connection.cpp index 70167d8..274ac8e 100644 --- a/network/connection.cpp +++ b/network/connection.cpp @@ -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 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); } diff --git a/network/connection.h b/network/connection.h index bc02424..72a9745 100644 --- a/network/connection.h +++ b/network/connection.h @@ -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 diff --git a/testing/repoindex.conf.js b/testing/repoindex.conf.js index a9b9d71..6f393fb 100644 --- a/testing/repoindex.conf.js +++ b/testing/repoindex.conf.js @@ -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" - ] + ] } ] } diff --git a/web/index.html b/web/index.html index cd8e73f..0deae09 100644 --- a/web/index.html +++ b/web/index.html @@ -44,7 +44,7 @@ - +