Improve startup/shutdown behavior; add `--replace` CLI option
* Make functions in `main.cpp` static as they are not used by other units * Delete the `TrayIcon` via an extra parent object that is deleted before the `QNetworkAccessManager` is deleted; otherwise the destruction of `SyncthingConnection` (which aborts pending replies) might access dangling `QNetworkReply` objects * Improve error handling in `SingleInstance` code * Allow to replace the current instance via the new `--replace` argument; this may be useful when creating an installer/updater
This commit is contained in:
parent
0c733837ce
commit
c9cd81311d
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/application/commandlineutils.h>
|
||||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
#include <c++utilities/misc/parseerror.h>
|
||||
|
||||
#include <qtutilities/misc/dialogutils.h>
|
||||
|
@ -52,7 +53,7 @@ Q_IMPORT_PLUGIN(ForkAwesomeIconEnginePlugin)
|
|||
ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES
|
||||
|
||||
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
||||
void handleSystemdServiceError(const QString &context, const QString &name, const QString &message)
|
||||
static void handleSystemdServiceError(const QString &context, const QString &name, const QString &message)
|
||||
{
|
||||
auto *const msgBox = new QMessageBox;
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
@ -63,7 +64,9 @@ void handleSystemdServiceError(const QString &context, const QString &name, cons
|
|||
}
|
||||
#endif
|
||||
|
||||
int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectionConfigArg)
|
||||
QObject *parentObject = nullptr;
|
||||
|
||||
static int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectionConfigArg)
|
||||
{
|
||||
// get settings
|
||||
auto &settings = Settings::values();
|
||||
|
@ -103,7 +106,7 @@ int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectio
|
|||
// show a tray icon for each connection
|
||||
TrayWidget *widget;
|
||||
for (const auto *const connectionConfig : connectionConfigurations) {
|
||||
auto *const trayIcon = new TrayIcon(QString::fromLocal8Bit(connectionConfig), QApplication::instance());
|
||||
auto *const trayIcon = new TrayIcon(QString::fromLocal8Bit(connectionConfig), parentObject);
|
||||
trayIcon->show();
|
||||
widget = &trayIcon->trayMenu().widget();
|
||||
}
|
||||
|
@ -141,7 +144,7 @@ static void trigger(bool tray, bool webUi, bool wizard)
|
|||
}
|
||||
}
|
||||
|
||||
void shutdownSyncthingTray()
|
||||
static void shutdownSyncthingTray()
|
||||
{
|
||||
Settings::save();
|
||||
if (const auto &error = Settings::values().error; !error.isEmpty()) {
|
||||
|
@ -150,7 +153,7 @@ void shutdownSyncthingTray()
|
|||
Settings::Launcher::terminate();
|
||||
}
|
||||
|
||||
int runApplication(int argc, const char *const *argv)
|
||||
static int runApplication(int argc, const char *const *argv)
|
||||
{
|
||||
// setup argument parser
|
||||
SET_APPLICATION_INFO;
|
||||
|
@ -174,9 +177,12 @@ int runApplication(int argc, const char *const *argv)
|
|||
configPathArg.setEnvironmentVariable(PROJECT_VARNAME_UPPER "_CONFIG_DIR");
|
||||
auto singleInstanceArg = Argument("single-instance", '\0', "does nothing if a tray icon is already shown");
|
||||
auto newInstanceArg = Argument("new-instance", '\0', "disable the usual single-process behavior");
|
||||
auto replaceArg = Argument("replace", '\0', "replaces a currently running instance");
|
||||
auto quitArg = OperationArgument("quit", '\0', "quits the currently running instance");
|
||||
quitArg.setFlags(Argument::Flags::Deprecated, true); // hide as only used internally for --replace
|
||||
auto &widgetsGuiArg = qtConfigArgs.qtWidgetsGuiArg();
|
||||
widgetsGuiArg.addSubArguments({ &windowedArg, &showWebUiArg, &triggerArg, &waitForTrayArg, &connectionArg, &configPathArg, &singleInstanceArg,
|
||||
&newInstanceArg, &showWizardArg, &assumeFirstLaunchArg, &wipArg });
|
||||
&newInstanceArg, &replaceArg, &showWizardArg, &assumeFirstLaunchArg, &wipArg });
|
||||
#ifdef SYNCTHINGTRAY_USE_LIBSYNCTHING
|
||||
auto cliArg = OperationArgument("cli", 'c', "run Syncthing's CLI");
|
||||
auto cliHelp = ConfigValueArgument("help", 'h', "show help for Syncthing's CLI");
|
||||
|
@ -189,7 +195,9 @@ int runApplication(int argc, const char *const *argv)
|
|||
#ifdef SYNCTHINGTRAY_USE_LIBSYNCTHING
|
||||
&cliArg,
|
||||
#endif
|
||||
&parser.noColorArg(), &parser.helpArg() });
|
||||
&parser.noColorArg(), &parser.helpArg(), &quitArg });
|
||||
|
||||
// parse arguments
|
||||
parser.parseArgs(argc, argv);
|
||||
#ifdef SYNCTHINGTRAY_USE_LIBSYNCTHING
|
||||
if (cliArg.isPresent()) {
|
||||
|
@ -197,8 +205,18 @@ int runApplication(int argc, const char *const *argv)
|
|||
return static_cast<int>(LibSyncthing::runCli(cliArg.values()));
|
||||
}
|
||||
#endif
|
||||
|
||||
// quit already running application if quit is present
|
||||
static auto firstRun = true;
|
||||
if (quitArg.isPresent() && !firstRun) {
|
||||
std::cerr << EscapeCodes::Phrases::Info << "Quitting as told by another instance" << EscapeCodes::Phrases::EndFlush;
|
||||
QCoreApplication::quit();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// quit unless Qt Widgets GUI should be shown
|
||||
if (!qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
|
||||
return 0;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// handle override for config dir
|
||||
|
@ -206,16 +224,18 @@ int runApplication(int argc, const char *const *argv)
|
|||
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QString::fromLocal8Bit(configPathDir));
|
||||
}
|
||||
|
||||
// check whether runApplication() has been called for the first time
|
||||
static auto firstRun = true;
|
||||
// do first-time initializations
|
||||
if (firstRun) {
|
||||
firstRun = false;
|
||||
|
||||
// do first-time initializations
|
||||
SET_QT_APPLICATION_INFO;
|
||||
QApplication application(argc, const_cast<char **>(argv));
|
||||
auto application = QApplication(argc, const_cast<char **>(argv));
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
SingleInstance singleInstance(argc, argv, newInstanceArg.isPresent());
|
||||
// stop possibly running instance if --replace is present
|
||||
if (replaceArg.isPresent()) {
|
||||
const char *const argv[] = { parser.executable(), quitArg.name() };
|
||||
SingleInstance::passArgsToRunningInstance(2, argv, SingleInstance::applicationId(), true);
|
||||
}
|
||||
auto singleInstance = SingleInstance(argc, argv, newInstanceArg.isPresent(), replaceArg.isPresent());
|
||||
networkAccessManager().setParent(&singleInstance);
|
||||
QObject::connect(&singleInstance, &SingleInstance::newInstance, &runApplication);
|
||||
Settings::restore();
|
||||
|
@ -233,10 +253,10 @@ int runApplication(int argc, const char *const *argv)
|
|||
if (!settings.error.isEmpty()) {
|
||||
QMessageBox::critical(nullptr, QCoreApplication::applicationName(), settings.error);
|
||||
}
|
||||
SyncthingLauncher launcher;
|
||||
auto launcher = SyncthingLauncher();
|
||||
SyncthingLauncher::setMainInstance(&launcher);
|
||||
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
||||
SyncthingService service;
|
||||
auto service = SyncthingService();
|
||||
SyncthingService::setMainInstance(&service);
|
||||
settings.systemd.setupService(service);
|
||||
QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError);
|
||||
|
@ -246,6 +266,8 @@ int runApplication(int argc, const char *const *argv)
|
|||
}
|
||||
|
||||
// init Syncthing Tray and immediately shutdown on failure
|
||||
auto parent = QObject();
|
||||
parentObject = &parent;
|
||||
if (const auto res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg)) {
|
||||
shutdownSyncthingTray();
|
||||
return res;
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QStringBuilder>
|
||||
#include <QThread>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
@ -64,57 +66,81 @@ static QString getCurrentProcessSIDAsString()
|
|||
}
|
||||
#endif
|
||||
|
||||
SingleInstance::SingleInstance(int argc, const char *const *argv, bool newInstance, QObject *parent)
|
||||
SingleInstance::SingleInstance(int argc, const char *const *argv, bool skipSingleInstanceBehavior, bool skipPassing, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_server(nullptr)
|
||||
{
|
||||
if (newInstance) {
|
||||
// just do nothing if supposed to skip single instance behavior
|
||||
if (skipSingleInstanceBehavior) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for running instance
|
||||
static const auto appId = QString(QCoreApplication::applicationName() % QChar('-') % QCoreApplication::organizationName() % QChar('-') %
|
||||
// check for running instance; if there is one pass parameters and exit
|
||||
static const auto appId = applicationId();
|
||||
if (!skipPassing && passArgsToRunningInstance(argc, argv, appId)) {
|
||||
std::exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// create local server; at this point no previous instance is running anymore
|
||||
// -> cleanup possible leftover (previous instance might have crashed)
|
||||
QLocalServer::removeServer(appId);
|
||||
// -> setup server
|
||||
m_server = new QLocalServer(this);
|
||||
connect(m_server, &QLocalServer::newConnection, this, &SingleInstance::handleNewConnection);
|
||||
if (!m_server->listen(appId)) {
|
||||
cerr << Phrases::Error << "Unable to launch as single instance application as " << appId.toStdString() << Phrases::EndFlush;
|
||||
} else {
|
||||
cerr << Phrases::Info << "Single instance application ID: " << appId.toStdString() << Phrases::EndFlush;
|
||||
}
|
||||
}
|
||||
|
||||
const QString &SingleInstance::applicationId()
|
||||
{
|
||||
static const auto id = QString(QCoreApplication::applicationName() % QChar('-') % QCoreApplication::organizationName() % QChar('-') %
|
||||
#ifdef Q_OS_WINDOWS
|
||||
getCurrentProcessSIDAsString()
|
||||
#else
|
||||
QString::number(getuid())
|
||||
#endif
|
||||
);
|
||||
passArgsToRunningInstance(argc, argv, appId);
|
||||
|
||||
// no previous instance running
|
||||
// -> however, previous server instance might not have been cleaned up dute to crash
|
||||
QLocalServer::removeServer(appId);
|
||||
// -> start server
|
||||
m_server = new QLocalServer(this);
|
||||
connect(m_server, &QLocalServer::newConnection, this, &SingleInstance::handleNewConnection);
|
||||
if (!m_server->listen(appId)) {
|
||||
cerr << Phrases::Error << "Unable to launch as single instance application as " << appId.toStdString() << Phrases::EndFlush;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void SingleInstance::passArgsToRunningInstance(int argc, const char *const *argv, const QString &appId)
|
||||
bool SingleInstance::passArgsToRunningInstance(int argc, const char *const *argv, const QString &appId, bool waitUntilGone)
|
||||
{
|
||||
QLocalSocket socket;
|
||||
socket.connectToServer(appId, QLocalSocket::ReadWrite);
|
||||
if (socket.waitForConnected(1000)) {
|
||||
cerr << Phrases::Info << "Application already running, sending args to previous instance" << Phrases::EndFlush;
|
||||
if (argc >= 0 && argc <= 0xFFFF) {
|
||||
char buffer[2];
|
||||
BE::getBytes(static_cast<std::uint16_t>(argc), buffer);
|
||||
socket.write(buffer, 2);
|
||||
*buffer = '\0';
|
||||
for (const char *const *end = argv + argc; argv != end; ++argv) {
|
||||
socket.write(*argv);
|
||||
socket.write(buffer, 1);
|
||||
}
|
||||
} else {
|
||||
cerr << Phrases::Error << "Unable to pass the specified number of arguments" << Phrases::EndFlush;
|
||||
}
|
||||
socket.flush();
|
||||
socket.close();
|
||||
exit(0);
|
||||
if (argc < 0 || argc > 0xFFFF) {
|
||||
cerr << Phrases::Error << "Unable to pass the specified number of arguments" << Phrases::EndFlush;
|
||||
return false;
|
||||
}
|
||||
auto socket = QLocalSocket();
|
||||
socket.connectToServer(appId, QLocalSocket::ReadWrite);
|
||||
const auto fullServerName = socket.fullServerName();
|
||||
if (!socket.waitForConnected(1000)) {
|
||||
return false;
|
||||
}
|
||||
cerr << Phrases::Info << "Application already running, sending args to previous instance" << Phrases::EndFlush;
|
||||
char buffer[2];
|
||||
BE::getBytes(static_cast<std::uint16_t>(argc), buffer);
|
||||
auto error = socket.write(buffer, 2) < 0;
|
||||
*buffer = '\0';
|
||||
for (const char *const *end = argv + argc; argv != end && !error; ++argv) {
|
||||
error = socket.write(*argv) < 0 || socket.write(buffer, 1) < 0;
|
||||
}
|
||||
error = error || !socket.flush();
|
||||
socket.disconnectFromServer();
|
||||
if (socket.state() != QLocalSocket::UnconnectedState) {
|
||||
error = !socket.waitForDisconnected(1000) || error;
|
||||
}
|
||||
if (error) {
|
||||
cerr << Phrases::Error << "Unable to pass args to previous instance: " << socket.errorString().toStdString() << Phrases::EndFlush;
|
||||
}
|
||||
if (waitUntilGone) {
|
||||
cerr << Phrases::Info << "Waiting for previous instance to shutdown" << Phrases::EndFlush;
|
||||
while (QFile::exists(fullServerName)) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
void SingleInstance::handleNewConnection()
|
||||
|
@ -142,7 +168,7 @@ void SingleInstance::readArgs()
|
|||
|
||||
// reconstruct argc and argv array
|
||||
const auto argc = BE::toUInt16(argData.get());
|
||||
vector<const char *> args;
|
||||
auto args = vector<const char *>();
|
||||
args.reserve(argc + 1);
|
||||
for (const char *argv = argData.get() + 2, *end = argData.get() + argDataSize, *i = argv; i != end && *argv;) {
|
||||
if (!*i) {
|
||||
|
|
|
@ -14,18 +14,21 @@ namespace QtGui {
|
|||
class SingleInstance : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SingleInstance(int argc, const char *const *argv, bool newInstance = false, QObject *parent = nullptr);
|
||||
explicit SingleInstance(
|
||||
int argc, const char *const *argv, bool skipSingleInstanceBehavior = false, bool skipPassing = false, QObject *parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
void newInstance(int argc, const char *const *argv);
|
||||
|
||||
public:
|
||||
static const QString &applicationId();
|
||||
static bool passArgsToRunningInstance(int argc, const char *const *argv, const QString &appId, bool waitUntilGone = false);
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleNewConnection();
|
||||
void readArgs();
|
||||
|
||||
private:
|
||||
void passArgsToRunningInstance(int argc, const char *const *argv, const QString &appId);
|
||||
|
||||
QLocalServer *m_server;
|
||||
};
|
||||
} // namespace QtGui
|
||||
|
|
Loading…
Reference in New Issue