1
0
mirror of https://github.com/strawberrymusicplayer/strawberry synced 2025-01-29 08:39:33 +01:00

Add SingleCoreApplication

This commit is contained in:
Jonas Kvinge 2019-01-11 01:04:13 +01:00
parent 5746ee74cf
commit c94f9073f8
11 changed files with 845 additions and 40 deletions

14
3rdparty/README.md vendored
View File

@ -1,18 +1,18 @@
3rdparty libraries located in this directory
============================================
3rdparty/singleapplication
singleapplication
-----------------
This is a small static library used by Strawberry to prevent it from starting twice per user session.
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
The library is unmodified from the original version. If you dynamically link to your systems version
it is important that it is using the QApplication class, otherwise it will crash.
If you dynamically link to your systems version, you'll need two versions, one defined as QApplication and
one as a QCoreApplication.
It is included here because it is normally not packaged by distros, and is also used on macOS and Windows.
URL: https://github.com/itay-grudev/SingleApplication
3rdparty/qocoa
qocoa
--------------
This is a small static library currently used for the search fields above the collection, playlist and in
the cover manager. It is slightly modified from original version, so it should not be used as a dynamic
@ -22,14 +22,14 @@ The plan in the long run is to replace it with something else.
URL: https://github.com/mikemcquaid/Qocoa
3rdparty/SPMediaKeyTap
SPMediaKeyTap
----------------------
This is used for macOS only to enable strawberry to grab global shortcuts and can safely be deleted on other
platforms.
3rdparty/taglib
taglib
---------------
TagLib is a library for reading and editing the meta-data of several popular audio formats. It is also used
@ -51,7 +51,7 @@ The current taglib in 3rdparty also has the following features:
URL: https://github.com/taglib/taglib
3rdparty/utf8-cpp
utf8-cpp
-----------------
This is 2 header files used by taglib, but kept in a seperate directory because it is maintained by others.

View File

@ -1,13 +1,15 @@
cmake_minimum_required(VERSION 2.8.11)
set(CMAKE_CXX_STANDARD 11)
if(CMAKE_VERSION VERSION_GREATER 3.11.4)
add_compile_definitions(QAPPLICATION_CLASS=QApplication)
else()
add_definitions(-DQAPPLICATION_CLASS=QApplication)
endif()
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
QT5_WRAP_CPP(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
ADD_LIBRARY(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
target_link_libraries(singleapplication Qt5::Core Qt5::Widgets Qt5::Network)
include_directories(${CMAKE_BINARY_DIR}/src)
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
QT5_WRAP_CPP(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
ADD_LIBRARY(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
target_link_libraries(singlecoreapplication Qt5::Core Qt5::Widgets Qt5::Network)

View File

@ -20,6 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <QApplication>
#include <QtCore/QTime>
#include <QtCore/QThread>
#include <QtCore/QDateTime>

View File

@ -20,18 +20,13 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#ifndef SINGLEAPPLICATION_H
#define SINGLEAPPLICATION_H
#include <QtCore/QtGlobal>
#include <QApplication>
#include <QtNetwork/QLocalSocket>
#ifndef QAPPLICATION_CLASS
#error "QAPPLICATION_CLASS not defined."
#endif
#include QT_STRINGIFY(QAPPLICATION_CLASS)
class SingleApplicationPrivate;
/**
@ -39,11 +34,11 @@ class SingleApplicationPrivate;
* Application
* @see QCoreApplication
*/
class SingleApplication : public QAPPLICATION_CLASS
class SingleApplication : public QApplication
{
Q_OBJECT
typedef QAPPLICATION_CLASS app_t;
typedef QApplication app_t;
public:
/**
@ -132,4 +127,4 @@ private:
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLE_APPLICATION_H
#endif // SINGLEAPPLICATION_H

View File

@ -0,0 +1,175 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
//
// 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 <QCoreApplication>
#include <QtCore/QTime>
#include <QtCore/QThread>
#include <QtCore/QDateTime>
#include <QtCore/QByteArray>
#include <QtCore/QSharedMemory>
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists
* @param argc
* @param argv
* @param {bool} allowSecondaryInstances
*/
SingleCoreApplication::SingleCoreApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout )
: app_t( argc, argv ), d_ptr( new SingleCoreApplicationPrivate( this ) )
{
Q_D(SingleCoreApplication);
// Store the current mode of the program
d->options = options;
// Generating an application ID used for identifying the shared memory
// block and QLocalServer
d->genBlockServerName();
#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
d->memory->lock();
d->initializeMemoryBlock();
d->memory->unlock();
} else {
// Attempt to attach to the memory segment
if( ! d->memory->attach() ) {
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
qCritical() << d->memory->errorString();
delete d;
::exit( EXIT_FAILURE );
}
}
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() );
QTime time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while( true ) {
d->memory->lock();
if( d->blockChecksum() == inst->checksum ) break;
if( time.elapsed() > 5000 ) {
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
d->memory->unlock();
// Random sleep here limits the probability of a collision between two racing apps
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) );
}
if( inst->primary == false) {
d->startPrimary();
d->memory->unlock();
return;
}
// Check if another instance can be started
if( allowSecondary ) {
inst->secondary += 1;
inst->checksum = d->blockChecksum();
d->instanceNumber = inst->secondary;
d->startSecondary();
if( d->options & Mode::SecondaryNotification ) {
d->connectToPrimary( timeout, SingleCoreApplicationPrivate::SecondaryInstance );
}
d->memory->unlock();
return;
}
d->memory->unlock();
d->connectToPrimary( timeout, SingleCoreApplicationPrivate::NewInstance );
delete d;
::exit( EXIT_SUCCESS );
}
/**
* @brief Destructor
*/
SingleCoreApplication::~SingleCoreApplication()
{
Q_D(SingleCoreApplication);
delete d;
}
bool SingleCoreApplication::isPrimary()
{
Q_D(SingleCoreApplication);
return d->server != nullptr;
}
bool SingleCoreApplication::isSecondary()
{
Q_D(SingleCoreApplication);
return d->server == nullptr;
}
quint32 SingleCoreApplication::instanceId()
{
Q_D(SingleCoreApplication);
return d->instanceNumber;
}
qint64 SingleCoreApplication::primaryPid()
{
Q_D(SingleCoreApplication);
return d->primaryPid();
}
bool SingleCoreApplication::sendMessage( QByteArray message, int timeout )
{
Q_D(SingleCoreApplication);
// Nobody to connect to
if( isPrimary() ) return false;
// Make sure the socket is connected
d->connectToPrimary( timeout, SingleCoreApplicationPrivate::Reconnect );
d->socket->write( message );
bool dataWritten = d->socket->flush();
d->socket->waitForBytesWritten( timeout );
return dataWritten;
}

View File

@ -0,0 +1,130 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
//
// 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.
#ifndef SINGLECOREAPPLICATION_H
#define SINGLECOREAPPLICATION_H
#include <QtCore/QtGlobal>
#include <QCoreApplication>
#include <QtNetwork/QLocalSocket>
class SingleCoreApplicationPrivate;
/**
* @brief The SingleCoreApplication class handles multipe instances of the same
* Application
* @see QCoreApplication
*/
class SingleCoreApplication : public QCoreApplication
{
Q_OBJECT
typedef QCoreApplication app_t;
public:
/**
* @brief Mode of operation of SingleCoreApplication.
* Whether the block should be user-wide or system-wide and whether the
* primary instance should be notified when a secondary instance had been
* started.
* @note Operating system can restrict the shared memory blocks to the same
* user, in which case the User/System modes will have no effect and the
* block will be user wide.
* @enum
*/
enum Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
/**
* @brief Intitializes a SingleCoreApplication instance with argc command line
* arguments in argv
* @arg {int &} argc - Number of arguments in argv
* @arg {const char *[]} argv - Supplied command line arguments
* @arg {bool} allowSecondary - Whether to start the instance as secondary
* if there is already a primary instance.
* @arg {Mode} mode - Whether for the SingleCoreApplication block to be applied
* User wide or System wide.
* @arg {int} timeout - Timeout to wait in miliseconds.
* @note argc and argv may be changed as Qt removes arguments that it
* recognizes
* @note Mode::SecondaryNotification only works if set on both the primary
* instance and the secondary instance.
* @note The timeout is just a hint for the maximum time of blocking
* operations. It does not guarantee that the SingleCoreApplication
* initialisation will be completed in given time, though is a good hint.
* 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 );
~SingleCoreApplication();
/**
* @brief Returns if the instance is the primary instance
* @returns {bool}
*/
bool isPrimary();
/**
* @brief Returns if the instance is a secondary instance
* @returns {bool}
*/
bool isSecondary();
/**
* @brief Returns a unique identifier for the current instance
* @returns {qint32}
*/
quint32 instanceId();
/**
* @brief Returns the process ID (PID) of the primary instance
* @returns {qint64}
*/
qint64 primaryPid();
/**
* @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting
* @returns {bool}
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( QByteArray message, int timeout = 100 );
Q_SIGNALS:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
private:
SingleCoreApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleCoreApplication)
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
#endif // SINGLECOREAPPLICATION_H

View File

@ -0,0 +1,402 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
//
// 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.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleCoreApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <cstdlib>
#include <cstddef>
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtCore/QByteArray>
#include <QtCore/QSemaphore>
#include <QtCore/QDataStream>
#include <QtCore/QStandardPaths>
#include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <lmcons.h>
#endif
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate( SingleCoreApplication *q_ptr )
: q_ptr( q_ptr )
{
server = nullptr;
socket = nullptr;
}
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate()
{
if( socket != nullptr ) {
socket->close();
delete socket;
}
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ) {
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->checksum = blockChecksum();
}
memory->unlock();
delete memory;
}
void SingleCoreApplicationPrivate::genBlockServerName()
{
QCryptographicHash appData( QCryptographicHash::Sha256 );
appData.addData( "SingleCoreApplication", 17 );
appData.addData( SingleCoreApplication::app_t::applicationName().toUtf8() );
appData.addData( SingleCoreApplication::app_t::organizationName().toUtf8() );
appData.addData( SingleCoreApplication::app_t::organizationDomain().toUtf8() );
if( ! (options & SingleCoreApplication::Mode::ExcludeAppVersion) ) {
appData.addData( SingleCoreApplication::app_t::applicationVersion().toUtf8() );
}
if( ! (options & SingleCoreApplication::Mode::ExcludeAppPath) ) {
#ifdef Q_OS_WIN
appData.addData( SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8() );
#else
appData.addData( SingleCoreApplication::app_t::applicationFilePath().toUtf8() );
#endif
}
// User level block requires a user specific data in the hash
if( options & SingleCoreApplication::Mode::User ) {
#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( QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).join("").toUtf8() );
}
#endif
#ifdef Q_OS_UNIX
QProcess process;
process.start( "whoami" );
if( process.waitForFinished( 100 ) &&
process.exitCode() == QProcess::NormalExit) {
appData.addData( process.readLine() );
} else {
appData.addData(
QDir(
QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).first()
).absolutePath().toUtf8()
);
}
#endif
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
// server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_");
}
void SingleCoreApplicationPrivate::initializeMemoryBlock()
{
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->checksum = blockChecksum();
}
void SingleCoreApplicationPrivate::startPrimary()
{
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
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = q->applicationPid();
inst->checksum = blockChecksum();
instanceNumber = 0;
}
void SingleCoreApplicationPrivate::startSecondary()
{
}
void SingleCoreApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
{
// Connect to the Local Server of the Primary Instance if not already
// connected.
if( socket == nullptr ) {
socket = new QLocalSocket();
}
// If already connected - we are done;
if( socket->state() == QLocalSocket::ConnectedState )
return;
// If not connect
if( socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState ) {
socket->connectToServer( blockServerName );
}
// Wait for being connected
if( socket->state() == QLocalSocket::ConnectingState ) {
socket->waitForConnected( msecs );
}
// Initialisation message according to the SingleCoreApplication protocol
if( socket->state() == QLocalSocket::ConnectedState ) {
// Notify the parent that a new instance had been started;
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
writeStream.setVersion(QDataStream::Qt_5_6);
#endif
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
writeStream << checksum;
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6);
#endif
headerStream << static_cast <quint64>( initMsg.length() );
socket->write( header );
socket->write( initMsg );
socket->flush();
socket->waitForBytesWritten( msecs );
}
}
quint16 SingleCoreApplicationPrivate::blockChecksum()
{
return qChecksum(
static_cast <const char *>( memory->data() ),
offsetof( InstancesInfo, checksum )
);
}
qint64 SingleCoreApplicationPrivate::primaryPid()
{
qint64 pid;
memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid;
memory->unlock();
return pid;
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleCoreApplicationPrivate::slotConnectionEstablished()
{
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
}
);
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
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;
};
}
);
}
void SingleCoreApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
{
if (!connectionMap.contains( sock )) {
return;
}
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) {
return;
}
QDataStream headerStream( sock );
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion( QDataStream::Qt_5_6 );
#endif
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
info.stage = StageBody;
info.msgLen = msgLen;
if ( sock->bytesAvailable() >= (qint64) msgLen ) {
readInitMessageBody( sock );
}
}
void SingleCoreApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
{
Q_Q(SingleCoreApplication);
if (!connectionMap.contains( sock )) {
return;
}
ConnectionInfo &info = connectionMap[sock];
if( sock->bytesAvailable() < ( qint64 )info.msgLen ) {
return;
}
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
readStream.setVersion( QDataStream::Qt_5_6 );
#endif
// server name
QByteArray latin1Name;
readStream >> latin1Name;
// connection type
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>( connTypeVal );
// instance id
quint32 instanceId = 0;
readStream >> instanceId;
// checksum
quint16 msgChecksum = 0;
readStream >> msgChecksum;
const quint16 actualChecksum = qChecksum( msgBytes.constData(), static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) );
bool isValid = readStream.status() == QDataStream::Ok &&
QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
if( !isValid ) {
sock->close();
return;
}
info.instanceId = instanceId;
info.stage = StageConnected;
if( connectionType == NewInstance ||
( connectionType == SecondaryInstance &&
options & SingleCoreApplication::Mode::SecondaryNotification ) )
{
Q_EMIT q->instanceStarted();
}
if (sock->bytesAvailable() > 0) {
Q_EMIT this->slotDataAvailable( sock, instanceId );
}
}
void SingleCoreApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
{
Q_Q(SingleCoreApplication);
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
}
void SingleCoreApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
{
if( closedSocket->bytesAvailable() > 0 )
Q_EMIT slotDataAvailable( closedSocket, instanceId );
}

View File

@ -0,0 +1,99 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2016
//
// 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.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleCoreApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#ifndef SINGLECOREAPPLICATION_P_H
#define SINGLECOREAPPLICATION_P_H
#include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include "singlecoreapplication.h"
struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
quint16 checksum;
};
struct ConnectionInfo {
explicit ConnectionInfo() :
msgLen(0), instanceId(0), stage(0) {}
qint64 msgLen;
quint32 instanceId;
quint8 stage;
};
class SingleCoreApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
NewInstance = 1,
SecondaryInstance = 2,
Reconnect = 3
};
enum ConnectionStage : quint8 {
StageHeader = 0,
StageBody = 1,
StageConnected = 2,
};
Q_DECLARE_PUBLIC(SingleCoreApplication)
SingleCoreApplicationPrivate( SingleCoreApplication *q_ptr );
~SingleCoreApplicationPrivate();
void genBlockServerName();
void initializeMemoryBlock();
void startPrimary();
void startSecondary();
void connectToPrimary(int msecs, ConnectionType connectionType );
quint16 blockChecksum();
qint64 primaryPid();
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
SingleCoreApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleCoreApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
public Q_SLOTS:
void slotConnectionEstablished();
void slotDataAvailable( QLocalSocket*, quint32 );
void slotClientConnectionClosed( QLocalSocket*, quint32 );
};
#endif // SINGLECOREAPPLICATION_P_H

View File

@ -207,27 +207,24 @@ endif()
# SingleApplication
find_library(SINGLEAPPLICATION singleapplication)
if (SINGLEAPPLICATION_FOUND)
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication library" ON)
else(SINGLEAPPLICATION_FOUND)
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication library" OFF)
endif(SINGLEAPPLICATION_FOUND)
find_library(SINGLECOREAPPLICATION singlecoreapplication)
if (SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication/SingleCoreApplication libraries" ON)
else(SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
option(USE_SYSTEM_SINGLEAPPLICATION "Use system SingleApplication/SingleCoreApplication libraries" OFF)
endif(SINGLEAPPLICATION_FOUND AND SINGLECOREAPPLICATION_FOUND)
if(USE_SYSTEM_SINGLEAPPLICATION)
if (NOT SINGLEAPPLICATION_FOUND)
message(FATAL_ERROR "Missing SingleApplication")
if (NOT SINGLEAPPLICATION_FOUND OR NOT SINGLECOREAPPLICATION_FOUND)
message(FATAL_ERROR "Missing SingleApplication or SingleCoreApplication")
endif()
message(STATUS "Using system SingleApplication library")
message(STATUS "Using system SingleApplication and SingleCoreApplication libraries")
else(USE_SYSTEM_SINGLEAPPLICATION)
message(STATUS "Using builtin SingleApplication library")
message(STATUS "Using builtin SingleApplication and SingleCoreApplication libraries")
add_subdirectory(3rdparty/singleapplication)
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
endif(USE_SYSTEM_SINGLEAPPLICATION)
if(CMAKE_VERSION VERSION_GREATER 3.11.4)
add_compile_definitions(QAPPLICATION_CLASS=QApplication)
else()
add_definitions(-DQAPPLICATION_CLASS=QApplication)
endif()
# Qocoa
set(QOCOA_LIBRARIES Qocoa)

View File

@ -72,6 +72,7 @@ link_directories(${TAGLIB_LIBRARY_DIRS})
include_directories(${TAGLIB_INCLUDE_DIRS})
include_directories(${SINGLEAPPLICATION_INCLUDE_DIRS})
include_directories(${SINGLECOREAPPLICATION_INCLUDE_DIRS})
include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common)
include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader)
@ -919,6 +920,7 @@ target_link_libraries(strawberry_lib
${SQLITE_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${SINGLEAPPLICATION_LIBRARIES}
${SINGLECOREAPPLICATION_LIBRARIES}
${QOCOA_LIBRARIES}
z
)
@ -1027,7 +1029,7 @@ if (UNIX AND NOT APPLE)
endif ()
endif ()
add_dependencies(strawberry_lib singleapplication)
add_dependencies(strawberry_lib singleapplication singlecoreapplication)
###############################################################################

View File

@ -49,6 +49,7 @@
#include <QObject>
#include <QApplication>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QFileDevice>
#include <QIODevice>
@ -69,6 +70,7 @@
#include "core/logging.h"
#include <singleapplication.h>
#include <singlecoreapplication.h>
#ifdef HAVE_DBUS
# include "core/mpris.h"
@ -125,7 +127,7 @@ int main(int argc, char* argv[]) {
{
// Only start a core application now so we can check if there's another Strawberry running without needing an X server.
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
SingleApplication a(argc, argv, true, SingleApplication::Mode::User);
SingleCoreApplication a(argc, argv, true, SingleCoreApplication::Mode::User);
Utilities::CheckPortable();
// Parse commandline options - need to do this before starting the full QApplication so it works without an X server