mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2024-10-28 08:49:35 +01:00
main: use IPC to terminate secondary client during re-install(win)
To coordinate the MSI installer with Jami's close-to-tray feature, a custom action was used to kill QtWebEngineProcess and Jami, prior to the file installation step. The close-to-tray feature makes it so the only way to terminate the app is via the systray context menu. This patch harnesses the IPC mechanism used by the run-guard, and adds a command-line option('--term') used to signal the secondary process and provoke graceful termination. The benefits are: - the app can save data before closing - system tray icons aren't left dangling after updates - QtWebEngineProcess is guaranteed to be terminated during install Gitlab: #654 Gitlab: #543 Change-Id: I79421eeab49c9ec0826010af99a364471bb81d1a
This commit is contained in:
parent
cab5a2223b
commit
5613a81ada
10 changed files with 286 additions and 226 deletions
|
@ -97,7 +97,7 @@ set(COMMON_SOURCES
|
|||
${SRC_DIR}/bannedlistmodel.cpp
|
||||
${SRC_DIR}/accountlistmodel.cpp
|
||||
${SRC_DIR}/networkmanager.cpp
|
||||
${SRC_DIR}/runguard.cpp
|
||||
${SRC_DIR}/instancemanager.cpp
|
||||
${SRC_DIR}/updatemanager.cpp
|
||||
${SRC_DIR}/main.cpp
|
||||
${SRC_DIR}/smartlistmodel.cpp
|
||||
|
@ -152,7 +152,7 @@ set(COMMON_HEADERS
|
|||
${SRC_DIR}/bannedlistmodel.h
|
||||
${SRC_DIR}/version.h
|
||||
${SRC_DIR}/accountlistmodel.h
|
||||
${SRC_DIR}/runguard.h
|
||||
${SRC_DIR}/instancemanager.h
|
||||
${SRC_DIR}/rendermanager.h
|
||||
${SRC_DIR}/connectivitymonitor.h
|
||||
${SRC_DIR}/jamiavatartheme.h
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
<?define Name="Jami (BETA)" ?>
|
||||
<?endif ?>
|
||||
|
||||
<?define ExeName="Jami"?>
|
||||
|
||||
<?if $(var.Configuration) = Release ?>
|
||||
<?define ReleaseDir="..\x64\Release"?>
|
||||
<?else?>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<Package InstallerVersion="301" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes"/>
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" MaximumUncompressedMediaSize="4" />
|
||||
|
||||
<!--Disables interaction of the package with the Restart Manager.-->
|
||||
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
|
||||
|
@ -44,7 +44,7 @@
|
|||
<WixVariable Id="WixUIDialogBmp" Value="main-banner.bmp" />
|
||||
<WixVariable Id="WixUISupportPerUser" Value="0" />
|
||||
|
||||
<CustomAction Id="removeOldJamiFiles"
|
||||
<CustomAction Id="RemoveOldJamiFiles"
|
||||
Directory="APPLICATIONFOLDER"
|
||||
ExeCommand="cmd /c "del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;""
|
||||
Execute="deferred"
|
||||
|
@ -52,8 +52,9 @@
|
|||
HideTarget="no"
|
||||
Impersonate="no"/>
|
||||
|
||||
<Property Id="QtExecCmdLine" Value='"[WindowsFolder]\System32\taskkill.exe" /F /IM QtWebEngineProcess.exe /IM Jami.exe'/>
|
||||
<CustomAction Id="JamiProcesses.TaskKill"
|
||||
<Property Id="QtExecCmdLine"
|
||||
Value='"[APPLICATIONFOLDER]/$(var.ExeName).exe" --term'/>
|
||||
<CustomAction Id="TerminateAppProcess"
|
||||
BinaryKey="WixCA"
|
||||
DllEntry="CAQuietExec"
|
||||
Execute="immediate"
|
||||
|
@ -123,8 +124,8 @@
|
|||
</InstallUISequence>
|
||||
</UI>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action='JamiProcesses.TaskKill' Before='InstallValidate'/>
|
||||
<Custom Action="removeOldJamiFiles" After="RemoveFiles" />
|
||||
<Custom Action='TerminateAppProcess' Before='InstallValidate'/>
|
||||
<Custom Action="RemoveOldJamiFiles" After="RemoveFiles" />
|
||||
<Custom Action="LaunchApplication_nonUI" After="InstallFinalize"> WIXNONUILAUNCH </Custom>
|
||||
<Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
|
||||
</InstallExecuteSequence>
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<File Id="filD6887AD9110E4A8D49143C9A8F0B5843" KeyPath="yes" Source="$(var.UcrtDir)\ucrtbase.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp9CFEE34E3A162AB05264E8B756EC1DEC" Directory="APPLICATIONFOLDER" Guid="*">
|
||||
<File Id="fileMain.exe" KeyPath="yes" Source="$(var.ReleaseDir)\Jami.exe" />
|
||||
<File Id="fileMain.exe" KeyPath="yes" Source="$(var.ReleaseDir)\$(var.ExeName).exe" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
|
208
src/instancemanager.cpp
Normal file
208
src/instancemanager.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (C) 2019-2022 Savoir-faire Linux Inc.
|
||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "instancemanager.h"
|
||||
|
||||
#include "mainapplication.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
|
||||
static QString
|
||||
generateKeyHash(const QString& key, const QString& salt)
|
||||
{
|
||||
QByteArray data;
|
||||
data.append(key.toUtf8());
|
||||
data.append(salt.toUtf8());
|
||||
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
|
||||
return data;
|
||||
}
|
||||
|
||||
class InstanceManager::Impl : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Impl)
|
||||
public:
|
||||
Impl(const QString& key, MainApplication* mainApp)
|
||||
: QObject(nullptr)
|
||||
, mainAppInstance_(mainApp)
|
||||
, key_(key)
|
||||
, memLockKey_(generateKeyHash(key, "_memLockKey"))
|
||||
, sharedmemKey_(generateKeyHash(key, "_sharedmemKey"))
|
||||
, sharedMem_(sharedmemKey_)
|
||||
, memLock_(memLockKey_, 1)
|
||||
{}
|
||||
~Impl() = default;
|
||||
|
||||
bool tryToRun()
|
||||
{
|
||||
if (isAnotherRunning()) {
|
||||
// This is a secondary instance,
|
||||
// connect to the primary instance to trigger a restore
|
||||
// then fail.
|
||||
if (connectToLocal()) {
|
||||
return false;
|
||||
}
|
||||
// If not connected, this means that the server doesn't exist
|
||||
// and the app can be relaunched (can be the case after a client crash or Ctrl+C)
|
||||
}
|
||||
|
||||
memLock_.acquire();
|
||||
const bool result = sharedMem_.create(sizeof(quint64));
|
||||
memLock_.release();
|
||||
if (!result) {
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the primary instance,
|
||||
// listen for subsequent instances.
|
||||
QLocalServer::removeServer(key_);
|
||||
server_ = new QLocalServer();
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
server_->listen(key_);
|
||||
QObject::connect(server_,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&Impl::handleIncomingConnection);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void tryToKill()
|
||||
{
|
||||
if (!isAnotherRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a secondary instance, connect to the primary
|
||||
// instance to trigger a termination then fail.
|
||||
if (!connectToLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket_->write(reinterpret_cast<const char*>(terminateSeq_.data()), 4);
|
||||
socket_->waitForBytesWritten();
|
||||
};
|
||||
|
||||
void release()
|
||||
{
|
||||
memLock_.acquire();
|
||||
if (sharedMem_.isAttached())
|
||||
sharedMem_.detach();
|
||||
memLock_.release();
|
||||
};
|
||||
|
||||
private Q_SLOTS:
|
||||
bool connectToLocal()
|
||||
{
|
||||
if (!socket_)
|
||||
socket_ = new QLocalSocket();
|
||||
if (!socket_)
|
||||
return false;
|
||||
if (socket_->state() == QLocalSocket::UnconnectedState
|
||||
|| socket_->state() == QLocalSocket::ClosingState) {
|
||||
socket_->connectToServer(key_);
|
||||
}
|
||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||
socket_->waitForConnected(connectionTimeoutMs_);
|
||||
}
|
||||
return socket_->state() == QLocalSocket::ConnectedState;
|
||||
}
|
||||
|
||||
void handleIncomingConnection()
|
||||
{
|
||||
connection_ = new QLocalSocket(this);
|
||||
connection_ = server_->nextPendingConnection();
|
||||
connect(connection_, &QLocalSocket::readyRead, this, [this] {
|
||||
QLocalSocket* clientSocket = (QLocalSocket*) sender();
|
||||
QByteArray recievedData;
|
||||
recievedData = clientSocket->readAll();
|
||||
if (recievedData == terminateSeq_) {
|
||||
qWarning() << "Received terminate signal.";
|
||||
mainAppInstance_->quit();
|
||||
}
|
||||
});
|
||||
|
||||
// Restore primary instance
|
||||
mainAppInstance_->restoreApp();
|
||||
};
|
||||
|
||||
private:
|
||||
MainApplication* mainAppInstance_;
|
||||
|
||||
const QString key_;
|
||||
const QString memLockKey_;
|
||||
const QString sharedmemKey_;
|
||||
|
||||
QSharedMemory sharedMem_;
|
||||
QSystemSemaphore memLock_;
|
||||
|
||||
QLocalSocket* socket_ {nullptr};
|
||||
QLocalServer* server_ {nullptr};
|
||||
QLocalSocket* connection_ {nullptr};
|
||||
|
||||
const int connectionTimeoutMs_ {2000};
|
||||
const QByteArray terminateSeq_ {QByteArrayLiteral("\xde\xad\xbe\xef")};
|
||||
|
||||
bool isAnotherRunning()
|
||||
{
|
||||
if (sharedMem_.isAttached())
|
||||
return false;
|
||||
|
||||
memLock_.acquire();
|
||||
const bool isRunning = sharedMem_.attach();
|
||||
if (isRunning)
|
||||
sharedMem_.detach();
|
||||
memLock_.release();
|
||||
|
||||
return isRunning;
|
||||
};
|
||||
};
|
||||
|
||||
InstanceManager::InstanceManager(MainApplication* mainApp)
|
||||
: QObject(mainApp)
|
||||
{
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData(QApplication::applicationName().toUtf8());
|
||||
appData.addData(QApplication::organizationDomain().toUtf8());
|
||||
pimpl_ = std::make_unique<Impl>(appData.result(), mainApp);
|
||||
}
|
||||
|
||||
InstanceManager::~InstanceManager()
|
||||
{
|
||||
pimpl_->release();
|
||||
}
|
||||
|
||||
bool
|
||||
InstanceManager::tryToRun()
|
||||
{
|
||||
return pimpl_->tryToRun();
|
||||
}
|
||||
|
||||
void
|
||||
InstanceManager::tryToKill()
|
||||
{
|
||||
pimpl_->tryToKill();
|
||||
}
|
||||
|
||||
#include "moc_instancemanager.cpp"
|
||||
#include "instancemanager.moc"
|
|
@ -15,45 +15,27 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Based on: https://stackoverflow.com/a/28172162
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class MainApplication;
|
||||
|
||||
class RunGuard : public QObject
|
||||
class InstanceManager final : public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
Q_DISABLE_COPY(InstanceManager)
|
||||
public:
|
||||
RunGuard(const QString& key, MainApplication* mainApp = nullptr);
|
||||
~RunGuard();
|
||||
explicit InstanceManager(MainApplication* mainApp);
|
||||
~InstanceManager();
|
||||
|
||||
bool isAnotherRunning();
|
||||
bool tryToRun();
|
||||
void release();
|
||||
|
||||
private Q_SLOTS:
|
||||
void tryRestorePrimaryInstance();
|
||||
void tryToKill();
|
||||
|
||||
private:
|
||||
MainApplication* mainAppInstance_;
|
||||
|
||||
const QString key_;
|
||||
const QString memLockKey_;
|
||||
const QString sharedmemKey_;
|
||||
|
||||
QSharedMemory sharedMem_;
|
||||
QSystemSemaphore memLock_;
|
||||
|
||||
QLocalSocket* socket_ {nullptr};
|
||||
QLocalServer* server_ {nullptr};
|
||||
|
||||
Q_DISABLE_COPY(RunGuard)
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> pimpl_;
|
||||
};
|
33
src/main.cpp
33
src/main.cpp
|
@ -1,4 +1,4 @@
|
|||
/*!
|
||||
/*
|
||||
* Copyright (C) 2015-2022 Savoir-faire Linux Inc.
|
||||
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
|
||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "mainapplication.h"
|
||||
#include "runguard.h"
|
||||
#include "instancemanager.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
@ -97,29 +97,24 @@ main(int argc, char* argv[])
|
|||
|
||||
MainApplication app(argc, newArgv);
|
||||
|
||||
/*
|
||||
* Runguard to make sure that only one instance runs at a time.
|
||||
* Note: needs to be after the creation of the application
|
||||
*/
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData(QApplication::applicationName().toUtf8());
|
||||
appData.addData(QApplication::organizationDomain().toUtf8());
|
||||
RunGuard guard(appData.result(), &app);
|
||||
if (!guard.tryToRun()) {
|
||||
// InstanceManager prevents multiple instances, and will handle
|
||||
// IPC termination requests to and from secondary instances, which
|
||||
// is used to gracefully terminate the app from an installer script
|
||||
// during an update.
|
||||
InstanceManager im(&app);
|
||||
if (app.getOpt(MainApplication::Option::TerminationRequested).toBool()) {
|
||||
qWarning() << "Attempting to terminate other instances.";
|
||||
im.tryToKill();
|
||||
return 0;
|
||||
} else if (!im.tryToRun()) {
|
||||
qWarning() << "Another instance is running.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!app.init()) {
|
||||
guard.release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exec the application.
|
||||
*/
|
||||
auto ret = app.exec();
|
||||
|
||||
guard.release();
|
||||
return ret;
|
||||
return app.exec();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -53,16 +53,6 @@
|
|||
#include <gnutls/gnutls.h>
|
||||
#endif
|
||||
|
||||
namespace opts {
|
||||
// Keys used to store command-line options.
|
||||
constexpr static const char STARTMINIMIZED[] = "STARTMINIMIZED";
|
||||
constexpr static const char DEBUG[] = "DEBUG";
|
||||
constexpr static const char DEBUGCONSOLE[] = "DEBUGCONSOLE";
|
||||
constexpr static const char DEBUGFILE[] = "DEBUGFILE";
|
||||
constexpr static const char UPDATEURL[] = "UPDATEURL";
|
||||
constexpr static const char MUTEDAEMON[] = "MUTEDAEMON";
|
||||
} // namespace opts
|
||||
|
||||
static void
|
||||
consoleDebug()
|
||||
{
|
||||
|
@ -147,6 +137,7 @@ MainApplication::fileDebug(QFile* debugFile)
|
|||
MainApplication::MainApplication(int& argc, char** argv)
|
||||
: QApplication(argc, argv)
|
||||
{
|
||||
parseArguments();
|
||||
QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
|
||||
}
|
||||
|
||||
|
@ -179,9 +170,7 @@ MainApplication::init()
|
|||
setenv("QT_QPA_PLATFORMTHEME", "gtk3", true);
|
||||
#endif
|
||||
|
||||
auto results = parseArguments();
|
||||
|
||||
if (results[opts::DEBUG].toBool()) {
|
||||
if (runOptions_[Option::Debug].toBool()) {
|
||||
consoleDebug();
|
||||
}
|
||||
|
||||
|
@ -193,9 +182,9 @@ MainApplication::init()
|
|||
gnutls_global_init();
|
||||
#endif
|
||||
|
||||
initLrc(results[opts::UPDATEURL].toString(),
|
||||
initLrc(runOptions_[Option::UpdateUrl].toString(),
|
||||
connectivityMonitor_.get(),
|
||||
results[opts::DEBUG].toBool() && !results[opts::MUTEDAEMON].toBool());
|
||||
runOptions_[Option::Debug].toBool() && !runOptions_[Option::MuteJamid].toBool());
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
using namespace Interfaces;
|
||||
|
@ -206,10 +195,12 @@ MainApplication::init()
|
|||
engine_->load(QUrl(QStringLiteral("qrc:/src/DaemonReconnectWindow.qml")));
|
||||
exec();
|
||||
|
||||
if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid()))
|
||||
if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid())) {
|
||||
qWarning() << "Can't connect to the daemon via D-Bus.";
|
||||
return false;
|
||||
else
|
||||
} else {
|
||||
engine_.reset(new QQmlApplicationEngine());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -228,14 +219,14 @@ MainApplication::init()
|
|||
[this] { engine_->quit(); },
|
||||
Qt::DirectConnection);
|
||||
|
||||
if (results[opts::DEBUGFILE].toBool()) {
|
||||
if (runOptions_[Option::DebugToFile].toBool()) {
|
||||
debugFile_.reset(new QFile(getDebugFilePath()));
|
||||
debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
debugFile_->close();
|
||||
fileDebug(debugFile_.get());
|
||||
}
|
||||
|
||||
if (results[opts::DEBUGCONSOLE].toBool()) {
|
||||
if (runOptions_[Option::DebugToConsole].toBool()) {
|
||||
vsConsoleDebug();
|
||||
}
|
||||
|
||||
|
@ -253,7 +244,7 @@ MainApplication::init()
|
|||
initQmlLayer();
|
||||
|
||||
settingsManager_->setValue(Settings::Key::StartMinimized,
|
||||
results[opts::STARTMINIMIZED].toBool());
|
||||
runOptions_[Option::StartMinimized].toBool());
|
||||
|
||||
initSystray();
|
||||
|
||||
|
@ -296,10 +287,9 @@ MainApplication::initLrc(const QString& downloadUrl, ConnectivityMonitor* cm, bo
|
|||
lrcInstance_->subscribeToDebugReceived();
|
||||
}
|
||||
|
||||
const QVariantMap
|
||||
void
|
||||
MainApplication::parseArguments()
|
||||
{
|
||||
QVariantMap results;
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
@ -325,32 +315,34 @@ MainApplication::parseArguments()
|
|||
QCommandLineOption debugOption({"d", "debug"}, "Debug out.");
|
||||
parser.addOption(debugOption);
|
||||
|
||||
QCommandLineOption debugFileOption({"f", "file"}, "Debug to file.");
|
||||
parser.addOption(debugFileOption);
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QCommandLineOption debugConsoleOption({"c", "console"}, "Debug out to IDE console.");
|
||||
parser.addOption(debugConsoleOption);
|
||||
|
||||
QCommandLineOption debugFileOption({"f", "file"}, "Debug to file.");
|
||||
parser.addOption(debugFileOption);
|
||||
|
||||
QCommandLineOption updateUrlOption({"u", "url"}, "<url> for debugging version queries.", "url");
|
||||
parser.addOption(updateUrlOption);
|
||||
|
||||
#endif
|
||||
QCommandLineOption terminateOption({"t", "term"}, "Terminate all instances.");
|
||||
parser.addOption(terminateOption);
|
||||
|
||||
QCommandLineOption muteDaemonOption({"q", "quiet"}, "Mute daemon logging. (only if debug)");
|
||||
parser.addOption(muteDaemonOption);
|
||||
|
||||
parser.process(*this);
|
||||
|
||||
results[opts::STARTMINIMIZED] = parser.isSet(minimizedOption);
|
||||
results[opts::DEBUG] = parser.isSet(debugOption);
|
||||
runOptions_[Option::StartMinimized] = parser.isSet(minimizedOption);
|
||||
runOptions_[Option::Debug] = parser.isSet(debugOption);
|
||||
runOptions_[Option::DebugToFile] = parser.isSet(debugFileOption);
|
||||
#ifdef Q_OS_WINDOWS
|
||||
results[opts::DEBUGCONSOLE] = parser.isSet(debugConsoleOption);
|
||||
results[opts::DEBUGFILE] = parser.isSet(debugFileOption);
|
||||
results[opts::UPDATEURL] = parser.value(updateUrlOption);
|
||||
runOptions_[Option::DebugToConsole] = parser.isSet(debugConsoleOption);
|
||||
runOptions_[Option::UpdateUrl] = parser.value(updateUrlOption);
|
||||
#endif
|
||||
results[opts::MUTEDAEMON] = parser.isSet(muteDaemonOption);
|
||||
|
||||
return results;
|
||||
runOptions_[Option::TerminationRequested] = parser.isSet(terminateOption);
|
||||
runOptions_[Option::MuteJamid] = parser.isSet(muteDaemonOption);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -57,6 +57,7 @@ private:
|
|||
class MainApplication : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MainApplication)
|
||||
|
||||
public:
|
||||
explicit MainApplication(int& argc, char** argv);
|
||||
|
@ -65,21 +66,36 @@ public:
|
|||
bool init();
|
||||
void restoreApp();
|
||||
|
||||
enum class Option {
|
||||
StartMinimized = 0,
|
||||
Debug,
|
||||
DebugToConsole,
|
||||
DebugToFile,
|
||||
UpdateUrl,
|
||||
MuteJamid,
|
||||
TerminationRequested
|
||||
};
|
||||
QVariant getOpt(const Option opt)
|
||||
{
|
||||
return runOptions_[opt];
|
||||
};
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequested();
|
||||
|
||||
private:
|
||||
void vsConsoleDebug();
|
||||
void fileDebug(QFile* debugFile);
|
||||
|
||||
void initLrc(const QString& downloadUrl, ConnectivityMonitor* cm, bool logDaemon);
|
||||
const QVariantMap parseArguments();
|
||||
void parseArguments();
|
||||
void setApplicationFont();
|
||||
void initQmlLayer();
|
||||
void initSystray();
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
std::map<Option, QVariant> runOptions_;
|
||||
|
||||
QScopedPointer<QFile> debugFile_;
|
||||
QScopedPointer<QQmlApplicationEngine> engine_;
|
||||
QScopedPointer<LRCInstance> lrcInstance_;
|
||||
|
|
136
src/runguard.cpp
136
src/runguard.cpp
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2019-2022 Savoir-faire Linux Inc.
|
||||
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Based on: https://stackoverflow.com/a/28172162
|
||||
|
||||
#include "runguard.h"
|
||||
|
||||
#include "mainapplication.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalSocket>
|
||||
|
||||
namespace {
|
||||
|
||||
QString
|
||||
generateKeyHash(const QString& key, const QString& salt)
|
||||
{
|
||||
QByteArray data;
|
||||
|
||||
data.append(key.toUtf8());
|
||||
data.append(salt.toUtf8());
|
||||
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RunGuard::RunGuard(const QString& key, MainApplication* mainApp)
|
||||
: key_(key)
|
||||
, memLockKey_(generateKeyHash(key, "_memLockKey"))
|
||||
, sharedmemKey_(generateKeyHash(key, "_sharedmemKey"))
|
||||
, sharedMem_(sharedmemKey_)
|
||||
, memLock_(memLockKey_, 1)
|
||||
, mainAppInstance_(mainApp)
|
||||
{}
|
||||
|
||||
RunGuard::~RunGuard()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void
|
||||
RunGuard::tryRestorePrimaryInstance()
|
||||
{
|
||||
mainAppInstance_->restoreApp();
|
||||
}
|
||||
|
||||
bool
|
||||
RunGuard::isAnotherRunning()
|
||||
{
|
||||
if (sharedMem_.isAttached())
|
||||
return false;
|
||||
|
||||
memLock_.acquire();
|
||||
const bool isRunning = sharedMem_.attach();
|
||||
if (isRunning)
|
||||
sharedMem_.detach();
|
||||
memLock_.release();
|
||||
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
bool
|
||||
RunGuard::tryToRun()
|
||||
{
|
||||
if (isAnotherRunning()) {
|
||||
/*
|
||||
* This is a secondary instance,
|
||||
* connect to the primary instance to trigger a restore
|
||||
* then fail.
|
||||
*/
|
||||
if (!socket_)
|
||||
socket_ = new QLocalSocket();
|
||||
if (!socket_)
|
||||
return false;
|
||||
if (socket_->state() == QLocalSocket::UnconnectedState
|
||||
|| socket_->state() == QLocalSocket::ClosingState) {
|
||||
socket_->connectToServer(key_);
|
||||
}
|
||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||
socket_->waitForConnected();
|
||||
}
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) {
|
||||
return false;
|
||||
}
|
||||
// If not connected, this means that the server doesn't exists
|
||||
// and the app can be relaunched (can be the case after a client crash or Ctrl+C)
|
||||
}
|
||||
|
||||
memLock_.acquire();
|
||||
const bool result = sharedMem_.create(sizeof(quint64));
|
||||
memLock_.release();
|
||||
if (!result) {
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the primary instance,
|
||||
* listen for subsequent instances.
|
||||
*/
|
||||
QLocalServer::removeServer(key_);
|
||||
server_ = new QLocalServer();
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
server_->listen(key_);
|
||||
QObject::connect(server_,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&RunGuard::tryRestorePrimaryInstance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
RunGuard::release()
|
||||
{
|
||||
memLock_.acquire();
|
||||
if (sharedMem_.isAttached())
|
||||
sharedMem_.detach();
|
||||
memLock_.release();
|
||||
}
|
Loading…
Reference in a new issue