Skip to content

Commit

Permalink
FEAT(client): profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
Hartmnt committed Jan 3, 2025
1 parent e64f334 commit b2d9d89
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 22 deletions.
15 changes: 15 additions & 0 deletions src/mumble/JSONSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,21 @@ void from_json(const nlohmann::json &j, OverlaySettings &settings) {
}


void to_json(nlohmann::json &j, const Profiles &settings) {
#define PROCESS(category, key, variable) save(j, SettingsKeys::key, settings.variable);

PROCESS_ALL_PROFILE_SETTINGS

#undef PROCESS
}

void from_json(const nlohmann::json &j, Profiles &settings) {
#define PROCESS(category, key, variable) load(j, SettingsKeys::key, settings.variable, settings.variable, true);

PROCESS_ALL_PROFILE_SETTINGS

#undef PROCESS
}

void to_json(nlohmann::json &j, const QString &string) {
j = string.toStdString();
Expand Down
2 changes: 2 additions & 0 deletions src/mumble/JSONSerialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ void to_json(nlohmann::json &j, const Settings &settings);
void from_json(const nlohmann::json &j, Settings &settings);
void to_json(nlohmann::json &j, const OverlaySettings &settings);
void from_json(const nlohmann::json &j, OverlaySettings &settings);
void to_json(nlohmann::json &j, const Profiles &settings);
void from_json(const nlohmann::json &j, Profiles &settings);

void to_json(nlohmann::json &j, const QString &string);
void from_json(const nlohmann::json &j, QString &string);
Expand Down
2 changes: 1 addition & 1 deletion src/mumble/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ void MainWindow::openUrl(const QUrl &url) {

try {
Settings newSettings;
newSettings.load(f.fileName());
newSettings.loadFile(f.fileName());

std::swap(newSettings, Global::get().s);

Expand Down
81 changes: 62 additions & 19 deletions src/mumble/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,24 @@ void Settings::save(const QString &path) const {
throw std::runtime_error("Expected settings file to have \".json\" extension");
}

nlohmann::json settingsJSON = *this;
nlohmann::json settingsJSON = profiles;

qWarning() << "settingsJSON " << QString::fromStdString(settingsJSON.dump(4));

nlohmann::json profilesJSON = settingsJSON.at(SettingsKeys::PROFILES);

qWarning() << "profilesJSON " << QString::fromStdString(profilesJSON.dump(4));

nlohmann::json activeProfileJSON = *this;

qWarning() << "activeProfile " << QString::fromStdString(activeProfileJSON.dump(4));

profilesJSON.erase(profiles.activeProfileName.toStdString());
profilesJSON.push_back({ profiles.activeProfileName, activeProfileJSON });

qWarning() << "profilesJSON (after) " << QString::fromStdString(profilesJSON.dump(4));

qWarning() << "settingsJSON (after) " << QString::fromStdString(settingsJSON.dump(4));

QFile tmpFile(QString::fromLatin1("%1/mumble_settings.json.tmp")
.arg(QStandardPaths::writableLocation(QStandardPaths::TempLocation)));
Expand Down Expand Up @@ -216,7 +233,7 @@ void Settings::save() const {
}
}

void Settings::load(const QString &path) {
void Settings::loadFile(const QString &path) {
if (path.endsWith(QLatin1String(BACKUP_FILE_EXTENSION))) {
// Trim away the backup extension
settingsLocation = path.left(path.size() - static_cast< int >(std::strlen(BACKUP_FILE_EXTENSION)));
Expand All @@ -226,11 +243,29 @@ void Settings::load(const QString &path) {

std::ifstream stream(Mumble::QtUtils::qstring_to_path(path));

nlohmann::json settingsJSON;
try {
stream >> settingsJSON;
nlohmann::json profilesJSON;
stream >> profilesJSON;

settingsJSON.get_to(*this);
if (profilesJSON.contains(SettingsKeys::ACTIVE_PROFILE)) {
profilesJSON.get_to(profiles);

if (profiles.allProfiles.contains(profiles.activeProfileName)) {
*this = profiles.allProfiles[profiles.activeProfileName];
} else if (profiles.allProfiles.contains("default")) {
qWarning("Failed to load profile '%s'. Falling back to 'default'...", qUtf8Printable(profiles.activeProfileName));
*this = profiles.allProfiles["default"];
} else {
qWarning("Failed to 'default' profile. Trying to load settings file as is...");
profilesJSON.get_to(*this);
}
} else {
// The file does not contain the key "SettingsKeys::ACTIVE_PROFILE"
// We assume the JSON file does not contain any profiles, because it is
// old. We load that instead and convert it to the "default" profile.
qWarning("Migrating settings file to 'default' profile");
profilesJSON.get_to(*this);
}

if (!mumbleQuitNormally) {
// These settings were saved without Mumble quitting normally afterwards. In order to prevent loading
Expand All @@ -254,7 +289,7 @@ void Settings::load(const QString &path) {
if (msgBox.exec() == QMessageBox::Yes) {
// Load the backup instead
qWarning() << "Loading backup settings from" << backupPath;
load(backupPath);
loadFile(backupPath);
}
}
} else {
Expand All @@ -274,12 +309,13 @@ void Settings::load(const QString &path) {
msgBox.exec();
}
}

} catch (const nlohmann::json::parse_error &e) {
qWarning() << "Failed to load settings from" << path << "due to invalid format: " << e.what();

if (!path.endsWith(QLatin1String(BACKUP_FILE_EXTENSION)) && QFileInfo(path + BACKUP_FILE_EXTENSION).exists()) {
qWarning() << "Falling back to backup" << path + BACKUP_FILE_EXTENSION;
load(path + BACKUP_FILE_EXTENSION);
loadFile(path + BACKUP_FILE_EXTENSION);
}
}

Expand All @@ -295,11 +331,11 @@ void Settings::load() {
if (foundExisting) {
// If we found a regular settings file, then use that and be done with it
qInfo() << "Loading settings from" << settingsPath;
load(settingsPath);
loadFile(settingsPath);
} else if (QFileInfo(settingsPath + BACKUP_FILE_EXTENSION).exists()) {
// Load backup settings instead
qInfo() << "Loading backup settings from" << settingsPath + BACKUP_FILE_EXTENSION;
load(settingsPath + BACKUP_FILE_EXTENSION);
loadFile(settingsPath + BACKUP_FILE_EXTENSION);
} else {
// Otherwise check for a legacy settings file and if that is found, load settings from there
QString legacySettingsPath = findSettingsLocation(true, &foundExisting);
Expand Down Expand Up @@ -1123,8 +1159,8 @@ void Settings::legacyLoad(const QString &path) {
settings_ptr->endGroup();


// This field previously populated Settings::uiUpdateCounter, which no longer exists. We require it though, in order
// to determine whether we have to perform some migration work.
// This field previously populated Settings::uiUpdateCounter, which no longer exists. We require it though, in
// order to determine whether we have to perform some migration work.
unsigned int configVersion = 0;
LOAD(configVersion, "lastupdate");
audioWizardShown = true;
Expand Down Expand Up @@ -1222,6 +1258,7 @@ void Settings::verifySettingsKeys() const {
#define INTERMEDIATE_OPERATION categoryNames.push_back(currentCategoryName);
PROCESS_ALL_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_OVERLAY_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_PROFILE_SETTINGS_WITH_INTERMEDIATE_OPERATION

// Assert that all entries in categoryNames are unique
std::sort(categoryNames.begin(), categoryNames.end());
Expand All @@ -1241,6 +1278,7 @@ void Settings::verifySettingsKeys() const {
keyNames.clear();
PROCESS_ALL_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_OVERLAY_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_PROFILE_SETTINGS_WITH_INTERMEDIATE_OPERATION
#undef PROCESS
#undef INTERMEDIATE_OPERATION

Expand All @@ -1255,15 +1293,20 @@ void Settings::verifySettingsKeys() const {
PROCESS_ALL_OVERLAY_SETTINGS
std::sort(variableNames.begin(), variableNames.end());
assert(std::unique(variableNames.begin(), variableNames.end()) == variableNames.end());
variableNames.clear();

PROCESS_ALL_PROFILE_SETTINGS
std::sort(variableNames.begin(), variableNames.end());
assert(std::unique(variableNames.begin(), variableNames.end()) == variableNames.end());
#undef PROCESS
}


QString Settings::findSettingsLocation(bool legacy, bool *foundExistingFile) const {
// In order to make sure we'll find (mostly legacy) settings files, even if they end up being in a slightly
// different dir than we currently expect, we construct a search path list that we'll traverse while searching for
// the settings file. In case we find a suitable settings file within the search path, then we'll continue to use
// this path instead of creating a new one (in the location that we currently think is best to use).
// different dir than we currently expect, we construct a search path list that we'll traverse while searching
// for the settings file. In case we find a suitable settings file within the search path, then we'll continue
// to use this path instead of creating a new one (in the location that we currently think is best to use).
QStringList paths;
paths << QCoreApplication::instance()->applicationDirPath();
paths << QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
Expand Down Expand Up @@ -1300,11 +1343,11 @@ QString Settings::findSettingsLocation(bool legacy, bool *foundExistingFile) con
chosenPath = QString::fromLatin1("%1/%2")
.arg(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation))
.arg(settingsFileNames[0]);
// Note: QStandardPaths::AppConfigLocation will return a directory of the style <root>/<org>/<application> where
// <root> is the path to the general config related directory on the respective OS, <org> is the name of our
// organization and <application> is our application's name. In our case (at the time of writing this) <org> =
// <application> = Mumble, leading to a doubly nested "Mumble" directory. This should only be a cosmetic issue
// though.
// Note: QStandardPaths::AppConfigLocation will return a directory of the style <root>/<org>/<application>
// where <root> is the path to the general config related directory on the respective OS, <org> is the name
// of our organization and <application> is our application's name. In our case (at the time of writing
// this) <org> = <application> = Mumble, leading to a doubly nested "Mumble" directory. This should only be
// a cosmetic issue though.
}

return chosenPath;
Expand Down
10 changes: 9 additions & 1 deletion src/mumble/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

class QSettings;
struct MigratedPath;
struct Settings;

// Global helper classes to spread variables around across threads
// especially helpful to initialize things like the stored
Expand Down Expand Up @@ -184,6 +185,11 @@ struct OverlaySettings {
friend bool operator!=(const OverlaySettings &lhs, const OverlaySettings &rhs);
};

struct Profiles {
QString activeProfileName = QStringLiteral("default");
QMap< QString, Settings > allProfiles = {};
};

struct Settings {
enum AudioTransmit { Continuous, VAD, PushToTalk };
enum VADSource { Amplitude, SignalToNoise };
Expand Down Expand Up @@ -558,6 +564,8 @@ struct Settings {
/// A flag used in order to determine whether or not to offer loading the setting's backup file instead
bool mumbleQuitNormally = false;

Profiles profiles;

bool doEcho() const;
bool doPositionalAudio() const;

Expand All @@ -566,8 +574,8 @@ struct Settings {
void save(const QString &path) const;
void save() const;

void load(const QString &path);
void load();
void loadFile(const QString &path);

void legacyLoad(const QString &path = {});

Expand Down
4 changes: 4 additions & 0 deletions src/mumble/SettingsKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ namespace SettingsKeys {
* loading settings.
*/

// Meta
const SettingsKey ACTIVE_PROFILE = { "active_profile" };
const SettingsKey PROFILES = { "profiles" };

// Audio settings
const SettingsKey UNMUTE_ON_UNDEAF_KEY = { "unmute_on_undeaf" };
const SettingsKey MUTE_KEY = { "mute" };
Expand Down
9 changes: 9 additions & 0 deletions src/mumble/SettingsMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

// Mappings between SettingsKey objects and the corresponding fields in the Settings struct

#define PROFILE_SETTINGS \
PROCESS(profiles, ACTIVE_PROFILE, activeProfileName) \
PROCESS(profiles, PROFILES, allProfiles)

#define MISC_SETTINGS \
PROCESS(misc, DATABASE_LOCATION_KEY, qsDatabaseLocation) \
PROCESS(misc, IMAGE_DIRECTORY_KEY, qsImagePath) \
Expand Down Expand Up @@ -342,6 +346,7 @@

#define PROCESS_ALL_OVERLAY_SETTINGS OVERLAY_SETTINGS

#define PROCESS_ALL_PROFILE_SETTINGS PROFILE_SETTINGS

#define PROCESS_ALL_SETTINGS_WITH_INTERMEDIATE_OPERATION \
MISC_SETTINGS \
Expand Down Expand Up @@ -391,5 +396,9 @@
OVERLAY_SETTINGS \
INTERMEDIATE_OPERATION

#define PROCESS_ALL_PROFILE_SETTINGS_WITH_INTERMEDIATE_OPERATION \
PROFILE_SETTINGS \
INTERMEDIATE_OPERATION


#endif // MUMBLE_MUMBLE_SETTINGS_MACROS_H_
2 changes: 1 addition & 1 deletion src/mumble/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ int main(int argc, char **argv) {
if (settingsFile.isEmpty()) {
Global::get().s.load();
} else {
Global::get().s.load(settingsFile);
Global::get().s.loadFile(settingsFile);
}
if (!Global::get().migratedDBPath.isEmpty()) {
// We have migrated the DB to a new location. Make sure that the settings hold the correct (new) path and that
Expand Down

0 comments on commit b2d9d89

Please sign in to comment.