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

@ -98,6 +98,7 @@ add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
############################### Built Artifacts ###############################

View File

@ -322,7 +322,7 @@ qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
# Add executable
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown rainbow)
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown rainbow LocalPeer)
if(APPLE)
find_library(OSX_CORE_FOUNDATION CoreFoundation)

View File

@ -174,11 +174,6 @@ void InstanceWindow::closeEvent(QCloseEvent *event)
MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
emit isClosing();
event->accept();
if(m_shouldQuit)
{
// this needs to be delayed so we don't do horrible things
QMetaObject::invokeMethod(MMC, "quit", Qt::QueuedConnection);
}
}
bool InstanceWindow::saveAll()
@ -203,11 +198,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked()
// FIXME: duplicate logic between MainWindow and InstanceWindow
else if(saveAll())
{
m_launchController.reset(new LaunchController());
m_launchController->setInstance(m_instance);
m_launchController->setOnline(true);
m_launchController->setParentWidget(this);
m_launchController->start();
MMC->launch(m_instance, true, nullptr);
}
}

View File

@ -36,11 +36,6 @@ public:
QString instanceId();
void setQuitOnClose(bool shouldQuit = false)
{
m_shouldQuit = shouldQuit;
}
// save all settings and changes (prepare for launch)
bool saveAll();
@ -68,9 +63,7 @@ private:
private:
std::shared_ptr<LaunchTask> m_proc;
unique_qobject_ptr<LaunchController> m_launchController;
InstancePtr m_instance;
bool m_shouldQuit = false;
bool m_doNotSave = false;
PageContainer *m_container = nullptr;
QPushButton *m_closeButton = nullptr;

View File

@ -205,21 +205,10 @@ void LaunchController::launchInstance()
return;
}
auto mainWindow = qobject_cast<MainWindow *>(m_parentWidget);
auto instanceWindow = qobject_cast<InstanceWindow *>(m_parentWidget);
if(mainWindow)
auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
if(!console)
{
m_console = mainWindow->showInstanceWindow(m_instance);
}
else if(instanceWindow)
{
// NOOP
}
else
{
// this is used when launching directly from command line
m_console = new InstanceWindow(m_instance);
m_console->setQuitOnClose(true);
MMC->showInstanceWindow(m_instance);
}
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);

View File

@ -691,7 +691,7 @@ void MainWindow::updateToolsMenu()
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
connect(normalLaunch, &QAction::triggered, [this]()
{
launch(m_selectedInstance);
MMC->launch(m_selectedInstance);
});
launchMenu->addSeparator()->setText(tr("Profilers"));
for (auto profiler : MMC->profilers().values())
@ -707,7 +707,7 @@ void MainWindow::updateToolsMenu()
{
connect(profilerAction, &QAction::triggered, [this, profiler]()
{
launch(m_selectedInstance, true, profiler.get());
MMC->launch(m_selectedInstance, true, profiler.get());
});
}
}
@ -953,7 +953,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
{
qDebug() << "Downloading updates.";
ProgressDialog updateDlg(this);
status.rootPath = MMC->rootPath;
status.rootPath = MMC->root();
auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX");
if (!FS::ensureFilePathExists(dlPath))
@ -1004,8 +1004,8 @@ void MainWindow::waitForMinecraftVersions()
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning())
{
QEventLoop waitLoop;
waitLoop.connect(m_versionLoadTask, SIGNAL(failed(QString)), SLOT(quit()));
waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit()));
waitLoop.connect(m_versionLoadTask, &Task::failed, &waitLoop, &QEventLoop::quit);
waitLoop.connect(m_versionLoadTask, &Task::succeeded, &waitLoop, &QEventLoop::quit);
waitLoop.exec();
}
}
@ -1261,62 +1261,24 @@ void MainWindow::on_actionSettings_triggered()
update();
}
InstanceWindow *MainWindow::showInstanceWindow(InstancePtr instance, QString page)
{
if(!instance)
return nullptr;
auto id = instance->id();
InstanceWindow * window = nullptr;
auto iter = m_instanceWindows.find(id);
if(iter != m_instanceWindows.end())
{
window = *iter;
window->raise();
window->activateWindow();
}
else
{
window = new InstanceWindow(instance);
m_instanceWindows[id] = window;
connect(window, &InstanceWindow::isClosing, this, &MainWindow::on_instanceWindowClose);
}
if(!page.isEmpty())
{
window->selectPage(page);
}
return window;
}
void MainWindow::on_instanceWindowClose()
{
auto senderWindow = qobject_cast<InstanceWindow *>(QObject::sender());
if(!senderWindow)
{
return;
}
m_instanceWindows.remove(senderWindow->instanceId());
}
void MainWindow::on_actionInstanceSettings_triggered()
{
showInstanceWindow(m_selectedInstance, "settings");
MMC->showInstanceWindow(m_selectedInstance, "settings");
}
void MainWindow::on_actionEditInstNotes_triggered()
{
showInstanceWindow(m_selectedInstance, "notes");
MMC->showInstanceWindow(m_selectedInstance, "notes");
}
void MainWindow::on_actionEditInstance_triggered()
{
showInstanceWindow(m_selectedInstance);
MMC->showInstanceWindow(m_selectedInstance);
}
void MainWindow::on_actionScreenshots_triggered()
{
showInstanceWindow(m_selectedInstance, "screenshots");
MMC->showInstanceWindow(m_selectedInstance, "screenshots");
}
void MainWindow::on_actionManageAccounts_triggered()
@ -1440,14 +1402,14 @@ void MainWindow::instanceActivated(QModelIndex index)
if (!inst)
return;
launch(inst);
MMC->launch(inst);
}
void MainWindow::on_actionLaunchInstance_triggered()
{
if (m_selectedInstance)
{
launch(m_selectedInstance);
MMC->launch(m_selectedInstance);
}
}
@ -1455,33 +1417,7 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
{
if (m_selectedInstance)
{
launch(m_selectedInstance, false);
}
}
void MainWindow::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
{
if(instance->canLaunch())
{
// FIXME: duplicate logic between MainWindow and InstanceWindow
auto window = m_instanceWindows.find(instance->id());
if(window != m_instanceWindows.end())
{
if(!(*window)->saveAll())
{
return;
}
}
m_launchController.reset(new LaunchController());
m_launchController->setInstance(instance);
m_launchController->setOnline(online);
m_launchController->setParentWidget(this);
m_launchController->setProfiler(profiler);
m_launchController->start();
}
else if (instance->isRunning())
{
showInstanceWindow(instance, "console");
MMC->launch(m_selectedInstance, false);
}
}

View File

@ -37,7 +37,6 @@ class MinecraftLauncher;
class BaseProfilerFactory;
class GroupView;
class ServerStatus;
class InstanceWindow;
class MainWindow : public QMainWindow
{
@ -55,8 +54,6 @@ public:
void checkSetDefaultJava();
void checkInstancePathForProblems();
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
private slots:
void onCatToggled(bool);
@ -162,8 +159,6 @@ private slots:
*/
void downloadUpdates(GoUpdate::Status status);
void on_instanceWindowClose();
private:
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
@ -174,7 +169,6 @@ private:
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
void finalizeInstance(InstancePtr inst);
void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
private:
std::unique_ptr<Ui> ui;
@ -194,14 +188,10 @@ private:
unique_qobject_ptr<NetJob> skin_download_job;
unique_qobject_ptr<NewsChecker> m_newsChecker;
unique_qobject_ptr<NotificationChecker> m_notificationChecker;
unique_qobject_ptr<LaunchController> m_launchController;
InstancePtr m_selectedInstance;
QString m_currentInstIcon;
// managed by the application object
Task *m_versionLoadTask;
// map from instance ID to its window
QMap<QString, InstanceWindow *> m_instanceWindows;
};

View File

@ -1,5 +1,7 @@
#include "MultiMC.h"
#include "BuildConfig.h"
#include "MainWindow.h"
#include "InstanceWindow.h"
#include "pages/BasePageProvider.h"
#include "pages/global/MultiMCPage.h"
#include "pages/global/MinecraftPage.h"
@ -59,6 +61,7 @@
#include <Commandline.h>
#include <FileSystem.h>
#include <DesktopServices.h>
#include <LocalPeer.h>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@ -159,6 +162,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
return;
}
}
m_instanceIdToLaunch = args["launch"].toString();
QString origcwdPath = QDir::currentPath();
QString binPath = applicationDirPath();
@ -179,21 +183,32 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
adjustedBy += "Fallback to binary path " + dataPath;
}
instanceIdToLaunch = args["launch"].toString();
if (!FS::ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
{
// BAD STUFF. WHAT DO?
initLogger();
qCritical() << "Failed to set work path. Will exit. NOW.";
m_status = MultiMC::Failed;
return;
}
m_peerInstance = new LocalPeer(this, ApplicationId::fromPathAndVersion(dataPath, BuildConfig.printableVersionString()));
connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
if(m_peerInstance->isClient())
{
if(m_instanceIdToLaunch.isEmpty())
{
m_peerInstance->sendMessage("activate", 2000);
}
else
{
m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000);
}
quit();
return;
}
// in test mode, root path is the same as the binary path.
#ifdef Q_OS_LINUX
QDir foo(FS::PathCombine(binPath, ".."));
rootPath = foo.absolutePath();
m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
rootPath = binPath;
#elif defined(Q_OS_MAC)
@ -219,10 +234,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "Work dir : " << QDir::currentPath();
}
qDebug() << "Binary path : " << binPath;
qDebug() << "Application root path : " << rootPath;
if(!instanceIdToLaunch.isEmpty())
qDebug() << "Application root path : " << m_rootPath;
if(!m_instanceIdToLaunch.isEmpty())
{
qDebug() << "ID of instance to launch : " << instanceIdToLaunch;
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
// load settings
@ -309,7 +324,22 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
m_status = MultiMC::Initialized;
setIconTheme(settings()->get("IconTheme").toString());
setApplicationTheme(settings()->get("ApplicationTheme").toString());
if(!m_instanceIdToLaunch.isEmpty())
{
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if(inst)
{
minecraftlist();
launch(inst, true, nullptr);
return;
}
}
showMainWindow();
}
MultiMC::~MultiMC()
@ -333,6 +363,22 @@ MultiMC::~MultiMC()
#endif
}
void MultiMC::messageReceived(const QString& message)
{
if(message == "activate")
{
showMainWindow();
}
else
{
auto inst = instances()->getInstanceById(message);
if(inst)
{
launch(inst, true, nullptr);
}
}
}
#ifdef Q_OS_MAC
#include "CertWorkaround.h"
#endif
@ -1024,4 +1070,101 @@ bool MultiMC::openJsonEditor(const QString &filename)
}
}
void MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
{
if(instance->canLaunch())
{
m_launchController.reset(new LaunchController());
m_launchController->setInstance(instance);
m_launchController->setOnline(online);
m_launchController->setProfiler(profiler);
auto windowIter = m_instanceWindows.find(instance->id());
if(windowIter != m_instanceWindows.end())
{
auto window = *windowIter;
if(!window->saveAll())
{
return;
}
m_launchController->setParentWidget(window);
}
if(m_mainWindow)
{
m_launchController->setParentWidget(m_mainWindow);
}
m_launchController->start();
}
else if (instance->isRunning())
{
showInstanceWindow(instance, "console");
}
}
MainWindow * MultiMC::showMainWindow()
{
if(m_mainWindow)
{
m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized);
m_mainWindow->raise();
m_mainWindow->activateWindow();
}
else
{
m_mainWindow = new MainWindow();
m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
m_mainWindow->show();
m_mainWindow->checkSetDefaultJava();
m_mainWindow->checkInstancePathForProblems();
}
return m_mainWindow;
}
InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page)
{
if(!instance)
return nullptr;
auto id = instance->id();
InstanceWindow * window = nullptr;
auto iter = m_instanceWindows.find(id);
if(iter != m_instanceWindows.end())
{
window = *iter;
window->raise();
window->activateWindow();
}
else
{
window = new InstanceWindow(instance);
m_instanceWindows[id] = window;
connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose);
}
if(!page.isEmpty())
{
window->selectPage(page);
}
return window;
}
void MultiMC::on_windowClose()
{
auto instWindow = qobject_cast<InstanceWindow *>(QObject::sender());
if(instWindow)
{
m_instanceWindows.remove(instWindow->instanceId());
return;
}
auto mainWindow = qobject_cast<MainWindow *>(QObject::sender());
if(mainWindow)
{
m_mainWindow = nullptr;
}
// quit when there are no more windows.
if(m_instanceWindows.isEmpty() && !m_mainWindow)
{
quit();
}
}
#include "MultiMC.moc"

View File

@ -7,8 +7,14 @@
#include <QIcon>
#include <QDateTime>
#include <updater/GoUpdate.h>
class FolderInstanceProvider;
#include <BaseInstance.h>
class LaunchController;
class LocalPeer;
class InstanceWindow;
class MainWindow;
class FolderInstanceProvider;
class GenericPageProvider;
class QFile;
class MinecraftVersionList;
@ -36,8 +42,6 @@ class ITheme;
class MultiMC : public QApplication
{
// friends for the purpose of limiting access to deprecated stuff
friend class MultiMCPage;
friend class MainWindow;
Q_OBJECT
public:
enum Status
@ -51,13 +55,12 @@ public:
MultiMC(int &argc, char **argv);
virtual ~MultiMC();
// InstanceList, IconList, OneSixFTBInstance, LegacyUpdate, LegacyInstance, MCEditTool, JVisualVM, MinecraftInstance, JProfiler, BaseInstance
std::shared_ptr<SettingsObject> settings()
std::shared_ptr<SettingsObject> settings() const
{
return m_settings;
}
std::shared_ptr<GenericPageProvider> globalSettingsPages()
std::shared_ptr<GenericPageProvider> globalSettingsPages() const
{
return m_globalSettingsProvider;
}
@ -72,6 +75,7 @@ public:
void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes();
void setApplicationTheme(const QString& name);
// DownloadUpdateTask
@ -86,7 +90,6 @@ public:
std::shared_ptr<LiteLoaderVersionList> liteloaderlist();
std::shared_ptr<JavaInstallList> javalist();
// APPLICATION ONLY
std::shared_ptr<InstanceList> instances() const
{
return m_instances;
@ -102,46 +105,44 @@ public:
return m_icons;
}
// APPLICATION ONLY
std::shared_ptr<MojangAccountList> accounts() const
{
return m_accounts;
}
// APPLICATION ONLY
Status status() const
{
return m_status;
}
// APPLICATION ONLY
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const
{
return m_profilers;
}
// APPLICATION ONLY
const QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> &tools() const
{
return m_tools;
}
// APPLICATION ONLY
/// this is the root of the 'installation'. Used for automatic updates
const QString &root()
{
return m_rootPath;
}
// install updates now.
void installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations);
/*!
* Opens a json file using either a system default editor, or, if note empty, the editor
* Opens a json file using either a system default editor, or, if not empty, the editor
* specified in the settings
*/
bool openJsonEditor(const QString &filename);
protected: /* to be removed! */
// FIXME: remove. used by MainWindow to create application update tasks
/// this is the root of the 'installation'. Used for automatic updates
const QString &root()
{
return rootPath;
}
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow *showMainWindow();
void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
private slots:
/**
@ -149,6 +150,10 @@ private slots:
*/
void onExit();
void on_windowClose();
void messageReceived(const QString & message);
private:
void initLogger();
void initIcons();
@ -160,6 +165,7 @@ private:
private:
QDateTime startTime;
unique_qobject_ptr<LaunchController> m_launchController;
std::shared_ptr<QTranslator> m_qt_translator;
std::shared_ptr<QTranslator> m_mmc_translator;
std::shared_ptr<SettingsObject> m_settings;
@ -180,12 +186,19 @@ private:
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools;
QString rootPath;
QString m_rootPath;
Status m_status = MultiMC::Failed;
// used on Windows to attach the standard IO streams
bool consoleAttached = false;
// map from instance ID to its window
QMap<QString, InstanceWindow *> m_instanceWindows;
// main window, if any
MainWindow * m_mainWindow = nullptr;
LocalPeer * m_peerInstance = nullptr;
public:
QString instanceIdToLaunch;
QString m_instanceIdToLaunch;
std::unique_ptr<QFile> logFile;
};

View File

@ -4,41 +4,6 @@
#include <InstanceList.h>
#include <QDebug>
int launchMainWindow(MultiMC &app)
{
MainWindow mainWin;
mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
mainWin.show();
mainWin.checkSetDefaultJava();
mainWin.checkInstancePathForProblems();
return app.exec();
}
int launchInstance(MultiMC &app, InstancePtr inst)
{
app.minecraftlist();
LaunchController launchController;
launchController.setInstance(inst);
launchController.setOnline(true);
QMetaObject::invokeMethod(&launchController, "start", Qt::QueuedConnection);
return app.exec();
}
int main_gui(MultiMC &app)
{
app.setIconTheme(MMC->settings()->get("IconTheme").toString());
app.setApplicationTheme(MMC->settings()->get("ApplicationTheme").toString());
// show main window
auto inst = app.instances()->getInstanceById(app.instanceIdToLaunch);
if(inst)
{
return launchInstance(app, inst);
}
return launchMainWindow(app);
}
int main(int argc, char *argv[])
{
// initialize Qt
@ -59,7 +24,7 @@ int main(int argc, char *argv[])
switch (app.status())
{
case MultiMC::Initialized:
return main_gui(app);
return app.exec();
case MultiMC::Failed:
return 1;
case MultiMC::Succeeded:

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);
}

View File

@ -143,3 +143,10 @@ Available either under LGPL version 2.1 or later.
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
Public domain.
## LocalPeer
Library for making only one instance of the application run at all times.
BSD licensed, derived from [QtSingleApplication](https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication).
Changes are made to make the code more generic and useful in less usual conditions.