From 412855ae3d967ff81a383688397c4d9448a4ee15 Mon Sep 17 00:00:00 2001
From: Petr Mrázek <peterix@gmail.com>
Date: Sun, 30 Oct 2016 02:37:38 +0100
Subject: NOISSUE refactor window management and launch, make MultiMC a single
 instance application.

---
 libraries/LocalPeer/CMakeLists.txt          |  29 ++++
 libraries/LocalPeer/include/LocalPeer.h     |  97 ++++++++++++
 libraries/LocalPeer/src/LocalPeer.cpp       | 236 ++++++++++++++++++++++++++++
 libraries/LocalPeer/src/LockedFile.cpp      | 193 +++++++++++++++++++++++
 libraries/LocalPeer/src/LockedFile.h        |  77 +++++++++
 libraries/LocalPeer/src/LockedFile_unix.cpp | 114 ++++++++++++++
 libraries/LocalPeer/src/LockedFile_win.cpp  | 205 ++++++++++++++++++++++++
 7 files changed, 951 insertions(+)
 create mode 100644 libraries/LocalPeer/CMakeLists.txt
 create mode 100644 libraries/LocalPeer/include/LocalPeer.h
 create mode 100644 libraries/LocalPeer/src/LocalPeer.cpp
 create mode 100644 libraries/LocalPeer/src/LockedFile.cpp
 create mode 100644 libraries/LocalPeer/src/LockedFile.h
 create mode 100644 libraries/LocalPeer/src/LockedFile_unix.cpp
 create mode 100644 libraries/LocalPeer/src/LockedFile_win.cpp

(limited to 'libraries/LocalPeer')

diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt
new file mode 100644
index 00000000..99e3fe4d
--- /dev/null
+++ b/libraries/LocalPeer/CMakeLists.txt
@@ -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)
diff --git a/libraries/LocalPeer/include/LocalPeer.h b/libraries/LocalPeer/include/LocalPeer.h
new file mode 100644
index 00000000..940bfcbd
--- /dev/null
+++ b/libraries/LocalPeer/include/LocalPeer.h
@@ -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;
+};
diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp
new file mode 100644
index 00000000..db0c73e5
--- /dev/null
+++ b/libraries/LocalPeer/src/LocalPeer.cpp
@@ -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)
+}
diff --git a/libraries/LocalPeer/src/LockedFile.cpp b/libraries/LocalPeer/src/LockedFile.cpp
new file mode 100644
index 00000000..a4951bfe
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile.cpp
@@ -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.
+*/
diff --git a/libraries/LocalPeer/src/LockedFile.h b/libraries/LocalPeer/src/LockedFile.h
new file mode 100644
index 00000000..8c178250
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile.h
@@ -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;
+};
diff --git a/libraries/LocalPeer/src/LockedFile_unix.cpp b/libraries/LocalPeer/src/LockedFile_unix.cpp
new file mode 100644
index 00000000..4b68916c
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile_unix.cpp
@@ -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();
+}
diff --git a/libraries/LocalPeer/src/LockedFile_win.cpp b/libraries/LocalPeer/src/LockedFile_win.cpp
new file mode 100644
index 00000000..3d7d5553
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile_win.cpp
@@ -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);
+}
-- 
cgit