/** * @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 Gerlinde Rudolphus, NLE, 2019-03-25 * Rebuilt to use httq by Mike Wille, NLE, 2020-07-07 */ /* TODO: FIXME: sme problem here! * 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!!) */ /* * * TODO: * index.html that searches all exercises instead of stuff * copy everything in exercise folder for now until above is done * copy situation_map init into share folder * */ #include "ui_tpm_usb_dialog.h" #include "tpm_usb_dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #include #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 namespace TakeHomePackageManager { // TODO: mv to LtsCommon class AsyncFileCopy : public QObject { Q_OBJECT public: static const int s_chunkSize = 1 * 1024 * 1024; static AsyncFileCopy *createAsyncFileCopy(const QDir &src, const QDir &dst, const QFileInfoList &fil = {}, bool cdUp = true) // to prevent the user from thinking having to delete the class (which it does itself) { return new AsyncFileCopy(src, dst, fil, cdUp); } ~AsyncFileCopy() = default; private: AsyncFileCopy(const QDir &src, const QDir &dst, const QFileInfoList &fil, bool cdUp) { QFutureWatcher *fw = new QFutureWatcher(this); connect(fw, &QFutureWatcher::finished, this, [this, fw]() { emit finished(fw->result()); }); connect(fw, &QFutureWatcher::finished, this, &QObject::deleteLater); // self-deleting fw->setFuture(QtConcurrent::run(this, &AsyncFileCopy::copy, src, dst, fil, cdUp)); } private: #define cancelationPoint if (m_wasCanceled) { qWarning() << "canceled"; return false; } (void)m_wasCanceled bool copy(const QDir &src, const QDir &dst, const QFileInfoList &fil, bool cdUp) { qint64 totalAll = 0, writtenAll = 0; int fileCount = 0; //TODO { QDirIterator it(src.absolutePath(), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (it.hasNext()) { const QFileInfo fi(it.next()); //qWarning() << "size fi 1" << fi; // copy only files in fil (only if fil is not optional aka. empty) // operator== doesn't work as intended, so no fil.contains(fi) :( if (!fil.isEmpty()) { auto it = std::find_if(fil.constBegin(), fil.constEnd(), [fi](const QFileInfo &_fi) { // qWarning() << "does" << fi.absoluteFilePath() << "and" << _fi.absoluteFilePath() << "match?"; return (fi.absoluteFilePath() == _fi.absoluteFilePath()); }); if (it == fil.constEnd()) continue; } //qWarning() << "size fi 2" << fi; totalAll += fi.size(); fileCount++; } } //qWarning() << "fileList size" << fileList.size() << fileList; //qWarning() << "src for it" << src; int currentFile = 0; QDirIterator it(src.absolutePath(), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (it.hasNext()) { cancelationPoint; const QFileInfo fi(it.next()); //qWarning() << "copy fi 1" << fi; // copy only files in fil (only if fil is not optional aka. empty) // operator== doesn't work as intended, so no fil.contains(fi) :( if (!fil.isEmpty()) { auto it = std::find_if(fil.constBegin(), fil.constEnd(), [fi](const QFileInfo &_fi) { return (fi.absoluteFilePath() == _fi.absoluteFilePath()); }); if (it == fil.constEnd()) continue; } //qWarning() << "copy fi 2" << fi; currentFile++; qint64 totalFile = fi.size(), writtenFile = 0; QDir x(src); if (cdUp) x.cdUp(); QString srcFilePath(fi.absolutePath()); QString dstFilePath(dst.absolutePath() + QDir::separator() + srcFilePath.remove(x.absolutePath())); if (!QDir().mkpath(dstFilePath) /* why is this not static, Qt? Directory validation? */) { qWarning() << "cannot create path"; return false; } QFile srcFile(fi.absoluteFilePath()); QFile dstFile(dstFilePath + QDir::separator() + fi.fileName()); // qWarning() << "fi" << fi.absolutePath() // << "src" << src.absolutePath() // << "x" << x.absolutePath() // << "srcFilePath" << srcFilePath // << "dstFilePath" << dstFilePath // << "srcFile" << fi.absoluteFilePath() // << "dstFile" << dstFilePath + QDir::separator() + fi.fileName() // ; if (!srcFile.open(QIODevice::ReadOnly)) { qWarning() << QStringLiteral("failed to open src '%1' (error: %2)").arg(srcFile.fileName(), srcFile.errorString()); continue; } if (!dstFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) { qWarning() << QStringLiteral("failed to open dst '%1' (error: %2)").arg(dstFile.fileName(), dstFile.errorString()); continue; } // not needed with mode truncate: dstFile.seek(0); // if file exists, overwrite qint64 fileSize = srcFile.size(); while (fileSize) { cancelationPoint; const auto data = srcFile.read(s_chunkSize); cancelationPoint; const auto _written = dstFile.write(data); qWarning() << "progress" << writtenFile << totalFile << writtenAll << totalAll << currentFile << fileCount; emit newAction(tr("Copying...")); emit progress(writtenFile, totalFile, writtenAll, totalAll, currentFile, fileCount); // TODO: windoofs #ifdef Q_OS_UNIX //qWarning() << "flushing..."; emit newAction(tr("Writing...")); QElapsedTimer t; t.start(); fsync(dstFile.handle()); // fsync(2): "The call blocks until the device reports that the transfer has completed." //qWarning() << "took" << t.elapsed() << "ms"; #endif if (data.size() == _written) { writtenFile += _written; fileSize -= data.size(); } else { qWarning() << "cannot write to" << dstFile.fileName() << ":" << dstFile.errorString(); fileSize = 0; break; // skip this operation } } writtenAll += writtenFile; } if (totalAll == writtenAll) { qWarning() << "done - " << writtenAll << "/" << totalAll << "bytes"; return true; } else { qWarning() << "failed....."; return false; } } bool m_wasCanceled { false }; public slots: void slotCancel() { m_wasCanceled = true; } signals: void newAction(const QString &newAction); void progress(qint64 bytesCopiedFile, qint64 bytesTotalFile, qint64 bytesCopiedAll, qint64 bytesTotalAll, int currentFile, int fileCount); void finished(bool success); }; class CopyProcess : public QObject { Q_OBJECT public: CopyProcess(const QDir &src, const QDir &dst, const QFileInfoList &fil = {}, bool cdUp = true, QObject *parent = nullptr) : 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); }); connect(m_process, &ProcessOverlay::signalCancel, this, &CopyProcess::signalCancel); AsyncFileCopy *afc = AsyncFileCopy::createAsyncFileCopy(src, dst, fil, cdUp); connect(m_process, &ProcessOverlay::signalCancel, afc, &AsyncFileCopy::slotCancel); connect(afc, &AsyncFileCopy::progress, this, [this](qint64 bytesCopiedFile, qint64 bytesTotalFile, qint64 bytesCopiedAll, qint64 bytesTotalAll, int currentFile, int fileCount) { m_process->setStatus(m_currentAction, fileCount, currentFile, bytesTotalFile, bytesCopiedFile); }); 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() { //DEBUG_LOG(m_logger) << "~CopyProcess"; delete m_process; } QWidget *process() { return m_process; } signals: void signalCancel(); void finished(bool success); private: ProcessOverlay *m_process; bool m_wasCanceled { false }; QString m_currentAction; }; // USBDialog USBDialog::USBDialog(LtsSession::LtsSession *session, QWidget *parent) : LtsWidgets::LtsDialog(parent) , m_localThpModel(new ExportedScriptsModel(this)) , m_usbThpModel(new ExportedScriptsModel(this)) , m_ui(new Ui::USBDialog) , m_session(session) //, m_fsWatcher(new QFileSystemWatcher(this)) , m_exerciseId(m_session->exerciseId()) , m_paths(0) , m_logger(getLogger("USBDialog")) { m_ui->setupUi(this); connect(m_ui->pbCHange, &QPushButton::clicked, this, &USBDialog::slotChangePath); connect(m_ui->pbCopy, &QPushButton::clicked, this, &USBDialog::slotCopy); connect(m_ui->pbRemove, &QPushButton::clicked, this, &USBDialog::slotRemove); connect(this, &USBDialog::nextMap, this, &USBDialog::onNextMap); connect(m_ui->pbCopyMaps, &QPushButton::clicked, this, &USBDialog::slotCopyMaps); connect(m_ui->pbRemoveMaps, &QPushButton::clicked, this, &USBDialog::slotRemoveMaps); connect(m_ui->tvLocal->selectionModel(), &QItemSelectionModel::selectionChanged, this, &USBDialog::slotSelectionChanged); // compiler_warning("TODO") // auto initModelFn = [this]() // { // try // { // initModel(); // } // catch() // { // ERROR_LOG(m_logger) << "initModel failed"; // } // }; // connect(m_fsWatcher, &QFileSystemWatcher::directoryChanged, // this, initModelFn); // connect(m_fsWatcher, &QFileSystemWatcher::fileChanged, // this, initModelFn); initModel(); // throws! setOnlineHelpFilename(QStringLiteral("Script_Editor-Export_Script_Administration")); } USBDialog::~USBDialog() { delete m_ui; } void USBDialog::initUsbModel() { m_usbThpModel->clear(); if (!m_usbThpModel->init(m_usbRoot)) { new LtsWidgets::LtsMessageDialogNew(this, tr("The directory '%1' doesn't contain valid THP data.").arg(m_usbRoot)); ERROR_LOG(m_logger) << "cannot initialize usb thp model from usb root" << m_usbRoot; return; } QDir tmp; tmp.mkpath(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps")); //getContentFolder m_ui->tvUSB->setModel(m_usbThpModel); m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColId); m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColLocation); m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColUploaded); m_ui->tvUSB->hideColumn(ExportedScriptsModel::ColUploadedTime); m_ui->lblPath->setText(m_usbRoot); // fill usb maps { m_ui->tvUSBMaps->setRowCount(0); m_ui->tvUSBMaps->clearContents(); QDirIterator it(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps"), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (it.hasNext()) { QFileInfo fi(it.next()); m_ui->tvUSBMaps->insertRow(m_ui->tvUSBMaps->rowCount()); m_ui->tvUSBMaps->setItem(m_ui->tvUSBMaps->rowCount() - 1, 0, new QTableWidgetItem(fi.fileName())); if (fi.fileName().endsWith(QStringLiteral("json")) || fi.fileName().endsWith(QStringLiteral("tif"))) m_ui->tvUSBMaps->hideRow(m_ui->tvUSBMaps->rowCount() -1); } } } void USBDialog::slotChangePath() { QFileDialog *dlg = new QFileDialog(this); dlg->setFileMode(QFileDialog::Directory); dlg->setOption(QFileDialog::ShowDirsOnly); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); connect(dlg, &QFileDialog::accepted, this, [this, dlg]() { QStringList selected(dlg->selectedFiles()); if (selected.count() > 1) return; m_usbRoot = selected.first(); if (!QFile::exists(m_usbRoot + QDir::separator() + QStringLiteral("exercises.json"))) { auto m = new LtsWidgets::LtsMessageDialogNew(this, tr("The selected directory '%1' doesn't contain THPs yet.
Do you want to initialize the directory now?").arg(m_usbRoot), {}, LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::Cancel); connect(m, &LtsWidgets::LtsMessageDialogNew::yes, this, [this]() { QFile exerciseJsonFile(m_usbRoot + QDir::separator() + QStringLiteral("exercises.json")); if (!exerciseJsonFile.open(QIODevice::WriteOnly)) { new LtsWidgets::LtsMessageDialogNew(this, tr("The selected directory '%1' can't be written to and the initialization was aborted.").arg(m_usbRoot)); return; } exerciseJsonFile.write(QString(QStringLiteral("{ \"Exercises\": [ %1 ] }\n").arg(m_exerciseId)).toUtf8()); // meh... but quick exerciseJsonFile.close(); slotCopyWinBin(); //create data.json if it doesn't exist std::shared_ptr exmDtc; std::shared_ptr asmDtc; if (m_session->getDtc(asmDtc) && m_session->getDtc(exmDtc)) { if (!m_usbThpModel->createDataJson(m_usbRoot, exmDtc, asmDtc, m_exerciseId)) { // TODO: user msg ERROR_LOG(m_logger) << "Failed to initialize data.json"; return; } TMP_WARNING_LOG(m_logger) << "DONE!"; } else TMP_WARNING_LOG(m_logger) << "Failed to get dtcs :( !"; }); return; } initUsbModel(); generateLayerMappingXML(); }); } void USBDialog::slotCopyMaps() { if (m_usbRoot.isEmpty()) return; m_finished_mapCopys = 0; QDir dstDir(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps")); //getContentFolder for now if (!dstDir.exists()) { ERROR_LOG(m_logger) << "dst dir " << m_usbRoot << " is not accessible"; return; } const QModelIndexList mil = m_ui->tvLocalMaps->selectionModel()->selectedRows(); if (mil.size() < 1) return; DEBUG_LOG(m_logger) << "mil.size()" << mil.size(); for (const QModelIndex &idx : mil) { // qWarning() << "added fi" << idx.data().toString(); QString abs = m_ui->tvLocalMaps->item(idx.row(), 2)->text() + QDir::separator() + m_ui->tvLocalMaps->item(idx.row(), 1)->text(); m_files[m_ui->tvLocalMaps->item(idx.row(), 2)->text()] += abs; // qWarning() << "the fi" << QFileInfo(idx.data().toString()); } //add elevations //Eleveation // We will copy all elevations every time! QDirIterator eleit(QStringLiteral("/usr/share/arcgis/resources/elevation/"), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (eleit.hasNext()) { QString file = eleit.next(); QFileInfo eleFile(file); if (!eleFile.fileName().toLower().endsWith(QStringLiteral(".tif"))) continue; m_files[eleFile.absolutePath()] += eleFile.absoluteFilePath(); } m_paths = m_files.count(); //ProcessOverlay* overlay = new ProcessOverlay(); //setOverlay(overlay); //overlay->setStatus(QStringLiteral("Copy Maps"), paths, 1); //overlay->setCancelEnabled(false); emit nextMap(); } void USBDialog::onNextMap() { auto path = m_files.firstKey(); { QString src(path); DEBUG_LOG(m_logger) << "src" << src; QDir srcDir(src); if (!srcDir.exists()) { ERROR_LOG(m_logger) << "src dir " << src << " is not accessible"; return; } QDir dstDir(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps")); //getContentFolder for now auto cp = new CopyProcess(srcDir, dstDir, m_files[path], false, this); setOverlay(cp->process()); connect(cp, &CopyProcess::finished, this, [this, path](bool success) { if (!success) new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully from path! ") + path); m_finished_mapCopys++; m_files.remove(path); // overlay->setStatus(QStringLiteral("Copy Maps"), paths, m_finished_mapCopys); if(m_paths == m_finished_mapCopys) { // overlay->deleteLater(); new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully from path: ") + path); initUsbModel(); generateLayerMappingXML(); m_finished_mapCopys = 0; }else emit nextMap(); }); } } void USBDialog::generateLayerMappingXML() { QJsonObject layers; QJsonArray elevationUrls; QJsonArray mapUrls; QJsonArray sceneObjectUrls; QJsonArray sceneUrls; for(int i = 0; i < m_ui->tvUSBMaps->rowCount(); ++i) { QFileInfo fileInfo = QFileInfo(m_usbRoot + QDir::separator() + m_ui->tvUSBMaps->item(i,0)->text()); if(fileInfo.suffix() == QStringLiteral("tif") || fileInfo.suffix() == QStringLiteral("dt2")) { QJsonObject elavation; elavation.insert(QStringLiteral("description"), QJsonValue(QStringLiteral("Elevation"))); elavation.insert(QStringLiteral("maxScale"), QJsonValue(-1)); elavation.insert(QStringLiteral("minScale"), QJsonValue(-1)); elavation.insert(QStringLiteral("name"), QJsonValue(fileInfo.baseName())); elavation.insert(QStringLiteral("url"), QJsonValue(fileInfo.fileName())); elevationUrls.push_back(elavation); } else if (fileInfo.suffix() == QStringLiteral("slpk")) { QJsonObject objectScene; objectScene.insert(QStringLiteral("description"), QJsonValue(QStringLiteral("ObjectScene"))); objectScene.insert(QStringLiteral("maxScale"), QJsonValue(125)); objectScene.insert(QStringLiteral("minScale"), QJsonValue(-1)); objectScene.insert(QStringLiteral("altitudeOffset"), QJsonValue(0.5)); objectScene.insert(QStringLiteral("name"), QJsonValue(fileInfo.baseName())); objectScene.insert(QStringLiteral("url"), QJsonValue(fileInfo.fileName())); sceneObjectUrls.push_back(objectScene); } else if(fileInfo.suffix() == QStringLiteral("tpk")) { //Maps when WebMerc dann Scene sonst map if(fileInfo.fileName().contains(QStringLiteral("WebMerc"))) { QJsonObject scene; scene.insert(QStringLiteral("description"), QJsonValue(QStringLiteral("Scene"))); scene.insert(QStringLiteral("maxScale"), QJsonValue(125)); scene.insert(QStringLiteral("minScale"), QJsonValue(-1)); scene.insert(QStringLiteral("name"), QJsonValue(fileInfo.baseName())); scene.insert(QStringLiteral("url"), QJsonValue(fileInfo.fileName())); sceneUrls.push_back(scene); } else { QJsonObject map; map.insert(QStringLiteral("description"), QJsonValue(QStringLiteral("Map"))); map.insert(QStringLiteral("maxScale"), QJsonValue(125)); map.insert(QStringLiteral("minScale"), QJsonValue(-1)); map.insert(QStringLiteral("name"), QJsonValue(fileInfo.baseName())); map.insert(QStringLiteral("url"), QJsonValue(fileInfo.fileName())); mapUrls.push_back(map); } } } layers.insert(QStringLiteral("elevationUrls"), elevationUrls); layers.insert(QStringLiteral("mapUrls"), mapUrls); layers.insert(QStringLiteral("sceneObjectUrls"), sceneObjectUrls); layers.insert(QStringLiteral("sceneUrls"), sceneUrls); QFile layersMapping(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("layerMapping.json"));//getContentFolder QFile layersMappingAtMaps(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps") + QDir::separator() + QStringLiteral("layerMapping.json")); if (!layersMapping.open(QIODevice::WriteOnly)) { WARNING_LOG(m_logger) << "Couldn't open save file."; return; } if (!layersMappingAtMaps.open(QIODevice::WriteOnly)) { WARNING_LOG(m_logger) << "Couldn't open save file in folder Maps."; return; } QJsonDocument doc(layers); layersMapping.write(doc.toJson()); layersMappingAtMaps.write(doc.toJson()); } QFileInfoList USBDialog::prepareFileInfoListForFolder(QString src, QFileInfoList fil) { QDir dir(src); if (! dir.exists()) return fil; for (QString d : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { fil = prepareFileInfoListForFolder(src+ QDir::separator() + d, fil); } for (QString f : dir.entryList(QDir::Files)) { fil += QString(src + QDir::separator() + f); } return fil; } void USBDialog::slotCopyWinBin() { if (m_usbRoot.isEmpty()) return; QDir dstDir(getContentFolder(m_usbRoot) + QDir::separator()); //getContentFolder if (!dstDir.exists()) { dstDir.mkpath(QStringLiteral(".")); } //TODO richtiger Pfad QString src(QStringLiteral("/opt/THP/")); QDir srcDir(src); if (!srcDir.exists()) { ERROR_LOG(m_logger) << "src dir " << src << " is not accessible"; return; } QFileInfoList fil; fil = prepareFileInfoListForFolder(src, fil); DEBUG_LOG(m_logger) << "XXXXXXXXXXX filsize is: " << fil.size(); auto cp = new CopyProcess(srcDir, dstDir, fil, false, this); setOverlay(cp->process()); connect(cp, &CopyProcess::finished, this, [this](bool success) { if (success) { // new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully")); slotCopyWinBinP2(); } else new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully!")); }); } void USBDialog::slotCopyWinBinP2() { if (m_usbRoot.isEmpty()) return; QDir dstDir(getContentFolder(m_usbRoot) + QDir::separator()); //getContentFolder if (!dstDir.exists()) { dstDir.mkpath(QStringLiteral(".")); } const QString initFilePath = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("situation_map_defaults.ini")); if (initFilePath.isEmpty()) { ERROR_LOG(m_logger) << "sitmap init is not accessible"; return; } QFileInfo fileInfo(initFilePath); QFileInfoList fil2; fil2.append(fileInfo); DEBUG_LOG(m_logger) << "XXXXXXXXXXX sitmap is: " << fileInfo.absoluteFilePath(); ERROR_LOG(m_logger) << "XXXXXXXXXXX filsize is: " << fil2.size(); // QString x = QDir::separator() + QStringLiteral("thp") + QDir::separator() + QStringLiteral("share") + QDir::separator() + QStringLiteral("situation_Map"); const QDir dstDir2(dstDir.absolutePath() + QDir::separator() + QStringLiteral("thp") + QDir::separator() + QStringLiteral("share") + QDir::separator() + QStringLiteral("LTS") + QDir::separator() + QStringLiteral("situation_Map")) ; DEBUG_LOG(m_logger) << "XXXXXXXXXXX sitmap dst is: " << dstDir2.absolutePath(); auto cp2 = new CopyProcess(fileInfo.absoluteDir(), dstDir2, fil2, false, this); setOverlay(cp2->process()); connect(cp2, &CopyProcess::finished, this, [this](bool success) { if (success) { // new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully")); slotCopyWinBinP3(); } else new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully!")); }); } void USBDialog::slotCopyWinBinP3() { if (m_usbRoot.isEmpty()) return; QDir dstDir(getContentFolder(m_usbRoot) + QDir::separator()); //getContentFolder if (!dstDir.exists()) { dstDir.mkpath(QStringLiteral(".")); } const QString autorunFile = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("autorun.inf")); if (autorunFile.isEmpty()) { ERROR_LOG(m_logger) << "autorunFile init is not accessible"; return; } QFileInfo fileInfo2(autorunFile); QFileInfoList fil; fil = prepareFileInfoListForFolder(fileInfo2.absoluteDir().path(), fil); DEBUG_LOG(m_logger) << "XXXXXXXXXXX autofinFile is: " << fileInfo2.absoluteDir().path(); DEBUG_LOG(m_logger) << "XXXXXXXXXXX filsize is: " << fil.size(); auto cp3 = new CopyProcess(fileInfo2.absoluteDir(), dstDir, fil, false, this); setOverlay(cp3->process()); connect(cp3, &CopyProcess::finished, this, [this](bool success) { if (success) { // new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully")); initUsbModel(); } else new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully!")); }); } class JsonHandler { public: JsonHandler() // invalid {} // JsonHandler(JsonHandler &&o) // { // m_changesMade = o.m_changesMade; // m_exists = o.m_exists; // m_root = o.m_root; // m_f = o.m_f; // m_filepath = o.m_filepath; // m_filename = o.m_filename; // } JsonHandler(const QString &filename, const QString &root, bool mustExist = true) : m_f(new QFile) , m_filename(filename) { m_filepath = QString(root + QDir::separator() + filename); if (!QFile::exists(m_filepath) && mustExist) throw QStringLiteral("%1 does not exist in given root").arg(filename); m_f->setFileName(m_filepath); if (!m_f->open(QIODevice::ReadWrite)) throw QStringLiteral("Cannot open %1").arg(filename); qint64 s = m_f->size(); if (s == 0 && mustExist) throw QStringLiteral("%1 is empty").arg(filename); if (s == 0) return; QJsonParseError err; QJsonDocument doc(QJsonDocument::fromJson(m_f->readAll(), &err)); if (err.error != QJsonParseError::NoError) throw QStringLiteral("Json malformed: %1 - offset %2").arg(err.errorString(), err.offset); m_root = doc.object(); if (m_root.isEmpty()) throw QStringLiteral("%1's object is empty").arg(filename); m_exists = true; // } // else // since exericses.json is empty / did not exist, create the 'Exercises' array now // m_root[s_exercisesObjectIdentifier] = QJsonObject(); // QJsonValue exercisesArrayValue = m_root[s_exercisesObjectIdentifier]; // if (s > 0 && exercisesArrayValue.isUndefined()) // aka. not found (even though the json file has valid json in it) // throw "exercises.json's object is missing the 'Exercises' array"; // if (s == 0 && !mustExist) // m_changesMade = true; // NOTE: can only be set if no throw might follow as the d_tor would write to the file is changesMade == true, despite an error } virtual ~JsonHandler() { std::shared_ptr _(nullptr, [this](int *) { delete m_f; }); // TODO: QScopeGuard if (m_f == nullptr || !m_f->isOpen() || !m_changesMade) return; //m_root[s_exercisesObjectIdentifier] = m_exercisesArray; // move to start of file and set file size to zero m_f->reset(); m_f->resize(0); m_f->write(QJsonDocument(m_root).toJson(QJsonDocument::JsonFormat::Compact)); } protected: bool m_changesMade { false }; bool m_exists { false }; QJsonObject m_root; private: QFile *m_f { nullptr }; QString m_filepath; QString m_filename; }; class ExerciseJsonHandler : public JsonHandler { public: ExerciseJsonHandler(const QString &root, bool mustExist = true) : JsonHandler(QStringLiteral("exercises.json"), root, mustExist) { QJsonValue exercisesArrayValue(m_root[s_exercisesArrayIdentifier]); if (m_exists && exercisesArrayValue.isUndefined()) // aka. not found (even though the json file has valid json in it) throw "exercises.json's object is missing the 'Exercises' array"; m_exercisesArray = m_root[s_exercisesArrayIdentifier].toArray(); if (!m_exists) m_changesMade = true; // NOTE: can only be set if no throw might follow as the d_tor would write to the file if changesMade == true, despite an error } ~ExerciseJsonHandler() { m_root[s_exercisesArrayIdentifier] = m_exercisesArray; } void addExercise(int exerciseId) { m_exercisesArray.append(exerciseId); m_changesMade = true; } bool removeExercise(int exerciseId) { auto it = std::find_if(m_exercisesArray.begin(), m_exercisesArray.end(), [exerciseId](const QJsonValue &val) { bool k = false; int v = val.toVariant().toInt(&k); if (!k) return false; // ouch return (v == exerciseId); }); bool ret = (it != m_exercisesArray.end()); if (ret) { m_exercisesArray.erase(it); m_changesMade = true; } return ret; } private: static const QString s_exercisesArrayIdentifier; QJsonArray m_exercisesArray; // sadly a QJsonValueRef cannot be used here :( }; const QString ExerciseJsonHandler::s_exercisesArrayIdentifier = QStringLiteral("Exercises"); class DataJsonHandler : public JsonHandler { public: DataJsonHandler() // invalid {} // DataJsonHandler(DataJsonHandler &&o) // { // m_presentationsArray = o.m_presentationsArray; // } DataJsonHandler(const QString &root, int exerciseId/*, bool mustExist = true*/) : JsonHandler(QStringLiteral("data.json"), QStringLiteral("%1/%2").arg(root).arg(USBDialog::exerciseDirectoryName(exerciseId)), true) { QJsonValue scriptsArrayValue(m_root[s_presentationsArrayIdentifier]); if (m_exists && scriptsArrayValue.isUndefined()) // aka. not found (even though the json file has valid json in it) throw "data.json's object is missing the 'presentations' array"; m_presentationsArray = m_root[s_presentationsArrayIdentifier].toArray(); if (!m_exists) m_changesMade = true; // NOTE: can only be set if no throw might follow as the d_tor would write to the file if changesMade == true, despite an error } ~DataJsonHandler() { m_root[s_presentationsArrayIdentifier] = m_presentationsArray; } // void addScript(int scriptId) nah... once deleted, data cannot be restored - re-copy data.json instead // { // m_presentationsArray.append(scriptId); // m_changesMade = true; // } QVector scripts() { QVector ret; for (const QJsonValue &val : qAsConst(m_presentationsArray)) { QJsonObject scriptObject(val.toObject()); bool k = false; int v = scriptObject[QStringLiteral("scriptId")].toVariant().toInt(&k); if (!k) continue; // ouch ret += v; } return ret; } bool removeScript(int scriptId) { auto it = std::find_if(m_presentationsArray.begin(), m_presentationsArray.end(), [scriptId](const QJsonValue &val) { QJsonObject scriptObject(val.toObject()); bool k = false; int v = scriptObject[QStringLiteral("scriptId")].toVariant().toInt(&k); if (!k) return false; // ouch return (v == scriptId); }); bool ret = (it != m_presentationsArray.end()); if (ret) { m_presentationsArray.erase(it); m_changesMade = true; } return ret; } private: static const QString s_presentationsArrayIdentifier; QJsonArray m_presentationsArray; // sadly a QJsonValueRef cannot be used here :( }; const QString DataJsonHandler::s_presentationsArrayIdentifier = QStringLiteral("presentations"); void USBDialog::adjustJsonFiles(What what) { /* Problem: data.json has a weird location and has to be updated if a script was copied. * As it has all presentations in it that were ever exported, data.json needs to be adjusted * if a script was deleted or copied. * * Use-cases: * * Script was deleted from exercise: * Check if other scripts exists in exercise, otherwise delete exercise directory, delete exercise from exercises.json * * Exercise diretory was deleted (by removing the last existing script from an exericse): * New exercise directory was created (by copying a script): * Rewrite exercises.json by checking what exercise directories exist * * Script was copied to an existing exercise: * re-copy exercise's data.json and remove all scripts that don't exist in the exercise's directory * */ switch (what) { case What::ScriptWasDeletedFromExercise: { // // look into exercise folder and check if there are script dirs left // QDir dir(getContentFolder(m_usbRoot)); // dir.setFilter(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot); // const QFileInfoList fil(dir.entryInfoList()); // if (fil.isEmpty()) // { // DEBUG_LOG(m_logger) << "no scripts left"; // /* ... determined by checking if there are directories, as scripts are // * in their own directories and scripts are the only directories in an // * exercise directory */ // // delete exercise directory // dir.removeRecursively(); // TODO: hangs.. should be async?? + error handling // // finally remove exercise from exercise.json // try // { // ExerciseJsonHandler(m_usbRoot).removeExercise(m_exerciseId); // } // catch (const QString &str) // { // ERROR_LOG(m_logger) << "ExerciseJsonHandler failed: " << str; // break; // } // break; // } // // there are scripts left, only remove script from exercise dir's data.json // try // { // DataJsonHandler(m_usbRoot, m_exerciseId).removeScript(m_exerciseId); // } // catch (const QString &str) // { // ERROR_LOG(m_logger) << "ExerciseJsonHandler failed: " << str; // break; // } // break; } case What::ExerciseDirectoryWasDeleted: case What::NewExerciseDirectoryWasCreated: { break; } case What::ScriptWasCopiedToExercise: break; } } void USBDialog::scriptDeleted(int scriptId) { // look into exercise folder and check if there are script dirs left QDir dir(m_usbRoot); dir.setFilter(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot); const QFileInfoList fil(dir.entryInfoList()); if (fil.isEmpty()) { DEBUG_LOG(m_logger) << "no scripts left"; /* ... determined by checking if there are directories, as scripts are * in their own directories and scripts are the only directories in an * exercise directory */ // delete exercise directory dir.removeRecursively(); // TODO: hangs.. should be async?? + error handling // finally remove exercise from exercise.json try { ExerciseJsonHandler(m_usbRoot).removeExercise(m_exerciseId); } catch (const QString &str) { ERROR_LOG(m_logger) << "ExerciseJsonHandler failed: " << str; return; } return; } // there are scripts left, only remove script from exercise dir's data.json try { DataJsonHandler(m_usbRoot, m_exerciseId).removeScript(scriptId); } catch (const QString &str) { ERROR_LOG(m_logger) << "DataJsonHandler failed: " << str; } } void USBDialog::scriptCopied(int /*scriptId*/) { compiler_warning("TODO: re-copy data.json ???") // open data.json and remove all 'presentation's that do not exist on the USB // get list of scripts that exist on the USB QDir dir(getContentFolder(m_usbRoot)); dir.setFilter(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot); const QFileInfoList fil(dir.entryInfoList()); Q_ASSERT_X(!fil.isEmpty(), "", "no scripts on USB"); // Logic failure: No scripts on the USB, but missing presentations in data.json isn't possible as a deleted script would have triggered a deletion of the whole exercise directory QVector existingScripts; for (const QFileInfo &fi : fil) { bool k = false; int val = fi.fileName().toInt(&k); if (!k) continue; // hmm.. the only allowed directories are script directories, which have integer-convertible filenames existingScripts += val; } QVector usbScripts; DataJsonHandler djh; // invalid try { // get list of scripts on usb djh = DataJsonHandler(m_usbRoot, m_exerciseId); } catch (const QString &str) { ERROR_LOG(m_logger) << "DataJsonHandler failed: " << str; return; } usbScripts = djh.scripts(); // delete all scripts from data.json that do not exist on USB // prepare usbScripts list to only have the script ids now that DON'T exist on usb usbScripts.erase(std::remove_if(usbScripts.begin(), usbScripts.end(), [existingScripts](int id) { return (std::find(existingScripts.begin(), existingScripts.end(), id) != existingScripts.end()); }), usbScripts.end()); // now really delete scripts from data.json for (const int scriptId : qAsConst(usbScripts)) djh.removeScript(scriptId); // delete all script directories of scripts that do not exist on USB for (const QFileInfo &fi : fil) { bool k = false; int val = fi.fileName().toInt(&k); if (!k || !usbScripts.contains(val)) continue; fi.absoluteDir().removeRecursively(); } } void USBDialog::slotCopy() { if (m_usbRoot.isEmpty()) return; QDir dstDir(m_usbRoot); if (!dstDir.exists()) { ERROR_LOG(m_logger) << "dst dir " << m_usbRoot << " is not accessible"; return; } dstDir = QDir(getContentFolder(m_usbRoot)); if (!dstDir.mkpath(dstDir.absolutePath())) // NOTE: returns true if path already exists { ERROR_LOG(m_logger) << "Could not create destination path " << dstDir.absolutePath(); return; } qWarning() << "dst" << dstDir.absolutePath(); const QModelIndexList mil = m_ui->tvLocal->selectionModel()->selectedRows(); if (mil.size() != 1) return; ExportedScriptInformation esi = m_localThpModel->getRowData(mil.first().row()); if (!QDir(esi.rootDirectory).exists()) return; QString src(getScriptFolder(esi.rootDirectory, esi.exerciseId, esi.scriptId)); DEBUG_LOG(m_logger) << "src" << src; QDir srcDir(src); QDir bldgDir( QStringLiteral("%1/BuildingModels").arg(getContentFolder(esi.rootDirectory, esi.exerciseId))); if (!srcDir.exists()) { ERROR_LOG(m_logger) << "src dir " << src << " is not accessible"; return; } auto cp = new CopyProcess(srcDir, dstDir, {}, true, this); //copy buildings secretly auto cpBuildings = new CopyProcess(bldgDir, dstDir, {}, true, this); cpBuildings->process(); setOverlay(cp->process()); connect(cp, &CopyProcess::finished, this, [this, esi](bool success) { if (!success) { new LtsWidgets::LtsMessageDialogNew(this, tr("The files could not be copied successfully!")); return; } new LtsWidgets::LtsMessageDialogNew(this, tr("The files were copied successfully")); TMP_WARNING_LOG(m_logger) << "GO GO GO"; ExportedScriptInformation esi2(esi); esi2.rootDirectory = m_usbRoot; // edit esi to point to the new rootdir (the usb) m_localThpModel->addDownloadData(esi2); initUsbModel(); // fill ! // scriptCopied(esi.scriptId); }); } void USBDialog::slotRemove() { // this operation is fast enough to NOT do it async 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(); for (const QModelIndex &idx : mil) { int scriptId = idx.data(Qt::UserRole).toInt(); // qWarning() << "rm" << scriptId; // qWarning() << "rm'd?" << QDir(getScriptFolder(m_usbRoot, m_exerciseId, scriptId)).removeRecursively(); scriptDeleted(scriptId); } initUsbModel(); // instead of deleting the rows, just reinit the whole model... ugh, but quickly done } void USBDialog::slotRemoveMaps() { // this operation is fast enough to NOT do it async if (m_usbRoot.isEmpty()) return; QDir dir(getContentFolder(m_usbRoot) + QDir::separator() + QStringLiteral("Maps")); //getContentFolder if (!dir.exists()) { ERROR_LOG(m_logger) << "dir " << m_usbRoot << " is not accessible"; return; } const QModelIndexList mil = m_ui->tvUSBMaps->selectionModel()->selectedRows(); if (mil.size() < 1) return; DEBUG_LOG(m_logger) << "mil.size()" << mil.size(); for (const QModelIndex &idx : mil) { DEBUG_LOG(m_logger) << "rm" << idx.data().toString(); QFile::remove(dir.absolutePath() + QDir::separator() + idx.data().toString()); } initUsbModel(); // instead of deleting the rows, just reinit the whole model... ugh, but quickly done generateLayerMappingXML(); } void USBDialog::slotSelectionChanged() { const QModelIndexList mil = m_ui->tvLocal->selectionModel()->selectedRows(); // compiler_warning("TODO") // if (mil.size() != 1) // { // 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 // return; // } // const ExportedScriptInformation esi = m_localThpModel->getRowData(mil.first().row()); // if (m_session->app() != LtsAppDescriptor::App::OC) // { // if (esi.local) // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(true); // local remove Button // m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(true); // upload to Server // } // else // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(false); // local remove Button // m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(false); // upload to Server // } // if (esi.uploaded == ThpAlreadyUploaded) // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(true); // Remove from Server Button // } // else // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(false); // Remove from Server Button // } // } // else // OC // { // if (esi.uploaded == ThpAlreadyUploaded) // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(true); // download from Server // m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(true); // Remove from Server Button // } // else // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setEnabled(false); // Remove from Server Button // } // if (esi.local) // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setEnabled(true); // Show presentation Button // m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(true); // local remove Button // } // else // { // m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setEnabled(false); // Show presentation Button // m_buttonBox->getButton(LtsWidgets::DialogButton::Remove)->setEnabled(false); // local remove Button // } // } } void USBDialog::retranslate() { translate(m_ui, tr("Manage exported scripts")); } void USBDialog::initModel() { DEBUG_LOG(m_logger) << "(re)initModel !!"; // TODO: move to model // compiler_warning("TODO") // m_fsWatcher->addPath(getRootFolder()); // m_fsWatcher->addPath(getContentFolder(m_session->exerciseId())); if (!m_localThpModel->init(getRootFolder())) { throw "init model failed for Export"; if (!m_localThpModel->init(getRootFolder(true))) // Download directory throw "init model failed for Download"; } m_ui->tvLocal->setModel(m_localThpModel); // important to set model before setting hidden columns m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColId); m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColLocation); m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColUploaded); m_ui->tvLocal->hideColumn(ExportedScriptsModel::ColUploadedTime); // fill local maps { m_ui->tvLocalMaps->setRowCount(0); m_ui->tvLocalMaps->clearContents(); //Maps when WebMerc dann Scene sonst map QDirIterator tpit(QStringLiteral("/usr/share/arcgis/resources/TilePackage/"), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); while (tpit.hasNext()) { QString file = tpit.next(); m_ui->tvLocalMaps->insertRow(m_ui->tvLocalMaps->rowCount()); QFileInfo mapFile(file); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 0, mapFile.fileName().contains(QStringLiteral("WebMerc")) ? new QTableWidgetItem(tr("3D Map")) : new QTableWidgetItem(tr("2D Map")) ); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 1, new QTableWidgetItem(mapFile.fileName())); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 2, new QTableWidgetItem(QFileInfo(file).absolutePath())); } //ObjectScene QDirIterator slpkit(QStringLiteral("/usr/share/arcgis/resources/slpk/"), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (slpkit.hasNext()) { QString file = slpkit.next(); if (!file.endsWith(QStringLiteral(".slpk"))) continue; QFileInfo fi(file); if(fi.isSymLink()) continue; m_ui->tvLocalMaps->insertRow(m_ui->tvLocalMaps->rowCount()); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 0, new QTableWidgetItem(tr("3D Object"))); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 1, new QTableWidgetItem(QFileInfo(file).fileName())); m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 2, new QTableWidgetItem(QFileInfo(file).absolutePath())); } //Eleveation // We will copy all elevations every time! // QDirIterator eleit(QStringLiteral("/usr/share/arcgis/resources/elevation/"), QDir::Files | QDir::NoDotAndDotDot, // QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); // while (eleit.hasNext()) // { // QString file = eleit.next(); // m_ui->tvLocalMaps->insertRow(m_ui->tvLocalMaps->rowCount()); // m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 0, new QTableWidgetItem(QStringLiteral("Eleveation"))); // m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 1, new QTableWidgetItem(QFileInfo(file).fileName())); // // m_ui->tvLocalMaps->setItem(m_ui->tvLocalMaps->rowCount() - 1, 2, new QTableWidgetItem(QFileInfo(file).absolutePath())); // } } m_ui->tvLocalMaps->sortItems(0); m_ui->tvLocalMaps->hideColumn(2); slotSelectionChanged(); // aka. enaDisa } QString USBDialog::getContentFolder(const QString &rootFolder) const { return QStringLiteral("%1/%2").arg(rootFolder, exerciseDirectoryName()); } QString USBDialog::getContentFolder() const { return getContentFolder(getRootFolder(), m_exerciseId); } /*static*/ QString USBDialog::exerciseDirectoryName(int exerciseId) { return QStringLiteral("Exercise_%1").arg(exerciseId); } QString USBDialog::exerciseDirectoryName() const { return exerciseDirectoryName(m_exerciseId); } /*static*/ QString USBDialog::getContentFolder(const QString &rootFolder, int exerciseId) { return QStringLiteral("%1/%2").arg(rootFolder, exerciseDirectoryName(exerciseId)); } QString USBDialog::getContentFolder(int exerciseId) const { return getContentFolder(getRootFolder(), exerciseId); } /* static */ QString USBDialog::getScriptFolder(const QString &rootFolder, int exerciseId, int scriptId) { return QStringLiteral("%1/%2").arg(getContentFolder(rootFolder, exerciseId)).arg(scriptId); } QString USBDialog::getScriptFolder(const QString &rootFolder, int scriptId) const { return getScriptFolder(rootFolder, m_exerciseId, scriptId); } QString USBDialog::getScriptFolder(int scriptId) const { return getScriptFolder(getRootFolder(), m_exerciseId, scriptId); } QString USBDialog::getRootFolder(bool forceDownloadDir) const { if (m_session->app() == LtsAppDescriptor::App::OC || forceDownloadDir) return QDir::tempPath() + QStringLiteral("/THP-Download/"); return QDir::tempPath() + QStringLiteral("/THP-Export/"); } } // namespace TakeHomePackageManager #include "tpm_usb_dialog.moc"