267 lines
11 KiB
C++
267 lines
11 KiB
C++
#include "userrepository.h"
|
|
|
|
#include "../alpm/aurpackage.h"
|
|
|
|
#include <c++utilities/misc/memory.h>
|
|
|
|
#include <KTar>
|
|
#include <KArchiveFile>
|
|
|
|
#include <QStringBuilder>
|
|
#include <QUrlQuery>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkAccessManager>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
|
|
using namespace std;
|
|
|
|
namespace RepoIndex {
|
|
|
|
const char *requestTypeProp = "type";
|
|
const QString rpcRequestTypeKey(QStringLiteral("type"));
|
|
const QString rpcRequestTypeSearch(QStringLiteral("search"));
|
|
const QString rpcRequestTypeSuggest(QStringLiteral("suggest"));
|
|
const QString rpcRequestTypeMultiInfo(QStringLiteral("multiinfo"));
|
|
const QString rpcArgKey(QStringLiteral("arg"));
|
|
const QString rpcArgArray(QStringLiteral("arg[]"));
|
|
const QString pkgbuildRequestType(QString("pkgbuild"));
|
|
QUrl UserRepository::m_aurRpcUrl = QUrl(QStringLiteral("https://aur.archlinux.org/rpc.php"));
|
|
QUrl UserRepository::m_aurPkgbuildUrl = QUrl(QStringLiteral("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD"));
|
|
QUrl UserRepository::m_aurSrcInfoUrl = QUrl(QStringLiteral("https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO"));
|
|
QString UserRepository::m_aurSnapshotPath = QStringLiteral("https://aur.archlinux.org/cgit/aur.git/snapshot/%1.tar.gz");
|
|
|
|
AurPackageReply::AurPackageReply(QNetworkReply *networkReply, const QStringList &requestedPackages, UserRepository *userRepo) :
|
|
PackageReply(networkReply, requestedPackages, userRepo),
|
|
m_userRepo(userRepo)
|
|
{}
|
|
|
|
void AurPackageReply::processData()
|
|
{
|
|
auto *reply = m_networkReplies.front();
|
|
if(reply->error() == QNetworkReply::NoError) {
|
|
QJsonParseError error;
|
|
//QByteArray data = m_networkReply->readAll();
|
|
//cerr << shchar << "AUR reply: " << data.data() << endl;
|
|
//const QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
|
const auto doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
auto &packages = m_repo->packages();
|
|
if(error.error == QJsonParseError::NoError) {
|
|
for(const auto &result : doc.object().value(QStringLiteral("results")).toArray()) {
|
|
QJsonObject obj = result.toObject();
|
|
QString packageName = obj.value(QStringLiteral("Name")).toString();
|
|
if(!packageName.isEmpty()) {
|
|
auto &package = packages[packageName];
|
|
if(package) {
|
|
static_cast<AurPackage *>(package.get())->putJson(obj);
|
|
} else {
|
|
package = make_unique<AurPackage>(obj, static_cast<UserRepository *>(m_userRepo));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to parse JSON received from AUR: ") % error.errorString() % QStringLiteral(" at character ") % QString::number(error.offset);
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to request data from AUR via AurJson: ") + reply->errorString();
|
|
}
|
|
emit resultsAvailable();
|
|
}
|
|
|
|
AurFullPackageReply::AurFullPackageReply(const QList<QNetworkReply *> &networkReplies, const QStringList &requestedPackages, UserRepository *userRepo) :
|
|
PackageReply(networkReplies, requestedPackages, userRepo),
|
|
m_userRepo(userRepo)
|
|
{}
|
|
|
|
void AurFullPackageReply::processData()
|
|
{
|
|
auto *reply = static_cast<QNetworkReply *>(sender());
|
|
if(reply->error() == QNetworkReply::NoError) {
|
|
KTar tar(reply);
|
|
if(tar.open(QIODevice::ReadOnly)) {
|
|
const auto *baseDir = tar.directory();
|
|
//const auto packageBase = baseDir->name();
|
|
const auto *srcInfoEntry = baseDir->entry(QStringLiteral(".SRCINFO"));
|
|
if(srcInfoEntry && srcInfoEntry->isFile()) {
|
|
const QByteArray srcInfo = static_cast<const KArchiveFile *>(srcInfoEntry)->data();
|
|
const auto packages = m_userRepo->addPackagesFromSrcInfo(srcInfo);
|
|
// TODO: error handling
|
|
for(const auto &entryName : baseDir->entries()) {
|
|
if(entryName != QLatin1String(".SRCINFO")) {
|
|
const auto *entry = baseDir->entry(entryName);
|
|
if(entry->isFile()) {
|
|
for(const auto *package : packages) {
|
|
// TODO: add source files
|
|
}
|
|
} else {
|
|
// don't think it is required to read sub directories recursively
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Aur tarball does not contain \".SRCINFO\".");
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to open tarball reply.");
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to request tarball from AUR: ") + reply->errorString();
|
|
}
|
|
}
|
|
|
|
AurSuggestionsReply::AurSuggestionsReply(QNetworkReply *networkReply, const QString &term, UserRepository *repo) :
|
|
SuggestionsReply(networkReply, term, repo)
|
|
{}
|
|
|
|
void AurSuggestionsReply::processData()
|
|
{
|
|
auto *reply = m_networkReplies.front();
|
|
if(reply->error() == QNetworkReply::NoError) {
|
|
QJsonParseError error;
|
|
//QByteArray data = m_networkReply->readAll();
|
|
//cerr << shchar << "AUR reply: " << data.data() << endl;
|
|
//const QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
|
const auto doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if(error.error == QJsonParseError::NoError) {
|
|
auto &packages = m_repo->packages();
|
|
if(doc.isObject()) {
|
|
for(const auto &result : doc.object().value(QStringLiteral("results")).toArray()) {
|
|
QJsonObject obj = result.toObject();
|
|
QString packageName = obj.value(QStringLiteral("Name")).toString();
|
|
if(!packageName.isEmpty()) {
|
|
auto &package = packages[packageName];
|
|
if(package) {
|
|
static_cast<AurPackage *>(package.get())->putJson(obj);
|
|
} else {
|
|
package = make_unique<AurPackage>(obj, static_cast<UserRepository *>(m_repo));
|
|
}
|
|
}
|
|
}
|
|
} else if(doc.isArray()) {
|
|
for(const auto &suggestion : doc.array()) {
|
|
const auto suggestionString = suggestion.toString();
|
|
if(!suggestionString.isEmpty()) {
|
|
auto &package = packages[suggestionString];
|
|
if(!package) {
|
|
package = make_unique<AurPackage>(suggestionString, static_cast<UserRepository *>(m_repo));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to parse JSON received from AUR: ") % error.errorString() % QStringLiteral(" at character ") % QString::number(error.offset);
|
|
}
|
|
} else {
|
|
m_error = QStringLiteral("Error: Unable to request data from AUR: ") + reply->errorString();
|
|
}
|
|
emit resultsAvailable();
|
|
}
|
|
|
|
UserRepository::UserRepository(QNetworkAccessManager &networkAccessManager, QObject *parent) :
|
|
Repository(QStringLiteral("AUR"), invalidIndex, parent),
|
|
m_networkAccessManager(networkAccessManager)
|
|
{
|
|
m_description = QStringLiteral("Arch User Repository");
|
|
}
|
|
|
|
RepositoryType UserRepository::type() const
|
|
{
|
|
return RepositoryType::UserRepository;
|
|
}
|
|
|
|
PackageDetailAvailability UserRepository::requestsRequired(PackageDetail packageDetail) const
|
|
{
|
|
switch(packageDetail) {
|
|
case PackageDetail::Basics:
|
|
return PackageDetailAvailability::Request;
|
|
case PackageDetail::Dependencies:
|
|
case PackageDetail::SourceInfo:
|
|
case PackageDetail::AllAvailable:
|
|
return PackageDetailAvailability::FullRequest;
|
|
default:
|
|
return PackageDetailAvailability::Never;
|
|
}
|
|
}
|
|
|
|
AurSuggestionsReply *UserRepository::requestSuggestions(const QString &term)
|
|
{
|
|
size_t remainingSuggestions = 20;
|
|
//for(auto i = packages().lower_bound(term), end = packages().cend(); i != end && remainingSuggestions && i->first.startsWith(term, Qt::CaseInsensitive); ++i, --remainingSuggestions);
|
|
for(const auto &pkgEntry : packages()) {
|
|
if(pkgEntry.first.compare(term, Qt::CaseInsensitive) == 0) {
|
|
if(!(--remainingSuggestions)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(remainingSuggestions) {
|
|
auto url = m_aurRpcUrl;
|
|
QUrlQuery query;
|
|
query.addQueryItem(rpcRequestTypeKey, term.size() < 3 ? rpcRequestTypeSuggest : rpcRequestTypeSearch);
|
|
query.addQueryItem(rpcArgKey, term);
|
|
url.setQuery(query);
|
|
return new AurSuggestionsReply(m_networkAccessManager.get(QNetworkRequest(url)), term, this);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
AurPackageReply *UserRepository::requestPackageInfo(const QStringList &packageNames, bool forceUpdate)
|
|
{
|
|
QUrlQuery query;
|
|
for(const auto &packageName : packageNames) {
|
|
try {
|
|
const auto &pkg = m_packages.at(packageName);
|
|
if(!pkg->hasGeneralInfo() || forceUpdate) {
|
|
query.addQueryItem(rpcArgArray, packageName);
|
|
}
|
|
} catch(const out_of_range &) {
|
|
query.addQueryItem(rpcArgArray, packageName);
|
|
}
|
|
}
|
|
if(query.isEmpty()) {
|
|
return nullptr;
|
|
} else {
|
|
auto url = m_aurRpcUrl;
|
|
query.addQueryItem(rpcRequestTypeKey, rpcRequestTypeMultiInfo);
|
|
url.setQuery(query);
|
|
return new AurPackageReply(m_networkAccessManager.get(QNetworkRequest(url)), packageNames, this);
|
|
}
|
|
}
|
|
|
|
AurFullPackageReply *UserRepository::requestFullPackageInfo(const QStringList &packageNames, bool forceUpdate)
|
|
{
|
|
QList<QNetworkReply *> replies;
|
|
for(const auto &packageName : packageNames) {
|
|
try {
|
|
const auto &pkg = m_packages.at(packageName);
|
|
if(!pkg->hasGeneralInfo() || !pkg->hasSourceRelatedMetaData() || forceUpdate) {
|
|
if(pkg->tarUrl().isEmpty()) {
|
|
replies << m_networkAccessManager.get(QNetworkRequest(m_aurSnapshotPath.arg(pkg->name())));
|
|
} else {
|
|
replies << m_networkAccessManager.get(QNetworkRequest(pkg->tarUrl()));
|
|
}
|
|
}
|
|
} catch(const out_of_range &) {
|
|
if(forceUpdate) {
|
|
replies << m_networkAccessManager.get(QNetworkRequest(m_aurSnapshotPath.arg(packageName)));
|
|
}
|
|
}
|
|
}
|
|
if(replies.isEmpty()) {
|
|
return nullptr;
|
|
} else {
|
|
return new AurFullPackageReply(replies, packageNames, this);
|
|
}
|
|
}
|
|
|
|
unique_ptr<Package> UserRepository::emptyPackage()
|
|
{
|
|
return make_unique<AurPackage>(this);
|
|
}
|
|
|
|
} // namespace Alpm
|
|
|