#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();
}
}