diff options
author | 2023-03-07 21:11:06 +0600 | |
---|---|---|
committer | 2023-03-07 21:11:06 +0600 | |
commit | d830bfc8ce7c6763d074beafdde7cab1835d31f9 (patch) | |
tree | 179f81f3b42779b1f6fb69deed6b69dab39f4f89 /src/singleapplication/singleapplication.cpp | |
download | whatsie-d830bfc8ce7c6763d074beafdde7cab1835d31f9.tar.gz whatsie-d830bfc8ce7c6763d074beafdde7cab1835d31f9.zip |
Import Upstream version 4.12.0upstream/4.12.0
Diffstat (limited to 'src/singleapplication/singleapplication.cpp')
-rw-r--r-- | src/singleapplication/singleapplication.cpp | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/src/singleapplication/singleapplication.cpp b/src/singleapplication/singleapplication.cpp new file mode 100644 index 0000000..1234ff9 --- /dev/null +++ b/src/singleapplication/singleapplication.cpp @@ -0,0 +1,272 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2020 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include <QtCore/QElapsedTimer> +#include <QtCore/QByteArray> +#include <QtCore/QSharedMemory> + +#include "singleapplication.h" +#include "singleapplication_p.h" + +/** + * @brief Constructor. Checks and fires up LocalServer or closes the program + * if another instance already exists + * @param argc + * @param argv + * @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, const QString &userData ) + : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) +{ + Q_D( SingleApplication ); + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + // On Android and iOS since the library is not supported fallback to + // standard QApplication behaviour by simply returning at this point. + qWarning() << "SingleApplication is not supported on Android and iOS systems."; + return; +#endif + + // Store the current mode of the program + d->options = options; + + // Add any unique user data + if ( ! userData.isEmpty() ) + d->addAppData( userData ); + + // Generating an application ID used for identifying the shared memory + // block and QLocalServer + d->genBlockServerName(); + + // To mitigate QSharedMemory issues with large amount of processes + // attempting to attach at the same time + SingleApplicationPrivate::randomSleep(); + +#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. + d->memory = new QSharedMemory( d->blockServerName ); + d->memory->attach(); + delete d->memory; +#endif + // Guarantee thread safe behaviour with a shared memory block. + d->memory = new QSharedMemory( d->blockServerName ); + + // Create a shared memory block + if( d->memory->create( sizeof( InstancesInfo ) )){ + // Initialize the shared memory block + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory block after create."; + abortSafely(); + } + d->initializeMemoryBlock(); + } else { + if( d->memory->error() == QSharedMemory::AlreadyExists ){ + // Attempt to attach to the memory segment + if( ! d->memory->attach() ){ + qCritical() << "SingleApplication: Unable to attach to shared memory block."; + abortSafely(); + } + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory block after attach."; + abortSafely(); + } + } else { + qCritical() << "SingleApplication: Unable to create block."; + abortSafely(); + } + } + + auto *inst = static_cast<InstancesInfo*>( d->memory->data() ); + QElapsedTimer time; + time.start(); + + // Make sure the shared memory block is initialised and in consistent state + while( true ){ + // If the shared memory block's checksum is valid continue + 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 ){ + qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; + d->initializeMemoryBlock(); + } + + // 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 + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory for random wait."; + qDebug() << d->memory->errorString(); + } + SingleApplicationPrivate::randomSleep(); + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory after random wait."; + abortSafely(); + } + } + + if( inst->primary == false ){ + d->startPrimary(); + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory after primary start."; + qDebug() << d->memory->errorString(); + } + return; + } + + // Check if another instance can be started + if( allowSecondary ){ + d->startSecondary(); + if( d->options & Mode::SecondaryNotification ){ + d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance ); + } + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; + qDebug() << d->memory->errorString(); + } + return; + } + + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; + qDebug() << d->memory->errorString(); + } + + d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); + + delete d; + + ::exit( EXIT_SUCCESS ); +} + +SingleApplication::~SingleApplication() +{ + Q_D( SingleApplication ); + delete d; +} + +/** + * Checks if the current application instance is primary. + * @return Returns true if the instance is primary, false otherwise. + */ +bool SingleApplication::isPrimary() const +{ + Q_D( const SingleApplication ); + 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() const +{ + Q_D( const SingleApplication ); + 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() const +{ + Q_D( const SingleApplication ); + 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() const +{ + Q_D( const SingleApplication ); + 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() const +{ + Q_D( const 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() const +{ + return SingleApplicationPrivate::getUsername(); +} + +/** + * Sends message to the Primary Instance. + * @param message The message to send. + * @param timeout the maximum timeout in milliseconds for blocking functions. + * @param sendMode mode of operation + * @return true if the message was sent successfuly, false otherwise. + */ +bool SingleApplication::sendMessage( const QByteArray &message, int timeout, SendMode sendMode ) +{ + Q_D( SingleApplication ); + + // Nobody to connect to + if( isPrimary() ) return false; + + // Make sure the socket is connected + if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) ) + return false; + + return d->writeConfirmedMessage( timeout, message, sendMode ); +} + +/** + * 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 ); +} + +QStringList SingleApplication::userData() const +{ + Q_D( const SingleApplication ); + return d->appData(); +} |