summaryrefslogblamecommitdiffstats
path: root/src/gui.cpp
blob: 08b4475a9659ff36861f6b2fff1d786313182ac6 (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 <QSettings>

#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");
static QString STATE_CONN_ERROR("CONN_ERROR"); // Not a remote error, used when remote is unreachable

Gui::Gui(const QString &urlbase, const QString &uuid, int dnbd3pid, const QString &statusFile, 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)
    , _statusFile(statusFile)
    , _totalClusters(0)
{
    _nam->setAutoDeleteReplies(true);
    _nam->setTransferTimeout(5000);
    setupUi();
    auto flags = windowFlags() | Qt::WindowStaysOnTopHint;
    setWindowFlags(flags & ~(Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint));
    _tmrStatus->start(2000);
    QObject::connect(_tmrStatus, &QTimer::timeout, [this]() {
        Gui::queryRemoteStatus();
        if (_remoteState == STATE_WAITING_FOR_UPLOAD_DONE || _remoteState == STATE_COPYING) {
            Gui::readDnbd3Status();
        }
    });
}

void Gui::queryRemoteStatus()
{
    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 {
                _remoteState = STATE_CONN_ERROR;
                _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;
                setProgressState(title, percent, err);
                if (!err.isEmpty()) {
                    _status->setText(_remoteState + QLatin1String(": ") + err);
                }
            }
        }
    });
}

void Gui::setProgressState(const QString &title, int percent, const QString &err)
{
    Progress *item = _items.value(title);
    if (item == nullptr) {
        if (_items.size() > 10)
            return;
        item = new Progress(title, this);
        _itemBox->addWidget(item);
        _items.insert(title, item);
        item->show();
    }
    item->setProgress(percent);
    if (err.isEmpty()) {
        item->setCaption(title);
    } else {
        item->setCaption(title + QLatin1String(" ") + err);
    }
}

void Gui::readDnbd3Status()
{
    if (_statusFile.isEmpty())
        return;
    /*
    [General]
    uuid=43dd730b-56e0-4581-9480-4fbafdfa772d
    state=backgroundUpload
    inQueue=0
    modifiedClusters=8
    idleClusters=134
    totalClustersUploaded=211
    activeUploads=0
    avgSpeedKb=0.00
    */
    int remaining = 0;
    QSettings setting(_statusFile, QSettings::IniFormat);
    remaining += setting.value(QLatin1String("inQueue")).toInt();
    remaining += setting.value(QLatin1String("modifiedClusters")).toInt();
    if (remaining > _totalClusters) {
        _totalClusters = remaining;
    }
    if (_totalClusters == 0)
        return;
    int percent = ((_totalClusters - remaining) * 100) / _totalClusters;
    setProgressState(tr("Änderungen hochladen"), percent, QString());
}

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();
            QTimer::singleShot(5000, [this]() {
                _allowClose = true;
                this->close();
            });
        }
        _btnAbort->setEnabled(false);
        _btnOk->setEnabled(true);
        _btnOk->setText(tr("Schließen"));
        QFont f(_btnOk->font());
        f.setBold(true);
        _btnOk->setFont(f);
    } else if (_remoteState == STATE_UPLOAD_DONE) {
        setProgressState(tr("Änderungen hochladen"), 100, QString());
        _btnAbort->setEnabled(true);
        _btnOk->setEnabled(true);
    } else if (_remoteState == STATE_ERROR) {
        _btnAbort->setEnabled(true);
        _btnOk->setEnabled(false);
        _tmrStatus->stop();
        _btnAbort->setText(tr("Schließen"));
        QFont f(_btnAbort->font());
        f.setBold(true);
        _btnAbort->setFont(f);
    } 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();
    }
}