repoindex/network/connection.cpp

200 lines
7.3 KiB
C++

#include "./connection.h"
#include "../alpm/manager.h"
#include "../alpm/upgradelookup.h"
#include "../alpm/suggestionslookup.h"
#include "../alpm/packageinfolookup.h"
#include "../alpm/config.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QWebSocket>
#include <QCoreApplication>
#include <iostream>
using namespace std;
namespace RepoIndex {
Connection::Connection(Manager &manager, QWebSocket *socket, QObject *parent) :
QObject(parent),
m_socket(socket),
m_manager(manager),
m_repoInfoUpdatesRequested(false),
m_groupInfoUpdatesRequested(false)
{
socket->setParent(this);
connect(socket, &QWebSocket::textMessageReceived, this, &Connection::processTextMessage);
connect(socket, &QWebSocket::binaryMessageReceived, this, &Connection::processBinaryMessage);
connect(socket, &QWebSocket::disconnected, this, &Connection::socketDisconnected);
connect(&manager, &Manager::updatesAvailable, this, &Connection::updatedAvailable);
}
void Connection::sendJson(const QJsonObject &obj)
{
m_socket->sendTextMessage(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
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);
}
void Connection::sendResult(const QJsonValue &what, const QJsonValue &id, const QJsonValue &value)
{
QJsonObject response;
response.insert(QStringLiteral("class"), QStringLiteral("results"));
response.insert(QStringLiteral("what"), what);
response.insert(QStringLiteral("value"), value);
if(!id.isNull() && !id.isUndefined()) {
response.insert("id", id);
}
sendJson(response);
}
void Connection::sendResults(const QJsonValue &what, const QJsonValue &id, const QJsonValue &values)
{
QJsonObject response;
response.insert(QStringLiteral("class"), QStringLiteral("results"));
response.insert(QStringLiteral("what"), what);
response.insert(QStringLiteral("values"), values);
if(!id.isNull() && !id.isUndefined()) {
response.insert("id", id);
}
sendJson(response);
}
void Connection::updatedAvailable()
{
if(m_repoInfoUpdatesRequested) {
sendResult(QStringLiteral("basicrepoinfo"), QJsonValue(), m_manager.basicRepoInfo());
}
if(m_groupInfoUpdatesRequested) {
sendResults(QStringLiteral("groupinfo"), QJsonValue(), m_manager.groupInfo());
}
}
template<class Lookup, typename... Args>
void Connection::performLookup(const QJsonObject &request, Args &&...args)
{
auto *lookup = new Lookup(forward<Args>(args)...);
if(lookup->finished()) {
// the lookup has already finished when constructing the lookup object
// -> send the results immidiately
sendResult(request.value(QStringLiteral("what")), request.value(QStringLiteral("id")), lookup->results());
} else if(!lookup->errors().isEmpty()) {
// error occured during the construction of the lookup object
// -> send the errors immidiately
QJsonObject results;
results.insert(QStringLiteral("errors"), lookup->errors());
sendResult(request.value(QStringLiteral("what")), request.value(QStringLiteral("id")), results);
} else {
// the lookup object has been created without errors but the lookup is not done yet
// -> send results when available
connect(lookup, &Lookup::resultsAvailable, this, &Connection::sendResult);
}
// in any case: the lookup object destroys itself
}
void Connection::handleQuery(const QJsonObject &obj)
{
const auto what = obj.value(QStringLiteral("what")).toString();
const auto id = obj.value(QStringLiteral("id"));
if(what == QLatin1String("basicrepoinfo")) {
m_repoInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_repoInfoUpdatesRequested);
sendResult(what, id, m_manager.basicRepoInfo());
} else if(what == QLatin1String("basicpkginfo") || what == QLatin1String("pkgdetails") || what == QLatin1String("fullpkginfo")) {
performLookup<PackageInfoLookup>(obj, m_manager, obj);
} else if(what == QLatin1String("groupinfo")) {
m_groupInfoUpdatesRequested = obj.value(QStringLiteral("updates")).toBool(m_groupInfoUpdatesRequested);
sendResults(what, id, m_manager.groupInfo());
} else if(what == QLatin1String("suggestions")) {
performLookup<SuggestionsLookup>(obj, m_manager, obj);
} else if(what == QLatin1String("upgradelookup")) {
performLookup<UpgradeLookupJson>(obj, m_manager, obj);
} else if(what == QLatin1String("ping")) {
sendResult(what, id, QStringLiteral("pong"));
} else {
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()) {
cerr << shchar << "Info: Server stopped via web interface." << endl;
QCoreApplication::quit();
} else {
sendError(QStringLiteral("rejected"), id);
}
} else if(what == QLatin1String("reinitalpm")) {
if(m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Re-initializing ALPM databases (triggered via web interface) ..." << endl;
m_manager.removeAllDatabases();
if(m_manager.config().isLocalDatabaseEnabled()) {
m_manager.addLocalDatabase();
}
if(m_manager.config().areReposFromPacmanConfEnabled()) {
m_manager.addDataBasesFromPacmanConfig();
}
m_manager.addDatabasesFromRepoIndexConfig();
m_manager.initAlpmDataBases();
} else {
sendError(QStringLiteral("rejected"), id);
}
} else if(what == QLatin1String("updatealpm")) {
if(m_socket->peerAddress().isLoopback()) {
cerr << shchar << "Info: Forcing update of all ALPM databases (triggered via web interface) ..." << endl;
m_manager.forceUpdateAlpmDatabases();
} else {
sendError(QStringLiteral("rejected"), id);
}
} else {
sendError(QStringLiteral("unknown command"), id);
}
}
void Connection::processTextMessage(const QString &message)
{
QJsonParseError error;
QByteArray jsonData;
jsonData.append(message);
const QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
if(error.error == QJsonParseError::NoError) {
const QJsonObject obj = doc.object();
const QString msgClass = obj.value(QStringLiteral("class")).toString();
if(msgClass == QLatin1String("query")) {
handleQuery(obj);
} else if(msgClass == QLatin1String("cmd")) {
handleCmd(obj);
} else {
sendError(QStringLiteral("no message class specified"));
}
} else {
sendError(QStringLiteral("unable to parse JSON: ") + error.errorString());
}
}
void Connection::processBinaryMessage(const QByteArray &)
{
sendError(QStringLiteral("unable to parse binary messages"));
}
void Connection::socketDisconnected()
{
deleteLater();
}
}