Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
+18
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+389
@@ -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
|
||||
+65
@@ -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 ©Info : 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
|
||||
+989
@@ -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
|
||||
+189
@@ -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
|
||||
+77
@@ -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
|
||||
+212
@@ -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>
|
||||
+638
@@ -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
|
||||
+93
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
+53
@@ -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
|
||||
Reference in new issue
Block a user