summaryrefslogblamecommitdiffstats
path: root/src/gui.cpp
blob: 361d802e8238ccdc5b643fa6820e55fd22f4d18a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                
                      
 

                   

                   







                                                                                                                                                          
                                                                                    

                                           


                                                           
                             
                        

                                  
                         



                                     
                                                          






















































                                                                                              
                                     
































































                                                                                    
                                                                                
                           




                         



                                                




                                                                                                                                   

                                                     
                                                                                       
                                   
     
                                                                         












                                                                                
                                   








                                                                                    
                                                                                         
                           









                                                                                                                                             
                                                                                














                                                                                      








                                    
#include "gui.h"
#include "progress.h"

#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QMessageBox>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QCloseEvent>

#include <iostream>

#include <signal.h>

static QString STATE_COPYING("COPYING"); // Still busy copying original file, new chunks are only accepted in already copied range
static QString STATE_WAITING_FOR_UPLOAD_DONE("WAITING_FOR_UPLOAD_DONE"); // Copying done, just waiting for new chunks until client signals that it's done
static QString STATE_WAITING_FOR_COPYING_DONE("WAITING_FOR_COPYING_DONE"); // Copying not done, but dnbd3-fuse already told us it's done, wait for copying
static QString STATE_UPLOAD_DONE("UPLOAD_DONE"); // dnbd3-fuse signaled merge request, upload is thus done and no new chunks are accepted
static QString STATE_PROCESSING("PROCESSING"); // Hashing, renaming, creating DB entry
static QString STATE_ERROR("ERROR");
static QString STATE_COMPLETELY_DONE("COMPLETELY_DONE");

Gui::Gui(const QString &urlbase, const QString &uuid, int dnbd3pid, QWidget *parent)
    : QDialog(parent)
    , _nam(new QNetworkAccessManager(this))
    , _urlStatus(urlbase + QLatin1String("status/") + uuid)
    , _urlAbort(urlbase + QLatin1String("abort/") + uuid)
    , _urlFinish(urlbase + QLatin1String("finish/") + uuid)
    , _denyInteraction(false)
    , _allowClose(false)
    , _tmrStatus(new QTimer(this))
    , _status(nullptr)
    , _dnbd3pid(dnbd3pid)
{
    _nam->setAutoDeleteReplies(true);
    _nam->setTransferTimeout(5000);
    setupUi();
    auto flags = windowFlags() | Qt::WindowStaysOnTopHint;
    setWindowFlags(flags & ~(Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint));
    _tmrStatus->start(2500);
    QObject::connect(_tmrStatus, &QTimer::timeout, [this]() {
        QNetworkReply *reply = _nam->get(QNetworkRequest(_urlStatus));
        QObject::connect(reply, &QNetworkReply::finished, [reply, this]() {
            // JSON
            auto repData = reply->readAll();
            auto doc = QJsonDocument::fromJson(repData);
            if (doc.isEmpty() || !doc.isObject()) {
                QString str = QString::fromUtf8(repData);
                int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
                if (code == 404) {
                    _remoteState = STATE_ERROR;
                    _status->setText(tr("Sitzung dem Server nicht bekannt"));
                    updateButtons();
                } else if (str.length() > 0 && str.length() < 160) {
                    _status->setText(QLatin1String("Server: ") + str);
                } else {
                    _status->setText(tr("Fehler beim Statusabruf"));
                }
                return;
            }
            // Got JSON
            auto outer = doc.object();
            QJsonValue state = outer[QLatin1String("state")];
            if (state.isString()) {
                auto ns = state.toString();
                if (ns != _remoteState) {
                    _status->setText(ns);
                    _remoteState = ns;
                    updateButtons();
                }
            }
            QJsonValue tasks = outer[QLatin1String("tasks")];
            if (tasks.isArray()) {
                auto arr = tasks.toArray();
                for (auto it : arr) {
                    if (!it.isObject())
                        continue;
                    auto o = it.toObject();
                    auto jt = o.value(QLatin1String("title"));
                    if (!jt.isString())
                        continue;
                    auto jp = o.value(QLatin1String("percent"));
                    auto je = o.value(QLatin1String("error"));
                    QString title = jt.toString();
                    QString err = je.isString() ? je.toString() : QLatin1String("");
                    int percent = err.isEmpty() && jp.isDouble() ? (int)jp.toDouble() : -1;
                    Progress *item = _items.value(title);
                    if (item == nullptr) {
                        if (_items.size() > 10)
                            continue;
                        item = new Progress(title, this);
                        _itemBox->addWidget(item);
                        _items.insert(title, item);
                        item->show();
                    }
                    item->setProgress(percent);
                    item->setCaption(title + QLatin1String(" ") + err);
                }
            }
        });
    });
}

Gui::~Gui()
{
    _nam->blockSignals(true);
}

void Gui::setupUi()
{
    QVBoxLayout *outerVert = new QVBoxLayout(this);
    outerVert->setMargin(3);

    _itemBox = new QVBoxLayout;
    _itemBox->setObjectName("ItemLayout");
    outerVert->addLayout(_itemBox);

    QLayout *buttons = new QHBoxLayout;
    buttons->setObjectName("ButtonLayout");
    outerVert->addLayout(buttons);

    _status = new QLabel("-", this);
    _itemBox->addWidget(_status);

    _btnAbort = new QPushButton(tr("Abbrechen/Löschen"), this);
    buttons->addWidget(_btnAbort);
    QObject::connect(_btnAbort, &QPushButton::clicked, this, &Gui::pushedCancel);

    _btnOk = new QPushButton(tr("Änderungen permanent behalten"), this);
    buttons->addWidget(_btnOk);
    QObject::connect(_btnOk, &QPushButton::clicked, this, &Gui::pushedOk);
}

void Gui::updateButtons()
{
    if (_remoteState == STATE_COMPLETELY_DONE || _remoteState == STATE_PROCESSING) {
        if (_remoteState == STATE_COMPLETELY_DONE) {
            _tmrStatus->stop();
        }
        _btnAbort->setEnabled(false);
        _btnOk->setEnabled(true);
        _btnOk->setText(tr("Schließen"));
    } else if (_remoteState == STATE_UPLOAD_DONE) {
        _btnAbort->setEnabled(true);
        _btnOk->setEnabled(true);
    } else if (_remoteState == STATE_ERROR) {
        _btnAbort->setEnabled(true);
        _btnOk->setEnabled(false);
        _tmrStatus->stop();
        _btnAbort->setText(tr("Schließen"));
    } else {
        _btnAbort->setEnabled(true);
        _btnOk->setEnabled(false);
    }
}

void Gui::pushedCancel(bool)
{
    if (_remoteState == STATE_ERROR) {
        std::cerr << "[cowgui] User pressed cancel in error state" << std::endl;
        _allowClose = true;
        this->close();
        return;
    }
    if (_denyInteraction)
        return;
    if (_remoteState == STATE_COMPLETELY_DONE) {
        _btnAbort->setEnabled(false);
        return;
    }
    if (_remoteState != STATE_ERROR) {
        int ret = QMessageBox::question(this, tr("Bestätigung"), tr("Wollen Sie diesen Bearbeitungsvorgang wirklich abbrechen?"));
        if (ret == QMessageBox::No)
            return;
    }
    if (_dnbd3pid != 0 && _dnbd3pid != -1) {
        // SIGQUIT tells dnbd3-fuse to stop uploading
        std::cerr << "[cowgui] Sending QUIT to dnbd3-fuse to stop upload" << std::endl;
        ::kill(_dnbd3pid, SIGQUIT);
    }
    std::cerr << "[cowgui] Sending abort command to server" << std::endl;
    _denyInteraction = true;
    QNetworkReply *reply = _nam->post(QNetworkRequest(_urlAbort), QByteArray());
    connect(reply, &QNetworkReply::finished, [reply, this]() {
        QString msg = QString::fromUtf8(reply->readAll());
        if (reply->error() != QNetworkReply::NoError) {
            QMessageBox::critical(this, tr("Fehler"), msg);
            _denyInteraction = false;
        } else {
            _btnAbort->setEnabled(false);
            _btnOk->setEnabled(false);
            _remoteState = STATE_ERROR;
            QMessageBox::information(this, tr("Ergebnis"), msg);
            QTimer::singleShot(2000, [this]() {
                _allowClose = true;
                this->close();
            });
        }
    });
}

void Gui::pushedOk(bool)
{
    if (_remoteState == STATE_COMPLETELY_DONE || _remoteState == STATE_PROCESSING) {
        std::cerr << "[cowgui] User pressed OK in done or processing state" << std::endl;
        _allowClose = true;
        this->close();
        return;
    }
    if (_denyInteraction)
        return;
    if (_remoteState == STATE_UPLOAD_DONE) {
        int ret = QMessageBox::question(this, tr("Bestätigung"), tr("Die Änderungen an der VM werden dauerhaft beibehalten. Fortfahren?"));
        if (ret == QMessageBox::No)
            return;
    }
    std::cerr << "[cowgui] Sending finalization request to server" << std::endl;
    _denyInteraction = true;
    QNetworkReply *reply = _nam->get(QNetworkRequest(_urlFinish));
    connect(reply, &QNetworkReply::finished, [reply, this]() {
        QString msg = QString::fromUtf8(reply->readAll());
        int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        if (reply->error() != QNetworkReply::NoError || (code < 200 || code >= 300)) {
            QMessageBox::critical(this, tr("Fehler"), msg);
            _denyInteraction = false;
        } else {
            _remoteState = STATE_PROCESSING;
            _status->setText(tr("Finalisiere..."));
            updateButtons();
        }
    });
}

void Gui::closeEvent(QCloseEvent *e)
{
    if (_allowClose) {
        QDialog::closeEvent(e);
    } else {
        e->ignore();
    }
}