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

+10
View File
@@ -0,0 +1,10 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, ESZS, 2024-02-20
#
ADD_SUBDIRECTORY(THP2)
#ADD_SUBDIRECTORY(OldTHP)
+17
View File
@@ -0,0 +1,17 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, SLTE, 2020-03-23
#
ADD_SUBDIRECTORY(Common)
ADD_SUBDIRECTORY(ThpManagement)
ADD_SUBDIRECTORY(ThpUsbManagement)
ADD_INTERFACE_LIBRARY(TpmGuiPluginInterface
${CMAKE_CURRENT_SOURCE_DIR}/tpm_gui_plugin_interface.h
${CMAKE_CURRENT_SOURCE_DIR}/..
GuiPluginInterface
)
@@ -0,0 +1,43 @@
#
# $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
logger.cpp
logger.h
tpm_exported_scripts_model.cpp
tpm_exported_scripts_model.h
tpm_process_dialog.cpp
tpm_process_dialog.h
)
QT5_WRAP_UI(src
tpm_process_dialog.ui
)
NGL_ADD_LIBRARY2(WS ThpCommon SHARED ${src})
NGL_TARGET_INCLUDE_DIRECTORIES(ThpCommon
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../ # export file
)
NGL_TARGET_LINK_LIBRARIES(ThpCommon
Qt5::Core
Qt5::Concurrent
NGLLogger
LtsSession
AsmDataPluginInterface
ExmDataPluginInterface
#nglcompression
#nglsettings
#ngltools
#LtsSessionData
#httq
)
NGL_INSTALL_TARGET(ThpCommon)
@@ -0,0 +1,37 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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 $
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM.Common.";
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
+22
View File
@@ -0,0 +1,22 @@
/**
* @RDE_HEADER@
* Logging for CentralEffectSimulation
* $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 $
*/
#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,632 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.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_model.cpp $
* $Author: wille_m $
* Created by Gerlinde Rudolphus, NLE, 2019-03-25
*/
#include "tpm_exported_scripts_model.h"
#include <ngltools/ngltools_dtg.h>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace TakeHomePackageManager
{
ExportedScriptsModel::ExportedScriptsModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>(
{ tr("id"), tr("Exercise"), tr("Name"), tr("Created"), tr("Last downloaded"), tr("Location"), tr("Last uploaded"), tr("Server")}, parent)
, m_logger(getLogger("ExportedScriptsModel"))
{}
bool ExportedScriptsModel::init(const QString &rootDirectoryToScan)
{
std::shared_ptr<int> _(nullptr, [this](int *) { endResetModel(); }); // TODO: QScopeGuard
m_rootDirectoryToScan = rootDirectoryToScan;
beginResetModel();
m_vecData.clear();
QVector <int> localExercises;
DEBUG_LOG(m_logger) << "ExportedScriptsModel::init rootDirectoryToScan=" << rootDirectoryToScan
/*<< " directoryToScan=" << directoryToScan*/;
//clear(); multiple inits needed, don't clear
// die Skripte aller Uebungen auf der lokalen Workstation anzeigen.
// Die Uebungen sind in der Datei /tmp/THP-xxxxxxx/data.json im Array Exercises enthalten.
{
DEBUG_LOG(m_logger) << "Get all local exercises rootDirectoryToScan=" << rootDirectoryToScan ;
QFile file(rootDirectoryToScan + QDir::separator() + QStringLiteral("exercises.json"));
if (!file.open(QFile::ReadOnly)) // file only exists after a first download, but that's okay
WARNING_LOG(m_logger) << "Cannot open " << file.fileName() << " for read/write: " << file.errorString();
const QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
const QJsonObject obj = doc.object(); // okay to be empty
const QJsonArray arr = obj[QStringLiteral("Exercises")].toArray();
localExercises.reserve(arr.size());
for (const auto &ele : arr)
localExercises.append(ele.toInt());
}
WARNING_LOG(m_logger) << "local exericses: " << localExercises.count();
// Alle lokalen Übungen:
for (const int exId : qAsConst(localExercises))
{
const QString baseDir = exercisePath(rootDirectoryToScan, exId);
QFile file(baseDir + QDir::separator() + QStringLiteral("data.json"));
if (!file.open(QIODevice::ReadOnly))
{
WARNING_LOG(m_logger) << "Cannot open " << file.fileName() << " for reading: " << file.errorString() << " - not critical";
// There are no local scripts but maybe some are on the server
return true;
}
const QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
const QJsonObject obj = doc.object();
const QJsonArray presentations = obj[QStringLiteral("presentations")].toArray();
DEBUG_LOG(m_logger) << "exercise: " << exId << ", presentations: " << presentations.size();
for (const QJsonValue &val : presentations)
{
ExportedScriptInformation esi = ExportedScriptInformation::fromJson(val);
DEBUG_LOG(m_logger) << "obj=" << QJsonDocument(obj).toJson(QJsonDocument::Compact);
const QJsonObject presentationObj = val.toObject();
DEBUG_LOG(m_logger) << "presentationObj=" << QJsonDocument(presentationObj).toJson(QJsonDocument::Compact);
if (obj.contains(QStringLiteral("exerciseId")))
{
esi.exerciseId = obj[QStringLiteral("exerciseId")].toInt();
esi.exerciseName = obj[QStringLiteral("exerciseName")].toString();
if (presentationObj.contains(QStringLiteral("name")) )
esi.scriptName = presentationObj[QStringLiteral("name")].toString();
if (presentationObj.contains(QStringLiteral("displayName")) )
esi.scriptName = presentationObj[QStringLiteral("displayName")].toString();
}
esi.local = true;
DEBUG_LOG(m_logger) << "push_back "
<< " exportTime=" << esi.exportTime
<< " scriptId=" << esi.scriptId
<< " scriptName=" << esi.scriptName
<< " exerciseId=" << esi.exerciseId
<< " exerciseName=" << esi.exerciseName
<< " local=" << esi.local;
esi.uploaded = ThpUnknownUploadState;
esi.rootDirectory = rootDirectoryToScan; // to determine if this script is from THP-Download or THP-Export directory (for USB Management, which can copy from both dirs)
m_vecData.push_back(esi);
}
}
return true;
}
/**
* @brief ExportedScriptsModel::setServerContent fills the table with local and
* server scripts and deletes rows of deleted scripts.
* @param jsonArray of scripts from the server
* @return true if jsonArray could be parsed and was processed, false if
* jsonArray could not be parsed
*/
void ExportedScriptsModel::setServerContent(const QString &jsonArray)
{
if (jsonArray.isEmpty()) // no content received yet? no problem, just return
return;
/* these vectors are needed to compare which scripts are already
* on the server or are local or are both on the server and local
* e.g. if the script was downloaded after it was once uploaded
* to the server */
QMap<int, QVector<int>> exerciseContentScriptIds; // scripts from server - Key = ExerciseId, value = script ids of exercise
QMap<int, QVector<int>> exerciseScriptIds; // scripts from local data.json file - Key = ExerciseId, value = script ids of exercise
DEBUG_LOG(m_logger) << "ExportedScriptsModel::setServerContent jsonArray =" << jsonArray;
QJsonParseError err;
const QJsonArray presentations = QJsonDocument::fromJson(jsonArray.toUtf8(), &err).array();
DEBUG_LOG(m_logger) << "presentations.size() =" << presentations.size();
if (err.error != QJsonParseError::NoError)
{
ERROR_LOG(m_logger) << "json array invalid at offset " << err.offset << ": " << err.errorString();
return;
}
// put local scripts into local scripts list
for (const auto &data : qAsConst(m_vecData))
{
exerciseScriptIds[data.exerciseId].append(data.scriptId);
DEBUG_LOG(m_logger) << "exerciseId=" << data.exerciseId
<< " modelScriptIds.append: " << data.scriptId;
}
// put server scripts into server scripts list
for (const QJsonValue &val : presentations)
{
const ExportedScriptInformation esi = ExportedScriptInformation::fromJson(val, false, ThpAlreadyUploaded);
exerciseContentScriptIds[esi.exerciseId].append(esi.scriptId);
}
/* look for scripts that are on the server but also on the local side to
* either remove the corresponding row from the table if the script is not on
* the server and not set as local (that makes it non-local and non-existant
* on the server, which means it was at one point on the server, but was
* deleted from it after the server script list was received here some
* minutes ago) -
* or to add the not-uploaded state to the table row if it's local only
* (but what about local scripts that were uploaded? => This is handeled at
* the next main for-loop below)
*/
for (int i = m_vecData.size() - 1; i >= 0; --i) // scriptId ist nur im Modell aber nicht im content
{
const auto &rowData = m_vecData[i];
const QVector<int> contentScriptIds = exerciseContentScriptIds.value(rowData.exerciseId);
DEBUG_LOG(m_logger) << "m_vecData.at(" << i << ").scriptId=" << rowData.scriptId
<< " exerciseId=" << rowData.exerciseId
<< " contentScriptIds.contains=" << contentScriptIds.contains(rowData.scriptId)
<< " rowData.local=" << rowData.local
<< " contentScriptIds.size()=" << contentScriptIds.size();
if (!contentScriptIds.contains(rowData.scriptId))
{
// the script already in the table is not available on the server side
if (rowData.local == false)
{
/* script already in table is marked as non-loal, which means - in
* combination with the script not being on the server (anymore) - that
* the script was removed from the server after the server script list
* was received from the server and added to the table. So remove it
* from the table */
DEBUG_LOG(m_logger) << "removeRow(" << i << ")"
<< " ScriptId=" << rowData.scriptId
<< " ExerciseId=" << rowData.exerciseId;
removeRow(i);
}
else
{
/* otherwise, if it's marked as local, that means - in combination
* with he script not being on the server (anymore) - that the script
* is only locally available */
setUploadState(i, ThpUploadNotActive, 0, 0);
}
}
}
/* get all server scripts and add them to the table if they're not already in
* the table.LtsUtils::ProjectSettings *ps = L
* if the corresponding row already exist then set the uploaded-state to
* uploaded, because they came from the server list, which means they were
* obviously uploaded at some point */
for (const QJsonValue &val : presentations)
{
ExportedScriptInformation esi = ExportedScriptInformation::fromJson(val, false, ThpAlreadyUploaded);
QVector<int> modelScriptIds = exerciseScriptIds.value(esi.exerciseId);
DEBUG_LOG(m_logger) << " exportTime=" << esi.exportTime
<< " scriptId=" << esi.scriptId
<< " exerciseId=" << esi.exerciseId
<< " exerciseName=" << esi.exerciseName
<< " scriptName=" << esi.scriptName
<< " fileSize=" << esi.fileSize
<< " modelScriptIds.size=" << modelScriptIds.size();
// if a new script was added on the server and not available in this model already, then add it to the model
if (!modelScriptIds.contains(esi.scriptId))
{
DEBUG_LOG(m_logger) << "insertRow()";
insertRow(esi);
continue;
}
// update model
for (int i = 0; i < m_vecData.size(); i++)
{
const auto &rowData = m_vecData[i];
if (rowData.scriptId == esi.scriptId &&
rowData.exerciseId == esi.exerciseId)
{
DEBUG_LOG(m_logger) << "setUploadState i=" << i
<< " esi.scriptId=" << esi.scriptId
<< " esi.exerciseId=" << esi.exerciseId
<< " esi.fileSize=" << esi.fileSize
<< " esi.uploadTime=" << esi.uploadTime;
setUploadState(i, ThpAlreadyUploaded, esi.uploadTime, esi.fileSize);
break; // can only match 1 time
}
}
}
update();
return;
}
qint64 ExportedScriptsModel::downloadTime(const ExportedScriptInformation &val) const
{
if (!val.local) // can't be downloaded if it's not local
return 0;
QString filename(QStringLiteral("%1/THP-Download/Exercise_%2/%3/index.html").arg(QDir::tempPath()).arg(val.exerciseId).arg(val.scriptId));
DEBUG_LOG(m_logger) << "downloadTime: filename " << filename;
QFileInfo fi(filename);
if (!fi.exists())
return 0;
return fi.lastModified().toMSecsSinceEpoch();
}
QVariant ExportedScriptsModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
const ExportedScriptInformation &val = m_vecData[index.row()];
switch (role)
{
case Qt::FontRole:
{
if (!m_isOC
/* because non-oc app has no "downloaded" column, telling the
* user he has a newer version to download (by making the uploaded
* cell fat) makes no sense */
|| !val.local
/* non-downloaded scripts can't be older than the script on the server
* because there obviously is no downloaded (local) script */
|| index.column() != ColUploadedTime
|| val.uploadTime <= (downloadTime(val) + (10 * 1000) /* 10 second grace */))
/* if the script was downloaded from the server before the script was
* updated / uploaded to the server again, mark the uploaded time cell
* as fat to indicate the local, downloaded script is outdated */
break;
QFont font;
font.setBold(true);
return font;
}
case Qt::DisplayRole:
{
switch (index.column())
{
case ColId:
return val.scriptId;
case ColExercise:
return val.exerciseName;
case ColName:
return val.scriptName;
case ColCreated:
return QDateTime::fromMSecsSinceEpoch(val.exportTime);
case ColDownloaded:
{
qint64 dlTime = downloadTime(val);
if (dlTime == 0)
return QVariant();
return QDateTime::fromMSecsSinceEpoch(dlTime);
}
case ColUploadedTime:
return QDateTime::fromMSecsSinceEpoch(val.uploadTime);
case ColLocation:
return val.local ? tr("Local") : tr("Server");
case ColUploaded:
switch (val.uploaded)
{
case ThpConnectToHost:
return tr("Connect to host");
case ThpUploadActive:
return tr("Upload Active");
case ThpUploadError:
return tr("Upload Error");
case ThpUploadNotActive:
return tr("Not uploaded");
case ThpUnknownUploadState:
return tr("Unknown upload status");
case ThpAlreadyUploaded:
return tr("Already uploaded");
}
}
case Qt::UserRole:
{
return val.scriptId;
break;
}
break;
}
}
return QVariant();
}
bool ExportedScriptsModel::createDataJson(const QString &rootDirectory,
const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc,
int exerciseId) const // TODO: static, static logger
{
const QString baseDir = exercisePath(rootDirectory, exerciseId);
if (!QDir().mkpath(baseDir)) // NOTE: returns true if already exists
{
ERROR_LOG(m_logger) << "Failed to create path " << baseDir;
return false;
}
QFile file(baseDir + QStringLiteral("data.json"));
DEBUG_LOG(m_logger) << "createDataJson=" << file.fileName();
if (!file.open(QFile::ReadWrite))
{
QString msg(tr("Could not open data file in script directory: %1").arg(file.errorString()));
ERROR_LOG(m_logger) << msg << " - " << file.fileName();
return false;
}
QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
if (obj[QStringLiteral("exerciseId")].toInt() == exerciseId)
{
DEBUG_LOG(m_logger) << baseDir << " exists for exerciseId " << QString::number(exerciseId);
return true;
}
const ExerciseManagement::Exercise exercise = exmDtc->exercise(exerciseId);
const AssetManagement::TrainingArea ta = asmDtc->trainingArea(exercise.trainingAreaId());
obj[QStringLiteral("title")] = QString::fromStdString(exercise.name());
obj[QStringLiteral("exerciseId")] = exercise.id();
obj[QStringLiteral("exerciseName")] = QString::fromStdString(exercise.name());
obj[QStringLiteral("exerciseArea")] = QString::fromStdString(ta.name());
obj[QStringLiteral("cycleTime")] = QString(QDateTime::fromMSecsSinceEpoch(exercise.beginTime()).toString() +
QStringLiteral(" - ") + QDateTime::fromMSecsSinceEpoch(exercise.endTime()).toString());
obj[QStringLiteral("exerciseStart")] = exercise.beginTime();
obj[QStringLiteral("exerciseEnd")] = exercise.endTime();
obj[QStringLiteral("exercisingForces")] = QString::fromStdString(exercise.forces());
obj[QStringLiteral("exercisingPurpose")] = QString::fromStdString(exercise.description());
file.resize(0);
file.write(QJsonDocument(obj).toJson());
return true;
}
void ExportedScriptsModel::addDownloadData(const ExportedScriptInformation &esi)
{
// Add exercise to ExportRootPath
{
// modify exercises.json
DEBUG_LOG(m_logger) << "Add exercise to ExportRootPath";
if (!QDir(esi.rootDirectory).exists())
{
ERROR_LOG(m_logger) << "Cannot modify exercises.json as the info's root directory '"<<esi.rootDirectory<<"' does not exist!";
return;
}
QFile file(esi.rootDirectory + QDir::separator() + QStringLiteral("exercises.json"));
if (!file.open(QFile::ReadWrite))
{
WARNING_LOG(m_logger) << "Can not open " << file.fileName() << " for read/write: " << file.errorString();
return;
}
const QString key = QStringLiteral("Exercises");
QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
QJsonArray arr = obj.value(key).toArray();
if (!arr.contains(esi.exerciseId))
{
DEBUG_LOG(m_logger) << "arr.append Exercise=" << esi.exerciseId;
arr.append(esi.exerciseId);
}
obj.insert(key, arr);
file.resize(0);
file.write(QJsonDocument(obj).toJson());
INFO_LOG(m_logger) << " file.write " << QJsonDocument(obj).toJson();
file.close();
}
// NOTE: mkdir actually not necessary because the decompressed bzip file of the script already created the exercise directory
const QString baseDir = exercisePath(esi.rootDirectory, esi.exerciseId);
if (!QDir().mkpath(baseDir)) // NOTE: returns true if dir already exists
{
ERROR_LOG(m_logger) << "Cannot create destination path " << baseDir;
return;
}
// modify data.json
QFile file(baseDir + QDir::separator() + QStringLiteral("data.json"));
DEBUG_LOG(m_logger) << "File= " << file.fileName();
if (!file.open(QFile::ReadWrite))
{
WARNING_LOG(m_logger) << "Can not open " << file.fileName() << " for read/write: " << file.errorString();
return;
}
const QString keyArray = QStringLiteral("presentations");
const QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
QJsonArray arr = obj.value(keyArray).toArray();
bool exported = false;
DEBUG_LOG(m_logger) << "arr.size=" << arr.size();
for (int i = 0; i < arr.size(); ++i)
{
auto arresi = ExportedScriptInformation::fromJson(arr.at(i));
if (arresi.scriptId == esi.scriptId &&
arresi.exerciseId == esi.exerciseId)
{
arresi.jsonPath = esi.jsonPath;
arr[i] = esi.toJson();
exported = true;
}
}
if (!exported)
{
DEBUG_LOG(m_logger) << "info scriptId=" << esi.scriptId
<< " Name=" << esi.scriptName
<< " Exercise=" << esi.exerciseName;
arr.append(esi.toJson());
}
obj.insert(keyArray, arr);
auto content = QJsonDocument(obj).toJson();
DEBUG_LOG(m_logger) << "content =" << content;
file.resize(0);
file.write(content);
file.close();
}
bool ExportedScriptsModel::setLocalState(const int row, const bool localHost)
{
ExportedScriptInformation data = getRowData(row);
data.local = localHost;
//data.exportTime = exportTime;
DEBUG_LOG(m_logger) << "setLocalState row=" << row
<< " scriptId=" << data.scriptId
<< " data.scriptName=" << data.scriptName
<< " Exercise=" << data.exerciseName;
bool result = setRowData(row, data);
if (result && localHost && m_rootDirectoryToScan.contains(QStringLiteral("Download")))
{
data.rootDirectory = m_rootDirectoryToScan;
setRowData(row, data); //again for new download path mabe not needed
addDownloadData(data);
}
return result;
}
bool ExportedScriptsModel::setUploadState(const int row,
const UploadState uploadState,
const qint64 uploadTime,
const qint64 fileSize)
{
ExportedScriptInformation data = getRowData(row);
data.uploaded = uploadState;
data.uploadTime = uploadTime;
data.fileSize = fileSize;
return setRowData(row, data);
}
bool ExportedScriptsModel::removeRow(int row)
{
ExportedScriptInformation data = getRowData(row);
if (!LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>::removeRow(row))
return false;
QString baseDir = exercisePath(data.rootDirectory, data.exerciseId);
QString filename = baseDir + QStringLiteral("data.json");
DEBUG_LOG(m_logger) << "filename= '" << filename << "'";
QFile file(baseDir + QStringLiteral("data.json"));
if (!file.open(QIODevice::ReadWrite))
{
DEBUG_LOG(m_logger) << "!file.open '" << filename << "'"
<< " no local scripts";
return true;
}
const QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
const int exId = obj.value(QStringLiteral("exerciseId")).toInt();
QJsonArray arr;
for (const auto &esi : qAsConst(m_vecData))
{
if (esi.exerciseId == exId)
arr += esi.toJson();
}
obj[QStringLiteral("presentations")] = arr;
file.resize(0);
auto content = QJsonDocument(obj).toJson();
DEBUG_LOG(m_logger) << "content =" << content;
return file.write(QJsonDocument(obj).toJson());
}
QString ExportedScriptsModel::exercisePath(const QString &rootDirectory, const int exerciseId) const
{
QString path = QStringLiteral("%1/Exercise_%2/").arg(rootDirectory).arg(exerciseId);
#ifndef WIN32
path.prepend(QStringLiteral("/"));
#endif
return path;
}
//
// ExportedScriptInformation
//
QJsonValue ExportedScriptInformation::toJson() const
{
return QJsonObject({
{ QStringLiteral("displayName"), scriptName },
{ QStringLiteral("path"), jsonPath },
{ QStringLiteral("scriptId"), scriptId },
{ QStringLiteral("exportTime"), exportTime },
{ QStringLiteral("fileSize"), fileSize },
{ QStringLiteral("uploadTime"), uploadTime },
{ QStringLiteral("exerciseName"), exerciseName },
{ QStringLiteral("exerciseId"), exerciseId }
});
}
ExportedScriptInformation ExportedScriptInformation::fromJson(const QJsonValue &val,
const bool local,
const UploadState uploadState)
{
const QJsonObject obj = val.toObject();
ExportedScriptInformation esi;
esi.local = local;
esi.scriptName = obj[QStringLiteral("displayName")].toString();
if (esi.scriptName.isEmpty())
esi.scriptName = obj[QStringLiteral("name")].toString();
esi.jsonPath = obj[QStringLiteral("path")].toString();
esi.scriptId = obj[QStringLiteral("scriptId")].toInt();
esi.exportTime = qRound64(obj[QStringLiteral("exportTime")].toDouble());
esi.fileSize = obj[QStringLiteral("fileSize")].toInt();
esi.uploaded = uploadState;
esi.uploadTime = qRound64(obj[QStringLiteral("uploadTime")].toDouble());
esi.exerciseName = obj[QStringLiteral("exerciseName")].toString();
esi.exerciseId = obj[QStringLiteral("exerciseId")].toInt();
Q_ASSERT(esi.scriptId != -1);
Q_ASSERT(esi.exerciseId != -1);
if (esi.scriptId != -1 && esi.exerciseId != -1)
esi.valid = true;
return esi;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,99 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
* Created by Gerlinde Rudolphus, NLE, 2019-03-25
*/
#pragma once
#include <logger.h>
#include <LtsWidgets/lts_abstract_table_model.h>
#include <Plugins/ExerciseManagement/exm_data_plugin_interface.h>
#include <Plugins/AssetManagement/asm_data_plugin_interface.h>
#include <Common/thpcommon_export.h>
namespace TakeHomePackageManager
{
enum UploadState
{
ThpUnknownUploadState,
ThpUploadNotActive,
ThpConnectToHost,
ThpUploadActive,
ThpAlreadyUploaded,
ThpUploadError,
};
struct ExportedScriptInformation
{
bool valid { false };
QString exerciseName;
QString scriptName;
QString jsonPath;
QString rootDirectory; // temp
qint64 exerciseId = -1;
qint64 exportTime = 0;
qint64 fileSize = 0;
bool local = true; // On local Host
UploadState uploaded = ThpUnknownUploadState;
qint64 uploadTime = 0;
int scriptId = -1;
int id() const { return scriptId; }
QJsonValue toJson() const;
static ExportedScriptInformation fromJson(const QJsonValue &val,
const bool local = true,
const UploadState uploadStates = ThpUnknownUploadState);
};
class THPCOMMON_EXPORT ExportedScriptsModel : public LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>
{
Q_OBJECT
public:
enum Columns
{
ColId,
ColExercise,
ColName,
ColCreated,
ColDownloaded,
ColLocation,
ColUploadedTime,
ColUploaded,
};
explicit ExportedScriptsModel(QObject *parent = nullptr);
bool init(const QString &rootDirectoryToScan);
void setServerContent(const QString &jsonArray);
void setOC(bool isOC) { m_isOC = isOC; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool removeRow(int row) override;
bool setLocalState(const int row, const bool localHost);
bool setUploadState(const int row, const UploadState uploadState, const qint64 uploadTime, const qint64 fileSize);
bool createDataJson(const QString &rootDirectory, const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc,
int exerciseId) const;
void addDownloadData(const ExportedScriptInformation &esi);
private:
QString exercisePath(const QString &rootDirectory, const int exerciseId) const;
qint64 downloadTime(const ExportedScriptInformation &val) const;
private:
bool m_isOC { false };
QString m_rootDirectoryToScan;
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,66 @@
#include "tpm_process_dialog.h"
#include "ui_tpm_process_dialog.h"
namespace TakeHomePackageManager
{
ProcessOverlay::ProcessOverlay(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::ProcessOverlay)
{
m_ui->setupUi(this);
connect(m_ui->pbCancel, &QPushButton::clicked,
this, [this]()
{
m_ui->pbCancel->setEnabled(false);
emit signalCancel();
});
}
ProcessOverlay::~ProcessOverlay()
{
delete m_ui;
}
void ProcessOverlay::setCancelEnabled(bool enabled)
{
m_ui->pbCancel->setEnabled(enabled);
}
void ProcessOverlay::setStatus(const QString &text, qint64 totalFiles, qint64 curFile, qint64 totalSize, qint64 curPos, const QString &curName)
{
if (!text.isEmpty()) // only set new text if not empty (we dont want to send the text on every update..
m_ui->lblStatus->setText(text);
if (!curName.isEmpty()) // only set new text if not empty (we dont want to send the text on every update..
m_ui->lblFilename->setText(curName);
double progressBytes = (totalSize == 0) ? (double)0 : ((double)curPos / (double)totalSize);
double progressFiles = (((double)curFile - 1) / (double)totalFiles);
double progressTotal = progressFiles + (progressBytes / (double)totalFiles);
progressBytes = qRound(progressBytes * 100);
progressFiles = qRound(progressFiles * 100);
progressTotal = qRound(progressTotal * 100);
m_ui->pbarFiles->setValue(progressFiles);
m_ui->pbarBytes->setValue(progressBytes);
m_ui->pbarProgress->setValue(progressTotal);
QLocale l;
if (curFile == 0)
m_ui->lblFiles->setText(QStringLiteral(" - "));
else
m_ui->lblFiles->setText(QStringLiteral("%1 / %2").arg(l.toString(curFile)).arg(l.toString(totalFiles)));
if (curPos == 0)
m_ui->lblBytes->setText(QStringLiteral(" - "));
else
m_ui->lblBytes->setText(QStringLiteral("%1 / %2").arg(l.toString(curPos)).arg(l.toString(totalSize)));
}
} // namespace TakehomePackageManager
@@ -0,0 +1,32 @@
#pragma once
#include <Common/thpcommon_export.h>
#include <QWidget>
namespace Ui
{
class ProcessOverlay;
}
namespace TakeHomePackageManager
{
class THPCOMMON_EXPORT ProcessOverlay : public QWidget
{
Q_OBJECT
public:
ProcessOverlay(QWidget *parent = nullptr);
~ProcessOverlay();
void setStatus(const QString &text = QString(), qint64 totalFiles = 1, qint64 curFile = 1, qint64 totalSize = 0, qint64 curPos = 0, const QString &curName = QString());
void setCancelEnabled(bool enabled = true);
signals:
void signalCancel();
private:
Ui::ProcessOverlay *m_ui;
bool m_decompressing { false };
};
} // namespace TakehomePackageManager
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessOverlay</class>
<widget class="QWidget" name="ProcessOverlay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>220</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>700</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="1">
<widget class="QLabel" name="lblBytes">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Progress</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QProgressBar" name="pbarFiles">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="lblFiles">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblStatus">
<property name="text">
<string notr="true">Preparing ...</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Files</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QProgressBar" name="pbarBytes">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="7" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="pbCancel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0" colspan="3">
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblFilename">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Bytes</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QProgressBar" name="pbarProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="Line" name="line_2">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,48 @@
#
# $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 Gerlinde Rudolphus, NLE, 2019-03-25
#
#
# Common
#
SET(thp_SRCS
tpm_gui_plugin.cpp
tpm_gui_plugin.h
tpm_exported_scripts_dialog.cpp
tpm_exported_scripts_dialog.h
logger.cpp
logger.h
)
QT5_WRAP_UI(thp_SRCS
tpm_exported_scripts_dialog.ui
)
#TODO: auto-remove "libTakeHomePackageManagerGuiPlugin.so" from lib as this lib replaces it
NGL_ADD_LIBRARY2(WS ThpManagementGuiPlugin MODULE ${thp_SRCS})
NGL_TARGET_LINK_LIBRARIES(ThpManagementGuiPlugin
ThpCommon
TpmGuiPluginInterface
Qt5::Widgets
Qt5::Network
Qt5::WebSockets
NGLLogger
LtsWidgets
LtsSession
LtsDataView
nglcompression
nglsettings
ngltools
AsmDataPluginInterface
ExmDataPluginInterface
LtsSessionData
httq
)
NGL_INSTALL_TARGET(ThpManagementGuiPlugin)
@@ -0,0 +1,37 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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 $
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM.";
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,22 @@
/**
* @RDE_HEADER@
* Logging for CentralEffectSimulation
* $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 $
*/
#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
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,214 @@
/**
* @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 Gerlinde Rudolphus, NLE, 2019-03-25
* Rebuilt to use httq by Mike Wille, NLE, 2020-07-07
*/
#pragma once
#include "logger.h"
#include <Common/tpm_exported_scripts_model.h>
#include <LtsWidgets/lts_dialog.h>
#include <QUrlQuery>
namespace Ui
{
class ExportedScriptsDialog;
}
namespace NGL
{
class Compress;
}
namespace LtsSession
{
class LtsSession;
}
class QTemporaryFile;
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkRequest;
class QFileSystemWatcher;
namespace TakeHomePackageManager
{
class ProcessOverlay;
class HttpProcess : public QObject
{
Q_OBJECT
public:
HttpProcess(ExportedScriptInformation &&esi, QObject *parent);
virtual ~HttpProcess();
ProcessOverlay *process() const { return m_process; }
signals:
void signalReadyRead();
//void signalFailed(int status, const QString &msg);
void signalSuccess();
void signalTimeout();
void signalCancel();
void signalError();
private slots:
void slotReadyRead();
void slotError();
void slotSuccess();
protected slots:
void stopTimeout();
void resetTimeout();
private:
void doConnections(QNetworkReply *r);
protected:
// do not connect to QNetworkReply's readyRead, connect to this classe's signalReadyRead
QNetworkReply *post(const QString &path, QIODevice *data, const QUrlQuery &q = QUrlQuery(), const QString &contentType = QStringLiteral("application/octet-stream"));
QNetworkReply *get(const QString &path, const QUrlQuery &q = QUrlQuery());
QNetworkReply *deleteResource(const QString &path, const QUrlQuery &q = QUrlQuery());
ExportedScriptInformation m_esi;
bool m_isError { false };
NGL::INGLLogger &m_logger;
ProcessOverlay *m_process;
bool m_wasCanceled { false };
bool m_isTimeout { false };
private:
static QNetworkRequest forgeRequest(const QString &path, const QUrlQuery &q, const QString &contentType = QString());
QNetworkAccessManager *m_nam;
QTimer *m_timeout;
};
class UploadProcess : public HttpProcess
{
Q_OBJECT
public:
UploadProcess(const QString &rootFolder, ExportedScriptInformation &&esi, QObject *parent);
signals:
//void signalDone();
private slots:
void compressionDone();
private:
void abortCompress();
QTemporaryFile *m_tempFile;
NGL::Compress *m_compress;
QString m_rootDir;
};
class DownloadProcess : public HttpProcess
{
Q_OBJECT
public:
DownloadProcess(const QString &rootFolder, int modelRowId, ExportedScriptInformation &&esi, QObject *parent);
int modelRowId() const { return m_modelRowId; }
private slots:
void downloadFinished();
signals:
void signalDone();
private:
QTemporaryFile *m_tempFile;
NGL::Compress *m_compress { nullptr };
int m_modelRowId { -1 };
QString m_rootFolder;
};
class RemoveProcess : public HttpProcess
{
Q_OBJECT
public:
RemoveProcess(int modelRowId, ExportedScriptInformation &&esi, QObject *parent);
int modelRowId() const { return m_modelRowId; }
signals:
void signalDone();
private:
int m_modelRowId { -1 };
};
class ExportedScriptsDialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
explicit ExportedScriptsDialog(QWidget *parent = nullptr);
~ExportedScriptsDialog();
void retranslate() override;
bool init(LtsSession::LtsSession *session);
static QString getContentFolder(const QString &rootFolder, int exerciseId);
private slots:
void uploadScript();
void downloadScript();
void removeScriptFromServer();
void removeScriptLocal();
void openScript();
void onSelectionChanged();
private:
enum class WsConnectionStatus
{
NotConnected,
Connecting,
Disconnected,
Error,
Connected,
};
void startUpload();
QString getContentFolder(int exerciseId) const;
QString getRootFolder() const;
bool initModel();
void setWsConnectionStatus(WsConnectionStatus status);
void setWsConnectionStatusLabel();
private:
ExportedScriptsModel *m_model;
Ui::ExportedScriptsDialog *m_ui;
LtsSession::LtsSession *m_session;
int m_exerciseId;
QFileSystemWatcher *m_fsWatcher;
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>ExportedScriptsDialog</class>
<widget class="QWidget" name="ExportedScriptsDialog">
<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">
<property name="showColumnCount" stdset="0">
<bool>false</bool>
</property>
<property name="toolButtonsVisible">
<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,60 @@
/**
* @RDE_HEADER@
* GUI Plugin for ExerciseManangement
* $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 Christian Ehrlicher, SLSE, 2016-02-29
*/
#include "tpm_gui_plugin.h"
#include "tpm_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::initDialog(new ExportedScriptsDialog(), m_session);
};
ret.append(plData);
return ret;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,51 @@
/**
* @RDE_HEADER@
* Gui Plugin for MarkerManagement
* $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 Christian Ehrlicher, SLSE, 2016-02-29
*/
#pragma once
#include <OldTHP/tpm_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:tpm.man")
Q_INTERFACES(GuiPluginBase::Plugin)
Q_OBJECT
public:
GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return QLatin1String("TakeHomePackageManager"); }
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 ForeignTpmGuiInterface
{
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
@@ -0,0 +1,44 @@
#
# $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
tpm_usb_gui_plugin.cpp
tpm_usb_gui_plugin.h
tpm_usb_dialog.cpp
tpm_usb_dialog.h
logger.cpp
logger.h
)
QT5_WRAP_UI(src
tpm_usb_dialog.ui
)
NGL_ADD_LIBRARY2(WS ThpUsbManagementGuiPlugin MODULE ${src})
NGL_TARGET_LINK_LIBRARIES(ThpUsbManagementGuiPlugin
ThpCommon
TpmGuiPluginInterface
Qt5::Widgets
Qt5::Network
Qt5::WebSockets
NGLLogger
LtsWidgets
LtsSession
LtsDataView
nglcompression
nglsettings
ngltools
LtsSessionData
# for creating data.json
AsmDataPluginInterface
ExmDataPluginInterface
)
NGL_INSTALL_TARGET(ThpUsbManagementGuiPlugin)
@@ -0,0 +1,37 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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 $
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM.USB.";
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,22 @@
/**
* @RDE_HEADER@
* Logging for CentralEffectSimulation
* $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 $
*/
#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
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,138 @@
/**
* @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, SLTE, 2020-03-23
*/
#pragma once
#include "logger.h"
#include <LtsWidgets/lts_dialog.h>
#include <QFileInfoList>
namespace Ui
{
class USBDialog;
}
namespace LtsSession
{
class LtsSession;
}
namespace TakeHomePackageManager
{
class ExportedScriptsModel;
enum eLayerType
{
elevation,
map,
sceneObject,
scene
};
struct layerMappinEntry
{
eLayerType type;
QString description;
int maxScale;
int minScale;
QString name;
QString url;
public:
layerMappinEntry()
: type(eLayerType::map), description(QStringLiteral("NOTSET")), maxScale(125), minScale(-1), name(QStringLiteral("_name")), url(QStringLiteral("_url"))
{
}
layerMappinEntry(eLayerType _type, QString _description, int _maxScale, int _minScale, QString _name, QString _url)
: type(_type), description(_description), maxScale(_maxScale), minScale(_minScale), name(_name), url(_url)
{
}
};
class USBDialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
/**
* @brief USBDialog => WARNING: throws! catch exception!
* @param session
* @param parent
*/
explicit USBDialog(LtsSession::LtsSession *session, QWidget *parent = nullptr);
~USBDialog();
void retranslate() override;
static QString getContentFolder(const QString &rootFolder, int exerciseId);
QString getContentFolder(const QString &rootFolder) const;
static QString exerciseDirectoryName(int exerciseId);
static QString getScriptFolder(const QString &rootFolder, int exerciseId, int scriptId);
signals:
void nextMap();
private slots:
void slotSelectionChanged();
void slotCopy();
void slotCopyMaps();
void slotCopyWinBin();
void slotCopyWinBinP2();
void slotCopyWinBinP3();
void slotRemove();
void slotRemoveMaps();
void slotChangePath();
void onNextMap();
void generateLayerMappingXML();
private:
enum class What
{
ScriptWasDeletedFromExercise,
ExerciseDirectoryWasDeleted,
NewExerciseDirectoryWasCreated,
ScriptWasCopiedToExercise,
};
void adjustJsonFiles(What what);
void initModel();
void initUsbModel();
void scriptDeleted(int scriptId);
void scriptCopied(int scriptId);
QFileInfoList prepareFileInfoListForFolder(QString src, QFileInfoList fil);
QString getContentFolder(int exerciseId) const;
QString getContentFolder() const;
QString getRootFolder(bool forceDownloadDir = false) const;
QString exerciseDirectoryName() const;
QString getScriptFolder(const QString &rootFolder, int scriptId) const;
QString getScriptFolder(int scriptId) const;
ExportedScriptsModel *m_localThpModel;
ExportedScriptsModel *m_usbThpModel;
Ui::USBDialog *m_ui;
LtsSession::LtsSession *m_session;
int m_exerciseId;
int m_paths;
QMap<QString, QFileInfoList> m_files;
QString m_usbRoot;
NGL::INGLLogger &m_logger;
int m_finished_mapCopys;
};
} // namespace TakeHomePackageManager
@@ -0,0 +1,274 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>USBDialog</class>
<widget class="QWidget" name="USBDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1081</width>
<height>541</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QGroupBox" name="verticalGroupBox">
<property name="title">
<string>Local</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>THPs</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="tvLocal">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbCopy">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy selected THPs to USB</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_4">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Maps</string>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="tvLocalMaps">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbCopyMaps">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy selected Maps to USB</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="verticalGroupBox_2">
<property name="title">
<string>On USB</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Path:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPath">
<property name="text">
<string>(none)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbCHange">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Change path</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="QSplitter" name="splitter_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>THPs</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tvUSB">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbRemove">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove selected THPs from USB</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_5">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Maps</string>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="tvUSBMaps">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbRemoveMaps">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove selected Maps from USB</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,102 @@
/**
* @RDE_HEADER@
* GUI Plugin for ExerciseManangement
* $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, SLTE, 2020-03-23
*/
#include "tpm_usb_gui_plugin.h"
#include "tpm_usb_dialog.h"
#include "logger.h"
#include <QDebug>
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("Plugin"))
{
}
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("THP.USB.Dialog");
plData.menuIcon = QIcon(QLatin1String("icon:export_documents.svg"));
plData.menuText = tr("USB Management");
// plData.operatorRight = OperatorRights::ScriptEditorEdit;
plData.createWidgetFunc = [this](QWidget *)
{
return LtsWidgets::createThrowableDialog<USBDialog>(m_session);
};
ret.append(plData);
return ret;
}
} // namespace TakeHomePackageManager
//
@@ -0,0 +1,52 @@
/**
* @RDE_HEADER@
* Gui Plugin for MarkerManagement
* $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, SLTE, 2020-03-23
*/
#pragma once
#include <OldTHP/tpm_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:tpm.usb")
Q_INTERFACES(GuiPluginBase::Plugin)
Q_OBJECT
public:
GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return QLatin1String("ThpUsbManager"); }
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 ForeignTpmGuiInterface
{
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
@@ -0,0 +1,25 @@
/**
* @RDE_HEADER@
* HMI Plugin for Exercisemanagemt (Interface for access from other Plugins)
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
#pragma once
#include <QString>
namespace TakeHomePackageManager
{
class ForeignTpmGuiInterface
{
public:
ForeignTpmGuiInterface() = default;
virtual ~ForeignTpmGuiInterface() = default;
static QString pluginName() { return QStringLiteral("TpmGuiPlugin"); }
};
} // namespace TakeHomePackageManager
+18
View File
@@ -0,0 +1,18 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, ESZS, 2024-02-20
#
ADD_SUBDIRECTORY(Common2Core)
ADD_SUBDIRECTORY(Common2)
ADD_SUBDIRECTORY(Thp2Management)
ADD_SUBDIRECTORY(Thp2UsbManagement)
ADD_INTERFACE_LIBRARY(Tpm2GuiPluginInterface
${CMAKE_CURRENT_SOURCE_DIR}/tpm2_gui_plugin_interface.h
${CMAKE_CURRENT_SOURCE_DIR}/..
GuiPluginInterface
)
@@ -0,0 +1,46 @@
#
# $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, 2024-01-11
#
SET(src
logger.cpp
logger.h
tpm_exported_scripts_model.cpp
tpm_exported_scripts_model.h
tpm_process_dialog.cpp
tpm_process_dialog.h
tpm_topics_model.cpp
tpm_topics_model.h
tpm_file_copy.cpp
tpm_file_copy.h
)
QT5_WRAP_UI(src
tpm_process_dialog.ui
)
NGL_ADD_LIBRARY2(WS Thp2Common SHARED ${src})
NGL_TARGET_COMPILE_DEFINITIONS(Thp2Common PRIVATE L=QStringLiteral)
NGL_TARGET_INCLUDE_DIRECTORIES(Thp2Common
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../ # export file
)
NGL_TARGET_LINK_LIBRARIES(Thp2Common
PUBLIC
Thp2CommonCore
PRIVATE
Qt5::Core
Qt5::Concurrent
NGLLogger
LtsSession
LtsUtilsCoreHeaderOnly
TopicEditorBase # topic types
)
NGL_INSTALL_TARGET(Thp2Common)
+39
View File
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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, SLTE, 2024-01-11
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM2.Common.";
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
+24
View File
@@ -0,0 +1,24 @@
/**
* @RDE_HEADER@
* Logging for CentralEffectSimulation
* $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, SLTE, 2024-01-11
*/
#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,389 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.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_model.cpp $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include "tpm_exported_scripts_model.h"
#include <ngltools/ngltools_dtg.h>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
#include <QFileSystemWatcher>
#include <QMutableVectorIterator>
namespace TakeHomePackageManager
{
ExportedScriptsModel::ExportedScriptsModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>(
{ tr("id"), tr("Exercise"), tr("Name"), tr("Local version"), tr("Server version"), tr("Location"), tr("Upload status")}, parent)
, m_logger(getLogger("ExportedScriptsModel"))
{}
//ExportedScriptsModel::~ExportedScriptsModel()
//{
// delete m_scriptsJsParser;
//}
void ExportedScriptsModel::init(const QString &rootDirectoryToScan, bool checkForScriptsJsChanges) noexcept(false)
{
m_rootDirectoryToScan = rootDirectoryToScan;
beginResetModel();
std::shared_ptr<int> _(nullptr, [this](int *) { endResetModel(); }); // TODO: QScopeGuard
m_vecData = ScriptsJsParser(m_rootDirectoryToScan).getExportedScriptInformations().values().toVector(); // -.-
if (checkForScriptsJsChanges)
{
delete m_watcher;
m_watcher = new QFileSystemWatcher({ scriptsJsLocation(m_rootDirectoryToScan) }, this);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this](){try{reloadScriptsJs();}catch(...){}});
}
}
void ExportedScriptsModel::reloadScriptsJs() noexcept(false) // setServerContent, but from scripts.js
{
qWarning() << "RELOAD!!";
// scripts.js changed - check if something was changed, added or removed
const auto scriptsJsParser = ScriptsJsParser(m_rootDirectoryToScan);
const auto esiMap = scriptsJsParser.getExportedScriptInformations();
// check if model has local scripts that scriptsJs doesn't have
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (!currentEsi.local) // setServerContent added it and never was in scripts.js
{
continue;
}
// is local
if (esiMap.contains(currentEsi.id())) // still in scripts.js - do nothing
{
continue;
}
// not in scripts.js anymore
if (currentEsi.isUploaded()) // not in scripts.js anymore - set non-local / server-only
{
// not in scripts.js anymore, but still on server - don't remove but say it's not local anymore
currentEsi.unsetLocal();
setRowData(row, currentEsi);
qWarning() << "RELOAD!! UPDATED TO NON-LOCAL!" << currentEsi.id();
continue;
}
// ... also not uploaded
qWarning() << "RELOAD!! REMOVED!" << currentEsi.id();
removeRow(row);
}
const auto esis = esiMap.values().toVector();
for (const auto &localEsi : esis)
{
bool found = false;
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (currentEsi.id() != localEsi.id())
{
continue;
}
// changed
if (localEsi.createdAt > currentEsi.createdAt && localEsi.createdAt > currentEsi.createdAtServer) // is topic in scripts.js newer than currently in model AND in server?
{
currentEsi.createdAt = localEsi.createdAt;
currentEsi.exercise = localEsi.exercise;
currentEsi.script = localEsi.script;
qWarning() << "RELOAD!! UPDATED DATA FROM SERVER!" << currentEsi.id();
setRowData(row, currentEsi);
}
found = true;
break;
}
if (found)
{
continue;
}
// added
qWarning() << "RELOAD!! ADDED!" << localEsi.id();
insertRow(localEsi);
}
}
void ExportedScriptsModel::setServerContent(const QString &jsonObject) // reloadScriptsJs, but from server
{
if (jsonObject.isEmpty()) // no content received yet? no problem, just return
return;
try
{
const QJsonObject scripts(JsonObject::fromJson(jsonObject.toUtf8()).object());
// check if model has server scripts that server doesn't have anymore
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (scripts.contains(currentEsi.id()))
{
continue;
}
// script is in model and not on the server
if (currentEsi.local) // is still there locally, just set it to not uploaded
{
currentEsi.unsetUploaded();
setRowData(row, currentEsi);
continue;
}
// not on server and not local ...
removeRow(row);
}
// not in model
for (const QJsonValue &val : scripts)
{
const auto serverEsi(ExportedScriptInformation::fromJson(val, false /* not local - on server */, ExportedScriptInformation::UploadState::ThpAlreadyUploaded));
bool found = false;
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (currentEsi.id() != serverEsi.id())
{
continue;
}
// changed
if (serverEsi.createdAt > currentEsi.createdAt) // server script is newer than local - update info
{
currentEsi.createdAt = serverEsi.createdAtServer;
currentEsi.exercise = serverEsi.exercise;
currentEsi.script = serverEsi.script;
}
currentEsi.setUploaded(serverEsi.createdAtServer);
setRowData(row, currentEsi);
found = true;
break;
}
if (found)
{
continue;
}
// added
insertRow(serverEsi);
}
}
catch(const std::runtime_error &err)
{
ERROR_LOG(m_logger) << "something went wrong: " << err.what();
}
catch(...)
{
ERROR_LOG(m_logger) << "something went wrong";
}
}
QVariant ExportedScriptsModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
const ExportedScriptInformation &esi = m_vecData[index.row()];
switch (role)
{
case Qt::BackgroundRole:
{
if ((index.column() == ColCreatedAt && esi.createdAtServer.isValid() && esi.createdAt < esi.createdAtServer) != // XOR
(index.column() == ColCreatedAtServer && esi.createdAt.isValid() && esi.createdAtServer < esi.createdAt))
{
return QBrush(QColor(255, 205, 205)); // i bet this looks bad in dark style
}
break;
}
case Qt::DisplayRole:
{
switch (index.column())
{
case ColId:
return esi.id();
case ColExercise:
return esi.exercise.name;
case ColName:
return esi.script.name;
case ColCreatedAt:
return esi.createdAt;
case ColCreatedAtServer:
return esi.createdAtServer;
case ColLocation:
{
QStringList locations;
if (esi.local)
locations += tr("Local");
if (esi.uploaded == ExportedScriptInformation::UploadState::ThpAlreadyUploaded)
locations += tr("Server");
return locations.join(L(" & "));
}
case ColUploadStatus:
{
switch (esi.uploaded)
{
case ExportedScriptInformation::UploadState::ThpNotUploaded:
return tr("Not uploaded");
case ExportedScriptInformation::UploadState::ThpUnknownUploadState:
return tr("Unknown upload status");
case ExportedScriptInformation::UploadState::ThpAlreadyUploaded:
return tr("Already uploaded");
}
}
}
break;
}
case Qt::UserRole:
{
return esi.id();
break;
}
default:
break;
}
return {};
}
void ExportedScriptsModel::addScript(const int exerciseId, const QString &exerciseName,
const int scriptId, const QString &scriptName,
const QDateTime &createdAt) noexcept(false)
{
insertRow(ExportedScriptInformation({ createdAt, { exerciseId, exerciseName }, { scriptId, scriptName, {} } }));
writeScriptsJson();
}
void ExportedScriptsModel::removeLocalScript(const QModelIndex &idx) noexcept(false)
{
auto esi = getRowData(idx.row());
if (!esi.local)
{
return;
}
if (esi.isUploaded())
{
esi.unsetLocal();
setRowData(idx.row(), esi);
}
else
{
removeRow(idx.row());
}
writeScriptsJson();
}
void ExportedScriptsModel::removeLocalScript(const QString &scriptId) noexcept(false)
{
for (int i = 0; i < rowCount(); i++)
{
const auto idx = index(i, 0);
if (data(idx, Qt::UserRole) == scriptId)
{
removeLocalScript(idx);
return;
}
}
// TODO: fail if not found?
}
QVector<ExportedScriptInformation> ExportedScriptsModel::getLocalScripts() noexcept(false)
{
QVector<ExportedScriptInformation> ret;
ret.reserve(m_vecData.size());
for (const auto &esi : m_vecData)
{
if (esi.local)
{
ret += esi;
}
}
return ret;
}
void ExportedScriptsModel::writeScriptsJson() noexcept(false)
{
auto parser = ScriptsJsParser(m_rootDirectoryToScan);
parser.setExportedScriptInformations(getLocalScripts());
parser.save();
}
//bool ExportedScriptsModel::setUploadState(const int row,
// const ExportedScriptInformation::UploadState uploadState,
// const QDateTime &createdAtServer,
// const bool isLocal)
//{
// auto esi(getRowData(row));
// qWarning() << "setUploadState: esi.id = " << esi.id() << "uploadState" << (int)uploadState << "createdAtServer" << createdAtServer;
// esi.uploaded = uploadState;
// esi.createdAtServer = createdAtServer;
// esi.local = isLocal;
// //esi.fileSize = fileSize;
// return setRowData(row, esi);
//}
} // namespace TakeHomePackageManager
@@ -0,0 +1,65 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#pragma once
#include <logger.h>
#include <LtsWidgets/lts_abstract_table_model.h>
#include <Common2/thp2common_export.h>
#include <Common2Core/tpm_js_parser.h>
#include <QDateTime>
#include <QDir>
class QFileSystemWatcher;
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT ExportedScriptsModel : public LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>
{
Q_OBJECT
public:
enum Columns
{
ColId,
ColExercise,
ColName,
ColCreatedAt, // local
ColCreatedAtServer,
ColLocation,
ColUploadStatus,
};
explicit ExportedScriptsModel(QObject *parent = nullptr);
void init(const QString &rootDirectoryToScan, bool checkForScriptsJsChanges = false) noexcept(false);
void addScript(const int exerciseId, const QString &exerciseName, const int scriptId, const QString &scriptName, const QDateTime &createdAt) noexcept(false);
void removeLocalScript(const QString &scriptId) noexcept(false);
void removeLocalScript(const QModelIndex &idx) noexcept(false);
void setServerContent(const QString &jsonObject);
void setOC(bool isOC) { m_isOC = isOC; }
void writeScriptsJson() noexcept(false);
protected:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
void reloadScriptsJs() noexcept(false);
QVector<ExportedScriptInformation> getLocalScripts() noexcept(false);
private:
bool m_isOC { false };
QString m_rootDirectoryToScan;
QFileSystemWatcher *m_watcher { nullptr };
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,384 @@
/**
* @RDE_HEADER@
* Advanced file copy manager with progress dialog
* $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, SLTE, 2024-02-09
*/
#include "tpm_file_copy.h"
#include <Common2/tpm_process_dialog.h>
#include <Common2Core/tpm_tools.h>
#include <QDir>
#include <QFile>
#include <QTemporaryFile>
#include <QTimer>
#include <QFuture>
#include <QFutureWatcher>
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QtConcurrent>
#include <QDirIterator>
#include <QFileDialog>
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
#include <QDebug>
#ifdef Q_OS_UNIX
#define _compiler_warning(x) _Pragma(#x)
#define compiler_warning(x) _compiler_warning(GCC warning x)
#define TMP_WARNING_LOG(x) compiler_warning("Temporary warning log active!") \
WARNING_LOG(x)
#else
#define compiler_warning(x)
#define TMP_WARNING_LOG(x) WARNING_LOG(x)
#endif
typedef std::runtime_error error;
namespace TakeHomePackageManager
{
CopyInfoList getFilesFromFolder(const QString &srcPath, const QString &dstPath, const QStringList &excludes) noexcept(false)
{
int fuse = 50000;
QFileInfoList todoFiles({{srcPath}});
CopyInfoList ret;
do
{
fuse--;
if (fuse < 1)
{
throw error("fuse popped");
}
const QString path(todoFiles.takeFirst().absoluteFilePath());
bool exclude = false;
for (const auto &excludePath : excludes)
{
if ((srcPath + QDir::separator() + excludePath) == path)
{
qWarning() << "excluded" << path;
exclude = true;
break;
}
}
if (exclude)
continue; // skip excluded file / dir
QDir srcDir(path);
if (!srcDir.exists())
{
throw error(L("getFilesFromFolder: path (%1) does not exist!").arg(srcPath).toStdString());
}
// directories (aka. more dirs to scan)
for (const auto &dirEntryInfo : srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
{
//qWarning() << "srcDir" << srcDir.absolutePath() << "next" << QDir(dirEntryInfo.absoluteFilePath()).absolutePath();
todoFiles += dirEntryInfo; //_getFilesFromFolder(QDir(dirEntryInfo.absoluteFilePath()), files);
}
// files
for (const auto &fileEntryInfo : srcDir.entryInfoList(QDir::Files))
{
ret += { srcPath, dstPath, fileEntryInfo };
}
}
while (!todoFiles.isEmpty());
return ret;
}
void copyFiles(const CopyInfoList &fileList, LtsWidgets::LtsDialog *dialog) noexcept(false)
{
auto cp = new CopyProcess(fileList, dialog);
dialog->setOverlay(cp->process());
bool success = false;
QObject::connect(cp, &CopyProcess::finished, dialog, [&success](bool _success) { success = _success; });
awaitx(cp, &CopyProcess::finished);
if (!success)
{
throw error("!success");
}
}
// static AsyncFileCopy *AsyncFileCopy::createAsyncFileCopy(const CopyInfoList &fileList) // to prevent the user from thinking having to delete the class (which it does itself)
// {
// return new AsyncFileCopy(fileList);
// }
AsyncFileCopy::AsyncFileCopy(const CopyInfoList &fileList)
{
QFutureWatcher<bool> *fw = new QFutureWatcher<bool>(this);
connect(fw, &QFutureWatcher<bool>::finished,
this, [this, fw]()
{
emit finished(fw->result());
});
connect(fw, &QFutureWatcher<bool>::finished,
this, &QObject::deleteLater); // self-deleting
fw->setFuture(QtConcurrent::run(this, &AsyncFileCopy::copy, fileList));
}
#define cancelationPoint if (m_wasCanceled) { qWarning() << "AsyncFileCopy: canceled"; return false; } (void)m_wasCanceled
bool AsyncFileCopy::copy(const CopyInfoList &fileList)
{
qint64 totalAll = 0, writtenAll = 0;
int fileCount = 0;
for (const auto &fileInfo : fileList)
{
totalAll += fileInfo.fileInfo.size(); // there should be ONLY files in QFileInfoList!
fileCount++;
}
if (fileCount < 1)
{
qWarning() << "AsyncFileCopy: fileCount < 1";
return false;
}
//qWarning() << "AsyncFileCopy: copy" << fileList.count() << "files";
emit started(fileCount, totalAll);
cancelationPoint;
int currentFile = 0;
for (const auto &copyInfo : fileList)
{
cancelationPoint;
const QFileInfo &fileInfo = copyInfo.fileInfo;
const QString &srcPath = copyInfo.srcPath;
const QString &dstPath = copyInfo.dstPath;
currentFile++;
const qint64 totalFile = fileInfo.size();
qint64 writtenFile = 0;
QDir srcDir(srcPath);
// if (cdUp)
// srcDir.cdUp();
// src: /shd/CTC/WS/THP2
// file: /shd/CTC/WS/THP2/lib/script.js
// dst: /mnt/thps/42.3/
// full: /mnt/thps/42.3/lib/
// soll: /mnt/thps/42.3/lib/script.js
// NOTE: src: /shd/CTC/WS (if cdUp!)
const QString dstFullPath(dstPath + QDir::separator() + fileInfo.absolutePath().remove(srcDir.path()));
if (!QDir().mkpath(dstFullPath) /* why is this not static, Qt? Directory validation? */)
{
qWarning() << "AsyncFileCopy: cannot create path";
return false;
}
QFile srcFile(fileInfo.absoluteFilePath());
emit newFile(currentFile, totalFile, fileInfo.fileName());
QFile dstFile(dstFullPath + QDir::separator() + fileInfo.fileName());
qWarning() << "AsyncFileCopy: copy file from" << srcFile.fileName() << "to" << dstFile.fileName();
if (!srcFile.open(QIODevice::ReadOnly))
{
qWarning() << QStringLiteral("failed to open src '%1' (error: %2)").arg(srcFile.fileName(), srcFile.errorString());
return false;
}
if (!dstFile.open(QIODevice::Unbuffered /* make "done" really mean "done" */| QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << QStringLiteral("failed to open dst '%1' (error: %2)").arg(dstFile.fileName(), dstFile.errorString());
return false;
}
qint64 fileSize = srcFile.size();
while (1)
{
cancelationPoint;
//#define SUPERSLOW
emit status(tr("Reading..."));
const auto data = srcFile.read(
#ifdef SUPERSLOW
54321
#else
s_chunkSize
#endif
);
cancelationPoint;
#ifdef SUPERSLOW
usleep(100 * 1000); // 100ms
#endif
emit status(tr("Copying..."));
const auto _written = dstFile.write(data);
//qWarning() << "AsyncFileCopy: read" << data.size() << "write" << _written << "size" << fileSize;
#ifdef SUPERSLOW
usleep(100 * 1000);
#endif
if (_written < 0)
{
qWarning() << "AsyncFileCopy: unable to write to file" << dstFile.fileName() << ":" << dstFile.errorString();
return false;
}
writtenFile += _written;
writtenAll += _written;
//qWarning() << "AsyncFileCopy: progress" << writtenFile << totalFile << writtenAll << totalAll << currentFile << fileCount;
emit progress(writtenFile, writtenAll);
cancelationPoint;
// TODO: windoofs
#ifdef Q_OS_UNIX
//qWarning() << "AsyncFileCopy: flushing...";
emit status(tr("Writing..."));
QElapsedTimer t;
t.start();
fsync(dstFile.handle()); // fsync(2): "The call blocks until the device reports that the transfer has completed."
// TODO: use O_DIRECT instead (?)
//qWarning() << "took" << t.elapsed() << "ms";
#endif
if (data.size() != _written) // fail if it didn't write all it read before
{
qWarning() << "AsyncFileCopy: copy failed! read" << data.size() << "write" << _written << "size" << fileSize;
return false;
}
if (writtenFile == fileSize) // done
{
break;
}
}
}
if (totalAll == writtenAll)
{
//qWarning() << "AsyncFileCopy: done - " << writtenAll << "/" << totalAll << "bytes";
return true;
}
else
{
qWarning() << "AsyncFileCopy: failed! total written bytes doesn't match all requested bytes - " << writtenAll << "/" << totalAll << "bytes";
return false;
}
}
/// CopyProcess
CopyProcess::CopyProcess(const CopyInfoList &fileList, QObject *parent)
: QObject(parent)
, m_process(new ProcessOverlay)
{
connect(m_process, &ProcessOverlay::signalCancel,
this, [this]()
{
//DEBUG_LOG(m_logger) << "m_wasCanceled!!";
m_wasCanceled = true;
//m_process->setCancelEnabled(false); does so itself already
});
connect(m_process, &ProcessOverlay::signalCancel,
this, &CopyProcess::signalCancel);
AsyncFileCopy *afc = AsyncFileCopy::createAsyncFileCopy(fileList);
connect(m_process, &ProcessOverlay::signalCancel,
afc, &AsyncFileCopy::slotCancel);
connect(afc, &AsyncFileCopy::progress,
this, [this](qint64 bytesCopiedFile, qint64 bytesCopiedAll)
{
m_process->setFileProgress(bytesCopiedFile, bytesCopiedAll);
});
connect(afc, &AsyncFileCopy::newFile,
this, [this](int currentFile, qint64 bytesTotalFile, const QString &filename)
{
m_process->setNewFile(currentFile, bytesTotalFile, filename);
});
connect(afc, &AsyncFileCopy::started,
this, [this](int fileCount, qint64 bytesTotalAll)
{
m_process->setStarted(fileCount, bytesTotalAll);
});
connect(afc, &AsyncFileCopy::status,
this, [this](const QString &status)
{
m_process->setStatus(status);
});
connect(afc, &AsyncFileCopy::newAction,
this, [this](const QString &newAction)
{
m_currentAction = newAction;
});
connect(afc, &AsyncFileCopy::finished,
this, &QObject::deleteLater);
connect(afc, &AsyncFileCopy::finished,
this, &CopyProcess::finished);
}
CopyProcess::~CopyProcess()
{
//DEBUG_LOG(m_logger) << "~CopyProcess";
//delete m_process;
m_process->deleteLater();
}
QWidget *CopyProcess::process()
{
return m_process;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,97 @@
/**
* @RDE_HEADER@
* Advanced file copy manager with progress dialog
* $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, SLTE, 2024-02-09
*/
#pragma once
#include <Common2/thp2common_export.h>
#include <LtsWidgets/lts_dialog.h>
#include <QFileInfoList>
#include <QWidget>
namespace TakeHomePackageManager
{
class ProcessOverlay;
struct THP2COMMON_EXPORT CopyInfo // :'( we need this to cut the srcPath from the file path, to have the same folder structure in the destionation folder
{
QString srcPath;
QString dstPath;
QFileInfo fileInfo;
};
using CopyInfoList = QList<CopyInfo>;
THP2COMMON_EXPORT CopyInfoList getFilesFromFolder(const QString &srcPath, const QString &dstPath, const QStringList &excludes = {}) noexcept(false);
THP2COMMON_EXPORT void copyFiles(const CopyInfoList &fileList, LtsWidgets::LtsDialog *dialog) noexcept(false);
// TODO: mv to LtsCommon
class AsyncFileCopy : public QObject
{
Q_OBJECT
public:
static const int s_chunkSize = 1 * 1024 * 1024; // 1MB
static AsyncFileCopy *createAsyncFileCopy(const CopyInfoList &fileList) // to prevent the user from thinking having to delete the class (which it does itself)
{
return new AsyncFileCopy(fileList);
}
~AsyncFileCopy() = default;
private:
AsyncFileCopy(const CopyInfoList &fileList);
private:
bool copy(const CopyInfoList &fileList);
bool m_wasCanceled { false };
public slots:
void slotCancel()
{
m_wasCanceled = true;
}
signals:
void newAction(const QString &newAction);
void status(const QString &status);
void started(int fileCount, qint64 bytesTotalAll);
void newFile(int currentFile, qint64 bytesTotalFile, const QString &filename);
void progress(qint64 bytesCopiedFile, qint64 bytesCopiedAll);
void finished(bool success);
};
class THP2COMMON_EXPORT CopyProcess : public QObject
{
Q_OBJECT
public:
CopyProcess(const CopyInfoList &fileList, QObject *parent = nullptr);
~CopyProcess();
QWidget *process();
signals:
void signalCancel();
void finished(bool success);
private:
ProcessOverlay *m_process;
bool m_wasCanceled { false };
QString m_currentAction;
};
} // namespace TakeHomePackageManager
@@ -0,0 +1,115 @@
/**
* @RDE_HEADER@
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include "tpm_process_dialog.h"
#include <ui_tpm_process_dialog.h>
#include <QDebug>
namespace TakeHomePackageManager
{
ProcessOverlay::ProcessOverlay(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::ProcessOverlay)
{
m_ui->setupUi(this);
connect(m_ui->pbCancel, &QPushButton::clicked,
this, [this]()
{
m_ui->pbCancel->setEnabled(false);
emit signalCancel();
});
}
ProcessOverlay::~ProcessOverlay()
{
delete m_ui;
}
void ProcessOverlay::setCancelEnabled(bool enabled)
{
m_ui->pbCancel->setEnabled(enabled);
}
void ProcessOverlay::setStatus(const QString &text)
{
if (!text.isEmpty()) // only set new text if not empty (we dont want to send the text on every update..
m_ui->lblStatus->setText(text);
}
void ProcessOverlay::setFileProgress(qint64 bytesCopiedFile, qint64 bytesCopiedAll)
{
qWarning() << "setFileProgress" << "bytesCopiedFile" << bytesCopiedFile << "bytesCopiedAll" << bytesCopiedAll;
double progressBytes = (m_bytesTotalFile == 0) ? 0 : ((double)bytesCopiedFile / (double)m_bytesTotalFile);
double progressTotal = (m_bytesTotalAll == 0) ? 0 : (double)bytesCopiedAll / (double)m_bytesTotalAll;
qWarning() << "progressTotal" << progressTotal << "progressBytes" << progressBytes;
m_ui->pbarBytes->setValue(qRound(progressBytes * 100));
m_ui->pbarProgress->setValue(qRound(progressTotal * 100));
const QLocale l;
m_ui->lblBytes->setText((m_bytesTotalFile == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2 %3").arg(l.toString(bytesCopiedFile)).arg(l.toString(m_bytesTotalFile)).arg(tr("bytes")));
m_ui->lblAllBytes->setText((m_bytesTotalAll == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2 %3").arg(l.toString(bytesCopiedAll)).arg(l.toString(m_bytesTotalAll)).arg(tr("bytes")));
}
void ProcessOverlay::setNewFile(int currentFile, qint64 bytesTotalFile, const QString &filename)
{
qWarning() << "setNewFile" << "currentFile" << currentFile << "bytesTotalFile" << bytesTotalFile << "filename" << filename;
m_bytesTotalFile = bytesTotalFile;
m_currentFile = currentFile;
m_progressFiles = (m_fileCount == 0) ? 0 : (((double)m_currentFile - 1) / (double)m_fileCount);
m_ui->pbarFiles->setValue(qRound(m_progressFiles * 100));
m_ui->lblFilename->setText(filename);
const QLocale l;
m_ui->lblFiles->setText((m_currentFile == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2").arg(l.toString(m_currentFile)).arg(l.toString(m_fileCount)));
}
void ProcessOverlay::setStarted(int fileCount, qint64 bytesTotalAll)
{
qWarning() << "setStarted" << "fileCount" << fileCount << "bytesTotalAll" << bytesTotalAll;
m_fileCount = fileCount;
m_bytesTotalAll = bytesTotalAll;
setFileProgress(0, 0); // so that the user sees some initial information before file transfer starts
}
} // namespace TakehomePackageManager
@@ -0,0 +1,51 @@
#pragma once
/**
* @RDE_HEADER@
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include <Common2/thp2common_export.h>
#include <QWidget>
namespace Ui
{
class ProcessOverlay;
}
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT ProcessOverlay : public QWidget
{
Q_OBJECT
public:
ProcessOverlay(QWidget *parent = nullptr);
~ProcessOverlay();
void setStarted(int fileCount, qint64 bytesTotalAll);
void setNewFile(int currentFile, qint64 bytesTotalFile, const QString &filename);
void setStatus(const QString &text);
void setFileProgress(qint64 bytesCopiedFile, qint64 bytesCopiedAll);
void setCancelEnabled(bool enabled = true);
signals:
void signalCancel();
private:
qint64 m_bytesTotalFile = 0;
int m_currentFile = 0;
int m_fileCount = 0;
qint64 m_bytesTotalAll = 0;
double m_progressFiles = 0;
Ui::ProcessOverlay *m_ui;
bool m_decompressing { false };
};
} // namespace TakehomePackageManager
@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessOverlay</class>
<widget class="QWidget" name="ProcessOverlay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>250</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>700</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lblStatus">
<property name="text">
<string notr="true">Preparing ...</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblFilename">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Bytes</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="lblBytes">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarBytes">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Files</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="lblFiles">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarFiles">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Progress</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="lblAllBytes">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="pbCancel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,79 @@
/**
* @RDE_HEADER@
* List of topics of selected thp
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#include "tpm_topics_model.h"
#include <TopicEditorBase/topic_editor_utils.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace TakeHomePackageManager
{
TopicsModel::TopicsModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<Topic>(
{ tr("id"), tr("Channel"), tr("Filter"), tr("Description"), tr("Type"), tr("Start [mm:ss]"), tr("Duration [mm:ss]")}, parent)
, m_logger(getLogger("TopicsModel"))
{}
void TopicsModel::addTopic(const Topic &topic)
{
insertRow(topic);
}
QVariant TopicsModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
if (role == Qt::UserRole)
{
const auto &topic = m_vecData[index.row()];
return topic.id;
}
if (role != Qt::DisplayRole)
{
return {};
}
auto channelName = [](const int channel)
{
switch (channel)
{
case 1: return tr("Audio");
case 2: return tr("Replay");
case 3: return tr("Screen 1");
case 4: return tr("Screen 2");
default: { assert(false); return L("UNKNOWN"); }
}
};
const auto &topic = m_vecData[index.row()];
switch (index.column())
{
case ColId: return topic.id;
case ColChannel: return channelName(topic.channel);
case ColFilter: return (topic.filterSet ? QString::number(topic.filter) : L(" - "));
case ColDescription: return (topic.description.isEmpty() ? L(" - ") : topic.description);
case ColType: return TopicEditorBase::topicTypeTranslation((TopicEditorBase::TopicType)topic.type);
case ColStart: return QTime(0, 0).addSecs(topic.start).toString(L("mm:ss"));
case ColDuration: return QTime(0, 0).addSecs(topic.duration).toString(L("mm:ss"));
}
return {};
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,44 @@
/**
* @RDE_HEADER@
* List of topics of selected thp
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#pragma once
#include <logger.h>
#include <Common2/thp2common_export.h>
#include <Common2Core/tpm_js_parser.h>
#include <LtsWidgets/lts_abstract_table_model.h>
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT TopicsModel : public LtsWidgets::LtsAbstractTableModel<Topic>
{
Q_OBJECT
public:
enum Columns
{
ColId,
ColChannel,
ColFilter,
ColDescription,
ColType,
ColStart,
ColDuration,
};
explicit TopicsModel(QObject *parent = nullptr);
void addTopic(const Topic &topic);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,33 @@
#
# $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, 2024-01-11
#
SET(src
#logger.cpp
#logger.h
tpm_js_parser.cpp
tpm_js_parser.h
tpm_tools.cpp
tpm_tools.h
)
NGL_ADD_LIBRARY2(WS SV Thp2CommonCore SHARED ${src}) # SV because FLM Service (httq server) uses Thp2CommonCore, too
NGL_TARGET_COMPILE_DEFINITIONS(Thp2CommonCore PRIVATE L=QStringLiteral)
NGL_TARGET_INCLUDE_DIRECTORIES(Thp2CommonCore
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../ # export file
)
NGL_TARGET_LINK_LIBRARIES(Thp2CommonCore
PRIVATE
Qt5::Core
#NGLLogger
LtsUtilsCoreHeaderOnly
)
NGL_INSTALL_TARGET(Thp2CommonCore)
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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, SLTE, 2024-01-11
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM2.CommonCore.";
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 CentralEffectSimulation
* $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, SLTE, 2024-01-11
*/
#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,544 @@
/**
* @RDE_HEADER@
* topics.js parser
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, EZSZ, 2024-02-12
*/
#include "tpm_js_parser.h"
//#include <logger.h>
#include <tpm_tools.h>
#include <LtsUtilsCore/json.h>
#include <QMapIterator>
#include <utility>
#include <QDebug>
std::string error(const QString &msg) { return msg.toStdString(); }
using namespace std;
#ifndef DLOG
#define DLOG DEBUG_LOG(m_logger)
#endif
using namespace LtsUtils;
namespace TakeHomePackageManager
{
/// JsObjectParser
template<>
/*static*/ JsonObject JsParser<JsonObject, QJsonObject>::parse(const QByteArray &data) noexcept(false)
{
return JsonObject::fromJsonSafe(_parse(data, true).toUtf8());
}
template<>
JsParser<JsonObject, QJsonObject>::JsParser(const QString &globalThisName, const QString &filePath, bool mustExist) noexcept(false)
: m_globalThisName(globalThisName)
, m_filePath(filePath)
, m_json(load(mustExist))
//, m_logger(getLogger("JsObjectParser"))
{
qWarning() << "OBJECT - m_globalThisName" << m_globalThisName << "m_filePath" << m_filePath << "m_json" << m_json.object().count();
}
template<>
QJsonDocument JsParser<JsonObject, QJsonObject>::document() const
{
return QJsonDocument(static_cast<const QJsonObject &>(m_json));
}
template<>
/*static*/ const QJsonObject &JsParser<JsonObject, QJsonObject>::get() const
{
return m_json;
}
///// JsArrayParser
template<>
/*static*/ JsonArray JsParser<JsonArray, QJsonArray>::parse(const QByteArray &data) noexcept(false)
{
return JsonArray::fromJsonSafe(_parse(data, false).toUtf8());
}
template<>
JsParser<JsonArray, QJsonArray>::JsParser(const QString &globalThisName, const QString &filePath, bool mustExist) noexcept(false)
: m_globalThisName(globalThisName)
, m_filePath(filePath)
, m_json(load(mustExist))
//, m_logger(getLogger("JsArrayParser"))
{
qWarning() << "ARRAY - m_globalThisName" << m_globalThisName << "m_filePath" << m_filePath << "m_json" << m_json.size();
}
template<>
QJsonDocument JsParser<JsonArray, QJsonArray>::document() const
{
return QJsonDocument(m_json());
}
template<>
/*static*/ const QJsonArray &JsParser<JsonArray, QJsonArray>::get() const
{
return m_json();
}
/// JsParser
template<typename LtsJsonType, typename QJsonType>
void JsParser<LtsJsonType, QJsonType>::save() noexcept(false)
{
if (!m_dataChanged)
{
return;
}
auto file(openSaveFile(m_filePath)); // throws (incl. dtor)
file->write(L("globalThis.%1 = \n").arg(m_globalThisName).toUtf8());
file->write(document().toJson(QJsonDocument::JsonFormat::Indented /* adds \n to the end */));
file->write(";");
}
template<typename LtsJsonType, typename QJsonType>
/*static*/ QByteArray JsParser<LtsJsonType, QJsonType>:: _load(bool mustExist) const noexcept(false)
{
const auto data(readFile(m_filePath, mustExist));
qWarning() << "JsParser: file" << m_filePath << "######################## content:" << data;
if (data.isEmpty())
{
return {};
}
return data;
}
template<typename LtsJsonType, typename QJsonType>
/*static*/ QString JsParser<LtsJsonType, QJsonType>::_parse(const QByteArray &data, bool isObject) noexcept(false)
{
const QString opener((isObject) ? L("{") : L("["));
const QString closer((isObject) ? L("}") : L("]"));
const QRegExp regexp(L(R"RRR(\%1[\S\s]*\%2)RRR").arg(opener, closer));
const QString topicsJsText(QString::fromUtf8(data));
//DLOG << "topicsJsText: " << topicsJsText;
if (regexp.indexIn(topicsJsText) < 0)
{
//throw runtime_error(L("no %1 with name '%2' in js-file '%3' found").arg(isObject ? L("object") : L("array").arg(globalThisName, filePath).toStdString());
throw runtime_error(L("no %1 in js-file found").arg(isObject ? L("object") : L("array")).toStdString());
}
// js file, not a json file, so get the first '[' and the last ']' to get the pure json compatible data structure from the incompatible javascript (see regexp pattern above)
const QString jsonString(regexp.cap());
//DLOG << "jsonString: " << jsonString;
qWarning() << "jsonString" << jsonString;
return jsonString;
}
template<typename LtsJsonType, typename QJsonType>
/*static*/ LtsJsonType JsParser<LtsJsonType, QJsonType>::load(bool mustExist) const noexcept(false)
{
auto data = _load(mustExist);
if (data.isEmpty())
{
if (mustExist)
{
throw runtime_error("js-file is missing content");
}
qWarning() << ":(";
return {};
}
return parse(data);
}
template<typename LtsJsonType, typename QJsonType>
/*static*/ void JsParser<LtsJsonType, QJsonType>::write(const QString &globalThisName, const QJsonType &json, const QString &filePath, bool mustExist) noexcept(false)
{
auto parser = JsParser<LtsJsonType, QJsonType>(globalThisName, filePath, mustExist);
parser.set(json);
parser.save();
}
template void JsParser<JsonObject, QJsonObject>::write(const QString &globalThisName, const QJsonObject &json, const QString &filePath, bool mustExist) noexcept(false);
template void JsParser<JsonArray, QJsonArray>::write(const QString &globalThisName, const QJsonArray &json, const QString &filePath, bool mustExist) noexcept(false);
template<typename LtsJsonType, typename QJsonType>
void JsParser<LtsJsonType, QJsonType>::set(const QJsonType &json)
{
m_json = LtsJsonType(json);
m_dataChanged = true;
}
template void JsParser<JsonObject, QJsonObject>::set(const QJsonObject &json);
template void JsParser<JsonArray, QJsonArray>::set(const QJsonArray &json);
template<typename LtsJsonType, typename QJsonType>
/*static*/ QJsonType JsParser<LtsJsonType, QJsonType>::read(const QString &globalThisName, const QString &filePath, bool mustExist) noexcept(false)
{
return JsParser(globalThisName, filePath, mustExist).get();
}
template QJsonObject JsParser<JsonObject, QJsonObject>::read(const QString &globalThisName, const QString &filePath, bool mustExist) noexcept(false);
template QJsonArray JsParser<JsonArray, QJsonArray>::read(const QString &globalThisName, const QString &filePath, bool mustExist) noexcept(false);
/// ScriptsJsParser
ScriptsJsParser::ScriptsJsParser(const QString &root, bool mustExist) noexcept(false)
: JsObjectParser(L("scriptsJs"), scriptsJsLocation(root), mustExist)
{
const QJsonObject obj(get());
qWarning() << "ScriptsJsParser" << obj;
for (auto it = obj.constBegin(); it != obj.constEnd(); ++it)
{
const auto esi(ExportedScriptInformation::fromJson(it.value(), true /* local */, ExportedScriptInformation::UploadState::ThpUnknownUploadState));
qWarning() << "adding" << esi.id();
//DEBUG_LOG(m_logger) << "obj=" << QJsonDocument(obj).toJson(QJsonDocument::Compact);
//esi.rootDirectory = rootDirectoryToScan; // to determine if this script is from THP-Download or THP-Export directory (for USB Management, which can copy from both dirs)
m_esis.insert(esi.id(), esi);
}
}
//ScriptsJsParser::~ScriptsJsParser() noexcept(false)
void ScriptsJsParser::save() noexcept(false)
{
QJsonObject obj;
QMapIterator<QString, ExportedScriptInformation> it(m_esis);
while (it.hasNext())
{
auto &esi = it.next().value();
obj.insert(esi.id(), esi.toJson());
}
set(obj);
JsParser::save();
}
const QMap<QString, ExportedScriptInformation> &ScriptsJsParser::getExportedScriptInformations() const
{
return m_esis;
}
const ExportedScriptInformation &ScriptsJsParser::getExportedScriptInformation(const QString &scriptId) const noexcept(false)
{
auto it = m_esis.constFind(scriptId);
if (it == m_esis.constEnd())
{
throw L("getExportedScriptInformation: script with id '%1' not found").arg(scriptId);
}
return it.value();
}
bool ScriptsJsParser::hasExportedScriptInformation(const QString &scriptId) const
{
return m_esis.contains(scriptId);
}
void ScriptsJsParser::setExportedScriptInformations(const QVector<ExportedScriptInformation> &esis) noexcept(false)
{
m_esis.clear();
addExportedScriptInformations(esis);
}
void ScriptsJsParser::addExportedScriptInformations(const QVector<ExportedScriptInformation> &esis) noexcept(false)
{
for (const ExportedScriptInformation &esi : esis)
addExportedScriptInformation(esi);
}
void ScriptsJsParser::addExportedScriptInformation(const ExportedScriptInformation &esi) noexcept(false)
{
m_esis.insert(esi.id(), esi);
m_dataChanged = true;
}
void ScriptsJsParser::removeExportedScriptInformation(const QString &scriptId) noexcept(false)
{
if (m_esis.remove(scriptId) != 1)
{
throw L("removeExportedScriptInformation: unable to remove script with id '%1' because it was not found").arg(scriptId);
}
m_dataChanged = true;
}
///// TopicsJsParser
//TopicsJsParser::TopicsJsParser(const QString &root, bool mustExist) noexcept(false)
// : JsArrayParser(L("topicsJs"), scriptsJsLocation(root), mustExist)
//{
// const auto topics = Topic::topicsFromJson(get());
// qWarning() << "TopicsJsParser: topics " << topics.count();
// for (const auto &topic : topics)
// {
// qWarning() << "adding" << topic.id;
// m_topics.insert(topic.id, topic);
// }
//}
//void TopicsJsParser::save() noexcept(false)
//{
// QJsonArray arr;
// QMapIterator<int, Topic> it(m_topics);
// while (it.hasNext())
// {
// auto &topic = it.next().value();
// arr += topic.toJson();
// }
// set(arr);
// JsParser::save();
//}
//const QMap<int, Topic> &TopicsJsParser::getTopics() const
//{
// return m_topics;
//}
//const Topic &TopicsJsParser::getTopic(const int topicId) const noexcept(false)
//{
// auto it = m_topics.constFind(topicId);
// if (it == m_topics.constEnd())
// {
// throw L("getTopic: topic with id '%1' not found").arg(topicId);
// }
// return it.value();
//}
//bool TopicsJsParser::hasTopic(const int topicId) const
//{
// return m_topics.contains(topicId);
//}
//void TopicsJsParser::setTopics(const QVector<Topic> &topics) noexcept(false)
//{
// m_topics.clear();
// addTopics(topics);
//}
//void TopicsJsParser::addTopics(const QVector<Topic> &topics) noexcept(false)
//{
// for (const Topic &topic : topics)
// addTopic(topic);
//}
//void TopicsJsParser::addTopic(const Topic &topic) noexcept(false)
//{
// m_topics.insert(topic.id, topic);
// m_dataChanged = true;
//}
//void TopicsJsParser::removeTopic(const int topicId) noexcept(false)
//{
// if (m_topics.remove(topicId) != 1)
// {
// throw L("removeTopic: unable to remove topic with id '%1' because it was not found").arg(topicId);
// }
// m_dataChanged = true;
//}
/// ExportedScriptInformation
QJsonObject dateTimeToJson(const QDateTime &dt)
{
return { { L("iso"), dt.toUTC().toString(Qt::DateFormat::ISODate) }, { L("secsSinceEpoch"), dt.toSecsSinceEpoch() } };
}
QDateTime jsonToDateTime(const QJsonValue &val) noexcept(false)
{
bool k = false;
auto ret = QDateTime::fromSecsSinceEpoch(val.toObject()[L("secsSinceEpoch")].toVariant().toULongLong(&k));
if (!k)
{
throw std::runtime_error("jsonToDateTime: !k");
}
if (!ret.isValid())
{
throw std::runtime_error("jsonToDateTime: !ret.isValid()");
}
return ret;
}
QString ExportedScriptInformation::id() const
{
return L("%1.%2%3").arg(exercise.id).arg(script.id).arg(script.customName.isEmpty() ? QString() : L("-%1").arg(script.customName));
}
QJsonValue ExportedScriptInformation::toJson() const
{
QJsonObject ret({
{ L("createdAt"), dateTimeToJson(createdAt) },
{ L("exerciseId"), exercise.id },
{ L("exerciseName"), exercise.name },
{ L("scriptId"), script.id },
{ L("scriptName"), script.name }
});
if (!script.customName.isEmpty()) // js's '...' operator would be very handy here
{
ret[L("scriptCustomName")] = script.customName; // optional, needed for usb exports where some topics were unselected by the user
}
// if (uploadedAt.isValid())
// ret[L("uploadedAt")] = dateTimeToJson(uploadedAt);
// if (downloadedAt.isValid())
// ret[L("downloadedAt")] = dateTimeToJson(downloadedAt);
return ret;
}
ExportedScriptInformation ExportedScriptInformation::fromJson(const QJsonValue &val,
const bool local,
const UploadState uploadState) noexcept(false)
{
const QJsonObject obj(val.toObject());
ExportedScriptInformation esi;
const auto createdAt = jsonToDateTime(obj[L("createdAt")]);
if (local)
{
esi.createdAt = createdAt;
}
if (uploadState == UploadState::ThpAlreadyUploaded)
{
esi.createdAtServer = createdAt;
}
esi.script.id = obj[L("scriptId")].toInt();
esi.script.name = obj[L("scriptName")].toString();
auto scriptCustomNameValIt = obj.constFind(L("scriptCustomName")); // optional
if (scriptCustomNameValIt != obj.constEnd())
{
esi.script.customName = scriptCustomNameValIt->toString();
}
esi.exercise.id = obj[L("exerciseId")].toInt();
esi.exercise.name = obj[L("exerciseName")].toString();
if (esi.script.id < 1 || esi.script.name.isEmpty() || esi.exercise.id < 1 || esi.exercise.name.isEmpty())
{
throw std::runtime_error("one or more of (script id / script name / exc id / exc name) missing");
}
esi.local = local;
esi.uploaded = uploadState;
return esi;
}
/// Topic
//QJsonObject Topic::toJson() const
//{
// every topic type is different.....
//}
/*static*/ Topic Topic::topicFromJson(const QJsonObject &obj) noexcept(false)
{
const JsonObject o(obj);
// no optional :(
const auto filterKey = JsonObject::Key(L("filter"));
Topic t; // initializer list too nice for 42.3 :'(
t.id = o.value<int>(L("id"));
t.channel = o.value<int>(L("channel"));
t.description = o.value<QString>(L("description"));
t.type = o.value<int>(L("type"));
t.start = o.value<int>(L("start"));
t.duration = o.value<int>(L("duration"));
t.filename = o.valueOr<QString>(L("filename"), o.valueOr<QString>(L("src"), {}));
t.filter = (o.isNull(filterKey) ? -1 : o.value<int>(filterKey));
t.filterSet = o.contains<int>(filterKey);
return t;
}
/*static*/ QVector<Topic> Topic::topicsFromJson(const QJsonArray &arr) noexcept(false)
{
QVector<Topic> ret;
for (const auto &val : arr)
{
const QJsonObject obj(val.toObject());
ret += topicFromJson(obj);
}
return ret;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,190 @@
#pragma once
/**
* @RDE_HEADER@
* js-file parsers & specific script.js & topics.js parsers
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, EZSZ, 2024-02-12
*/
#include <Common2Core/thp2commoncore_export.h>
//#include <NGLLogger/INGLLogger.h>
#include <LtsUtilsCore/json.h>
#include <Common2Core/tpm_tools.h>
#include <QJsonArray>
#include <QFile>
#include <QMap>
using namespace LtsUtils;
namespace TakeHomePackageManager
{
/// JsParser
template <typename LtsJsonType, typename QJsonType>
class THP2COMMONCORE_EXPORT JsParser
{
public:
explicit JsParser(const QString &globalThisName, const QString &filePath, bool mustExist = false) noexcept(false);
void save() noexcept(false);
static QJsonType read(const QString &globalThisName, const QString &filePath, bool mustExist = false) noexcept(false);
static void write(const QString &globalThisName, const QJsonType &json, const QString &filePath, bool mustExist = false) noexcept(false);
static LtsJsonType parse(const QByteArray &data) noexcept(false);
QJsonDocument document() const;
const QJsonType &get() const;
void set(const QJsonType &json);
protected:
LtsJsonType load(bool mustExist) const noexcept(false);
static QString _parse(const QByteArray &data, bool isObject) noexcept(false);
QByteArray _load(bool mustExist) const noexcept(false);
const QString m_globalThisName;
const QString m_filePath;
LtsJsonType m_json;
bool m_dataChanged { false };
};
template class THP2COMMONCORE_EXPORT JsParser<JsonObject, QJsonObject>;
using JsObjectParser = JsParser<JsonObject, QJsonObject>;
template class THP2COMMONCORE_EXPORT JsParser<JsonArray, QJsonArray>;
using JsArrayParser = JsParser<JsonArray, QJsonArray>;
/// ExportedScriptInformation
struct THP2COMMONCORE_EXPORT ExportedScriptInformation
{
enum class UploadState
{
ThpUnknownUploadState,
ThpNotUploaded,
ThpAlreadyUploaded,
};
struct Exercise
{
int id;
QString name;
};
struct Script
{
int id;
QString name;
QString customName;
};
bool operator==(const ExportedScriptInformation &other) const
{
return (id() == other.id());
}
QString id() const;
QDateTime createdAt; // NOTE: invalid if script only on server
Exercise exercise;
Script script;
// things not saved to json - only informational
QDateTime createdAtServer = {};
bool local = true; // only set to false if it's from setServerContent and NOT locally available!
UploadState uploaded = UploadState::ThpUnknownUploadState;
void setLocal(const QDateTime &_createdAt) { local = true; createdAt = _createdAt; }
void unsetLocal() { local = false; createdAt = {}; }
bool isUploaded() const { return (uploaded == UploadState::ThpAlreadyUploaded); }
void setUploaded(const QDateTime &_createdAtServer) { uploaded = UploadState::ThpAlreadyUploaded; createdAtServer = _createdAtServer; }
void unsetUploaded() { uploaded = UploadState::ThpNotUploaded; createdAtServer= {}; }
QJsonValue toJson() const;
static ExportedScriptInformation fromJson(const QJsonValue &val,
const bool local = true,
const UploadState uploadState = UploadState::ThpUnknownUploadState);
};
/// ScriptsJsParser
class THP2COMMONCORE_EXPORT ScriptsJsParser : /*protected*/ public JsObjectParser
{
public:
ScriptsJsParser(const QString &root, bool mustExist = false) noexcept(false);
void save() noexcept(false);
const QMap<QString, ExportedScriptInformation> &getExportedScriptInformations() const;
const ExportedScriptInformation &getExportedScriptInformation(const QString &scriptId) const noexcept(false);
bool hasExportedScriptInformation(const QString &scriptId) const;
void setExportedScriptInformations(const QVector<ExportedScriptInformation> &esis) noexcept(false);
void addExportedScriptInformation(const ExportedScriptInformation &esi) noexcept(false);
void addExportedScriptInformations(const QVector<ExportedScriptInformation> &esis) noexcept(false);
void removeExportedScriptInformation(const QString &scriptId) noexcept(false);
private:
QMap<QString /*scriptId*/, ExportedScriptInformation> m_esis;
bool m_dataChanged { false };
};
/// Topic
struct THP2COMMONCORE_EXPORT Topic
{
int id;
int channel;
QString description; // user given description
int type;
int start;
int duration;
QString filename;
// bool getFilter(int &_filter) // TODO: optional Ò_ó
// {
// if (filterSet)
// _filter = filter;
// return filterSet;
// }
//private:
int filter;
bool filterSet = false;
static Topic topicFromJson(const QJsonObject &obj) noexcept(false);
static QVector<Topic> topicsFromJson(const QJsonArray &arr) noexcept(false);
//QJsonObject toJson() const;
};
///// TopicsJsParser
//class THP2COMMONCORE_EXPORT TopicsJsParser : /*protected*/ public JsArrayParser
//{
//public:
// TopicsJsParser(const QString &root, bool mustExist = false) noexcept(false);
// void save() noexcept(false);
// const QMap<int, Topic> &getTopics() const;
// const Topic &getTopic(const int topicId) const noexcept(false);
// bool hasTopic(const int topicId) const;
// void setTopics(const QVector<Topic> &topics) noexcept(false);
// void addTopic(const Topic &topic) noexcept(false);
// void addTopics(const QVector<Topic> &topics) noexcept(false);
// void removeTopic(const int topicId) noexcept(false);
//private:
// QMap<int /*topicId*/, Topic> m_topics;
// bool m_dataChanged { false };
//};
} // namespace TakeHomePackageManager
@@ -0,0 +1,159 @@
/**
* @RDE_HEADER@
* Some useful tools to be shared
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, EZSZ, 2024-02-12
*/
#include "tpm_tools.h"
#include "tpm_js_parser.h"
#include <QFile>
#include <QSaveFile>
#include <QDir>
#include <memory>
using namespace std;
//std::string error(const QString &msg) { return msg.toStdString(); }
namespace TakeHomePackageManager
{
/// SaveFile
SaveFile::SaveFile(SaveFile &&other)
{
m_saveFile.swap(other.m_saveFile);
}
SaveFile::SaveFile(const QString &filePath) noexcept(false)
: m_saveFile(new QSaveFile(filePath))
{}
SaveFile::~SaveFile() noexcept(false)
{
if (!m_saveFile->isOpen())
{
return;
}
if (!m_saveFile->commit())
{
throw std::runtime_error("Commit on temporary save file '" + m_saveFile->fileName().toStdString() + "' failed: " + m_saveFile->errorString().toStdString());
}
}
SaveFile SaveFile::open(const QString &filePath) noexcept(false)
{
SaveFile f(filePath);
if (!f->open(QIODevice::OpenModeFlag::WriteOnly /* implies truncate */))
{
throw runtime_error("Failed to create temporary save file '" + filePath.toStdString() + "': " + f->errorString().toStdString());
}
return f;
}
/// etc.
QString localThpDir() { return (QDir::homePath() + QDir::separator() + L("THP2")); } // e.g. /shd/CTC/WS/THP2
QString serverThpDir() { return (QDir::homePath() + QDir::separator() + L("THP2-Uploads")); } // e.g. /shd/CTC/SV/THP2-Uploads
QString thpOverviewLocation() { return L("/shd/CTC/WS/share/Lts/THP-overview"); }
QString scriptsLocation(const QString &root) { return root + QDir::separator() + L("scripts"); } // e.g. /shd/CTC/WS/THP2/scripts
QString scriptsJsLocation(const QString &root) { return scriptsLocation(root) + QDir::separator() + L("scripts.js"); } // e.g. /shd/CTC/WS/THP2/scripts/scripts.js
QString scriptLocation(const QString &root, const QString &scriptId) { return scriptsLocation(root) + QDir::separator() + scriptId; } // NOTE: counterpart of scriptArchiveLocation
QString scriptHtmlLocation(const QString &root, const QString &scriptId) { return scriptLocation(root, scriptId) + QDir::separator() + L("index.html"); }
QString scriptArchiveLocation(const QString &root, const QString &scriptId) { return scriptLocation(root, scriptId) + L(".tar.bz2"); } // NOTE: FLM only! client uploads as archive - server stores as archive (use instead of scriptLocation)
QString topicsLocation(const QString &root, const QString &scriptId) { return scriptLocation(root, scriptId) + QDir::separator() + L("topics"); }
QString topicsJsLocation(const QString &root, const QString &scriptId) { return topicsLocation(root, scriptId) + QDir::separator() + L("topics.js"); }
QString topicLocation(const QString &root, const QString &scriptId, const QString &topicFilename) { return topicsLocation(root, scriptId) + QDir::separator() + topicFilename; }
//QVector<ExportedScriptInformation> esisFromScriptsJs(const QString &thpRoot) noexcept(false)
//{
// const QJsonObject obj(JsObjectParser::read(L("scriptsJs"), scriptsJsLocation(thpRoot), false));
// QVector<ExportedScriptInformation> esis;
// esis.reserve(obj.count());
// for (auto it = obj.constBegin(); it != obj.constEnd(); ++it)
// {
// const auto esi(ExportedScriptInformation::fromJson(it.value(), true /* local */, ExportedScriptInformation::UploadState::ThpUnknownUploadState));
// //DEBUG_LOG(m_logger) << "obj=" << QJsonDocument(obj).toJson(QJsonDocument::Compact);
// //esi.rootDirectory = rootDirectoryToScan; // to determine if this script is from THP-Download or THP-Export directory (for USB Management, which can copy from both dirs)
// esis.push_back(esi);
// }
// return esis;
//}
SaveFile openSaveFile(const QString &filePath) noexcept(false)
{
return SaveFile::open(filePath);
}
void writeSaveFile(const QString &filePath, const QByteArray &content) noexcept(false)
{
SaveFile f(openSaveFile(filePath));
f->write(content);
}
QByteArray readFile(const QString &filename, bool mustExist) noexcept(false)
{
QFile f(filename);
if (!f.exists())
{
if (mustExist)
{
throw runtime_error("File '" + filename.toStdString() + "' doesn't exist");
}
return {};
}
if (!f.open(QIODevice::ReadOnly))
{
throw runtime_error("Unable to read file at " + filename.toStdString() + ": " + f.errorString().toStdString());
}
return f.readAll();
}
void openFile(QFile &file, bool readOnly, bool existingOnly) noexcept(false)
{
QFlags<QIODevice::OpenModeFlag> flags(QIODevice::ReadWrite);
if (readOnly)
{
flags.setFlag(QIODevice::ReadOnly);
}
// if (existingOnly)
// {
// flags.setFlag(QIODevice::ExistingOnly); 42.3 is missing this flag -.-
// }
// polyfill for above ^
if (!file.exists() && existingOnly)
{
throw runtime_error("Unable to open file '" + file.fileName().toStdString() + "' for " + (readOnly ? "reading" : "r/w") + ": <file does not exist!>");
}
if (!file.open(flags))
{
throw runtime_error("Unable to open file '" + file.fileName().toStdString() + "' for " + (readOnly ? "reading" : "r/w") + ": " + file.errorString().toStdString());
}
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,174 @@
#pragma once
/**
* @RDE_HEADER@
* Some useful tools to be shared
* $Id: tpm_exported_scripts_model.h 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_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, EZSZ, 2024-02-12
*/
//#include <logger.h>
#include <Common2Core/thp2commoncore_export.h>
#include <memory>
#include <QSaveFile>
#include <QEventLoop>
#include <QTimer>
#include <QDateTime>
#include <QDebug>
#ifdef Q_OS_UNIX
#define _compiler_warning(x) _Pragma(#x)
#define compiler_warning(x) _compiler_warning(GCC warning x)
#else
#define compiler_warning(x)
#endif
class QFile;
namespace TakeHomePackageManager
{
/// SaveFile
class THP2COMMONCORE_EXPORT SaveFile // NOTE: auto-commits - call `->cancelWriting()` to not commit the file
{
public:
SaveFile() = delete;
SaveFile(SaveFile &&other);
~SaveFile() noexcept(false);
static SaveFile open(const QString &filePath) noexcept(false);
QSaveFile *get() { return m_saveFile.get(); }
QSaveFile *operator->() { return m_saveFile.get(); }
private:
explicit SaveFile(const QString &filePath) noexcept(false);
std::unique_ptr<QSaveFile> m_saveFile;
};
/// etc.
THP2COMMONCORE_EXPORT void writeSaveFile(const QString &filePath, const QByteArray &content) noexcept(false);
THP2COMMONCORE_EXPORT QByteArray readFile(const QString &filename, bool mustExist = false) noexcept(false);
THP2COMMONCORE_EXPORT void openFile(QFile &file, bool readOnly = false, bool existingOnly = false) noexcept(false);
THP2COMMONCORE_EXPORT void writeToFileOrRollback(QFile &file, const QByteArray &data) noexcept(false);
THP2COMMONCORE_EXPORT void writeToFileOrRollback(QFile &file, const QString &string) noexcept(false);
THP2COMMONCORE_EXPORT SaveFile openSaveFile(const QString &filePath) noexcept(false);
//THP2COMMONCORE_EXPORT QVector<ExportedScriptInformation> esisFromScriptsJs(const QString &thpRoot) noexcept(false);
THP2COMMONCORE_EXPORT QString localThpDir();
THP2COMMONCORE_EXPORT QString serverThpDir();
THP2COMMONCORE_EXPORT QString thpOverviewLocation();
THP2COMMONCORE_EXPORT QString scriptsLocation(const QString &root);
THP2COMMONCORE_EXPORT QString scriptsJsLocation(const QString &root);
THP2COMMONCORE_EXPORT QString scriptLocation(const QString &root, const QString &scriptId);
THP2COMMONCORE_EXPORT QString scriptHtmlLocation(const QString &root, const QString &scriptId);
THP2COMMONCORE_EXPORT QString scriptArchiveLocation(const QString &root, const QString &scriptId);
THP2COMMONCORE_EXPORT QString topicsJsLocation(const QString &root, const QString &scriptId);
THP2COMMONCORE_EXPORT QString topicsLocation(const QString &root, const QString &scriptId);
THP2COMMONCORE_EXPORT QString topicLocation(const QString &root, const QString &scriptId, const QString &topicFilename);
// NOTE: DeferedDelete (deleteLater) will not trigger deletions while inside any of the await functions (nested event loops)!
template <typename Func1>
void awaitx(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const int timeoutMs = 0)
{
QEventLoop x;
QObject::connect(sender, signal, &x, &QEventLoop::quit);
std::unique_ptr<QTimer> t(new QTimer); // NOTE: do not use make_unique because of 42.3!
bool timeout = false;
if (timeoutMs > 0)
{
t->setInterval(timeoutMs);
t->setSingleShot(true);
QObject::connect(t.get(), &QTimer::timeout, &x, [&timeout](){ timeout = true; });
QObject::connect(t.get(), &QTimer::timeout, &x, &QEventLoop::quit);
}
x.exec();
if (timeout)
{
throw std::runtime_error("Timeout.");
}
}
template <typename Func1, typename Func2>
void awaitWithCancelationSignalx(const typename QtPrivate::FunctionPointer<Func1>::Object *sender1, Func1 signal1,
const typename QtPrivate::FunctionPointer<Func2>::Object *sender2, Func2 signal2,
const int timeoutMs = 0)
{
QEventLoop x;
bool success = false;
QObject::connect(sender1, signal1, &x, [&success](){ success = true; /*qWarning() << "=> SUCCESS";*/});
QObject::connect(sender1, signal1, &x, &QEventLoop::quit, Qt::QueuedConnection);
// bool cancel = false;
// QObject::connect(sender2, signal2, &x, [/*&cancel*/](){ /*cancel = true;*/ qWarning() << "=> cancel....";});
QObject::connect(sender2, signal2, &x, &QEventLoop::quit, Qt::QueuedConnection);
std::unique_ptr<QTimer> t(new QTimer); // NOTE: do not use make_unique because of 42.3!
bool timeout = false;
if (timeoutMs > 0)
{
t->setInterval(timeoutMs);
t->setSingleShot(true);
QObject::connect(t.get(), &QTimer::timeout, &x, [&timeout](){ timeout = true; });
QObject::connect(t.get(), &QTimer::timeout, &x, &QEventLoop::quit, Qt::QueuedConnection);
}
//qWarning() << "## STARTING AWAIT NOW!";
x.exec();
//qWarning() << "## AWAIT DONE!";
if (timeout && !success)
{
throw std::runtime_error("Timeout.");
}
if (!success)
{
throw std::runtime_error("Canceled.");
}
}
}
@@ -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
@@ -0,0 +1,46 @@
#
# $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, 2024-01-11
#
SET(src
tpm_usb_gui_plugin.cpp
tpm_usb_gui_plugin.h
tpm_usb_dialog.cpp
tpm_usb_dialog.h
logger.cpp
logger.h
)
QT5_WRAP_UI(src
tpm_usb2_dialog.ui
)
NGL_ADD_LIBRARY2(WS Thp2UsbManagementGuiPlugin MODULE ${src})
NGL_TARGET_COMPILE_DEFINITIONS(Thp2UsbManagementGuiPlugin PRIVATE L=QStringLiteral)
NGL_TARGET_LINK_LIBRARIES(Thp2UsbManagementGuiPlugin
Thp2Common
Thp2CommonCore
Tpm2GuiPluginInterface
Qt5::Widgets
Qt5::Network
Qt5::Concurrent
NGLLogger
LtsWidgets
LtsSession
LtsDataView
nglcompression
nglsettings
ngltools
LtsSessionData
# for creating data.json
#AsmDataPluginInterface
#ExmDataPluginInterface
)
NGL_INSTALL_TARGET(Thp2UsbManagementGuiPlugin)
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $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, SLTE, 2024-01-11
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM2.USB.";
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 CentralEffectSimulation
* $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, SLTE, 2024-01-11
*/
#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,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>USB2Dialog</class>
<widget class="QWidget" name="USB2Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1081</width>
<height>541</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="verticalGroupBox">
<property name="title">
<string>Local</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>THPs</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="tvLocal">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Topics</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="tvLocalTopics">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pbCopy">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy selected THP + Topics to USB</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="verticalGroupBox_2">
<property name="title">
<string>On USB</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Path:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true"> - </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbCHange">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Change path</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbUnmount">
<property name="text">
<string>Unmount USB Device</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>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>THPs</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="tvUSB">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pbRemove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove selected THPs from USB</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,638 @@
/**
* @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, SLTE, 2024-01-11
*/
#include <ui_tpm_usb2_dialog.h>
#include "tpm_usb_dialog.h"
#include <Common2/tpm_file_copy.h>
#include <Common2/tpm_process_dialog.h>
#include <Common2/tpm_exported_scripts_model.h>
#include <Common2/tpm_topics_model.h>
#include <Common2Core/tpm_js_parser.h>
#include <nglcompression/nglcomp_compress.h>
#include <nglsettings/nglsettings_connections.h>
#include <nglsettings/nglsettings_settings.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 <ngltools/ngltools_filesystem.h>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QTemporaryFile>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QLocale>
#include <QTimer>
#include <QFileSystemWatcher>
#include <QTreeView>
#include <QFuture>
#include <QFutureWatcher>
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QVector>
//#include <QThread>
#include <QtConcurrent>
#include <QDirIterator>
#include <QFileDialog>
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
#include <QDebug>
#ifdef Q_OS_UNIX
#define _compiler_warning(x) _Pragma(#x)
#define compiler_warning(x) _compiler_warning(GCC warning x)
#define TMP_WARNING_LOG(x) compiler_warning("Temporary warning log active!") \
WARNING_LOG(x)
#else
#define compiler_warning(x)
#define TMP_WARNING_LOG(x) WARNING_LOG(x)
#endif
#ifndef ELOG
#define ELOG ERROR_LOG(m_logger) // polyfill
#endif
typedef std::runtime_error error;
namespace TakeHomePackageManager
{
/// USB2Dialog
USB2Dialog::USB2Dialog(LtsSession::LtsSession *session, QWidget *parent)
: LtsWidgets::LtsDialog(parent)
, m_localThpModel(new ExportedScriptsModel(this))
, m_localTopicsModel(new TopicsModel(this))
, m_usbThpModel(new ExportedScriptsModel(this))
, m_ui(new Ui::USB2Dialog)
, m_session(session)
//, m_fsWatcher(new QFileSystemWatcher(this))
// , m_exerciseId(m_session->exerciseId())
// , m_paths(0)
, m_logger(getLogger("USB2Dialog"))
{
m_ui->setupUi(this);
WARNING_LOG(m_logger)<<" USB2Dialog::USB2Dialog() START ";
connect(m_ui->pbCHange, &QPushButton::clicked,
this, &USB2Dialog::slotChangePath);
connect(m_ui->pbCopy, &QPushButton::clicked,
this, &USB2Dialog::slotCopy);
connect(m_ui->pbRemove, &QPushButton::clicked,
this, &USB2Dialog::slotRemove);
connect(m_ui->pbUnmount, &QPushButton::clicked, this, &USB2Dialog::slotUnmount);
reset();
#ifndef Q_OS_UNIX
m_ui->pbUnmount->hide();
#endif
m_ui->tvUSB->setModel(m_usbThpModel);
#ifndef LTS_DEBUG
m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColId);
#endif
m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColLocation);
m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColCreatedAtServer);
m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColUploadStatus);
m_ui->tvLocal->setModel(m_localThpModel); // important to set model before setting hidden columns
#ifndef LTS_DEBUG
m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColId);
#endif
m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColLocation);
m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColCreatedAtServer);
m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColUploadStatus);
m_ui->tvLocalTopics->setModel(m_localTopicsModel);
connect(m_ui->tvLocal->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &USB2Dialog::slotSelectionChangedLocal);
connect(m_ui->tvUSB->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &USB2Dialog::slotSelectionChangedUSB);
try{
initLocalModel(); // throws!
}
catch(...)
{
WARNING_LOG(m_logger)<<" data corrupted path: "<< localThpDir();
new LtsWidgets::LtsMessageDialogNew(this, tr("Couldn't Load THP(s)"));
}
setOnlineHelpFilename(QStringLiteral("Script_Editor-Export_Script_Administration"));
WARNING_LOG(m_logger)<<" USB2Dialog::USB2Dialog() END ";
}
USB2Dialog::~USB2Dialog()
{
delete m_ui;
}
void USB2Dialog::initUsbModel()
{
try
{
m_usbThpModel->init(m_usbRoot);
}
catch (const error &e)
{
new LtsWidgets::LtsMessageDialogNew(this, tr("The directory '%1' doesn't contain valid THP data.").arg(m_usbRoot));
ELOG << "cannot initialize usb thp model from usb root: " << m_usbRoot << " - " << e.what();
reset();
return;
}
m_ui->lblPath->setText(m_usbRoot);
m_ui->pbCopy->setEnabled(m_ui->tvLocal->selectionModel()->selectedRows().size() == 1);
}
/*!
* \brief USB2Dialog::unmountUSB
* \param device
* \details Use lsblk to get path (/dev/sdb1) from name(Niklas-Stick), then start normal umounting command with the part
* \author Niklas Schmidt
*/
void USB2Dialog::unmountUSB(const QString &device)
{
QProcess process;
/*!
* The sed is for substitute the '\x20' (HEX decimal for space) with real space
!*/
//
process.start(QStringLiteral("bash"), QStringList() << QStringLiteral("-c") << QStringLiteral("lsblk -o NAME,MOUNTPOINT -r"));
process.waitForFinished();
process.setReadChannel(QProcess::StandardOutput);
while (process.canReadLine())
{
QString outputLine = QString::fromUtf8(process.readLine());
DEBUG_LOG(m_logger) << "OutputLine: " << outputLine;
auto outputDevicePath = outputLine.split(QStringLiteral(" ")).last();
DEBUG_LOG(m_logger) << "OutputDevicePath: " << outputDevicePath;
auto outputPathStripped = outputDevicePath.replace(QLatin1String("\\x20"), QLatin1String(" "));
DEBUG_LOG(m_logger) << "OutputPathStripped: " << outputPathStripped;
auto outputDeviceName = outputPathStripped.split(QStringLiteral("/")).last();
DEBUG_LOG(m_logger) << "OutputDeviceName: " << outputDeviceName;
if (outputDeviceName.trimmed().compare(device.trimmed()) == 0)
{
auto serialPort = outputLine.split(QStringLiteral(" ")).first();
QString absPath = QStringLiteral("/dev/") + serialPort;
DEBUG_LOG(m_logger) << "Serial Port: " << absPath;
process.start(QStringLiteral("udisksctl"), QStringList() << QStringLiteral("unmount") << QStringLiteral("-b") << absPath);
process.waitForFinished();
reset();
return;
}
}
new LtsWidgets::LtsMessageDialogNew(this, tr("Cant unmount USB device"));
}
template <typename Func1>
void await(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, int timeoutMs = 0)
{
QEventLoop x;
QObject::connect(sender, signal, &x, &QEventLoop::quit);
std::unique_ptr<QTimer> t(nullptr);
bool timeout = false;
if (timeoutMs > 0)
{
t = std::make_unique<QTimer>(new QTimer);
t->setInterval(timeoutMs);
t->setSingleShot(true);
QObject::connect(t.get(), &QTimer::timeout, &x, [&timeout](){ timeout = true; });
QObject::connect(t.get(), &QTimer::timeout, &x, &QEventLoop::quit);
}
x.exec();
if (timeoutMs && t)
{
throw std::runtime_error("Timeout.");
}
}
void USB2Dialog::reset()
{
m_ui->pbCopy->setEnabled(false);
m_ui->pbRemove->setEnabled(false);
m_usbRoot.clear();
m_usbThpModel->clear();
retranslate(); // lblPath !
}
void USB2Dialog::slotChangePath()
{
QFileDialog *dlg = new QFileDialog(this);
dlg->setFileMode(QFileDialog::Directory);
dlg->setOption(QFileDialog::ShowDirsOnly);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->open(); // use exec?
bool accepted = false;
connect(dlg, &QFileDialog::finished, this, [&accepted](const int value){accepted = (value == 1);});
await(dlg, &QFileDialog::finished);
if (!accepted)
return;
const QStringList selected(dlg->selectedFiles());
if (selected.count() != 1)
return;
reset();
m_usbRoot = selected.first();
const QString scriptsJsonPath(scriptsJsLocation(m_usbRoot));
qWarning() << "m_usbRoot" << m_usbRoot << "scriptsJsonPath" << scriptsJsonPath;
if (QFile::exists(scriptsJsonPath))
{
qWarning() << "scripts.js exists!" << scriptsJsonPath;
initUsbModel();
return;
}
qWarning() << "scripts.js doesn't exists";
auto res = LtsWidgets::LtsMessageDialog::showMessage(this, tr("The selected directory '%1' doesn't contain THPs yet. <br />Do you want to initialize the directory now?").arg(m_usbRoot),
{}, LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::Cancel);
if (res != LtsWidgets::Button::Yes)
{
return;
}
try
{
if (!QDir().mkpath(scriptsLocation(m_usbRoot)))
{
throw error("mkpath for m_usbRoot/scripts failed");
}
ScriptsJsParser(m_usbRoot, false).save(); // write empty scripts.js file
copyFiles(getFilesFromFolder(thpOverviewLocation(), m_usbRoot + QDir::separator()), this);
}
catch(const std::runtime_error &err)
{
ELOG << err.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("The selected directory '%1' can't be written to correctly and the initialization was aborted.").arg(m_usbRoot));
return;
}
initUsbModel();
}
/*!
* \brief USB2Dialog::slotUnmount
* \details Check if name of usb device is part of the current path. Extract name of the usb device and start unmounting
* \author Niklas Schmidt
*/
void USB2Dialog::slotUnmount()
{
QString usbPath = QStringLiteral("/run/media/ctc_ws/");
//#ifdef LTS_DEBUG
// usbPath= QStringLiteral("/run/media/ctc/");
//#endif
QDir usbDir(usbPath);
usbDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
QStringList folderNames = usbDir.entryList();
for(const auto &folder : qAsConst(folderNames))
{
DEBUG_LOG(m_logger) << "Folder: " << folder << " USB Root: " << m_usbRoot;
if(m_usbRoot.contains(folder))
{
unmountUSB(folder);
return;
}
}
new LtsWidgets::LtsMessageDialogNew(this, tr("Path is not inside of a USB device"));
}
void USB2Dialog::slotCopy()
{
if (m_usbRoot.isEmpty())
return;
const auto selectedTopicIndices(m_ui->tvLocalTopics->selectionModel()->selectedRows());
if (selectedTopicIndices.isEmpty())
{
new LtsWidgets::LtsMessageDialogNew(this, tr("To export a THP, please select at least one topic in the 'Topics' table."));
return;
}
const QModelIndexList mil(m_ui->tvLocal->selectionModel()->selectedRows());
if (mil.size() != 1)
return;
const auto esi = m_localThpModel->getRowData(mil.first().row());
const auto &scriptId = esi.id();
try
{
// if script already in json, remove - also try to remove script folder (if exists - otherwise ignore)
removeScript(scriptId);
const QString srcPath(scriptLocation(localThpDir(), scriptId));
const QString dstPath(scriptLocation(m_usbRoot, scriptId));
CopyInfoList fileList;
fileList += getFilesFromFolder(srcPath, dstPath, { L("topics") }); // excludes topics
QSet<int> topicsToCopy;
for (const auto &idx : selectedTopicIndices)
{
bool k = false;
const int topicId = m_localTopicsModel->data(idx, Qt::UserRole).toInt(&k);
if (!k)
{
continue;
}
topicsToCopy += topicId;
}
qWarning() << "topicsToCopy" << topicsToCopy;
// only copy selected topics ...
const QString topicsDestDirPath(topicsLocation(m_usbRoot, scriptId)); // DEST!
const QJsonArray topicsArray(JsArrayParser::read(L("topicsJs"), topicsJsLocation(localThpDir(), scriptId) /*src*/, true));
const auto topics = Topic::topicsFromJson(topicsArray);
for (const auto &topic : topics)
{
qWarning() << "topic" << topic.id;
if (!topicsToCopy.contains(topic.id) || topic.filename.isEmpty() /* e.g. markers don't have files, no need to copy here */)
{
qWarning() << "SKIP FILE!";
continue;
}
const QFileInfo topicFile(topicLocation(localThpDir(), scriptId, topic.filename)); // SRC
fileList += { topicFile.absolutePath(), topicsDestDirPath, topicFile };
qWarning() << "!!!!! copy topic" << topicFile.absoluteFilePath() << "to" << topicsDestDirPath;
}
copyFiles(fileList, this);
// then finally write new, adjusted topics.js file (take original src file, write to destination)
QJsonArray finalTopicsArray;
for (const auto &topicVal : topicsArray)
{
auto topic = Topic::topicFromJson(topicVal.toObject());
if (!topicsToCopy.contains(topic.id))
{
continue;
}
finalTopicsArray += topicVal;
}
if (!QDir().mkpath(topicsLocation(m_usbRoot, scriptId))) // if no topics were copied (only marker topics), at least create the directory first for the topics.js file ...
{
throw error("mkpath for directory 'topics' failed");
}
JsArrayParser::write(L("topicsJs"), finalTopicsArray, topicsJsLocation(m_usbRoot, scriptId)); // NOTE: new, dst file!
// done! add copied script to model
m_usbThpModel->addScript(esi.exercise.id, esi.exercise.name, esi.script.id, esi.script.name, esi.createdAt);
}
catch(const error &e)
{
ELOG << e.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully!"));
return;
}
new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully"));
}
void USB2Dialog::removeScript(const QString &scriptId) noexcept(false)
{
if (scriptId.length() < 3)
{
ELOG << "scriptId is not in format 'n.n' - skipping for safety reasons - scriptId was: " << scriptId;
throw error("scriptId.length() < 3");
}
QDir dir(scriptLocation(m_usbRoot, 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 error("!dir.removeRecursively()");
}
m_usbThpModel->removeLocalScript(scriptId);
}
void USB2Dialog::slotRemove()
{
// this operation is fast enough to NOT do it async (meh, maybe not)
if (m_usbRoot.isEmpty())
return;
const QModelIndexList mil = m_ui->tvUSB->selectionModel()->selectedRows();
if (mil.size() < 1)
return;
DEBUG_LOG(m_logger) << "mil.size()" << mil.size();
QStringList scriptIdList;
for (const QModelIndex &idx : mil)
{
const QString scriptId(idx.data(Qt::UserRole).toString());
scriptIdList += scriptId;
}
auto res = LtsWidgets::LtsMessageDialog::showMessage(this, tr("Do you really want to remove the following script(s)?<br>%1").arg(scriptIdList.join(L(", "))),
{}, LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::Cancel);
if (res != LtsWidgets::Button::Yes)
{
return;
}
try
{
for (const auto &scriptId : scriptIdList)
{
removeScript(scriptId);
}
}
catch(const std::runtime_error &e)
{
ELOG << e.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Failed to remove THP from from external medium."));
}
}
void USB2Dialog::slotSelectionChangedUSB()
{
const QModelIndexList mil(m_ui->tvUSB->selectionModel()->selectedRows());
bool enable = (mil.size() >= 1 && !m_usbRoot.isEmpty());
m_ui->pbRemove->setEnabled(enable);
}
QByteArray readFile(const QString &filename) noexcept(false)
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
{
throw error("Unable to read file at " + filename.toStdString() + ": " + f.errorString().toStdString());
}
return f.readAll();
}
QJsonDocument jsonDocument(const QByteArray &data) noexcept(false)
{
QJsonParseError err;
QJsonDocument doc(QJsonDocument::fromJson(data, &err));
if (err.error != QJsonParseError::NoError)
{
throw error(L("json parse error: %1 at offset %2").arg(err.errorString()).arg(err.offset).toStdString());
}
return doc;
}
void USB2Dialog::slotSelectionChangedLocal()
{
m_ui->pbCopy->setEnabled(false);
m_localTopicsModel->clear();
const QModelIndexList mil(m_ui->tvLocal->selectionModel()->selectedRows());
if (mil.size() != 1)
{
return;
}
const QString scriptId(mil.first().data(Qt::UserRole).toString());
try
{
qWarning() << "############### scriptId" << scriptId;
const QJsonArray topicsArray(JsArrayParser::read(L("topicsJs"), topicsJsLocation(localThpDir(), scriptId), true));
const auto topics = Topic::topicsFromJson(topicsArray);
for (const auto &topic : topics)
{
// if (topic.filterSet)
// continue;
m_localTopicsModel->addTopic(topic);
}
}
catch(const error &e)
{
ELOG << e.what();
new LtsWidgets::LtsMessageDialogNew(this, tr("Unable to read topics for this THP. Please select another THP."));
return;
}
m_ui->tvLocalTopics->selectAll();
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColId);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColChannel);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColFilter);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColDescription);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColType);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColStart);
m_ui->tvLocalTopics->resizeColumnToContents(TopicsModel::ColDuration);
if (!m_usbRoot.isEmpty())
{
m_ui->pbCopy->setEnabled(true);
}
}
void USB2Dialog::retranslate()
{
translate(m_ui, tr("Export THP USB"));
m_ui->lblPath->setText(m_usbRoot.isEmpty() ? tr("(none)") : m_usbRoot);
}
void USB2Dialog::initLocalModel() noexcept(false)
{
DEBUG_LOG(m_logger) << "(re)initModel !!";
// TODO: move to model
// compiler_warning("TODO")
// m_fsWatcher->addPath(getRootFolder());
// m_fsWatcher->addPath(getContentFolder(m_session->exerciseId()));
m_localThpModel->init(localThpDir(), true /* watch script.json */);
slotSelectionChangedLocal(); // aka. enaDisa
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,83 @@
/**
* @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, SLTE, 2024-01-11
*/
#pragma once
#include "logger.h"
#include <Common2/tpm_file_copy.h>
#include <LtsWidgets/lts_dialog.h>
#include <QFileInfoList>
namespace Ui
{
class USB2Dialog;
}
namespace LtsSession
{
class LtsSession;
}
namespace TakeHomePackageManager
{
class ExportedScriptsModel;
class TopicsModel;
class USB2Dialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
/**
* @brief USBDialog => WARNING: throws! catch exception!
* @param session
* @param parent
*/
explicit USB2Dialog(LtsSession::LtsSession *session, QWidget *parent = nullptr);
~USB2Dialog();
void retranslate() override;
static QString getContentFolder(const QString &rootFolder, int exerciseId);
QString getContentFolder(const QString &rootFolder) const;
static QString exerciseDirectoryName(int exerciseId);
static QString getScriptFolder(const QString &rootFolder, int exerciseId, int scriptId);
signals:
void nextMap();
private slots:
void slotSelectionChangedLocal();
void slotSelectionChangedUSB();
void slotCopy();
void slotRemove();
void slotChangePath();
void slotUnmount();
private:
void removeScript(const QString &scriptId);
void reset();
void initLocalModel() noexcept(false);
void initUsbModel();
bool checkIsInUSB(const QString &path);
void unmountUSB(const QString &device);
ExportedScriptsModel *m_localThpModel;
TopicsModel *m_localTopicsModel;
ExportedScriptsModel *m_usbThpModel;
Ui::USB2Dialog *m_ui;
LtsSession::LtsSession *m_session;
// int m_exerciseId;
// int m_paths;
QMap<QString, QFileInfoList> m_files;
QString m_usbRoot;
NGL::INGLLogger &m_logger;
};
} // namespace TakeHomePackageManager
@@ -0,0 +1,93 @@
/**
* @RDE_HEADER@
* GUI Plugin for ExerciseManangement
* $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, SLTE, 2024-01-11
*/
#include "tpm_usb_gui_plugin.h"
#include "tpm_usb_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("Plugin"))
{}
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("THP2.USB.Dialog");
plData.menuIcon = QIcon(QLatin1String("icon:export_documents.svg"));
plData.menuText = tr("Export THP USB");
// plData.operatorRight = OperatorRights::ScriptEditorEdit;
plData.createWidgetFunc = [this](QWidget *)
{
return LtsWidgets::createThrowableDialog<USB2Dialog>(m_session);
};
ret.append(plData);
return ret;
}
} // namespace TakeHomePackageManager
//
@@ -0,0 +1,53 @@
/**
* @RDE_HEADER@
* Gui Plugin for MarkerManagement
* $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, SLTE, 2024-01-11
*/
#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.usb")
Q_INTERFACES(GuiPluginBase::Plugin)
Q_OBJECT
public:
GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return QLatin1String("Thp2UsbManager"); }
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
@@ -0,0 +1,25 @@
/**
* @RDE_HEADER@
* HMI Plugin for TPM2
* $Id: tpm_gui_plugin_interface.h 46225 2019-03-25 13:59:56Z rudolphus_g $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/swp_2023_devel/Lts/Plugins/TakeHomePackageManager/tpm_gui_plugin_interface.h $
* $Author: rudolphus_g $
* Created by Mike Lennart Wille, EZSZ, 2024-02-12
*/
#pragma once
#include <QString>
namespace TakeHomePackageManager
{
class ForeignTpm2GuiInterface
{
public:
ForeignTpm2GuiInterface() = default;
virtual ~ForeignTpm2GuiInterface() = default;
static QString pluginName() { return QStringLiteral("Tpm2GuiPlugin"); }
};
} // namespace TakeHomePackageManager