Files
qt5-legatus-legacy/Lts/Plugins/TakeHomePackageManager/THP2/Common2/tpm_file_copy.cpp
T
2026-08-13 18:33:10 +02:00

385 lines
9.7 KiB
C++
Executable File

/**
* @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