Initial commit

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

No files matched your search

@@ -0,0 +1,46 @@
#
# $Id: CMakeLists.txt 51804 2020-07-31 04:47:28Z fritsche_h $
# $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/CMakeLists.txt $
# $Author: fritsche_h $
#
# Created by Mike Wille, SLTE, 2024-01-11
#
SET(src
logger.cpp
logger.h
tpm_exported_scripts_model.cpp
tpm_exported_scripts_model.h
tpm_process_dialog.cpp
tpm_process_dialog.h
tpm_topics_model.cpp
tpm_topics_model.h
tpm_file_copy.cpp
tpm_file_copy.h
)
QT5_WRAP_UI(src
tpm_process_dialog.ui
)
NGL_ADD_LIBRARY2(WS Thp2Common SHARED ${src})
NGL_TARGET_COMPILE_DEFINITIONS(Thp2Common PRIVATE L=QStringLiteral)
NGL_TARGET_INCLUDE_DIRECTORIES(Thp2Common
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../ # export file
)
NGL_TARGET_LINK_LIBRARIES(Thp2Common
PUBLIC
Thp2CommonCore
PRIVATE
Qt5::Core
Qt5::Concurrent
NGLLogger
LtsSession
LtsUtilsCoreHeaderOnly
TopicEditorBase # topic types
)
NGL_INSTALL_TARGET(Thp2Common)
+39
View File
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Building Config - logging facility
* $Id: logger.cpp 46225 2019-03-25 13:59:56Z rudolphus_g $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/logger.cpp $
* $Author: rudolphus_g $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace TakeHomePackageManager
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "TPM2.Common.";
std::string loggerName;
const size_t len = strlen(name);
loggerName.reserve(sizeof(ns) + len);
loggerName.append(ns, sizeof(ns) - 1);
loggerName.append(name, len);
try
{
return NGL::NGLLoggerBootstrap::getNGLRootLogger().
getNGLLogger(loggerName);
}
catch (const std::exception &ex)
{
fprintf(stderr, "Error retrieving logger: %s", ex.what());
abort();
}
}
} // namespace TakeHomePackageManager
+24
View File
@@ -0,0 +1,24 @@
/**
* @RDE_HEADER@
* Logging for CentralEffectSimulation
* $Id: logger.h 46225 2019-03-25 13:59:56Z rudolphus_g $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/logger.h $
* $Author: rudolphus_g $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#pragma once
#include <NGLLogger/INGLLogger.h>
namespace TakeHomePackageManager
{
typedef NGL::INGLLogger &LOGGER_T;
LOGGER_T getLogger(const char *name);
inline LOGGER_T getLogger(const QString &loggerName)
{
return getLogger(loggerName.toUtf8().constData());
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,389 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.cpp 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.cpp $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include "tpm_exported_scripts_model.h"
#include <ngltools/ngltools_dtg.h>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
#include <QFileSystemWatcher>
#include <QMutableVectorIterator>
namespace TakeHomePackageManager
{
ExportedScriptsModel::ExportedScriptsModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>(
{ tr("id"), tr("Exercise"), tr("Name"), tr("Local version"), tr("Server version"), tr("Location"), tr("Upload status")}, parent)
, m_logger(getLogger("ExportedScriptsModel"))
{}
//ExportedScriptsModel::~ExportedScriptsModel()
//{
// delete m_scriptsJsParser;
//}
void ExportedScriptsModel::init(const QString &rootDirectoryToScan, bool checkForScriptsJsChanges) noexcept(false)
{
m_rootDirectoryToScan = rootDirectoryToScan;
beginResetModel();
std::shared_ptr<int> _(nullptr, [this](int *) { endResetModel(); }); // TODO: QScopeGuard
m_vecData = ScriptsJsParser(m_rootDirectoryToScan).getExportedScriptInformations().values().toVector(); // -.-
if (checkForScriptsJsChanges)
{
delete m_watcher;
m_watcher = new QFileSystemWatcher({ scriptsJsLocation(m_rootDirectoryToScan) }, this);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this](){try{reloadScriptsJs();}catch(...){}});
}
}
void ExportedScriptsModel::reloadScriptsJs() noexcept(false) // setServerContent, but from scripts.js
{
qWarning() << "RELOAD!!";
// scripts.js changed - check if something was changed, added or removed
const auto scriptsJsParser = ScriptsJsParser(m_rootDirectoryToScan);
const auto esiMap = scriptsJsParser.getExportedScriptInformations();
// check if model has local scripts that scriptsJs doesn't have
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (!currentEsi.local) // setServerContent added it and never was in scripts.js
{
continue;
}
// is local
if (esiMap.contains(currentEsi.id())) // still in scripts.js - do nothing
{
continue;
}
// not in scripts.js anymore
if (currentEsi.isUploaded()) // not in scripts.js anymore - set non-local / server-only
{
// not in scripts.js anymore, but still on server - don't remove but say it's not local anymore
currentEsi.unsetLocal();
setRowData(row, currentEsi);
qWarning() << "RELOAD!! UPDATED TO NON-LOCAL!" << currentEsi.id();
continue;
}
// ... also not uploaded
qWarning() << "RELOAD!! REMOVED!" << currentEsi.id();
removeRow(row);
}
const auto esis = esiMap.values().toVector();
for (const auto &localEsi : esis)
{
bool found = false;
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (currentEsi.id() != localEsi.id())
{
continue;
}
// changed
if (localEsi.createdAt > currentEsi.createdAt && localEsi.createdAt > currentEsi.createdAtServer) // is topic in scripts.js newer than currently in model AND in server?
{
currentEsi.createdAt = localEsi.createdAt;
currentEsi.exercise = localEsi.exercise;
currentEsi.script = localEsi.script;
qWarning() << "RELOAD!! UPDATED DATA FROM SERVER!" << currentEsi.id();
setRowData(row, currentEsi);
}
found = true;
break;
}
if (found)
{
continue;
}
// added
qWarning() << "RELOAD!! ADDED!" << localEsi.id();
insertRow(localEsi);
}
}
void ExportedScriptsModel::setServerContent(const QString &jsonObject) // reloadScriptsJs, but from server
{
if (jsonObject.isEmpty()) // no content received yet? no problem, just return
return;
try
{
const QJsonObject scripts(JsonObject::fromJson(jsonObject.toUtf8()).object());
// check if model has server scripts that server doesn't have anymore
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (scripts.contains(currentEsi.id()))
{
continue;
}
// script is in model and not on the server
if (currentEsi.local) // is still there locally, just set it to not uploaded
{
currentEsi.unsetUploaded();
setRowData(row, currentEsi);
continue;
}
// not on server and not local ...
removeRow(row);
}
// not in model
for (const QJsonValue &val : scripts)
{
const auto serverEsi(ExportedScriptInformation::fromJson(val, false /* not local - on server */, ExportedScriptInformation::UploadState::ThpAlreadyUploaded));
bool found = false;
for (int row = 0; row < m_vecData.count(); row++)
{
auto currentEsi = getRowData(row);
if (currentEsi.id() != serverEsi.id())
{
continue;
}
// changed
if (serverEsi.createdAt > currentEsi.createdAt) // server script is newer than local - update info
{
currentEsi.createdAt = serverEsi.createdAtServer;
currentEsi.exercise = serverEsi.exercise;
currentEsi.script = serverEsi.script;
}
currentEsi.setUploaded(serverEsi.createdAtServer);
setRowData(row, currentEsi);
found = true;
break;
}
if (found)
{
continue;
}
// added
insertRow(serverEsi);
}
}
catch(const std::runtime_error &err)
{
ERROR_LOG(m_logger) << "something went wrong: " << err.what();
}
catch(...)
{
ERROR_LOG(m_logger) << "something went wrong";
}
}
QVariant ExportedScriptsModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
const ExportedScriptInformation &esi = m_vecData[index.row()];
switch (role)
{
case Qt::BackgroundRole:
{
if ((index.column() == ColCreatedAt && esi.createdAtServer.isValid() && esi.createdAt < esi.createdAtServer) != // XOR
(index.column() == ColCreatedAtServer && esi.createdAt.isValid() && esi.createdAtServer < esi.createdAt))
{
return QBrush(QColor(255, 205, 205)); // i bet this looks bad in dark style
}
break;
}
case Qt::DisplayRole:
{
switch (index.column())
{
case ColId:
return esi.id();
case ColExercise:
return esi.exercise.name;
case ColName:
return esi.script.name;
case ColCreatedAt:
return esi.createdAt;
case ColCreatedAtServer:
return esi.createdAtServer;
case ColLocation:
{
QStringList locations;
if (esi.local)
locations += tr("Local");
if (esi.uploaded == ExportedScriptInformation::UploadState::ThpAlreadyUploaded)
locations += tr("Server");
return locations.join(L(" & "));
}
case ColUploadStatus:
{
switch (esi.uploaded)
{
case ExportedScriptInformation::UploadState::ThpNotUploaded:
return tr("Not uploaded");
case ExportedScriptInformation::UploadState::ThpUnknownUploadState:
return tr("Unknown upload status");
case ExportedScriptInformation::UploadState::ThpAlreadyUploaded:
return tr("Already uploaded");
}
}
}
break;
}
case Qt::UserRole:
{
return esi.id();
break;
}
default:
break;
}
return {};
}
void ExportedScriptsModel::addScript(const int exerciseId, const QString &exerciseName,
const int scriptId, const QString &scriptName,
const QDateTime &createdAt) noexcept(false)
{
insertRow(ExportedScriptInformation({ createdAt, { exerciseId, exerciseName }, { scriptId, scriptName, {} } }));
writeScriptsJson();
}
void ExportedScriptsModel::removeLocalScript(const QModelIndex &idx) noexcept(false)
{
auto esi = getRowData(idx.row());
if (!esi.local)
{
return;
}
if (esi.isUploaded())
{
esi.unsetLocal();
setRowData(idx.row(), esi);
}
else
{
removeRow(idx.row());
}
writeScriptsJson();
}
void ExportedScriptsModel::removeLocalScript(const QString &scriptId) noexcept(false)
{
for (int i = 0; i < rowCount(); i++)
{
const auto idx = index(i, 0);
if (data(idx, Qt::UserRole) == scriptId)
{
removeLocalScript(idx);
return;
}
}
// TODO: fail if not found?
}
QVector<ExportedScriptInformation> ExportedScriptsModel::getLocalScripts() noexcept(false)
{
QVector<ExportedScriptInformation> ret;
ret.reserve(m_vecData.size());
for (const auto &esi : m_vecData)
{
if (esi.local)
{
ret += esi;
}
}
return ret;
}
void ExportedScriptsModel::writeScriptsJson() noexcept(false)
{
auto parser = ScriptsJsParser(m_rootDirectoryToScan);
parser.setExportedScriptInformations(getLocalScripts());
parser.save();
}
//bool ExportedScriptsModel::setUploadState(const int row,
// const ExportedScriptInformation::UploadState uploadState,
// const QDateTime &createdAtServer,
// const bool isLocal)
//{
// auto esi(getRowData(row));
// qWarning() << "setUploadState: esi.id = " << esi.id() << "uploadState" << (int)uploadState << "createdAtServer" << createdAtServer;
// esi.uploaded = uploadState;
// esi.createdAtServer = createdAtServer;
// esi.local = isLocal;
// //esi.fileSize = fileSize;
// return setRowData(row, esi);
//}
} // namespace TakeHomePackageManager
@@ -0,0 +1,65 @@
/**
* @RDE_HEADER@
* Show the exported scripts and allow them to upload them to the server
* $Id: tpm_exported_scripts_model.h 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#pragma once
#include <logger.h>
#include <LtsWidgets/lts_abstract_table_model.h>
#include <Common2/thp2common_export.h>
#include <Common2Core/tpm_js_parser.h>
#include <QDateTime>
#include <QDir>
class QFileSystemWatcher;
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT ExportedScriptsModel : public LtsWidgets::LtsAbstractTableModel<ExportedScriptInformation>
{
Q_OBJECT
public:
enum Columns
{
ColId,
ColExercise,
ColName,
ColCreatedAt, // local
ColCreatedAtServer,
ColLocation,
ColUploadStatus,
};
explicit ExportedScriptsModel(QObject *parent = nullptr);
void init(const QString &rootDirectoryToScan, bool checkForScriptsJsChanges = false) noexcept(false);
void addScript(const int exerciseId, const QString &exerciseName, const int scriptId, const QString &scriptName, const QDateTime &createdAt) noexcept(false);
void removeLocalScript(const QString &scriptId) noexcept(false);
void removeLocalScript(const QModelIndex &idx) noexcept(false);
void setServerContent(const QString &jsonObject);
void setOC(bool isOC) { m_isOC = isOC; }
void writeScriptsJson() noexcept(false);
protected:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
void reloadScriptsJs() noexcept(false);
QVector<ExportedScriptInformation> getLocalScripts() noexcept(false);
private:
bool m_isOC { false };
QString m_rootDirectoryToScan;
QFileSystemWatcher *m_watcher { nullptr };
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,384 @@
/**
* @RDE_HEADER@
* Advanced file copy manager with progress dialog
* $Id: tpm_exported_scripts_dialog.cpp 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_dialog.cpp $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#include "tpm_file_copy.h"
#include <Common2/tpm_process_dialog.h>
#include <Common2Core/tpm_tools.h>
#include <QDir>
#include <QFile>
#include <QTemporaryFile>
#include <QTimer>
#include <QFuture>
#include <QFutureWatcher>
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QtConcurrent>
#include <QDirIterator>
#include <QFileDialog>
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
#include <QDebug>
#ifdef Q_OS_UNIX
#define _compiler_warning(x) _Pragma(#x)
#define compiler_warning(x) _compiler_warning(GCC warning x)
#define TMP_WARNING_LOG(x) compiler_warning("Temporary warning log active!") \
WARNING_LOG(x)
#else
#define compiler_warning(x)
#define TMP_WARNING_LOG(x) WARNING_LOG(x)
#endif
typedef std::runtime_error error;
namespace TakeHomePackageManager
{
CopyInfoList getFilesFromFolder(const QString &srcPath, const QString &dstPath, const QStringList &excludes) noexcept(false)
{
int fuse = 50000;
QFileInfoList todoFiles({{srcPath}});
CopyInfoList ret;
do
{
fuse--;
if (fuse < 1)
{
throw error("fuse popped");
}
const QString path(todoFiles.takeFirst().absoluteFilePath());
bool exclude = false;
for (const auto &excludePath : excludes)
{
if ((srcPath + QDir::separator() + excludePath) == path)
{
qWarning() << "excluded" << path;
exclude = true;
break;
}
}
if (exclude)
continue; // skip excluded file / dir
QDir srcDir(path);
if (!srcDir.exists())
{
throw error(L("getFilesFromFolder: path (%1) does not exist!").arg(srcPath).toStdString());
}
// directories (aka. more dirs to scan)
for (const auto &dirEntryInfo : srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
{
//qWarning() << "srcDir" << srcDir.absolutePath() << "next" << QDir(dirEntryInfo.absoluteFilePath()).absolutePath();
todoFiles += dirEntryInfo; //_getFilesFromFolder(QDir(dirEntryInfo.absoluteFilePath()), files);
}
// files
for (const auto &fileEntryInfo : srcDir.entryInfoList(QDir::Files))
{
ret += { srcPath, dstPath, fileEntryInfo };
}
}
while (!todoFiles.isEmpty());
return ret;
}
void copyFiles(const CopyInfoList &fileList, LtsWidgets::LtsDialog *dialog) noexcept(false)
{
auto cp = new CopyProcess(fileList, dialog);
dialog->setOverlay(cp->process());
bool success = false;
QObject::connect(cp, &CopyProcess::finished, dialog, [&success](bool _success) { success = _success; });
awaitx(cp, &CopyProcess::finished);
if (!success)
{
throw error("!success");
}
}
// static AsyncFileCopy *AsyncFileCopy::createAsyncFileCopy(const CopyInfoList &fileList) // to prevent the user from thinking having to delete the class (which it does itself)
// {
// return new AsyncFileCopy(fileList);
// }
AsyncFileCopy::AsyncFileCopy(const CopyInfoList &fileList)
{
QFutureWatcher<bool> *fw = new QFutureWatcher<bool>(this);
connect(fw, &QFutureWatcher<bool>::finished,
this, [this, fw]()
{
emit finished(fw->result());
});
connect(fw, &QFutureWatcher<bool>::finished,
this, &QObject::deleteLater); // self-deleting
fw->setFuture(QtConcurrent::run(this, &AsyncFileCopy::copy, fileList));
}
#define cancelationPoint if (m_wasCanceled) { qWarning() << "AsyncFileCopy: canceled"; return false; } (void)m_wasCanceled
bool AsyncFileCopy::copy(const CopyInfoList &fileList)
{
qint64 totalAll = 0, writtenAll = 0;
int fileCount = 0;
for (const auto &fileInfo : fileList)
{
totalAll += fileInfo.fileInfo.size(); // there should be ONLY files in QFileInfoList!
fileCount++;
}
if (fileCount < 1)
{
qWarning() << "AsyncFileCopy: fileCount < 1";
return false;
}
//qWarning() << "AsyncFileCopy: copy" << fileList.count() << "files";
emit started(fileCount, totalAll);
cancelationPoint;
int currentFile = 0;
for (const auto &copyInfo : fileList)
{
cancelationPoint;
const QFileInfo &fileInfo = copyInfo.fileInfo;
const QString &srcPath = copyInfo.srcPath;
const QString &dstPath = copyInfo.dstPath;
currentFile++;
const qint64 totalFile = fileInfo.size();
qint64 writtenFile = 0;
QDir srcDir(srcPath);
// if (cdUp)
// srcDir.cdUp();
// src: /shd/CTC/WS/THP2
// file: /shd/CTC/WS/THP2/lib/script.js
// dst: /mnt/thps/42.3/
// full: /mnt/thps/42.3/lib/
// soll: /mnt/thps/42.3/lib/script.js
// NOTE: src: /shd/CTC/WS (if cdUp!)
const QString dstFullPath(dstPath + QDir::separator() + fileInfo.absolutePath().remove(srcDir.path()));
if (!QDir().mkpath(dstFullPath) /* why is this not static, Qt? Directory validation? */)
{
qWarning() << "AsyncFileCopy: cannot create path";
return false;
}
QFile srcFile(fileInfo.absoluteFilePath());
emit newFile(currentFile, totalFile, fileInfo.fileName());
QFile dstFile(dstFullPath + QDir::separator() + fileInfo.fileName());
qWarning() << "AsyncFileCopy: copy file from" << srcFile.fileName() << "to" << dstFile.fileName();
if (!srcFile.open(QIODevice::ReadOnly))
{
qWarning() << QStringLiteral("failed to open src '%1' (error: %2)").arg(srcFile.fileName(), srcFile.errorString());
return false;
}
if (!dstFile.open(QIODevice::Unbuffered /* make "done" really mean "done" */| QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << QStringLiteral("failed to open dst '%1' (error: %2)").arg(dstFile.fileName(), dstFile.errorString());
return false;
}
qint64 fileSize = srcFile.size();
while (1)
{
cancelationPoint;
//#define SUPERSLOW
emit status(tr("Reading..."));
const auto data = srcFile.read(
#ifdef SUPERSLOW
54321
#else
s_chunkSize
#endif
);
cancelationPoint;
#ifdef SUPERSLOW
usleep(100 * 1000); // 100ms
#endif
emit status(tr("Copying..."));
const auto _written = dstFile.write(data);
//qWarning() << "AsyncFileCopy: read" << data.size() << "write" << _written << "size" << fileSize;
#ifdef SUPERSLOW
usleep(100 * 1000);
#endif
if (_written < 0)
{
qWarning() << "AsyncFileCopy: unable to write to file" << dstFile.fileName() << ":" << dstFile.errorString();
return false;
}
writtenFile += _written;
writtenAll += _written;
//qWarning() << "AsyncFileCopy: progress" << writtenFile << totalFile << writtenAll << totalAll << currentFile << fileCount;
emit progress(writtenFile, writtenAll);
cancelationPoint;
// TODO: windoofs
#ifdef Q_OS_UNIX
//qWarning() << "AsyncFileCopy: flushing...";
emit status(tr("Writing..."));
QElapsedTimer t;
t.start();
fsync(dstFile.handle()); // fsync(2): "The call blocks until the device reports that the transfer has completed."
// TODO: use O_DIRECT instead (?)
//qWarning() << "took" << t.elapsed() << "ms";
#endif
if (data.size() != _written) // fail if it didn't write all it read before
{
qWarning() << "AsyncFileCopy: copy failed! read" << data.size() << "write" << _written << "size" << fileSize;
return false;
}
if (writtenFile == fileSize) // done
{
break;
}
}
}
if (totalAll == writtenAll)
{
//qWarning() << "AsyncFileCopy: done - " << writtenAll << "/" << totalAll << "bytes";
return true;
}
else
{
qWarning() << "AsyncFileCopy: failed! total written bytes doesn't match all requested bytes - " << writtenAll << "/" << totalAll << "bytes";
return false;
}
}
/// CopyProcess
CopyProcess::CopyProcess(const CopyInfoList &fileList, QObject *parent)
: QObject(parent)
, m_process(new ProcessOverlay)
{
connect(m_process, &ProcessOverlay::signalCancel,
this, [this]()
{
//DEBUG_LOG(m_logger) << "m_wasCanceled!!";
m_wasCanceled = true;
//m_process->setCancelEnabled(false); does so itself already
});
connect(m_process, &ProcessOverlay::signalCancel,
this, &CopyProcess::signalCancel);
AsyncFileCopy *afc = AsyncFileCopy::createAsyncFileCopy(fileList);
connect(m_process, &ProcessOverlay::signalCancel,
afc, &AsyncFileCopy::slotCancel);
connect(afc, &AsyncFileCopy::progress,
this, [this](qint64 bytesCopiedFile, qint64 bytesCopiedAll)
{
m_process->setFileProgress(bytesCopiedFile, bytesCopiedAll);
});
connect(afc, &AsyncFileCopy::newFile,
this, [this](int currentFile, qint64 bytesTotalFile, const QString &filename)
{
m_process->setNewFile(currentFile, bytesTotalFile, filename);
});
connect(afc, &AsyncFileCopy::started,
this, [this](int fileCount, qint64 bytesTotalAll)
{
m_process->setStarted(fileCount, bytesTotalAll);
});
connect(afc, &AsyncFileCopy::status,
this, [this](const QString &status)
{
m_process->setStatus(status);
});
connect(afc, &AsyncFileCopy::newAction,
this, [this](const QString &newAction)
{
m_currentAction = newAction;
});
connect(afc, &AsyncFileCopy::finished,
this, &QObject::deleteLater);
connect(afc, &AsyncFileCopy::finished,
this, &CopyProcess::finished);
}
CopyProcess::~CopyProcess()
{
//DEBUG_LOG(m_logger) << "~CopyProcess";
//delete m_process;
m_process->deleteLater();
}
QWidget *CopyProcess::process()
{
return m_process;
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,97 @@
/**
* @RDE_HEADER@
* Advanced file copy manager with progress dialog
* $Id: tpm_exported_scripts_dialog.cpp 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_dialog.cpp $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#pragma once
#include <Common2/thp2common_export.h>
#include <LtsWidgets/lts_dialog.h>
#include <QFileInfoList>
#include <QWidget>
namespace TakeHomePackageManager
{
class ProcessOverlay;
struct THP2COMMON_EXPORT CopyInfo // :'( we need this to cut the srcPath from the file path, to have the same folder structure in the destionation folder
{
QString srcPath;
QString dstPath;
QFileInfo fileInfo;
};
using CopyInfoList = QList<CopyInfo>;
THP2COMMON_EXPORT CopyInfoList getFilesFromFolder(const QString &srcPath, const QString &dstPath, const QStringList &excludes = {}) noexcept(false);
THP2COMMON_EXPORT void copyFiles(const CopyInfoList &fileList, LtsWidgets::LtsDialog *dialog) noexcept(false);
// TODO: mv to LtsCommon
class AsyncFileCopy : public QObject
{
Q_OBJECT
public:
static const int s_chunkSize = 1 * 1024 * 1024; // 1MB
static AsyncFileCopy *createAsyncFileCopy(const CopyInfoList &fileList) // to prevent the user from thinking having to delete the class (which it does itself)
{
return new AsyncFileCopy(fileList);
}
~AsyncFileCopy() = default;
private:
AsyncFileCopy(const CopyInfoList &fileList);
private:
bool copy(const CopyInfoList &fileList);
bool m_wasCanceled { false };
public slots:
void slotCancel()
{
m_wasCanceled = true;
}
signals:
void newAction(const QString &newAction);
void status(const QString &status);
void started(int fileCount, qint64 bytesTotalAll);
void newFile(int currentFile, qint64 bytesTotalFile, const QString &filename);
void progress(qint64 bytesCopiedFile, qint64 bytesCopiedAll);
void finished(bool success);
};
class THP2COMMON_EXPORT CopyProcess : public QObject
{
Q_OBJECT
public:
CopyProcess(const CopyInfoList &fileList, QObject *parent = nullptr);
~CopyProcess();
QWidget *process();
signals:
void signalCancel();
void finished(bool success);
private:
ProcessOverlay *m_process;
bool m_wasCanceled { false };
QString m_currentAction;
};
} // namespace TakeHomePackageManager
@@ -0,0 +1,115 @@
/**
* @RDE_HEADER@
* $Id: tpm_exported_scripts_model.h 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include "tpm_process_dialog.h"
#include <ui_tpm_process_dialog.h>
#include <QDebug>
namespace TakeHomePackageManager
{
ProcessOverlay::ProcessOverlay(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::ProcessOverlay)
{
m_ui->setupUi(this);
connect(m_ui->pbCancel, &QPushButton::clicked,
this, [this]()
{
m_ui->pbCancel->setEnabled(false);
emit signalCancel();
});
}
ProcessOverlay::~ProcessOverlay()
{
delete m_ui;
}
void ProcessOverlay::setCancelEnabled(bool enabled)
{
m_ui->pbCancel->setEnabled(enabled);
}
void ProcessOverlay::setStatus(const QString &text)
{
if (!text.isEmpty()) // only set new text if not empty (we dont want to send the text on every update..
m_ui->lblStatus->setText(text);
}
void ProcessOverlay::setFileProgress(qint64 bytesCopiedFile, qint64 bytesCopiedAll)
{
qWarning() << "setFileProgress" << "bytesCopiedFile" << bytesCopiedFile << "bytesCopiedAll" << bytesCopiedAll;
double progressBytes = (m_bytesTotalFile == 0) ? 0 : ((double)bytesCopiedFile / (double)m_bytesTotalFile);
double progressTotal = (m_bytesTotalAll == 0) ? 0 : (double)bytesCopiedAll / (double)m_bytesTotalAll;
qWarning() << "progressTotal" << progressTotal << "progressBytes" << progressBytes;
m_ui->pbarBytes->setValue(qRound(progressBytes * 100));
m_ui->pbarProgress->setValue(qRound(progressTotal * 100));
const QLocale l;
m_ui->lblBytes->setText((m_bytesTotalFile == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2 %3").arg(l.toString(bytesCopiedFile)).arg(l.toString(m_bytesTotalFile)).arg(tr("bytes")));
m_ui->lblAllBytes->setText((m_bytesTotalAll == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2 %3").arg(l.toString(bytesCopiedAll)).arg(l.toString(m_bytesTotalAll)).arg(tr("bytes")));
}
void ProcessOverlay::setNewFile(int currentFile, qint64 bytesTotalFile, const QString &filename)
{
qWarning() << "setNewFile" << "currentFile" << currentFile << "bytesTotalFile" << bytesTotalFile << "filename" << filename;
m_bytesTotalFile = bytesTotalFile;
m_currentFile = currentFile;
m_progressFiles = (m_fileCount == 0) ? 0 : (((double)m_currentFile - 1) / (double)m_fileCount);
m_ui->pbarFiles->setValue(qRound(m_progressFiles * 100));
m_ui->lblFilename->setText(filename);
const QLocale l;
m_ui->lblFiles->setText((m_currentFile == 0) ? QStringLiteral(" - ") : QStringLiteral("%1 / %2").arg(l.toString(m_currentFile)).arg(l.toString(m_fileCount)));
}
void ProcessOverlay::setStarted(int fileCount, qint64 bytesTotalAll)
{
qWarning() << "setStarted" << "fileCount" << fileCount << "bytesTotalAll" << bytesTotalAll;
m_fileCount = fileCount;
m_bytesTotalAll = bytesTotalAll;
setFileProgress(0, 0); // so that the user sees some initial information before file transfer starts
}
} // namespace TakehomePackageManager
@@ -0,0 +1,51 @@
#pragma once
/**
* @RDE_HEADER@
* $Id: tpm_exported_scripts_model.h 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-01-11
*/
#include <Common2/thp2common_export.h>
#include <QWidget>
namespace Ui
{
class ProcessOverlay;
}
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT ProcessOverlay : public QWidget
{
Q_OBJECT
public:
ProcessOverlay(QWidget *parent = nullptr);
~ProcessOverlay();
void setStarted(int fileCount, qint64 bytesTotalAll);
void setNewFile(int currentFile, qint64 bytesTotalFile, const QString &filename);
void setStatus(const QString &text);
void setFileProgress(qint64 bytesCopiedFile, qint64 bytesCopiedAll);
void setCancelEnabled(bool enabled = true);
signals:
void signalCancel();
private:
qint64 m_bytesTotalFile = 0;
int m_currentFile = 0;
int m_fileCount = 0;
qint64 m_bytesTotalAll = 0;
double m_progressFiles = 0;
Ui::ProcessOverlay *m_ui;
bool m_decompressing { false };
};
} // namespace TakehomePackageManager
@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessOverlay</class>
<widget class="QWidget" name="ProcessOverlay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>250</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>700</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lblStatus">
<property name="text">
<string notr="true">Preparing ...</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblFilename">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Bytes</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="lblBytes">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarBytes">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Files</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="lblFiles">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarFiles">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Progress</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="lblAllBytes">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pbarProgress">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="pbCancel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,79 @@
/**
* @RDE_HEADER@
* List of topics of selected thp
* $Id: tpm_exported_scripts_model.h 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#include "tpm_topics_model.h"
#include <TopicEditorBase/topic_editor_utils.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace TakeHomePackageManager
{
TopicsModel::TopicsModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<Topic>(
{ tr("id"), tr("Channel"), tr("Filter"), tr("Description"), tr("Type"), tr("Start [mm:ss]"), tr("Duration [mm:ss]")}, parent)
, m_logger(getLogger("TopicsModel"))
{}
void TopicsModel::addTopic(const Topic &topic)
{
insertRow(topic);
}
QVariant TopicsModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
if (role == Qt::UserRole)
{
const auto &topic = m_vecData[index.row()];
return topic.id;
}
if (role != Qt::DisplayRole)
{
return {};
}
auto channelName = [](const int channel)
{
switch (channel)
{
case 1: return tr("Audio");
case 2: return tr("Replay");
case 3: return tr("Screen 1");
case 4: return tr("Screen 2");
default: { assert(false); return L("UNKNOWN"); }
}
};
const auto &topic = m_vecData[index.row()];
switch (index.column())
{
case ColId: return topic.id;
case ColChannel: return channelName(topic.channel);
case ColFilter: return (topic.filterSet ? QString::number(topic.filter) : L(" - "));
case ColDescription: return (topic.description.isEmpty() ? L(" - ") : topic.description);
case ColType: return TopicEditorBase::topicTypeTranslation((TopicEditorBase::TopicType)topic.type);
case ColStart: return QTime(0, 0).addSecs(topic.start).toString(L("mm:ss"));
case ColDuration: return QTime(0, 0).addSecs(topic.duration).toString(L("mm:ss"));
}
return {};
}
} // namespace TakeHomePackageManager
@@ -0,0 +1,44 @@
/**
* @RDE_HEADER@
* List of topics of selected thp
* $Id: tpm_exported_scripts_model.h 53111 2021-03-22 08:21:29Z wille_m $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_03_salted_caramel/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_model.h $
* $Author: wille_m $
*
* Created by Mike Wille, SLTE, 2024-02-09
*/
#pragma once
#include <logger.h>
#include <Common2/thp2common_export.h>
#include <Common2Core/tpm_js_parser.h>
#include <LtsWidgets/lts_abstract_table_model.h>
namespace TakeHomePackageManager
{
class THP2COMMON_EXPORT TopicsModel : public LtsWidgets::LtsAbstractTableModel<Topic>
{
Q_OBJECT
public:
enum Columns
{
ColId,
ColChannel,
ColFilter,
ColDescription,
ColType,
ColStart,
ColDuration,
};
explicit TopicsModel(QObject *parent = nullptr);
void addTopic(const Topic &topic);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
NGL::INGLLogger &m_logger;
};
}