5#include <QtCore/QCryptographicHash>
8#include <QtCore/QFileInfo>
9#include <QtCore/QSaveFile>
10#include <QtCore/QStandardPaths>
11#include <QtCore/QStorageInfo>
12#include <QtCore/QTemporaryFile>
19QByteArray readFile(
const QString &filePath, QString *
errorString, qint64 maxBytes)
21 if (filePath.isEmpty()) {
29 if (!file.open(QIODevice::ReadOnly)) {
31 *
errorString = QObject::tr(
"Failed to open file: %1 - %2").arg(filePath, file.errorString());
38 data = file.read(maxBytes);
40 data = file.readAll();
49 static size_t cachedSize = 0;
50 if (cachedSize != 0) {
57 if (!path.isEmpty()) {
58 QStorageInfo storage(path);
59 if (storage.isValid()) {
60 blockSize = storage.blockSize();
66 blockSize = QStorageInfo::root().blockSize();
74 cachedSize =
static_cast<size_t>(
89QString
joinPath(
const QString &dir,
const QString &name)
94 if (dir.endsWith(QLatin1Char(
'/'))) {
97 return dir + QLatin1Char(
'/') + name;
106 return dir.mkpath(path);
116 QDir sourceDir(sourcePath);
117 if (!sourceDir.exists()) {
118 qCWarning(QGCFileHelperLog) <<
"Source directory doesn't exist:" << sourcePath;
123 qCWarning(QGCFileHelperLog) <<
"Failed to create destination directory:" << destPath;
128 const QStringList files = sourceDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
129 for (
const QString &fileName : files) {
130 const QString srcFilePath =
joinPath(sourcePath, fileName);
131 const QString dstFilePath =
joinPath(destPath, fileName);
132 if (!QFile::copy(srcFilePath, dstFilePath)) {
133 qCWarning(QGCFileHelperLog) <<
"Failed to copy file:" << srcFilePath <<
"to" << dstFilePath;
139 const QStringList dirs = sourceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
140 for (
const QString &dirName : dirs) {
141 const QString srcSubDir =
joinPath(sourcePath, dirName);
142 const QString dstSubDir =
joinPath(destPath, dirName);
153 if (sourcePath.isEmpty() || destPath.isEmpty()) {
154 qCWarning(QGCFileHelperLog) <<
"moveFileOrCopy: empty path";
159 if (QFile::rename(sourcePath, destPath)) {
164 qCDebug(QGCFileHelperLog) <<
"moveFileOrCopy: rename failed, using copy for:" << sourcePath;
166 const bool isDir = QFileInfo(sourcePath).isDir();
167 bool copySuccess =
false;
172 QDir(sourcePath).removeRecursively();
175 copySuccess = QFile::copy(sourcePath, destPath);
177 QFile::remove(sourcePath);
182 qCWarning(QGCFileHelperLog) <<
"moveFileOrCopy: failed to move:" << sourcePath <<
"to" << destPath;
190 if (filePath.isEmpty()) {
191 qCWarning(QGCFileHelperLog) <<
"atomicWrite: file path is empty";
197 qCWarning(QGCFileHelperLog) <<
"atomicWrite: failed to create parent directory for:" << filePath;
202 QSaveFile file(filePath);
203 if (!file.open(QIODevice::WriteOnly)) {
204 qCWarning(QGCFileHelperLog) <<
"atomicWrite: failed to open:" << filePath
205 <<
"-" << file.errorString();
209 if (file.write(data) != data.size()) {
210 qCWarning(QGCFileHelperLog) <<
"atomicWrite: write failed:" << file.errorString();
211 file.cancelWriting();
215 if (!file.commit()) {
216 qCWarning(QGCFileHelperLog) <<
"atomicWrite: commit failed:" << file.errorString();
225 if (path.isEmpty()) {
230 QStorageInfo storage(path);
231 if (!storage.isValid()) {
233 storage = QStorageInfo(QFileInfo(path).absolutePath());
236 if (!storage.isValid()) {
237 qCDebug(QGCFileHelperLog) <<
"availableDiskSpace: cannot determine storage for:" << path;
241 return storage.bytesAvailable();
246 if (requiredBytes <= 0) {
251 if (bytesAvailable < 0) {
252 qCDebug(QGCFileHelperLog) <<
"hasSufficientDiskSpace: cannot determine disk space, proceeding anyway";
256 const qint64 bytesRequired =
static_cast<qint64
>(
static_cast<double>(requiredBytes) * margin);
258 if (bytesAvailable < bytesRequired) {
259 const int marginPercent =
static_cast<int>((margin - 1.0) * 100);
260 qCWarning(QGCFileHelperLog) <<
"Insufficient disk space:"
261 <<
"required" << bytesRequired <<
"bytes"
262 <<
"(" << requiredBytes <<
"+" << marginPercent <<
"% margin)"
263 <<
"available" << bytesAvailable <<
"bytes";
276 if (urlOrPath.isEmpty()) {
281 if (urlOrPath.startsWith(QLatin1String(
":/"))) {
287 if (url.isValid() && !url.scheme().isEmpty()) {
297 if (!url.isValid()) {
299 const QString path = url.path();
300 if (!path.isEmpty()) {
306 const QString scheme = url.scheme().toLower();
309 if (scheme == QLatin1String(
"file")) {
310 return url.toLocalFile();
314 if (scheme == QLatin1String(
"qrc")) {
315 QString path = url.path();
316 if (!path.startsWith(QLatin1Char(
'/'))) {
317 path.prepend(QLatin1Char(
'/'));
319 return QLatin1String(
":/") + path.mid(1);
323 if (scheme.isEmpty()) {
328 qCDebug(QGCFileHelperLog) <<
"toLocalPath: URL scheme not supported for local access:" << scheme;
329 return url.toString();
334 if (urlOrPath.isEmpty()) {
345 if (url.isValid() && !url.scheme().isEmpty()) {
346 const QString scheme = url.scheme().toLower();
347 return scheme == QLatin1String(
"file") ||
348 scheme == QLatin1String(
"qrc");
357 return path.startsWith(QLatin1String(
":/")) ||
358 path.startsWith(QLatin1String(
"qrc:/"), Qt::CaseInsensitive);
365QString computeFileHash(
const QString &filePath, QCryptographicHash::Algorithm algorithm)
367 if (filePath.isEmpty()) {
368 qCWarning(QGCFileHelperLog) <<
"computeFileHash: empty file path";
372 QFile file(filePath);
373 if (!file.open(QIODevice::ReadOnly)) {
374 qCWarning(QGCFileHelperLog) <<
"computeFileHash: failed to open file:" << filePath
375 <<
"-" << file.errorString();
379 QCryptographicHash hash(algorithm);
380 if (!hash.addData(&file)) {
381 qCWarning(QGCFileHelperLog) <<
"computeFileHash: failed to read file:" << filePath;
385 return QString::fromLatin1(hash.result().toHex());
388QString
computeHash(
const QByteArray &data, QCryptographicHash::Algorithm algorithm)
390 return QString::fromLatin1(QCryptographicHash::hash(data, algorithm).toHex());
394 QCryptographicHash::Algorithm algorithm)
396 if (expectedHash.isEmpty()) {
397 qCWarning(QGCFileHelperLog) <<
"verifyFileHash: empty expected hash";
401 const QString actualHash = computeFileHash(filePath, algorithm);
402 if (actualHash.isEmpty()) {
406 const bool match = (actualHash.compare(expectedHash, Qt::CaseInsensitive) == 0);
408 qCWarning(QGCFileHelperLog) <<
"verifyFileHash: hash mismatch for" << filePath
409 <<
"- expected:" << expectedHash.left(16) <<
"..."
410 <<
"actual:" << actualHash.left(16) <<
"...";
418 case QCryptographicHash::Md4:
return QStringLiteral(
"MD4");
419 case QCryptographicHash::Md5:
return QStringLiteral(
"MD5");
420 case QCryptographicHash::Sha1:
return QStringLiteral(
"SHA-1");
421 case QCryptographicHash::Sha224:
return QStringLiteral(
"SHA-224");
422 case QCryptographicHash::Sha256:
return QStringLiteral(
"SHA-256");
423 case QCryptographicHash::Sha384:
return QStringLiteral(
"SHA-384");
424 case QCryptographicHash::Sha512:
return QStringLiteral(
"SHA-512");
425 case QCryptographicHash::Sha3_224:
return QStringLiteral(
"SHA3-224");
426 case QCryptographicHash::Sha3_256:
return QStringLiteral(
"SHA3-256");
427 case QCryptographicHash::Sha3_384:
return QStringLiteral(
"SHA3-384");
428 case QCryptographicHash::Sha3_512:
return QStringLiteral(
"SHA3-512");
429 case QCryptographicHash::Blake2b_160:
return QStringLiteral(
"BLAKE2b-160");
430 case QCryptographicHash::Blake2b_256:
return QStringLiteral(
"BLAKE2b-256");
431 case QCryptographicHash::Blake2b_384:
return QStringLiteral(
"BLAKE2b-384");
432 case QCryptographicHash::Blake2b_512:
return QStringLiteral(
"BLAKE2b-512");
433 case QCryptographicHash::Blake2s_128:
return QStringLiteral(
"BLAKE2s-128");
434 case QCryptographicHash::Blake2s_160:
return QStringLiteral(
"BLAKE2s-160");
435 case QCryptographicHash::Blake2s_224:
return QStringLiteral(
"BLAKE2s-224");
436 case QCryptographicHash::Blake2s_256:
return QStringLiteral(
"BLAKE2s-256");
437 default:
return QStringLiteral(
"Unknown");
449QString normalizeTemplateName(
const QString &templateName)
451 QString name = templateName.isEmpty() ? QStringLiteral(
"qgc_XXXXXX") : templateName;
453 if (!name.contains(QLatin1String(
"XXXXXX"))) {
454 const qsizetype dotPos = name.lastIndexOf(QLatin1Char(
'.'));
456 name.insert(dotPos, QLatin1String(
"_XXXXXX"));
458 name.append(QLatin1String(
"_XXXXXX"));
469 return QStandardPaths::writableLocation(QStandardPaths::TempLocation);
474 const QString name = normalizeTemplateName(templateName);
476 QTemporaryFile temp(
tempDirectory() + QLatin1Char(
'/') + name);
478 qCWarning(QGCFileHelperLog) <<
"uniqueTempPath: failed to create temp file";
483 const QString path = temp.fileName();
490std::unique_ptr<QTemporaryFile>
createTempFile(
const QByteArray &data,
const QString &templateName)
492 const QString name = normalizeTemplateName(templateName);
494 auto temp = std::make_unique<QTemporaryFile>(
tempDirectory() + QLatin1Char(
'/') + name);
496 qCWarning(QGCFileHelperLog) <<
"createTempFile: failed to create temp file";
500 if (temp->write(data) != data.size()) {
501 qCWarning(QGCFileHelperLog) <<
"createTempFile: failed to write data";
508 qCDebug(QGCFileHelperLog) <<
"createTempFile: created" << temp->fileName()
509 <<
"with" << data.size() <<
"bytes";
513std::unique_ptr<QTemporaryFile>
createTempCopy(
const QString &sourcePath,
const QString &templateName)
515 if (sourcePath.isEmpty()) {
516 qCWarning(QGCFileHelperLog) <<
"createTempCopy: source path is empty";
520 QFile source(sourcePath);
521 if (!source.open(QIODevice::ReadOnly)) {
522 qCWarning(QGCFileHelperLog) <<
"createTempCopy: failed to open source:" << sourcePath
523 <<
"-" << source.errorString();
527 QString name = templateName;
528 if (name.isEmpty()) {
530 name = QFileInfo(sourcePath).fileName() + QLatin1String(
"_XXXXXX");
535 qCDebug(QGCFileHelperLog) <<
"createTempCopy: copied" << sourcePath <<
"to" << temp->fileName();
542 if (!tempFile || !tempFile->isOpen()) {
543 qCWarning(QGCFileHelperLog) <<
"replaceFileFromTemp: temp file is null or not open";
547 if (targetPath.isEmpty()) {
548 qCWarning(QGCFileHelperLog) <<
"replaceFileFromTemp: target path is empty";
554 qCWarning(QGCFileHelperLog) <<
"replaceFileFromTemp: failed to create parent directory";
562 if (!backupPath.isEmpty() && QFile::exists(targetPath)) {
563 QFile::remove(backupPath);
564 if (!QFile::copy(targetPath, backupPath)) {
565 qCWarning(QGCFileHelperLog) <<
"replaceFileFromTemp: failed to create backup";
568 qCDebug(QGCFileHelperLog) <<
"replaceFileFromTemp: backed up to" << backupPath;
572 QFile::remove(targetPath);
575 tempFile->setAutoRemove(
false);
576 const QString tempPath = tempFile->fileName();
580 qCWarning(QGCFileHelperLog) <<
"replaceFileFromTemp: failed to move temp file to target";
581 QFile::remove(tempPath);
585 qCDebug(QGCFileHelperLog) <<
"replaceFileFromTemp: replaced" << targetPath;
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Generic file system helper utilities.
qint64 availableDiskSpace(const QString &path)
bool isLocalPath(const QString &urlOrPath)
bool isQtResource(const QString &path)
QString uniqueTempPath(const QString &templateName)
constexpr size_t kBufferSizeMax
Maximum buffer size for I/O operations.
bool ensureParentExists(const QString &filePath)
bool moveFileOrCopy(const QString &sourcePath, const QString &destPath)
QString hashAlgorithmName(QCryptographicHash::Algorithm algorithm)
QString computeHash(const QByteArray &data, QCryptographicHash::Algorithm algorithm)
bool copyDirectoryRecursively(const QString &sourcePath, const QString &destPath)
std::unique_ptr< QTemporaryFile > createTempCopy(const QString &sourcePath, const QString &templateName)
bool atomicWrite(const QString &filePath, const QByteArray &data)
QString toLocalPath(const QString &urlOrPath)
bool hasSufficientDiskSpace(const QString &path, qint64 requiredBytes, double margin)
bool ensureDirectoryExists(const QString &path)
std::unique_ptr< QTemporaryFile > createTempFile(const QByteArray &data, const QString &templateName)
QString joinPath(const QString &dir, const QString &name)
bool exists(const QString &path)
constexpr size_t kBufferSizeMin
Minimum buffer size for I/O operations.
size_t optimalBufferSize(const QString &path)
bool replaceFileFromTemp(QTemporaryFile *tempFile, const QString &targetPath, const QString &backupPath)
bool verifyFileHash(const QString &filePath, const QString &expectedHash, QCryptographicHash::Algorithm algorithm)
constexpr size_t kBufferSizeDefault
Default buffer size when detection unavailable.