diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 39 | ||||
-rw-r--r-- | src/gui.cpp | 217 | ||||
-rw-r--r-- | src/gui.h | 41 | ||||
-rw-r--r-- | src/main.cpp | 12 | ||||
-rw-r--r-- | src/progress.cpp | 60 | ||||
-rw-r--r-- | src/progress.h | 29 |
7 files changed, 402 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68e0f4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build/ +*.autosave +*~ +.*.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2a72808 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +# project name +project(cowgui CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall -Wextra -Werror -Wno-multichar -std=c++11") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wno-multichar -std=c++11") + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +file(GLOB COWGUI_SOURCES src/*.cpp) +#file(GLOB_RECURSE COWGUI_MOC_HEADERS src/*.h) +#file(GLOB_RECURSE COWGUI_UIS src/ui/*.ui) +#file(GLOB_RECURSE COWGUI_RESOURCES src/*.qrc) + +FIND_PACKAGE(Qt5 COMPONENTS Widgets Network REQUIRED) + +#QT5_WRAP_UI(COWGUI_UI_HEADERS ${COWGUI_UIS}) +#QT5_WRAP_CPP(COWGUI_MOC_SOURCES ${COWGUI_MOC_HEADERS}) + +add_executable(cowgui + ${COWGUI_SOURCES} +# ${COWGUI_MOC_SOURCES} +# ${COWGUI_UI_HEADERS} +# ${COWGUI_RC_SOURCES} +# ${COWGUI_QMS} +) + +target_link_libraries(cowgui + Qt5::Widgets + Qt5::Network +) + +install(TARGETS cowgui RUNTIME DESTINATION bin) diff --git a/src/gui.cpp b/src/gui.cpp new file mode 100644 index 0000000..e27086a --- /dev/null +++ b/src/gui.cpp @@ -0,0 +1,217 @@ +#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> + +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 char *urlbase, const char *uuid, QWidget *parent) + : QDialog(parent) + , _nam(new QNetworkAccessManager(this)) + , _urlStatus(QString(urlbase).append("status/").append(uuid)) + , _urlAbort(QString(urlbase).append("abort/").append(uuid)) + , _urlFinish(QString(urlbase).append("finish/").append(uuid)) + , _denyInteraction(false) + , _tmrStatus(new QTimer(this)) + , _status(nullptr) +{ + _nam->setAutoDeleteReplies(true); + _nam->setTransferTimeout(5000); + setupUi(); + auto flags = windowFlags(); + 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->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) { + this->close(); + return; + } + if (_denyInteraction) + 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 (_remoteState == STATE_COMPLETELY_DONE) { + _btnAbort->setEnabled(false); + return; + } + _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]() { + this->close(); + }); + } + }); +} + +void Gui::pushedOk(bool) +{ + if (_remoteState == STATE_COMPLETELY_DONE || _remoteState == STATE_PROCESSING) { + 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; + } + _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(); + } + }); +} diff --git a/src/gui.h b/src/gui.h new file mode 100644 index 0000000..58f0c54 --- /dev/null +++ b/src/gui.h @@ -0,0 +1,41 @@ +#ifndef _COW_GUI_H_ +#define _COW_GUI_H_ + +#include <QDialog> +#include <QHash> + +class QVBoxLayout; +class QNetworkAccessManager; +class QPushButton; +class QTimer; +class QLabel; + +class Progress; + +class Gui : public QDialog +{ + Q_OBJECT +public: + explicit Gui(const char *urlbase, const char *uuid, QWidget *parent = nullptr); + ~Gui(); + +private slots: + void pushedCancel(bool pushed); + void pushedOk(bool pushed); + +private: + void setupUi(); + void updateButtons(); + + QNetworkAccessManager *_nam; + QVBoxLayout *_itemBox; + QString _remoteState; + QString _urlStatus, _urlAbort, _urlFinish; + bool _denyInteraction; + QPushButton *_btnAbort, *_btnOk; + QTimer *_tmrStatus; + QHash<QString, Progress*> _items; + QLabel *_status; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..310d2a3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,12 @@ +#include "gui.h" + +#include <QApplication> + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + Gui window("http://10.4.9.57/cow/", "uuuuuuid"); + + window.show(); + return QGuiApplication::exec(); +} diff --git a/src/progress.cpp b/src/progress.cpp new file mode 100644 index 0000000..6d01482 --- /dev/null +++ b/src/progress.cpp @@ -0,0 +1,60 @@ +#include "progress.h" + +#include <QProgressBar> +#include <QHBoxLayout> +#include <QResizeEvent> +#include <QLabel> + +Progress::Progress(QString text, QWidget *parent) + : QWidget(parent) + , _label(new QLabel(text, this)) + , _progress(new QProgressBar(this)) + , _hidden(false) +{ + auto *l = new QHBoxLayout(this); + l->setMargin(0); + setLayout(l); + _progress->setRange(0, 100); +} + +Progress::~Progress() +{ + +} + +void Progress::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + auto size = event->size(); + int width = size.width() - 150; + if (width < 250) { + width = size.width(); + _progress->hide(); + } else if (_progress->value() != -1) { + _progress->show(); + } + _label->setMinimumWidth(width); +} + +void Progress::setProgress(int percent) +{ + if (percent == -1) { + if (!_hidden || _progress->isVisible()) { + _hidden = true; + _progress->hide(); + } + } else { + _progress->setValue(percent); + if (_hidden) { + _hidden = false; + if (_label->width() > 250) { + _progress->show(); + } + } + } +} + +void Progress::setCaption(const QString &caption) +{ + _label->setText(caption); +} diff --git a/src/progress.h b/src/progress.h new file mode 100644 index 0000000..23b54ee --- /dev/null +++ b/src/progress.h @@ -0,0 +1,29 @@ +#ifndef _PROGRESS_H_ +#define _PROGRESS_H_ + +#include <QWidget> + +class QProgressBar; +class QResizeEvent; +class QLabel; + +class Progress : public QWidget +{ + Q_OBJECT +public: + explicit Progress(QString text, QWidget *parent = nullptr); + ~Progress(); + + void setProgress(int percent); + void setCaption(const QString& caption); + +protected: + virtual void resizeEvent(QResizeEvent *event) override; + +private: + QLabel *_label; + QProgressBar *_progress; + bool _hidden; +}; + +#endif |