repoindex/lib/alpm/alpmdatabase.cpp

333 lines
13 KiB
C++
Raw Normal View History

#include "./alpmdatabase.h"
#include "./upgradelookup.h"
#include "./alpmpackage.h"
#include "./utilities.h"
#include "./config.h"
#include "../network/networkaccessmanager.h"
2015-09-04 14:37:01 +02:00
2016-02-14 23:48:43 +01:00
#include <KTar>
#include <KArchiveDirectory>
2015-09-04 14:37:01 +02:00
#include <QList>
#include <QJsonObject>
#include <QStringBuilder>
#include <QtConcurrent>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
2015-09-04 14:37:01 +02:00
2017-03-25 19:49:41 +01:00
#include <memory>
2016-02-14 23:48:43 +01:00
#include <iostream>
2015-09-04 14:37:01 +02:00
using namespace std;
2019-06-10 22:51:09 +02:00
using namespace CppUtilities;
2015-09-04 14:37:01 +02:00
namespace RepoIndex {
2015-09-04 14:37:01 +02:00
using namespace Utilities;
/*!
2016-01-18 20:34:29 +01:00
* \class AlpmDatabase
* \brief The AlpmDatabase class wraps an ALPM data base struct and holds additional meta information.
2015-09-04 14:37:01 +02:00
*
2016-01-18 20:34:29 +01:00
* All packages returned by the AlpmDatabase class are AlpmPackage instances.
2015-09-04 14:37:01 +02:00
*/
class LoadPackage
{
public:
2016-02-27 21:00:58 +01:00
LoadPackage(AlpmDatabase *database, PackageOrigin origin, DateTime descriptionsLastModified) :
2016-02-14 23:48:43 +01:00
m_db(database),
2016-02-27 21:00:58 +01:00
m_origin(origin),
m_descriptionsLastModified(descriptionsLastModified)
{}
2016-02-14 23:48:43 +01:00
void operator()(const QPair<QString, QList<QByteArray> > &description)
{
2016-02-27 21:00:58 +01:00
m_db->addPackageFromDescription(description.first, description.second, m_origin, m_descriptionsLastModified);
}
private:
2016-02-14 23:48:43 +01:00
AlpmDatabase *const m_db;
const PackageOrigin m_origin;
2016-02-27 21:00:58 +01:00
const DateTime m_descriptionsLastModified;
};
2019-06-10 22:51:09 +02:00
DatabaseError AlpmDatabase::loadDescriptions(QList<QPair<QString, QList<QByteArray> > > &descriptions, CppUtilities::DateTime *lastModified)
{
2016-03-09 15:56:06 +01:00
if(!databasePath().isEmpty()) {
QFileInfo pathInfo(databasePath());
if(pathInfo.isDir()) {
if(lastModified) {
// just use current date here since this is usually the local db
*lastModified = DateTime::gmtNow();
2016-02-14 23:48:43 +01:00
}
2016-03-09 15:56:06 +01:00
static const QStringList relevantFiles = QStringList() << QStringLiteral("desc") << QStringLiteral("files");
QDir dbDir(databasePath());
QStringList pkgDirNames = dbDir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
2016-02-14 23:48:43 +01:00
descriptions.reserve(pkgDirNames.size());
for(QString &pkgDirName : pkgDirNames) {
2016-03-09 15:56:06 +01:00
if(dbDir.cd(pkgDirName)) {
Utilities::stripVersion(pkgDirName);
const QStringList descFileNames = dbDir.entryList(relevantFiles, QDir::Files | QDir::Readable | QDir::NoDotAndDotDot);
QList<QByteArray> descData;
descData.reserve(descFileNames.size());
for(const QString &descFileName : descFileNames) {
QFile descFile(dbDir.absoluteFilePath(descFileName));
if(descFile.open(QFile::ReadOnly)) {
descData << descFile.readAll();
} else {
return DatabaseError::UnableToOpenDescFile;
}
}
if(!descData.isEmpty()) {
descriptions << qMakePair(pkgDirName, descData);
}
dbDir.cdUp();
} else {
return DatabaseError::UnableToEnterDirectory;
}
}
} else if(pathInfo.isFile()) {
if(lastModified) {
2019-06-10 15:39:56 +02:00
*lastModified = DateTime::fromTimeStampGmt(pathInfo.lastModified().toUTC().toSecsSinceEpoch());
2016-03-09 15:56:06 +01:00
}
KTar tar(databasePath());
const KArchiveDirectory *dbDir;
if(tar.open(QIODevice::ReadOnly) && (dbDir = tar.directory())) {
QStringList pkgDirNames = dbDir->entries();
descriptions.reserve(pkgDirNames.size());
for(QString &pkgDirName : pkgDirNames) {
if(const auto *pkgEntry = dbDir->entry(pkgDirName)) {
if(pkgEntry->isDirectory()) {
Utilities::stripVersion(pkgDirName);
const auto *pkgDir = static_cast<const KArchiveDirectory *>(pkgEntry);
const QStringList descFileNames = pkgDir->entries();
QList<QByteArray> descData;
descData.reserve(descFileNames.size());
for(const QString &descFileName : descFileNames) {
if(const auto *descEntry = pkgDir->entry(descFileName)) {
if(descEntry->isFile()) {
descData << static_cast<const KArchiveFile *>(descEntry)->data();
} else {
// there shouldn't be any subdirs anyways
}
2016-02-14 23:48:43 +01:00
}
}
2016-03-09 15:56:06 +01:00
if(!descData.isEmpty()) {
descriptions << qMakePair(pkgDirName, descData);
}
} else {
// there shouldn't be any files anyways
2016-02-14 23:48:43 +01:00
}
}
}
2016-03-09 15:56:06 +01:00
} else {
return DatabaseError::UnableToOpenArchive;
2016-02-14 23:48:43 +01:00
}
} else {
2016-03-09 15:56:06 +01:00
return DatabaseError::NotFound;
2016-02-14 23:48:43 +01:00
}
2016-03-09 15:56:06 +01:00
return DatabaseError::NoError;
2016-02-14 23:48:43 +01:00
} else {
2016-02-25 22:53:33 +01:00
return DatabaseError::NotFound;
}
2016-02-14 23:48:43 +01:00
}
2016-02-25 22:53:33 +01:00
AlpmPackageLoader::AlpmPackageLoader(AlpmDatabase *repository, PackageOrigin origin) :
m_db(repository)
2016-02-14 23:48:43 +01:00
{
2016-02-27 21:00:58 +01:00
if((m_error = repository->loadDescriptions(m_descriptions, &m_descriptionsLastModified)) == DatabaseError::NoError) {
m_future = QtConcurrent::map(m_descriptions, LoadPackage(repository, origin, m_descriptionsLastModified));
2016-02-25 22:53:33 +01:00
}
}
2015-09-04 14:37:01 +02:00
/*!
* \brief Creates a new instance wrapping the specified database struct.
*/
2019-03-14 18:15:03 +01:00
AlpmDatabase::AlpmDatabase(const QString &name, const QString &dbPath, RepositoryUsage usage, SignatureLevel sigLevel, std::uint32_t index, QObject *parent) :
2016-02-14 23:48:43 +01:00
Repository(name, index, parent),
m_dbPath(dbPath)
{
m_usage = usage;
m_sigLevel = sigLevel;
}
2016-02-25 22:53:33 +01:00
AlpmPackageLoader *AlpmDatabase::internalInit()
2015-09-04 14:37:01 +02:00
{
2016-02-14 23:48:43 +01:00
// set description, determine origin
PackageOrigin origin;
2015-09-04 14:37:01 +02:00
if(m_name.compare(QLatin1String("local"), Qt::CaseInsensitive) == 0) {
m_description = QStringLiteral("The local database");
2016-02-14 23:48:43 +01:00
origin = PackageOrigin::LocalDb;
2015-09-04 14:37:01 +02:00
} else {
2016-02-14 23:48:43 +01:00
if((m_usage & RepositoryUsage::Sync) || (m_usage & RepositoryUsage::Install) || (m_usage & RepositoryUsage::Upgrade)) {
2015-09-04 14:37:01 +02:00
m_description = QStringLiteral("Sync database »%1«").arg(m_name);
} else {
m_description = QStringLiteral("Database »%1«").arg(m_name);
}
2016-02-14 23:48:43 +01:00
origin = PackageOrigin::SyncDb;
2015-09-04 14:37:01 +02:00
}
2016-02-25 22:53:33 +01:00
// initialization of packages is done concurrently via AlpmPackageLoader
2016-02-14 23:48:43 +01:00
return new AlpmPackageLoader(this, origin);
2016-02-25 22:53:33 +01:00
// without concurrency
2016-02-14 23:48:43 +01:00
//QList<QPair<QString, QList<QByteArray> > > descriptions;
//loadDescriptions(descriptions);
//for(const auto &description : descriptions) {
// addPackageFromDescription(description.first, description.second, origin);
//}
2016-02-25 22:53:33 +01:00
//emit initialized();
2016-02-14 23:48:43 +01:00
//return nullptr;
}
2015-09-04 14:37:01 +02:00
RepositoryType AlpmDatabase::type() const
2015-09-04 14:37:01 +02:00
{
return RepositoryType::AlpmDatabase;
2015-09-04 14:37:01 +02:00
}
PackageDetailAvailability AlpmDatabase::requestsRequired(PackageDetail packageDetail) const
2015-09-04 14:37:01 +02:00
{
switch(packageDetail) {
case PackageDetail::Basics:
case PackageDetail::Dependencies:
case PackageDetail::PackageInfo:
case PackageDetail::AllAvailable:
return PackageDetailAvailability::Immediately;
default:
return PackageDetailAvailability::Never;
}
2015-09-04 14:37:01 +02:00
}
QNetworkRequest AlpmDatabase::regularDatabaseRequest()
{
2017-01-29 23:10:21 +01:00
return QNetworkRequest(m_filesRedirs.isEmpty()
? (QUrl(serverUrls().front() % QChar('/') % name() % QStringLiteral(".db")))
: m_regularRedirs.back());
}
QNetworkRequest AlpmDatabase::filesDatabaseRequest()
{
2017-01-29 23:10:21 +01:00
return QNetworkRequest(m_filesRedirs.isEmpty()
? (QUrl(serverUrls().front() % QChar('/') % name() % QStringLiteral(".files")))
: m_filesRedirs.back());
}
/*!
* \brief Downloads the database from the server.
* \param targetDir Specifies the directory to store the downloaded database file. Shall not include the filename.
* \remarks
* - The download is performed asynchronously - this method returns immediately.
* - After successfull download the database path is update to the path of the new file and the
* repository is reinitiated.
* - Does nothing if there is not at least one server URL available.
* - Status messages are printed via cerr.
*/
2016-02-27 21:00:58 +01:00
bool AlpmDatabase::downloadDatabase(const QString &targetDir, bool filesDatabase)
{
2016-02-25 22:53:33 +01:00
QWriteLocker locker(lock());
if(serverUrls().isEmpty()) {
2016-02-27 21:00:58 +01:00
return false; // no server URLs available
}
2016-02-27 21:00:58 +01:00
addBusyFlag();
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());
reply->setProperty("filesDatabase", filesDatabase);
m_downloadTargetDir = targetDir.isEmpty() ? QString(QChar('.')) : targetDir;
connect(reply, &QNetworkReply::finished, this, &AlpmDatabase::databaseDownloadFinished);
2016-02-27 21:00:58 +01:00
return true;
}
/*!
* \brief Refreshes the database by downloading it from the server
* or just reinitializing it if there is not at least one server URL available.
* \param targetDir Specifies the directory to store the downloaded database file. Shall not include the filename.
* Ignored when downloading is not possible.
* \remarks Effectively updates the database file of sync databases and just refreshes
* local databases.
*/
void AlpmDatabase::refresh(const QString &targetDir)
{
2016-02-27 21:00:58 +01:00
if(!downloadDatabase(targetDir, true)) {
init();
}
}
2016-02-12 01:05:08 +01:00
std::unique_ptr<Package> AlpmDatabase::emptyPackage()
{
return make_unique<AlpmPackage>(this);
}
/*!
* \brief Internally called to handle finishing of database download.
* \remarks Connected in downloadDatabase().
*/
void AlpmDatabase::databaseDownloadFinished()
{
auto *reply = static_cast<QNetworkReply *>(sender());
2016-02-25 22:53:33 +01:00
reply->deleteLater();
bool filesDatabase = reply->property("filesDatabase").toBool();
2017-01-29 23:10:21 +01:00
const QString redirTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString();
2016-02-25 22:53:33 +01:00
QReadLocker locker(lock());
2017-01-29 23:10:21 +01:00
switch(reply->error()) {
case QNetworkReply::NoError: {
if(!redirTarget.isEmpty()) {
if((!filesDatabase && m_regularRedirs.contains(redirTarget)) || (filesDatabase && m_filesRedirs.contains(redirTarget))) {
cerr << "Redirection-loop for database file [" << name().toLocal8Bit().data() << "]" << endl;
removeBusyFlag();
return;
}
(filesDatabase ? m_filesRedirs : m_regularRedirs) << redirTarget;
cerr << "Redirection target for database file [" << name().toLocal8Bit().data() << "] available: " << redirTarget.toLocal8Bit().data() << endl;
locker.unlock();
downloadDatabase(m_downloadTargetDir, filesDatabase);
}
m_filesRedirs.clear();
2016-02-25 22:53:33 +01:00
QString newDatabasePath;
cerr << "Downloaded database file for [" << name().toLocal8Bit().data() << "] successfully." << endl;
2016-02-25 22:53:33 +01:00
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;
2016-02-25 22:53:33 +01:00
reply = nullptr;
2016-02-28 02:33:25 +01:00
removeBusyFlag();
return;
}
}
2016-02-25 22:53:33 +01:00
locker.unlock();
QFile outputFile(newDatabasePath);
if(outputFile.open(QFile::WriteOnly) && outputFile.write(reply->readAll())) {
outputFile.close();
2016-02-25 22:53:33 +01:00
{
QWriteLocker locker(lock());
m_dbPath = newDatabasePath;
}
2016-02-28 02:33:25 +01:00
init();
} else {
2016-02-25 22:53:33 +01:00
locker.relock();
cerr << "An IO error occured when storing database file for [" << name().toLocal8Bit().data() << "]: Unable to create/write output file." << endl;
2016-02-28 02:33:25 +01:00
removeBusyFlag();
}
2019-06-10 15:39:56 +02:00
break;
2017-01-29 23:10:21 +01:00
} default:
cerr << "An error occured when dwonloading database file for [" << name().toLocal8Bit().data() << "]: " << reply->errorString().toLocal8Bit().data() << endl;
2016-02-25 22:53:33 +01:00
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);
2016-02-28 02:33:25 +01:00
} else {
removeBusyFlag();
2016-02-25 22:53:33 +01:00
}
}
}
2015-09-04 14:37:01 +02:00
} // namespace Alpm