caching is now implemented, overall improvements

This commit is contained in:
Martchus 2015-09-11 21:59:47 +02:00
parent 98e565b8c3
commit 6d06663791
23 changed files with 690 additions and 106 deletions

View File

@ -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;

View File

@ -4,6 +4,8 @@
#include <QJsonObject>
#include <iostream>
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));

View File

@ -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

View File

@ -11,6 +11,7 @@ class AurPackage : public Package
{
public:
AurPackage(const QJsonValue &aurJsonValue, UserRepository *source);
AurPackage(UserRepository *source);
};
} // namespace PackageManagement

View File

@ -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();

View File

@ -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;

View File

@ -13,6 +13,9 @@
#include <QList>
#include <QSysInfo>
#include <QMutexLocker>
#include <QStringBuilder>
#include <QFile>
#include <QDataStream>
#include <fstream>
#include <iostream>
@ -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<PackageLoader *> loaders;
loaders.reserve(m_syncDbs.size() + 1);
loaders << localDatabase()->init();
for(auto &syncDbEntry : m_syncDbs) {
loaders << syncDbEntry.second->init();
// call the init method
{
QList<PackageLoader *> 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<QFuture<void> > 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 {

View File

@ -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<QString, std::unique_ptr<AlpmDatabase> > &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();
}

View File

@ -2,11 +2,16 @@
#include "./alpmdatabase.h"
#include "./utilities.h"
#include "./repository.h"
#include "./manager.h"
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QJsonDocument>
#include <QVariant>
#include <QDataStream>
#include <iostream>
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<Dependency> &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<Dependency> dependencies)
{
out << static_cast<quint32>(dependencies.count());
for(const auto &dependency : dependencies) {
out << dependency.name << dependency.version << static_cast<quint32>(dependency.mode);
}
return out;
}
QDataStream &operator >>(QDataStream &in, QList<Dependency> &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<quint64>(dateTime.totalTicks());
}
inline QDataStream &operator >>(QDataStream &in, DateTime &dateTime)
{
quint64 ticks;
in >> ticks;
dateTime = DateTime(ticks);
return in;
}
QDataStream &operator <<(QDataStream &out, const map<QString, QByteArray> fileMap)
{
out << static_cast<quint32>(fileMap.size());
for(const auto &entry : fileMap) {
out << entry.first << entry.second;
}
return out;
}
QDataStream &operator >>(QDataStream &in, map<QString, QByteArray> &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<qint32>(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<qint32>(m_validationMethods) << static_cast<qint32>(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<quint32>(headerEnd - headerStart - 4);
out.device()->seek(headerEnd);
// no extended header
out << static_cast<quint32>(0);
}
void Package::restoreFromCacheStream(QDataStream &in)
{
qint32 tmp;
// origin
in >> tmp;
m_origin = static_cast<PackageOrigin>(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<alpm_pkgvalidation_t>(m_validationMethods); // TODO: validate value
in >> tmp;
m_installReason = static_cast<alpm_pkgreason_t>(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.
*/

View File

@ -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<Dependency> &conflicts() const;
const QList<Dependency> &provides() const;
const QList<Dependency> &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<Dependency> m_conflicts;
QList<Dependency> m_provides;
QList<Dependency> 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<Dependency> &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;
}

View File

@ -1,9 +1,14 @@
#include "./repository.h"
#include "./upgradelookup.h"
#include "./utilities.h"
#include "./config.h"
#include <QJsonObject>
#include <QNetworkReply>
#include <QDataStream>
#include <QtConcurrent>
#include <iostream>
using namespace std;
@ -209,6 +214,43 @@ QList<Package *> Repository::packageByFilter(std::function<bool (const Package *
return packages;
}
/*!
* \cond
*/
class ComputeRequired
{
public:
ComputeRequired(Manager &manager, bool forceUpdate) :
m_manager(manager),
m_forceUpdate(forceUpdate)
{}
void operator () (const pair<const QString, unique_ptr<Package> > &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<void> 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<quint32>(0x7265706F); // magic number
out << static_cast<quint32>(0x0); // version
out << static_cast<quint32>(type());
out << static_cast<quint32>(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<quint32>(headerEnd - headerStart - 4);
out.device()->seek(headerEnd);
// no extended header
out << static_cast<quint32>(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<quint32>(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> 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<Package> Repository::emptyPackage()
{
return unique_ptr<Package>();
}
/*!
* \brief Restores the repo-type-specific cache header.
*/
void Repository::restoreSpecificCacheHeader(QDataStream &in)
{
Q_UNUSED(in)
}
} // namespace PackageManagement

View File

@ -5,6 +5,7 @@
#include "./group.h"
#include <QObject>
#include <QFuture>
#include <memory>
#include <functional>
@ -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<const Package *> packagesProviding(const Dependency &dependency) const;
QList<Package *> packageByFilter(std::function<bool (const Package *)> pred);
QFuture<void> computeRequiredBy(Manager &manager, bool forceUpdate = false);
// upgrade lookup
const QList<const Repository *> &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<Package> 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

View File

@ -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

View File

@ -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();

View File

@ -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()) {

View File

@ -7,13 +7,14 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QWebSocket>
#include <QCoreApplication>
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"));
}

View File

@ -2,6 +2,7 @@
#define CONNECTION_H
#include <QObject>
#include <QJsonValue>
#include <map>
@ -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;

View File

@ -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();

View File

@ -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<UserRepository *>(this));
if(replies.isEmpty()) {
return nullptr;
} else {
return new AurFullPackageReply(replies, const_cast<UserRepository *>(this));
}
}
unique_ptr<Package> UserRepository::emptyPackage()
{
return make_unique<AurPackage>(this);
}
} // namespace Alpm

View File

@ -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<Package> emptyPackage();
private:
QNetworkAccessManager &m_networkAccessManager;
static QUrl m_aurRpcUrl;

View File

@ -47,6 +47,9 @@
<li id="nav_settings"><a id="link_settings" href="#" aria-label="Settings" data-toggle="popover" data-placement="bottom" title="Settings" data-content="TODO"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a></li>
<li id="nav_about"><a id="link_settings" href="#" onclick="$('#dlg_about').modal('show'); return false;"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></a></li>
</ul>
<form class="navbar-form navbar-right">
<button class="btn btn-danger" onclick="repoindex.alpmClient.stopServer();">Stop server</button>
</form>
<form class="navbar-form navbar-right">
<button id="nav_connect" class="btn btn-danger" onclick="repoindex.alpmClient.init();"><span class="glyphicon glyphicon glyphicon-refresh" aria-hidden="true" id="connection_glyphicon"></span> <span id="connection_status">Disconnected</span></button>
</form>

View File

@ -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

View File

@ -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))) {