123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729 |
- #include "SettingsComponent.h"
- #include "SettingsSection.h"
- #include "Paths.h"
- #include "utils/Utils.h"
- #include "QsLog.h"
- #include "AudioSettingsController.h"
- #include "Names.h"
- #include <QSaveFile>
- #include <QJsonDocument>
- #include <QJsonObject>
- #include <QJsonArray>
- #include <QList>
- #include <QSettings>
- #include "input/InputComponent.h"
- #include "system/SystemComponent.h"
- #include "Version.h"
- #define OLDEST_PREVIOUS_VERSION_KEY "oldestPreviousVersion"
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- SettingsComponent::SettingsComponent(QObject *parent) : ComponentBase(parent), m_settingsVersion(-1)
- {
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::componentPostInitialize()
- {
- InputComponent::Get().registerHostCommand("cycle_setting", this, "cycleSetting");
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::cycleSetting(const QString& args)
- {
- QString settingName = args;
- QStringList sub = settingName.split(".");
- if (sub.size() != 2)
- {
- QLOG_ERROR() << "Setting must be in the form section.name but got:" << settingName;
- return;
- }
- QString sectionID = sub[0];
- QString valueName = sub[1];
- SettingsSection* section = getSection(sectionID);
- if (!section)
- {
- QLOG_ERROR() << "Section" << sectionID << "is unknown";
- return;
- }
- QVariantList values = section->possibleValues(valueName);
- // If no possible values are defined, check the type of the default value.
- // In the case it's a boolean simply negate the current value to cycle through.
- // Otherwise log an error message, that it's not possible to cycle through the value.
- if (values.size() == 0)
- {
- if (section->defaultValue(valueName).type() == QVariant::Bool)
- {
- QVariant currentValue = section->value(valueName);
- auto nextValue = currentValue.toBool() ? false : true;
- setValue(sectionID, valueName, nextValue);
- QLOG_DEBUG() << "Setting" << settingName << "to " << (nextValue ? "Enabled" : "Disabled");
- emit SystemComponent::Get().settingsMessage(valueName, nextValue ? "Enabled" : "Disabled");
- return;
- }
- else
- {
- QLOG_ERROR() << "Setting" << settingName << "is unknown or is not cycleable.";
- return;
- }
- }
- QVariant currentValue = section->value(valueName);
- int nextValueIndex = 0;
- for (int n = 0; n < values.size(); n++)
- {
- if (currentValue == values[n].toMap()["value"])
- {
- nextValueIndex = n + 1;
- break;
- }
- }
- if (nextValueIndex >= values.size())
- nextValueIndex = 0;
- auto nextSetting = values[nextValueIndex].toMap();
- auto nextValue = nextSetting["value"];
- QLOG_DEBUG() << "Setting" << settingName << "to" << nextValue;
- setValue(sectionID, valueName, nextValue);
- emit SystemComponent::Get().settingsMessage(valueName, nextSetting["title"].toString());
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::updatePossibleValues(const QString §ionID, const QString &key, const QVariantList &possibleValues)
- {
- SettingsSection* section = getSection(sectionID);
- if (!section)
- {
- QLOG_ERROR() << "Section" << sectionID << "is unknown";
- return;
- }
- section->updatePossibleValues(key, possibleValues);
- QLOG_DEBUG() << "Updated possible values for:" << key << "to" << possibleValues;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QVariant SettingsComponent::allValues(const QString& section)
- {
- if (section.isEmpty())
- {
- QVariantMap all;
- for(const QString& sname : m_sections.keys())
- all[sname] = m_sections[sname]->allValues();
- return all;
- }
- else if (m_sections.contains(section))
- {
- return m_sections[section]->allValues();
- }
- else
- {
- // look for the value in the webclient section
- return m_sections[SETTINGS_SECTION_WEBCLIENT]->value(section);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- static void writeFile(const QString& filename, const QByteArray& data)
- {
- QSaveFile file(filename);
- file.open(QIODevice::WriteOnly | QIODevice::Text);
- file.write(data);
- if (!file.commit())
- {
- QLOG_ERROR() << "Could not write" << filename;
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- static QJsonObject loadJson(const QString& filename)
- {
- // Checking existence before opening is technically a race condition, but
- // it looks like Qt doesn't let us distinguish errors on opening.
- if (!QFile(filename).exists())
- return QJsonObject();
- QJsonParseError err;
- QJsonDocument json = Utils::OpenJsonDocument(filename, &err);
- if (json.isNull())
- {
- QLOG_ERROR() << "Could not open" << filename << "due to" << err.errorString();
- }
- return json.object();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- static void writeJson(const QString& filename, const QJsonObject& data, bool pretty = true)
- {
- QJsonDocument json(data);
- writeFile(filename, json.toJson(pretty ? QJsonDocument::Indented : QJsonDocument::Compact));
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QVariant SettingsComponent::readPreinitValue(const QString& sectionID, const QString& key)
- {
- QJsonObject json = loadJson(Paths::dataDir("plexmediaplayer.conf"));
- return json["sections"].toObject()[sectionID].toObject()[key].toVariant();
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::load()
- {
- loadConf(Paths::dataDir("plexmediaplayer.conf"), false);
- loadConf(Paths::dataDir("storage.json"), true);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::loadConf(const QString& path, bool storage)
- {
- QJsonObject json = loadJson(path);
- int version = json["version"].toInt(0);
- if (version != m_settingsVersion)
- {
- QString backup = path + ".broken";
- QFile::remove(backup);
- QFile::rename(path, backup);
- if (version == 0)
- QLOG_ERROR() << "Could not read config file.";
- else
- QLOG_ERROR() << "Config version is" << version << "but" << m_settingsVersion << "expected. Moving old config to" << backup;
- // Overwrite/create it with the defaults.
- if (storage)
- saveStorage();
- else
- saveSettings();
- return;
- }
- QJsonObject jsonSections = json["sections"].toObject();
- for(const QString& section : jsonSections.keys())
- {
- QJsonObject jsonSection = jsonSections[section].toObject();
- SettingsSection* sec = getSection(section);
- if (!sec && storage)
- {
- sec = new SettingsSection(section, PLATFORM_ANY, -1, this);
- sec->setHidden(true);
- sec->setStorage(true);
- m_sections.insert(section, sec);
- }
- else if (!sec)
- {
- QLOG_ERROR() << "Trying to load section:" << section << "from config file, but we don't want that.";
- continue;
- }
- for(const QString& setting : jsonSection.keys())
- sec->setValue(setting, jsonSection.value(setting).toVariant());
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::saveSettings()
- {
- if (m_oldestPreviousVersion.isEmpty())
- {
- QLOG_ERROR() << "Not writing settings: uninitialized.\n";
- return;
- }
- QVariantMap sections;
- for(SettingsSection* section : m_sections.values())
- {
- if (!section->isStorage())
- sections.insert(section->sectionName(), section->allValues());
- }
- QJsonObject json;
- json.insert("sections", QJsonValue::fromVariant(sections));
- json.insert("version", m_settingsVersion);
- writeJson(Paths::dataDir("plexmediaplayer.conf"), json);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::saveStorage()
- {
- QVariantMap storage;
- for(SettingsSection* section : m_sections.values())
- {
- if (section->isStorage())
- storage.insert(section->sectionName(), section->allValues());
- }
- QJsonObject storagejson;
- storagejson.insert("sections", QJsonValue::fromVariant(storage));
- storagejson.insert("version", m_settingsVersion);
- writeJson(Paths::dataDir("storage.json"), storagejson, false);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::saveSection(SettingsSection* section)
- {
- if (section && section->isStorage())
- saveStorage();
- else
- saveSettings();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QVariant SettingsComponent::value(const QString& sectionID, const QString &key)
- {
- SettingsSection* section = getSection(sectionID);
- if (!section)
- {
- QLOG_ERROR() << "Section" << sectionID << "is unknown";
- return QVariant();
- }
- return section->value(key);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::setValue(const QString& sectionID, const QString &key, const QVariant &value)
- {
- SettingsSection* section = getSection(sectionID);
- if (!section)
- {
- QLOG_ERROR() << "Section" << sectionID << "is unknown";
- return;
- }
- section->setValue(key, value);
- saveSection(section);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::setValues(const QVariantMap& options)
- {
- Q_ASSERT(options.contains("key"));
- Q_ASSERT(options.contains("value"));
- QString key = options["key"].toString();
- QVariant values = options["value"];
- if (values.type() == QVariant::Map || values.isNull())
- {
- SettingsSection* section = getSection(key);
- if (!section)
- {
- // let's create this section since it's most likely created by the webclient
- section = new SettingsSection(key, PLATFORM_ANY, -1, this);
- section->setHidden(true);
- section->setStorage(true);
- m_sections.insert(key, section);
- }
- if (values.isNull())
- section->resetValues();
- else
- section->setValues(values);
- saveSection(section);
- }
- else if (values.type() == QVariant::String)
- {
- setValue(SETTINGS_SECTION_WEBCLIENT, key, values);
- }
- else
- {
- QLOG_WARN() << "the values sent was not a map, string or empty value. it will be ignored";
- // return so we don't call save()
- return;
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::removeValue(const QString §ionOrKey)
- {
- SettingsSection* section = getSection(sectionOrKey);
- if (section)
- {
- // we want to remove a full section
- // dont remove the section, but remove all keys
- section->resetValues();
- saveSection(section);
- }
- else
- {
- // we want to remove a root key from webclient
- // which is stored in webclient section
- section = m_sections[SETTINGS_SECTION_WEBCLIENT];
- section->resetValue(sectionOrKey);
- saveSection(section);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::resetToDefault(const QString §ionID)
- {
- SettingsSection* section = getSection(sectionID);
- if (section)
- {
- section->resetValues();
- saveSection(section);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::resetToDefaultAll()
- {
- for(SettingsSection *section : m_sections)
- {
- section->resetValues();
- saveSection(section);
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- struct SectionOrderIndex
- {
- inline bool operator ()(SettingsSection* a, SettingsSection* b)
- {
- return a->orderIndex() < b->orderIndex();
- }
- };
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QVariantList SettingsComponent::settingDescriptions()
- {
- QJsonArray desc;
- QList<SettingsSection*> sectionList = m_sections.values();
- std::sort(sectionList.begin(), sectionList.end(), SectionOrderIndex());
- for(SettingsSection* section : sectionList)
- {
- if (!section->isHidden())
- desc.push_back(QJsonValue::fromVariant(section->descriptions()));
- }
- return desc.toVariantList();
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- bool SettingsComponent::loadDescription()
- {
- QJsonParseError err;
- auto doc = Utils::OpenJsonDocument(":/settings/settings_description.json", &err);
- if (doc.isNull())
- {
- QLOG_ERROR() << "Failed to read settings description:" << err.errorString();
- throw FatalException("Failed to read settings description!");
- }
- if (!doc.isArray())
- {
- QLOG_ERROR() << "The object needs to be an array";
- return false;
- }
- m_sectionIndex = 0;
- for(const QJsonValue& val : doc.array())
- {
- if (!val.isObject())
- {
- QLOG_ERROR() << "Hoped to find sections in the root array, but they where not JSON objects";
- return false;
- }
- QJsonObject section = val.toObject();
- if (!section.contains("section"))
- {
- QLOG_ERROR() << "A section needs to contain the section keyword.";
- return false;
- }
- if (section["section"] == "__meta__")
- m_settingsVersion = section.value("version").toInt();
- else
- parseSection(section);
- }
- return true;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::parseSection(const QJsonObject& sectionObject)
- {
- QString sectionName = sectionObject.value("section").toString();
- if (!sectionObject.contains("values") || !sectionObject.value("values").isArray())
- {
- QLOG_ERROR() << "section object:" << sectionName << "did not contain a values array";
- return;
- }
- int platformMask = platformMaskFromObject(sectionObject);
- auto section = new SettingsSection(sectionName, (quint8)platformMask, m_sectionIndex ++, this);
- section->setHidden(sectionObject.value("hidden").toBool(false));
- section->setStorage(sectionObject.value("storage").toBool(false));
- auto values = sectionObject.value("values").toArray();
- int order = 0;
- for(auto val : values)
- {
- if (!val.isObject())
- continue;
- QJsonObject valobj = val.toObject();
- if (!valobj.contains("value") || !valobj.contains("default") || valobj.value("value").isNull())
- continue;
- QJsonValue defaults = valobj.value("default");
- QVariant defaultval = defaults.toVariant();
- if (defaults.isArray())
- {
- defaultval = QVariant();
- // Whichever default matches the current platform first is used.
- for(const auto& v : defaults.toArray())
- {
- auto vobj = v.toObject();
- int defPlatformMask = platformMaskFromObject(vobj);
- if ((defPlatformMask & Utils::CurrentPlatform()) == Utils::CurrentPlatform())
- {
- defaultval = vobj.value("value").toVariant();
- break;
- }
- }
- }
- int vPlatformMask = platformMaskFromObject(valobj);
- SettingsValue* setting = new SettingsValue(valobj.value("value").toString(), defaultval, (quint8)vPlatformMask, this);
- setting->setHasDescription(true);
- setting->setHidden(valobj.value("hidden").toBool(false));
- setting->setIndexOrder(order ++);
- if (valobj.contains("input_type"))
- setting->setInputType(valobj.value("input_type").toString());
- if (valobj.contains("possible_values") && valobj.value("possible_values").isArray())
- {
- auto list = valobj.value("possible_values").toArray();
- for(const auto& v : list)
- {
- int platform = PLATFORM_ANY;
- auto vl = v.toArray();
- if (vl.size() < 2)
- continue;
- if (vl.size() == 3 && vl.at(2).isObject())
- {
- // platform options
- QJsonObject options = vl.at(2).toObject();
- platform = platformMaskFromObject(options);
- }
- if ((platform & Utils::CurrentPlatform()) == Utils::CurrentPlatform())
- setting->addPossibleValue(vl.at(1).toString(), vl.at(0).toVariant());
- }
- }
- section->registerSetting(setting);
- }
- m_sections.insert(sectionName, section);
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- int SettingsComponent::platformMaskFromObject(const QJsonObject& object)
- {
- int platformMask = PLATFORM_ANY;
- // only include the listed platforms
- if (object.contains("platforms"))
- {
- // start by resetting it to no platforms
- platformMask = PLATFORM_UNKNOWN;
- QJsonValue platforms = object.value("platforms");
- // platforms can be both array or a single string
- if (platforms.isArray())
- {
- for(const QJsonValue& pl : platforms.toArray())
- {
- if (!pl.isString())
- continue;
- platformMask |= platformFromString(pl.toString());
- }
- }
- else
- {
- platformMask = platformFromString(platforms.toString());
- }
- }
- else if (object.contains("platforms_excluded"))
- {
- QJsonValue val = object.value("platforms_excluded");
- if (val.isArray())
- {
- for(const QJsonValue& pl : val.toArray())
- {
- if (!pl.isString())
- continue;
- platformMask &= ~platformFromString(pl.toString());
- }
- }
- else
- {
- platformMask &= ~platformFromString(val.toString());
- }
- }
- return platformMask;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- Platform SettingsComponent::platformFromString(const QString& platformString)
- {
- if (platformString == "osx")
- return PLATFORM_OSX;
- else if (platformString == "windows")
- return PLATFORM_WINDOWS;
- else if (platformString == "linux")
- return PLATFORM_LINUX;
- else if (platformString == "oe")
- return PLATFORM_OE;
- else if (platformString == "oe_rpi")
- return PLATFORM_OE_RPI;
- else if (platformString == "oe_x86")
- return PLATFORM_OE_X86;
- else if (platformString == "any")
- return PLATFORM_ANY;
- return PLATFORM_UNKNOWN;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- bool SettingsComponent::componentInitialize()
- {
- if (!loadDescription())
- return false;
- // Must be called before we possibly write the config file.
- setupVersion();
- load();
- // add our AudioSettingsController that will inspect audio settings and react.
- // then run the signal the first time to make sure that we set the proper visibility
- // on the items from the start.
- //
- auto ctrl = new AudioSettingsController(this);
- QVariantMap val;
- val.insert("devicetype", value(SETTINGS_SECTION_AUDIO, "devicetype"));
- val.insert("advanced", value(SETTINGS_SECTION_AUDIO, "advanced"));
- ctrl->valuesUpdated(val);
- connect(ctrl, &AudioSettingsController::settingsUpdated, this, &SettingsComponent::groupUpdate);
- return true;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::setupVersion()
- {
- QSettings settings;
- m_oldestPreviousVersion = settings.value(OLDEST_PREVIOUS_VERSION_KEY).toString();
- if (m_oldestPreviousVersion.isEmpty())
- {
- // Version key was not present. It could still be a pre-1.1 PMP install,
- // so here we try to find out whether this is the very first install, or
- // if an older one exists.
- QFile configFile(Paths::dataDir("plexmediaplayer.conf"));
- if (configFile.exists())
- m_oldestPreviousVersion = "legacy";
- else
- m_oldestPreviousVersion = Version::GetVersionString();
- settings.setValue(OLDEST_PREVIOUS_VERSION_KEY, m_oldestPreviousVersion);
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SettingsComponent::setUserRoleList(const QStringList& userRoles)
- {
- QVariantList values;
- // stable is always available
- QVariantMap stable;
- stable.insert("value", 0);
- stable.insert("title", "Stable");
- values << stable;
- for(const QString& role : userRoles)
- {
- QVariantMap channel;
- int value = 0;
- QString title;
- if (role == "ninja")
- {
- value = 4;
- title = "Ninja";
- }
- else if (role == "plexpass")
- {
- value = 8;
- title = "PlexPass";
- }
- else if (role == "employee")
- {
- value = 2;
- title = "Employee";
- }
- channel.insert("value", value);
- channel.insert("title", title);
- values << channel;
- }
- updatePossibleValues(SETTINGS_SECTION_MAIN, "updateChannel", values);
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- bool SettingsComponent::resetAndSaveOldConfiguration()
- {
- QFile settingsFile(Paths::dataDir("plexmediaplayer.conf"));
- return settingsFile.rename(Paths::dataDir("plexmediaplayer.conf.old"));
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QString SettingsComponent::getWebClientUrl()
- {
- auto mode = SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "webMode").toString();
- QString url;
- if (url == "desktop")
- url = SettingsComponent::Get().value(SETTINGS_SECTION_PATH, "startupurl_desktop").toString();
- else
- url = SettingsComponent::Get().value(SETTINGS_SECTION_PATH, "startupurl_tv").toString();
- // Transition to the new value so that old users are not screwed.
- if (url == "qrc:/konvergo/index.html")
- {
- SettingsComponent::Get().setValue(SETTINGS_SECTION_PATH, "startupurl", "bundled");
- url = "bundled";
- }
- if (url == "bundled")
- {
- auto path = Paths::webClientPath(mode);
- if (path.startsWith("/"))
- url = "file://" + path;
- url = "file:///" + path;
- }
- QLOG_DEBUG() << "Using web-client URL: " << url;
- return url;
- }
|