Long live QTemporaryFileName!

So we can use it in QTemporaryFile, QTemporaryDir and QFile::rename()

[ChangeLog][QtCore][QTemporaryDir] The class now supports the "XXXXXX"
replacement token anywhere in the template, not just at the end. This
behavior is similar to what QTemporaryFile supports.

Change-Id: I1eba2b016de74620bfc8fffd14ccb645729de170
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
bb10
Thiago Macieira 2017-06-29 14:53:05 -07:00
parent 3981a1f440
commit 3d6cbe3409
4 changed files with 111 additions and 165 deletions

View File

@ -1,7 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Copyright (C) 2017 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -44,22 +44,15 @@
#include "qdiriterator.h"
#include "qplatformdefs.h"
#include "private/qtemporaryfile_p.h"
#include <QDebug>
#include <QPair>
#include <QRandomGenerator>
#if defined(QT_BUILD_CORE_LIB)
#include "qcoreapplication.h"
#endif
#if !defined(Q_OS_QNX) && !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_INTEGRITY)
# define USE_SYSTEM_MKDTEMP
#endif
#include <stdlib.h> // mkdtemp
#ifndef USE_SYSTEM_MKDTEMP
#include <private/qfilesystemengine_p.h>
#endif
#if !defined(Q_OS_WIN)
#include <errno.h>
#endif
@ -102,38 +95,12 @@ static QString defaultTemplateName()
return QDir::tempPath() + QLatin1Char('/') + baseName + QLatin1String("-XXXXXX");
}
#ifndef USE_SYSTEM_MKDTEMP
static int nextRand(int &v)
void QTemporaryDirPrivate::create(const QString &templateName)
{
int r = v % 62;
v /= 62;
if (v < 62)
v = qrand();
return r;
}
QPair<QString, bool> q_mkdtemp(QString templateName)
{
Q_ASSERT(templateName.endsWith(QLatin1String("XXXXXX")));
static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const int length = templateName.size();
QChar *XXXXXX = templateName.data() + length - 6;
QTemporaryFileName tfn(templateName);
for (int i = 0; i < 256; ++i) {
int v = qrand();
/* Fill in the random bits. */
XXXXXX[0] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[1] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[2] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[3] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[4] = QLatin1Char(letters[nextRand(v)]);
XXXXXX[5] = QLatin1Char(letters[v % 62]);
QFileSystemEntry fileSystemEntry(templateName);
tfn.generateNext();
QFileSystemEntry fileSystemEntry(tfn.path, QFileSystemEntry::FromNativePath());
if (QFileSystemEngine::createDirectory(fileSystemEntry, false)) {
QSystemError error;
QFileSystemEngine::setPermissions(fileSystemEntry,
@ -145,7 +112,9 @@ QPair<QString, bool> q_mkdtemp(QString templateName)
qWarning() << "Unable to remove unused directory" << templateName;
continue;
}
return qMakePair(templateName, true);
success = true;
pathOrError = fileSystemEntry.filePath();
return;
}
# ifdef Q_OS_WIN
const int exists = ERROR_ALREADY_EXISTS;
@ -155,36 +124,10 @@ QPair<QString, bool> q_mkdtemp(QString templateName)
int code = errno;
# endif
if (code != exists)
return qMakePair(qt_error_string(code), false);
break;
}
return qMakePair(qt_error_string(), false);
}
#else // !USE_SYSTEM_MKDTEMP
QPair<QString, bool> q_mkdtemp(char *templateName)
{
bool ok = (mkdtemp(templateName) != 0);
return qMakePair(ok ? QFile::decodeName(templateName) : qt_error_string(), ok);
}
#endif // USE_SYSTEM_MKDTEMP
void QTemporaryDirPrivate::create(const QString &templateName)
{
#ifndef USE_SYSTEM_MKDTEMP
QString buffer = templateName;
if (!buffer.endsWith(QLatin1String("XXXXXX")))
buffer += QLatin1String("XXXXXX");
const QPair<QString, bool> result = q_mkdtemp(buffer);
#else // !USE_SYSTEM_MKDTEMP
QByteArray buffer = QFile::encodeName(templateName);
if (!buffer.endsWith("XXXXXX"))
buffer += "XXXXXX";
QPair<QString, bool> result = q_mkdtemp(buffer.data()); // modifies buffer
#endif // USE_SYSTEM_MKDTEMP
pathOrError = result.first;
success = result.second;
pathOrError = qt_error_string();
success = false;
}
//************* QTemporaryDir

View File

@ -39,8 +39,6 @@
#include "qtemporaryfile.h"
#ifndef QT_NO_TEMPORARYFILE
#include "qplatformdefs.h"
#include "qrandom.h"
#include "private/qtemporaryfile_p.h"
@ -76,12 +74,63 @@ typedef char Latin1Char;
typedef int NativeFileHandle;
#endif
namespace {
struct NativeTemplate {
QFileSystemEntry::NativePath path;
qssize_t pos;
qssize_t length;
};
QTemporaryFileName::QTemporaryFileName(const QString &templateName)
{
// Ensure there is a placeholder mask
QString qfilename = templateName;
uint phPos = qfilename.length();
uint phLength = 0;
while (phPos != 0) {
--phPos;
if (qfilename[phPos] == QLatin1Char('X')) {
++phLength;
continue;
}
if (phLength >= 6
|| qfilename[phPos] == QLatin1Char('/')) {
++phPos;
break;
}
// start over
phLength = 0;
}
if (phLength < 6)
qfilename.append(QLatin1String(".XXXXXX"));
// "Nativify" :-)
QFileSystemEntry::NativePath filename = QFileSystemEngine::absoluteName(
QFileSystemEntry(qfilename, QFileSystemEntry::FromInternalPath()))
.nativeFilePath();
// Find mask in native path
phPos = filename.length();
phLength = 0;
while (phPos != 0) {
--phPos;
if (filename[phPos] == Latin1Char('X')) {
++phLength;
continue;
}
if (phLength >= 6) {
++phPos;
break;
}
// start over
phLength = 0;
}
Q_ASSERT(phLength >= 6);
path = filename;
pos = phPos;
length = phLength;
}
// The following code is a heavily modified version, originally copyright:
@ -120,12 +169,8 @@ struct NativeTemplate {
Generates a unique file path from the template \a templ and returns it.
The path in \c templ.path is modified.
*/
static QFileSystemEntry::NativePath generateNameFromTemplate(NativeTemplate &templ)
QFileSystemEntry::NativePath QTemporaryFileName::generateNext()
{
QFileSystemEntry::NativePath &path = templ.path;
qssize_t pos = templ.pos;
qssize_t length = templ.length;
Q_ASSERT(length != 0);
Q_ASSERT(pos < path.length());
Q_ASSERT(length <= path.length() - pos);
@ -159,6 +204,8 @@ static QFileSystemEntry::NativePath generateNameFromTemplate(NativeTemplate &tem
return path;
}
#ifndef QT_NO_TEMPORARYFILE
/*!
\internal
@ -172,13 +219,13 @@ static QFileSystemEntry::NativePath generateNameFromTemplate(NativeTemplate &tem
condition in \a error. In both cases, the string in \a templ will be
changed and contain the generated path name.
*/
static bool createFileFromTemplate(NativeFileHandle &file, NativeTemplate &templ,
static bool createFileFromTemplate(NativeFileHandle &file, QTemporaryFileName &templ,
quint32 mode, QSystemError &error)
{
const int maxAttempts = 16;
for (int attempt = 0; attempt < maxAttempts; ++attempt) {
// Atomically create file and obtain handle
const QFileSystemEntry::NativePath &path = generateNameFromTemplate(templ);
const QFileSystemEntry::NativePath &path = templ.generateNext();
#if defined(Q_OS_WIN)
Q_UNUSED(mode);
@ -261,62 +308,6 @@ void QTemporaryFileEngine::setFileName(const QString &file)
QFSFileEngine::setFileName(file);
}
static NativeTemplate makeNativeTemplate(QString qfilename)
{
// Ensure there is a placeholder mask
uint phPos = qfilename.length();
uint phLength = 0;
while (phPos != 0) {
--phPos;
if (qfilename[phPos] == QLatin1Char('X')) {
++phLength;
continue;
}
if (phLength >= 6
|| qfilename[phPos] == QLatin1Char('/')) {
++phPos;
break;
}
// start over
phLength = 0;
}
if (phLength < 6)
qfilename.append(QLatin1String(".XXXXXX"));
// "Nativify" :-)
QFileSystemEntry::NativePath filename = QFileSystemEngine::absoluteName(
QFileSystemEntry(qfilename, QFileSystemEntry::FromInternalPath()))
.nativeFilePath();
// Find mask in native path
phPos = filename.length();
phLength = 0;
while (phPos != 0) {
--phPos;
if (filename[phPos] == Latin1Char('X')) {
++phLength;
continue;
}
if (phLength >= 6) {
++phPos;
break;
}
// start over
phLength = 0;
}
Q_ASSERT(phLength >= 6);
return {filename, phPos, phLength};
}
bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode)
{
Q_D(QFSFileEngine);
@ -327,8 +318,7 @@ bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode)
if (!filePathIsTemplate)
return QFSFileEngine::open(openMode);
QString qfilename = templateName;
NativeTemplate templ = makeNativeTemplate(qfilename);
QTemporaryFileName tfn(templateName);
QSystemError error;
#if defined(Q_OS_WIN)
@ -337,12 +327,12 @@ bool QTemporaryFileEngine::open(QIODevice::OpenMode openMode)
NativeFileHandle &file = d->fd;
#endif
if (!createFileFromTemplate(file, templ, fileMode, error)) {
if (!createFileFromTemplate(file, tfn, fileMode, error)) {
setError(QFile::OpenError, error.toString());
return false;
}
d->fileEntry = QFileSystemEntry(templ.path, QFileSystemEntry::FromNativePath());
d->fileEntry = QFileSystemEntry(tfn.path, QFileSystemEntry::FromNativePath());
#if !defined(Q_OS_WIN) || defined(Q_OS_WINRT)
d->closeFileHandle = true;
@ -815,10 +805,10 @@ bool QTemporaryFile::open(OpenMode flags)
return false;
}
QT_END_NAMESPACE
#endif // QT_NO_TEMPORARYFILE
QT_END_NAMESPACE
#ifndef QT_NO_QOBJECT
#include "moc_qtemporaryfile.cpp"
#endif

View File

@ -53,14 +53,25 @@
#include <QtCore/qglobal.h>
#ifndef QT_NO_TEMPORARYFILE
#include "private/qfsfileengine_p.h"
#include "private/qfilesystemengine_p.h"
#include "private/qfile_p.h"
#include "qtemporaryfile.h"
QT_BEGIN_NAMESPACE
struct QTemporaryFileName
{
QFileSystemEntry::NativePath path;
qssize_t pos;
qssize_t length;
QTemporaryFileName(const QString &templateName);
QFileSystemEntry::NativePath generateNext();
};
#ifndef QT_NO_TEMPORARYFILE
class QTemporaryFilePrivate : public QFilePrivate
{
Q_DECLARE_PUBLIC(QTemporaryFile)
@ -120,9 +131,9 @@ public:
bool filePathWasTemplate;
};
QT_END_NAMESPACE
#endif // QT_NO_TEMPORARYFILE
QT_END_NAMESPACE
#endif /* QTEMPORARYFILE_P_H */

View File

@ -148,25 +148,24 @@ void tst_QTemporaryDir::fileTemplate_data()
{
QTest::addColumn<QString>("constructorTemplate");
QTest::addColumn<QString>("prefix");
QTest::addColumn<QString>("suffix");
QTest::newRow("constructor default") << "" << "tst_qtemporarydir-";
QTest::newRow("default") << "" << "tst_qtemporarydir-" << "";
QTest::newRow("constructor with xxx sufix") << "qt_XXXXXXxxx" << "qt_XXXXXXxxx";
QTest::newRow("constructor with xXx sufix") << "qt_XXXXXXxXx" << "qt_XXXXXXxXx";
QTest::newRow("constructor with no suffix") << "qt_XXXXXX" << "qt_";
QTest::newRow("constructor with >6 X's, no suffix") << "qt_XXXXXXXXXX" << "qt_";
// When more than 6 X are present at the end, linux and windows will only replace the last 6,
// while Mac OS will actually replace all of them so we can only expect "qt_" (and check isValid).
QTest::newRow("constructor with XXXX suffix") << "qt_XXXXXX_XXXX" << "qt_";
QTest::newRow("constructor with XXXX prefix") << "qt_XXXX" << "qt_";
QTest::newRow("constructor with XXXXX prefix") << "qt_XXXXX" << "qt_";
QTest::newRow("xxx-suffix") << "qt_XXXXXXxxx" << "qt_" << "xxx";
QTest::newRow("xXx-suffix") << "qt_XXXXXXxXx" << "qt_" << "xXx";
QTest::newRow("no-suffix") << "qt_XXXXXX" << "qt_" << "";
QTest::newRow("10X") << "qt_XXXXXXXXXX" << "qt_" << "";
QTest::newRow("4Xsuffix") << "qt_XXXXXX_XXXX" << "qt_" << "_XXXX";
QTest::newRow("4Xprefix") << "qt_XXXX" << "qt_XXXX" << "";
QTest::newRow("5Xprefix") << "qt_XXXXX" << "qt_XXXXX" << "";
if (canHandleUnicodeFileNames()) {
// Test Umlauts (contained in Latin1)
QString prefix = "qt_" + umlautTestText();
QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix;
// Test Chinese
QTest::newRow("Umlauts") << (prefix + "XXXXXX") << prefix << "";
// test non-Latin1
prefix = "qt_" + hanTestText();
QTest::newRow("Chinese characters") << (prefix + "XXXXXX") << prefix;
QTest::newRow("Chinese") << (prefix + "XXXXXX" + umlautTestText()) << prefix << umlautTestText();
}
}
@ -174,14 +173,17 @@ void tst_QTemporaryDir::fileTemplate()
{
QFETCH(QString, constructorTemplate);
QFETCH(QString, prefix);
QFETCH(QString, suffix);
QTemporaryDir tempDir(constructorTemplate);
QVERIFY(tempDir.isValid());
QString dirName = QDir(tempDir.path()).dirName();
if (prefix.length())
if (prefix.length()) {
QCOMPARE(dirName.left(prefix.length()), prefix);
QCOMPARE(dirName.right(suffix.length()), suffix);
}
}