Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* @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
|
||||
Reference in new issue
Block a user