2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2011-03-30 12:06:05 +02:00
|
|
|
|
|
|
|
|
#include "fileutils.h"
|
|
|
|
|
#include "savefile.h"
|
|
|
|
|
|
2014-09-10 14:00:12 +02:00
|
|
|
#include "algorithm.h"
|
2023-01-24 16:49:41 +01:00
|
|
|
#include "qtcassert.h"
|
|
|
|
|
#include "utilstr.h"
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2022-05-31 11:16:44 +02:00
|
|
|
#include "fsengine/fileiconprovider.h"
|
|
|
|
|
#include "fsengine/fsengine.h"
|
|
|
|
|
|
2017-08-18 14:32:39 +02:00
|
|
|
#include <QDataStream>
|
2022-08-19 14:53:28 +02:00
|
|
|
#include <QDateTime>
|
2020-06-11 15:41:26 +02:00
|
|
|
#include <QDebug>
|
2022-07-26 13:32:07 +02:00
|
|
|
#include <QRegularExpression>
|
2022-05-24 12:31:20 +02:00
|
|
|
#include <QTemporaryFile>
|
2022-05-17 22:25:02 +02:00
|
|
|
#include <QTextStream>
|
2022-05-24 00:40:44 +02:00
|
|
|
#include <QXmlStreamWriter>
|
|
|
|
|
|
2019-04-04 23:03:03 +03:00
|
|
|
#include <qplatformdefs.h>
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2017-08-18 14:32:39 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
#include <QMessageBox>
|
2022-05-31 11:16:44 +02:00
|
|
|
#include <QGuiApplication>
|
2017-08-18 14:32:39 +02:00
|
|
|
#endif
|
|
|
|
|
|
2013-10-23 22:07:46 +03:00
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 10:28:07 +02:00
|
|
|
#ifdef QTCREATOR_PCH_H
|
2021-05-01 21:04:48 +02:00
|
|
|
#define CALLBACK WINAPI
|
2021-05-05 10:28:07 +02:00
|
|
|
#endif
|
2013-10-23 22:07:46 +03:00
|
|
|
#include <qt_windows.h>
|
2018-07-19 00:07:58 +03:00
|
|
|
#include <shlobj.h>
|
2013-10-23 22:07:46 +03:00
|
|
|
#endif
|
|
|
|
|
|
2022-10-13 11:11:29 +02:00
|
|
|
#ifdef Q_OS_MACOS
|
2015-09-01 15:59:27 +02:00
|
|
|
#include "fileutils_mac.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2011-03-30 12:06:05 +02:00
|
|
|
namespace Utils {
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
// FileReader
|
2021-06-11 14:33:58 +02:00
|
|
|
|
2011-03-30 12:06:05 +02:00
|
|
|
QByteArray FileReader::fetchQrc(const QString &fileName)
|
|
|
|
|
{
|
2021-05-18 09:47:07 +02:00
|
|
|
QTC_ASSERT(fileName.startsWith(':'), return QByteArray());
|
2011-03-30 12:06:05 +02:00
|
|
|
QFile file(fileName);
|
|
|
|
|
bool ok = file.open(QIODevice::ReadOnly);
|
2012-04-17 08:01:25 +02:00
|
|
|
QTC_ASSERT(ok, qWarning() << fileName << "not there!"; return QByteArray());
|
2011-03-30 12:06:05 +02:00
|
|
|
return file.readAll();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 09:47:07 +02:00
|
|
|
bool FileReader::fetch(const FilePath &filePath, QIODevice::OpenMode mode)
|
2011-03-30 12:06:05 +02:00
|
|
|
{
|
2012-04-17 08:01:25 +02:00
|
|
|
QTC_ASSERT(!(mode & ~(QIODevice::ReadOnly | QIODevice::Text)), return false);
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2022-11-24 08:52:47 +01:00
|
|
|
const expected_str<QByteArray> contents = filePath.fileContents();
|
|
|
|
|
if (!contents) {
|
|
|
|
|
m_errorString = contents.error();
|
2011-03-30 12:06:05 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2022-11-24 08:52:47 +01:00
|
|
|
m_data = *contents;
|
2011-03-30 12:06:05 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 09:47:07 +02:00
|
|
|
bool FileReader::fetch(const FilePath &filePath, QIODevice::OpenMode mode, QString *errorString)
|
2011-03-30 12:06:05 +02:00
|
|
|
{
|
2021-05-18 09:47:07 +02:00
|
|
|
if (fetch(filePath, mode))
|
2011-03-30 12:06:05 +02:00
|
|
|
return true;
|
|
|
|
|
if (errorString)
|
|
|
|
|
*errorString = m_errorString;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 14:32:39 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2021-05-18 09:47:07 +02:00
|
|
|
bool FileReader::fetch(const FilePath &filePath, QIODevice::OpenMode mode, QWidget *parent)
|
2011-03-30 12:06:05 +02:00
|
|
|
{
|
2021-05-18 09:47:07 +02:00
|
|
|
if (fetch(filePath, mode))
|
2011-03-30 12:06:05 +02:00
|
|
|
return true;
|
|
|
|
|
if (parent)
|
2023-01-24 16:49:41 +01:00
|
|
|
QMessageBox::critical(parent, Tr::tr("File Error"), m_errorString);
|
2011-03-30 12:06:05 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-18 14:32:39 +02:00
|
|
|
#endif // QT_GUI_LIB
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
// FileSaver
|
|
|
|
|
|
2018-07-23 10:45:40 +02:00
|
|
|
FileSaverBase::FileSaverBase() = default;
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2017-07-03 12:02:26 +02:00
|
|
|
FileSaverBase::~FileSaverBase() = default;
|
|
|
|
|
|
2011-03-30 12:06:05 +02:00
|
|
|
bool FileSaverBase::finalize()
|
|
|
|
|
{
|
|
|
|
|
m_file->close();
|
|
|
|
|
setResult(m_file->error() == QFile::NoError);
|
2017-06-29 15:04:01 +02:00
|
|
|
m_file.reset();
|
2011-03-30 12:06:05 +02:00
|
|
|
return !m_hasError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::finalize(QString *errStr)
|
|
|
|
|
{
|
|
|
|
|
if (finalize())
|
|
|
|
|
return true;
|
|
|
|
|
if (errStr)
|
|
|
|
|
*errStr = errorString();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 14:32:39 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2011-03-30 12:06:05 +02:00
|
|
|
bool FileSaverBase::finalize(QWidget *parent)
|
|
|
|
|
{
|
|
|
|
|
if (finalize())
|
|
|
|
|
return true;
|
2023-01-24 16:49:41 +01:00
|
|
|
QMessageBox::critical(parent, Tr::tr("File Error"), errorString());
|
2011-03-30 12:06:05 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-18 14:32:39 +02:00
|
|
|
#endif // QT_GUI_LIB
|
2011-03-30 12:06:05 +02:00
|
|
|
|
|
|
|
|
bool FileSaverBase::write(const char *data, int len)
|
|
|
|
|
{
|
|
|
|
|
if (m_hasError)
|
|
|
|
|
return false;
|
|
|
|
|
return setResult(m_file->write(data, len) == len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::write(const QByteArray &bytes)
|
|
|
|
|
{
|
|
|
|
|
if (m_hasError)
|
|
|
|
|
return false;
|
|
|
|
|
return setResult(m_file->write(bytes) == bytes.count());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::setResult(bool ok)
|
|
|
|
|
{
|
|
|
|
|
if (!ok && !m_hasError) {
|
2017-06-29 15:16:26 +02:00
|
|
|
if (!m_file->errorString().isEmpty()) {
|
2023-01-24 16:49:41 +01:00
|
|
|
m_errorString = Tr::tr("Cannot write file %1: %2")
|
2021-05-18 09:47:07 +02:00
|
|
|
.arg(m_filePath.toUserOutput(), m_file->errorString());
|
2017-06-29 15:16:26 +02:00
|
|
|
} else {
|
2023-01-24 16:49:41 +01:00
|
|
|
m_errorString = Tr::tr("Cannot write file %1. Disk full?")
|
|
|
|
|
.arg(m_filePath.toUserOutput());
|
2017-06-29 15:16:26 +02:00
|
|
|
}
|
2011-03-30 12:06:05 +02:00
|
|
|
m_hasError = true;
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::setResult(QTextStream *stream)
|
|
|
|
|
{
|
|
|
|
|
stream->flush();
|
|
|
|
|
return setResult(stream->status() == QTextStream::Ok);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::setResult(QDataStream *stream)
|
|
|
|
|
{
|
2011-04-18 15:40:10 +02:00
|
|
|
return setResult(stream->status() == QDataStream::Ok);
|
2011-03-30 12:06:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaverBase::setResult(QXmlStreamWriter *stream)
|
|
|
|
|
{
|
|
|
|
|
return setResult(!stream->hasError());
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
// FileSaver
|
2011-03-30 12:06:05 +02:00
|
|
|
|
2021-05-18 09:47:07 +02:00
|
|
|
FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode)
|
2011-03-30 12:06:05 +02:00
|
|
|
{
|
2021-05-18 09:47:07 +02:00
|
|
|
m_filePath = filePath;
|
2016-10-07 10:34:40 +02:00
|
|
|
// Workaround an assert in Qt -- and provide a useful error message, too:
|
2021-07-23 16:30:40 +02:00
|
|
|
if (m_filePath.osType() == OsType::OsTypeWindows) {
|
2016-10-07 10:34:40 +02:00
|
|
|
// Taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
|
|
|
|
static const QStringList reservedNames
|
2017-02-22 15:09:35 +01:00
|
|
|
= {"CON", "PRN", "AUX", "NUL",
|
|
|
|
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
|
|
|
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
|
2021-06-03 12:53:40 +02:00
|
|
|
const QString fn = filePath.baseName().toUpper();
|
2021-05-06 16:42:02 +02:00
|
|
|
if (reservedNames.contains(fn)) {
|
2023-01-24 16:49:41 +01:00
|
|
|
m_errorString = Tr::tr("%1: Is a reserved filename on Windows. Cannot save.")
|
2021-07-23 16:30:40 +02:00
|
|
|
.arg(filePath.toUserOutput());
|
2021-05-06 16:42:02 +02:00
|
|
|
m_hasError = true;
|
|
|
|
|
return;
|
2016-10-07 10:34:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-07-23 16:30:40 +02:00
|
|
|
if (filePath.needsDevice()) {
|
|
|
|
|
// Write to a local temporary file first. Actual saving to the selected location
|
|
|
|
|
// is done via m_filePath.writeFileContents() in finalize()
|
|
|
|
|
m_isSafe = false;
|
|
|
|
|
auto tf = new QTemporaryFile(QDir::tempPath() + "/remotefilesaver-XXXXXX");
|
|
|
|
|
tf->setAutoRemove(false);
|
|
|
|
|
m_file.reset(tf);
|
|
|
|
|
} else if (mode & (QIODevice::ReadOnly | QIODevice::Append)) {
|
|
|
|
|
m_file.reset(new QFile{filePath.path()});
|
2011-03-30 12:06:05 +02:00
|
|
|
m_isSafe = false;
|
|
|
|
|
} else {
|
2023-01-13 09:53:45 +01:00
|
|
|
m_file.reset(new SaveFile(filePath));
|
2011-03-30 12:06:05 +02:00
|
|
|
m_isSafe = true;
|
|
|
|
|
}
|
|
|
|
|
if (!m_file->open(QIODevice::WriteOnly | mode)) {
|
2021-05-18 09:47:07 +02:00
|
|
|
QString err = filePath.exists() ?
|
2023-01-24 16:49:41 +01:00
|
|
|
Tr::tr("Cannot overwrite file %1: %2") : Tr::tr("Cannot create file %1: %2");
|
2021-05-18 09:47:07 +02:00
|
|
|
m_errorString = err.arg(filePath.toUserOutput(), m_file->errorString());
|
2011-03-30 12:06:05 +02:00
|
|
|
m_hasError = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileSaver::finalize()
|
|
|
|
|
{
|
2021-07-23 16:30:40 +02:00
|
|
|
if (m_filePath.needsDevice()) {
|
|
|
|
|
m_file->close();
|
|
|
|
|
m_file->open(QIODevice::ReadOnly);
|
|
|
|
|
const QByteArray data = m_file->readAll();
|
2022-11-24 08:52:47 +01:00
|
|
|
const expected_str<qint64> res = m_filePath.writeFileContents(data);
|
2021-07-23 16:30:40 +02:00
|
|
|
m_file->remove();
|
|
|
|
|
m_file.reset();
|
2022-11-24 08:52:47 +01:00
|
|
|
return res.has_value();
|
2021-07-23 16:30:40 +02:00
|
|
|
}
|
|
|
|
|
|
2011-03-30 12:06:05 +02:00
|
|
|
if (!m_isSafe)
|
|
|
|
|
return FileSaverBase::finalize();
|
|
|
|
|
|
2018-07-19 16:39:41 +02:00
|
|
|
auto sf = static_cast<SaveFile *>(m_file.get());
|
2013-06-04 18:00:42 +02:00
|
|
|
if (m_hasError) {
|
|
|
|
|
if (sf->isOpen())
|
|
|
|
|
sf->rollback();
|
|
|
|
|
} else {
|
2011-03-30 12:06:05 +02:00
|
|
|
setResult(sf->commit());
|
2013-06-04 18:00:42 +02:00
|
|
|
}
|
2017-06-29 15:04:01 +02:00
|
|
|
m_file.reset();
|
2011-03-30 12:06:05 +02:00
|
|
|
return !m_hasError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TempFileSaver::TempFileSaver(const QString &templ)
|
|
|
|
|
{
|
2017-06-29 15:04:01 +02:00
|
|
|
m_file.reset(new QTemporaryFile{});
|
2018-07-19 16:39:41 +02:00
|
|
|
auto tempFile = static_cast<QTemporaryFile *>(m_file.get());
|
2011-03-30 12:06:05 +02:00
|
|
|
if (!templ.isEmpty())
|
|
|
|
|
tempFile->setFileTemplate(templ);
|
|
|
|
|
tempFile->setAutoRemove(false);
|
|
|
|
|
if (!tempFile->open()) {
|
2023-01-24 16:49:41 +01:00
|
|
|
m_errorString = Tr::tr("Cannot create temporary file in %1: %2").arg(
|
2011-03-30 12:06:05 +02:00
|
|
|
QDir::toNativeSeparators(QFileInfo(tempFile->fileTemplate()).absolutePath()),
|
|
|
|
|
tempFile->errorString());
|
|
|
|
|
m_hasError = true;
|
|
|
|
|
}
|
2021-05-18 09:47:07 +02:00
|
|
|
m_filePath = FilePath::fromString(tempFile->fileName());
|
2011-03-30 12:06:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TempFileSaver::~TempFileSaver()
|
|
|
|
|
{
|
2017-06-29 15:04:01 +02:00
|
|
|
m_file.reset();
|
2011-03-30 12:06:05 +02:00
|
|
|
if (m_autoRemove)
|
2021-05-18 09:47:07 +02:00
|
|
|
QFile::remove(m_filePath.toString());
|
2011-03-30 12:06:05 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-26 13:32:07 +02:00
|
|
|
/*! \class Utils::FileUtils
|
|
|
|
|
|
|
|
|
|
\brief The FileUtils class contains file and directory related convenience
|
|
|
|
|
functions.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2021-08-09 08:53:40 +02:00
|
|
|
FileUtils::CopyAskingForOverwrite::CopyAskingForOverwrite(QWidget *dialogParent, const std::function<void (FilePath)> &postOperation)
|
2021-07-16 11:16:45 +02:00
|
|
|
: m_parent(dialogParent)
|
|
|
|
|
, m_postOperation(postOperation)
|
|
|
|
|
{}
|
2021-06-04 07:59:00 +02:00
|
|
|
|
2021-08-09 08:53:40 +02:00
|
|
|
bool FileUtils::CopyAskingForOverwrite::operator()(const FilePath &src,
|
|
|
|
|
const FilePath &dest,
|
2021-07-16 11:16:45 +02:00
|
|
|
QString *error)
|
2020-06-11 15:41:26 +02:00
|
|
|
{
|
|
|
|
|
bool copyFile = true;
|
|
|
|
|
if (dest.exists()) {
|
|
|
|
|
if (m_skipAll)
|
|
|
|
|
copyFile = false;
|
|
|
|
|
else if (!m_overwriteAll) {
|
|
|
|
|
const int res = QMessageBox::question(
|
|
|
|
|
m_parent,
|
2023-01-24 16:49:41 +01:00
|
|
|
Tr::tr("Overwrite File?"),
|
|
|
|
|
Tr::tr("Overwrite existing file \"%1\"?").arg(dest.toUserOutput()),
|
2020-06-11 15:41:26 +02:00
|
|
|
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll
|
|
|
|
|
| QMessageBox::Cancel);
|
|
|
|
|
if (res == QMessageBox::Cancel) {
|
|
|
|
|
return false;
|
|
|
|
|
} else if (res == QMessageBox::No) {
|
|
|
|
|
copyFile = false;
|
|
|
|
|
} else if (res == QMessageBox::NoToAll) {
|
|
|
|
|
m_skipAll = true;
|
|
|
|
|
copyFile = false;
|
|
|
|
|
} else if (res == QMessageBox::YesToAll) {
|
|
|
|
|
m_overwriteAll = true;
|
|
|
|
|
}
|
|
|
|
|
if (copyFile)
|
2021-08-09 08:53:40 +02:00
|
|
|
dest.removeFile();
|
2020-06-11 15:41:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (copyFile) {
|
2021-08-09 08:53:40 +02:00
|
|
|
dest.parentDir().ensureWritableDir();
|
|
|
|
|
if (!src.copyFile(dest)) {
|
2020-06-11 15:41:26 +02:00
|
|
|
if (error) {
|
2023-01-24 16:49:41 +01:00
|
|
|
*error = Tr::tr("Could not copy file \"%1\" to \"%2\".")
|
2021-08-09 08:53:40 +02:00
|
|
|
.arg(src.toUserOutput(), dest.toUserOutput());
|
2020-06-11 15:41:26 +02:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-02-12 16:06:24 +01:00
|
|
|
if (m_postOperation)
|
|
|
|
|
m_postOperation(dest);
|
2020-06-11 15:41:26 +02:00
|
|
|
}
|
|
|
|
|
m_files.append(dest.absoluteFilePath());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 16:49:42 +02:00
|
|
|
FilePaths FileUtils::CopyAskingForOverwrite::files() const
|
2020-06-11 15:41:26 +02:00
|
|
|
{
|
2021-08-09 08:53:40 +02:00
|
|
|
return m_files;
|
2020-06-11 15:41:26 +02:00
|
|
|
}
|
|
|
|
|
#endif // QT_GUI_LIB
|
|
|
|
|
|
2022-07-21 18:23:27 +02:00
|
|
|
FilePath FileUtils::commonPath(const FilePaths &paths)
|
|
|
|
|
{
|
|
|
|
|
if (paths.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
if (paths.count() == 1)
|
|
|
|
|
return paths.constFirst();
|
|
|
|
|
|
|
|
|
|
const FilePath &first = paths.constFirst();
|
|
|
|
|
const FilePaths others = paths.mid(1);
|
|
|
|
|
FilePath result;
|
|
|
|
|
|
|
|
|
|
// Common scheme
|
2022-08-04 13:35:42 +02:00
|
|
|
const QStringView commonScheme = first.scheme();
|
2022-07-21 18:23:27 +02:00
|
|
|
auto sameScheme = [&commonScheme] (const FilePath &fp) {
|
|
|
|
|
return commonScheme == fp.scheme();
|
|
|
|
|
};
|
|
|
|
|
if (!allOf(others, sameScheme))
|
|
|
|
|
return result;
|
2022-08-04 15:00:24 +02:00
|
|
|
result.setParts(commonScheme, {}, {});
|
2022-07-21 18:23:27 +02:00
|
|
|
|
|
|
|
|
// Common host
|
2022-08-04 13:35:42 +02:00
|
|
|
const QStringView commonHost = first.host();
|
2022-07-21 18:23:27 +02:00
|
|
|
auto sameHost = [&commonHost] (const FilePath &fp) {
|
|
|
|
|
return commonHost == fp.host();
|
|
|
|
|
};
|
|
|
|
|
if (!allOf(others, sameHost))
|
|
|
|
|
return result;
|
2022-08-04 15:00:24 +02:00
|
|
|
result.setParts(commonScheme, commonHost, {});
|
2022-07-21 18:23:27 +02:00
|
|
|
|
|
|
|
|
// Common path
|
|
|
|
|
QString commonPath;
|
|
|
|
|
auto sameBasePath = [&commonPath] (const FilePath &fp) {
|
|
|
|
|
return QString(fp.path() + '/').startsWith(commonPath);
|
|
|
|
|
};
|
|
|
|
|
const QStringList pathSegments = first.path().split('/');
|
|
|
|
|
for (const QString &segment : pathSegments) {
|
|
|
|
|
commonPath += segment + '/';
|
|
|
|
|
if (!allOf(others, sameBasePath))
|
|
|
|
|
return result;
|
2022-08-04 15:00:24 +02:00
|
|
|
result.setParts(commonScheme, commonHost, commonPath.chopped(1));
|
2022-07-21 18:23:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-19 15:37:39 +01:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
template <>
|
2018-06-05 14:22:26 +02:00
|
|
|
void withNtfsPermissions(const std::function<void()> &task)
|
2018-02-19 15:37:39 +01:00
|
|
|
{
|
|
|
|
|
qt_ntfs_permission_lookup++;
|
|
|
|
|
task();
|
|
|
|
|
qt_ntfs_permission_lookup--;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-04-27 12:05:13 +02:00
|
|
|
|
2019-12-17 14:18:14 +01:00
|
|
|
|
2021-07-16 15:24:26 +02:00
|
|
|
#ifdef QT_WIDGETS_LIB
|
|
|
|
|
|
|
|
|
|
static std::function<QWidget *()> s_dialogParentGetter;
|
|
|
|
|
|
|
|
|
|
void FileUtils::setDialogParentGetter(const std::function<QWidget *()> &getter)
|
|
|
|
|
{
|
|
|
|
|
s_dialogParentGetter = getter;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 09:01:53 +02:00
|
|
|
static QWidget *dialogParent(QWidget *parent)
|
2021-07-16 15:24:26 +02:00
|
|
|
{
|
2021-08-11 09:01:53 +02:00
|
|
|
return parent ? parent : s_dialogParentGetter ? s_dialogParentGetter() : nullptr;
|
2021-07-16 15:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-12-13 08:45:37 +01:00
|
|
|
static FilePath qUrlToFilePath(const QUrl &url)
|
2022-10-12 11:59:51 +02:00
|
|
|
{
|
|
|
|
|
if (url.isLocalFile())
|
|
|
|
|
return FilePath::fromString(url.toLocalFile());
|
2022-12-13 08:45:37 +01:00
|
|
|
return FilePath::fromParts(url.scheme(), url.host(), url.path());
|
2022-10-12 11:59:51 +02:00
|
|
|
}
|
|
|
|
|
|
2022-12-13 08:45:37 +01:00
|
|
|
static QUrl filePathToQUrl(const FilePath &filePath)
|
2022-10-12 11:59:51 +02:00
|
|
|
{
|
2023-01-19 08:05:43 +01:00
|
|
|
return QUrl::fromLocalFile(filePath.toFSPathString());
|
2022-10-12 11:59:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void prepareNonNativeDialog(QFileDialog &dialog)
|
|
|
|
|
{
|
2023-01-19 08:05:43 +01:00
|
|
|
const auto isValidSideBarPath = [](const FilePath &fp) {
|
|
|
|
|
return !fp.needsDevice() || fp.hasFileAccess();
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-12 11:59:51 +02:00
|
|
|
// Checking QFileDialog::itemDelegate() seems to be the only way to determine
|
|
|
|
|
// whether the dialog is native or not.
|
|
|
|
|
if (dialog.itemDelegate()) {
|
2022-10-12 14:52:15 +02:00
|
|
|
FilePaths sideBarPaths;
|
|
|
|
|
|
2023-01-19 08:05:43 +01:00
|
|
|
// Check existing urls, remove paths that need a device and are no longer valid.
|
2022-10-12 14:52:15 +02:00
|
|
|
for (const QUrl &url : dialog.sidebarUrls()) {
|
|
|
|
|
FilePath path = qUrlToFilePath(url);
|
2023-01-19 08:05:43 +01:00
|
|
|
if (isValidSideBarPath(path))
|
2022-10-12 14:52:15 +02:00
|
|
|
sideBarPaths.append(path);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 08:05:43 +01:00
|
|
|
// Add all device roots that are not already in the sidebar and valid.
|
2022-10-12 11:59:51 +02:00
|
|
|
for (const FilePath &path : FSEngine::registeredDeviceRoots()) {
|
2023-01-19 08:05:43 +01:00
|
|
|
if (!sideBarPaths.contains(path) && isValidSideBarPath(path))
|
2022-10-12 14:52:15 +02:00
|
|
|
sideBarPaths.append(path);
|
2022-10-12 11:59:51 +02:00
|
|
|
}
|
2022-10-12 14:52:15 +02:00
|
|
|
|
|
|
|
|
dialog.setSidebarUrls(Utils::transform(sideBarPaths, filePathToQUrl));
|
2022-10-12 11:59:51 +02:00
|
|
|
dialog.setIconProvider(Utils::FileIconProvider::iconProvider());
|
2023-01-12 09:37:25 +01:00
|
|
|
dialog.setFilter(QDir::Hidden | dialog.filter());
|
2022-10-12 11:59:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePaths getFilePaths(QWidget *parent,
|
|
|
|
|
const QString &caption,
|
|
|
|
|
const FilePath &dir,
|
|
|
|
|
const QString &filter,
|
|
|
|
|
QString *selectedFilter,
|
|
|
|
|
QFileDialog::Options options,
|
|
|
|
|
const QStringList &supportedSchemes,
|
|
|
|
|
const bool forceNonNativeDialog,
|
|
|
|
|
QFileDialog::FileMode fileMode,
|
|
|
|
|
QFileDialog::AcceptMode acceptMode)
|
|
|
|
|
{
|
|
|
|
|
QFileDialog dialog(parent, caption, dir.toFSPathString(), filter);
|
|
|
|
|
dialog.setFileMode(fileMode);
|
|
|
|
|
|
|
|
|
|
if (forceNonNativeDialog)
|
|
|
|
|
options.setFlag(QFileDialog::DontUseNativeDialog);
|
|
|
|
|
|
|
|
|
|
dialog.setOptions(options);
|
|
|
|
|
prepareNonNativeDialog(dialog);
|
|
|
|
|
|
|
|
|
|
dialog.setSupportedSchemes(supportedSchemes);
|
|
|
|
|
dialog.setAcceptMode(acceptMode);
|
|
|
|
|
|
|
|
|
|
if (selectedFilter && !selectedFilter->isEmpty())
|
|
|
|
|
dialog.selectNameFilter(*selectedFilter);
|
|
|
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
|
|
|
if (selectedFilter)
|
|
|
|
|
*selectedFilter = dialog.selectedNameFilter();
|
|
|
|
|
return Utils::transform(dialog.selectedUrls(), &qUrlToFilePath);
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath firstOrEmpty(const FilePaths &filePaths)
|
|
|
|
|
{
|
|
|
|
|
return filePaths.isEmpty() ? FilePath() : filePaths.first();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 11:33:24 +01:00
|
|
|
bool FileUtils::hasNativeFileDialog()
|
|
|
|
|
{
|
|
|
|
|
static std::optional<bool> hasNative;
|
|
|
|
|
if (!hasNative.has_value()) {
|
|
|
|
|
// Checking QFileDialog::itemDelegate() seems to be the only way to determine
|
|
|
|
|
// whether the dialog is native or not.
|
|
|
|
|
QFileDialog dialog;
|
|
|
|
|
hasNative = dialog.itemDelegate() == nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return *hasNative;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 09:01:53 +02:00
|
|
|
FilePath FileUtils::getOpenFilePath(QWidget *parent,
|
|
|
|
|
const QString &caption,
|
2021-07-16 15:24:26 +02:00
|
|
|
const FilePath &dir,
|
|
|
|
|
const QString &filter,
|
|
|
|
|
QString *selectedFilter,
|
2022-05-31 11:16:44 +02:00
|
|
|
QFileDialog::Options options,
|
2022-11-22 11:33:24 +01:00
|
|
|
bool fromDeviceIfShiftIsPressed,
|
|
|
|
|
bool forceNonNativeDialog)
|
2021-07-16 15:24:26 +02:00
|
|
|
{
|
2022-11-22 11:33:24 +01:00
|
|
|
forceNonNativeDialog = forceNonNativeDialog || dir.needsDevice();
|
2022-05-31 11:16:44 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (fromDeviceIfShiftIsPressed && qApp->queryKeyboardModifiers() & Qt::ShiftModifier) {
|
2022-10-12 11:59:51 +02:00
|
|
|
forceNonNativeDialog = true;
|
2022-05-31 11:16:44 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-10-12 11:59:51 +02:00
|
|
|
const QStringList schemes = QStringList(QStringLiteral("file"));
|
|
|
|
|
return firstOrEmpty(getFilePaths(dialogParent(parent),
|
|
|
|
|
caption,
|
|
|
|
|
dir,
|
|
|
|
|
filter,
|
|
|
|
|
selectedFilter,
|
|
|
|
|
options,
|
|
|
|
|
schemes,
|
|
|
|
|
forceNonNativeDialog,
|
|
|
|
|
QFileDialog::ExistingFile,
|
|
|
|
|
QFileDialog::AcceptOpen));
|
2021-07-16 15:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-11 09:01:53 +02:00
|
|
|
FilePath FileUtils::getSaveFilePath(QWidget *parent,
|
|
|
|
|
const QString &caption,
|
|
|
|
|
const FilePath &dir,
|
|
|
|
|
const QString &filter,
|
|
|
|
|
QString *selectedFilter,
|
2022-11-22 11:33:24 +01:00
|
|
|
QFileDialog::Options options,
|
|
|
|
|
bool forceNonNativeDialog)
|
2021-07-16 15:24:26 +02:00
|
|
|
{
|
2022-11-22 11:33:24 +01:00
|
|
|
forceNonNativeDialog = forceNonNativeDialog || dir.needsDevice();
|
2022-10-12 11:59:51 +02:00
|
|
|
|
|
|
|
|
const QStringList schemes = QStringList(QStringLiteral("file"));
|
|
|
|
|
return firstOrEmpty(getFilePaths(dialogParent(parent),
|
|
|
|
|
caption,
|
|
|
|
|
dir,
|
|
|
|
|
filter,
|
|
|
|
|
selectedFilter,
|
|
|
|
|
options,
|
|
|
|
|
schemes,
|
|
|
|
|
forceNonNativeDialog,
|
|
|
|
|
QFileDialog::AnyFile,
|
|
|
|
|
QFileDialog::AcceptSave));
|
2021-07-16 15:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-11 09:01:53 +02:00
|
|
|
FilePath FileUtils::getExistingDirectory(QWidget *parent,
|
|
|
|
|
const QString &caption,
|
|
|
|
|
const FilePath &dir,
|
2022-10-14 09:44:46 +02:00
|
|
|
QFileDialog::Options options,
|
2022-11-22 11:33:24 +01:00
|
|
|
bool fromDeviceIfShiftIsPressed,
|
|
|
|
|
bool forceNonNativeDialog)
|
2021-07-16 15:24:26 +02:00
|
|
|
{
|
2022-11-22 11:33:24 +01:00
|
|
|
forceNonNativeDialog = forceNonNativeDialog || dir.needsDevice();
|
2022-10-12 11:59:51 +02:00
|
|
|
|
2022-10-14 09:44:46 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (fromDeviceIfShiftIsPressed && qApp->queryKeyboardModifiers() & Qt::ShiftModifier) {
|
|
|
|
|
forceNonNativeDialog = true;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-10-12 11:59:51 +02:00
|
|
|
const QStringList schemes = QStringList(QStringLiteral("file"));
|
|
|
|
|
return firstOrEmpty(getFilePaths(dialogParent(parent),
|
|
|
|
|
caption,
|
|
|
|
|
dir,
|
|
|
|
|
{},
|
|
|
|
|
nullptr,
|
|
|
|
|
options,
|
|
|
|
|
schemes,
|
|
|
|
|
forceNonNativeDialog,
|
|
|
|
|
QFileDialog::Directory,
|
|
|
|
|
QFileDialog::AcceptOpen));
|
2021-07-16 15:24:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-11 09:01:53 +02:00
|
|
|
FilePaths FileUtils::getOpenFilePaths(QWidget *parent,
|
|
|
|
|
const QString &caption,
|
2021-07-16 15:24:26 +02:00
|
|
|
const FilePath &dir,
|
|
|
|
|
const QString &filter,
|
|
|
|
|
QString *selectedFilter,
|
|
|
|
|
QFileDialog::Options options)
|
|
|
|
|
{
|
2022-10-12 11:59:51 +02:00
|
|
|
bool forceNonNativeDialog = dir.needsDevice();
|
2022-05-31 11:16:44 +02:00
|
|
|
|
2022-10-12 11:59:51 +02:00
|
|
|
const QStringList schemes = QStringList(QStringLiteral("file"));
|
|
|
|
|
return getFilePaths(dialogParent(parent),
|
|
|
|
|
caption,
|
|
|
|
|
dir,
|
|
|
|
|
filter,
|
|
|
|
|
selectedFilter,
|
|
|
|
|
options,
|
|
|
|
|
schemes,
|
|
|
|
|
forceNonNativeDialog,
|
|
|
|
|
QFileDialog::ExistingFiles,
|
|
|
|
|
QFileDialog::AcceptOpen);
|
2022-05-31 11:16:44 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-05 17:50:40 +02:00
|
|
|
#endif // QT_WIDGETS_LIB
|
|
|
|
|
|
2023-02-23 15:34:05 +01:00
|
|
|
FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase)
|
2022-10-05 15:42:14 +02:00
|
|
|
{
|
2023-02-14 08:09:28 +01:00
|
|
|
// Copied from stat.h
|
|
|
|
|
enum st_mode {
|
|
|
|
|
IFMT = 00170000,
|
|
|
|
|
IFSOCK = 0140000,
|
|
|
|
|
IFLNK = 0120000,
|
|
|
|
|
IFREG = 0100000,
|
|
|
|
|
IFBLK = 0060000,
|
|
|
|
|
IFDIR = 0040000,
|
|
|
|
|
IFCHR = 0020000,
|
|
|
|
|
IFIFO = 0010000,
|
|
|
|
|
ISUID = 0004000,
|
|
|
|
|
ISGID = 0002000,
|
|
|
|
|
ISVTX = 0001000,
|
|
|
|
|
IRWXU = 00700,
|
|
|
|
|
IRUSR = 00400,
|
|
|
|
|
IWUSR = 00200,
|
|
|
|
|
IXUSR = 00100,
|
|
|
|
|
IRWXG = 00070,
|
|
|
|
|
IRGRP = 00040,
|
|
|
|
|
IWGRP = 00020,
|
|
|
|
|
IXGRP = 00010,
|
|
|
|
|
IRWXO = 00007,
|
|
|
|
|
IROTH = 00004,
|
|
|
|
|
IWOTH = 00002,
|
|
|
|
|
IXOTH = 00001,
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
bool ok = false;
|
2023-02-23 15:34:05 +01:00
|
|
|
uint mode = hexString.toUInt(&ok, modeBase);
|
2022-10-05 15:42:14 +02:00
|
|
|
|
|
|
|
|
QTC_ASSERT(ok, return {});
|
|
|
|
|
|
|
|
|
|
FilePathInfo::FileFlags result;
|
|
|
|
|
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IRUSR)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ReadOwnerPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IWUSR)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::WriteOwnerPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IXUSR)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ExeOwnerPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IRGRP)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ReadGroupPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IWGRP)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::WriteGroupPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IXGRP)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ExeGroupPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IROTH)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ReadOtherPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IWOTH)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::WriteOtherPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
if (mode & IXOTH)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::ExeOtherPerm;
|
2023-02-14 08:09:28 +01:00
|
|
|
|
|
|
|
|
if ((mode & IFMT) == IFLNK)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::LinkType;
|
2023-02-14 08:09:28 +01:00
|
|
|
if ((mode & IFMT) == IFREG)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::FileType;
|
2023-02-14 08:09:28 +01:00
|
|
|
if ((mode & IFMT) == IFDIR)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::DirectoryType;
|
2023-02-14 08:09:28 +01:00
|
|
|
if ((mode & IFMT) == IFBLK)
|
2022-10-05 15:42:14 +02:00
|
|
|
result |= FilePathInfo::LocalDiskFlag;
|
|
|
|
|
|
|
|
|
|
if (result != 0) // There is no Exist flag, but if anything was set before, it must exist.
|
|
|
|
|
result |= FilePathInfo::ExistsFlag;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-23 15:34:05 +01:00
|
|
|
FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase)
|
2022-10-05 15:42:14 +02:00
|
|
|
{
|
|
|
|
|
const QStringList parts = infos.split(' ', Qt::SkipEmptyParts);
|
|
|
|
|
if (parts.size() != 3)
|
|
|
|
|
return {};
|
|
|
|
|
|
2023-02-23 15:34:05 +01:00
|
|
|
FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase);
|
2022-10-05 15:42:14 +02:00
|
|
|
|
|
|
|
|
const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC);
|
|
|
|
|
qint64 size = parts[2].toLongLong();
|
|
|
|
|
return {size, flags, dt};
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-20 11:30:21 +01:00
|
|
|
bool FileUtils::copyRecursively(
|
|
|
|
|
const FilePath &srcFilePath,
|
|
|
|
|
const FilePath &tgtFilePath,
|
|
|
|
|
QString *error,
|
|
|
|
|
std::function<bool(const FilePath &, const FilePath &, QString *)> copyHelper)
|
|
|
|
|
{
|
|
|
|
|
if (srcFilePath.isDir()) {
|
|
|
|
|
if (!tgtFilePath.ensureWritableDir()) {
|
|
|
|
|
if (error) {
|
2023-01-24 16:49:41 +01:00
|
|
|
*error = Tr::tr("Failed to create directory \"%1\".")
|
2023-01-20 11:30:21 +01:00
|
|
|
.arg(tgtFilePath.toUserOutput());
|
2022-07-26 13:32:07 +02:00
|
|
|
}
|
2023-01-20 11:30:21 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const QDir sourceDir(srcFilePath.toString());
|
|
|
|
|
const QStringList fileNames = sourceDir.entryList(
|
|
|
|
|
QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
|
|
|
|
|
for (const QString &fileName : fileNames) {
|
|
|
|
|
const FilePath newSrcFilePath = srcFilePath / fileName;
|
|
|
|
|
const FilePath newTgtFilePath = tgtFilePath / fileName;
|
|
|
|
|
if (!copyRecursively(newSrcFilePath, newTgtFilePath, error, copyHelper))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!copyHelper(srcFilePath, tgtFilePath, error))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2022-07-26 13:32:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different
|
|
|
|
|
(file contents and last modification time).
|
|
|
|
|
|
|
|
|
|
Returns whether the operation succeeded.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(srcFilePath.exists(), return false);
|
|
|
|
|
QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false);
|
|
|
|
|
QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false);
|
|
|
|
|
|
|
|
|
|
if (tgtFilePath.exists()) {
|
|
|
|
|
const QDateTime srcModified = srcFilePath.lastModified();
|
|
|
|
|
const QDateTime tgtModified = tgtFilePath.lastModified();
|
|
|
|
|
if (srcModified == tgtModified) {
|
2022-11-24 08:52:47 +01:00
|
|
|
// TODO: Create FilePath::hashFromContents() and compare hashes.
|
|
|
|
|
const expected_str<QByteArray> srcContents = srcFilePath.fileContents();
|
|
|
|
|
const expected_str<QByteArray> tgtContents = srcFilePath.fileContents();
|
|
|
|
|
if (srcContents && srcContents == tgtContents)
|
2022-07-26 13:32:07 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
tgtFilePath.removeFile();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-24 08:52:47 +01:00
|
|
|
const expected_str<void> copyResult = srcFilePath.copyFile(tgtFilePath);
|
|
|
|
|
|
|
|
|
|
// TODO forward error to caller instead of assert, since IO errors can always be expected
|
|
|
|
|
QTC_ASSERT_EXPECTED(copyResult, return false);
|
|
|
|
|
return true;
|
2022-07-26 13:32:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FileUtils::fileSystemFriendlyName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
QString result = name;
|
|
|
|
|
result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_"));
|
|
|
|
|
result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _
|
|
|
|
|
result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _
|
|
|
|
|
result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _
|
|
|
|
|
if (result.isEmpty())
|
|
|
|
|
result = QLatin1String("unknown");
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos)
|
|
|
|
|
{
|
|
|
|
|
static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]"));
|
|
|
|
|
return checkRegExp.match(name, startpos).capturedStart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FileUtils::qmakeFriendlyName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
QString result = name;
|
|
|
|
|
|
|
|
|
|
// Remove characters that might trip up a build system (especially qmake):
|
|
|
|
|
int pos = indexOfQmakeUnfriendly(result);
|
|
|
|
|
while (pos >= 0) {
|
|
|
|
|
result[pos] = QLatin1Char('_');
|
|
|
|
|
pos = indexOfQmakeUnfriendly(result, pos);
|
|
|
|
|
}
|
|
|
|
|
return fileSystemFriendlyName(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FileUtils::makeWritable(const FilePath &path)
|
|
|
|
|
{
|
|
|
|
|
return path.setPermissions(path.permissions() | QFile::WriteUser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// makes sure that capitalization of directories is canonical on Windows and macOS.
|
|
|
|
|
// This mimics the logic in QDeclarative_isFileCaseCorrect
|
|
|
|
|
QString FileUtils::normalizedPathName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
const QString nativeSeparatorName(QDir::toNativeSeparators(name));
|
|
|
|
|
const auto nameC = reinterpret_cast<LPCTSTR>(nativeSeparatorName.utf16()); // MinGW
|
|
|
|
|
PIDLIST_ABSOLUTE file;
|
|
|
|
|
HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL);
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
|
return name;
|
|
|
|
|
TCHAR buffer[MAX_PATH];
|
|
|
|
|
const bool success = SHGetPathFromIDList(file, buffer);
|
|
|
|
|
ILFree(file);
|
|
|
|
|
return success ? QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast<const ushort *>(buffer)))
|
|
|
|
|
: name;
|
|
|
|
|
#elif defined(Q_OS_MACOS)
|
|
|
|
|
return Internal::normalizePathName(name);
|
|
|
|
|
#else // do not try to handle case-insensitive file systems on Linux
|
|
|
|
|
return name;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath)
|
|
|
|
|
{
|
|
|
|
|
FilePath newCommonPath = oldCommonPath;
|
|
|
|
|
while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath))
|
|
|
|
|
newCommonPath = newCommonPath.parentDir();
|
|
|
|
|
return newCommonPath.canonicalPath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FileUtils::homePath()
|
|
|
|
|
{
|
2023-02-09 13:58:01 +01:00
|
|
|
return FilePath::fromUserInput(QDir::homePath());
|
2022-07-26 13:32:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-04-04 16:03:53 +02:00
|
|
|
FilePaths FileUtils::toFilePathList(const QStringList &paths)
|
|
|
|
|
{
|
|
|
|
|
return transform(paths, &FilePath::fromString);
|
2022-08-02 09:27:21 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-14 14:19:59 +02:00
|
|
|
qint64 FileUtils::bytesAvailableFromDFOutput(const QByteArray &dfOutput)
|
|
|
|
|
{
|
|
|
|
|
const auto lines = filtered(dfOutput.split('\n'),
|
|
|
|
|
[](const QByteArray &line) { return line.size() > 0; });
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(lines.size() == 2, return -1);
|
|
|
|
|
const auto headers = filtered(lines[0].split(' '),
|
|
|
|
|
[](const QByteArray &field) { return field.size() > 0; });
|
|
|
|
|
QTC_ASSERT(headers.size() >= 4, return -1);
|
|
|
|
|
QTC_ASSERT(headers[3] == QByteArray("Available"), return -1);
|
|
|
|
|
|
|
|
|
|
const auto fields = filtered(lines[1].split(' '),
|
|
|
|
|
[](const QByteArray &field) { return field.size() > 0; });
|
|
|
|
|
QTC_ASSERT(fields.size() >= 4, return -1);
|
|
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const quint64 result = QString::fromUtf8(fields[3]).toULongLong(&ok);
|
|
|
|
|
if (ok)
|
|
|
|
|
return result;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 15:24:26 +02:00
|
|
|
} // namespace Utils
|