summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2024-04-29 15:46:12 +0200
committerSimon Rettberg2024-04-29 15:46:12 +0200
commitefa4b67ced6d4e0a5c850640e615724782637d50 (patch)
treea50d63b1867206fb72af4a69567c3848c47da68d
downloadcowgui-efa4b67ced6d4e0a5c850640e615724782637d50.tar.gz
cowgui-efa4b67ced6d4e0a5c850640e615724782637d50.tar.xz
cowgui-efa4b67ced6d4e0a5c850640e615724782637d50.zip
Inischl commit
-rw-r--r--.gitignore4
-rw-r--r--CMakeLists.txt39
-rw-r--r--src/gui.cpp217
-rw-r--r--src/gui.h41
-rw-r--r--src/main.cpp12
-rw-r--r--src/progress.cpp60
-rw-r--r--src/progress.h29
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