Utils: Add FilePath::tmpDir and createTempFile

Change-Id: I6f3143e59a87edffeee5e08708ba721293a8a369
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-02-07 07:57:17 +01:00
parent 02777c4179
commit 516fce6f53
5 changed files with 162 additions and 0 deletions

View File

@@ -19,6 +19,8 @@
#include <QOperatingSystemVersion> #include <QOperatingSystemVersion>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStorageInfo> #include <QStorageInfo>
#include <QTemporaryFile>
#include <QRandomGenerator>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#ifdef QTCREATOR_PCH_H #ifdef QTCREATOR_PCH_H
@@ -30,6 +32,8 @@
#include <qplatformdefs.h> #include <qplatformdefs.h>
#endif #endif
#include <algorithm>
namespace Utils { namespace Utils {
// DeviceFileAccess // DeviceFileAccess
@@ -397,6 +401,15 @@ void DeviceFileAccess::asyncCopyFile(const FilePath &filePath,
cont(copyFile(filePath, target)); cont(copyFile(filePath, target));
} }
expected_str<FilePath> DeviceFileAccess::createTempFile(const FilePath &filePath)
{
Q_UNUSED(filePath)
QTC_CHECK(false);
return make_unexpected(Tr::tr("createTempFile is not implemented for \"%1\"")
.arg(filePath.toUserOutput()));
}
// DesktopDeviceFileAccess // DesktopDeviceFileAccess
DesktopDeviceFileAccess::~DesktopDeviceFileAccess() = default; DesktopDeviceFileAccess::~DesktopDeviceFileAccess() = default;
@@ -690,6 +703,16 @@ expected_str<qint64> DesktopDeviceFileAccess::writeFileContents(const FilePath &
return res; return res;
} }
expected_str<FilePath> DesktopDeviceFileAccess::createTempFile(const FilePath &filePath)
{
QTemporaryFile file(filePath.path());
file.setAutoRemove(false);
if (!file.open())
return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2)").arg(filePath.toUserOutput()).arg(file.errorString()));
return FilePath::fromString(file.fileName()).onDevice(filePath);
}
QDateTime DesktopDeviceFileAccess::lastModified(const FilePath &filePath) const QDateTime DesktopDeviceFileAccess::lastModified(const FilePath &filePath) const
{ {
return QFileInfo(filePath.path()).lastModified(); return QFileInfo(filePath.path()).lastModified();
@@ -965,6 +988,65 @@ expected_str<qint64> UnixDeviceFileAccess::writeFileContents(const FilePath &fil
return data.size(); return data.size();
} }
expected_str<FilePath> UnixDeviceFileAccess::createTempFile(const FilePath &filePath)
{
if (!m_hasMkTemp.has_value())
m_hasMkTemp = runInShellSuccess({"which", {"mktemp"}, OsType::OsTypeLinux});
QString tmplate = filePath.path();
// Some mktemp implementations require a suffix of XXXXXX.
// They will only accept the template if at least the last 6 characters are X.
if (!tmplate.endsWith("XXXXXX"))
tmplate += ".XXXXXX";
if (m_hasMkTemp) {
const RunResult result = runInShell({"mktemp", {tmplate}, OsType::OsTypeLinux});
if (result.exitCode != 0) {
return make_unexpected(
Tr::tr("Failed creating temporary file \"%1\": %2")
.arg(filePath.toUserOutput(), QString::fromUtf8(result.stdErr)));
}
return FilePath::fromString(QString::fromUtf8(result.stdOut.trimmed())).onDevice(filePath);
}
// Manually create a temporary/unique file.
std::reverse_iterator<QChar *> firstX = std::find_if_not(std::rbegin(tmplate),
std::rend(tmplate),
[](QChar ch) { return ch == 'X'; });
static constexpr std::array<QChar, 62> chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
std::uniform_int_distribution<> dist(0, chars.size() - 1);
int maxTries = 10;
FilePath newPath;
do {
for (QChar *it = firstX.base(); it != std::end(tmplate); ++it) {
*it = chars[dist(*QRandomGenerator::global())];
}
newPath = FilePath::fromString(tmplate).onDevice(filePath);
if (--maxTries == 0) {
return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries)")
.arg(filePath.toUserOutput()));
}
} while (newPath.exists());
const expected_str<qint64> createResult = newPath.writeFileContents({});
if (!createResult)
return make_unexpected(createResult.error());
return newPath;
}
OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const
{ {
Q_UNUSED(filePath) Q_UNUSED(filePath)

View File

@@ -81,6 +81,8 @@ protected:
virtual void asyncCopyFile(const FilePath &filePath, virtual void asyncCopyFile(const FilePath &filePath,
const Continuation<expected_str<void>> &cont, const Continuation<expected_str<void>> &cont,
const FilePath &target) const; const FilePath &target) const;
virtual expected_str<FilePath> createTempFile(const FilePath &filePath);
}; };
class QTCREATOR_UTILS_EXPORT DesktopDeviceFileAccess : public DeviceFileAccess class QTCREATOR_UTILS_EXPORT DesktopDeviceFileAccess : public DeviceFileAccess
@@ -135,6 +137,9 @@ protected:
expected_str<qint64> writeFileContents(const FilePath &filePath, expected_str<qint64> writeFileContents(const FilePath &filePath,
const QByteArray &data, const QByteArray &data,
qint64 offset) const override; qint64 offset) const override;
expected_str<FilePath> createTempFile(const FilePath &filePath) override;
}; };
class QTCREATOR_UTILS_EXPORT UnixDeviceFileAccess : public DeviceFileAccess class QTCREATOR_UTILS_EXPORT UnixDeviceFileAccess : public DeviceFileAccess
@@ -186,6 +191,9 @@ protected:
const QByteArray &data, const QByteArray &data,
qint64 offset) const override; qint64 offset) const override;
expected_str<FilePath> createTempFile(const FilePath &filePath) override;
private: private:
bool iterateWithFind( bool iterateWithFind(
const FilePath &filePath, const FilePath &filePath,
@@ -197,6 +205,7 @@ private:
QStringList *found) const; QStringList *found) const;
mutable bool m_tryUseFind = true; mutable bool m_tryUseFind = true;
mutable std::optional<bool> m_hasMkTemp;
}; };
} // Utils } // Utils

View File

@@ -20,6 +20,7 @@
#include <QStringView> #include <QStringView>
#include <QUrl> #include <QUrl>
#include <QtGlobal> #include <QtGlobal>
#include <QTemporaryFile>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#ifdef QTCREATOR_PCH_H #ifdef QTCREATOR_PCH_H
@@ -490,6 +491,40 @@ std::optional<FilePath> FilePath::refersToExecutableFile(MatchScope matchScope)
return fileAccess()->refersToExecutableFile(*this, matchScope); return fileAccess()->refersToExecutableFile(*this, matchScope);
} }
expected_str<FilePath> FilePath::tmpDir() const
{
if (needsDevice()) {
const Environment env = deviceEnvironment();
if (env.hasKey("TMPDIR"))
return FilePath::fromUserInput(env.value("TMPDIR")).onDevice(*this);
if (env.hasKey("TEMP"))
return FilePath::fromUserInput(env.value("TEMP")).onDevice(*this);
if (env.hasKey("TMP"))
return FilePath::fromUserInput(env.value("TMP")).onDevice(*this);
if (osType() != OsTypeWindows)
return FilePath("/tmp").onDevice(*this);
return make_unexpected(QString("Could not find temporary directory on device %1")
.arg(displayName()));
}
return FilePath::fromUserInput(QDir::tempPath());
}
expected_str<FilePath> FilePath::createTempFile() const
{
if (!needsDevice()) {
QTemporaryFile file(toFSPathString());
file.setAutoRemove(false);
if (file.open())
return FilePath::fromString(file.fileName());
return make_unexpected(QString("Could not create temporary file: %1").arg(file.errorString()));
}
return fileAccess()->createTempFile(*this);
}
bool FilePath::isReadableFile() const bool FilePath::isReadableFile() const
{ {
return fileAccess()->isReadableFile(*this); return fileAccess()->isReadableFile(*this);

View File

@@ -184,6 +184,9 @@ public:
WithExeOrBatSuffix, WithAnySuffix }; WithExeOrBatSuffix, WithAnySuffix };
std::optional<FilePath> refersToExecutableFile(MatchScope considerScript) const; std::optional<FilePath> refersToExecutableFile(MatchScope considerScript) const;
[[nodiscard]] expected_str<FilePath> tmpDir() const;
[[nodiscard]] expected_str<FilePath> createTempFile() const;
// makes sure that capitalization of directories is canonical // makes sure that capitalization of directories is canonical
// on Windows and macOS. This is rarely needed. // on Windows and macOS. This is rarely needed.
[[nodiscard]] FilePath normalizedPathName() const; [[nodiscard]] FilePath normalizedPathName() const;

View File

@@ -115,6 +115,9 @@ private slots:
void hostSpecialChars_data(); void hostSpecialChars_data();
void hostSpecialChars(); void hostSpecialChars();
void tmp_data();
void tmp();
private: private:
QTemporaryDir tempDir; QTemporaryDir tempDir;
QString rootPath; QString rootPath;
@@ -1325,6 +1328,36 @@ void tst_fileutils::hostSpecialChars()
QCOMPARE(toFromExpected, expected); QCOMPARE(toFromExpected, expected);
} }
void tst_fileutils::tmp_data()
{
QTest::addColumn<QString>("templatepath");
QTest::addColumn<bool>("expected");
QTest::addRow("empty") << "" << true;
QTest::addRow("no-template") << "foo" << true;
QTest::addRow("realtive-template") << "my-file-XXXXXXXX" << true;
QTest::addRow("absolute-template") << QDir::tempPath() + "/my-file-XXXXXXXX" << true;
QTest::addRow("non-existing-dir") << "/this/path/does/not/exist/my-file-XXXXXXXX" << false;
QTest::addRow("on-device") << "device://test/./my-file-XXXXXXXX" << true;
}
void tst_fileutils::tmp()
{
QFETCH(QString, templatepath);
QFETCH(bool, expected);
FilePath fp = FilePath::fromString(templatepath);
const auto result = fp.createTempFile();
QCOMPARE(result.has_value(), expected);
if (result.has_value()) {
QVERIFY(result->exists());
QVERIFY(result->removeFile());
}
}
QTEST_GUILESS_MAIN(tst_fileutils) QTEST_GUILESS_MAIN(tst_fileutils)
#include "tst_fileutils.moc" #include "tst_fileutils.moc"