385 lines
9.7 KiB
C++
Executable File
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 ©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
|