From 4edae462fa74fecc42377604c0405480c6c24f83 Mon Sep 17 00:00:00 2001 From: Popkornium18 Date: Fri, 31 Jul 2020 20:18:51 +0200 Subject: [PATCH] FEAT(client): Allow specifying config file via commandline switch This adds the switch -c/--config to the mumble client. If -c is followed by a filename, this file will be read instead of the standard config. A new config option database= has been added to the 'General' section of the ini file. This can be used to specify a different database which is necessary to run multiple completely separate Mumble instances at the same time. Additionaly the (undocumented) function to merge another ini file by providing it as a parameter has been removed as it was conflicting with the added functionality. FIXES #3953 --- src/mumble/Database.cpp | 82 ++++++++++++++++++++++++++--------------- src/mumble/Database.h | 4 ++ src/mumble/Global.cpp | 46 +++++++++++++---------- src/mumble/Global.h | 2 +- src/mumble/Settings.cpp | 4 ++ src/mumble/Settings.h | 3 ++ src/mumble/main.cpp | 27 +++++++++++++- 7 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/mumble/Database.cpp b/src/mumble/Database.cpp index 8b1fb83b1..8a5e89568 100644 --- a/src/mumble/Database.cpp +++ b/src/mumble/Database.cpp @@ -40,11 +40,10 @@ static bool execQueryAndLogFailure(QSqlQuery &query, const QString &queryString) } -Database::Database(const QString &dbname) { - db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbname); +bool Database::findOrCreateDatabase() +{ QSettings qs; QStringList datapaths; - int i; datapaths << g.qdBasePath.absolutePath(); datapaths << QStandardPaths::writableLocation(QStandardPaths::DataLocation); @@ -55,42 +54,65 @@ Database::Database(const QString &dbname) { datapaths << QDir::currentPath(); datapaths << qApp->applicationDirPath(); datapaths << qs.value(QLatin1String("InstPath")).toString(); - bool found = false; + datapaths.removeAll(QLatin1String("")); + datapaths.removeDuplicates(); - for (i = 0; (i < datapaths.size()) && ! found; i++) { - if (!datapaths[i].isEmpty()) { - // Try the legacy path first, and use it if it exists. - // If it doesn't, use the new, non-hidden version. - QFile legacyDatabaseFile(datapaths[i] + QLatin1String("/.mumble.sqlite")); - if (legacyDatabaseFile.exists()) { - db.setDatabaseName(legacyDatabaseFile.fileName()); - found = db.open(); + // Try to find an existing database + foreach(const QString &datapath, datapaths) { + // Try the legacy path first, and use it if it exists. + // If it doesn't, use the new, non-hidden version. + QFile legacyDatabaseFile(datapath + QLatin1String("/.mumble.sqlite")); + if (legacyDatabaseFile.exists()) { + db.setDatabaseName(legacyDatabaseFile.fileName()); + if (db.open()) { + return true; } - if (found) { - break; - } - QFile databaseFile(datapaths[i] + QLatin1String("/mumble.sqlite")); - if (databaseFile.exists()) { - db.setDatabaseName(databaseFile.fileName()); - found = db.open(); + } + QFile databaseFile(datapath + QLatin1String("/mumble.sqlite")); + if (databaseFile.exists()) { + db.setDatabaseName(databaseFile.fileName()); + if (db.open()) { + return true; } } } - if (! found) { - for (i = 0; (i < datapaths.size()) && ! found; i++) { - if (!datapaths[i].isEmpty()) { - QDir::root().mkpath(datapaths[i]); - QFile f(datapaths[i] + QLatin1String("/mumble.sqlite")); - db.setDatabaseName(f.fileName()); - found = db.open(); + // There is no existing database, so we create one + foreach(const QString &datapath, datapaths) { + QDir::root().mkpath(datapath); + QFile f(datapath + QLatin1String("/mumble.sqlite")); + db.setDatabaseName(f.fileName()); + if (db.open()) { + return true; + } + } + return false; +} + +Database::Database(const QString &dbname) { + db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbname); + if (!g.s.qsDatabaseLocation.isEmpty()) { + QFile configuredLocation(g.s.qsDatabaseLocation); + if (configuredLocation.exists()) { + db.setDatabaseName(g.s.qsDatabaseLocation); + db.open(); + } else { + int result = QMessageBox::critical(nullptr, QLatin1String("Mumble"), tr("The database file '%1' set in the configuration file does not exist. Do you want to create a new database file at this location?").arg(g.s.qsDatabaseLocation), QMessageBox::Yes | QMessageBox::No); + if (result == QMessageBox::Yes) { + db.setDatabaseName(g.s.qsDatabaseLocation); + db.open(); + } else { + qFatal("Database: File not found"); } } } - - if (! found) { - QMessageBox::critical(nullptr, QLatin1String("Mumble"), tr("Mumble failed to initialize a database in any\nof the possible locations."), QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton); - qFatal("Database: Failed initialization"); + if (!db.isOpen()) { + if (findOrCreateDatabase()) { + g.s.qsDatabaseLocation = db.databaseName(); + } else { + QMessageBox::critical(nullptr, QLatin1String("Mumble"), tr("Mumble failed to initialize a database in any of the possible locations."), QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton); + qFatal("Database: Failed initialization"); + } } QFileInfo fi(db.databaseName()); diff --git a/src/mumble/Database.h b/src/mumble/Database.h index 3e626c044..569e38fa3 100644 --- a/src/mumble/Database.h +++ b/src/mumble/Database.h @@ -25,6 +25,10 @@ class Database : public QObject { Q_DISABLE_COPY(Database) QSqlDatabase db; + /// This function is called when no database location is configured + /// in the config file. It tries to find an existing database file and + /// creates a new one if none was found. + bool findOrCreateDatabase(); public: Database(const QString &dbname); ~Database() Q_DECL_OVERRIDE; diff --git a/src/mumble/Global.cpp b/src/mumble/Global.cpp index 23dbb7054..5f36e8e4e 100644 --- a/src/mumble/Global.cpp +++ b/src/mumble/Global.cpp @@ -65,7 +65,7 @@ static void migrateDataDir() { } #endif // Q_OS_WIN -Global::Global() { +Global::Global(const QString &qsConfigPath) { mw = 0; db = 0; p = 0; @@ -119,29 +119,37 @@ Global::Global() { bDebugDumpInput = false; bDebugPrintQueue = false; - QStringList qsl; - qsl << QCoreApplication::instance()->applicationDirPath(); - qsl << QStandardPaths::writableLocation(QStandardPaths::DataLocation); - #if defined(Q_OS_WIN) QString appdata; wchar_t appData[MAX_PATH]; - if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, appData))) { - appdata = QDir::fromNativeSeparators(QString::fromWCharArray(appData)); - - if (!appdata.isEmpty()) { - appdata.append(QLatin1String("/Mumble")); - qsl << appdata; - } - } #endif - foreach(const QString &dir, qsl) { - QFile inifile(QString::fromLatin1("%1/mumble.ini").arg(dir)); - if (inifile.exists() && inifile.permissions().testFlag(QFile::WriteUser)) { - qdBasePath.setPath(dir); - qs = new QSettings(inifile.fileName(), QSettings::IniFormat); - break; + if (!qsConfigPath.isEmpty()) { + QFile inifile(qsConfigPath); + qs = new QSettings(inifile.fileName(), QSettings::IniFormat); + } else { + QStringList qsl; + qsl << QCoreApplication::instance()->applicationDirPath(); + qsl << QStandardPaths::writableLocation(QStandardPaths::DataLocation); + +#if defined(Q_OS_WIN) + if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, appData))) { + appdata = QDir::fromNativeSeparators(QString::fromWCharArray(appData)); + + if (!appdata.isEmpty()) { + appdata.append(QLatin1String("/Mumble")); + qsl << appdata; + } + } +#endif + + foreach(const QString &dir, qsl) { + QFile inifile(QString::fromLatin1("%1/mumble.ini").arg(dir)); + if (inifile.exists() && inifile.permissions().testFlag(QFile::WriteUser)) { + qdBasePath.setPath(dir); + qs = new QSettings(inifile.fileName(), QSettings::IniFormat); + break; + } } } diff --git a/src/mumble/Global.h b/src/mumble/Global.h index 5c4dd292d..8e48033bd 100644 --- a/src/mumble/Global.h +++ b/src/mumble/Global.h @@ -108,7 +108,7 @@ public: bool bHappyEaster; static const char ccHappyEaster[]; - Global(); + Global(const QString &qsConfigPath = QString()); ~Global(); }; diff --git a/src/mumble/Settings.cpp b/src/mumble/Settings.cpp index e7c12b4de..461822373 100644 --- a/src/mumble/Settings.cpp +++ b/src/mumble/Settings.cpp @@ -674,6 +674,8 @@ void Settings::load(QSettings* settings_ptr) { // Config updates SAVELOAD(uiUpdateCounter, "lastupdate"); + SAVELOAD(qsDatabaseLocation, "databaselocation"); + SAVELOAD(bMute, "audio/mute"); SAVELOAD(bDeaf, "audio/deaf"); LOADENUM(atTransmit, "audio/transmit"); @@ -1031,6 +1033,8 @@ void Settings::save() { // Config updates SAVELOAD(uiUpdateCounter, "lastupdate"); + SAVELOAD(qsDatabaseLocation, "databaselocation"); + SAVELOAD(bMute, "audio/mute"); SAVELOAD(bDeaf, "audio/deaf"); SAVELOAD(atTransmit, "audio/transmit"); diff --git a/src/mumble/Settings.h b/src/mumble/Settings.h index f273a42f5..308c50367 100644 --- a/src/mumble/Settings.h +++ b/src/mumble/Settings.h @@ -430,6 +430,9 @@ struct Settings { // Config updates unsigned int uiUpdateCounter; + /// Path to SQLite-DB + QString qsDatabaseLocation; + // Nonsaved LoopMode lmLoopMode; float dPacketLoss; diff --git a/src/mumble/main.cpp b/src/mumble/main.cpp index 7849194c8..f223106bc 100644 --- a/src/mumble/main.cpp +++ b/src/mumble/main.cpp @@ -125,7 +125,26 @@ int main(int argc, char **argv) { } #endif - Global::g_global_struct = new Global(); + // This argument has to be parsed first, since it's value is needed to create the global struct, + // which other switches are modifying. If it is parsed first, the order of the arguments does not matter. + QStringList args = a.arguments(); + const int index = std::max(args.lastIndexOf(QLatin1String("-c")), args.lastIndexOf(QLatin1String("--config"))); + if (index >= 0) { + if (index + 1 < args.count()) { + QFile inifile(args.at(index + 1)); + if (inifile.exists() && inifile.permissions().testFlag(QFile::WriteUser)) { + Global::g_global_struct = new Global(args.at(index + 1)); + } else { + printf("%s", qPrintable(MainWindow::tr("Configuration file %1 does not exist or is not writable.\n").arg(args.at(index + 1)))); + return 1; + } + } else { + qCritical("Missing argument for --config!"); + return 1; + } + } else { + Global::g_global_struct = new Global(); + } #if QT_VERSION < QT_VERSION_CHECK(5,10,0) // For Qt >= 5.10 we use QRandomNumberGenerator that is seeded automatically @@ -143,8 +162,8 @@ int main(int argc, char **argv) { bool bRpcMode = false; QString rpcCommand; QUrl url; + if (a.arguments().count() > 1) { - QStringList args = a.arguments(); for (int i = 1; i < args.count(); ++i) { if (args.at(i) == QLatin1String("-h") || args.at(i) == QLatin1String("--help") #if defined(Q_OS_WIN) @@ -165,6 +184,10 @@ int main(int argc, char **argv) { " -h, --help Show this help text and exit.\n" " -m, --multiple\n" " Allow multiple instances of the client to be started.\n" + " -c, --config\n" + " Specify an alternative configuration file.\n" + " If you use this to run multiple instances of Mumble at once,\n" + " make sure to set an alternative 'database' value in the config.\n" " -n, --noidentity\n" " Suppress loading of identity files (i.e., certificates.)\n" " -jn, --jackname \n"