Add class QSaveFile.
This QIODevice uses a temporary file for writing, so that in case of write errors, the writing operation is canceled, without losing any existing file. It also avoids having a partially-written file visible by other processes, at the final destination. Change-Id: I9482df45751cb890b1b6f1382ec2eea3eb980627 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
e3a10e15ff
commit
e993df8771
@ -28,6 +28,7 @@ HEADERS += \
|
||||
io/qtemporaryfile_p.h \
|
||||
io/qresource_p.h \
|
||||
io/qresource_iterator_p.h \
|
||||
io/qsavefile.h \
|
||||
io/qstandardpaths.h \
|
||||
io/qurl.h \
|
||||
io/qurl_p.h \
|
||||
@ -67,6 +68,7 @@ SOURCES += \
|
||||
io/qtemporaryfile.cpp \
|
||||
io/qresource.cpp \
|
||||
io/qresource_iterator.cpp \
|
||||
io/qsavefile.cpp \
|
||||
io/qstandardpaths.cpp \
|
||||
io/qurl.cpp \
|
||||
io/qurlidna.cpp \
|
||||
|
316
src/corelib/io/qsavefile.cpp
Normal file
316
src/corelib/io/qsavefile.cpp
Normal file
@ -0,0 +1,316 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2012 David Faure <faure@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qplatformdefs.h"
|
||||
#include "qsavefile.h"
|
||||
#include "private/qsavefile_p.h"
|
||||
#include "qfileinfo.h"
|
||||
#include "qabstractfileengine_p.h"
|
||||
#include "qdebug.h"
|
||||
#include "qtemporaryfile.h"
|
||||
#include "private/qiodevice_p.h"
|
||||
#include "private/qtemporaryfile_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QSaveFilePrivate::QSaveFilePrivate()
|
||||
: writeError(QFileDevice::NoError)
|
||||
{
|
||||
}
|
||||
|
||||
QSaveFilePrivate::~QSaveFilePrivate()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QSaveFile
|
||||
\inmodule QtCore
|
||||
\brief The QSaveFile class provides an interface for safely writing to files.
|
||||
|
||||
\ingroup io
|
||||
|
||||
\reentrant
|
||||
|
||||
\since 5.1
|
||||
|
||||
QSaveFile is an I/O device for writing text and binary files, without losing
|
||||
existing data if the writing operation fails.
|
||||
|
||||
While writing, the contents will be written to a temporary file, and if
|
||||
no error happened, commit() will move it to the final file. This ensures that
|
||||
no data at the final file is lost in case an error happens while writing,
|
||||
and no partially-written file is ever present at the final location. Always
|
||||
use QSaveFile when saving entire documents to disk.
|
||||
|
||||
QSaveFile automatically detects errors while writing, such as the full partition
|
||||
situation, where write() cannot write all the bytes. It will remember that
|
||||
an error happened, and will discard the temporary file in commit().
|
||||
|
||||
Much like with QFile, the file is opened with open(). Data is usually read
|
||||
and written using QDataStream or QTextStream, but you can also call the
|
||||
QIODevice-inherited functions read(), readLine(), readAll(), write().
|
||||
|
||||
Unlike QFile, calling close() is not allowed. commit() replaces it. If commit()
|
||||
was not called and the QSaveFile instance is destroyed, the temporary file is
|
||||
discarded.
|
||||
|
||||
To abort saving due to an application error, call cancelWriting(), so that
|
||||
even a call to commit() later on will not save.
|
||||
|
||||
\sa QTextStream, QDataStream, QFileInfo, QDir, QFile, QTemporaryFile
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs a new file object with the given \a parent.
|
||||
*/
|
||||
QSaveFile::QSaveFile(QObject *parent)
|
||||
: QFileDevice(*new QSaveFilePrivate, parent)
|
||||
{
|
||||
}
|
||||
/*!
|
||||
Constructs a new file object to represent the file with the given \a name.
|
||||
*/
|
||||
QSaveFile::QSaveFile(const QString &name)
|
||||
: QFileDevice(*new QSaveFilePrivate, 0)
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
d->fileName = name;
|
||||
}
|
||||
/*!
|
||||
Constructs a new file object with the given \a parent to represent the
|
||||
file with the specified \a name.
|
||||
*/
|
||||
QSaveFile::QSaveFile(const QString &name, QObject *parent)
|
||||
: QFileDevice(*new QSaveFilePrivate, parent)
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
d->fileName = name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Destroys the file object, discarding the saved contents unless commit() was called.
|
||||
*/
|
||||
QSaveFile::~QSaveFile()
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
QFileDevice::close();
|
||||
if (d->fileEngine) {
|
||||
d->fileEngine->remove();
|
||||
delete d->fileEngine;
|
||||
d->fileEngine = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the name set by setFileName() or to the QSaveFile
|
||||
constructor.
|
||||
|
||||
\sa setFileName()
|
||||
*/
|
||||
QString QSaveFile::fileName() const
|
||||
{
|
||||
return d_func()->fileName;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the \a name of the file. The name can have no path, a
|
||||
relative path, or an absolute path.
|
||||
|
||||
\sa QFile::setFileName(), fileName()
|
||||
*/
|
||||
void QSaveFile::setFileName(const QString &name)
|
||||
{
|
||||
d_func()->fileName = name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens the file using OpenMode \a mode, returning true if successful;
|
||||
otherwise false.
|
||||
|
||||
Important: the \a mode must include QIODevice::WriteOnly.
|
||||
It may also have additional flags, such as QIODevice::Text and QIODevice::Unbuffered.
|
||||
|
||||
QIODevice::ReadWrite and QIODevice::Append are not supported at the moment.
|
||||
|
||||
\sa QIODevice::OpenMode, setFileName()
|
||||
*/
|
||||
bool QSaveFile::open(OpenMode mode)
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
if (isOpen()) {
|
||||
qWarning("QSaveFile::open: File (%s) already open", qPrintable(fileName()));
|
||||
return false;
|
||||
}
|
||||
unsetError();
|
||||
if ((mode & (ReadOnly | WriteOnly)) == 0) {
|
||||
qWarning("QSaveFile::open: Open mode not specified");
|
||||
return false;
|
||||
}
|
||||
// In the future we could implement ReadWrite by copying from the existing file to the temp file...
|
||||
if ((mode & ReadOnly) || (mode & Append)) {
|
||||
qWarning("QSaveFile::open: Unsupported open mode 0x%x", int(mode));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if existing file is writable
|
||||
QFileInfo existingFile(d->fileName);
|
||||
if (existingFile.exists() && !existingFile.isWritable()) {
|
||||
d->setError(QFileDevice::WriteError, QSaveFile::tr("Existing file %1 is not writable").arg(d->fileName));
|
||||
d->writeError = QFileDevice::WriteError;
|
||||
return false;
|
||||
}
|
||||
d->fileEngine = new QTemporaryFileEngine(d->fileName);
|
||||
// Same as in QFile: QIODevice provides the buffering, so there's no need to request it from the file engine.
|
||||
if (!d->fileEngine->open(mode | QIODevice::Unbuffered)) {
|
||||
QFileDevice::FileError err = d->fileEngine->error();
|
||||
if (err == QFileDevice::UnspecifiedError)
|
||||
err = QFileDevice::OpenError;
|
||||
d->setError(err, d->fileEngine->errorString());
|
||||
delete d->fileEngine;
|
||||
d->fileEngine = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileDevice::open(mode);
|
||||
if (existingFile.exists())
|
||||
setPermissions(existingFile.permissions());
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
This method has been made private so that it cannot be called, in order to prevent mistakes.
|
||||
In order to finish writing the file, call commit().
|
||||
If instead you want to abort writing, call cancelWriting().
|
||||
*/
|
||||
void QSaveFile::close()
|
||||
{
|
||||
qFatal("QSaveFile::close called");
|
||||
}
|
||||
|
||||
/*!
|
||||
Commits the changes to disk, if all previous writes were successful.
|
||||
|
||||
It is mandatory to call this at the end of the saving operation, otherwise the file will be
|
||||
discarded.
|
||||
|
||||
If an error happened during writing, deletes the temporary file and returns false.
|
||||
Otherwise, renames it to the final fileName and returns true on success.
|
||||
Finally, closes the device.
|
||||
|
||||
\sa cancelWriting()
|
||||
*/
|
||||
bool QSaveFile::commit()
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
if (!d->fileEngine)
|
||||
return false;
|
||||
|
||||
if (!isOpen()) {
|
||||
qWarning("QSaveFile::commit: File (%s) is not open", qPrintable(fileName()));
|
||||
return false;
|
||||
}
|
||||
QFileDevice::close(); // calls flush()
|
||||
|
||||
// Sync to disk if possible. Ignore errors (e.g. not supported).
|
||||
d->fileEngine->syncToDisk();
|
||||
|
||||
if (d->writeError != QFileDevice::NoError) {
|
||||
d->fileEngine->remove();
|
||||
d->writeError = QFileDevice::NoError;
|
||||
delete d->fileEngine;
|
||||
d->fileEngine = 0;
|
||||
return false;
|
||||
}
|
||||
// atomically replace old file with new file
|
||||
// Can't use QFile::rename for that, must use the file engine directly
|
||||
Q_ASSERT(d->fileEngine);
|
||||
if (!d->fileEngine->renameOverwrite(d->fileName)) {
|
||||
d->setError(d->fileEngine->error(), d->fileEngine->errorString());
|
||||
d->fileEngine->remove();
|
||||
delete d->fileEngine;
|
||||
d->fileEngine = 0;
|
||||
return false;
|
||||
}
|
||||
delete d->fileEngine;
|
||||
d->fileEngine = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
Cancels writing the new file.
|
||||
|
||||
If the application changes its mind while saving, it can call cancelWriting(),
|
||||
which sets an error code so that commit() will discard the temporary file.
|
||||
|
||||
Alternatively, it can simply make sure not to call commit().
|
||||
|
||||
Further write operations are possible after calling this method, but none
|
||||
of it will have any effect, the written file will be discarded.
|
||||
|
||||
\sa commit()
|
||||
*/
|
||||
void QSaveFile::cancelWriting()
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
if (!isOpen())
|
||||
return;
|
||||
d->setError(QFileDevice::WriteError, QSaveFile::tr("Writing canceled by application"));
|
||||
d->writeError = QFileDevice::WriteError;
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
qint64 QSaveFile::writeData(const char *data, qint64 len)
|
||||
{
|
||||
Q_D(QSaveFile);
|
||||
if (d->writeError != QFileDevice::NoError)
|
||||
return -1;
|
||||
|
||||
const qint64 ret = QFileDevice::writeData(data, len);
|
||||
|
||||
if (d->error != QFileDevice::NoError)
|
||||
d->writeError = d->error;
|
||||
return ret;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
94
src/corelib/io/qsavefile.h
Normal file
94
src/corelib/io/qsavefile.h
Normal file
@ -0,0 +1,94 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2012 David Faure <faure@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSAVEFILE_H
|
||||
#define QSAVEFILE_H
|
||||
|
||||
#include <QtCore/qfiledevice.h>
|
||||
#include <QtCore/qstring.h>
|
||||
|
||||
#ifdef open
|
||||
#error qsavefile.h must be included before any header file that defines open
|
||||
#endif
|
||||
|
||||
QT_BEGIN_HEADER
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
class QAbstractFileEngine;
|
||||
class QSaveFilePrivate;
|
||||
|
||||
class Q_CORE_EXPORT QSaveFile : public QFileDevice
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(QSaveFile)
|
||||
|
||||
public:
|
||||
|
||||
explicit QSaveFile(const QString &name);
|
||||
explicit QSaveFile(QObject *parent = 0);
|
||||
explicit QSaveFile(const QString &name, QObject *parent);
|
||||
~QSaveFile();
|
||||
|
||||
QString fileName() const Q_DECL_OVERRIDE;
|
||||
void setFileName(const QString &name);
|
||||
|
||||
bool open(OpenMode flags) Q_DECL_OVERRIDE;
|
||||
bool commit();
|
||||
|
||||
void cancelWriting();
|
||||
|
||||
protected:
|
||||
qint64 writeData(const char *data, qint64 len) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void close() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(QSaveFile)
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
QT_END_HEADER
|
||||
|
||||
#endif // QSAVEFILE_H
|
75
src/corelib/io/qsavefile_p.h
Normal file
75
src/corelib/io/qsavefile_p.h
Normal file
@ -0,0 +1,75 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2012 David Faure <faure@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSAVEFILE_P_H
|
||||
#define QSAVEFILE_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "private/qfiledevice_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QSaveFilePrivate : public QFileDevicePrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QSaveFile)
|
||||
|
||||
protected:
|
||||
QSaveFilePrivate();
|
||||
~QSaveFilePrivate();
|
||||
|
||||
QString fileName;
|
||||
|
||||
QFileDevice::FileError writeError;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QSAVEFILE_P_H
|
@ -20,6 +20,7 @@ SUBDIRS=\
|
||||
qprocessenvironment \
|
||||
qresourceengine \
|
||||
qsettings \
|
||||
qsavefile \
|
||||
qstandardpaths \
|
||||
qtemporarydir \
|
||||
qtemporaryfile \
|
||||
|
5
tests/auto/corelib/io/qsavefile/qsavefile.pro
Normal file
5
tests/auto/corelib/io/qsavefile/qsavefile.pro
Normal file
@ -0,0 +1,5 @@
|
||||
CONFIG += testcase parallel_test
|
||||
TARGET = tst_qsavefile
|
||||
QT = core testlib
|
||||
SOURCES = tst_qsavefile.cpp
|
||||
TESTDATA += tst_qsavefile.cpp
|
234
tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp
Normal file
234
tests/auto/corelib/io/qsavefile/tst_qsavefile.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2012 David Faure <faure@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qstring.h>
|
||||
#include <qtemporaryfile.h>
|
||||
#include <qfile.h>
|
||||
#include <qdir.h>
|
||||
#include <qset.h>
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
# include <unistd.h> // for geteuid
|
||||
# include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
class tst_QSaveFile : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void transactionalWrite();
|
||||
void textStreamManualFlush();
|
||||
void textStreamAutoFlush();
|
||||
void saveTwice();
|
||||
void transactionalWriteNoPermissions();
|
||||
void transactionalWriteCanceled();
|
||||
void transactionalWriteErrorRenaming();
|
||||
};
|
||||
|
||||
void tst_QSaveFile::transactionalWrite()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QFile::remove(targetFile);
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
QVERIFY(file.isOpen());
|
||||
QCOMPARE(file.fileName(), targetFile);
|
||||
QVERIFY(!QFile::exists(targetFile));
|
||||
|
||||
QCOMPARE(file.write("Hello"), Q_INT64_C(5));
|
||||
QCOMPARE(file.error(), QFile::NoError);
|
||||
QVERIFY(!QFile::exists(targetFile));
|
||||
|
||||
QVERIFY(file.commit());
|
||||
QVERIFY(QFile::exists(targetFile));
|
||||
QCOMPARE(file.fileName(), targetFile);
|
||||
|
||||
QFile reader(targetFile);
|
||||
QVERIFY(reader.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("Hello"));
|
||||
}
|
||||
|
||||
void tst_QSaveFile::saveTwice()
|
||||
{
|
||||
// Check that we can reuse a QSaveFile object
|
||||
// (and test the case of an existing target file)
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
QCOMPARE(file.write("Hello"), Q_INT64_C(5));
|
||||
QVERIFY2(file.commit(), qPrintable(file.errorString()));
|
||||
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
QCOMPARE(file.write("World"), Q_INT64_C(5));
|
||||
QVERIFY2(file.commit(), qPrintable(file.errorString()));
|
||||
|
||||
QFile reader(targetFile);
|
||||
QVERIFY(reader.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(QString::fromLatin1(reader.readAll()), QString::fromLatin1("World"));
|
||||
}
|
||||
|
||||
void tst_QSaveFile::textStreamManualFlush()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
|
||||
QTextStream ts(&file);
|
||||
ts << "Manual flush";
|
||||
ts.flush();
|
||||
QCOMPARE(file.error(), QFile::NoError);
|
||||
QVERIFY(!QFile::exists(targetFile));
|
||||
|
||||
QVERIFY(file.commit());
|
||||
QFile reader(targetFile);
|
||||
QVERIFY(reader.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(QString::fromLatin1(reader.readAll().constData()), QString::fromLatin1("Manual flush"));
|
||||
QFile::remove(targetFile);
|
||||
}
|
||||
|
||||
void tst_QSaveFile::textStreamAutoFlush()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
|
||||
QTextStream ts(&file);
|
||||
ts << "Auto-flush.";
|
||||
// no flush
|
||||
QVERIFY(file.commit()); // QIODevice::close will emit aboutToClose, which will flush the stream
|
||||
QFile reader(targetFile);
|
||||
QVERIFY(reader.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(QString::fromLatin1(reader.readAll().constData()), QString::fromLatin1("Auto-flush."));
|
||||
QFile::remove(targetFile);
|
||||
}
|
||||
|
||||
void tst_QSaveFile::transactionalWriteNoPermissions()
|
||||
{
|
||||
#ifdef Q_OS_UNIX
|
||||
if (::geteuid() == 0)
|
||||
QSKIP("not valid running this test as root");
|
||||
|
||||
// You can write into /dev/zero, but you can't create a /dev/zero.XXXXXX temp file.
|
||||
QSaveFile file("/dev/zero");
|
||||
if (!QDir("/dev").exists())
|
||||
QSKIP("/dev doesn't exist on this system");
|
||||
|
||||
QVERIFY(!file.open(QIODevice::WriteOnly));
|
||||
QCOMPARE((int)file.error(), (int)QFile::OpenError);
|
||||
QVERIFY(!file.commit());
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QSaveFile::transactionalWriteCanceled()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QFile::remove(targetFile);
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
|
||||
QTextStream ts(&file);
|
||||
ts << "This writing operation will soon be canceled.\n";
|
||||
ts.flush();
|
||||
QCOMPARE(file.error(), QFile::NoError);
|
||||
QVERIFY(!QFile::exists(targetFile));
|
||||
|
||||
// We change our mind, let's abort writing
|
||||
file.cancelWriting();
|
||||
|
||||
QVERIFY(!file.commit());
|
||||
|
||||
QVERIFY(!QFile::exists(targetFile)); // temp file was discarded
|
||||
QCOMPARE(file.fileName(), targetFile);
|
||||
}
|
||||
|
||||
void tst_QSaveFile::transactionalWriteErrorRenaming()
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
const QString targetFile = dir.path() + QString::fromLatin1("/outfile");
|
||||
QSaveFile file(targetFile);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
QCOMPARE(file.write("Hello"), qint64(5));
|
||||
QVERIFY(!QFile::exists(targetFile));
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// Make rename() fail for lack of permissions in the directory
|
||||
QFile dirAsFile(dir.path()); // yay, I have to use QFile to change a dir's permissions...
|
||||
QVERIFY(dirAsFile.setPermissions(QFile::Permissions(0))); // no permissions
|
||||
#else
|
||||
// Windows: Make rename() fail for lack of permissions on an existing target file
|
||||
QFile existingTargetFile(targetFile);
|
||||
QVERIFY(existingTargetFile.open(QIODevice::WriteOnly));
|
||||
QCOMPARE(file.write("Target"), qint64(6));
|
||||
existingTargetFile.close();
|
||||
QVERIFY(existingTargetFile.setPermissions(QFile::ReadOwner));
|
||||
#endif
|
||||
|
||||
// The saving should fail.
|
||||
QVERIFY(!file.commit());
|
||||
#ifdef Q_OS_UNIX
|
||||
QVERIFY(!QFile::exists(targetFile)); // renaming failed
|
||||
#endif
|
||||
QCOMPARE(file.error(), QFile::RenameError);
|
||||
|
||||
// Restore permissions so that the cleanup can happen
|
||||
#ifdef Q_OS_UNIX
|
||||
QVERIFY(dirAsFile.setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)));
|
||||
#else
|
||||
QVERIFY(existingTargetFile.setPermissions(QFile::WriteOwner));
|
||||
QVERIFY(existingTargetFile.remove());
|
||||
#endif
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QSaveFile)
|
||||
#include "tst_qsavefile.moc"
|
Loading…
Reference in New Issue
Block a user