NOISSUE refactor window management and launch, make MultiMC a single instance application.

This commit is contained in:
Petr Mrázek
2016-10-30 02:37:38 +01:00
parent deabfa78f8
commit 412855ae3d
18 changed files with 1164 additions and 185 deletions

View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.1)
project(LocalPeer)
find_package(Qt5Core REQUIRED QUIET)
find_package(Qt5Network REQUIRED QUIET)
set(SINGLE_SOURCES
src/LocalPeer.cpp
src/LockedFile.cpp
src/LockedFile.h
include/LocalPeer.h
)
if(UNIX)
list(APPEND SINGLE_SOURCES
src/LockedFile_unix.cpp
)
endif()
if(WIN32)
list(APPEND SINGLE_SOURCES
src/LockedFile_win.cpp
)
endif()
add_library(LocalPeer STATIC ${SINGLE_SOURCES})
target_include_directories(LocalPeer PUBLIC include)
qt5_use_modules(LocalPeer Core Network)

View File

@ -0,0 +1,97 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#pragma once
#include <QObject>
#include <QString>
#include <memory>
class QLocalServer;
class LockedFile;
class ApplicationId
{
public: /* methods */
// traditional app = installed system wide and used in a multi-user environment
static ApplicationId fromTraditionalApp();
// ID based on a path with all the application data (no two instances with the same data path should run)
static ApplicationId fromPathAndVersion(const QString & dataPath, const QString & version);
// fully custom ID
static ApplicationId fromCustomId(const QString & id);
QString toString()
{
return m_id;
}
private: /* methods */
ApplicationId(const QString & value)
{
m_id = value;
}
private: /* data */
QString m_id;
};
class LocalPeer : public QObject
{
Q_OBJECT
public:
LocalPeer(QObject *parent, const ApplicationId &appId);
~LocalPeer();
bool isClient();
bool sendMessage(const QString &message, int timeout);
ApplicationId applicationId() const;
Q_SIGNALS:
void messageReceived(const QString &message);
protected Q_SLOTS:
void receiveConnection();
protected:
ApplicationId id;
QString socketName;
std::unique_ptr<QLocalServer> server;
std::unique_ptr<LockedFile> lockFile;
};

View File

@ -0,0 +1,236 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "LocalPeer.h"
#include <QCoreApplication>
#include <QDataStream>
#include <QTime>
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include "LockedFile.h"
#if defined(Q_OS_WIN)
#include <QLibrary>
#include <qt_windows.h>
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif
#if defined(Q_OS_UNIX)
#include <sys/types.h>
#include <unistd.h>
#endif
#include <chrono>
#include <thread>
#include <QCryptographicHash>
static const char* ack = "ack";
ApplicationId ApplicationId::fromTraditionalApp()
{
QString protoId = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN)
protoId = protoId.toLower();
#endif
auto prefix = protoId.section(QLatin1Char('/'), -1);
prefix.remove(QRegExp("[^a-zA-Z]"));
prefix.truncate(6);
QByteArray idc = protoId.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId)
{
QLibrary lib("kernel32");
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
}
if (pProcessIdToSessionId)
{
DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
}
#else
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif
return ApplicationId(socketName);
}
ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const QString& version)
{
QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1);
QString result = dataPath + QLatin1Char('-') + version;
shasum.addData(result.toUtf8());
return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex()));
}
ApplicationId ApplicationId::fromCustomId(const QString& id)
{
return ApplicationId(QLatin1String("qtsingleapp-") + id);
}
LocalPeer::LocalPeer(QObject * parent, const ApplicationId &appId)
: QObject(parent), id(appId)
{
socketName = id.toString();
server.reset(new QLocalServer());
QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile");
lockFile.reset(new LockedFile(lockName));
lockFile->open(QIODevice::ReadWrite);
}
LocalPeer::~LocalPeer()
{
}
ApplicationId LocalPeer::applicationId() const
{
return id;
}
bool LocalPeer::isClient()
{
if (lockFile->isLocked())
return false;
if (!lockFile->lock(LockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
#if defined(Q_OS_UNIX)
// ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName);
}
#endif
if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
QObject::connect(server.get(), SIGNAL(newConnection()), SLOT(receiveConnection()));
return false;
}
bool LocalPeer::sendMessage(const QString &message, int timeout)
{
if (!isClient())
return false;
QLocalSocket socket;
bool connOk = false;
for(int i = 0; i < 2; i++) {
// Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
{
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
if (!connOk)
{
return false;
}
QByteArray uMsg(message.toUtf8());
QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size());
if(!socket.waitForBytesWritten(timeout))
{
return false;
}
// wait for 'ack'
if(!socket.waitForReadyRead(timeout))
{
return false;
}
// make sure we got 'ack'
if(!(socket.read(qstrlen(ack)) == ack))
{
return false;
}
return true;
}
void LocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
if (!socket)
{
return;
}
while (socket->bytesAvailable() < (int)sizeof(quint32))
{
socket->waitForReadyRead();
}
QDataStream ds(socket);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();
do
{
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0)
{
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
delete socket;
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;
emit messageReceived(message); //### (might take a long time to return)
}

View File

@ -0,0 +1,193 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "LockedFile.h"
/*!
\class QtLockedFile
\brief The QtLockedFile class extends QFile with advisory locking
functions.
A file may be locked in read or write mode. Multiple instances of
\e QtLockedFile, created in multiple processes running on the same
machine, may have a file locked in read mode. Exactly one instance
may have it locked in write mode. A read and a write lock cannot
exist simultaneously on the same file.
The file locks are advisory. This means that nothing prevents
another process from manipulating a locked file using QFile or
file system functions offered by the OS. Serialization is only
guaranteed if all processes that access the file use
QLockedFile. Also, while holding a lock on a file, a process
must not open the same file again (through any API), or locks
can be unexpectedly lost.
The lock provided by an instance of \e QtLockedFile is released
whenever the program terminates. This is true even when the
program crashes and no destructors are called.
*/
/*! \enum QtLockedFile::LockMode
This enum describes the available lock modes.
\value ReadLock A read lock.
\value WriteLock A write lock.
\value NoLock Neither a read lock nor a write lock.
*/
/*!
Constructs an unlocked \e QtLockedFile object. This constructor
behaves in the same way as \e QFile::QFile().
\sa QFile::QFile()
*/
LockedFile::LockedFile()
: QFile()
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Constructs an unlocked QtLockedFile object with file \a name. This
constructor behaves in the same way as \e QFile::QFile(const
QString&).
\sa QFile::QFile()
*/
LockedFile::LockedFile(const QString &name)
: QFile(name)
{
#ifdef Q_OS_WIN
wmutex = 0;
rmutex = 0;
#endif
m_lock_mode = NoLock;
}
/*!
Opens the file in OpenMode \a mode.
This is identical to QFile::open(), with the one exception that the
Truncate mode flag is disallowed. Truncation would conflict with the
advisory file locking, since the file would be modified before the
write lock is obtained. If truncation is required, use resize(0)
after obtaining the write lock.
Returns true if successful; otherwise false.
\sa QFile::open(), QFile::resize()
*/
bool LockedFile::open(OpenMode mode)
{
if (mode & QIODevice::Truncate) {
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
return false;
}
return QFile::open(mode);
}
/*!
Returns \e true if this object has a in read or write lock;
otherwise returns \e false.
\sa lockMode()
*/
bool LockedFile::isLocked() const
{
return m_lock_mode != NoLock;
}
/*!
Returns the type of lock currently held by this object, or \e
QtLockedFile::NoLock.
\sa isLocked()
*/
LockedFile::LockMode LockedFile::lockMode() const
{
return m_lock_mode;
}
/*!
\fn bool QtLockedFile::lock(LockMode mode, bool block = true)
Obtains a lock of type \a mode. The file must be opened before it
can be locked.
If \a block is true, this function will block until the lock is
aquired. If \a block is false, this function returns \e false
immediately if the lock cannot be aquired.
If this object already has a lock of type \a mode, this function
returns \e true immediately. If this object has a lock of a
different type than \a mode, the lock is first released and then a
new lock is obtained.
This function returns \e true if, after it executes, the file is
locked by this object, and \e false otherwise.
\sa unlock(), isLocked(), lockMode()
*/
/*!
\fn bool QtLockedFile::unlock()
Releases a lock.
If the object has no lock, this function returns immediately.
This function returns \e true if, after it executes, the file is
not locked by this object, and \e false otherwise.
\sa lock(), isLocked(), lockMode()
*/
/*!
\fn QtLockedFile::~QtLockedFile()
Destroys the \e QtLockedFile object. If any locks were held, they
are released.
*/

View File

@ -0,0 +1,77 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#pragma once
#include <QFile>
#ifdef Q_OS_WIN
#include <QVector>
#endif
class LockedFile : public QFile
{
public:
enum LockMode { NoLock = 0, ReadLock, WriteLock };
LockedFile();
LockedFile(const QString &name);
~LockedFile();
bool open(OpenMode mode);
bool lock(LockMode mode, bool block = true);
bool unlock();
bool isLocked() const;
LockMode lockMode() const;
private:
#ifdef Q_OS_WIN
Qt::HANDLE wmutex;
Qt::HANDLE rmutex;
QVector<Qt::HANDLE> rmutexes;
QString mutexname;
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
#endif
LockMode m_lock_mode;
};

View File

@ -0,0 +1,114 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include "LockedFile.h"
bool LockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
int cmd = block ? F_SETLKW : F_SETLK;
int ret = fcntl(handle(), cmd, &fl);
if (ret == -1) {
if (errno != EINTR && errno != EAGAIN)
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = mode;
return true;
}
bool LockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
struct flock fl;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = F_UNLCK;
int ret = fcntl(handle(), F_SETLKW, &fl);
if (ret == -1) {
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
return false;
}
m_lock_mode = NoLock;
return true;
}
LockedFile::~LockedFile()
{
if (isOpen())
unlock();
}

View File

@ -0,0 +1,205 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "LockedFile.h"
#include <qt_windows.h>
#include <QFileInfo>
#define MUTEX_PREFIX "QtLockedFile mutex "
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
Qt::HANDLE LockedFile::getMutexHandle(int idx, bool doCreate)
{
if (mutexname.isEmpty()) {
QFileInfo fi(*this);
mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ fi.absoluteFilePath().toLower();
}
QString mname(mutexname);
if (idx >= 0)
mname += QString::number(idx);
Qt::HANDLE mutex;
if (doCreate) {
mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16());
if (!mutex) {
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
return 0;
}
}
else {
OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16());
if (!mutex) {
if (GetLastError() != ERROR_FILE_NOT_FOUND)
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
return 0;
}
}
return mutex;
}
bool LockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
{
Q_ASSERT(mutex);
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
switch (res) {
case WAIT_OBJECT_0:
case WAIT_ABANDONED:
return true;
break;
case WAIT_TIMEOUT:
break;
default:
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
}
return false;
}
bool LockedFile::lock(LockMode mode, bool block)
{
if (!isOpen()) {
qWarning("QtLockedFile::lock(): file is not opened");
return false;
}
if (mode == NoLock)
return unlock();
if (mode == m_lock_mode)
return true;
if (m_lock_mode != NoLock)
unlock();
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
return false;
if (!waitMutex(wmutex, block))
return false;
if (mode == ReadLock) {
int idx = 0;
for (; idx < MAX_READERS; idx++) {
rmutex = getMutexHandle(idx, false);
if (!rmutex || waitMutex(rmutex, false))
break;
CloseHandle(rmutex);
}
bool ok = true;
if (idx >= MAX_READERS) {
qWarning("QtLockedFile::lock(): too many readers");
rmutex = 0;
ok = false;
}
else if (!rmutex) {
rmutex = getMutexHandle(idx, true);
if (!rmutex || !waitMutex(rmutex, false))
ok = false;
}
if (!ok && rmutex) {
CloseHandle(rmutex);
rmutex = 0;
}
ReleaseMutex(wmutex);
if (!ok)
return false;
}
else {
Q_ASSERT(rmutexes.isEmpty());
for (int i = 0; i < MAX_READERS; i++) {
Qt::HANDLE mutex = getMutexHandle(i, false);
if (mutex)
rmutexes.append(mutex);
}
if (rmutexes.size()) {
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
TRUE, block ? INFINITE : 0);
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
if (res != WAIT_TIMEOUT)
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
unlock();
return false;
}
}
}
m_lock_mode = mode;
return true;
}
bool LockedFile::unlock()
{
if (!isOpen()) {
qWarning("QtLockedFile::unlock(): file is not opened");
return false;
}
if (!isLocked())
return true;
if (m_lock_mode == ReadLock) {
ReleaseMutex(rmutex);
CloseHandle(rmutex);
rmutex = 0;
}
else {
foreach(Qt::HANDLE mutex, rmutexes) {
ReleaseMutex(mutex);
CloseHandle(mutex);
}
rmutexes.clear();
ReleaseMutex(wmutex);
}
m_lock_mode = LockedFile::NoLock;
return true;
}
LockedFile::~LockedFile()
{
if (isOpen())
unlock();
if (wmutex)
CloseHandle(wmutex);
}