Since the event loop may not be running while the core is being
initialized, calling exit() does not have the desired effect.
Instead, throw an exception with exit code and error message to
be able to handle this correctly. Exceptions must not be thrown
through the event loop, so ensure that they're caught before in
cases where the event loop was started already.
For the monolithic client, introduce an asynchronous init method
that catches potential exceptions and calls exit() instead. Show
an error popup in the UI if an error message is provided, so the
user gets some feedback before the client is terminated.
14 files changed:
+void Client::onExitRequested(int exitCode, const QString &reason)
+{
+ if (!reason.isEmpty()) {
+ qCritical() << reason;
+ emit exitRequested(reason);
+ }
+ QCoreApplication::exit(exitCode);
+}
+
+
/*** Network handling ***/
QList<NetworkId> Client::networkIds()
/*** Network handling ***/
QList<NetworkId> Client::networkIds()
//! Emitted when database schema upgrade starts or ends (only mono client)
void dbUpgradeInProgress(bool inProgress);
//! Emitted when database schema upgrade starts or ends (only mono client)
void dbUpgradeInProgress(bool inProgress);
+ //! Emitted before an exit request is handled
+ void exitRequested(const QString &reason);
+
public slots:
void disconnectFromCore();
public slots:
void disconnectFromCore();
void markBufferAsRead(BufferId id);
void onDbUpgradeInProgress(bool inProgress);
void markBufferAsRead(BufferId id);
void onDbUpgradeInProgress(bool inProgress);
+ void onExitRequested(int exitCode, const QString &reason);
private slots:
void setSyncedToCore();
private slots:
void setSyncedToCore();
#endif
#include "quassel.h"
#endif
#include "quassel.h"
int main(int argc, char **argv)
{
int main(int argc, char **argv)
{
AboutData::setQuasselPersons(&aboutData);
KAboutData::setApplicationData(aboutData.kAboutData());
#endif
AboutData::setQuasselPersons(&aboutData);
KAboutData::setApplicationData(aboutData.kAboutData());
#endif
- if (!app.init())
- return EXIT_FAILURE;
+ try {
+ app.init();
+ }
+ catch (ExitException e) {
+ if (!e.errorString.isEmpty()) {
+ qCritical() << e.errorString;
+ }
+ return e.exitCode;
+ }
value = static_cast<T>(v);
return in;
}
value = static_cast<T>(v);
return in;
}
+
+// Exceptions
+
+/**
+ * Thrown during initialization to enforce exiting the application before the event loop is started
+ */
+struct ExitException
+{
+ int exitCode;
+ QString errorString;
+
+ ExitException(int code = EXIT_FAILURE, QString error = {})
+ : exitCode(code), errorString(std::move(error)) {}
+};
#include "quassel.h"
#include "sqlauthenticator.h"
#include "sqlitestorage.h"
#include "quassel.h"
#include "sqlauthenticator.h"
#include "sqlitestorage.h"
#include "util.h"
// Currently building with LDAP bindings is optional.
#include "util.h"
// Currently building with LDAP bindings is optional.
{
_startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
{
_startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
// so far, we only have 1
CoreSettings s;
if (s.version() != 1) {
// so far, we only have 1
CoreSettings s;
if (s.version() != 1) {
- qCritical() << "Invalid core settings version, terminating!";
- QCoreApplication::exit(EXIT_FAILURE);
- return false;
+ throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
}
// Set up storage and authentication backends
}
// Set up storage and authentication backends
if (config_from_environment) {
db_backend = environment.value("DB_BACKEND");
auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
if (config_from_environment) {
db_backend = environment.value("DB_BACKEND");
auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
CoreSettings cs;
QVariantMap dbsettings = cs.storageSettings().toMap();
CoreSettings cs;
QVariantMap dbsettings = cs.storageSettings().toMap();
writeError = !cs.isWritable();
}
writeError = !cs.isWritable();
}
- // legacy
- _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
-
- // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work!
- if (_configured) {
- initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
+ try {
+ _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
+ if (_configured) {
+ _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
+ }
+ }
+ catch (ExitException) {
+ // Try again later
+ _configured = false;
}
if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
}
if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
if (Quassel::isOptionSet("select-authenticator")) {
success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
}
if (Quassel::isOptionSet("select-authenticator")) {
success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
}
- QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
- return success;
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
}
if (!_configured) {
if (config_from_environment) {
}
if (!_configured) {
if (config_from_environment) {
- _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
- initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
+ try {
+ _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
+ if (_configured) {
+ _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
+ }
+ }
+ catch (ExitException e) {
+ throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
+ }
- qWarning() << "Cannot configure from environment";
- QCoreApplication::exit(EXIT_FAILURE);
- return false;
+ throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
}
}
else {
if (_registeredStorageBackends.empty()) {
}
}
else {
if (_registeredStorageBackends.empty()) {
- quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
- quWarning()
- << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
- "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
- "to work."));
- QCoreApplication::exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup)
- return false;
+ throw ExitException{EXIT_FAILURE,
+ tr("Could not initialize any storage backend! Exiting...\n"
+ "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
+ "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
+ "to work.")};
- qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
- QCoreApplication::exit(EXIT_FAILURE);
- return false;
+ throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
}
quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
}
quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
else {
if (Quassel::isOptionSet("add-user")) {
bool success = createUser();
else {
if (Quassel::isOptionSet("add-user")) {
bool success = createUser();
- QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
- return success;
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
}
if (Quassel::isOptionSet("change-userpass")) {
bool success = changeUserPass(Quassel::optionValue("change-userpass"));
}
if (Quassel::isOptionSet("change-userpass")) {
bool success = changeUserPass(Quassel::optionValue("change-userpass"));
- QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
- return success;
+ throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
}
_strictIdentEnabled = Quassel::isOptionSet("strict-ident");
}
_strictIdentEnabled = Quassel::isOptionSet("strict-ident");
connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
if (!startListening()) {
connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
if (!startListening()) {
- QCoreApplication::exit(EXIT_FAILURE); // TODO make this less brutal
- return false;
+ throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
}
if (_configured && !Quassel::isOptionSet("norestore")) {
}
if (_configured && !Quassel::isOptionSet("norestore")) {
connectInternalPeer(_pendingInternalConnection);
_pendingInternalConnection = {};
}
connectInternalPeer(_pendingInternalConnection);
_pendingInternalConnection = {};
}
+
+void Core::initAsync()
+{
+ try {
+ init();
+ }
+ catch (ExitException e) {
+ emit exitRequested(e.exitCode, e.errorString);
+ }
/*** Session Restore ***/
void Core::saveState()
/*** Session Restore ***/
void Core::saveState()
if (adminUser.isEmpty() || adminPassword.isEmpty()) {
return tr("Admin user or password not set.");
}
if (adminUser.isEmpty() || adminPassword.isEmpty()) {
return tr("Admin user or password not set.");
}
- if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
- return tr("Could not setup storage!");
- }
+ try {
+ if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
+ return tr("Could not setup storage!");
+ }
- quInfo() << "Selected authenticator:" << authenticator;
- if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
- {
- return tr("Could not setup authenticator!");
+ quInfo() << "Selected authenticator:" << authenticator;
+ if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
+ {
+ return tr("Could not setup authenticator!");
+ }
+ }
+ catch (ExitException e) {
+ // Event loop is running, so trigger an exit rather than throwing an exception
+ QCoreApplication::exit(e.exitCode);
+ return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
}
if (!saveBackendSettings(backend, setupData)) {
}
if (!saveBackendSettings(backend, setupData)) {
return it != _registeredStorageBackends.end() ? *it : nullptr;
}
return it != _registeredStorageBackends.end() ? *it : nullptr;
}
-// old db settings:
-// "Type" => "sqlite"
bool Core::initStorage(const QString &backend, const QVariantMap &settings,
const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
{
bool Core::initStorage(const QString &backend, const QVariantMap &settings,
const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
{
return initStorage(backend, settings, environment, loadFromEnvironment, false);
return false;
return initStorage(backend, settings, environment, loadFromEnvironment, false);
return false;
- // if initialization wasn't successful, we quit to keep from coming up unconfigured
case Storage::NotAvailable:
case Storage::NotAvailable:
- qCritical() << "FATAL: Selected storage backend is not available:" << backend;
- QCoreApplication::exit(EXIT_FAILURE);
+ // If initialization wasn't successful, we quit to keep from coming up unconfigured
+ throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
+ qCritical() << "Selected storage backend is not available:" << backend;
return false;
case Storage::IsReady:
return false;
case Storage::IsReady:
return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
return false;
return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
return false;
- // if initialization wasn't successful, we quit to keep from coming up unconfigured
case Authenticator::NotAvailable:
case Authenticator::NotAvailable:
- qCritical() << "FATAL: Selected auth backend is not available:" << backend;
- QCoreApplication::exit(EXIT_FAILURE);
+ // If initialization wasn't successful, we quit to keep from coming up unconfigured
+ throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
+ qCritical() << "Selected auth backend is not available:" << backend;
return false;
case Authenticator::IsReady:
return false;
case Authenticator::IsReady:
{
if (!_configured) {
stopListening();
{
if (!_configured) {
stopListening();
- setupCoreForInternalUsage();
+ auto errorString = setupCoreForInternalUsage();
+ if (!errorString.isEmpty()) {
+ emit exitRequested(EXIT_FAILURE, errorString);
+ return;
+ }
}
else {
quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
}
else {
quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
- QCoreApplication::exit(EXIT_FAILURE);
+ emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
Core();
~Core() override;
Core();
~Core() override;
/*** Storage access ***/
// These methods are threadsafe.
/*** Storage access ***/
// These methods are threadsafe.
//! Emitted when database schema upgrade starts or ends
void dbUpgradeInProgress(bool inProgress);
//! Emitted when database schema upgrade starts or ends
void dbUpgradeInProgress(bool inProgress);
+ //! Emitted when a fatal error was encountered during async initialization
+ void exitRequested(int exitCode, const QString &reason);
+
-bool CoreApplication::init()
+void CoreApplication::init()
- if (Quassel::init()) {
- _core.reset(new Core{}); // FIXME C++14: std::make_unique
- return _core->init();
+ if (!Quassel::init()) {
+ throw ExitException{EXIT_FAILURE, tr("Could not initialize Quassel!")};
+
+ _core.reset(new Core{}); // FIXME C++14: std::make_unique
+ _core->init();
CoreApplication(int &argc, char **argv);
~CoreApplication() override;
CoreApplication(int &argc, char **argv);
~CoreApplication() override;
private:
std::unique_ptr<Core> _core;
private:
std::unique_ptr<Core> _core;
connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
connect(Client::instance(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
connect(Client::instance(), SIGNAL(dbUpgradeInProgress(bool)), SLOT(showMigrationWarning(bool)));
connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
connect(Client::instance(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
connect(Client::instance(), SIGNAL(dbUpgradeInProgress(bool)), SLOT(showMigrationWarning(bool)));
+ connect(Client::instance(), SIGNAL(exitRequested(QString)), SLOT(onExitRequested(QString)));
connect(Client::coreConnection(), SIGNAL(startCoreSetup(QVariantList, QVariantList)), SLOT(showCoreConfigWizard(QVariantList, QVariantList)));
connect(Client::coreConnection(), SIGNAL(connectionErrorPopup(QString)), SLOT(handleCoreConnectionError(QString)));
connect(Client::coreConnection(), SIGNAL(startCoreSetup(QVariantList, QVariantList)), SLOT(showCoreConfigWizard(QVariantList, QVariantList)));
connect(Client::coreConnection(), SIGNAL(connectionErrorPopup(QString)), SLOT(handleCoreConnectionError(QString)));
+void MainWin::onExitRequested(const QString &reason)
+{
+ if (!reason.isEmpty()) {
+ QMessageBox box(QMessageBox::Critical,
+ tr("Fatal error"),
+ "<b>" + tr("Quassel encountered a fatal error and is terminated.") + "</b>",
+ QMessageBox::Ok, this);
+ box.setInformativeText("<p>" + tr("Reason:<em>") + " " + reason + "</em>");
+ box.exec();
+ }
+}
+
+
void MainWin::changeActiveBufferView(bool backwards)
{
if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
void MainWin::changeActiveBufferView(bool backwards)
{
if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
void showMigrationWarning(bool show);
void showMigrationWarning(bool show);
+ void onExitRequested(const QString &reason);
+
//! Quit application
void quit();
//! Quit application
void quit();
-bool MonolithicApplication::init()
+void MonolithicApplication::init()
- if (!QtUiApplication::init())
- return false;
+ QtUiApplication::init();
connect(Client::coreConnection(), SIGNAL(connectToInternalCore(QPointer<InternalPeer>)), this, SLOT(onConnectionRequest(QPointer<InternalPeer>)));
connect(Client::coreConnection(), SIGNAL(connectToInternalCore(QPointer<InternalPeer>)), this, SLOT(onConnectionRequest(QPointer<InternalPeer>)));
if (Quassel::isOptionSet("port")) {
startInternalCore();
}
if (Quassel::isOptionSet("port")) {
startInternalCore();
}
// Start internal core in a separate thread, so it doesn't block the UI
_core = new Core{};
_core->moveToThread(&_coreThread);
// Start internal core in a separate thread, so it doesn't block the UI
_core = new Core{};
_core->moveToThread(&_coreThread);
- connect(&_coreThread, SIGNAL(started()), _core, SLOT(init()));
+ connect(&_coreThread, SIGNAL(started()), _core, SLOT(initAsync()));
connect(&_coreThread, SIGNAL(finished()), _core, SLOT(deleteLater()));
connect(this, SIGNAL(connectInternalPeer(QPointer<InternalPeer>)), _core, SLOT(connectInternalPeer(QPointer<InternalPeer>)));
connect(_core, SIGNAL(sessionState(Protocol::SessionState)), Client::coreConnection(), SLOT(internalSessionStateReceived(Protocol::SessionState)));
connect(_core, SIGNAL(dbUpgradeInProgress(bool)), Client::instance(), SLOT(onDbUpgradeInProgress(bool)));
connect(&_coreThread, SIGNAL(finished()), _core, SLOT(deleteLater()));
connect(this, SIGNAL(connectInternalPeer(QPointer<InternalPeer>)), _core, SLOT(connectInternalPeer(QPointer<InternalPeer>)));
connect(_core, SIGNAL(sessionState(Protocol::SessionState)), Client::coreConnection(), SLOT(internalSessionStateReceived(Protocol::SessionState)));
connect(_core, SIGNAL(dbUpgradeInProgress(bool)), Client::instance(), SLOT(onDbUpgradeInProgress(bool)));
+ connect(_core, SIGNAL(exitRequested(int,QString)), Client::instance(), SLOT(onExitRequested(int,QString)));
MonolithicApplication(int &, char **);
~MonolithicApplication() override;
MonolithicApplication(int &, char **);
~MonolithicApplication() override;
signals:
void connectInternalPeer(QPointer<InternalPeer> peer);
signals:
void connectInternalPeer(QPointer<InternalPeer> peer);
#include "mainwin.h"
#include "qtui.h"
#include "qtuisettings.h"
#include "mainwin.h"
#include "qtui.h"
#include "qtuisettings.h"
QtUiApplication::QtUiApplication(int &argc, char **argv)
#ifdef HAVE_KDE4
QtUiApplication::QtUiApplication(int &argc, char **argv)
#ifdef HAVE_KDE4
-bool QtUiApplication::init()
+void QtUiApplication::init()
- if (Quassel::init()) {
- // Settings upgrade/downgrade handling
- if (!migrateSettings()) {
- qCritical() << "Could not load or upgrade client settings, terminating!";
- return false;
- }
+ if (!Quassel::init()) {
+ throw ExitException{EXIT_FAILURE, tr("Could not initialize Quassel!")};
+ }
- Client::init(new QtUi());
+ // Settings upgrade/downgrade handling
+ if (!migrateSettings()) {
+ throw ExitException{EXIT_FAILURE, tr("Could not load or upgrade client settings!")};
+ }
- // Init UI only after the event loop has started
- // TODO Qt5: Make this a lambda
- QTimer::singleShot(0, this, SLOT(initUi()));
+ Client::init(new QtUi());
- Quassel::registerQuitHandler([]() {
- QtUi::mainWindow()->quit();
- });
+ // Init UI only after the event loop has started
+ // TODO Qt5: Make this a lambda
+ QTimer::singleShot(0, this, SLOT(initUi()));
- return true;
- }
- return false;
+ Quassel::registerQuitHandler([]() {
+ QtUi::mainWindow()->quit();
+ });
public:
QtUiApplication(int &, char **);
~QtUiApplication();
public:
QtUiApplication(int &, char **);
~QtUiApplication();
void resumeSessionIfPossible();
inline bool isAboutToQuit() const { return _aboutToQuit; }
void resumeSessionIfPossible();
inline bool isAboutToQuit() const { return _aboutToQuit; }