#include "cliapplicationhelper.h"
#include <unistd.h>
#include <QLoggingCategory>
#include <QLockFile>
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
#include <QDataStream>
using HelperCreator = CliApplicationHelper::HelperCreator;
Q_DECLARE_LOGGING_CATEGORY(logApp)
Q_GLOBAL_STATIC(QLocalServer, _d_singleServer)
@brief cli程序单实例类
@private
*/
class _CliApplicationHelper
{
public:
#define INVALID_HELPER reinterpret_cast<CliApplicationHelper*>(1)
inline CliApplicationHelper *helper()
{
if (m_helper.testAndSetRelaxed(nullptr, INVALID_HELPER)) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
m_helper.storeRelaxed(creator());
}
return m_helper.loadRelaxed();
#else
m_helper.store(creator());
}
return m_helper.load();
#endif
}
inline void clear()
{
if (m_helper != INVALID_HELPER)
delete m_helper.fetchAndStoreRelaxed(nullptr);
}
static CliApplicationHelper *defaultCreator()
{
return new CliApplicationHelper();
}
QAtomicPointer<CliApplicationHelper> m_helper;
static HelperCreator creator;
};
HelperCreator _CliApplicationHelper::creator = _CliApplicationHelper::defaultCreator;
static quint8 _d_singleServerVersion = 1;
Q_GLOBAL_STATIC(_CliApplicationHelper, _globalHelper)
int CliApplicationHelper::waitTime = 3000;
bool CliApplicationHelper::setSingleInstance(const QString &key, CliApplicationHelper::SingleScope singleScope)
{
qCDebug(logApp) << "Setting up single instance with key:" << key << "and scope:" << singleScope;
bool new_server = !_d_singleServer.exists();
if (_d_singleServer->isListening()) {
qCDebug(logApp) << "Closing existing server instance";
_d_singleServer->close();
}
QString socket_key = "_d_deepin_logger_single_instance_";
switch (singleScope) {
case GroupScope:
qCDebug(logApp) << "Setting up group scope";
_d_singleServer->setSocketOptions(QLocalServer::GroupAccessOption);
#ifdef Q_OS_LINUX
socket_key += QString("%1_").arg(getgid());
#endif
break;
case WorldScope:
qCDebug(logApp) << "Setting up world scope";
_d_singleServer->setSocketOptions(QLocalServer::WorldAccessOption);
break;
default:
qCDebug(logApp) << "Setting up user scope";
_d_singleServer->setSocketOptions(QLocalServer::UserAccessOption);
#ifdef Q_OS_LINUX
socket_key += QString("%1_").arg(getuid());
#endif
break;
}
socket_key += key;
qCDebug(logApp) << "Generated socket key:" << socket_key;
QString lockfile = socket_key;
if (!lockfile.startsWith(QLatin1Char('/'))) {
qCDebug(logApp) << "Lock file does not start with /, using temp path";
lockfile = QDir::cleanPath(QDir::tempPath());
lockfile += QLatin1Char('/') + socket_key;
}
lockfile += QStringLiteral(".lock");
qCDebug(logApp) << "Using lock file:" << lockfile;
static QScopedPointer <QLockFile> lock(new QLockFile(lockfile));
qint64 tpid = -1;
QString hostname, appname;
if (lock->isLocked() && lock->getLockInfo(&tpid, &hostname, &appname) && tpid == getpid()) {
qCWarning(logApp) << "call setSingleInstance again within the same process";
lock->unlock();
lock.reset(new QLockFile(lockfile));
}
if (!lock->tryLock()) {
qCInfo(logApp) << "Instance already running (pid:" << getpid() << "), connecting to existing instance";
QLocalSocket socket;
qCDebug(logApp) << "Connecting to server at:" << socket_key;
socket.connectToServer(socket_key);
qCDebug(logApp) << "Waiting for server connection (timeout:" << CliApplicationHelper::waitTime << "ms)";
if (socket.waitForConnected(CliApplicationHelper::waitTime) &&
socket.waitForReadyRead(CliApplicationHelper::waitTime)) {
qCDebug(logApp) << "Server connection established";
qint8 version;
qint64 pid;
QStringList arguments;
QDataStream ds(&socket);
ds >> version >> pid >> arguments;
qCInfo(logApp) << "Process is started: pid=" << pid << "arguments=" << arguments;
ds << _d_singleServerVersion << qApp->applicationPid() << qApp->arguments();
socket.flush();
}
qCDebug(logApp) << "Server connection failed";
return false;
}
qCInfo(logApp) << "Attempting to listen on socket:" << socket_key;
if (!_d_singleServer->listen(socket_key)) {
qCCritical(logApp) << "Failed to listen on socket:" << socket_key
<< "- Error:" << _d_singleServer->errorString();
return false;
}
if (new_server) {
qCInfo(logApp) << "Setting up new server connection handler for socket:" << socket_key;
QObject::connect(_d_singleServer, &QLocalServer::newConnection, qApp, [] {
QLocalSocket *instance = _d_singleServer->nextPendingConnection();
QDataStream ds(instance);
ds << _d_singleServerVersion
<< qApp->applicationPid()
<< qApp->arguments();
qCDebug(logApp) << "New connection received, setting up readyRead handler";
QObject::connect(instance, &QLocalSocket::readyRead, qApp, [instance] {
QDataStream ds(instance);
qint8 version;
qint64 pid;
QStringList arguments;
ds >> version >> pid >> arguments;
instance->close();
qCInfo(logApp) << "New instance connected - pid:" << pid << "args:" << arguments;
if (_globalHelper.exists() && _globalHelper->helper()) {
qCDebug(logApp) << "Emitting newProcessInstance signal";
Q_EMIT _globalHelper->helper()->newProcessInstance(pid, arguments);
} else {
qCWarning(logApp) << "Global helper not available to emit signal";
}
});
qCDebug(logApp) << "Flushing data to new instance";
instance->flush();
});
}
qCDebug(logApp) << "Single instance set up successfully";
return true;
}
CliApplicationHelper::CliApplicationHelper()
: QObject(nullptr)
{
qCDebug(logApp) << "Creating CliApplicationHelper instance";
qAddPostRoutine(staticCleanApplication);
}
CliApplicationHelper::~CliApplicationHelper()
{
_globalHelper->m_helper = nullptr;
}
void CliApplicationHelper::staticCleanApplication()
{
qCDebug(logApp) << "Cleaning up application helper resources";
if (_globalHelper.exists()) {
qCDebug(logApp) << "Found global helper instance, clearing it";
_globalHelper->clear();
}
}
CliApplicationHelper *CliApplicationHelper::instance()
{
return _globalHelper->helper();
}