Skip to content
Snippets Groups Projects
xdgurl.cpp 8.89 KiB
Newer Older
akiraohgaki's avatar
akiraohgaki committed
#include <QDebug>
akiraohgaki's avatar
akiraohgaki committed
#include <QUrl>
#include <QUrlQuery>
akiraohgaki's avatar
akiraohgaki committed
#include <QTemporaryFile>
#include <QMimeDatabase>
#include <QProcess>
akiraohgaki's avatar
akiraohgaki committed
#include <QNetworkReply>
akiraohgaki's avatar
akiraohgaki committed

#include "../core/config.h"
#include "../core/network.h"
#include "../utility/file.h"
#include "../utility/json.h"

#include "xdgurl.h"

namespace Handlers {

akiraohgaki's avatar
akiraohgaki committed
XdgUrl::XdgUrl(const QString &xdgUrl, Core::Config *appConfig, Core::Config *userConfig, Core::Network *asyncNetwork, QObject *parent) :
    QObject(parent), _xdgUrl(xdgUrl), _appConfig(appConfig), _userConfig(userConfig), _asyncNetwork(asyncNetwork)
akiraohgaki's avatar
akiraohgaki committed
{
akiraohgaki's avatar
akiraohgaki committed
    _metadata = _parse();
akiraohgaki's avatar
akiraohgaki committed
    _destinations = _loadDestinations();
    _archiveTypes = _loadArchiveTypes();
akiraohgaki's avatar
akiraohgaki committed
}

akiraohgaki's avatar
akiraohgaki committed
QJsonObject XdgUrl::_parse()
{
akiraohgaki's avatar
akiraohgaki committed
    QUrl url(_xdgUrl);
    QUrlQuery query(url);
akiraohgaki's avatar
akiraohgaki committed
    QJsonObject metadata;
akiraohgaki's avatar
akiraohgaki committed

akiraohgaki's avatar
akiraohgaki committed
    metadata["scheme"] = QString("xdg");
    metadata["command"] = QString("download");
    metadata["url"] = QString("");
    metadata["type"] = QString("downloads");
    metadata["filename"] = QString("");
akiraohgaki's avatar
akiraohgaki committed

    if (!url.scheme().isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        metadata["scheme"] = url.scheme();
akiraohgaki's avatar
akiraohgaki committed
    }

    if (!url.host().isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        metadata["command"] = url.host();
akiraohgaki's avatar
akiraohgaki committed
    }

    if (query.hasQueryItem("url") && !query.queryItemValue("url").isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        metadata["url"] = query.queryItemValue("url", QUrl::FullyDecoded);
akiraohgaki's avatar
akiraohgaki committed
    }

    if (query.hasQueryItem("type") && !query.queryItemValue("type").isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        metadata["type"] = query.queryItemValue("type", QUrl::FullyDecoded);
akiraohgaki's avatar
akiraohgaki committed
    }

    if (query.hasQueryItem("filename") && !query.queryItemValue("filename").isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        metadata["filename"] = QUrl(query.queryItemValue("filename", QUrl::FullyDecoded)).fileName();
akiraohgaki's avatar
akiraohgaki committed
    }

akiraohgaki's avatar
akiraohgaki committed
    if (!metadata["url"].toString().isEmpty() && metadata["filename"].toString().isEmpty()) {
        metadata["filename"] = QUrl(metadata["url"].toString()).fileName();
akiraohgaki's avatar
akiraohgaki committed
    }

akiraohgaki's avatar
akiraohgaki committed
    return metadata;
akiraohgaki's avatar
akiraohgaki committed
QString XdgUrl::_convertPathString(const QString &path)
{
    QString newPath = path;
akiraohgaki's avatar
akiraohgaki committed
    if (newPath.contains("$HOME")) {
        newPath.replace("$HOME", Utility::File::homePath());
    }
    else if (newPath.contains("$XDG_DATA")) {
        newPath.replace("$XDG_DATA", Utility::File::xdgDataHomePath());
    }
    else if (newPath.contains("$KDE_DATA")) {
        newPath.replace("$KDE_DATA", Utility::File::kdeDataHomePath());
    }
akiraohgaki's avatar
akiraohgaki committed
    return newPath;
}

akiraohgaki's avatar
akiraohgaki committed
QJsonObject XdgUrl::_loadDestinations()
{
    QJsonObject destinations;
    QJsonObject appConfigDestinations = _appConfig->get("destinations");
    QJsonObject appConfigDestinationsAlias = _appConfig->get("destinations_alias");
akiraohgaki's avatar
akiraohgaki committed
    QJsonObject userConfigDestinations = _userConfig->get("destinations");
    QJsonObject userConfigDestinationsAlias = _userConfig->get("destinations_alias");

    foreach (const QString key, appConfigDestinations.keys()) {
akiraohgaki's avatar
akiraohgaki committed
        destinations[key] = _convertPathString(appConfigDestinations[key].toString());
    }

    foreach (const QString key, appConfigDestinationsAlias.keys()) {
        QString value = appConfigDestinationsAlias[key].toString();
        if (destinations.contains(value)) {
akiraohgaki's avatar
akiraohgaki committed
            destinations[key] = destinations.value(value);
        }
    }

    if (!userConfigDestinations.isEmpty()) {
        foreach (const QString key, userConfigDestinations.keys()) {
            destinations[key] = _convertPathString(userConfigDestinations[key].toString());
        }
    }

    if (!userConfigDestinationsAlias.isEmpty()) {
        foreach (const QString key, userConfigDestinationsAlias.keys()) {
            QString value = userConfigDestinationsAlias[key].toString();
            if (destinations.contains(value)) {
                destinations[key] = destinations.value(value);
            }
akiraohgaki's avatar
akiraohgaki committed
QJsonObject XdgUrl::_loadArchiveTypes()
akiraohgaki's avatar
akiraohgaki committed
{
akiraohgaki's avatar
akiraohgaki committed
    QJsonObject archiveTypes;
    QJsonObject appConfigArchiveTypes = _appConfig->get("archive_types");
    QJsonObject userConfigArchiveTypes = _userConfig->get("archive_types");
akiraohgaki's avatar
akiraohgaki committed
    archiveTypes = appConfigArchiveTypes;

    if (!userConfigArchiveTypes.isEmpty()) {
        foreach (const QString key, userConfigArchiveTypes.keys()) {
            archiveTypes[key] = userConfigArchiveTypes.value(key);
        }
    }

    return archiveTypes;
akiraohgaki's avatar
akiraohgaki committed
bool XdgUrl::_installPlasmapkg(const QString &path, const QString &type)
{
    QProcess process;
akiraohgaki's avatar
akiraohgaki committed
    QString program = "plasmapkg2"; // Use plasmapkg2 for now
    QStringList arguments;
    arguments << "-t" << type << "-i" << path;
akiraohgaki's avatar
akiraohgaki committed
    process.start(program, arguments);
    if (process.waitForFinished()) {
        return true;
    }
    return false;
akiraohgaki's avatar
akiraohgaki committed
}

bool XdgUrl::_uncompressArchive(const QString &path, const QString &targetDir)
{
    QMimeDatabase mimeDb;
    QString mimeType = mimeDb.mimeTypeForFile(path).name();
    QString archiveType;

    QProcess process;
    QString program;
    QStringList arguments;

    if (_archiveTypes.contains(mimeType)) {
        archiveType = _archiveTypes[mimeType].toString();

        if (archiveType == "tar") {
            program = "tar";
            arguments << "-xf" << path << "-C" << targetDir;
        }
        else if (archiveType == "zip") {
            program = "unzip";
            arguments << "-o" << path << "-d" << targetDir;
        }
        else if (archiveType == "7z") {
            program = "7z";
            arguments << "x" << path << "-o" + targetDir; // No space between -o and directory
        }
        else if (archiveType == "rar") {
            program = "unrar";
            arguments << "e" << path << targetDir;
        }

        process.start(program, arguments);

        if (process.waitForFinished()) {
            process.waitForReadyRead();
            return true;
        }
    }

    return false;
akiraohgaki's avatar
akiraohgaki committed
void XdgUrl::_saveDownloadedFile(QNetworkReply *reply)
akiraohgaki's avatar
akiraohgaki committed
{
akiraohgaki's avatar
akiraohgaki committed
    QJsonObject result;

akiraohgaki's avatar
akiraohgaki committed
    QTemporaryFile temporaryFile;

    if (!temporaryFile.open()) {
        result["error"] = QString("save_error");
        emit finished(Utility::Json::convertObjToStr(result));
        return;
    }

    temporaryFile.write(reply->readAll());

    QMimeDatabase mimeDb;
    QString mimeType = mimeDb.mimeTypeForFile(temporaryFile.fileName()).name();

    if (mimeType == "text/html" || mimeType == "application/xhtml+xml") {
        result["error"] = QString("filetype_error");
        emit finished(Utility::Json::convertObjToStr(result));
        return;
    }

akiraohgaki's avatar
akiraohgaki committed
    QString destination = _destinations[_metadata["type"].toString()].toString();
    QString path = destination + "/" + _metadata["filename"].toString();

    Utility::File::makeDir(destination);
    Utility::File::remove(path); // Remove previous downloaded file

    if (!temporaryFile.copy(path)) {
        result["error"] = QString("save_error");
        emit finished(Utility::Json::convertObjToStr(result));
        return;
    }

    result["success"] = QString("download_success");
    result["destination"] = destination;
    emit finished(Utility::Json::convertObjToStr(result));
akiraohgaki's avatar
akiraohgaki committed
void XdgUrl::_installDownloadedFile(QNetworkReply *reply)
akiraohgaki's avatar
akiraohgaki committed
/**
 * Private slots
 */

akiraohgaki's avatar
akiraohgaki committed
void XdgUrl::_downloaded(QNetworkReply *reply)
akiraohgaki's avatar
akiraohgaki committed
{
akiraohgaki's avatar
akiraohgaki committed
    QJsonObject result;
akiraohgaki's avatar
akiraohgaki committed

akiraohgaki's avatar
akiraohgaki committed
    if (reply->error() != QNetworkReply::NoError) {
        result["error"] = QString("network_error");
        emit finished(Utility::Json::convertObjToStr(result));
        return;
    }

    // If the network reply has a refresh header, retry download
    if (reply->hasRawHeader("Refresh")) {
        QString refreshUrl = QString(reply->rawHeader("Refresh")).split("url=").last();
        if (refreshUrl.startsWith("/")) {
            QUrl url = reply->url();
            refreshUrl = url.scheme() + "://" + url.host() + refreshUrl;
        }
        _asyncNetwork->get(QUrl(refreshUrl));
        return;
    }

    if (_metadata["command"].toString() == "download") {
akiraohgaki's avatar
akiraohgaki committed
        _saveDownloadedFile(reply);
akiraohgaki's avatar
akiraohgaki committed
    }
    else if (_metadata["command"].toString() == "install") {
akiraohgaki's avatar
akiraohgaki committed
        _installDownloadedFile(reply);
akiraohgaki's avatar
akiraohgaki committed
    }
akiraohgaki's avatar
akiraohgaki committed
}

/**
 * Public slots
 */

akiraohgaki's avatar
akiraohgaki committed
QString XdgUrl::getXdgUrl()
{
    return _xdgUrl;
}

akiraohgaki's avatar
akiraohgaki committed
QString XdgUrl::getMetadata()
{
    return Utility::Json::convertObjToStr(_metadata);
}

akiraohgaki's avatar
akiraohgaki committed
bool XdgUrl::isValid()
{
    bool isValid = true;

akiraohgaki's avatar
akiraohgaki committed
    if (_metadata["scheme"].toString() != "xdg" && _metadata["scheme"].toString() != "xdgs") {
akiraohgaki's avatar
akiraohgaki committed
        isValid = false;
    }

akiraohgaki's avatar
akiraohgaki committed
    if (_metadata["command"].toString() != "download" && _metadata["command"].toString() != "install") {
akiraohgaki's avatar
akiraohgaki committed
        isValid = false;
    }

akiraohgaki's avatar
akiraohgaki committed
    if (!QUrl(_metadata["url"].toString()).isValid()) {
akiraohgaki's avatar
akiraohgaki committed
        isValid = false;
    }

akiraohgaki's avatar
akiraohgaki committed
    if (!_destinations.contains(_metadata["type"].toString())) {
akiraohgaki's avatar
akiraohgaki committed
        isValid = false;
    }

akiraohgaki's avatar
akiraohgaki committed
    if (_metadata["filename"].toString().isEmpty()) {
akiraohgaki's avatar
akiraohgaki committed
        isValid = false;
    }

    return isValid;
}

akiraohgaki's avatar
akiraohgaki committed
void XdgUrl::process()
akiraohgaki's avatar
akiraohgaki committed
{
akiraohgaki's avatar
akiraohgaki committed
    /**
     * xdgs scheme is a reserved name, so the process of xdgs
     * is the same process of the xdg scheme currently.
     */

    if (isValid()) {
akiraohgaki's avatar
akiraohgaki committed
        connect(_asyncNetwork, &Core::Network::finished, this, &XdgUrl::_downloaded);
akiraohgaki's avatar
akiraohgaki committed
        _asyncNetwork->get(QUrl(_metadata["url"].toString()));
akiraohgaki's avatar
akiraohgaki committed
    }
akiraohgaki's avatar
akiraohgaki committed
} // namespace Handlers