Remove QSaveFile and QLockFile.

They are part of Qt >= 5.1.
This commit is contained in:
Felix Geyer 2015-07-22 17:01:12 +02:00
parent 813c64a055
commit 531018e58d
14 changed files with 3 additions and 1886 deletions

View File

@ -54,9 +54,6 @@ set(keepassx_SOURCES
core/ListDeleter.h
core/Metadata.cpp
core/PasswordGenerator.cpp
core/qlockfile.cpp
core/qsavefile.cpp
core/qsavefile_p.h
core/SignalMultiplexer.cpp
core/TimeDelta.cpp
core/TimeInfo.cpp
@ -143,18 +140,6 @@ if(NOT GCRYPT_HAS_SALSA20)
)
endif()
if(UNIX)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/qlockfile_unix.cpp
)
elseif(MINGW)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/qlockfile_win.cpp
)
endif()
set(keepassx_SOURCES_MAINEXE
main.cpp
)

View File

@ -1,344 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qlockfile.h"
#include "qlockfile_p.h"
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
# include <QElapsedTimer>
#else
# include <QTime>
#endif
#include <QDateTime>
QT_BEGIN_NAMESPACE
/*!
\class QLockFile
\inmodule QtCore
\brief The QLockFile class provides locking between processes using a file.
\since 5.1
A lock file can be used to prevent multiple processes from accessing concurrently
the same resource. For instance, a configuration file on disk, or a socket, a port,
a region of shared memory...
Serialization is only guaranteed if all processes that access the shared resource
use QLockFile, with the same file path.
QLockFile supports two use cases:
to protect a resource for a short-term operation (e.g. verifying if a configuration
file has changed before saving new settings), and for long-lived protection of a
resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
When protecting for a short-term operation, it is acceptable to call lock() and wait
until any running operation finishes.
When protecting a resource over a long time, however, the application should always
call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
warn the user that the resource is locked.
If the process holding the lock crashes, the lock file stays on disk and can prevent
any other process from accessing the shared resource, ever. For this reason, QLockFile
tries to detect such a "stale" lock file, based on the process ID written into the file,
and (in case that process ID got reused meanwhile), on the last modification time of
the lock file (30s by default, for the use case of a short-lived operation).
If the lock file is found to be stale, it will be deleted.
For the use case of protecting a resource over a long time, you should therefore call
setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
that the document is locked, possibly using getLockInfo() for more details.
*/
/*!
\enum QLockFile::LockError
This enum describes the result of the last call to lock() or tryLock().
\value NoError The lock was acquired successfully.
\value LockFailedError The lock could not be acquired because another process holds it.
\value PermissionError The lock file could not be created, for lack of permissions
in the parent directory.
\value UnknownError Another error happened, for instance a full partition
prevented writing out the lock file.
*/
/*!
Constructs a new lock file object.
The object is created in an unlocked state.
When calling lock() or tryLock(), a lock file named \a fileName will be created,
if it doesn't already exist.
\sa lock(), unlock()
*/
QLockFile::QLockFile(const QString &fileName)
: d_ptr(new QLockFilePrivate(fileName))
{
}
/*!
Destroys the lock file object.
If the lock was acquired, this will release the lock, by deleting the lock file.
*/
QLockFile::~QLockFile()
{
unlock();
}
/*!
Sets \a staleLockTime to be the time in milliseconds after which
a lock file is considered stale.
The default value is 30000, i.e. 30 seconds.
If your application typically keeps the file locked for more than 30 seconds
(for instance while saving megabytes of data for 2 minutes), you should set
a bigger value using setStaleLockTime().
The value of \a staleLockTime is used by lock() and tryLock() in order
to determine when an existing lock file is considered stale, i.e. left over
by a crashed process. This is useful for the case where the PID got reused
meanwhile, so the only way to detect a stale lock file is by the fact that
it has been around for a long time.
\sa staleLockTime()
*/
void QLockFile::setStaleLockTime(int staleLockTime)
{
Q_D(QLockFile);
d->staleLockTime = staleLockTime;
}
/*!
Returns the time in milliseconds after which
a lock file is considered stale.
\sa setStaleLockTime()
*/
int QLockFile::staleLockTime() const
{
Q_D(const QLockFile);
return d->staleLockTime;
}
/*!
Returns \c true if the lock was acquired by this QLockFile instance,
otherwise returns \c false.
\sa lock(), unlock(), tryLock()
*/
bool QLockFile::isLocked() const
{
Q_D(const QLockFile);
return d->isLocked;
}
/*!
Creates the lock file.
If another process (or another thread) has created the lock file already,
this function will block until that process (or thread) releases it.
Calling this function multiple times on the same lock from the same
thread without unlocking first is not allowed. This function will
\e dead-lock when the file is locked recursively.
Returns \c true if the lock was acquired, false if it could not be acquired
due to an unrecoverable error, such as no permissions in the parent directory.
\sa unlock(), tryLock()
*/
bool QLockFile::lock()
{
return tryLock(-1);
}
/*!
Attempts to create the lock file. This function returns \c true if the
lock was obtained; otherwise it returns \c false. If another process (or
another thread) has created the lock file already, this function will
wait for at most \a timeout milliseconds for the lock file to become
available.
Note: Passing a negative number as the \a timeout is equivalent to
calling lock(), i.e. this function will wait forever until the lock
file can be locked if \a timeout is negative.
If the lock was obtained, it must be released with unlock()
before another process (or thread) can successfully lock it.
Calling this function multiple times on the same lock from the same
thread without unlocking first is not allowed, this function will
\e always return false when attempting to lock the file recursively.
\sa lock(), unlock()
*/
bool QLockFile::tryLock(int timeout)
{
Q_D(QLockFile);
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
QElapsedTimer timer;
#else
QTime timer;
#endif
if (timeout > 0)
timer.start();
int sleepTime = 100;
Q_FOREVER {
d->lockError = d->tryLock_sys();
switch (d->lockError) {
case NoError:
d->isLocked = true;
return true;
case PermissionError:
case UnknownError:
return false;
case LockFailedError:
if (!d->isLocked && d->isApparentlyStale()) {
// Stale lock from another thread/process
// Ensure two processes don't remove it at the same time
QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
if (rmlock.tryLock()) {
if (d->isApparentlyStale() && d->removeStaleLock())
continue;
}
}
break;
}
if (timeout == 0 || (timeout > 0 && (timer.elapsed() > timeout)))
return false;
QLockFileThread::msleep(sleepTime);
if (sleepTime < 5 * 1000)
sleepTime *= 2;
}
// not reached
return false;
}
/*!
\fn void QLockFile::unlock()
Releases the lock, by deleting the lock file.
Calling unlock() without locking the file first, does nothing.
\sa lock(), tryLock()
*/
/*!
Retrieves information about the current owner of the lock file.
If tryLock() returns \c false, and error() returns LockFailedError,
this function can be called to find out more information about the existing
lock file:
\list
\li the PID of the application (returned in \a pid)
\li the \a hostname it's running on (useful in case of networked filesystems),
\li the name of the application which created it (returned in \a appname),
\endlist
Note that tryLock() automatically deleted the file if there is no
running application with this PID, so LockFailedError can only happen if there is
an application with this PID (it could be unrelated though).
This can be used to inform users about the existing lock file and give them
the choice to delete it. After removing the file using removeStaleLockFile(),
the application can call tryLock() again.
This function returns \c true if the information could be successfully retrieved, false
if the lock file doesn't exist or doesn't contain the expected data.
This can happen if the lock file was deleted between the time where tryLock() failed
and the call to this function. Simply call tryLock() again if this happens.
*/
bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
{
Q_D(const QLockFile);
return d->getLockInfo(pid, hostname, appname);
}
bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
{
QFile reader(fileName);
if (!reader.open(QIODevice::ReadOnly))
return false;
QByteArray pidLine = reader.readLine();
pidLine.chop(1);
QByteArray appNameLine = reader.readLine();
appNameLine.chop(1);
QByteArray hostNameLine = reader.readLine();
hostNameLine.chop(1);
if (pidLine.isEmpty())
return false;
qint64 thePid = pidLine.toLongLong();
if (pid)
*pid = thePid;
if (appname)
*appname = QString::fromUtf8(appNameLine);
if (hostname)
*hostname = QString::fromUtf8(hostNameLine);
return thePid > 0;
}
/*!
Attempts to forcefully remove an existing lock file.
Calling this is not recommended when protecting a short-lived operation: QLockFile
already takes care of removing lock files after they are older than staleLockTime().
This method should only be called when protecting a resource for a long time, i.e.
with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
agreed on removing the lock file.
Returns \c true on success, false if the lock file couldn't be removed. This happens
on Windows, when the application owning the lock is still running.
*/
bool QLockFile::removeStaleLockFile()
{
Q_D(QLockFile);
if (d->isLocked) {
qWarning("removeStaleLockFile can only be called when not holding the lock");
return false;
}
return d->removeStaleLock();
}
/*!
Returns the lock file error status.
If tryLock() returns \c false, this function can be called to find out
the reason why the locking failed.
*/
QLockFile::LockError QLockFile::error() const
{
Q_D(const QLockFile);
return d->lockError;
}
QT_END_NAMESPACE

View File

@ -1,79 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QLOCKFILE_H
#define QLOCKFILE_H
#include <QString>
#include <QScopedPointer>
QT_BEGIN_NAMESPACE
class QLockFilePrivate;
class QLockFile
{
public:
QLockFile(const QString &fileName);
~QLockFile();
bool lock();
bool tryLock(int timeout = 0);
void unlock();
void setStaleLockTime(int);
int staleLockTime() const;
bool isLocked() const;
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
bool removeStaleLockFile();
enum LockError {
NoError = 0,
LockFailedError = 1,
PermissionError = 2,
UnknownError = 3
};
LockError error() const;
protected:
QScopedPointer<QLockFilePrivate> d_ptr;
private:
Q_DECLARE_PRIVATE(QLockFile)
Q_DISABLE_COPY(QLockFile)
};
QT_END_NAMESPACE
#endif // QLOCKFILE_H

View File

@ -1,101 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QLOCKFILE_P_H
#define QLOCKFILE_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 "qlockfile.h"
#include <QFile>
#include <QThread>
#ifdef Q_OS_WIN
#include <qt_windows.h>
#endif
QT_BEGIN_NAMESPACE
class QLockFileThread : public QThread
{
public:
static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
};
class QLockFilePrivate
{
public:
QLockFilePrivate(const QString &fn)
: fileName(fn),
#ifdef Q_OS_WIN
fileHandle(INVALID_HANDLE_VALUE),
#else
fileHandle(-1),
#endif
staleLockTime(30 * 1000), // 30 seconds
lockError(QLockFile::NoError),
isLocked(false)
{
}
QLockFile::LockError tryLock_sys();
bool removeStaleLock();
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
// Returns \c true if the lock belongs to dead PID, or is old.
// The attempt to delete it will tell us if it was really stale or not, though.
bool isApparentlyStale() const;
static QString processNameByPid(qint64 pid);
QString fileName;
#ifdef Q_OS_WIN
Qt::HANDLE fileHandle;
#else
int fileHandle;
#endif
int staleLockTime; // "int milliseconds" is big enough for 24 days
QLockFile::LockError lockError;
bool isLocked;
};
QT_END_NAMESPACE
#endif /* QLOCKFILE_P_H */

View File

@ -1,244 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qlockfile_p.h"
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QDateTime>
#include <sys/file.h> // flock
#include <sys/types.h> // kill
#include <signal.h> // kill
#include <unistd.h> // gethostname
#include <errno.h>
#if defined(Q_OS_MAC)
# include <libproc.h>
#elif defined(Q_OS_LINUX)
# include <unistd.h>
# include <cstdio>
#endif
QT_BEGIN_NAMESPACE
#define EINTR_LOOP(var, cmd) \
do { \
var = cmd; \
} while (var == -1 && errno == EINTR)
// don't call QT_OPEN or ::open
// call qt_safe_open
static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777)
{
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
int fd;
EINTR_LOOP(fd, ::open(pathname, flags, mode));
// unknown flags are ignored, so we have no way of verifying if
// O_CLOEXEC was accepted
if (fd != -1)
::fcntl(fd, F_SETFD, FD_CLOEXEC);
return fd;
}
static inline qint64 qt_safe_write(int fd, const void *data, qint64 len)
{
qint64 ret = 0;
EINTR_LOOP(ret, ::write(fd, data, len));
return ret;
}
static QByteArray localHostName() // from QHostInfo::localHostName(), modified to return a QByteArray
{
QByteArray hostName(512, Qt::Uninitialized);
if (gethostname(hostName.data(), hostName.size()) == -1)
return QByteArray();
hostName.truncate(strlen(hostName.data()));
return hostName;
}
// ### merge into qt_safe_write?
static qint64 qt_write_loop(int fd, const char *data, qint64 len)
{
qint64 pos = 0;
while (pos < len) {
const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
if (ret == -1) // e.g. partition full
return pos;
pos += ret;
}
return pos;
}
static bool setNativeLocks(int fd)
{
#if defined(LOCK_EX) && defined(LOCK_NB)
if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
return false;
#endif
struct flock flockData;
flockData.l_type = F_WRLCK;
flockData.l_whence = SEEK_SET;
flockData.l_start = 0;
flockData.l_len = 0; // 0 = entire file
flockData.l_pid = getpid();
if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
return false;
return true;
}
QLockFile::LockError QLockFilePrivate::tryLock_sys()
{
// Assemble data, to write in a single call to write
// (otherwise we'd have to check every write call)
// Use operator% from the fast builder to avoid multiple memory allocations.
QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n'
+ QCoreApplication::applicationName().toUtf8() + '\n'
+ localHostName() + '\n';
const QByteArray lockFileName = QFile::encodeName(fileName);
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd < 0) {
switch (errno) {
case EEXIST:
return QLockFile::LockFailedError;
case EACCES:
case EROFS:
return QLockFile::PermissionError;
default:
return QLockFile::UnknownError;
}
}
// Ensure nobody else can delete the file while we have it
if (!setNativeLocks(fd))
qWarning() << "setNativeLocks failed:" << strerror(errno);
if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
close(fd);
if (!QFile::remove(fileName))
qWarning("QLockFile: Could not remove our own lock file %s.", qPrintable(fileName));
return QLockFile::UnknownError; // partition full
}
// We hold the lock, continue.
fileHandle = fd;
return QLockFile::NoError;
}
bool QLockFilePrivate::removeStaleLock()
{
const QByteArray lockFileName = QFile::encodeName(fileName);
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644);
if (fd < 0) // gone already?
return false;
#ifdef Q_OS_MAC
// ugly workaround: ignore setNativeLocks() result on Mac since it's broken there
setNativeLocks(fd);
bool success = (::unlink(lockFileName) == 0);
#else
bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
#endif
close(fd);
return success;
}
bool QLockFilePrivate::isApparentlyStale() const
{
qint64 pid;
QString hostname, appname;
if (getLockInfo(&pid, &hostname, &appname)) {
if (hostname.isEmpty() || hostname == QString::fromLocal8Bit(localHostName())) {
if (::kill(pid, 0) == -1 && errno == ESRCH)
return true; // PID doesn't exist anymore
const QString processName = processNameByPid(pid);
if (!processName.isEmpty()) {
QFileInfo fi(appname);
if (fi.isSymLink())
fi.setFile(fi.symLinkTarget());
if (processName.toLower() != fi.fileName().toLower())
return true; // PID got reused by a different application.
}
}
}
const qint64 age = QFileInfo(fileName).lastModified().secsTo(QDateTime::currentDateTime()) * 1000;
return staleLockTime > 0 && age > staleLockTime;
}
QString QLockFilePrivate::processNameByPid(qint64 pid)
{
#if defined(Q_OS_MAC)
char name[1024];
proc_name(pid, name, sizeof(name) / sizeof(char));
return QFile::decodeName(name);
#elif defined(Q_OS_LINUX)
if (!QFile::exists(QString("/proc/version")))
return QString();
char exePath[64];
char buf[PATH_MAX + 1];
sprintf(exePath, "/proc/%lld/exe", pid);
size_t len = static_cast<size_t>(readlink(exePath, buf, sizeof(buf)));
if (len >= sizeof(buf)) {
// The pid is gone. Return some invalid process name to fail the test.
return QString("/ERROR/");
}
buf[len] = 0;
return QFileInfo(QFile::decodeName(buf)).fileName();
#else
return QString();
#endif
}
void QLockFile::unlock()
{
Q_D(QLockFile);
if (!d->isLocked)
return;
close(d->fileHandle);
d->fileHandle = -1;
if (!QFile::remove(d->fileName)) {
qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?";
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
}
d->lockError = QLockFile::NoError;
d->isLocked = false;
}
QT_END_NAMESPACE

View File

@ -1,227 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef UNICODE
#define UNICODE
#endif
#include "qlockfile_p.h"
#include <qt_windows.h>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QDateTime>
#include <QDebug>
QT_BEGIN_NAMESPACE
static inline QByteArray localHostName()
{
return qgetenv("COMPUTERNAME");
}
static inline bool fileExists(const wchar_t *fileName)
{
WIN32_FILE_ATTRIBUTE_DATA data;
return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
}
QLockFile::LockError QLockFilePrivate::tryLock_sys()
{
const ushort* nativePath = QDir::toNativeSeparators(fileName).utf16();
// When writing, allow others to read.
// When reading, QFile will allow others to read and write, all good.
// Adding FILE_SHARE_DELETE would allow forceful deletion of stale files,
// but Windows doesn't allow recreating it while this handle is open anyway,
// so this would only create confusion (can't lock, but no lock file to read from).
const DWORD dwShareMode = FILE_SHARE_READ;
#ifndef Q_OS_WINRT
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
HANDLE fh = CreateFile((const wchar_t*)nativePath,
GENERIC_WRITE,
dwShareMode,
&securityAtts,
CREATE_NEW, // error if already exists
FILE_ATTRIBUTE_NORMAL,
NULL);
#else // !Q_OS_WINRT
HANDLE fh = CreateFile2((const wchar_t*)nativePath,
GENERIC_WRITE,
dwShareMode,
CREATE_NEW, // error if already exists
NULL);
#endif // Q_OS_WINRT
if (fh == INVALID_HANDLE_VALUE) {
const DWORD lastError = GetLastError();
switch (lastError) {
case ERROR_SHARING_VIOLATION:
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
return QLockFile::LockFailedError;
case ERROR_ACCESS_DENIED:
// readonly file, or file still in use by another process.
// Assume the latter if the file exists, since we don't create it readonly.
return fileExists((const wchar_t*)nativePath)
? QLockFile::LockFailedError
: QLockFile::PermissionError;
default:
qWarning() << "Got unexpected locking error" << lastError;
return QLockFile::UnknownError;
}
}
// We hold the lock, continue.
fileHandle = fh;
// Assemble data, to write in a single call to write
// (otherwise we'd have to check every write call)
QByteArray fileData;
fileData += QByteArray::number(QCoreApplication::applicationPid());
fileData += '\n';
fileData += QCoreApplication::applicationName().toUtf8();
fileData += '\n';
fileData += localHostName();
fileData += '\n';
DWORD bytesWritten = 0;
QLockFile::LockError error = QLockFile::NoError;
if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
error = QLockFile::UnknownError; // partition full
return error;
}
bool QLockFilePrivate::removeStaleLock()
{
// QFile::remove fails on Windows if the other process is still using the file, so it's not stale.
return QFile::remove(fileName);
}
bool QLockFilePrivate::isApparentlyStale() const
{
qint64 pid;
QString hostname, appname;
// On WinRT there seems to be no way of obtaining information about other
// processes due to sandboxing
#ifndef Q_OS_WINRT
if (getLockInfo(&pid, &hostname, &appname)) {
if (hostname.isEmpty() || hostname == QString::fromLocal8Bit(localHostName())) {
HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!procHandle)
return true;
// We got a handle but check if process is still alive
DWORD dwR = ::WaitForSingleObject(procHandle, 0);
::CloseHandle(procHandle);
if (dwR == WAIT_TIMEOUT)
return true;
const QString processName = processNameByPid(pid);
if (!processName.isEmpty() && processName != appname)
return true; // PID got reused by a different application.
}
}
#else // !Q_OS_WINRT
Q_UNUSED(pid);
Q_UNUSED(hostname);
Q_UNUSED(appname);
#endif // Q_OS_WINRT
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
return staleLockTime > 0 && age > staleLockTime;
}
QString QLockFilePrivate::processNameByPid(qint64 pid)
{
#if !defined(Q_OS_WINRT) && !defined(Q_OS_WINCE)
typedef DWORD (WINAPI *GetModuleFileNameExFunc)(HANDLE, HMODULE, LPTSTR, DWORD);
HMODULE hPsapi = LoadLibraryA("psapi");
if (!hPsapi)
return QString();
GetModuleFileNameExFunc qGetModuleFileNameEx
= (GetModuleFileNameExFunc)GetProcAddress(hPsapi, "GetModuleFileNameExW");
if (!qGetModuleFileNameEx) {
FreeLibrary(hPsapi);
return QString();
}
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid));
if (!hProcess) {
FreeLibrary(hPsapi);
return QString();
}
wchar_t buf[MAX_PATH];
const DWORD length = qGetModuleFileNameEx(hProcess, NULL, buf, sizeof(buf) / sizeof(wchar_t));
CloseHandle(hProcess);
FreeLibrary(hPsapi);
if (!length)
return QString();
QString name = QString::fromWCharArray(buf, length);
int i = name.lastIndexOf(QLatin1Char('\\'));
if (i >= 0)
name.remove(0, i + 1);
i = name.lastIndexOf(QLatin1Char('.'));
if (i >= 0)
name.truncate(i);
return name;
#else
Q_UNUSED(pid);
return QString();
#endif
}
void QLockFile::unlock()
{
Q_D(QLockFile);
if (!d->isLocked)
return;
CloseHandle(d->fileHandle);
int attempts = 0;
static const int maxAttempts = 500; // 500ms
while (!QFile::remove(d->fileName) && ++attempts < maxAttempts) {
// Someone is reading the lock file right now (on Windows this prevents deleting it).
QLockFileThread::msleep(1);
}
if (attempts == maxAttempts) {
qWarning() << "Could not remove our own lock file" << d->fileName << ". Either other users of the lock file are reading it constantly for 500 ms, or we (no longer) have permissions to delete the file";
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
}
d->lockError = QLockFile::NoError;
d->isLocked = false;
}
QT_END_NAMESPACE

View File

@ -1,452 +0,0 @@
/****************************************************************************
**
** 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 <QAbstractFileEngine>
#include <QFileInfo>
#include <QTemporaryFile>
#ifdef Q_OS_WIN
# include <windows.h>
#else
# include <unistd.h>
#endif
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();
delete d;
}
/*!
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;
if (!isOpen()) {
qWarning("QSaveFile::commit: File (%s) is not open", qPrintable(fileName()));
return false;
}
flush();
#ifdef Q_OS_WIN
FlushFileBuffers(reinterpret_cast<HANDLE>(handle()));
#elif defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
fdatasync(d->tempFile->handle());
#else
fsync(d->tempFile->handle());
#endif
QIODevice::close();
if (d->error != QFile::NoError) {
d->tempFile->remove();
unsetError();
delete d->tempFile;
d->tempFile = 0;
return false;
}
d->tempFile->close();
#ifdef Q_OS_WIN
// On Windows QAbstractFileEngine::rename() fails if the the target exists,
// so we have to rename the target.
// Ideally the winapi ReplaceFile() method should be used.
QString bakname = d->fileName + "~";
QFile::remove(bakname);
QFile::rename(d->fileName, bakname);
#endif
QAbstractFileEngine* fileEngine = d->tempFile->fileEngine();
Q_ASSERT(fileEngine);
if (!fileEngine->rename(d->fileName)) {
d->error = fileEngine->error();
setErrorString(fileEngine->errorString());
d->tempFile->remove();
delete d->tempFile;
d->tempFile = 0;
#ifdef Q_OS_WIN
QFile::rename(bakname, d->fileName);
#endif
return false;
}
delete d->tempFile;
d->tempFile = 0;
#ifdef Q_OS_WIN
QFile::remove(bakname);
#endif
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;
}

View File

@ -1,105 +0,0 @@
/****************************************************************************
**
** 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 <QFile>
#include <QString>
#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

View File

@ -1,71 +0,0 @@
/****************************************************************************
**
** 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 <QTemporaryFile>
class QSaveFilePrivate
{
public:
QSaveFilePrivate();
~QSaveFilePrivate();
QString fileName;
QTemporaryFile *tempFile;
QFile::FileError error;
};
#endif // QSAVEFILE_P_H

View File

@ -18,6 +18,8 @@
#include "DatabaseTabWidget.h"
#include <QFileInfo>
#include <QLockFile>
#include <QSaveFile>
#include <QTabWidget>
#include "autotype/AutoType.h"
@ -25,7 +27,6 @@
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/qsavefile.h"
#include "format/CsvExporter.h"
#include "gui/DatabaseWidget.h"
#include "gui/DatabaseWidgetStateSync.h"

View File

@ -21,7 +21,6 @@
#include <QHash>
#include <QTabWidget>
#include "core/qlockfile.h"
#include "format/KeePass2Writer.h"
#include "gui/DatabaseWidget.h"
@ -29,6 +28,7 @@ class DatabaseWidget;
class DatabaseWidgetStateSync;
class DatabaseOpenWidget;
class QFile;
class QLockFile;
struct DatabaseManagerStruct
{

View File

@ -138,9 +138,6 @@ add_unit_test(NAME testdeletedobjects SOURCES TestDeletedObjects.cpp
add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testqsavefile SOURCES TestQSaveFile.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp
LIBS ${TEST_LIBRARIES})

View File

@ -1,204 +0,0 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* 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 <http://www.gnu.org/licenses/>.
*/
#include "TestQSaveFile.h"
#include <QtTest>
#include <unistd.h>
#if defined(Q_OS_WIN)
# include <windows.h>
#endif
#include "tests.h"
#include "core/qsavefile.h"
QTEST_GUILESS_MAIN(TestQSaveFile)
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();
QVERIFY(!dir.isEmpty());
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();
QVERIFY(!dir.isEmpty());
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."));
reader.close();
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<int>(file.error()), static_cast<int>(QFile::OpenError));
QVERIFY(!file.commit());
#endif
}
void TestQSaveFile::transactionalWriteCanceled()
{
const QString dir = tmpDir();
QVERIFY(!dir.isEmpty());
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()
{
#ifndef Q_OS_WIN
if (::geteuid() == 0) {
QSKIP("not valid running this test as root", SkipAll);
}
const QString dir = tmpDir();
QVERIFY(!dir.isEmpty());
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
#endif // !Q_OS_WIN
}
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;
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TESTQSAVEFILE_H
#define KEEPASSX_TESTQSAVEFILE_H
#include <QObject>
class TestQSaveFile : public QObject
{
Q_OBJECT
private Q_SLOTS:
void transactionalWrite();
void autoFlush();
void transactionalWriteNoPermissions();
void transactionalWriteCanceled();
void transactionalWriteErrorRenaming();
private:
QString tmpDir();
};
#endif // KEEPASSX_TESTQSAVEFILE_H