Initial commit
This commit is contained in:
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
|
||||
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
|
||||
Reference in new issue
Block a user