From 6d06663791979d5b676a3ff884570faf80104826 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 11 Sep 2015 21:59:47 +0200 Subject: [PATCH] caching is now implemented, overall improvements --- alpm/alpmdatabase.cpp | 3 +- alpm/alpmpackage.cpp | 12 +- alpm/aurpackage.cpp | 8 + alpm/aurpackage.h | 1 + alpm/config.cpp | 18 ++- alpm/config.h | 7 + alpm/manager.cpp | 91 ++++++++++-- alpm/manager.h | 35 ++++- alpm/package.cpp | 295 ++++++++++++++++++++++++++++++++----- alpm/package.h | 37 +++-- alpm/repository.cpp | 149 ++++++++++++++++++- alpm/repository.h | 27 +++- alpm/upgradelookup.cpp | 5 +- alpm/upgradelookup.h | 3 + main.cpp | 8 +- network/connection.cpp | 48 ++++-- network/connection.h | 6 +- network/server.cpp | 9 +- network/userrepository.cpp | 18 ++- network/userrepository.h | 3 + web/index.html | 3 + web/js/alpm.js | 8 + web/js/repomanagement.js | 2 +- 23 files changed, 690 insertions(+), 106 deletions(-) diff --git a/alpm/alpmdatabase.cpp b/alpm/alpmdatabase.cpp index 71cd66d..bb445c2 100644 --- a/alpm/alpmdatabase.cpp +++ b/alpm/alpmdatabase.cpp @@ -41,6 +41,7 @@ public: } m_db->packages().emplace(res->name(), move(res)); } + private: AlpmDatabase *m_db; QMutex *m_mutex; @@ -89,7 +90,6 @@ PackageLoader *AlpmDatabase::init() m_serverUrls << qstr(str); } m_sigLevel = alpm_db_get_siglevel(m_ptr); - return new PackageLoader(this); } @@ -109,6 +109,7 @@ PackageDetailAvailability AlpmDatabase::requestsRequired(PackageDetail packageDe case PackageDetail::Basics: case PackageDetail::Dependencies: case PackageDetail::PackageInfo: + case PackageDetail::AllAvailable: return PackageDetailAvailability::Immediately; default: return PackageDetailAvailability::Never; diff --git a/alpm/alpmpackage.cpp b/alpm/alpmpackage.cpp index d050de1..d25e615 100644 --- a/alpm/alpmpackage.cpp +++ b/alpm/alpmpackage.cpp @@ -4,6 +4,8 @@ #include +#include + using namespace ChronoUtilities; namespace RepoIndex { @@ -54,11 +56,11 @@ AlpmPackage::AlpmPackage(alpm_pkg_t *package, AlpmDatabase *source) : m_conflicts = depinfos(alpm_pkg_get_conflicts(package)); m_provides = depinfos(alpm_pkg_get_provides(package)); m_replaces = depinfos(alpm_pkg_get_replaces(package)); - alpm_list_t *tmp; - m_requiredBy = qstrlist(tmp = alpm_pkg_compute_requiredby(package)); - FREELIST(tmp); - m_optionalFor = qstrlist(tmp = alpm_pkg_compute_optionalfor(package)); - FREELIST(tmp); + //alpm_list_t *tmp; + //m_requiredBy = qstrlist(tmp = alpm_pkg_compute_requiredby(package)); + //FREELIST(tmp); + //m_optionalFor = qstrlist(tmp = alpm_pkg_compute_optionalfor(package)); + //FREELIST(tmp); m_hasInstallScript = alpm_pkg_has_scriptlet(package); m_fileName = qstr(alpm_pkg_get_filename(package)); m_buildDate = DateTime::fromTimeStamp(alpm_pkg_get_builddate(package)); diff --git a/alpm/aurpackage.cpp b/alpm/aurpackage.cpp index bcddef7..5f5b812 100644 --- a/alpm/aurpackage.cpp +++ b/alpm/aurpackage.cpp @@ -37,5 +37,13 @@ AurPackage::AurPackage(const QJsonValue &aurJsonValue, UserRepository *source) : m_tarUrl = obj.value(QStringLiteral("URLPath")).toString(); } +/*! + * \brief Creates a new, empty instance. + * \remarks The only purpose of this c'tor is to use it with restoreFromCacheStream(). + */ +AurPackage::AurPackage(UserRepository *source) : + Package(QString(), source) +{} + } // namespace PackageManagement diff --git a/alpm/aurpackage.h b/alpm/aurpackage.h index 9c1f3ac..a3b582c 100644 --- a/alpm/aurpackage.h +++ b/alpm/aurpackage.h @@ -11,6 +11,7 @@ class AurPackage : public Package { public: AurPackage(const QJsonValue &aurJsonValue, UserRepository *source); + AurPackage(UserRepository *source); }; } // namespace PackageManagement diff --git a/alpm/config.cpp b/alpm/config.cpp index 2f9a0fe..85a0fa1 100644 --- a/alpm/config.cpp +++ b/alpm/config.cpp @@ -113,7 +113,7 @@ ConfigArgs::ConfigArgs(ArgumentParser &parser) : shSyntaxArg.setCombinable(true); repoArg.setRequiredValueCount(1); repoArg.setValueNames({"repo name"}); - serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg}); + serverArg.setSecondaryArguments({&rootdirArg, &dbpathArg, &pacmanConfArg, &certFileArg, &keyFileArg, &websocketAddrArg, &websocketPortArg, &insecureArg, &aurArg, &shSyntaxArg}); upgradeLookupArg.setSecondaryArguments({&shSyntaxArg}); buildOrderArg.setSecondaryArguments({&aurArg, &verboseArg, &shSyntaxArg}); mingwBundleArg.setSecondaryArguments({&targetDirArg, &targetNameArg, &targetFormatArg, &iconThemesArg}); @@ -160,7 +160,20 @@ inline void assign(quint16 &num, const QJsonValue &val) inline void assign(QHostAddress &addr, const QString &val) { if(!val.isEmpty() && !addr.setAddress(val)) { - cerr << shchar << "Error: Unable to parse the specified host address \"" << val.toStdString() << "\"." << endl; + // checking some special values might be useful + if(val == QLatin1String("localhost")) { + addr = QHostAddress::LocalHost; + } else if(val == QLatin1String("localhost-IPv6")) { + addr = QHostAddress::LocalHostIPv6; + } else if(val == QLatin1String("any-IPv4")) { + addr = QHostAddress::AnyIPv4; + } else if(val == QLatin1String("any-IPv6")) { + addr = QHostAddress::AnyIPv6; + } else if(val == QLatin1String("any")) { + addr = QHostAddress::Any; + } else { + cerr << shchar << "Error: Unable to parse the specified host address \"" << val.toStdString() << "\"." << endl; + } } } @@ -206,6 +219,7 @@ void Config::loadFromConfigFile(const QString &configFilePath) m_alpmRootDir = alpmObj.value(QStringLiteral("rootDir")).toString(m_alpmRootDir); m_alpmDbPath = alpmObj.value(QStringLiteral("dbPath")).toString(m_alpmDbPath); m_pacmanConfFile = alpmObj.value(QStringLiteral("pacmanConfigFile")).toString(m_pacmanConfFile); + m_cacheDir = mainObj.value(QStringLiteral("cacheDir")).toString(m_cacheDir); auto aurObj = mainObj.value(QStringLiteral("aur")).toObject(); m_aurEnabled = aurObj.value(QStringLiteral("enabled")).toBool(m_aurEnabled); auto serverObj = mainObj.value(QStringLiteral("server")).toObject(); diff --git a/alpm/config.h b/alpm/config.h index 370f5e4..b9bfd0d 100644 --- a/alpm/config.h +++ b/alpm/config.h @@ -117,6 +117,7 @@ public: const QString &alpmRootDir() const; const QString &alpmDbPath() const; const QString &pacmanConfFile() const; + const QString &cacheDir() const; const QHostAddress &websocketServerListeningAddr() const; quint16 websocketServerListeningPort() const; const QString &serverCertFile() const; @@ -137,6 +138,7 @@ private: QString m_alpmRootDir; QString m_alpmDbPath; QString m_pacmanConfFile; + QString m_cacheDir; QHostAddress m_websocketServerListeningAddr; quint16 m_websocketServerListeningPort; @@ -166,6 +168,11 @@ inline const QString &Config::pacmanConfFile() const return m_pacmanConfFile; } +inline const QString &Config::cacheDir() const +{ + return m_cacheDir; +} + inline const QHostAddress &Config::websocketServerListeningAddr() const { return m_websocketServerListeningAddr; diff --git a/alpm/manager.cpp b/alpm/manager.cpp index 3af13fd..fe76511 100644 --- a/alpm/manager.cpp +++ b/alpm/manager.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include @@ -56,6 +59,7 @@ inline ostream &operator <<(ostream &stream, const QString &str) */ Manager::Manager(const Config &config) : m_config(config), + m_writeCacheBeforeGone(true), m_sigLevel(defaultSigLevel), m_localFileSigLevel(ALPM_SIG_USE_DEFAULT) { @@ -74,6 +78,9 @@ Manager::Manager(const Config &config) : */ Manager::~Manager() { + if(m_writeCacheBeforeGone) { + writeCache(); + } alpm_release(m_handle); } @@ -390,7 +397,7 @@ void Manager::applyPacmanConfig() // add sync db to internal map if(usage & ALPM_DB_USAGE_UPGRADE) { // -> db is used to upgrade local database - localDatabase()->upgradeSources() << emplaced.first->second.get(); + localDataBase()->upgradeSources() << emplaced.first->second.get(); } } else { cerr << shchar << "Error: Unable to add sync database [" << scope.first << "]" << endl; @@ -469,17 +476,69 @@ void Manager::applyRepoIndexConfig() * \brief Initiates all ALPM data bases. * \remarks Must be called, after all relevant sync data bases have been registered (eg. via applyPacmanConfig()). */ -void Manager::initAlpmDataBases() +void Manager::initAlpmDataBases(bool computeRequiredBy) { - QList loaders; - loaders.reserve(m_syncDbs.size() + 1); - loaders << localDatabase()->init(); - for(auto &syncDbEntry : m_syncDbs) { - loaders << syncDbEntry.second->init(); + // call the init method + { + QList loaders; + loaders.reserve(m_syncDbs.size() + 1); + loaders << localDataBase()->init(); + for(auto &syncDbEntry : m_syncDbs) { + loaders << syncDbEntry.second->init(); + } + for(auto *loader : loaders) { + loader->future().waitForFinished(); + delete loader; + } } - for(auto *loader : loaders) { - loader->future().waitForFinished(); - delete loader; + // compute required-by and optional-for + if(computeRequiredBy) { + QList > futures; + futures.reserve(m_syncDbs.size() + 1); + futures << localDataBase()->computeRequiredBy(*this); + for(auto &syncDbEntry : m_syncDbs) { + futures << syncDbEntry.second->computeRequiredBy(*this); + } + for(auto &future : futures) { + future.waitForFinished(); + } + } +} + +/*! + * \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; + } + } } } @@ -514,7 +573,7 @@ const QJsonArray &Manager::basicRepoInfo() const QMutexLocker locker(&m_basicRepoInfoMutex); if(m_basicRepoInfo.isEmpty()) { // add local data base - m_basicRepoInfo << localDatabase()->basicInfo(); + m_basicRepoInfo << localDataBase()->basicInfo(); // add sync data bases for(const auto &syncDb : syncDatabases()) { // check if the "sync" database is actually used for syncing @@ -581,7 +640,7 @@ const QJsonArray &Manager::groupInfo() const if(m_groupInfo.empty()) { QMutexLocker locker(&m_groupInfoMutex); if(m_groupInfo.empty()) { - m_groupInfo << localDatabase()->groupInfo(); + m_groupInfo << localDataBase()->groupInfo(); for(const auto &db : m_syncDbs) { m_groupInfo << db.second->groupInfo(); } @@ -596,7 +655,7 @@ const QJsonArray &Manager::groupInfo() const const AlpmDatabase *Manager::databaseByName(const QString &dbName) const { if(dbName.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) { - return localDatabase(); + return localDataBase(); } else { try { return m_syncDbs.at(dbName).get(); @@ -612,7 +671,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_syncDbs.at(dbName).get(); @@ -628,7 +687,7 @@ AlpmDatabase *Manager::databaseByName(const QString &dbName) const Repository *Manager::repositoryByName(const QString &name) const { if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) { - return localDatabase(); + return localDataBase(); } else if(config().isAurEnabled() && (name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0)) { return userRepository(); } else { @@ -646,7 +705,7 @@ const Repository *Manager::repositoryByName(const QString &name) const Repository *Manager::repositoryByName(const QString &name) { if(name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) { - return localDatabase(); + return localDataBase(); } else if(config().isAurEnabled() && (name.compare(QLatin1String("aur"), Qt::CaseInsensitive) == 0)) { return userRepository(); } else { diff --git a/alpm/manager.h b/alpm/manager.h index 6437206..ba3bd36 100644 --- a/alpm/manager.h +++ b/alpm/manager.h @@ -31,6 +31,8 @@ public: // configuration, signature level, etc const Config &config() const; + bool writeCacheBeforeGone() const; + void setWriteCacheBeforeGone(bool writeCacheBeforeGone); int sigLevel() const; void setSigLevel(int sigLevel); int localFileSigLevel() const; @@ -40,7 +42,9 @@ public: static int parseUsage(const std::string &usageStr); void applyPacmanConfig(); void applyRepoIndexConfig(); - void initAlpmDataBases(); + void initAlpmDataBases(bool computeRequiredBy); + void writeCache(); + void restoreCache(); void unregisterSyncDataBases(); @@ -56,8 +60,8 @@ public: void setInstallReason(AlpmPackage *package, alpm_pkgreason_t reason); // 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); @@ -79,6 +83,7 @@ private: void cleanup(); const Config &m_config; + bool m_writeCacheBeforeGone; alpm_handle_t *m_handle; int m_sigLevel; int m_localFileSigLevel; @@ -102,6 +107,26 @@ inline const Config &Manager::config() const return m_config; } +/*! + * \brief Returns whether the manager writes cache files for all relevant repositories before the manager is + * gone (out of scope, deleted, ...). + * \sa setWriteCacheBeforeGone() + */ +inline bool Manager::writeCacheBeforeGone() const +{ + return m_writeCacheBeforeGone; +} + +/*! + * \brief Sets whether the manager writes cache files for all relevant repositories before the manager is + * gone (out of scope, deleted, ...). + * \sa writeCacheBeforeGone() + */ +inline void Manager::setWriteCacheBeforeGone(bool writeCacheBeforeGone) +{ + m_writeCacheBeforeGone = writeCacheBeforeGone; +} + /*! * \brief Returns the signature level. * @@ -172,7 +197,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(); } @@ -180,7 +205,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 603a947..5458da0 100644 --- a/alpm/package.cpp +++ b/alpm/package.cpp @@ -2,11 +2,16 @@ #include "./alpmdatabase.h" #include "./utilities.h" #include "./repository.h" +#include "./manager.h" #include #include #include +#include #include +#include + +#include using namespace std; using namespace ChronoUtilities; @@ -29,6 +34,7 @@ Package::Package(const QString &name, Repository *source) : m_source(source), m_hasGeneralInfo(false), m_name(name), + m_requiredByComputed(false), m_hasInstallScript(false), m_hasBuildRelatedMetaData(false), m_hasInstallRelatedMetaData(false), @@ -41,9 +47,79 @@ Package::Package(const QString &name, Repository *source) : // initialization must be done in derived class } +/*! + * \brief Writes the package-type-specific cache header. + */ +void Package::writeSpecificCacheHeader(QDataStream &out) +{ + Q_UNUSED(out) +} + +/*! + * \brief Restores the package-type-specific cache header. + */ +void Package::restoreSpecificCacheHeader(QDataStream &in) +{ + Q_UNUSED(in) +} + +/*! + * \brief Destroys the package. + */ Package::~Package() {} +/*! + * \brief Computes required-by and optional-for. + */ +void Package::computeRequiredBy(Manager &manager) +{ + 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 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; + 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; +} + bool Package::matches(const QString &name, const QString &version, const Dependency &dependency) { if(name == QStringLiteral("gst-plugins-base-libs") && dependency.name == QStringLiteral("gst-plugins-base-libs")) { @@ -79,9 +155,7 @@ namespace Utilities { inline void put(QJsonObject &obj, const QString &key, const QJsonValue &value) { - if(!value.isNull()) { - obj.insert(key, value); - } + obj.insert(key, value); } inline void put(QJsonObject &obj, const QString &key, const DateTime dateTime) @@ -93,43 +167,117 @@ inline void put(QJsonObject &obj, const QString &key, const DateTime dateTime) inline void put(QJsonObject &obj, const QString &key, const QStringList &values) { - if(!values.isEmpty()) { - put(obj, key, QJsonArray::fromStringList(values)); - } + put(obj, key, QJsonArray::fromStringList(values)); } void put(QJsonObject &obj, const QString &key, const QList &dependencies) { - if(!dependencies.isEmpty()) { - QJsonArray jsonArray; - for(const auto &dep : dependencies) { - QJsonObject depObj; - depObj.insert(QStringLiteral("name"), dep.name); - depObj.insert(QStringLiteral("ver"), dep.version); - switch(dep.mode) { - case ALPM_DEP_MOD_ANY: - depObj.insert(QStringLiteral("mod"), QStringLiteral("any")); - break; - case ALPM_DEP_MOD_EQ: - depObj.insert(QStringLiteral("mod"), QStringLiteral("eq")); - break; - case ALPM_DEP_MOD_GE: - depObj.insert(QStringLiteral("mod"), QStringLiteral("ge")); - break; - case ALPM_DEP_MOD_LE: - depObj.insert(QStringLiteral("mod"), QStringLiteral("le")); - break; - case ALPM_DEP_MOD_GT: - depObj.insert(QStringLiteral("mod"), QStringLiteral("gt")); - break; - case ALPM_DEP_MOD_LT: - depObj.insert(QStringLiteral("mod"), QStringLiteral("lt")); - break; - } - jsonArray << depObj; + QJsonArray jsonArray; + for(const auto &dep : dependencies) { + QJsonObject depObj; + depObj.insert(QStringLiteral("name"), dep.name); + depObj.insert(QStringLiteral("ver"), dep.version); + switch(dep.mode) { + case ALPM_DEP_MOD_ANY: + depObj.insert(QStringLiteral("mod"), QStringLiteral("any")); + break; + case ALPM_DEP_MOD_EQ: + depObj.insert(QStringLiteral("mod"), QStringLiteral("eq")); + break; + case ALPM_DEP_MOD_GE: + depObj.insert(QStringLiteral("mod"), QStringLiteral("ge")); + break; + case ALPM_DEP_MOD_LE: + depObj.insert(QStringLiteral("mod"), QStringLiteral("le")); + break; + case ALPM_DEP_MOD_GT: + depObj.insert(QStringLiteral("mod"), QStringLiteral("gt")); + break; + case ALPM_DEP_MOD_LT: + depObj.insert(QStringLiteral("mod"), QStringLiteral("lt")); + break; } - put(obj, key, jsonArray); + jsonArray << depObj; } + put(obj, key, jsonArray); +} + +QDataStream &operator <<(QDataStream &out, const QList dependencies) +{ + out << static_cast(dependencies.count()); + for(const auto &dependency : dependencies) { + out << dependency.name << dependency.version << static_cast(dependency.mode); + } + return out; +} + +QDataStream &operator >>(QDataStream &in, QList &dependencies) +{ + quint32 size; + in >> size; + for(quint32 i = 0; i < size; ++i) { + QString name; + in >> name; + QString version; + in >> version; + quint32 mode; + in >> mode; + dependencies << Dependency(name, version, static_cast<_alpm_depmod_t>(mode)); + } + return in; +} + +inline QDataStream &operator <<(QDataStream &out, const DateTime dateTime) +{ + return out << static_cast(dateTime.totalTicks()); +} + +inline QDataStream &operator >>(QDataStream &in, DateTime &dateTime) +{ + quint64 ticks; + in >> ticks; + dateTime = DateTime(ticks); + return in; +} + +QDataStream &operator <<(QDataStream &out, const map fileMap) +{ + out << static_cast(fileMap.size()); + for(const auto &entry : fileMap) { + out << entry.first << entry.second; + } + return out; +} + +QDataStream &operator >>(QDataStream &in, map &fileMap) +{ + quint32 size; + in >> size; + for(quint32 i = 0; i < size; ++i) { + QString path; + in >> path; + QByteArray data; + in >> data; + fileMap.emplace(path, data); + } + return in; +} + +QDataStream &operator <<(QDataStream &out, const QJsonArray &jsonArray) +{ + QJsonDocument doc; + doc.setArray(jsonArray); + out << doc.toBinaryData(); + return out; +} + +QDataStream &operator >>(QDataStream &in, QJsonArray &jsonArray) +{ + QByteArray data; + in >> data; + QJsonDocument doc = QJsonDocument::fromBinaryData(data); + jsonArray = doc.array(); + return in; } } @@ -145,19 +293,19 @@ using namespace Utilities; */ QJsonObject Package::basicInfo(bool includeRepoAndName) const { - QJsonObject info; + QJsonObject info; if(includeRepoAndName) { if(source()) { put(info, QStringLiteral("repo"), source()->name()); } put(info, QStringLiteral("name"), name()); } - put(info, QStringLiteral("archs"), architectures()); - put(info, QStringLiteral("arch"), buildArchitecture()); put(info, QStringLiteral("ver"), version()); put(info, QStringLiteral("desc"), description()); - put(info, QStringLiteral("bdate"), buildDate()); put(info, QStringLiteral("flagdate"), outOfDate()); + put(info, QStringLiteral("arch"), buildArchitecture()); + put(info, QStringLiteral("bdate"), buildDate()); + put(info, QStringLiteral("archs"), architectures()); return info; } @@ -173,6 +321,8 @@ QJsonObject Package::fullInfo(bool includeRepoAndName) const } put(info, QStringLiteral("name"), name()); } + put(info, QStringLiteral("build_avail"), hasBuildRelatedMetaData()); + put(info, QStringLiteral("src_avail"), hasSourceRelatedMetaData()); put(info, QStringLiteral("archs"), architectures()); put(info, QStringLiteral("arch"), buildArchitecture()); put(info, QStringLiteral("ver"), version()); @@ -201,6 +351,75 @@ QJsonObject Package::fullInfo(bool includeRepoAndName) const return info; } +/*! + * \brief Writes the package contents to the specified data stream. + */ +void Package::writeToCacheStream(QDataStream &out) +{ + out << static_cast(m_origin); + // general info + out << m_hasGeneralInfo << m_name << m_version << m_description << m_upstreamUrl << m_licenses + << m_groups << m_dependencies << m_optionalDependencies << m_conflicts << m_provides + << m_replaces << m_requiredByComputed << m_requiredBy << m_optionalFor << m_hasInstallScript; + // build related meta data + out << m_hasBuildRelatedMetaData << m_fileName << m_files << m_buildDate << m_packer + << m_md5 << m_sha256 << m_buildArchitecture << m_packageSize << m_makeDependencies; + // installation related meta data + out << m_hasInstallRelatedMetaData << m_installDate << m_installedSize << m_backupFiles + << static_cast(m_validationMethods) << static_cast(m_installReason); + // source related meta data + out << m_hasSourceRelatedMetaData << m_baseName << m_architectures << m_id + << m_categoryId << m_votes << m_outOfDate + << m_maintainer << m_firstSubmitted << m_lastModified << m_tarUrl << m_sourceFiles; + // write specific header + auto headerStart = out.device()->pos(); + out.device()->seek(headerStart + 4); + writeSpecificCacheHeader(out); + auto headerEnd = out.device()->pos(); + out.device()->seek(headerStart); + out << static_cast(headerEnd - headerStart - 4); + out.device()->seek(headerEnd); + // no extended header + out << static_cast(0); +} + +void Package::restoreFromCacheStream(QDataStream &in) +{ + qint32 tmp; + // origin + in >> tmp; + m_origin = static_cast(tmp); // TODO: validate value + // general info + in >> m_hasGeneralInfo >> m_name >> m_version >> m_description >> m_upstreamUrl >> m_licenses + >> m_groups >> m_dependencies >> m_optionalDependencies >> m_conflicts >> m_provides + >> m_replaces >> m_requiredByComputed >> m_requiredBy >> m_optionalFor >> m_hasInstallScript; + // build related meta data + in >> m_hasBuildRelatedMetaData >> m_fileName >> m_files >> m_buildDate >> m_packer + >> m_md5 >> m_sha256 >> m_buildArchitecture >> m_packageSize >> m_makeDependencies; + // installation related meta data + in >> m_hasInstallRelatedMetaData >> m_installDate >> m_installedSize >> m_backupFiles; + in >> tmp; + m_validationMethods = static_cast(m_validationMethods); // TODO: validate value + in >> tmp; + m_installReason = static_cast(m_installReason); // TODO: validate value + // source related meta data + in >> m_hasSourceRelatedMetaData >> m_baseName >> m_architectures >> m_id + >> m_categoryId >> m_votes >> m_outOfDate + >> m_maintainer >> m_firstSubmitted >> m_lastModified >> m_tarUrl >> m_sourceFiles; + // specific header + quint32 headerSize; + in >> headerSize; + quint64 headerEnd = in.device()->pos() + headerSize; + restoreSpecificCacheHeader(in); + in.device()->seek(headerEnd); + if(in.status() == QDataStream::Ok) { + // skip extended header + in >> headerSize; + quint64 headerEnd = in.device()->pos() + headerSize; + in.device()->seek(headerEnd); + } +} + /*! * \brief The PackageVersion class helps parsing package versions. */ diff --git a/alpm/package.h b/alpm/package.h index f967fb8..cf505a4 100644 --- a/alpm/package.h +++ b/alpm/package.h @@ -88,6 +88,8 @@ inline Dependency::Dependency(const QString &name, const QString &version, _alpm mode(mode) {} +class Manager; + class Package { public: @@ -108,6 +110,8 @@ public: const QList &conflicts() const; const QList &provides() const; const QList &replaces() const; + bool isRequiredByComputed() const; + void computeRequiredBy(Manager &manager); const QStringList &requiredBy() const; const QStringList &optionalFor() const; bool hasInstallScript() const; @@ -136,9 +140,9 @@ public: bool hasSourceRelatedMetaData() const; const QString &baseName() const; const QStringList &architectures() const; - int id() const; - int categoryId() const; - int votes() const; + int32 id() const; + int32 categoryId() const; + int32 votes() const; ChronoUtilities::DateTime outOfDate() const; const QString &maintainer() const; ChronoUtilities::DateTime firstSubmitted() const; @@ -156,8 +160,14 @@ public: QJsonObject basicInfo(bool includeRepoAndName = false) const; QJsonObject fullInfo(bool includeRepoAndName = false) const; + // caching + void writeToCacheStream(QDataStream &out); + void restoreFromCacheStream(QDataStream &in); + protected: explicit Package(const QString &name, Repository *source); + virtual void writeSpecificCacheHeader(QDataStream &out); + virtual void restoreSpecificCacheHeader(QDataStream &in); PackageOrigin m_origin; Repository *m_source; @@ -175,6 +185,7 @@ protected: QList m_conflicts; QList m_provides; QList m_replaces; + bool m_requiredByComputed; QStringList m_requiredBy; QStringList m_optionalFor; bool m_hasInstallScript; @@ -203,9 +214,9 @@ protected: bool m_hasSourceRelatedMetaData; QString m_baseName; QStringList m_architectures; - int m_id; - int m_categoryId; - int m_votes; + int32 m_id; + int32 m_categoryId; + int32 m_votes; ChronoUtilities::DateTime m_outOfDate; QString m_maintainer; ChronoUtilities::DateTime m_firstSubmitted; @@ -327,6 +338,14 @@ inline const QList &Package::replaces() const return m_replaces; } +/*! + * \brief Returns whether required-by and optional-for have been computed. + */ +inline bool Package::isRequiredByComputed() const +{ + return m_requiredByComputed; +} + /*! * \brief Returns packages requiring this packages. */ @@ -520,7 +539,7 @@ inline const QStringList &Package::architectures() const /*! * \brief Returns the ID. */ -inline int Package::id() const +inline int32 Package::id() const { return m_id; } @@ -528,7 +547,7 @@ inline int Package::id() const /*! * \brief Returns the category ID. */ -inline int Package::categoryId() const +inline int32 Package::categoryId() const { return m_categoryId; } @@ -536,7 +555,7 @@ inline int Package::categoryId() const /*! * \brief Returns the votes of the package. */ -inline int Package::votes() const +inline int32 Package::votes() const { return m_votes; } diff --git a/alpm/repository.cpp b/alpm/repository.cpp index f331c1a..da1ce7d 100644 --- a/alpm/repository.cpp +++ b/alpm/repository.cpp @@ -1,9 +1,14 @@ #include "./repository.h" #include "./upgradelookup.h" #include "./utilities.h" +#include "./config.h" #include #include +#include +#include + +#include using namespace std; @@ -209,6 +214,43 @@ QList Repository::packageByFilter(std::function > &packageEntry) + { + if(m_forceUpdate || !packageEntry.second->isRequiredByComputed()) { + packageEntry.second->computeRequiredBy(m_manager); + } + } + +private: + Manager &m_manager; + bool m_forceUpdate; +}; + +/*! + * \endcond + */ + +/*! + * \brief Computes required-by and optional-for for all packages. + * \remarks Computition is done async. + */ +QFuture Repository::computeRequiredBy(Manager &manager, bool forceUpdate) +{ + return QtConcurrent::map(m_packages, ComputeRequired(manager, forceUpdate)); +} + QJsonArray Repository::upgradeSourcesJsonArray() const { QJsonArray sources; @@ -323,5 +365,110 @@ QJsonObject Repository::groupInfo() const return info; } -} // namespace PackageManagement +/*! + * \brief Writes the repository information to the specified cache stream. + */ +void Repository::writeToCacheStream(QDataStream &out) +{ + out << static_cast(0x7265706F); // magic number + out << static_cast(0x0); // version + out << static_cast(type()); + out << static_cast(m_packages.size()); + for(const auto &pkg : m_packages) { + pkg.second->writeToCacheStream(out); + } + // write specific header + auto headerStart = out.device()->pos(); + out.device()->seek(headerStart + 4); + writeSpecificCacheHeader(out); + auto headerEnd = out.device()->pos(); + out.device()->seek(headerStart); + out << static_cast(headerEnd - headerStart - 4); + out.device()->seek(headerEnd); + // no extended header + out << static_cast(0x0); +} +/*! + * \brief Restores the repository information from cache. + */ +void Repository::restoreFromCacheStream(QDataStream &in) +{ + quint32 magic; + in >> magic; + if(magic == 0x7265706F) { + // read version + quint32 version; + in >> version; + // read type + quint32 denotedType; + in >> denotedType; + if(denotedType == static_cast(type())) { + // read packages + quint32 packageCount; + in >> packageCount; + bool good = true; + for(quint32 i = 0; i < packageCount && good && in.status() == QDataStream::Ok; ++i) { + if(unique_ptr package = emptyPackage()) { + package->restoreFromCacheStream(in); + if(!package->name().isEmpty()) { + m_packages[package->name()] = move(package); + } else { + good = false; + } + } else { + good = false; + } + } + if(in.status() == QDataStream::Ok) { + // specific header + quint32 headerSize; + in >> headerSize; + quint64 headerEnd = in.device()->pos() + headerSize; + restoreSpecificCacheHeader(in); + in.device()->seek(headerEnd); + if(in.status() == QDataStream::Ok) { + // skip extended header + in >> headerSize; + quint64 headerEnd = in.device()->pos() + headerSize; + in.device()->seek(headerEnd); + } else { + cerr << shchar << "Failed to restore cache for repository \"" << m_name.toLocal8Bit().data() << "\": unable to parse specific cache header" << endl; + } + } else { + cerr << shchar << "Failed to restore cache for repository \"" << m_name.toLocal8Bit().data() << "\": unable to parse packages" << endl; + } + } else { + cerr << shchar << "Failed to restore cache for repository \"" << m_name.toLocal8Bit().data() << "\": denoted type does not match expected type" << endl; + } + } else { + cerr << shchar << "Failed to restore cache for repository \"" << m_name.toLocal8Bit().data() << "\": bad magic number" << endl; + } +} + +/*! + * \brief Writes the repo-type-specific cache header. + */ +void Repository::writeSpecificCacheHeader(QDataStream &out) +{ + Q_UNUSED(out) +} + +/*! + * \brief Returns an new, empty package. + * \remarks Used to when restoring packages from cache. + */ +unique_ptr Repository::emptyPackage() +{ + return unique_ptr(); +} + +/*! + * \brief Restores the repo-type-specific cache header. + */ +void Repository::restoreSpecificCacheHeader(QDataStream &in) +{ + Q_UNUSED(in) +} + +} // namespace PackageManagement diff --git a/alpm/repository.h b/alpm/repository.h index 6869aa3..9a9e7ab 100644 --- a/alpm/repository.h +++ b/alpm/repository.h @@ -5,6 +5,7 @@ #include "./group.h" #include +#include #include #include @@ -104,7 +105,8 @@ enum class PackageDetail Basics, /*! Basic information about the package such as knowing it exists, its name, version and description. */ Dependencies, /*! The runtime dependencies of the package. */ SourceInfo, /*! The source info such as make dependencies of the package. */ - PackageInfo /*! Information related to a specific package (pkg-file). */ + PackageInfo, /*! Information related to a specific package (pkg-file). */ + AllAvailable /*! All available package details. */ }; /*! @@ -153,6 +155,7 @@ public: Package *packageProviding(const Dependency &dependency); QList packagesProviding(const Dependency &dependency) const; QList packageByFilter(std::function pred); + QFuture computeRequiredBy(Manager &manager, bool forceUpdate = false); // upgrade lookup const QList &upgradeSources() const; @@ -172,6 +175,14 @@ public: QJsonObject basicInfo() const; QJsonObject groupInfo() const; + // caching + bool isCachingUseful() const; + void writeToCacheStream(QDataStream &out); + void restoreFromCacheStream(QDataStream &in); + virtual void writeSpecificCacheHeader(QDataStream &out); + virtual std::unique_ptr emptyPackage(); + virtual void restoreSpecificCacheHeader(QDataStream &in); + protected: explicit Repository(const QString &name, QObject *parent = nullptr); @@ -331,6 +342,20 @@ inline void Repository::setPackagesDirectory(const QString &dir) m_pkgDir = dir; } +/*! + * \brief Returns whether caching this repository is useful. + */ +inline bool Repository::isCachingUseful() const +{ + switch(requestsRequired(PackageDetail::AllAvailable)) { + case PackageDetailAvailability::Request: + case PackageDetailAvailability::FullRequest: + return true; + default: + return false; + } +} + } // namespace PackageManagement #endif // PACKAGEMANAGEMENT_PACKAGESOURCE_H diff --git a/alpm/upgradelookup.cpp b/alpm/upgradelookup.cpp index 778d72b..282b932 100644 --- a/alpm/upgradelookup.cpp +++ b/alpm/upgradelookup.cpp @@ -103,7 +103,6 @@ void UpgradeLookupProcess::checkUpgrades() /*! * \class UpgradeLookup * \brief The UpgradeLookup class performs an async upgrade lookup for using multiple upgrade sources. - * \remarks The object deletes itself after the lookup is done. */ /*! @@ -123,6 +122,7 @@ UpgradeLookup::UpgradeLookup(QObject *parent) : /*! * \class UpgradeLookupJson + * \remarks The object deletes itself after the lookup is done (hence it must be created using new). * \brief The UpgradeLookupJson class performs an async upgrade lookup for using multiple upgrade sources. * * The request and the results are in JSON. @@ -367,8 +367,7 @@ void UpgradeLookupCli::printResults() Utilities::printValues(cout, "Orphaned packages", m_orphanedPackagesArray); } } - // lookup done, delete this helper object - deleteLater(); + emit finished(); } } // namespace PackageManagement diff --git a/alpm/upgradelookup.h b/alpm/upgradelookup.h index 7ce4354..ea91b02 100644 --- a/alpm/upgradelookup.h +++ b/alpm/upgradelookup.h @@ -160,6 +160,9 @@ public: explicit UpgradeLookupCli(const Manager &manager, const std::string &repo, QObject *parent = nullptr); const Repository *toCheck() const; +signals: + void finished(); + private slots: void processFinished(); diff --git a/main.cpp b/main.cpp index 23f05ed..e2d4129 100644 --- a/main.cpp +++ b/main.cpp @@ -50,12 +50,13 @@ int main(int argc, char *argv[]) }) != parser.mainArguments().cend()) { // create app QCoreApplication application(argc, argv); - // setup ALPM + // setup manager Manager manager(config); manager.applyPacmanConfig(); manager.applyRepoIndexConfig(); cerr << shchar << "Loading databases ..." << endl; - manager.initAlpmDataBases(); + manager.initAlpmDataBases(configArgs.serverArg.isPresent()); + manager.restoreCache(); if(configArgs.serverArg.isPresent()) { // setup the server Server server(manager, manager.config()); @@ -71,7 +72,8 @@ int main(int argc, char *argv[]) configArgs.targetNameArg.values().front(), configArgs.targetFormatArg.isPresent() ? configArgs.targetFormatArg.values().front() : string("zip")); } else if(configArgs.upgradeLookupArg.isPresent()) { - QObject::connect(new UpgradeLookupCli(manager, configArgs.upgradeLookupArg.values().front()), &QObject::destroyed, &application, &QCoreApplication::quit); + UpgradeLookupCli upgradeLookup(manager, configArgs.upgradeLookupArg.values().front()); + QObject::connect(&upgradeLookup, &UpgradeLookupCli::finished, &application, &QCoreApplication::quit); return application.exec(); } } else if(!configArgs.helpArg.isPresent()) { diff --git a/network/connection.cpp b/network/connection.cpp index 0cda69c..9d133e3 100644 --- a/network/connection.cpp +++ b/network/connection.cpp @@ -7,13 +7,14 @@ #include #include #include +#include namespace RepoIndex { Connection::Connection(const Manager &alpmManager, QWebSocket *socket, QObject *parent) : QObject(parent), m_socket(socket), - m_alpmManager(alpmManager), + m_manager(alpmManager), m_repoInfoUpdatesRequested(false), m_groupInfoUpdatesRequested(false) { @@ -28,11 +29,14 @@ void Connection::sendJson(const QJsonObject &obj) m_socket->sendTextMessage(QJsonDocument(obj).toJson(QJsonDocument::Compact)); } -void Connection::sendError(const QString &msg) +void Connection::sendError(const QString &msg, const QJsonValue &id) { QJsonObject response; response.insert(QStringLiteral("class"), QStringLiteral("error")); response.insert(QStringLiteral("msg"), msg); + if(!id.isNull() && !id.isUndefined()) { + response.insert(QStringLiteral("id"), id); + } sendJson(response); } @@ -42,7 +46,7 @@ void Connection::sendResult(const QJsonValue &what, const QJsonValue &id, const response.insert(QStringLiteral("class"), QStringLiteral("results")); response.insert(QStringLiteral("what"), what); response.insert(QStringLiteral("value"), value); - if(!id.isNull()) { + if(!id.isNull() && !id.isUndefined()) { response.insert("id", id); } sendJson(response); @@ -54,7 +58,7 @@ void Connection::sendResults(const QJsonValue &what, const QJsonValue &id, const response.insert(QStringLiteral("class"), QStringLiteral("results")); response.insert(QStringLiteral("what"), what); response.insert(QStringLiteral("values"), values); - if(!id.isNull()) { + if(!id.isNull() && !id.isUndefined()) { response.insert("id", id); } sendJson(response); @@ -66,20 +70,35 @@ void Connection::handleQuery(const QJsonObject &obj) const auto id = obj.value(QStringLiteral("id")); if(what == QLatin1String("basicrepoinfo")) { m_repoInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_repoInfoUpdatesRequested); - sendResults(what, id, m_alpmManager.basicRepoInfo()); + sendResults(what, id, m_manager.basicRepoInfo()); } else if(what == QLatin1String("basicpkginfo")) { - sendResults(what, id, m_alpmManager.packageInfo(obj.value("sel").toObject(), false)); + sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), false)); } else if(what == QLatin1String("fullpkginfo")) { - sendResults(what, id, m_alpmManager.packageInfo(obj.value("sel").toObject(), true)); + sendResults(what, id, m_manager.packageInfo(obj.value("sel").toObject(), true)); } else if(what == QLatin1String("groupinfo")) { m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested); - sendResults(what, id, m_alpmManager.groupInfo()); + sendResults(what, id, m_manager.groupInfo()); } else if(what == QLatin1String("checkforupdates")) { - connect(new UpgradeLookupJson(m_alpmManager, obj), &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult); + connect(new UpgradeLookupJson(m_manager, obj), &UpgradeLookupJson::resultsAvailable, this, &Connection::sendResult); } else if(what == QLatin1String("ping")) { sendResult(what, id, QStringLiteral("pong")); } else { - sendResult(what, id, QStringLiteral("unknownquery")); + sendError(QStringLiteral("unknown query"), id); + } +} + +void Connection::handleCmd(const QJsonObject &obj) +{ + const auto what = obj.value(QStringLiteral("what")).toString(); + const auto id = obj.value(QStringLiteral("id")); + if(what == QLatin1String("stop")) { + if(m_socket->peerAddress().isLoopback()) { + QCoreApplication::quit(); + } else { + sendError(QStringLiteral("rejected"), id); + } + } else { + sendError(QStringLiteral("unknown command"), id); } } @@ -88,11 +107,14 @@ void Connection::processTextMessage(const QString &message) QJsonParseError error; QByteArray jsonData; jsonData.append(message); - QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); + const QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); if(error.error == QJsonParseError::NoError) { - QString msgClass = doc.object().value(QStringLiteral("class")).toString(); + const QJsonObject obj = doc.object(); + const QString msgClass = obj.value(QStringLiteral("class")).toString(); if(msgClass == QLatin1String("query")) { - handleQuery(doc.object()); + handleQuery(obj); + } else if(msgClass == QLatin1String("cmd")) { + handleCmd(obj); } else { sendError(QStringLiteral("no message class specified")); } diff --git a/network/connection.h b/network/connection.h index 17063d7..52f182d 100644 --- a/network/connection.h +++ b/network/connection.h @@ -2,6 +2,7 @@ #define CONNECTION_H #include +#include #include @@ -27,11 +28,12 @@ private slots: private: void sendJson(const QJsonObject &obj); - void sendError(const QString &msg); + void sendError(const QString &msg, const QJsonValue &id = QJsonValue()); void handleQuery(const QJsonObject &obj); + void handleCmd(const QJsonObject &obj); QWebSocket *m_socket; - const RepoIndex::Manager &m_alpmManager; + const RepoIndex::Manager &m_manager; bool m_repoInfoUpdatesRequested; bool m_groupInfoUpdatesRequested; diff --git a/network/server.cpp b/network/server.cpp index 712e930..c980a8b 100644 --- a/network/server.cpp +++ b/network/server.cpp @@ -52,7 +52,12 @@ Server::Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &c } // start listening if(m_server->listen(config.websocketServerListeningAddr(), config.websocketServerListeningPort())) { - cerr << shchar << m_server->serverName().toLocal8Bit().data() << " is listening on port " << m_server->serverPort() << endl; + if(useShSyntax) { + cout << "export REPOINDEX_SERVER_NAME='" << m_server->serverName().toLocal8Bit().data() << '\'' << endl; + cout << "export REPOINDEX_SERVER_PORT='" << m_server->serverPort() << '\'' << endl; + } else { + cout << m_server->serverName().toLocal8Bit().data() << " is listening on port " << m_server->serverPort() << endl; + } connect(m_server, &QWebSocketServer::newConnection, this, &Server::incomingConnection); connect(m_server, &QWebSocketServer::closed, this, &Server::closed); } else { @@ -60,8 +65,6 @@ Server::Server(const RepoIndex::Manager &alpmManager, const RepoIndex::Config &c } } - - Server::~Server() { m_server->close(); diff --git a/network/userrepository.cpp b/network/userrepository.cpp index 0a6e951..7e70e58 100644 --- a/network/userrepository.cpp +++ b/network/userrepository.cpp @@ -115,8 +115,9 @@ PackageDetailAvailability UserRepository::requestsRequired(PackageDetail package return PackageDetailAvailability::Request; case PackageDetail::Dependencies: case PackageDetail::SourceInfo: + case PackageDetail::AllAvailable: return PackageDetailAvailability::FullRequest; - case PackageDetail::PackageInfo: + default: return PackageDetailAvailability::Never; } } @@ -163,10 +164,21 @@ AurFullPackageReply *UserRepository::requestFullPackageInfo(const QStringList &p } } } catch(const out_of_range &) { - replies << m_networkAccessManager.get(QNetworkRequest(m_aurSnapshotPath.arg(packageName))); + if(forceUpdate) { + replies << m_networkAccessManager.get(QNetworkRequest(m_aurSnapshotPath.arg(packageName))); + } } } - return new AurFullPackageReply(replies, const_cast(this)); + if(replies.isEmpty()) { + return nullptr; + } else { + return new AurFullPackageReply(replies, const_cast(this)); + } +} + +unique_ptr UserRepository::emptyPackage() +{ + return make_unique(this); } } // namespace Alpm diff --git a/network/userrepository.h b/network/userrepository.h index 7005ef6..c5acaa3 100644 --- a/network/userrepository.h +++ b/network/userrepository.h @@ -69,6 +69,9 @@ public: AurPackageReply *requestPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; AurFullPackageReply *requestFullPackageInfo(const QStringList &packageNames, bool forceUpdate = false) const; +protected: + std::unique_ptr emptyPackage(); + private: QNetworkAccessManager &m_networkAccessManager; static QUrl m_aurRpcUrl; diff --git a/web/index.html b/web/index.html index ba64b6f..fd655ac 100644 --- a/web/index.html +++ b/web/index.html @@ -47,6 +47,9 @@ + diff --git a/web/js/alpm.js b/web/js/alpm.js index 2201bf1..9ae2db2 100644 --- a/web/js/alpm.js +++ b/web/js/alpm.js @@ -226,6 +226,14 @@ this.scheduleRequest(repoindex.RequestType.GroupInfo, {updates: "true"}, callback); }; + this.stopServer = function() { + if(this.isOpen()) { + this.socket.send(JSON.stringify({class: "cmd", what: "stop"})); + } else { + window.alert("Not connected to a server."); + } + }; + this.checkForUpdates = function(dbName, syncdbNames, callback) { var params = { db: dbName diff --git a/web/js/repomanagement.js b/web/js/repomanagement.js index d642583..68e0927 100644 --- a/web/js/repomanagement.js +++ b/web/js/repomanagement.js @@ -253,7 +253,7 @@ if(updates[i1].entries[i2].pkg) { newEntry.applyBasicInfo(updates[i1].entries[i2].pkg, true); if(updates[i1].entries[i2].curVer) { - newEntry.info.ver = updates[i1].entries[i2].prevVersion + " → " + (newEntry.info.ver ? newEntry.info.ver : "?"); + newEntry.info.ver = updates[i1].entries[i2].curVer + " → " + (newEntry.info.ver ? newEntry.info.ver : "?"); } // find associated repo entry if((newEntry.repoEntry = repoMgr.entryByName(newEntry.info.repo))) {