forked from qt-creator/qt-creator
Utils: Add FilePath::tmpDir and createTempFile
Change-Id: I6f3143e59a87edffeee5e08708ba721293a8a369 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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"
|
||||||
|
Reference in New Issue
Block a user