/** * @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 #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 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 *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, fileList)); } #define cancelationPoint if (m_wasCanceled) { qWarning() << "AsyncFileCopy: canceled"; return false; } (void)m_wasCanceled bool AsyncFileCopy::copy(const CopyInfoList &fileList) { qint64 totalAll = 0, writtenAll = 0; int fileCount = 0; for (const auto &fileInfo : fileList) { totalAll += fileInfo.fileInfo.size(); // there should be ONLY files in QFileInfoList! fileCount++; } if (fileCount < 1) { qWarning() << "AsyncFileCopy: fileCount < 1"; return false; } //qWarning() << "AsyncFileCopy: copy" << fileList.count() << "files"; emit started(fileCount, totalAll); cancelationPoint; int currentFile = 0; for (const auto ©Info : fileList) { cancelationPoint; const QFileInfo &fileInfo = copyInfo.fileInfo; const QString &srcPath = copyInfo.srcPath; const QString &dstPath = copyInfo.dstPath; currentFile++; const qint64 totalFile = fileInfo.size(); qint64 writtenFile = 0; QDir srcDir(srcPath); // if (cdUp) // srcDir.cdUp(); // src: /shd/CTC/WS/THP2 // file: /shd/CTC/WS/THP2/lib/script.js // dst: /mnt/thps/42.3/ // full: /mnt/thps/42.3/lib/ // soll: /mnt/thps/42.3/lib/script.js // NOTE: src: /shd/CTC/WS (if cdUp!) const QString dstFullPath(dstPath + QDir::separator() + fileInfo.absolutePath().remove(srcDir.path())); if (!QDir().mkpath(dstFullPath) /* why is this not static, Qt? Directory validation? */) { qWarning() << "AsyncFileCopy: cannot create path"; return false; } QFile srcFile(fileInfo.absoluteFilePath()); emit newFile(currentFile, totalFile, fileInfo.fileName()); QFile dstFile(dstFullPath + QDir::separator() + fileInfo.fileName()); qWarning() << "AsyncFileCopy: copy file from" << srcFile.fileName() << "to" << dstFile.fileName(); if (!srcFile.open(QIODevice::ReadOnly)) { qWarning() << QStringLiteral("failed to open src '%1' (error: %2)").arg(srcFile.fileName(), srcFile.errorString()); return false; } if (!dstFile.open(QIODevice::Unbuffered /* make "done" really mean "done" */| QIODevice::WriteOnly | QIODevice::Truncate)) { qWarning() << QStringLiteral("failed to open dst '%1' (error: %2)").arg(dstFile.fileName(), dstFile.errorString()); return false; } qint64 fileSize = srcFile.size(); while (1) { cancelationPoint; //#define SUPERSLOW emit status(tr("Reading...")); const auto data = srcFile.read( #ifdef SUPERSLOW 54321 #else s_chunkSize #endif ); cancelationPoint; #ifdef SUPERSLOW usleep(100 * 1000); // 100ms #endif emit status(tr("Copying...")); const auto _written = dstFile.write(data); //qWarning() << "AsyncFileCopy: read" << data.size() << "write" << _written << "size" << fileSize; #ifdef SUPERSLOW usleep(100 * 1000); #endif if (_written < 0) { qWarning() << "AsyncFileCopy: unable to write to file" << dstFile.fileName() << ":" << dstFile.errorString(); return false; } writtenFile += _written; writtenAll += _written; //qWarning() << "AsyncFileCopy: progress" << writtenFile << totalFile << writtenAll << totalAll << currentFile << fileCount; emit progress(writtenFile, writtenAll); cancelationPoint; // TODO: windoofs #ifdef Q_OS_UNIX //qWarning() << "AsyncFileCopy: flushing..."; emit status(tr("Writing...")); QElapsedTimer t; t.start(); fsync(dstFile.handle()); // fsync(2): "The call blocks until the device reports that the transfer has completed." // TODO: use O_DIRECT instead (?) //qWarning() << "took" << t.elapsed() << "ms"; #endif if (data.size() != _written) // fail if it didn't write all it read before { qWarning() << "AsyncFileCopy: copy failed! read" << data.size() << "write" << _written << "size" << fileSize; return false; } if (writtenFile == fileSize) // done { break; } } } if (totalAll == writtenAll) { //qWarning() << "AsyncFileCopy: done - " << writtenAll << "/" << totalAll << "bytes"; return true; } else { qWarning() << "AsyncFileCopy: failed! total written bytes doesn't match all requested bytes - " << writtenAll << "/" << totalAll << "bytes"; return false; } } /// CopyProcess CopyProcess::CopyProcess(const CopyInfoList &fileList, QObject *parent) : QObject(parent) , m_process(new ProcessOverlay) { connect(m_process, &ProcessOverlay::signalCancel, this, [this]() { //DEBUG_LOG(m_logger) << "m_wasCanceled!!"; m_wasCanceled = true; //m_process->setCancelEnabled(false); does so itself already }); connect(m_process, &ProcessOverlay::signalCancel, this, &CopyProcess::signalCancel); AsyncFileCopy *afc = AsyncFileCopy::createAsyncFileCopy(fileList); connect(m_process, &ProcessOverlay::signalCancel, afc, &AsyncFileCopy::slotCancel); connect(afc, &AsyncFileCopy::progress, this, [this](qint64 bytesCopiedFile, qint64 bytesCopiedAll) { m_process->setFileProgress(bytesCopiedFile, bytesCopiedAll); }); connect(afc, &AsyncFileCopy::newFile, this, [this](int currentFile, qint64 bytesTotalFile, const QString &filename) { m_process->setNewFile(currentFile, bytesTotalFile, filename); }); connect(afc, &AsyncFileCopy::started, this, [this](int fileCount, qint64 bytesTotalAll) { m_process->setStarted(fileCount, bytesTotalAll); }); connect(afc, &AsyncFileCopy::status, this, [this](const QString &status) { m_process->setStatus(status); }); connect(afc, &AsyncFileCopy::newAction, this, [this](const QString &newAction) { m_currentAction = newAction; }); connect(afc, &AsyncFileCopy::finished, this, &QObject::deleteLater); connect(afc, &AsyncFileCopy::finished, this, &CopyProcess::finished); } CopyProcess::~CopyProcess() { //DEBUG_LOG(m_logger) << "~CopyProcess"; //delete m_process; m_process->deleteLater(); } QWidget *CopyProcess::process() { return m_process; } } // namespace TakeHomePackageManager