Update singleapplication
This commit is contained in:
188
3rdparty/singleapplication/singleapplication.cpp
vendored
188
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -35,149 +35,207 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QCoreApplication>
|
#include <QApplication>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor.
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
* Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
* @param argc
|
||||||
* @param argv
|
* @param argv
|
||||||
* @param {bool} allowSecondaryInstances
|
* @param allowSecondary Whether to enable secondary instance support
|
||||||
|
* @param options Optional flags to toggle specific behaviour
|
||||||
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
*/
|
*/
|
||||||
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||||
: app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) {
|
: app_t(argc, argv),
|
||||||
|
d_ptr(new SingleApplicationPrivate(this)) {
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
// Store the current mode of the program
|
// Store the current mode of the program
|
||||||
d->options = options;
|
d->options_ = options;
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
d->randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_->attach();
|
||||||
d->memory->attach();
|
delete d->memory_;
|
||||||
delete d->memory;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
|
|
||||||
// Create a shared memory block
|
// Create a shared memory block
|
||||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||||
// Initialize the shared memory block
|
// Initialize the shared memory block
|
||||||
d->memory->lock();
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
d->memory->unlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Attempt to attach to the memory segment
|
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
if (!d->memory->attach()) {
|
// Attempt to attach to the memory segment
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
if (!d->memory_->attach()) {
|
||||||
qCritical() << d->memory->errorString();
|
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||||
delete d;
|
abortSafely();
|
||||||
::exit(EXIT_FAILURE);
|
}
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to create block.";
|
||||||
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(d->memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
while (true) {
|
forever {
|
||||||
d->memory->lock();
|
// If the shared memory block's checksum is valid continue
|
||||||
|
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == inst->checksum) break;
|
||||||
|
|
||||||
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
// Otherwise wait for a random period and try again.
|
||||||
|
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
||||||
// Random sleep here limits the probability of a collision between two racing apps
|
if (!d->memory_->unlock()) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
qDebug() << d->memory_->errorString();
|
||||||
#else
|
}
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
d->randomSleep();
|
||||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
if (!d->memory_->lock()) {
|
||||||
#endif
|
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (inst->primary == false) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another instance can be started
|
// Check if another instance can be started
|
||||||
if (allowSecondary) {
|
if (allowSecondary) {
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = d->blockChecksum();
|
|
||||||
d->instanceNumber = inst->secondary;
|
|
||||||
d->startSecondary();
|
d->startSecondary();
|
||||||
if (d->options & Mode::SecondaryNotification) {
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
||||||
}
|
}
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
||||||
|
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
::exit(EXIT_SUCCESS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor
|
|
||||||
*/
|
|
||||||
SingleApplication::~SingleApplication() {
|
SingleApplication::~SingleApplication() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleApplication::isPrimary() {
|
bool SingleApplication::isPrimary() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
return d->server != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is secondary.
|
||||||
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleApplication::isSecondary() {
|
bool SingleApplication::isSecondary() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
return d->server == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||||
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
|
* @return Returns a unique instance id.
|
||||||
|
*/
|
||||||
quint32 SingleApplication::instanceId() {
|
quint32 SingleApplication::instanceId() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
return d->instanceNumber;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||||
|
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||||
|
* @return Returns the primary instance PID.
|
||||||
|
*/
|
||||||
qint64 SingleApplication::primaryPid() {
|
qint64 SingleApplication::primaryPid() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the primary instance is running as.
|
||||||
|
* @return Returns the username the primary instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplication::primaryUser() {
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->primaryUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the current instance is running as.
|
||||||
|
* @return Returns the username the current instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplication::currentUser() {
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
return d->getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends message to the Primary Instance.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
|
* @return true if the message was sent successfuly, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
@@ -186,10 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
|||||||
if (isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect);
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
|
||||||
|
return false;
|
||||||
|
|
||||||
d->socket->write(message);
|
d->socket_->write(message);
|
||||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||||
d->socket->flush();
|
d->socket_->flush();
|
||||||
return dataWritten;
|
return dataWritten;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the shared memory block and exits with a failure.
|
||||||
|
* This function halts program execution.
|
||||||
|
*/
|
||||||
|
void SingleApplication::abortSafely() {
|
||||||
|
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
|
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
|
delete d;
|
||||||
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
3rdparty/singleapplication/singleapplication.h
vendored
20
3rdparty/singleapplication/singleapplication.h
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,9 +42,8 @@
|
|||||||
class SingleApplicationPrivate;
|
class SingleApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleApplication class handles multipe instances of the same
|
* @brief The SingleApplication class handles multipe instances of the same Application
|
||||||
* Application
|
* @see QApplication
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
*/
|
||||||
class SingleApplication : public QApplication {
|
class SingleApplication : public QApplication {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -118,6 +117,18 @@ class SingleApplication : public QApplication {
|
|||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the username of the user running the primary instance
|
||||||
|
* @returns {QString}
|
||||||
|
*/
|
||||||
|
QString primaryUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the username of the current user
|
||||||
|
* @returns {QString}
|
||||||
|
*/
|
||||||
|
QString currentUser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
* @param {int} timeout - Timeout for connecting
|
* @param {int} timeout - Timeout for connecting
|
||||||
@@ -134,6 +145,7 @@ class SingleApplication : public QApplication {
|
|||||||
private:
|
private:
|
||||||
SingleApplicationPrivate *d_ptr;
|
SingleApplicationPrivate *d_ptr;
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
Q_DECLARE_PRIVATE(SingleApplication)
|
||||||
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||||
|
|||||||
375
3rdparty/singleapplication/singleapplication_p.cpp
vendored
375
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -44,6 +44,8 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -52,6 +54,12 @@
|
|||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
# include <QRandomGenerator>
|
||||||
|
#else
|
||||||
|
# include <QDateTime>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
@@ -61,32 +69,74 @@
|
|||||||
# include <lmcons.h>
|
# include <lmcons.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr)
|
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||||
: q_ptr(_q_ptr),
|
: q_ptr(ptr),
|
||||||
memory(nullptr),
|
memory_(nullptr),
|
||||||
socket(nullptr),
|
socket_(nullptr),
|
||||||
server(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber(-1) {}
|
instanceNumber_(-1) {}
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||||
|
|
||||||
if (socket != nullptr) {
|
if (socket_ != nullptr) {
|
||||||
socket->close();
|
socket_->close();
|
||||||
delete socket;
|
delete socket_;
|
||||||
|
socket_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory->lock();
|
if (memory_ != nullptr) {
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
memory_->lock();
|
||||||
if (server != nullptr) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
server->close();
|
if (server_ != nullptr) {
|
||||||
delete server;
|
server_->close();
|
||||||
inst->primary = false;
|
delete server_;
|
||||||
inst->primaryPid = -1;
|
inst->primary = false;
|
||||||
inst->checksum = blockChecksum();
|
inst->primaryPid = -1;
|
||||||
}
|
inst->primaryUser[0] = '\0';
|
||||||
memory->unlock();
|
inst->checksum = blockChecksum();
|
||||||
|
}
|
||||||
|
memory_->unlock();
|
||||||
|
|
||||||
|
delete memory_;
|
||||||
|
memory_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SingleApplicationPrivate::getUsername() {
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
QString username;
|
||||||
|
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
||||||
|
struct passwd *pw = getpwuid(geteuid());
|
||||||
|
if (pw) {
|
||||||
|
username = QString::fromLocal8Bit(pw->pw_name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (username.isEmpty()) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
username = qEnvironmentVariable("USER");
|
||||||
|
#else
|
||||||
|
username = QString::fromLocal8Bit(qgetenv("USER"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
wchar_t username[UNLEN + 1];
|
||||||
|
// Specifies size of the buffer on input
|
||||||
|
DWORD usernameLength = UNLEN + 1;
|
||||||
|
if (GetUserNameW(username, &usernameLength)) {
|
||||||
|
return QString::fromWCharArray(username);
|
||||||
|
}
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
return qEnvironmentVariable("USERNAME");
|
||||||
|
#else
|
||||||
|
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
delete memory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName() {
|
void SingleApplicationPrivate::genBlockServerName() {
|
||||||
@@ -97,11 +147,11 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||||
|
|
||||||
if (!(options & SingleApplication::Mode::ExcludeAppVersion)) {
|
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
||||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
@@ -110,153 +160,162 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
// User level block requires a user specific data in the hash
|
||||||
if (options & SingleApplication::Mode::User) {
|
if (options_ & SingleApplication::Mode::User) {
|
||||||
#ifdef Q_OS_UNIX
|
appData.addData(getUsername().toUtf8());
|
||||||
QByteArray username;
|
|
||||||
# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
|
||||||
struct passwd *pw = getpwuid(geteuid());
|
|
||||||
if (pw) {
|
|
||||||
username = pw->pw_name;
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
if (username.isEmpty()) username = qgetenv("USER");
|
|
||||||
appData.addData(username);
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if (GetUserNameW(username, &usernameLength)) {
|
|
||||||
appData.addData(QString::fromWCharArray(username).toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appData.addData(qgetenv("USERNAME"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
||||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() {
|
void SingleApplicationPrivate::initializeMemoryBlock() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
inst->primary = false;
|
||||||
inst->secondary = 0;
|
inst->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
inst->primaryPid = -1;
|
||||||
|
inst->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary() {
|
void SingleApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer(blockServerName);
|
|
||||||
server = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the
|
|
||||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if (options & SingleApplication::Mode::User) {
|
|
||||||
server->setSocketOptions(QLocalServer::UserAccessOption);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
server->setSocketOptions(QLocalServer::WorldAccessOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
server->listen(blockServerName);
|
|
||||||
QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
inst->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
inst->primaryPid = q->applicationPid();
|
||||||
|
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
instanceNumber_ = 0;
|
||||||
|
// Successful creation means that no main process exists
|
||||||
|
// So we start a QLocalServer to listen for connections
|
||||||
|
QLocalServer::removeServer(blockServerName_);
|
||||||
|
server_ = new QLocalServer();
|
||||||
|
|
||||||
|
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||||
|
if (options_ & SingleApplication::Mode::User) {
|
||||||
|
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_->listen(blockServerName_);
|
||||||
|
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
||||||
|
|
||||||
instanceNumber = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {}
|
void SingleApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
void SingleApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
|
inst->secondary += 1;
|
||||||
|
inst->checksum = blockChecksum();
|
||||||
|
instanceNumber_ = inst->secondary;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||||
if (socket == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
socket = new QLocalSocket();
|
socket_ = new QLocalSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already connected - we are done;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
if (socket->state() == QLocalSocket::ConnectedState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If not connect
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
|
||||||
socket->state() == QLocalSocket::ClosingState) {
|
|
||||||
socket->connectToServer(blockServerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for being connected
|
forever {
|
||||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
randomSleep();
|
||||||
socket->waitForConnected(msecs);
|
|
||||||
|
if (socket_->state() != QLocalSocket::ConnectingState)
|
||||||
|
socket_->connectToServer(blockServerName_);
|
||||||
|
|
||||||
|
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||||
|
socket_->waitForConnected(timeout - time.elapsed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If connected break out of the loop
|
||||||
|
if (socket_->state() == QLocalSocket::ConnectedState) break;
|
||||||
|
|
||||||
|
// If elapsed time since start is longer than the method timeout return
|
||||||
|
if (time.elapsed() >= timeout) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
// Initialisation message according to the SingleApplication protocol
|
||||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
QByteArray initMsg;
|
||||||
// Notify the parent that a new instance had been started;
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
QByteArray initMsg;
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
writeStream << blockServerName_.toLatin1();
|
||||||
|
writeStream << static_cast<quint8>(connectionType);
|
||||||
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber;
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(initMsg);
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
#endif
|
#endif
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
writeStream << checksum;
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
// The header indicates the message length that follows
|
||||||
|
QByteArray header;
|
||||||
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
headerStream << static_cast<quint64>(initMsg.length());
|
||||||
|
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
socket_->write(header);
|
||||||
|
socket_->write(initMsg);
|
||||||
|
socket_->flush();
|
||||||
|
if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
socket->write(header);
|
|
||||||
socket->write(initMsg);
|
|
||||||
socket->flush();
|
|
||||||
socket->waitForBytesWritten(msecs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() {
|
quint16 SingleApplicationPrivate::blockChecksum() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum));
|
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() {
|
qint64 SingleApplicationPrivate::primaryPid() {
|
||||||
|
|
||||||
qint64 pid;
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
memory->lock();
|
qint64 pid = inst->primaryPid;
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
memory_->unlock();
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SingleApplicationPrivate::primaryUser() {
|
||||||
|
|
||||||
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
QByteArray username = inst->primaryUser;
|
||||||
|
memory_->unlock();
|
||||||
|
|
||||||
|
return QString::fromUtf8(username);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,43 +323,41 @@ qint64 SingleApplicationPrivate::primaryPid() {
|
|||||||
*/
|
*/
|
||||||
void SingleApplicationPrivate::slotConnectionEstablished() {
|
void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
connectionMap_.remove(nextConnSocket);
|
||||||
connectionMap.remove(nextConnSocket);
|
nextConnSocket->deleteLater();
|
||||||
nextConnSocket->deleteLater();
|
});
|
||||||
});
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||||
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
|
switch (info.stage) {
|
||||||
|
case StageHeader:
|
||||||
|
readInitMessageHeader(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageBody:
|
||||||
|
readInitMessageBody(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageConnected:
|
||||||
|
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
|
||||||
[nextConnSocket, this]() {
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
switch (info.stage) {
|
|
||||||
case StageHeader:
|
|
||||||
readInitMessageHeader(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnected:
|
|
||||||
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,30 +366,30 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
QDataStream headerStream(sock);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
// Read the header to know the message length
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = StageBody;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||||
readInitMessageBody(sock);
|
readInitMessageBody(sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -340,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->read(info.msgLen);
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
readStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// server name
|
// server name
|
||||||
QByteArray latin1Name;
|
QByteArray latin1Name;
|
||||||
@@ -362,12 +418,12 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
readStream >> msgChecksum;
|
readStream >> msgChecksum;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes);
|
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||||
#else
|
#else
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum;
|
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
sock->close();
|
sock->close();
|
||||||
@@ -377,23 +433,38 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnected;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
Q_EMIT q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
if (sock->bytesAvailable() > 0) {
|
||||||
Q_EMIT this->slotDataAvailable(sock, instanceId);
|
Q_EMIT this->slotDataAvailable(sock, instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0)
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::randomSleep() {
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
|
||||||
|
#else
|
||||||
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
|
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
3rdparty/singleapplication/singleapplication_p.h
vendored
30
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2016
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
|||||||
bool primary;
|
bool primary;
|
||||||
quint32 secondary;
|
quint32 secondary;
|
||||||
qint64 primaryPid;
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
quint16 checksum;
|
quint16 checksum;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
|||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
class SingleApplicationPrivate : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
@@ -74,32 +77,35 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *_q_ptr);
|
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||||
~SingleApplicationPrivate() override;
|
~SingleApplicationPrivate() override;
|
||||||
|
|
||||||
|
QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock();
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum();
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
QString primaryUser();
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
void readInitMessageHeader(QLocalSocket *socket);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplication *q_ptr;
|
||||||
QSharedMemory *memory;
|
QSharedMemory *memory_;
|
||||||
QLocalSocket *socket;
|
QLocalSocket *socket_;
|
||||||
QLocalServer *server;
|
QLocalServer *server_;
|
||||||
quint32 instanceNumber;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName;
|
QString blockServerName_;
|
||||||
SingleApplication::Options options;
|
SingleApplication::Options options_;
|
||||||
QMap<QLocalSocket *, ConnectionInfo> connectionMap;
|
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
void slotDataAvailable(QLocalSocket *, const quint32);
|
void slotDataAvailable(QLocalSocket*, const quint32);
|
||||||
void slotClientConnectionClosed(QLocalSocket *, const quint32);
|
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_P_H
|
#endif // SINGLEAPPLICATION_P_H
|
||||||
|
|||||||
186
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
186
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,142 +42,200 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
* if another instance already exists
|
|
||||||
* @param argc
|
* @param argc
|
||||||
* @param argv
|
* @param argv
|
||||||
* @param {bool} allowSecondaryInstances
|
* @param allowSecondary Whether to enable secondary instance support
|
||||||
|
* @param options Optional flags to toggle specific behaviour
|
||||||
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
*/
|
*/
|
||||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||||
: app_t(argc, argv), d_ptr(new SingleCoreApplicationPrivate(this)) {
|
: app_t(argc, argv),
|
||||||
|
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
|
|
||||||
// Store the current mode of the program
|
// Store the current mode of the program
|
||||||
d->options = options;
|
d->options_ = options;
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
d->randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_->attach();
|
||||||
d->memory->attach();
|
delete d->memory_;
|
||||||
delete d->memory;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
|
|
||||||
// Create a shared memory block
|
// Create a shared memory block
|
||||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||||
// Initialize the shared memory block
|
// Initialize the shared memory block
|
||||||
d->memory->lock();
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
d->memory->unlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Attempt to attach to the memory segment
|
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
if (!d->memory->attach()) {
|
// Attempt to attach to the memory segment
|
||||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
if (!d->memory_->attach()) {
|
||||||
qCritical() << d->memory->errorString();
|
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
||||||
delete d;
|
abortSafely();
|
||||||
::exit(EXIT_FAILURE);
|
}
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleCoreApplication: Unable to create block.";
|
||||||
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(d->memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
while (true) {
|
forever {
|
||||||
d->memory->lock();
|
// If the shared memory block's checksum is valid continue
|
||||||
|
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == inst->checksum) break;
|
||||||
|
|
||||||
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
// Otherwise wait for a random period and try again.
|
||||||
|
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
||||||
// Random sleep here limits the probability of a collision between two racing apps
|
if (!d->memory_->unlock()) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
qDebug() << d->memory_->errorString();
|
||||||
#else
|
}
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
d->randomSleep();
|
||||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
if (!d->memory_->lock()) {
|
||||||
#endif
|
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (inst->primary == false) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another instance can be started
|
// Check if another instance can be started
|
||||||
if (allowSecondary) {
|
if (allowSecondary) {
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = d->blockChecksum();
|
|
||||||
d->instanceNumber = inst->secondary;
|
|
||||||
d->startSecondary();
|
d->startSecondary();
|
||||||
if (d->options & Mode::SecondaryNotification) {
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
||||||
}
|
}
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
||||||
|
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
::exit(EXIT_SUCCESS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor
|
|
||||||
*/
|
|
||||||
SingleCoreApplication::~SingleCoreApplication() {
|
SingleCoreApplication::~SingleCoreApplication() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleCoreApplication::isPrimary() {
|
bool SingleCoreApplication::isPrimary() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
return d->server != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is secondary.
|
||||||
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleCoreApplication::isSecondary() {
|
bool SingleCoreApplication::isSecondary() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
return d->server == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||||
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
|
* @return Returns a unique instance id.
|
||||||
|
*/
|
||||||
quint32 SingleCoreApplication::instanceId() {
|
quint32 SingleCoreApplication::instanceId() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
return d->instanceNumber;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||||
|
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
||||||
|
* @return Returns the primary instance PID.
|
||||||
|
*/
|
||||||
qint64 SingleCoreApplication::primaryPid() {
|
qint64 SingleCoreApplication::primaryPid() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the primary instance is running as.
|
||||||
|
* @return Returns the username the primary instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleCoreApplication::primaryUser() {
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
return d->primaryUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the current instance is running as.
|
||||||
|
* @return Returns the username the current instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleCoreApplication::currentUser() {
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
return d->getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends message to the Primary Instance.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
|
* @return true if the message was sent successfuly, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
@@ -186,10 +244,26 @@ bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
|||||||
if (isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect);
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
|
||||||
|
return false;
|
||||||
|
|
||||||
d->socket->write(message);
|
d->socket_->write(message);
|
||||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||||
d->socket->flush();
|
d->socket_->flush();
|
||||||
return dataWritten;
|
return dataWritten;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the shared memory block and exits with a failure.
|
||||||
|
* This function halts program execution.
|
||||||
|
*/
|
||||||
|
void SingleCoreApplication::abortSafely() {
|
||||||
|
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
|
||||||
|
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
|
delete d;
|
||||||
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,8 +42,7 @@
|
|||||||
class SingleCoreApplicationPrivate;
|
class SingleCoreApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleCoreApplication class handles multiple instances of the same
|
* @brief The SingleCoreApplication class handles multipe instances of the same Application
|
||||||
* Application
|
|
||||||
* @see QCoreApplication
|
* @see QCoreApplication
|
||||||
*/
|
*/
|
||||||
class SingleCoreApplication : public QCoreApplication {
|
class SingleCoreApplication : public QCoreApplication {
|
||||||
@@ -89,9 +88,8 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
* operations. It does not guarantee that the SingleCoreApplication
|
* operations. It does not guarantee that the SingleCoreApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialisation will be completed in given time, though is a good hint.
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
|
||||||
*/
|
*/
|
||||||
explicit SingleCoreApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
|
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
~SingleCoreApplication() override;
|
~SingleCoreApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +116,18 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the username of the user running the primary instance
|
||||||
|
* @returns {QString}
|
||||||
|
*/
|
||||||
|
QString primaryUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the username of the current user
|
||||||
|
* @returns {QString}
|
||||||
|
*/
|
||||||
|
QString currentUser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
* @param {int} timeout - Timeout for connecting
|
* @param {int} timeout - Timeout for connecting
|
||||||
@@ -134,6 +144,7 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
private:
|
private:
|
||||||
SingleCoreApplicationPrivate *d_ptr;
|
SingleCoreApplicationPrivate *d_ptr;
|
||||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||||
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2018
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -44,6 +44,8 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -52,6 +54,12 @@
|
|||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
# include <QRandomGenerator>
|
||||||
|
#else
|
||||||
|
# include <QDateTime>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
@@ -61,32 +69,74 @@
|
|||||||
# include <lmcons.h>
|
# include <lmcons.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr)
|
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||||
: q_ptr(_q_ptr),
|
: q_ptr(ptr),
|
||||||
memory(nullptr),
|
memory_(nullptr),
|
||||||
socket(nullptr),
|
socket_(nullptr),
|
||||||
server(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber(-1) {}
|
instanceNumber_(-1) {}
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||||
|
|
||||||
if (socket != nullptr) {
|
if (socket_ != nullptr) {
|
||||||
socket->close();
|
socket_->close();
|
||||||
delete socket;
|
delete socket_;
|
||||||
|
socket_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory->lock();
|
if (memory_ != nullptr) {
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
memory_->lock();
|
||||||
if (server != nullptr) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
server->close();
|
if (server_ != nullptr) {
|
||||||
delete server;
|
server_->close();
|
||||||
inst->primary = false;
|
delete server_;
|
||||||
inst->primaryPid = -1;
|
inst->primary = false;
|
||||||
inst->checksum = blockChecksum();
|
inst->primaryPid = -1;
|
||||||
}
|
inst->primaryUser[0] = '\0';
|
||||||
memory->unlock();
|
inst->checksum = blockChecksum();
|
||||||
|
}
|
||||||
|
memory_->unlock();
|
||||||
|
|
||||||
|
delete memory_;
|
||||||
|
memory_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SingleCoreApplicationPrivate::getUsername() {
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
QString username;
|
||||||
|
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
||||||
|
struct passwd *pw = getpwuid(geteuid());
|
||||||
|
if (pw) {
|
||||||
|
username = QString::fromLocal8Bit(pw->pw_name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (username.isEmpty()) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
username = qEnvironmentVariable("USER");
|
||||||
|
#else
|
||||||
|
username = QString::fromLocal8Bit(qgetenv("USER"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
wchar_t username[UNLEN + 1];
|
||||||
|
// Specifies size of the buffer on input
|
||||||
|
DWORD usernameLength = UNLEN + 1;
|
||||||
|
if (GetUserNameW(username, &usernameLength)) {
|
||||||
|
return QString::fromWCharArray(username);
|
||||||
|
}
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
return qEnvironmentVariable("USERNAME");
|
||||||
|
#else
|
||||||
|
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
delete memory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||||
@@ -97,11 +147,11 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
|||||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
||||||
|
|
||||||
if (!(options & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
@@ -110,153 +160,162 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
// User level block requires a user specific data in the hash
|
||||||
if (options & SingleCoreApplication::Mode::User) {
|
if (options_ & SingleCoreApplication::Mode::User) {
|
||||||
#ifdef Q_OS_UNIX
|
appData.addData(getUsername().toUtf8());
|
||||||
QByteArray username;
|
|
||||||
# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
|
||||||
struct passwd *pw = getpwuid(geteuid());
|
|
||||||
if (pw) {
|
|
||||||
username = pw->pw_name;
|
|
||||||
}
|
|
||||||
# endif
|
|
||||||
if (username.isEmpty()) username = qgetenv("USER");
|
|
||||||
appData.addData(username);
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if (GetUserNameW(username, &usernameLength)) {
|
|
||||||
appData.addData(QString::fromWCharArray(username).toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appData.addData(qgetenv("USERNAME"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
||||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
|
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
inst->primary = false;
|
||||||
inst->secondary = 0;
|
inst->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
inst->primaryPid = -1;
|
||||||
|
inst->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startPrimary() {
|
void SingleCoreApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer(blockServerName);
|
|
||||||
server = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the
|
|
||||||
// SingleCoreApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if (options & SingleCoreApplication::Mode::User) {
|
|
||||||
server->setSocketOptions(QLocalServer::UserAccessOption);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
server->setSocketOptions(QLocalServer::WorldAccessOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
server->listen(blockServerName);
|
|
||||||
QObject::connect(server, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
inst->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
inst->primaryPid = q->applicationPid();
|
||||||
|
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
instanceNumber_ = 0;
|
||||||
|
// Successful creation means that no main process exists
|
||||||
|
// So we start a QLocalServer to listen for connections
|
||||||
|
QLocalServer::removeServer(blockServerName_);
|
||||||
|
server_ = new QLocalServer();
|
||||||
|
|
||||||
|
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
|
||||||
|
if (options_ & SingleCoreApplication::Mode::User) {
|
||||||
|
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_->listen(blockServerName_);
|
||||||
|
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
|
||||||
|
|
||||||
instanceNumber = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startSecondary() {}
|
void SingleCoreApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
|
inst->secondary += 1;
|
||||||
|
inst->checksum = blockChecksum();
|
||||||
|
instanceNumber_ = inst->secondary;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||||
if (socket == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
socket = new QLocalSocket();
|
socket_ = new QLocalSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already connected - we are done;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
if (socket->state() == QLocalSocket::ConnectedState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If not connect
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
|
||||||
socket->state() == QLocalSocket::ClosingState) {
|
|
||||||
socket->connectToServer(blockServerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for being connected
|
forever {
|
||||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
randomSleep();
|
||||||
socket->waitForConnected(msecs);
|
|
||||||
|
if (socket_->state() != QLocalSocket::ConnectingState)
|
||||||
|
socket_->connectToServer(blockServerName_);
|
||||||
|
|
||||||
|
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||||
|
socket_->waitForConnected(timeout - time.elapsed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If connected break out of the loop
|
||||||
|
if (socket_->state() == QLocalSocket::ConnectedState) break;
|
||||||
|
|
||||||
|
// If elapsed time since start is longer than the method timeout return
|
||||||
|
if (time.elapsed() >= timeout) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialisation message according to the SingleCoreApplication protocol
|
// Initialisation message according to the SingleCoreApplication protocol
|
||||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
QByteArray initMsg;
|
||||||
// Notify the parent that a new instance had been started;
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
QByteArray initMsg;
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
writeStream << blockServerName_.toLatin1();
|
||||||
|
writeStream << static_cast<quint8>(connectionType);
|
||||||
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber;
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(initMsg);
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
#endif
|
#endif
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
writeStream << checksum;
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
// The header indicates the message length that follows
|
||||||
|
QByteArray header;
|
||||||
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
headerStream << static_cast<quint64>(initMsg.length());
|
||||||
|
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
socket_->write(header);
|
||||||
|
socket_->write(initMsg);
|
||||||
|
socket_->flush();
|
||||||
|
if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
socket->write(header);
|
|
||||||
socket->write(initMsg);
|
|
||||||
socket->flush();
|
|
||||||
socket->waitForBytesWritten(msecs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum));
|
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleCoreApplicationPrivate::primaryPid() {
|
qint64 SingleCoreApplicationPrivate::primaryPid() {
|
||||||
|
|
||||||
qint64 pid;
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
memory->lock();
|
qint64 pid = inst->primaryPid;
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data());
|
memory_->unlock();
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SingleCoreApplicationPrivate::primaryUser() {
|
||||||
|
|
||||||
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
QByteArray username = inst->primaryUser;
|
||||||
|
memory_->unlock();
|
||||||
|
|
||||||
|
return QString::fromUtf8(username);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,43 +323,41 @@ qint64 SingleCoreApplicationPrivate::primaryPid() {
|
|||||||
*/
|
*/
|
||||||
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
connectionMap_.remove(nextConnSocket);
|
||||||
connectionMap.remove(nextConnSocket);
|
nextConnSocket->deleteLater();
|
||||||
nextConnSocket->deleteLater();
|
});
|
||||||
});
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||||
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
|
switch (info.stage) {
|
||||||
|
case StageHeader:
|
||||||
|
readInitMessageHeader(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageBody:
|
||||||
|
readInitMessageBody(nextConnSocket);
|
||||||
|
break;
|
||||||
|
case StageConnected:
|
||||||
|
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
|
||||||
[nextConnSocket, this]() {
|
|
||||||
auto &info = connectionMap[nextConnSocket];
|
|
||||||
switch (info.stage) {
|
|
||||||
case StageHeader:
|
|
||||||
readInitMessageHeader(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnected:
|
|
||||||
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,30 +366,30 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
QDataStream headerStream(sock);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
// Read the header to know the message length
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = StageBody;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||||
readInitMessageBody(sock);
|
readInitMessageBody(sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -340,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->read(info.msgLen);
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
readStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// server name
|
// server name
|
||||||
QByteArray latin1Name;
|
QByteArray latin1Name;
|
||||||
@@ -362,12 +418,12 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
readStream >> msgChecksum;
|
readStream >> msgChecksum;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes);
|
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||||
#else
|
#else
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum;
|
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
sock->close();
|
sock->close();
|
||||||
@@ -377,23 +433,38 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnected;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleCoreApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
Q_EMIT q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
if (sock->bytesAvailable() > 0) {
|
||||||
Q_EMIT this->slotDataAvailable(sock, instanceId);
|
Q_EMIT this->slotDataAvailable(sock, instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0)
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleCoreApplicationPrivate::randomSleep() {
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
|
||||||
|
#else
|
||||||
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
|
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2016
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -31,11 +31,12 @@
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef SINGLECOREAPPLICATION_P_H
|
#ifndef SINGLEAPPLICATION_P_H
|
||||||
#define SINGLECOREAPPLICATION_P_H
|
#define SINGLEAPPLICATION_P_H
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
|||||||
bool primary;
|
bool primary;
|
||||||
quint32 secondary;
|
quint32 secondary;
|
||||||
qint64 primaryPid;
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
quint16 checksum;
|
quint16 checksum;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
|||||||
|
|
||||||
class SingleCoreApplicationPrivate : public QObject {
|
class SingleCoreApplicationPrivate : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
@@ -74,32 +77,35 @@ class SingleCoreApplicationPrivate : public QObject {
|
|||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||||
|
|
||||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr);
|
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
||||||
~SingleCoreApplicationPrivate() override;
|
~SingleCoreApplicationPrivate() override;
|
||||||
|
|
||||||
|
QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock();
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum();
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
QString primaryUser();
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
void readInitMessageHeader(QLocalSocket *socket);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void randomSleep();
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
SingleCoreApplication *q_ptr;
|
||||||
QSharedMemory *memory;
|
QSharedMemory *memory_;
|
||||||
QLocalSocket *socket;
|
QLocalSocket *socket_;
|
||||||
QLocalServer *server;
|
QLocalServer *server_;
|
||||||
quint32 instanceNumber;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName;
|
QString blockServerName_;
|
||||||
SingleCoreApplication::Options options;
|
SingleCoreApplication::Options options_;
|
||||||
QMap<QLocalSocket *, ConnectionInfo> connectionMap;
|
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
void slotDataAvailable(QLocalSocket *, const quint32);
|
void slotDataAvailable(QLocalSocket*, const quint32);
|
||||||
void slotClientConnectionClosed(QLocalSocket *, const quint32);
|
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_P_H
|
#endif // SINGLEAPPLICATION_P_H
|
||||||
|
|||||||
Reference in New Issue
Block a user