diff options
author | Martin Kampas <martin.kampas@jolla.com> | 2016-09-28 09:19:15 +0200 |
---|---|---|
committer | Juergen Bocklage-Ryannel <juergen.bocklage-ryannel@pelagicore.com> | 2016-10-07 07:01:02 +0000 |
commit | dc23b1884a606cc4d34c8480deeac82599630e04 (patch) | |
tree | 502f1300b5255e3794e39337aa6784bbb4f5a25b | |
parent | e9be3efe9a53aa36d55ce24dfe3b243cbe0ea840 (diff) |
Bench: Singletonize
Make bench a singleton application. Attempt to launch another instance
will result in activation of the existing instance, forwarding command
line arguments whenever it makes sense.
Change-Id: Ia83091e9bf092df1c8557924fb37373227ef07b9
Reviewed-by: Juergen Bocklage-Ryannel <juergen.bocklage-ryannel@pelagicore.com>
-rw-r--r-- | src/bench/hostmodel.cpp | 10 | ||||
-rw-r--r-- | src/bench/hostmodel.h | 1 | ||||
-rw-r--r-- | src/bench/main.cpp | 304 | ||||
-rw-r--r-- | src/bench/mainwindow.cpp | 32 | ||||
-rw-r--r-- | src/bench/mainwindow.h | 10 | ||||
-rw-r--r-- | src/bench/options.cpp | 24 | ||||
-rw-r--r-- | src/bench/options.h | 11 |
7 files changed, 334 insertions, 58 deletions
diff --git a/src/bench/hostmodel.cpp b/src/bench/hostmodel.cpp index a94b37e..aad68da 100644 --- a/src/bench/hostmodel.cpp +++ b/src/bench/hostmodel.cpp @@ -188,6 +188,16 @@ QList<Host *> HostModel::findByAutoDiscoveryId(QUuid id) const return hosts; } +Host *HostModel::host(const QString &name) const +{ + foreach (Host *host, m_hosts) { + if (host->name() == name) + return host; + } + + return 0; +} + Host *HostModel::hostAt(int index) const { if (index < 0 || index >= m_hosts.count()) diff --git a/src/bench/hostmodel.h b/src/bench/hostmodel.h index 1a1aadf..a03d00b 100644 --- a/src/bench/hostmodel.h +++ b/src/bench/hostmodel.h @@ -61,6 +61,7 @@ public: QList<Host*> findByAutoDiscoveryId(QUuid id) const; + Host* host(const QString &name) const; Host* hostAt(int index) const; void restoreFromSettings(QSettings* s); diff --git a/src/bench/main.cpp b/src/bench/main.cpp index a111bf2..5c98d01 100644 --- a/src/bench/main.cpp +++ b/src/bench/main.cpp @@ -37,13 +37,120 @@ #include "mainwindow.h" #include "qmllive_version.h" -static void setDarkStyle(QApplication *app) +class Application : public QApplication +{ + Q_OBJECT + +public: + static Application *create(int &argc, char **argv); + +protected: + Application(int &argc, char **argv); + static bool isMaster(); + + QString serverName() const; + void setDarkStyle(); + void parseArguments(const QStringList &arguments, Options *options); +}; + +class MasterApplication : public Application +{ + Q_OBJECT + +public: + MasterApplication(int &argc, char **argv); + +private: + void listenForArguments(); + void applyOptions(const Options &options); + +private: + QPointer<MainWindow> m_window; +}; + +class SlaveApplication : public Application +{ + Q_OBJECT + +public: + SlaveApplication(int &argc, char **argv); + +private: + void warnAboutIgnoredOptions(const Options &options); + void forwardArguments(); +}; + +Application *Application::create(int &argc, char **argv) +{ + setApplicationName("QmlLiveBench"); + setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN)); + setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME)); + + if (isMaster()) + return new MasterApplication(argc, argv); + else + return new SlaveApplication(argc, argv); +} + +Application::Application(int &argc, char **argv) + : QApplication(argc, argv) +{ + setAttribute(Qt::AA_NativeWindows, true); + setAttribute(Qt::AA_ImmediateWidgetCreation, true); + + setDarkStyle(); +} + +bool Application::isMaster() +{ + Q_ASSERT(!applicationName().isEmpty()); + Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty()); + + static QSharedMemory *lock = 0; + static bool retv = false; + + if (lock != 0) + return retv; + + const QString key = QString::fromLatin1("%1.%2-lock") + .arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain()) + .arg(applicationName()); + + lock = new QSharedMemory(key, qApp); + +#ifdef Q_OS_UNIX + // Ensure there is no stale shared memory segment after crash - call QSharedMemory destructor + { QSharedMemory(key).attach(); } +#endif + + if (lock->attach(QSharedMemory::ReadOnly)) { + lock->detach(); + return retv = false; + } + + if (!lock->create(1)) + return retv = false; + + return retv = true; +} + +QString Application::serverName() const +{ + Q_ASSERT(!applicationName().isEmpty()); + Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty()); + + return QString::fromLatin1("%1.%2-app") + .arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain()) + .arg(applicationName()); +} + +void Application::setDarkStyle() { QStyle *style = QStyleFactory::create("fusion"); if (!style) { return; } - app->setStyle(style); + setStyle(style); QPalette palette; palette.setColor(QPalette::Window, QColor("#3D3D3D")); @@ -58,10 +165,10 @@ static void setDarkStyle(QApplication *app) palette.setColor(QPalette::BrightText, QColor("#D0021B")); palette.setColor(QPalette::Highlight, QColor("#F19300")); palette.setColor(QPalette::HighlightedText, QColor("#1C1C1C")); - app->setPalette(palette); + setPalette(palette); } -static void parseArguments(const QStringList& arguments, Options *options) +void Application::parseArguments(const QStringList &arguments, Options *options) { QCommandLineParser parser; parser.setApplicationDescription("QmlLive reloading workbench"); @@ -78,9 +185,12 @@ static void parseArguments(const QStringList& arguments, Options *options) parser.addOption(stayOnTopOption); QCommandLineOption addHostOption("addhost", "add or update remote host configuration and exit", "name,address[,port]"); parser.addOption(addHostOption); + QCommandLineOption remoteOnlyOption("remoteonly", "talk to a running bench, do nothing if none is running."); + parser.addOption(remoteOnlyOption); parser.process(arguments); + options->setRemoteOnly(parser.isSet(remoteOnlyOption)); options->setPluginPath(parser.value(pluginPathOption)); options->setImportPaths(parser.values(importPathOption)); options->setStayOnTop(parser.isSet(stayOnTopOption)); @@ -147,31 +257,179 @@ static void parseArguments(const QStringList& arguments, Options *options) } } -int main(int argc, char** argv) +/* + * class MasterApplication + */ + +MasterApplication::MasterApplication(int &argc, char **argv) + : Application(argc, argv) + , m_window(new MainWindow) +{ + Options options; + parseArguments(arguments(), &options); + + if (options.remoteOnly()) { + QTimer::singleShot(0, this, &QCoreApplication::quit); + return; + } + + applyOptions(options); + + if (options.hasNoninteractiveOptions()) { + QTimer::singleShot(0, this, &QCoreApplication::quit); + } else { + m_window->init(); + m_window->show(); + listenForArguments(); + } +} + +void MasterApplication::listenForArguments() +{ + QLocalServer *server = new QLocalServer(this); + + // Remove possibly stale server socket + QLocalServer::removeServer(serverName()); + + if (!server->listen(serverName())) { + qWarning() << "Failed to listen on local socket: " << server->errorString(); + return; + } + + auto handleConnection = [this](QLocalSocket *connection) { + auto QLocalSocket_error = static_cast<void (QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error); + connect(connection, QLocalSocket_error, this, [connection]() { + qWarning() << "Error receiving arguments:" << connection->errorString(); + connection->close(); + }); + + connect(connection, &QLocalSocket::readyRead, this, [this, connection]() { + QStringList arguments; + + QDataStream in(connection); + in.startTransaction(); + in >> arguments; + if (!in.commitTransaction()) + return; + + Options options; + parseArguments(arguments, &options); + applyOptions(options); + + connection->close(); + + if (!options.hasNoninteractiveOptions() && !options.remoteOnly()) + m_window->activateWindow(); + }); + + connect(connection, &QLocalSocket::disconnected, connection, &QObject::deleteLater); + }; + + connect(server, &QLocalServer::newConnection, this, [this, server, handleConnection]() { + while (QLocalSocket *connection = server->nextPendingConnection()) + handleConnection(connection); + }); +} + +void MasterApplication::applyOptions(const Options &options) { - QApplication app(argc, argv); - app.setApplicationName("QmlLiveBench"); - app.setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN)); - app.setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME)); - app.setAttribute(Qt::AA_NativeWindows, true); - app.setAttribute(Qt::AA_ImmediateWidgetCreation, true); + if (!options.workspace().isEmpty()) + m_window->setWorkspace(QDir(options.workspace()).absolutePath()); + + if (!options.pluginPath().isEmpty()) { + if (!m_window->isInitialized()) + m_window->setPluginPath(QDir(options.pluginPath()).absolutePath()); + else + qDebug() << "Ignoring attempt to set plugin path after initialization."; + } + + if (!options.importPaths().isEmpty()) { + if (!m_window->isInitialized()) + m_window->setImportPaths(options.importPaths()); + else + qDebug() << "Ignoring attempt to set import paths after initialization."; + } - setDarkStyle(&app); + if (!options.activeDocument().isEmpty()) { + m_window->activateDocument(options.activeDocument()); + } - Options *options = Options::instance(); - parseArguments(app.arguments(), options); + if (options.stayOnTop()) { + m_window->setStaysOnTop(true); + } - if (!options->hostsToAdd().isEmpty()) { - QSettings s; - foreach (const Options::HostOptions &host, options->hostsToAdd()) { - HostModel::addOrUpdateHost(&s, host.name, host.address, host.port); + if (!options.hostsToAdd().isEmpty()) { + if (!m_window->isInitialized()) { + QSettings s; + foreach (const Options::HostOptions &host, options.hostsToAdd()) { + HostModel::addOrUpdateHost(&s, host.name, host.address, host.port); + } + } else { + foreach (const Options::HostOptions &hostOptions, options.hostsToAdd()) { + Host *host = m_window->hostModel()->host(hostOptions.name); + if (host == 0) { + host = new Host; + host->setName(hostOptions.name); + m_window->hostModel()->addHost(host); + } + host->setAddress(hostOptions.address); + host->setPort(hostOptions.port); + } } - return EXIT_SUCCESS; } +} + +/* + * class SlaveApplication + */ + +SlaveApplication::SlaveApplication(int &argc, char **argv) + : Application(argc, argv) +{ + Options options; + parseArguments(arguments(), &options); + + if (!options.remoteOnly() && !options.hasNoninteractiveOptions()) + qInfo() << "Another instance running. Activating..."; + + warnAboutIgnoredOptions(options); + forwardArguments(); +} - MainWindow win; - win.init(options); // Parse and apply command line and settings file options - win.show(); +void SlaveApplication::warnAboutIgnoredOptions(const Options &options) +{ + if (!options.pluginPath().isEmpty()) + qWarning() << "Ignoring --pluginpath option"; - return app.exec(); + if (!options.importPaths().isEmpty()) + qWarning() << "Ignoring --importpaths option"; } + +void SlaveApplication::forwardArguments() +{ + QLocalSocket *socket = new QLocalSocket(this); + + auto QLocalSocket_error = static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error); + connect(socket, QLocalSocket_error, this, [socket]() { + qCritical() << "Error forwarding arguments:" << socket->errorString(); + exit(1); + }); + + connect(socket, &QLocalSocket::connected, this, [socket]() { + QDataStream out(socket); + out << arguments(); + socket->disconnectFromServer(); + }); + + connect(socket, &QLocalSocket::disconnected, this, &QCoreApplication::quit); + + socket->connectToServer(serverName()); +} + +int main(int argc, char** argv) +{ + QScopedPointer<Application> app(Application::create(argc, argv)); + return app->exec(); +} + +#include "main.moc" diff --git a/src/bench/mainwindow.cpp b/src/bench/mainwindow.cpp index 4de00a0..ac418c9 100644 --- a/src/bench/mainwindow.cpp +++ b/src/bench/mainwindow.cpp @@ -52,6 +52,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) + , m_initialized(false) , m_workspace(new WorkspaceView()) , m_log(new LogView(true, this)) , m_hostManager(new HostManager(this)) @@ -209,31 +210,13 @@ void MainWindow::setupMenuBar() about->setMenuRole(QAction::AboutRole); } -void MainWindow::init(Options *options) +void MainWindow::init() { - Q_ASSERT(options); - if (!options->workspace().isEmpty()) { - setWorkspace(QDir(options->workspace()).absolutePath()); - } - if (!options->pluginPath().isEmpty()) { - setPluginPath(QDir(options->pluginPath()).absolutePath()); - } - if (!options->importPaths().isEmpty()) { - setImportPaths(options->importPaths()); - } - if (!options->activeDocument().isEmpty()) { - activateDocument(options->activeDocument()); - } - - if (options->stayOnTop()) { - setStaysOnTop(true); - } - QSettings s; restoreGeometry(s.value("geometry").toByteArray()); //Only set the workspace if we didn't already set it by command line - if (options->workspace().isNull()) { - setWorkspace(s.value("workspace").toString()); + if (m_workspacePath.isEmpty()) { + setWorkspace(s.value("workspace", QDir::currentPath()).toString()); } if (s.value("http_proxy/enabled").toBool()) { @@ -258,7 +241,7 @@ void MainWindow::init(Options *options) updateRecentFolder(); //Only set the workspace if we didn't already set it by command line - if (options->activeDocument().isNull()) { + if (m_node->activeDocument().isEmpty()) { if (s.contains("activeDocument")) { activateDocument(s.value("activeDocument").toString()); } else { @@ -270,6 +253,8 @@ void MainWindow::init(Options *options) m_hostModel->restoreFromSettings(&s); restoreState(s.value("windowState").toByteArray()); + + m_initialized = true; } void MainWindow::writeSettings() @@ -277,7 +262,7 @@ void MainWindow::writeSettings() QSettings s; s.setValue("geometry", saveGeometry()); s.setValue("windowState", saveState()); - s.setValue("workspace", m_hub->workspace()); + s.setValue("workspace", m_workspacePath); s.setValue("activeDocument", m_node->activeDocument().toLocalFile()); s.beginWriteArray("recentFolder"); @@ -355,6 +340,7 @@ void MainWindow::slowDownAnimations(bool enable) void MainWindow::setWorkspace(const QString& path) { + m_workspacePath = path; m_workspace->setRootPath(path); m_node->setWorkspace(path); m_hub->setWorkspace(path); diff --git a/src/bench/mainwindow.h b/src/bench/mainwindow.h index 88cd398..7b85c21 100644 --- a/src/bench/mainwindow.h +++ b/src/bench/mainwindow.h @@ -62,7 +62,11 @@ public: void setPluginPath(const QString& path); void setImportPaths(const QStringList& pathList); void setStaysOnTop(bool enabled); - void init(Options *options); + void init(); + bool isInitialized() const { return m_initialized; } + + HostModel *hostModel() const { return m_hostModel; } + protected: void closeEvent(QCloseEvent *event); void showEvent(QShowEvent *event); @@ -93,8 +97,12 @@ private slots: private: void updateRecentFolder(const QString &path = QString()); + +private: + bool m_initialized; WindowWidget *m_ww; WorkspaceView *m_workspace; + QString m_workspacePath; LogView *m_log; QUrl m_currentSource; QDockWidget *m_logDock; diff --git a/src/bench/options.cpp b/src/bench/options.cpp index 13549c1..fa0102f 100644 --- a/src/bench/options.cpp +++ b/src/bench/options.cpp @@ -31,20 +31,30 @@ #include "options.h" -Options *Options::s_instance(nullptr); - Options::Options(QObject *parent) : QObject(parent) + , m_remoteOnly(false) + , m_stayOnTop(false) +{ + +} +bool Options::hasNoninteractiveOptions() const { + if (!m_hostsToAdd.isEmpty()) + return true; + return false; } -Options *Options::instance() { - if (!s_instance) { - s_instance = new Options(QCoreApplication::instance()); - } - return s_instance; +bool Options::remoteOnly() const +{ + return m_remoteOnly; +} + +void Options::setRemoteOnly(bool remoteOnly) +{ + m_remoteOnly = remoteOnly; } QString Options::activeDocument() const diff --git a/src/bench/options.h b/src/bench/options.h index 93c6a0b..f253cbd 100644 --- a/src/bench/options.h +++ b/src/bench/options.h @@ -44,10 +44,13 @@ public: int port = 10234; }; -private: - explicit Options(QObject *parent = 0); public: - static Options *instance(); + explicit Options(QObject *parent = 0); + + bool hasNoninteractiveOptions() const; + + bool remoteOnly() const; + void setRemoteOnly(bool remoteOnly); QString activeDocument() const; void setActiveDocument(const QString &activeDocument); @@ -70,7 +73,7 @@ public: void addHostToAdd(const HostOptions &hostOptions); private: - static Options *s_instance; + bool m_remoteOnly; QString m_activeDocument; QString m_workspace; QString m_pluginPath; |