diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 667c2a510..07a6454b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ set(keepassx_SOURCES core/EntryAttributes.cpp core/Group.cpp core/Metadata.cpp + core/qsavefile.cpp core/TimeInfo.cpp core/Tools.cpp core/Uuid.cpp @@ -97,6 +98,7 @@ set(keepassx_MOC core/EntryAttributes.h core/Group.h core/Metadata.h + core/qsavefile.h gui/AboutDialog.h gui/Application.h gui/ChangeMasterKeyWidget.h diff --git a/src/core/qsavefile.cpp b/src/core/qsavefile.cpp new file mode 100644 index 000000000..14b01398f --- /dev/null +++ b/src/core/qsavefile.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsavefile.h" +#include "qsavefile_p.h" + +#include +#include +#include + +QSaveFilePrivate::QSaveFilePrivate() + : tempFile(0), error(QFile::NoError) +{ +} + +QSaveFilePrivate::~QSaveFilePrivate() +{ +} + +/*! + \class QSaveFile + \brief The QSaveFile class provides an interface for safely writing to files. + + \ingroup io + + \reentrant + + 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. + + \sa QTextStream, QDataStream, QFileInfo, QDir, QFile, QTemporaryFile +*/ + +/*! + \internal +*/ +QSaveFile::QSaveFile() + : QIODevice(), d_ptr(new QSaveFilePrivate) +{ +} +/*! + Constructs a new file object with the given \a parent. +*/ +QSaveFile::QSaveFile(QObject *parent) + : QIODevice(parent), d_ptr(new QSaveFilePrivate) +{ +} +/*! + Constructs a new file object to represent the file with the given \a name. +*/ +QSaveFile::QSaveFile(const QString &name) + : QIODevice(0), d_ptr(new QSaveFilePrivate) +{ + 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) + : QIODevice(parent), d_ptr(new QSaveFilePrivate) +{ + Q_D(QSaveFile); + d->fileName = name; +} + +/*! + Destroys the file object, discarding the saved contents unless commit() was called. +*/ +QSaveFile::~QSaveFile() +{ + Q_D(QSaveFile); + if (d->tempFile) { + d->tempFile->setAutoRemove(true); + delete d->tempFile; + } + QIODevice::close(); +} + +/*! + Returns false since temporary files support random access. + + \sa QIODevice::isSequential() +*/ +bool QSaveFile::isSequential() const +{ + return false; +} + +/*! + Returns the file error status. + + The I/O device status returns an error code. For example, if open() + returns false, or a read/write operation returns -1, this function can + be called to find out the reason why the operation failed. + + Unlike QFile which clears the error on the next operation, QSaveFile remembers + the error until the file is closed, in order to discard the file contents in close(). + + \sa unsetError() +*/ + +QFile::FileError QSaveFile::error() const +{ + return d_func()->error; +} + +/*! + Sets the file's error to QFile::NoError. + + This will make QSaveFile forget that an error happened during saving, so you + probably don't want to call this, unless you're really sure that you want to + save the file anyway. + + \sa error() +*/ +void QSaveFile::unsetError() +{ + d_func()->error = QFile::NoError; + setErrorString(QString()); +} + +/*! + 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 be 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 Append and ReadWrite by copying from the existing file to the temp file... + if ((mode & ReadOnly) || (mode & Append)) { + qWarning("QSaveFile::open: Unsupported open mode %d", int(mode)); + return false; + } + + // check if existing file is writable + QFileInfo existingFile(d->fileName); + if (existingFile.exists() && !existingFile.isWritable()) { + d->error = QFile::WriteError; + setErrorString(QSaveFile::tr("Existing file %1 is not writable").arg(d->fileName)); + return false; + } + d->tempFile = new QTemporaryFile; + d->tempFile->setAutoRemove(false); + d->tempFile->setFileTemplate(d->fileName); + if (!d->tempFile->open()) { + d->error = d->tempFile->error(); + setErrorString(d->tempFile->errorString()); + delete d->tempFile; + d->tempFile = 0; + return false; + } + QIODevice::open(mode); + if (existingFile.exists()) + d->tempFile->setPermissions(existingFile.permissions()); + return true; +} + +/*! + \reimp + Cannot be called. + Call commit() instead. +*/ +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->tempFile) + return false; + Q_ASSERT(isOpen()); + QIODevice::close(); // flush and close + if (d->error != QFile::NoError) { + d->tempFile->remove(); + unsetError(); + delete d->tempFile; + d->tempFile = 0; + return false; + } + // atomically replace old file with new file + // Can't use QFile::rename for that, must use the file engine directly + // But that's not available in Qt5, nor is QTemporaryFileEngine + // (used by the Qt5-QFileDevice-based QSaveFile). + // So we have to do it by hand (non portable) for now. + d->tempFile->close(); +#if 1 + QAbstractFileEngine* fileEngine = d->tempFile->fileEngine(); + Q_ASSERT(fileEngine); + if (!fileEngine->rename(d->fileName)) { + d->error = fileEngine->error(); + setErrorString(fileEngine->errorString()); +#else + if (::rename(QFile::encodeName(d->tempFile->fileName()).constData(), QFile::encodeName(d->fileName).constData()) != 0) { + d->error = QFile::RenameError; + setErrorString(QString::fromLocal8Bit(strerror(errno))); +#endif + d->tempFile->remove(); + delete d->tempFile; + d->tempFile = 0; + return false; + } + delete d->tempFile; + d->tempFile = 0; + return true; +} + +/*! + Sets an error code so that commit() discards the temporary file. + + 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() +{ + if (!isOpen()) + return; + d_func()->error = QFile::WriteError; + setErrorString(QSaveFile::tr("Writing canceled by application")); +} + +/*! + Returns the size of the file. + \sa QFile::size() +*/ +qint64 QSaveFile::size() const +{ + Q_D(const QSaveFile); + return d->tempFile ? d->tempFile->size() : qint64(-1); +} + +/*! + \reimp +*/ +qint64 QSaveFile::pos() const +{ + Q_D(const QSaveFile); + return d->tempFile ? d->tempFile->pos() : qint64(-1); +} + +/*! + \reimp +*/ +bool QSaveFile::seek(qint64 offset) +{ + Q_D(QSaveFile); + return d->tempFile ? d->tempFile->seek(offset) : false; +} + +/*! + \reimp +*/ +bool QSaveFile::atEnd() const +{ + Q_D(const QSaveFile); + return d->tempFile ? d->tempFile->atEnd() : true; +} + +/*! + Flushes any buffered data to the file. Returns true if successful; + otherwise returns false. +*/ +bool QSaveFile::flush() +{ + Q_D(QSaveFile); + if (d->tempFile) { + if (!d->tempFile->flush()) { + d->error = d->tempFile->error(); + setErrorString(d->tempFile->errorString()); + return false; + } + return true; + } + return false; +} + +/*! + Returns the file handle of the temporary file. + + \sa QFile::handle() +*/ +int QSaveFile::handle() const +{ + Q_D(const QSaveFile); + return d->tempFile ? d->tempFile->handle() : -1; +} + +/*! + \reimp +*/ +qint64 QSaveFile::readData(char *data, qint64 maxlen) +{ + Q_D(QSaveFile); + return d->tempFile ? d->tempFile->read(data, maxlen) : -1; +} + +/*! + \reimp +*/ +qint64 QSaveFile::writeData(const char *data, qint64 len) +{ + Q_D(QSaveFile); + if (!d->tempFile) + return -1; + const qint64 written = d->tempFile->write(data, len); + if (written != len) { + d->error = QFile::WriteError; + setErrorString(QSaveFile::tr("Partial write. Partition full?")); + } + return written; +} + +/*! + \reimp +*/ +qint64 QSaveFile::readLineData(char *data, qint64 maxlen) +{ + Q_D(QSaveFile); + return d->tempFile ? d->tempFile->readLine(data, maxlen) : -1; +} diff --git a/src/core/qsavefile.h b/src/core/qsavefile.h new file mode 100644 index 000000000..48a8bf32b --- /dev/null +++ b/src/core/qsavefile.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSAVEFILE_H +#define QSAVEFILE_H + +#include +#include + +#ifdef open +#error qsavefile.h must be included before any header file that defines open +#endif + +class QAbstractFileEngine; +class QSaveFilePrivate; + +class QSaveFile : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSaveFile) + +public: + + QSaveFile(); + explicit QSaveFile(const QString &name); + explicit QSaveFile(QObject *parent); + QSaveFile(const QString &name, QObject *parent); + ~QSaveFile(); + + QFile::FileError error() const; + void unsetError(); + + QString fileName() const; + void setFileName(const QString &name); + + bool isSequential() const; + + virtual bool open(OpenMode flags); + bool commit(); + + void cancelWriting(); + + qint64 size() const; + qint64 pos() const; + bool seek(qint64 offset); + bool atEnd() const; + bool flush(); + + bool resize(qint64 sz); + + int handle() const; + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + qint64 readLineData(char *data, qint64 maxlen); + +private: + virtual void close(); + +private: + Q_DISABLE_COPY(QSaveFile) + + QSaveFilePrivate* const d_ptr; +}; + +#endif // QSAVEFILE_H diff --git a/src/core/qsavefile_p.h b/src/core/qsavefile_p.h new file mode 100644 index 000000000..7c7cfe663 --- /dev/null +++ b/src/core/qsavefile_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $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 + +class QSaveFilePrivate +{ +public: + QSaveFilePrivate(); + ~QSaveFilePrivate(); + + QString fileName; + QTemporaryFile *tempFile; + + QFile::FileError error; +}; + +#endif // QSAVEFILE_P_H + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1cc795bb5..c422c5adb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -146,6 +146,9 @@ add_unit_test(NAME testdeletedobjects SOURCES TestDeletedObjects.cpp MOCS TestDe add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp MOCS TestKeePass1Reader.h LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testqsavefile SOURCES TestQSaveFile.cpp MOCS TestQSaveFile.h + LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestQSaveFile.cpp b/tests/TestQSaveFile.cpp new file mode 100644 index 000000000..f00626b45 --- /dev/null +++ b/tests/TestQSaveFile.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TestQSaveFile.h" + +#include + +#include + +#if defined(Q_OS_WIN) +# include +#endif + +#include "tests.h" +#include "core/qsavefile.h" + +class DirCleanup +{ +public: + DirCleanup(const QString& dir, const QString& filePrefix) : m_dir(dir), m_filePrefix(filePrefix) {} + ~DirCleanup() { + QDir dir(m_dir); + QStringList files = dir.entryList(QStringList() << (m_filePrefix + "*"), QDir::Files); + Q_FOREACH (const QString& file, files) { + QFile::remove(m_dir + "/" + file); + } + + QDir().rmdir(m_dir); + } + +private: + QString m_dir; + QString m_filePrefix; +}; + +void TestQSaveFile::transactionalWrite() +{ + const QString dir = tmpDir(); + const QString targetFile = dir + QString::fromLatin1("/outfile"); + DirCleanup dirCleanup(dir, "outfile"); + QFile::remove(targetFile); + QSaveFile file(targetFile); + QVERIFY(file.open(QIODevice::WriteOnly)); + QVERIFY(file.isOpen()); + QCOMPARE(file.fileName(), targetFile); + QVERIFY(!QFile::exists(targetFile)); + + QTextStream ts(&file); + ts << "This is test data one.\n"; + ts.flush(); + QCOMPARE(file.error(), QFile::NoError); + QVERIFY(!QFile::exists(targetFile)); + + QVERIFY(file.commit()); + QVERIFY(QFile::exists(targetFile)); + QCOMPARE(file.fileName(), targetFile); + + // Check that we can reuse a QSaveFile object + // (and test the case of an existing target file) + QVERIFY(file.open(QIODevice::WriteOnly)); + QCOMPARE(file.write("Hello"), 5LL); + QVERIFY(file.commit()); + + QFile reader(targetFile); + QVERIFY(reader.open(QIODevice::ReadOnly)); + QCOMPARE(QString::fromLatin1(reader.readAll().constData()), QString::fromLatin1("Hello")); + reader.close(); + + QVERIFY(QFile::remove(targetFile)); +} + +void TestQSaveFile::autoFlush() +{ + const QString dir = tmpDir(); + const QString targetFile = dir + QString::fromLatin1("/outfile"); + DirCleanup dirCleanup(dir, "outfile"); + QFile::remove(targetFile); + QSaveFile file(targetFile); + QVERIFY(file.open(QIODevice::WriteOnly)); + + QTextStream ts(&file); + ts << "Auto-flush."; + // no flush + QVERIFY(file.commit()); // 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.")); + + QVERIFY(QFile::remove(targetFile)); +} + +void TestQSaveFile::transactionalWriteNoPermissions() +{ +#ifdef Q_OS_UNIX + if (::geteuid() == 0) + QSKIP("not valid running this test as root", SkipAll); + + // 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", SkipAll); + + QVERIFY(!file.open(QIODevice::WriteOnly)); + QCOMPARE(static_cast(file.error()), static_cast(QFile::OpenError)); + QVERIFY(!file.commit()); +#endif +} + +void TestQSaveFile::transactionalWriteCanceled() +{ + const QString dir = tmpDir(); + const QString targetFile = dir + QString::fromLatin1("/outfile"); + DirCleanup dirCleanup(dir, "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 TestQSaveFile::transactionalWriteErrorRenaming() +{ + const QString dir = tmpDir(); + const QString targetFile = dir + QString::fromLatin1("/outfile"); + DirCleanup dirCleanup(dir, "outfile"); + QSaveFile file(targetFile); + QVERIFY(file.open(QIODevice::WriteOnly)); + QCOMPARE(file.write("Hello"), qint64(5)); + QVERIFY(!QFile::exists(targetFile)); + +#ifdef Q_OS_UNIX + QFile dirAsFile(dir); // yay, I have to use QFile to change a dir's permissions... + QVERIFY(dirAsFile.setPermissions(QFile::Permissions(0))); // no permissions +#else + QVERIFY(file.setPermissions(QFile::ReadOwner)); +#endif + + QVERIFY(!file.commit()); + QVERIFY(!QFile::exists(targetFile)); // renaming failed + 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(file.setPermissions(QFile::ReadOwner | QFile::WriteOwner)); +#endif +} + +QString TestQSaveFile::tmpDir() +{ + QTemporaryFile* tmpFile = new QTemporaryFile(QDir::tempPath() + "/qttest_temp.XXXXXX"); + if (!tmpFile->open()) { + return QString(); + } + QString dirName = tmpFile->fileName(); + delete tmpFile; + if (!QDir().mkdir(dirName)) { + return QString(); + } + + return dirName; +} + +KEEPASSX_QTEST_CORE_MAIN(TestQSaveFile) diff --git a/tests/TestQSaveFile.h b/tests/TestQSaveFile.h new file mode 100644 index 000000000..67ad38548 --- /dev/null +++ b/tests/TestQSaveFile.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TESTQSAVEFILE_H +#define TESTQSAVEFILE_H + +#include + +class TestQSaveFile : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void transactionalWrite(); + void autoFlush(); + void transactionalWriteNoPermissions(); + void transactionalWriteCanceled(); + void transactionalWriteErrorRenaming(); + +private: + QString tmpDir(); +}; + +#endif // TESTQSAVEFILE_H