#include "gui.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(); } }