Update SingleApplication
This commit is contained in:
parent
79f0c494fa
commit
1dd4eb05f2
|
@ -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
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
SingleApplication
|
SingleApplication
|
||||||
=================
|
=================
|
||||||
|
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
|
||||||
|
|
||||||
This is a replacement of the QtSingleApplication for `Qt5`.
|
This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
|
||||||
|
|
||||||
Keeps the Primary Instance of your Application and kills each subsequent
|
Keeps the Primary Instance of your Application and kills each subsequent
|
||||||
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
||||||
|
@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
|
||||||
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
||||||
classes.
|
classes.
|
||||||
|
|
||||||
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
|
||||||
instance of your Application is your Primary Instance. It would check if the
|
|
||||||
shared memory block exists and if not it will start a `QLocalServer` and listen
|
|
||||||
for connections. Each subsequent instance of your application would check if the
|
|
||||||
shared memory block exists and if it does, it will connect to the QLocalServer
|
|
||||||
to notify the primary instance that a new instance had been started, after which
|
|
||||||
it would terminate with status code `0`. In the Primary Instance
|
|
||||||
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
|
||||||
that a new instance had been started.
|
|
||||||
|
|
||||||
The library uses `stdlib` to terminate the program with the `exit()` function.
|
|
||||||
|
|
||||||
You can use the library as if you use any other `QCoreApplication` derived
|
You can use the library as if you use any other `QCoreApplication` derived
|
||||||
class:
|
class:
|
||||||
|
|
||||||
|
@ -43,24 +32,49 @@ int main( int argc, char* argv[] )
|
||||||
```
|
```
|
||||||
|
|
||||||
To include the library files I would recommend that you add it as a git
|
To include the library files I would recommend that you add it as a git
|
||||||
submodule to your project and include it's contents with a `.pri` file. Here is
|
submodule to your project. Here is how:
|
||||||
how:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
|
git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication
|
||||||
```
|
```
|
||||||
|
|
||||||
Then include the `singleapplication.pri` file in your `.pro` project file. Also
|
**Qmake:**
|
||||||
don't forget to specify which `QCoreApplication` class your app is using if it
|
|
||||||
is not `QCoreApplication`.
|
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||||
|
|
||||||
```qmake
|
```qmake
|
||||||
include(singleapplication/singleapplication.pri)
|
include(singleapplication/singleapplication.pri)
|
||||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
DEFINES += QAPPLICATION_CLASS=QApplication
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**CMake:**
|
||||||
|
|
||||||
|
Then include the subdirectory in your `CMakeLists.txt` project file.
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||||
|
add_subdirectory(src/third-party/singleapplication)
|
||||||
|
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
||||||
|
instance of your Application is your Primary Instance. It would check if the
|
||||||
|
shared memory block exists and if not it will start a `QLocalServer` and listen
|
||||||
|
for connections. Each subsequent instance of your application would check if the
|
||||||
|
shared memory block exists and if it does, it will connect to the QLocalServer
|
||||||
|
to notify the primary instance that a new instance had been started, after which
|
||||||
|
it would terminate with status code `0`. In the Primary Instance
|
||||||
|
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
||||||
|
that a new instance had been started.
|
||||||
|
|
||||||
|
The library uses `stdlib` to terminate the program with the `exit()` function.
|
||||||
|
|
||||||
|
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
||||||
|
is not `QCoreApplication` as in examples above.
|
||||||
|
|
||||||
The `Instance Started` signal
|
The `Instance Started` signal
|
||||||
------------------------
|
-----------------------------
|
||||||
|
|
||||||
The SingleApplication class implements a `instanceStarted()` signal. You can
|
The SingleApplication class implements a `instanceStarted()` signal. You can
|
||||||
bind to that signal to raise your application's window when a new instance had
|
bind to that signal to raise your application's window when a new instance had
|
||||||
|
@ -125,13 +139,22 @@ app.isSecondary();
|
||||||
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
||||||
will replace the Primary one even if the Secondary flag has been set.*
|
will replace the Primary one even if the Secondary flag has been set.*
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
There are three examples provided in this repository:
|
||||||
|
|
||||||
|
* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
|
||||||
|
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
|
||||||
|
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
|
||||||
|
|
||||||
API
|
API
|
||||||
---
|
---
|
||||||
|
|
||||||
### Members
|
### Members
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
|
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on whether `allowSecondary` is set, this constructor may terminate
|
Depending on whether `allowSecondary` is set, this constructor may terminate
|
||||||
|
@ -140,7 +163,7 @@ can be specified to set whether the SingleApplication block should work
|
||||||
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
||||||
used to notify the primary instance whenever a secondary instance had been
|
used to notify the primary instance whenever a secondary instance had been
|
||||||
started (disabled by default). `timeout` specifies the maximum time in
|
started (disabled by default). `timeout` specifies the maximum time in
|
||||||
milliseconds to wait for blocking operations.
|
milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
|
||||||
|
|
||||||
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
||||||
recognizes.*
|
recognizes.*
|
||||||
|
@ -159,7 +182,8 @@ bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
|
||||||
```
|
```
|
||||||
|
|
||||||
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
||||||
in milliseconds for blocking functions
|
in milliseconds for blocking functions. Returns `true` if the message has been sent
|
||||||
|
successfully. If the message can't be sent or the function timeouts - returns `false`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -192,6 +216,22 @@ qint64 SingleApplication::primaryPid()
|
||||||
|
|
||||||
Returns the process ID (PID) of the primary instance.
|
Returns the process ID (PID) of the primary instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
QString SingleApplication::primaryUser()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the username the primary instance is running as.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
QString SingleApplication::currentUser()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the username the current instance is running as.
|
||||||
|
|
||||||
### Signals
|
### Signals
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
* @param options Optional flags to toggle specific behaviour
|
* @param options Optional flags to toggle specific behaviour
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
* @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[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
: app_t(argc, argv),
|
: app_t(argc, argv),
|
||||||
d_ptr(new SingleApplicationPrivate(this)) {
|
d_ptr(new SingleApplicationPrivate(this)) {
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
d->randomSleep();
|
SingleApplicationPrivate::randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
|
@ -106,14 +106,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
InstancesInfo *instance = 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
|
||||||
forever {
|
forever {
|
||||||
// If the shared memory block's checksum is valid continue
|
// If the shared memory block's checksum is valid continue
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == instance->checksum) break;
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
|
@ -127,14 +127,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||||
qDebug() << d->memory_->errorString();
|
qDebug() << d->memory_->errorString();
|
||||||
}
|
}
|
||||||
d->randomSleep();
|
SingleApplicationPrivate::randomSleep();
|
||||||
if (!d->memory_->lock()) {
|
if (!d->memory_->lock()) {
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||||
abortSafely();
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inst->primary) {
|
if (!instance->primary) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
if (!d->memory_->unlock()) {
|
if (!d->memory_->unlock()) {
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||||
|
@ -178,8 +178,8 @@ SingleApplication::~SingleApplication() {
|
||||||
* Checks if the current application instance is primary.
|
* Checks if the current application instance is primary.
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isPrimary() {
|
bool SingleApplication::isPrimary() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->server_ != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,8 +187,8 @@ bool SingleApplication::isPrimary() {
|
||||||
* Checks if the current application instance is secondary.
|
* Checks if the current application instance is secondary.
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isSecondary() {
|
bool SingleApplication::isSecondary() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->server_ == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,8 +197,8 @@ bool SingleApplication::isSecondary() {
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
* @return Returns a unique instance id.
|
* @return Returns a unique instance id.
|
||||||
*/
|
*/
|
||||||
quint32 SingleApplication::instanceId() {
|
quint32 SingleApplication::instanceId() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->instanceNumber_;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,8 +207,8 @@ quint32 SingleApplication::instanceId() {
|
||||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||||
* @return Returns the primary instance PID.
|
* @return Returns the primary instance PID.
|
||||||
*/
|
*/
|
||||||
qint64 SingleApplication::primaryPid() {
|
qint64 SingleApplication::primaryPid() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +216,8 @@ qint64 SingleApplication::primaryPid() {
|
||||||
* Returns the username the primary instance is running as.
|
* Returns the username the primary instance is running as.
|
||||||
* @return Returns the username the primary instance is running as.
|
* @return Returns the username the primary instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::primaryUser() {
|
QString SingleApplication::primaryUser() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->primaryUser();
|
return d->primaryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,9 +225,8 @@ QString SingleApplication::primaryUser() {
|
||||||
* Returns the username the current instance is running as.
|
* Returns the username the current instance is running as.
|
||||||
* @return Returns the username the current instance is running as.
|
* @return Returns the username the current instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::currentUser() {
|
QString SingleApplication::currentUser() const {
|
||||||
Q_D(SingleApplication);
|
return SingleApplicationPrivate::getUsername();
|
||||||
return d->getUsername();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,11 +247,7 @@ bool SingleApplication::sendMessage(const QByteArray &message, const int timeout
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->socket_->write(message);
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
|
||||||
d->socket_->flush();
|
|
||||||
return dataWritten;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,6 +260,7 @@ void SingleApplication::abortSafely() {
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class SingleApplicationPrivate;
|
||||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
typedef QApplication app_t;
|
using app_t = QApplication;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
@ -88,46 +88,45 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||||
* operations. It does not guarantee that the SingleApplication
|
* operations. It does not guarantee that the SingleApplication
|
||||||
* 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 SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
|
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
~SingleApplication() override;
|
~SingleApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isPrimary();
|
bool isPrimary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is a secondary instance
|
* @brief Returns if the instance is a secondary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isSecondary();
|
bool isSecondary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a unique identifier for the current instance
|
* @brief Returns a unique identifier for the current instance
|
||||||
* @returns {qint32}
|
* @returns {qint32}
|
||||||
*/
|
*/
|
||||||
quint32 instanceId();
|
quint32 instanceId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
* @returns {qint64}
|
* @returns {qint64}
|
||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the user running the primary instance
|
* @brief Returns the username of the user running the primary instance
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the current user
|
* @brief Returns the username of the current user
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString currentUser();
|
QString currentUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
|
|
|
@ -44,6 +44,14 @@
|
||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# ifndef NOMINMAX
|
||||||
|
# define NOMINMAX 1
|
||||||
|
# endif
|
||||||
|
# include <windows.h>
|
||||||
|
# include <lmcons.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
@ -53,7 +61,6 @@
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
# include <QRandomGenerator>
|
# include <QRandomGenerator>
|
||||||
|
@ -64,11 +71,6 @@
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
|
@ -86,14 +88,14 @@ SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
delete server_;
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
|
@ -152,7 +154,15 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#if defined(Q_OS_UNIX)
|
||||||
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
|
if (appImagePath.isEmpty()) {
|
||||||
|
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appData.addData(appImagePath);
|
||||||
|
};
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
@ -171,26 +181,24 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->secondary = 0;
|
instance->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary() {
|
void SingleApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
instance->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
instance->primaryPid = QCoreApplication::applicationPid();
|
||||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = 0;
|
instanceNumber_ = 0;
|
||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
|
@ -212,11 +220,11 @@ void SingleApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {
|
void SingleApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->secondary += 1;
|
instance->secondary += 1;
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = inst->secondary;
|
instanceNumber_ = instance->secondary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,25 +271,53 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
||||||
writeStream << instanceNumber_;
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
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;
|
writeStream << checksum;
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
|
||||||
|
sock->putChar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Frame 1: The header indicates the message length that follows
|
||||||
QByteArray header;
|
QByteArray header;
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
headerStream << static_cast<quint64>(msg.length());
|
||||||
|
|
||||||
socket_->write(header);
|
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
||||||
socket_->write(initMsg);
|
return false;
|
||||||
bool result = socket_->waitForBytesWritten(static_cast<int>(timeout - time.elapsed()));
|
}
|
||||||
|
|
||||||
|
// Frame 2: The message
|
||||||
|
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) {
|
||||||
|
|
||||||
|
socket_->write(msg);
|
||||||
socket_->flush();
|
socket_->flush();
|
||||||
|
|
||||||
return result;
|
bool result = socket_->waitForReadyRead(timeout);
|
||||||
|
if (result) {
|
||||||
|
socket_->read(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,8 +336,8 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
qint64 SingleApplicationPrivate::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
qint64 pid = inst->primaryPid;
|
qint64 pid = instance->primaryPid;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
@ -311,8 +347,8 @@ qint64 SingleApplicationPrivate::primaryPid() const {
|
||||||
QString SingleApplicationPrivate::primaryUser() const {
|
QString SingleApplicationPrivate::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
QByteArray username = inst->primaryUser;
|
QByteArray username = instance->primaryUser;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
return QString::fromUtf8(username);
|
||||||
|
@ -328,26 +364,30 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
nextConnSocket->deleteLater();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageHeader:
|
case StageInitHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readMessageHeader(nextConnSocket, StageInitBody);
|
||||||
break;
|
break;
|
||||||
case StageBody:
|
case StageInitBody:
|
||||||
readInitMessageBody(nextConnSocket);
|
readInitMessageBody(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
case StageConnected:
|
case StageConnectedHeader:
|
||||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
|
break;
|
||||||
|
case StageConnectedBody:
|
||||||
|
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -356,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
|
@ -373,30 +413,38 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = nextStage;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
writeAck(sock);
|
||||||
readInitMessageBody(sock);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
|
if (!connectionMap_.contains(sock)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ConnectionInfo &info = connectionMap_[sock];
|
||||||
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(static_cast<qint64>(info.msgLen));
|
QByteArray msgBytes = sock->readAll();
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
|
||||||
|
@ -431,23 +479,34 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
writeAck(sock);
|
||||||
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());
|
|
||||||
|
if (!isFrameComplete(dataSocket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray message = dataSocket->readAll();
|
||||||
|
|
||||||
|
writeAck(dataSocket);
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||||
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
|
emit q->receivedMessage(instanceId, message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +524,7 @@ void SingleApplicationPrivate::randomSleep() {
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
#else
|
#else
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
QThread::msleep(qrand() % 11 + 8);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,10 @@ class SingleApplicationPrivate : public QObject {
|
||||||
Reconnect = 3
|
Reconnect = 3
|
||||||
};
|
};
|
||||||
enum ConnectionStage : quint8 {
|
enum ConnectionStage : quint8 {
|
||||||
StageHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnected = 2,
|
StageConnectedHeader = 2,
|
||||||
|
StageConnectedBody = 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
|
@ -89,8 +90,12 @@ class SingleApplicationPrivate : public QObject {
|
||||||
quint16 blockChecksum() const;
|
quint16 blockChecksum() const;
|
||||||
qint64 primaryPid() const;
|
qint64 primaryPid() const;
|
||||||
QString primaryUser() const;
|
QString primaryUser() const;
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
bool isFrameComplete(QLocalSocket *sock);
|
||||||
|
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void writeAck(QLocalSocket *sock);
|
||||||
|
bool writeConfirmedFrame(const int timeout, const QByteArray &msg);
|
||||||
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg);
|
||||||
static void randomSleep();
|
static void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplication *q_ptr;
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
* @param options Optional flags to toggle specific behaviour
|
* @param options Optional flags to toggle specific behaviour
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
* @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[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
: app_t(argc, argv),
|
: app_t(argc, argv),
|
||||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
d->randomSleep();
|
SingleCoreApplicationPrivate::randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
|
@ -106,14 +106,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
InstancesInfo *instance = 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
|
||||||
forever {
|
forever {
|
||||||
// If the shared memory block's checksum is valid continue
|
// If the shared memory block's checksum is valid continue
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == instance->checksum) break;
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
|
@ -127,14 +127,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||||
qDebug() << d->memory_->errorString();
|
qDebug() << d->memory_->errorString();
|
||||||
}
|
}
|
||||||
d->randomSleep();
|
SingleCoreApplicationPrivate::randomSleep();
|
||||||
if (!d->memory_->lock()) {
|
if (!d->memory_->lock()) {
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||||
abortSafely();
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inst->primary) {
|
if (!instance->primary) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
if (!d->memory_->unlock()) {
|
if (!d->memory_->unlock()) {
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||||
|
@ -178,8 +178,8 @@ SingleCoreApplication::~SingleCoreApplication() {
|
||||||
* Checks if the current application instance is primary.
|
* Checks if the current application instance is primary.
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleCoreApplication::isPrimary() {
|
bool SingleCoreApplication::isPrimary() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->server_ != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,8 +187,8 @@ bool SingleCoreApplication::isPrimary() {
|
||||||
* Checks if the current application instance is secondary.
|
* Checks if the current application instance is secondary.
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleCoreApplication::isSecondary() {
|
bool SingleCoreApplication::isSecondary() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->server_ == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,8 +197,8 @@ bool SingleCoreApplication::isSecondary() {
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
* @return Returns a unique instance id.
|
* @return Returns a unique instance id.
|
||||||
*/
|
*/
|
||||||
quint32 SingleCoreApplication::instanceId() {
|
quint32 SingleCoreApplication::instanceId() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->instanceNumber_;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,8 +207,8 @@ quint32 SingleCoreApplication::instanceId() {
|
||||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
||||||
* @return Returns the primary instance PID.
|
* @return Returns the primary instance PID.
|
||||||
*/
|
*/
|
||||||
qint64 SingleCoreApplication::primaryPid() {
|
qint64 SingleCoreApplication::primaryPid() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +216,8 @@ qint64 SingleCoreApplication::primaryPid() {
|
||||||
* Returns the username the primary instance is running as.
|
* Returns the username the primary instance is running as.
|
||||||
* @return Returns the username the primary instance is running as.
|
* @return Returns the username the primary instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleCoreApplication::primaryUser() {
|
QString SingleCoreApplication::primaryUser() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->primaryUser();
|
return d->primaryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,9 +225,8 @@ QString SingleCoreApplication::primaryUser() {
|
||||||
* Returns the username the current instance is running as.
|
* Returns the username the current instance is running as.
|
||||||
* @return Returns the username the current instance is running as.
|
* @return Returns the username the current instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleCoreApplication::currentUser() {
|
QString SingleCoreApplication::currentUser() const {
|
||||||
Q_D(SingleCoreApplication);
|
return SingleCoreApplicationPrivate::getUsername();
|
||||||
return d->getUsername();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,11 +247,7 @@ bool SingleCoreApplication::sendMessage(const QByteArray &message, const int tim
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->socket_->write(message);
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
|
||||||
d->socket_->flush();
|
|
||||||
return dataWritten;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,6 +260,7 @@ void SingleCoreApplication::abortSafely() {
|
||||||
|
|
||||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,10 @@ class SingleCoreApplicationPrivate;
|
||||||
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
||||||
* @see QCoreApplication
|
* @see QCoreApplication
|
||||||
*/
|
*/
|
||||||
class SingleCoreApplication : public QCoreApplication {
|
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
typedef QCoreApplication app_t;
|
using app_t = QCoreApplication;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
@ -96,37 +96,37 @@ class SingleCoreApplication : public QCoreApplication {
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isPrimary();
|
bool isPrimary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is a secondary instance
|
* @brief Returns if the instance is a secondary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isSecondary();
|
bool isSecondary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a unique identifier for the current instance
|
* @brief Returns a unique identifier for the current instance
|
||||||
* @returns {qint32}
|
* @returns {qint32}
|
||||||
*/
|
*/
|
||||||
quint32 instanceId();
|
quint32 instanceId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
* @returns {qint64}
|
* @returns {qint64}
|
||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the user running the primary instance
|
* @brief Returns the username of the user running the primary instance
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the current user
|
* @brief Returns the username of the current user
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString currentUser();
|
QString currentUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
|
|
|
@ -44,6 +44,14 @@
|
||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# ifndef NOMINMAX
|
||||||
|
# define NOMINMAX 1
|
||||||
|
# endif
|
||||||
|
# include <windows.h>
|
||||||
|
# include <lmcons.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
@ -53,7 +61,6 @@
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
# include <QRandomGenerator>
|
# include <QRandomGenerator>
|
||||||
|
@ -64,11 +71,6 @@
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
|
@ -86,14 +88,14 @@ SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
delete server_;
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
|
@ -152,7 +154,15 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#if defined(Q_OS_UNIX)
|
||||||
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
|
if (appImagePath.isEmpty()) {
|
||||||
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appData.addData(appImagePath);
|
||||||
|
};
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
@ -171,26 +181,24 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->secondary = 0;
|
instance->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startPrimary() {
|
void SingleCoreApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
instance->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
instance->primaryPid = QCoreApplication::applicationPid();
|
||||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = 0;
|
instanceNumber_ = 0;
|
||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
|
@ -212,11 +220,11 @@ void SingleCoreApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startSecondary() {
|
void SingleCoreApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->secondary += 1;
|
instance->secondary += 1;
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = inst->secondary;
|
instanceNumber_ = instance->secondary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,25 +271,53 @@ bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const Con
|
||||||
writeStream << instanceNumber_;
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
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;
|
writeStream << checksum;
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) {
|
||||||
|
sock->putChar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Frame 1: The header indicates the message length that follows
|
||||||
QByteArray header;
|
QByteArray header;
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
headerStream << static_cast<quint64>(msg.length());
|
||||||
|
|
||||||
socket_->write(header);
|
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
||||||
socket_->write(initMsg);
|
return false;
|
||||||
bool result = socket_->waitForBytesWritten(timeout - static_cast<int>(time.elapsed()));
|
}
|
||||||
|
|
||||||
|
// Frame 2: The message
|
||||||
|
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) {
|
||||||
|
|
||||||
|
socket_->write(msg);
|
||||||
socket_->flush();
|
socket_->flush();
|
||||||
|
|
||||||
return result;
|
bool result = socket_->waitForReadyRead(timeout);
|
||||||
|
if (result) {
|
||||||
|
socket_->read(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,8 +336,8 @@ quint16 SingleCoreApplicationPrivate::blockChecksum() const {
|
||||||
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
qint64 pid = inst->primaryPid;
|
qint64 pid = instance->primaryPid;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
@ -311,8 +347,8 @@ qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||||
QString SingleCoreApplicationPrivate::primaryUser() const {
|
QString SingleCoreApplicationPrivate::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
QByteArray username = inst->primaryUser;
|
QByteArray username = instance->primaryUser;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
return QString::fromUtf8(username);
|
||||||
|
@ -328,26 +364,30 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
nextConnSocket->deleteLater();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageHeader:
|
case StageInitHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readMessageHeader(nextConnSocket, StageInitBody);
|
||||||
break;
|
break;
|
||||||
case StageBody:
|
case StageInitBody:
|
||||||
readInitMessageBody(nextConnSocket);
|
readInitMessageBody(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
case StageConnected:
|
case StageConnectedHeader:
|
||||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
|
break;
|
||||||
|
case StageConnectedBody:
|
||||||
|
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -356,7 +396,7 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
|
@ -373,30 +413,38 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = nextStage;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
writeAck(sock);
|
||||||
readInitMessageBody(sock);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
|
if (!connectionMap_.contains(sock)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(static_cast<qint64>(info.msgLen));
|
QByteArray msgBytes = sock->readAll();
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
|
||||||
|
@ -431,23 +479,34 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
writeAck(sock);
|
||||||
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());
|
|
||||||
|
if (!isFrameComplete(dataSocket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray message = dataSocket->readAll();
|
||||||
|
|
||||||
|
writeAck(dataSocket);
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||||
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
|
emit q->receivedMessage(instanceId, message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +524,7 @@ void SingleCoreApplicationPrivate::randomSleep() {
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
#else
|
#else
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
QThread::msleep(qrand() % 11 + 8);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,10 @@ class SingleCoreApplicationPrivate : public QObject {
|
||||||
Reconnect = 3
|
Reconnect = 3
|
||||||
};
|
};
|
||||||
enum ConnectionStage : quint8 {
|
enum ConnectionStage : quint8 {
|
||||||
StageHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnected = 2,
|
StageConnectedHeader = 2,
|
||||||
|
StageConnectedBody = 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||||
|
|
||||||
|
@ -89,8 +90,12 @@ class SingleCoreApplicationPrivate : public QObject {
|
||||||
quint16 blockChecksum() const;
|
quint16 blockChecksum() const;
|
||||||
qint64 primaryPid() const;
|
qint64 primaryPid() const;
|
||||||
QString primaryUser() const;
|
QString primaryUser() const;
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
bool isFrameComplete(QLocalSocket *sock);
|
||||||
|
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void writeAck(QLocalSocket *sock);
|
||||||
|
bool writeConfirmedFrame(const int timeout, const QByteArray &msg);
|
||||||
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg);
|
||||||
static void randomSleep();
|
static void randomSleep();
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
SingleCoreApplication *q_ptr;
|
||||||
|
|
Loading…
Reference in New Issue