Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

@@ -0,0 +1,47 @@
#
# Exported scripts management for uploading, downloading and removing exported scripts
#
# $Id: CMakeLists.txt 51804 2020-07-31 04:47:28Z fritsche_h $
# $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/CMakeLists.txt $
# $Author: fritsche_h $
#
# Created by Mike Wille, SLTE, 2020-03-23
#
SET(src
tpm2_gui_plugin.cpp
tpm2_gui_plugin.h
tpm2_exported_scripts_dialog.cpp
tpm2_exported_scripts_dialog.h
logger.cpp
logger.h
)
QT5_WRAP_UI(src
tpm2_exported_scripts_dialog.ui
)
NGL_ADD_LIBRARY2(WS Thp2ManagementGuiPlugin MODULE ${src})
NGL_TARGET_COMPILE_DEFINITIONS(Thp2ManagementGuiPlugin PRIVATE L=QStringLiteral)
NGL_TARGET_LINK_LIBRARIES(Thp2ManagementGuiPlugin
Thp2Common
Thp2CommonCore
Tpm2GuiPluginInterface
Qt5::Widgets
Qt5::Network
Qt5::WebSockets # get live changes from the server
NGLLogger
LtsWidgets
LtsSession
LtsDataView
nglcompression # tar
nglsettings # windoze / OC thp server hostname
ngltools # NGL::Connections::eConnection and findProjectConfigFile
#AsmDataPluginInterface
#ExmDataPluginInterface
LtsSessionData
#httq
)
NGL_INSTALL_TARGET(Thp2ManagementGuiPlugin)
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Logging for THP2
* $Id: logger.cpp 46225 2019-03-25 13:59:56Z rudolphus_g $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/logger.cpp $
* $Author: rudolphus_g $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM2.";
std::string loggerName;
const size_t len = strlen(name);
loggerName.reserve(sizeof(ns) + len);
loggerName.append(ns, sizeof(ns) - 1);
loggerName.append(name, len);
try
{
return NGL::NGLLoggerBootstrap::getNGLRootLogger().
getNGLLogger(loggerName);
}
catch (const std::exception &ex)
{
fprintf(stderr, "Error retrieving logger: %s", ex.what());
abort();
}
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,24 @@
/**
* @RDE_HEADER@
* Logging for THP2
* $Id: logger.h 46225 2019-03-25 13:59:56Z rudolphus_g $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/logger.h $
* $Author: rudolphus_g $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
#pragma once
#include <NGLLogger/INGLLogger.h>
namespace TakeHomePackageManager
{
typedef NGL::INGLLogger &LOGGER_T;
LOGGER_T getLogger(const char *name);
inline LOGGER_T getLogger(const QString &loggerName)
{
return getLogger(loggerName.toUtf8().constData());
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,989 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_dialog.cpp 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_dialog.cpp $
* $Author: wille_m $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
/* TODO: FIXME: adjust data.json when uploading file, as data.json contains ALL
* scripts, but we only upload ONE script at a time - so remove all scripts but
* the one to be uploaded from data.json (but don't touch the main data.json!!)
*/
#include "ui_tpm2_exported_scripts_dialog.h"
#include "tpm2_exported_scripts_dialog.h"
#include <Common2/tpm_process_dialog.h>
#include <Common2/tpm_file_copy.h>
#include <Common2Core/tpm_tools.h>
#include <nglcompression/nglcomp_compress.h>
#include <nglsettings/nglsettings_connections.h>
#include <nglsettings/nglsettings_settings.h>
#include <ngltools/ngltools_filesystem.h>
#include <LtsWidgets/lts_message_dialog.h>
#include <LtsWidgets/lts_global_wait_dialog.h>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_data.h>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QTemporaryFile>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QWebSocket>
#include <QLocale>
#include <QTimer>
#include <QTemporaryDir>
#include <QDesktopServices>
#include <memory>
#ifndef ELOG
#define ELOG ERROR_LOG(m_logger) // polyfill
#endif
using err = std::runtime_error;
err error(const QString &msg) { return err(msg.toStdString()); }
namespace TakeHomePackageManager
{
Compressor::Compressor()
: QObject(nullptr)
{}
void Compressor::go(ProcessOverlay *processOverlay, QIODevice &out, const QString &sourceDir, const QString &destRootDir) noexcept(false)
{
//std::unique_ptr<NGL::Compress> compress(new NGL::Compress(nullptr));
auto *compress = new NGL::Compress(nullptr);
bool firstTime = true;
connect(compress, &NGL::Compress::compressInfoEx,
this, [processOverlay, &firstTime](qint64 totalFiles, qint64 curFile, qint64 fileSize, qint64 curPos, const QString &curName, const qint64 bytesTotal)
{
if (firstTime)
{
firstTime = false;
processOverlay->setStarted(totalFiles, bytesTotal);
processOverlay->setNewFile(curFile, fileSize, curName);
processOverlay->setStatus(tr("Compressing files ..."));
}
processOverlay->setFileProgress(curPos, fileSize);
});
QString errorString;
connect(compress, &NGL::Compress::error, // finished is ALWAYS called by nglcompress! delete on finished!
this, [&errorString](const QString &errStr)
{
qWarning() << "## COMPRESS ERROR" << errStr;
errorString = errStr;
});
connect(this, &Compressor::abort,
compress, [&errorString]()
{
errorString = L("Aborted.");
});
compress->compress(&out, sourceDir, destRootDir);
try
{
awaitWithCancelationSignalx(compress, &NGL::Compress::finished, this, &Compressor::abort); // NOTE: finished is always called - even ofter abort!
}
catch(const std::runtime_error &err)
{
qWarning() << "## aborted" << err.what();
compress->abort();
compress->waitForFinished();
compress->deleteLater();
throw err;
}
const bool success = compress->waitForFinished();//success();
compress->deleteLater();
if (!success || !errorString.isEmpty())
{
throw error(L("compress failed: %1").arg(errorString));
}
}
/*/
* every process needs to deleteLater itself
* every process must listen to signalTimeout & signalCancel (+ call deleteLater in them!)
* only connect to signalDone & signalError when using a request
* disconnect signalTimeout & signalCancel before you do another step
* a QNetworkReply will always call either signalDone or signalError - call deleteLater in both of them!
* aborting a QNetworkReply finally calls signalError, so call deleteLater at this point
* nglcompress will always call finished, it doesn't matter if it failed or not - call deleteLater at this point
/*/
HttpProcess::HttpProcess(LtsWidgets::LtsDialog *dialog, const QString &statusMessage)
: QObject()
, m_logger(getLogger("ExportedScriptsDialog.HttpProcess"))
, m_process(new ProcessOverlay)
, m_nam(new QNetworkAccessManager)
{
connect(m_process, &ProcessOverlay::signalCancel,
this, [this]()
{
DEBUG_LOG(m_logger) << "m_wasCanceled!!";
m_process->setCancelEnabled(false);
emit signalCancel();
});
dialog->setOverlay(m_process);
m_process->setStatus(statusMessage);
}
HttpProcess::~HttpProcess()
{
DEBUG_LOG(m_logger) << "~HttpProcess";
m_process->deleteLater();
m_nam->deleteLater();
}
QUrl ExportedScriptsDialog::forgeUrl(const QString &path, const QString &protocol)
{
#ifdef WIN32
const bool isTabOC = true;
#else // unix
const bool isTabOC = false;
#endif
const QString hostname(isTabOC
? NGL::Settings(NGL::findProjectConfigFile()).getString(L("TabOC"), L("remoteServerHostname"), L("exctds01"))
: NGL::Connections::hostName(NGL::Connections::eConnection::FLM_THP_SERVER));
const QUrl url(QStringLiteral("%1://%2:%3%4").arg(protocol, hostname).arg(NGL::Connections::port(NGL::Connections::eConnection::FLM_THP_SERVER)).arg(path));
return url;
}
QNetworkRequest ExportedScriptsDialog::forgeRequest(const QString &path, const QUrlQuery &q, const QString &contentType)
{
auto url = forgeUrl(path);
url.setQuery(q);
QNetworkRequest r;
if (!contentType.isEmpty())
r.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType);
r.setUrl(url);
return r;
}
int getResponseStatus(QNetworkReply *r) noexcept(false)
{
bool k = false;
const int status = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&k);
if (!k)
{
throw err("Missing http status in http response");
}
return status;
}
void HttpProcess::waitForAnswer(QNetworkReply *r, const int expectedResponseStatus) noexcept(false)
{
if (!r->isFinished() && r->bytesAvailable() < 1)
{
// either r errors and emits finished, or the user cancels, which emits abort ... awaitWithCancelationSignal can only listen to ONE signal... that's the 'best' solution that came to my mind without getting into this for too long.... :'(
// wait for finished, readyRead or cancel
std::unique_ptr<NoticeDummy> dummy(new NoticeDummy);
connect(r, &QNetworkReply::finished, dummy.get(), &NoticeDummy::signal, Qt::QueuedConnection); // if called before readyRead => error
connect(r, &QNetworkReply::readyRead, dummy.get(), &NoticeDummy::signal, Qt::QueuedConnection);
awaitWithCancelationSignalx(dummy.get(), &NoticeDummy::signal, this, &HttpProcess::signalCancel);
}
if (r->error() != QNetworkReply::NetworkError::NoError)
{
r->deleteLater();
throw error(L("network error: %1").arg(r->errorString()));
}
const int status = getResponseStatus(r);
if (status == expectedResponseStatus)
{
return;
}
// not 201 (created) - must be an error message from the server
r->deleteLater();
const QByteArray data(r->readAll());
ELOG << "server error: data = '" << data << "' http status: " << r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QJsonParseError parseError;
const QJsonDocument doc(QJsonDocument::fromJson(data, &parseError));
if (parseError.error != QJsonParseError::NoError)
{
ELOG << "didn't receive a valid json from server at " << parseError.offset << ": " << parseError.errorString() << " - message was: '" << data << "'";
throw err("request failed and error msg from server was malformed");
}
const QString errorMsg(doc.object()[QStringLiteral("error")].toString());
if (errorMsg.isEmpty())
ELOG << "server sent empty error";
else
ELOG << "server sent error: " << errorMsg;
//new LtsWidgets::LtsMessageDialogNew(qobject_cast<QWidget *>(this->parent()), tr("The server failed to process the operation"));
throw error(L("request failed - server said: %1 (http status = %2)").arg(errorMsg).arg(r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()));
}
QNetworkReply *HttpProcess::post(const QString &path, QIODevice *data, const QUrlQuery &q, const QString &contentType, const QMap<QString, QString> &additionalHeaders) noexcept(false)
{
auto req = ExportedScriptsDialog::forgeRequest(path, q, contentType);
QMapIterator<QString, QString> it(additionalHeaders);
while (it.hasNext())
{
it.next();
auto key = it.key();
auto val = it.value();
req.setRawHeader(key.toUtf8(), val.toUtf8());
}
QNetworkReply *r = m_nam->post(req, data);
bool firstTime = true;
connect(r, &QNetworkReply::uploadProgress,
this, [this, &firstTime, path](const qint64 bytes, const qint64 bytesTotal)
{
if (firstTime)
{
firstTime = false;
m_process->setStarted(1, bytesTotal);
m_process->setNewFile(1, bytesTotal, path);
m_process->setStatus(tr("Uploading ..."));
}
m_process->setFileProgress(bytes, bytesTotal);
});
waitForAnswer(r, 201);
return r;
}
QNetworkReply *HttpProcess::get(const QString &path, const QUrlQuery &q) noexcept(false)
{
QNetworkReply *r = m_nam->get(ExportedScriptsDialog::forgeRequest(path, q));
bool firstTime = true;
connect(r, &QNetworkReply::downloadProgress,
this, [this, &firstTime, path](const qint64 bytes, const qint64 bytesTotal)
{
if (firstTime)
{
firstTime = false;
m_process->setStarted(1, bytesTotal);
m_process->setNewFile(1, bytesTotal, path);
m_process->setStatus(tr("Downloading ..."));
}
m_process->setFileProgress(bytes, bytesTotal);
});
/// wait for first read - to check if it's a 200 or an error message
waitForAnswer(r, 200);
return r;
}
QNetworkReply *HttpProcess::deleteResource(const QString &path, const QUrlQuery &q) noexcept(false)
{
QNetworkReply *r = m_nam->deleteResource(ExportedScriptsDialog::forgeRequest(path, q)); // hahah, because 'delete' is a reserved keyword in c++
waitForAnswer(r, 200);
return r;
}
// UploadProcess
UploadProcess::UploadProcess(LtsWidgets::LtsDialog *dialog)
: HttpProcess(dialog, tr("Preparing Script upload ..."))
{}
void UploadProcess::upload(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const ExportedScriptInformation &esi) noexcept(false)
{
UploadProcess up(dialog);
up.go(rootFolder, esi);
}
std::unique_ptr<QTemporaryFile> createTempFile() noexcept(false)
{
std::unique_ptr<QTemporaryFile> tempFile(new QTemporaryFile);
if (!tempFile->open())
{
const QString msg(L("Cannot open temporary file: %1").arg(tempFile->errorString()));
throw error(msg);
}
return tempFile;
}
void UploadProcess::go(const QString &rootFolder, const ExportedScriptInformation &esi) noexcept(false)
{
// No error & done here - as not a request, but only nglcompress
const QString dstDir(esi.id()); // this is the destination directory where bzip2 is going to extract it to (that is, when the user later downloads the thp on his machine) - this can be a relative path!
const QString srcDir(scriptLocation(rootFolder, esi.id()));
auto tempFile(createTempFile());
Compressor compressor;
connect(this, &HttpProcess::signalCancel, &compressor, &Compressor::abort);
compressor.go(m_process, *tempFile.get(), srcDir, dstDir);
DEBUG_LOG(m_logger) << "posting file of size " << tempFile->size();
tempFile->seek(0);
QNetworkReply *rep = post(L("/thp2"), tempFile.get(), {}, L("application/octet-stream"), { { L("x-thp-info"), QString::fromUtf8(QJsonDocument(esi.toJson().toObject()).toJson(QJsonDocument::JsonFormat::Compact)) } });
rep->deleteLater();
}
/// DownloadProcess
DownloadProcess::DownloadProcess(LtsWidgets::LtsDialog *dialog)
: HttpProcess(dialog, tr("Preparing Script download ..."))
{}
void DownloadProcess::download(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const QString &scriptId) noexcept(false)
{
DownloadProcess down(dialog);
down.go(dialog, rootFolder, scriptId);
}
void DownloadProcess::go(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const QString &scriptId) noexcept(false)
{
/// download file
auto tempFile = createTempFile();
QNetworkReply *rep = get(L("/thp2"), QUrlQuery({{L("scriptId"), scriptId}}));
const auto thpInfo(rep->rawHeader(L("x-thp-info").toUtf8()));
if (thpInfo.isEmpty())
{
throw err("x-thp-info header missing");
}
const auto esi = ExportedScriptInformation::fromJson(JsonObject::fromJson(thpInfo/*.toUtf8()*/).object());
connect(rep, &QNetworkReply::readyRead,
this, [rep, &tempFile]()
{
tempFile->write(rep->readAll());
});
if (rep->bytesAvailable() > 0)
{
tempFile->write(rep->readAll());
}
if (!rep->isFinished())
{
try
{
awaitWithCancelationSignalx(rep, &QNetworkReply::finished, this, &HttpProcess::signalCancel);
}
catch(const err &err)
{
rep->deleteLater();
throw;
}
}
/// decompress file
m_process->setStatus(tr("Preparing Script decompression ..."));
std::unique_ptr<NGL::Compress> compress(new NGL::Compress(nullptr));
tempFile->seek(0);
QTemporaryDir tmpDir;
if (!tmpDir.isValid())
{
throw error(L("Temp dir creation failed: %1").arg(tmpDir.errorString()));
}
bool firstTime = true;
connect(compress.get(), &NGL::Compress::uncompressInfo,
this, [this, &firstTime](qint64 size, qint64 pos, const QString &file)
{
if (firstTime)
{
firstTime = false;
m_process->setStarted(1, size);
m_process->setNewFile(1, size, file);
m_process->setStatus(tr("Decompressing ..."));
}
m_process->setFileProgress(pos, size);
});
QString errorString;
connect(compress.get(), &NGL::Compress::error, // finished is ALWAYS called by nglcompress! delete on finished!
this, [&errorString](const QString &errStr)
{
qWarning() << "## COMPRESS ERROR" << errStr;
errorString = errStr;
});
compress->uncompress(tempFile.get(), tmpDir.path());
try
{
awaitWithCancelationSignalx(compress.get(), &NGL::Compress::finished, this, &HttpProcess::signalCancel);
}
catch(const std::runtime_error &err)
{
qWarning() << "## abort" << err.what();
compress->abort();
compress->waitForFinished();
throw err;
}
const bool success = compress->waitForFinished();
if (!success || !errorString.isEmpty())
{
throw error(L("uncompress failed: %1").arg(errorString));
}
const auto scriptLocationString(scriptLocation(rootFolder, scriptId));
if (!QDir(scriptLocationString).removeRecursively())
{
throw error(L("failed to remove existing script directory at %1").arg(scriptLocationString));
}
// is cross-device move in production - so copy
CopyInfoList fileList;
fileList += getFilesFromFolder(tmpDir.path() + QDir::separator() + scriptId, scriptLocationString); // copy script's topics
m_process->hide();
dialog->unsetOverlay();
m_process->hide(); // as copyFiles does a setOverlay, too, we need to get rid of the currently existing loading overlay now
copyFiles(fileList, dialog); // ... and wait ...
}
// RemoveProcess
RemoveProcess::RemoveProcess(LtsWidgets::LtsDialog *dialog)
: HttpProcess(dialog, tr("Requesting Script deletion on server ..."))
{}
void RemoveProcess::remove(LtsWidgets::LtsDialog *dialog, const QString &scriptId) noexcept(false)
{
RemoveProcess rm(dialog);
rm.go(scriptId);
}
void RemoveProcess::go(const QString &scriptId) noexcept(false)
{
deleteResource(L("/thp2"), {{ L("scriptId"), scriptId }});
}
// ExportedScriptsDialog
ExportedScriptsDialog::ExportedScriptsDialog(LtsSession::LtsSession *session, QWidget *parent) noexcept(false)
: LtsWidgets::LtsDialog(parent)
, m_model(new ExportedScriptsModel(this))
, m_ui(new Ui::ExportedScriptsDialog2)
, m_session(session)
, m_logger(getLogger("ExportedScriptsDialog2"))
{
m_ui->setupUi(this);
m_buttonBox->connectButton(LtsWidgets::DialogButton::Remove, this, &ExportedScriptsDialog::removeScriptLocal);
m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setEnabled(false); // Show presentation Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(false); // local remove Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(false); // download from Server
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(false); // Remove from Server Button
connect(m_ui->ltsDataView, &LtsDataView::LtsDataView::signalSelectionChanged, this, &ExportedScriptsDialog::onSelectionChanged);
setOnlineHelpFilename(QStringLiteral("Script_Editor-Export_Script_Administration"));
if (isOCApp())
{
m_buttonBox->connectButton(LtsWidgets::DialogButton::Save, this, &ExportedScriptsDialog::downloadScript); // download from Server
m_buttonBox->connectButton(LtsWidgets::DialogButton::Show, this, &ExportedScriptsDialog::openScript);
}
else
{
m_buttonBox->connectButton(LtsWidgets::DialogButton::Save, this, &ExportedScriptsDialog::uploadScript); // upload to Server
m_buttonBox->connectButton(LtsWidgets::DialogButton::Import, this, &ExportedScriptsDialog::removeScriptFromServer);
}
QWebSocket *sock = new QWebSocket();
sock->setParent(this);
auto openFn = [this, sock]()
{
setWsConnectionStatus(WsConnectionStatus::Connecting);
sock->open(forgeUrl(L("/thp2"), L("ws")));
};
openFn();
connect(sock, &QWebSocket::connected,
this, [this]()
{
setWsConnectionStatus(WsConnectionStatus::Connected);
});
connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
this, [this, sock]()
{
setWsConnectionStatus(WsConnectionStatus::Error);
ELOG << "ws error: " << sock->errorString();
}, Qt::QueuedConnection /* prevent crash */);
connect(sock, &QWebSocket::disconnected,
this, [this, sock, openFn]()
{
WARNING_LOG(m_logger) << "disconnected from ws... reconnecting in 3...";
setWsConnectionStatus(WsConnectionStatus::Disconnected);
QTimer *t = new QTimer(sock);
t->setInterval(3000);
t->setSingleShot(true);
connect(t, &QTimer::timeout,
sock, [t, openFn]()
{
openFn();
t->deleteLater();
});
t->start();
}, Qt::QueuedConnection /* prevent crash */);
connect(sock, &QWebSocket::textMessageReceived,
this, [this](const QString &msg)
{
DEBUG_LOG(m_logger) << "new msg: '" << msg << "'";
m_lastServerContent = msg;
m_model->setServerContent(msg);
onSelectionChanged(); // aka. enaDisa
});
initModel(); // throws!
}
ExportedScriptsDialog::~ExportedScriptsDialog()
{
delete m_ui;
}
bool ExportedScriptsDialog::isOCApp() const
{
//return true;
return (m_session->app() == LtsAppDescriptor::App::OC);
}
void ExportedScriptsDialog::onSelectionChanged()
{
m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setEnabled(false); // Show presentation Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(false); // download from / upload to Server
m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(false); // local remove Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(false); // Remove from Server Button
QTimer::singleShot(500, this, &ExportedScriptsDialog::_onSelectionChanged); // -.-
}
void ExportedScriptsDialog::_onSelectionChanged()
{
const QModelIndexList mil(m_ui->ltsDataView->selectedRows());
const bool isOneSelected = (mil.size() == 1);
bool enableShow = false;
bool enableRemoveLocal = false;
bool enableDownload = false;
bool enableUpload = false;
bool enableRemoveServer = false;
if (isOneSelected)
{
const ExportedScriptInformation esi = m_model->getRowData(mil.first().row());
const bool isOC = isOCApp();
const bool onLocal = esi.local;
const bool onServer = (esi.uploaded == ExportedScriptInformation::UploadState::ThpAlreadyUploaded);
enableShow = isOC && onLocal;
enableDownload = isOC && onServer;
enableUpload = !isOC && onLocal;
enableRemoveLocal = onLocal;
enableRemoveServer = onServer;
}
m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setEnabled(enableShow); // Show presentation Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(enableDownload || enableUpload); // download from / upload to Server
m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(enableRemoveLocal); // local remove Button
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(enableRemoveServer); // Remove from Server Button
}
void ExportedScriptsDialog::retranslate()
{
translate(m_ui, tr("Manage exported scripts"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setText(tr("Remove local"));
Q_ASSERT(m_session != nullptr);
if (isOCApp())
{
m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setText(tr("Download from Server"));
}
else
{
m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setText(tr("Upload to Server"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setText(tr("Remove from Server"));
}
setWsConnectionStatusLabel();
}
void ExportedScriptsDialog::setWsConnectionStatus(WsConnectionStatus status)
{
m_wsStatus = status;
setWsConnectionStatusLabel();
}
void ExportedScriptsDialog::setWsConnectionStatusLabel()
{
QString str;
QColor color;
switch (m_wsStatus)
{
case WsConnectionStatus::NotConnected:
{
str = tr("Not connected");
color = QColor::fromRgb(100, 100, 100);
break;
}
case WsConnectionStatus::Disconnected:
{
str = tr("Disconnected");
color = QColor::fromRgb(200, 55, 0);
//color = QColor::fromRgb(100, 100, 100);
break;
}
case WsConnectionStatus::Error:
{
str = tr("Connection error");
color = QColor::fromRgb(200, 55, 0);
break;
}
case WsConnectionStatus::Connecting:
{
str = tr("Connecting ...");
color = QColor::fromRgb(100, 100, 0);
break;
}
case WsConnectionStatus::Connected:
{
str = tr("Connected");
color = QColor::fromRgb(50, 200, 50);
break;
}
}
m_ui->lblWsStatus->setText(QStringLiteral("<font color='%2'>%1</font>").arg(str, color.name()));
}
void ExportedScriptsDialog::initModel() noexcept(false)
{
DEBUG_LOG(m_logger) << "(re)initModel !!";
const QString rootDirectory(localThpDir());
QDir().mkpath(rootDirectory);
try{
m_model->init(rootDirectory, true);
}
catch(...)
{
new LtsWidgets::LtsMessageDialogNew(this, tr("Couldn't Load THP(s)"));
WARNING_LOG(m_logger)<<" data corrupted path: "<< localThpDir();
}
m_model->setOC(isOCApp()); // important to call *before* setting model
m_ui->ltsDataView->setModel(m_model);
// 'hide' is important to call *after* setting model
#ifndef LTS_DEBUG
m_ui->ltsDataView->hideColumn(ExportedScriptsModel::ColId);
#endif
m_ui->ltsDataView->hideColumn(ExportedScriptsModel::Columns::ColUploadStatus);
m_model->setServerContent(m_lastServerContent);
onSelectionChanged(); // aka. enaDisa
}
void ExportedScriptsDialog::uploadScript()
{
const QModelIndexList mil = m_ui->ltsDataView->selectedRows();
if (mil.size() != 1)
return;
const ExportedScriptInformation esi = m_model->getRowData(mil.first().row());
if (!esi.local)
{
return;
}
try
{
UploadProcess::upload(this, localThpDir(), std::move(esi));
// NOTE: no need to call setServerContent as the server will broadcast new content after the upload
}
catch(const std::runtime_error &err)
{
ERROR_LOG(m_logger) << "Uploaded script FAILED: " << err.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Uploading script failed: %1").arg(QString::fromUtf8(err.what())));
return;
}
DEBUG_LOG(m_logger) << "Uploaded script successfully";
new LtsWidgets::LtsMessageDialogNew(this, tr("Uploaded script successfully"));
}
void ExportedScriptsDialog::downloadScript()
{
const QModelIndexList mil = m_ui->ltsDataView->selectedRows();
if (mil.size() != 1)
return;
const QModelIndex mi = mil.first();
ExportedScriptInformation esi = m_model->getRowData(mi.row());
if (esi.uploaded != ExportedScriptInformation::UploadState::ThpAlreadyUploaded)
{
return;
}
try
{
DownloadProcess::download(this, localThpDir(), esi.id());
// modify esi in model
for (int row = 0; row < m_model->rowCount(); row++)
{
const auto rowEsi(m_model->getRowData(row));
if (rowEsi.id() != esi.id())
{
continue;
}
esi.setLocal(rowEsi.createdAtServer);
if (!m_model->setRowData(row, esi))
{
qWarning() << "failed... ignored...";
break; // can only match 1 time
}
m_model->writeScriptsJson(); // throws
break; // can only match 1 time
}
}
catch(const std::runtime_error &err)
{
ERROR_LOG(m_logger) << "Downloading script FAILED: " << err.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Downloading script failed: %1").arg(QString::fromUtf8(err.what())));
return;
}
//m_model->setServerContent(m_lastServerContent);
//onSelectionChanged(); // aka. enaDisa
DEBUG_LOG(m_logger) << "Downloaded script successfully";
new LtsWidgets::LtsMessageDialogNew(this, tr("Downloaded script successfully"));
}
void ExportedScriptsDialog::removeScriptFromServer()
{
const QModelIndexList mil = m_ui->ltsDataView->selectedRows();
if (mil.size() != 1)
return;
const ExportedScriptInformation esi = m_model->getRowData(mil.first().row());
if (esi.uploaded != ExportedScriptInformation::UploadState::ThpAlreadyUploaded)
{
return;
}
try
{
RemoveProcess::remove(this, esi.id());
// NOTE: server will broadcast new content, so do nothing here
}
catch (const std::runtime_error &err)
{
ERROR_LOG(m_logger) << err.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Failed to remove script from server!"));
return;
}
new LtsWidgets::LtsMessageDialogNew(this, tr("Script was removed from server successfully"));
}
void ExportedScriptsDialog::removeScriptLocal()
{
const QModelIndexList mil = m_ui->ltsDataView->selectedRows();
if (mil.size() != 1)
return;
const ExportedScriptInformation esi = m_model->getRowData(mil.first().row());
if (!esi.local)
{
return;
}
try
{
const auto &esi = m_model->getRowData(mil.first().row());
const auto scriptId = esi.id();
if (scriptId.length() < 3)
{
ELOG << "scriptId is not in format 'n.n' - skipping for safety reasons - scriptId was: " << scriptId;
throw err("scriptId.length() < 3");
}
// rm from scripts.js and model
m_model->removeLocalScript(scriptId); // NOTE: doesn't remove from model if not local or available on server, so no need to call setServerContent
onSelectionChanged();
// rm from fs
QDir dir(scriptLocation(localThpDir(), scriptId));
if (!dir.exists())
{
ELOG << "script dir doesn't exist - ignoring - path was: " << dir.path();
}
else if (!dir.removeRecursively())
{
ELOG << "failed to remove script dir - path was: " << dir.path();
throw err("!dir.removeRecursively()");
}
}
catch (const std::runtime_error &err)
{
ERROR_LOG(m_logger) << err.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Failed to remove script locally!"));
return;
}
new LtsWidgets::LtsMessageDialogNew(this, tr("Script was locally removed successfully."));
}
void ExportedScriptsDialog::openScript()
{
const QModelIndexList mil = m_ui->ltsDataView->selectedRows();
if (mil.size() != 1)
return;
const ExportedScriptInformation esi = m_model->getRowData(mil.first().row());
if (!esi.local)
{
return;
}
const auto path(QDir(scriptHtmlLocation(localThpDir(), esi.id())).path());
const auto url(QUrl::fromLocalFile(path));
const bool openUrlResult = QDesktopServices::openUrl(url);
DEBUG_LOG(m_logger) << "path: " << path << " - url: " << url.toString() << " - success? " << openUrlResult;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,189 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_dialog.h 51804 2020-07-31 04:47:28Z fritsche_h $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_dialog.h $
* $Author: fritsche_h $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
#pragma once
#include "logger.h"
#include <Common2/tpm_exported_scripts_model.h>
#include <LtsWidgets/lts_dialog.h>
#include <QUrlQuery>
namespace Ui
{
class ExportedScriptsDialog2;
}
namespace NGL
{
class Compress;
}
namespace LtsSession
{
class LtsSession;
}
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkRequest;
namespace TakeHomePackageManager
{
class ProcessOverlay;
class Compressor : public QObject
{
Q_OBJECT
public:
Compressor();
void go(ProcessOverlay *processOverlay, QIODevice &out, const QString &sourceDir, const QString &destRootDir) noexcept(false);
signals:
void abort();
};
class NoticeDummy : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
signals:
void signal();
};
class HttpProcess : public QObject
{
Q_OBJECT
public:
HttpProcess(LtsWidgets::LtsDialog *dialog, const QString &statusMessage);
virtual ~HttpProcess();
signals:
void signalCancel();
protected:
void waitForAnswer(QNetworkReply *r, const int expectedResponseStatus) noexcept(false);
QNetworkReply *post(const QString &path, QIODevice *data, const QUrlQuery &q = QUrlQuery(), const QString &contentType = QStringLiteral("application/octet-stream"), const QMap<QString, QString> &additionalHeaders = {}) noexcept(false);
QNetworkReply *get(const QString &path, const QUrlQuery &q = QUrlQuery()) noexcept(false);
QNetworkReply *deleteResource(const QString &path, const QUrlQuery &q = QUrlQuery()) noexcept(false);
NGL::INGLLogger &m_logger;
ProcessOverlay *m_process;
private:
QNetworkAccessManager *m_nam;
};
class UploadProcess : public HttpProcess
{
Q_OBJECT
public:
static void upload(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const ExportedScriptInformation &esi) noexcept(false);
private:
UploadProcess(LtsWidgets::LtsDialog *dialog);
protected:
void go(const QString &rootFolder, const ExportedScriptInformation &esi) noexcept(false);
};
class DownloadProcess : public HttpProcess
{
Q_OBJECT
public:
static void download(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const QString &scriptId) noexcept(false);
private:
DownloadProcess(LtsWidgets::LtsDialog *dialog);
protected:
void go(LtsWidgets::LtsDialog *dialog, const QString &rootFolder, const QString &scriptId) noexcept(false);
};
class RemoveProcess : public HttpProcess
{
Q_OBJECT
public:
static void remove(LtsWidgets::LtsDialog *dialog, const QString &scriptId) noexcept(false);
private:
RemoveProcess(LtsWidgets::LtsDialog *dialog);
protected:
void go(const QString &scriptId) noexcept(false);
};
class ExportedScriptsDialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
explicit ExportedScriptsDialog(LtsSession::LtsSession *session, QWidget *parent = nullptr) noexcept(false);
~ExportedScriptsDialog();
void retranslate() override;
static QUrl forgeUrl(const QString &path, const QString &protocol = L("http"));
static QNetworkRequest forgeRequest(const QString &path, const QUrlQuery &q, const QString &contentType = QString());
private slots:
void uploadScript();
void downloadScript();
void removeScriptFromServer();
void removeScriptLocal();
void openScript();
void onSelectionChanged();
void _onSelectionChanged();
private:
enum class WsConnectionStatus
{
NotConnected,
Connecting,
Disconnected,
Error,
Connected,
};
void initModel() noexcept(false);
void setWsConnectionStatus(WsConnectionStatus status);
void setWsConnectionStatusLabel();
bool isOCApp() const;
private:
ExportedScriptsModel *m_model;
Ui::ExportedScriptsDialog2 *m_ui;
LtsSession::LtsSession *m_session;
QString m_lastServerContent;
WsConnectionStatus m_wsStatus { WsConnectionStatus::NotConnected };
NGL::INGLLogger &m_logger;
};
} // namespace TakeHomePackageManager
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExportedScriptsDialog2</class>
<widget class="QWidget" name="ExportedScriptsDialog2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>834</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>The following Scripts are already exported. You can remove them from your local hard disk or upload them to the server here.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Server connection status:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblWsStatus">
<property name="text">
<string notr="true">-</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="LtsDataView::LtsDataView" name="ltsDataView" native="true">
<property name="showColumnCount" stdset="0">
<bool>false</bool>
</property>
<property name="toolButtonsVisible" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsDataView::LtsDataView</class>
<extends>QWidget</extends>
<header>LtsDataView/lts_data_view.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,61 @@
/**
* @RDE_HEADER@
* GUI Plugin for THP2
* $Id: tpm_gui_plugin.cpp 48572 2019-10-02 11:47:04Z rudolphus_g $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_gui_plugin.cpp $
* $Author: rudolphus_g $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
#include "tpm2_gui_plugin.h"
#include "tpm2_exported_scripts_dialog.h"
#include "logger.h"
namespace TakeHomePackageManager
{
std::shared_ptr<GuiPluginBase::Interface> GuiPlugin::createGuiInterface(LtsSession::LtsSession *session)
{
return std::make_shared<InterfaceImpl>(session);
}
InterfaceImpl::InterfaceImpl(LtsSession::LtsSession *session)
: GuiPluginBase::Interface(session, getLogger("Plugins.Gui.Tpm"))
{}
bool InterfaceImpl::fetchInterfaces()
{
return true;
}
QString InterfaceImpl::menuGroup() const
{
return tr("Presentation");
}
QVector<LtsWidgets::PluginData> InterfaceImpl::getActions()
{
QVector<LtsWidgets::PluginData> ret;
LtsWidgets::PluginData plData;
plData.menuGroup = menuGroup();
if (m_session->app() == LtsAppDescriptor::App::Scripts)
plData.exercisePart = LtsWidgets::ExercisePart::eSpecial;
else
plData.exercisePart = LtsWidgets::ExercisePart::eAfterAction;
plData.identifier = QLatin1String("PresentationManagement.ScriptDialog.ManageExportedScripts");
plData.menuIcon = QIcon(QLatin1String("icon:export_documents.svg"));
plData.menuText = tr("Manage Exported Scripts");
// plData.operatorRight = OperatorRights::ScriptEditorEdit;
plData.createWidgetFunc = [this](QWidget *)
{
return LtsWidgets::createThrowableDialog<ExportedScriptsDialog>(m_session, nullptr);
};
ret.append(plData);
return ret;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,52 @@
/**
* @RDE_HEADER@
* GUI Plugin for THP2
* $Id: tpm_gui_plugin.h 49807 2020-02-06 09:53:16Z ehrlicher $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_gui_plugin.h $
* $Author: ehrlicher $
*
* Created by Mike Wille, ESZS, 2024-02-20
*/
#pragma once
#include <THP2/tpm2_gui_plugin_interface.h>
#include "logger.h"
#include <LtsSession/lts_session.h>
#include <PluginInterfaces/gui_plugin_interface.h>
namespace TakeHomePackageManager
{
class GuiPlugin final : public QObject, public GuiPluginBase::Plugin
{
Q_PLUGIN_METADATA(IID "gui:3:tpm2.man")
Q_INTERFACES(GuiPluginBase::Plugin)
Q_OBJECT
public:
GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return QLatin1String("TakeHomePackageManager2"); }
std::shared_ptr<GuiPluginBase::Interface> createGuiInterface(LtsSession::LtsSession *session) override;
bool availableInPartition(IDL::PartitionType partition) const override
{
return partition == IDL::PartitionType::Exercise;
}
};
class InterfaceImpl final : public QObject, public GuiPluginBase::Interface, public ForeignTpm2GuiInterface
{
Q_OBJECT
public:
explicit InterfaceImpl(LtsSession::LtsSession *session);
bool fetchInterfaces() override;
QString menuGroup() const override;
QVector<LtsWidgets::PluginData> getActions() override;
bool availableInPartition(IDL::PartitionType partition) const override { return partition == IDL::PartitionType::Exercise; }
private:
};
} // namespace TakeHomePackageManager