SettingsComponent.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. #include "SettingsComponent.h"
  2. #include "SettingsSection.h"
  3. #include "Paths.h"
  4. #include "utils/Utils.h"
  5. #include "QsLog.h"
  6. #include "AudioSettingsController.h"
  7. #include "Names.h"
  8. #include <QSaveFile>
  9. #include <QJsonDocument>
  10. #include <QJsonObject>
  11. #include <QJsonArray>
  12. #include <QList>
  13. #include "input/InputComponent.h"
  14. ///////////////////////////////////////////////////////////////////////////////////////////////////
  15. SettingsComponent::SettingsComponent(QObject *parent) : ComponentBase(parent), m_settingsVersion(-1)
  16. {
  17. }
  18. /////////////////////////////////////////////////////////////////////////////////////////
  19. void SettingsComponent::componentPostInitalize()
  20. {
  21. InputComponent::Get().registerHostCommand("fullscreen", this, "toggleFullScreen");
  22. }
  23. /////////////////////////////////////////////////////////////////////////////////////////
  24. void SettingsComponent::toggleFullScreen(const QString& args)
  25. {
  26. setValue(SETTINGS_SECTION_MAIN, "fullscreen", !value(SETTINGS_SECTION_MAIN, "fullscreen").toBool());
  27. }
  28. ///////////////////////////////////////////////////////////////////////////////////////////////////
  29. void SettingsComponent::updatePossibleValues(const QString &sectionID, const QString &key, const QVariantList &possibleValues)
  30. {
  31. SettingsSection* section = getSection(sectionID);
  32. if (!section)
  33. {
  34. QLOG_ERROR() << "Section" << sectionID << "is unknown";
  35. return;
  36. }
  37. section->updatePossibleValues(key, possibleValues);
  38. QLOG_DEBUG() << "Updated possible values for:" << key << "to" << possibleValues;
  39. }
  40. ///////////////////////////////////////////////////////////////////////////////////////////////////
  41. QVariant SettingsComponent::allValues(const QString& section)
  42. {
  43. if (section.isEmpty())
  44. {
  45. QVariantMap all;
  46. foreach (const QString& sname, m_sections.keys())
  47. all[sname] = m_sections[sname]->allValues();
  48. return all;
  49. }
  50. else if (m_sections.contains(section))
  51. {
  52. return m_sections[section]->allValues();
  53. }
  54. else
  55. {
  56. // look for the value in the webclient section
  57. return m_sections[SETTINGS_SECTION_WEBCLIENT]->value(section);
  58. }
  59. }
  60. ///////////////////////////////////////////////////////////////////////////////////////////////////
  61. static void writeFile(const QString& filename, const QByteArray& data)
  62. {
  63. QSaveFile file(filename);
  64. file.open(QIODevice::WriteOnly | QIODevice::Text);
  65. file.write(data);
  66. if (!file.commit())
  67. {
  68. QLOG_ERROR() << "Could not write" << filename;
  69. }
  70. }
  71. ///////////////////////////////////////////////////////////////////////////////////////////////////
  72. static QJsonObject loadJson(const QString& filename)
  73. {
  74. // Checking existence before opening is technically a race condition, but
  75. // it looks like Qt doesn't let us distinguish errors on opening.
  76. if (!QFile(filename).exists())
  77. return QJsonObject();
  78. QJsonParseError err;
  79. QJsonDocument json = Utils::OpenJsonDocument(filename, &err);
  80. if (json.isNull())
  81. {
  82. QLOG_ERROR() << "Could not open" << filename << "due to" << err.errorString();
  83. }
  84. return json.object();
  85. }
  86. ///////////////////////////////////////////////////////////////////////////////////////////////////
  87. static void writeJson(const QString& filename, const QJsonObject& data, bool pretty = true)
  88. {
  89. QJsonDocument json(data);
  90. writeFile(filename, json.toJson(pretty ? QJsonDocument::Indented : QJsonDocument::Compact));
  91. }
  92. /////////////////////////////////////////////////////////////////////////////////////////
  93. QVariant SettingsComponent::readPreinitValue(const QString& sectionID, const QString& key)
  94. {
  95. QJsonObject json = loadJson(Paths::dataDir("plexmediaplayer.conf"));
  96. return json["sections"].toObject()[sectionID].toObject()[key].toVariant();
  97. }
  98. /////////////////////////////////////////////////////////////////////////////////////////
  99. void SettingsComponent::load()
  100. {
  101. loadConf(Paths::dataDir("plexmediaplayer.conf"), false);
  102. loadConf(Paths::dataDir("storage.json"), true);
  103. }
  104. ///////////////////////////////////////////////////////////////////////////////////////////////////
  105. void SettingsComponent::loadConf(const QString& path, bool storage)
  106. {
  107. QJsonObject json = loadJson(path);
  108. int version = json["version"].toInt(0);
  109. if (version != m_settingsVersion)
  110. {
  111. QString backup = path + ".broken";
  112. QFile::remove(backup);
  113. QFile::rename(path, backup);
  114. if (version == 0)
  115. QLOG_ERROR() << "Could not read config file.";
  116. else
  117. QLOG_ERROR() << "Config version is" << version << "but" << m_settingsVersion << "expected. Moving old config to" << backup;
  118. // Overwrite/create it with the defaults.
  119. if (storage)
  120. saveStorage();
  121. else
  122. saveSettings();
  123. return;
  124. }
  125. QJsonObject jsonSections = json["sections"].toObject();
  126. foreach (const QString& section, jsonSections.keys())
  127. {
  128. QJsonObject jsonSection = jsonSections[section].toObject();
  129. SettingsSection* sec = getSection(section);
  130. if (!sec && storage)
  131. {
  132. sec = new SettingsSection(section, PLATFORM_ANY, -1, this);
  133. sec->setHidden(true);
  134. sec->setStorage(true);
  135. m_sections.insert(section, sec);
  136. }
  137. else if (!sec)
  138. {
  139. QLOG_ERROR() << "Trying to load section:" << section << "from config file, but we don't want that.";
  140. continue;
  141. }
  142. foreach (const QString& setting, jsonSection.keys())
  143. sec->setValue(setting, jsonSection.value(setting).toVariant());
  144. }
  145. }
  146. ///////////////////////////////////////////////////////////////////////////////////////////////////
  147. void SettingsComponent::saveSettings()
  148. {
  149. QVariantMap sections;
  150. foreach(SettingsSection* section, m_sections.values())
  151. {
  152. if (!section->isStorage())
  153. sections.insert(section->sectionName(), section->allValues());
  154. }
  155. QJsonObject json;
  156. json.insert("sections", QJsonValue::fromVariant(sections));
  157. json.insert("version", m_settingsVersion);
  158. writeJson(Paths::dataDir("plexmediaplayer.conf"), json);
  159. }
  160. ///////////////////////////////////////////////////////////////////////////////////////////////////
  161. void SettingsComponent::saveStorage()
  162. {
  163. QVariantMap storage;
  164. foreach(SettingsSection* section, m_sections.values())
  165. {
  166. if (section->isStorage())
  167. storage.insert(section->sectionName(), section->allValues());
  168. }
  169. QJsonObject storagejson;
  170. storagejson.insert("sections", QJsonValue::fromVariant(storage));
  171. storagejson.insert("version", m_settingsVersion);
  172. writeJson(Paths::dataDir("storage.json"), storagejson, false);
  173. }
  174. ///////////////////////////////////////////////////////////////////////////////////////////////////
  175. void SettingsComponent::saveSection(SettingsSection* section)
  176. {
  177. if (section && section->isStorage())
  178. saveStorage();
  179. else
  180. saveSettings();
  181. }
  182. ///////////////////////////////////////////////////////////////////////////////////////////////////
  183. QVariant SettingsComponent::value(const QString& sectionID, const QString &key)
  184. {
  185. SettingsSection* section = getSection(sectionID);
  186. if (!section)
  187. {
  188. QLOG_ERROR() << "Section" << sectionID << "is unknown";
  189. return QVariant();
  190. }
  191. return section->value(key);
  192. }
  193. ///////////////////////////////////////////////////////////////////////////////////////////////////
  194. void SettingsComponent::setValue(const QString& sectionID, const QString &key, const QVariant &value)
  195. {
  196. SettingsSection* section = getSection(sectionID);
  197. if (!section)
  198. {
  199. QLOG_ERROR() << "Section" << sectionID << "is unknown";
  200. return;
  201. }
  202. section->setValue(key, value);
  203. saveSection(section);
  204. }
  205. ///////////////////////////////////////////////////////////////////////////////////////////////////
  206. void SettingsComponent::setValues(const QVariantMap& options)
  207. {
  208. Q_ASSERT(options.contains("key"));
  209. Q_ASSERT(options.contains("value"));
  210. QString key = options["key"].toString();
  211. QVariant values = options["value"];
  212. if (values.type() == QVariant::Map || values.isNull())
  213. {
  214. SettingsSection* section = getSection(key);
  215. if (!section)
  216. {
  217. // let's create this section since it's most likely created by the webclient
  218. section = new SettingsSection(key, PLATFORM_ANY, -1, this);
  219. section->setHidden(true);
  220. section->setStorage(true);
  221. m_sections.insert(key, section);
  222. }
  223. if (values.isNull())
  224. section->removeValues();
  225. else
  226. section->setValues(values);
  227. saveSection(section);
  228. }
  229. else if (values.type() == QVariant::String)
  230. {
  231. setValue(SETTINGS_SECTION_WEBCLIENT, key, values);
  232. }
  233. else
  234. {
  235. QLOG_WARN() << "the values sent was not a map, string or empty value. it will be ignored";
  236. // return so we don't call save()
  237. return;
  238. }
  239. }
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////
  241. void SettingsComponent::removeValue(const QString &sectionOrKey)
  242. {
  243. SettingsSection* section = getSection(sectionOrKey);
  244. if (section)
  245. {
  246. // we want to remove a full section
  247. // dont remove the section, but remove all keys
  248. section->removeValues();
  249. saveSection(section);
  250. }
  251. else
  252. {
  253. // we want to remove a root key from webclient
  254. // which is stored in webclient section
  255. section = m_sections[SETTINGS_SECTION_WEBCLIENT];
  256. section->removeValue(sectionOrKey);
  257. saveSection(section);
  258. }
  259. }
  260. ///////////////////////////////////////////////////////////////////////////////////////////////////
  261. void SettingsComponent::resetToDefault()
  262. {
  263. foreach(SettingsSection *section, m_sections)
  264. {
  265. section->resetToDefault();
  266. }
  267. }
  268. /////////////////////////////////////////////////////////////////////////////////////////
  269. struct section_order_index
  270. {
  271. inline bool operator ()(SettingsSection* a, SettingsSection* b)
  272. {
  273. return a->orderIndex() < b->orderIndex();
  274. }
  275. };
  276. ///////////////////////////////////////////////////////////////////////////////////////////////////
  277. QVariantList SettingsComponent::settingDescriptions()
  278. {
  279. QJsonArray desc;
  280. QList<SettingsSection*> sectionList = m_sections.values();
  281. std::sort(sectionList.begin(), sectionList.end(), section_order_index());
  282. foreach(SettingsSection* section, sectionList)
  283. {
  284. if (!section->isHidden())
  285. desc.push_back(QJsonValue::fromVariant(section->descriptions()));
  286. }
  287. return desc.toVariantList();
  288. }
  289. /////////////////////////////////////////////////////////////////////////////////////////
  290. bool SettingsComponent::loadDescription()
  291. {
  292. QJsonParseError err;
  293. auto doc = Utils::OpenJsonDocument(":/settings/settings_description.json", &err);
  294. if (doc.isNull())
  295. {
  296. QLOG_ERROR() << "Failed to read settings description:" << err.errorString();
  297. throw FatalException("Failed to read settings description!");
  298. }
  299. if (!doc.isArray())
  300. {
  301. QLOG_ERROR() << "The object needs to be an array";
  302. return false;
  303. }
  304. m_sectionIndex = 0;
  305. foreach(const QJsonValue& val, doc.array())
  306. {
  307. if (!val.isObject())
  308. {
  309. QLOG_ERROR() << "Hoped to find sections in the root array, but they where not JSON objects";
  310. return false;
  311. }
  312. QJsonObject section = val.toObject();
  313. if (!section.contains("section"))
  314. {
  315. QLOG_ERROR() << "A section needs to contain the section keyword.";
  316. return false;
  317. }
  318. if (section["section"] == "__meta__")
  319. m_settingsVersion = section.value("version").toInt();
  320. else
  321. parseSection(section);
  322. }
  323. return true;
  324. }
  325. /////////////////////////////////////////////////////////////////////////////////////////
  326. void SettingsComponent::parseSection(const QJsonObject& sectionObject)
  327. {
  328. QString sectionName = sectionObject.value("section").toString();
  329. if (!sectionObject.contains("values") || !sectionObject.value("values").isArray())
  330. {
  331. QLOG_ERROR() << "section object:" << sectionName << "did not contain a values array";
  332. return;
  333. }
  334. int platformMask = platformMaskFromObject(sectionObject);
  335. SettingsSection* section = new SettingsSection(sectionName, (quint8)platformMask, m_sectionIndex ++, this);
  336. section->setHidden(sectionObject.value("hidden").toBool(false));
  337. section->setStorage(sectionObject.value("storage").toBool(false));
  338. auto values = sectionObject.value("values").toArray();
  339. int order = 0;
  340. foreach(auto val, values)
  341. {
  342. if (!val.isObject())
  343. continue;
  344. QJsonObject valobj = val.toObject();
  345. if (!valobj.contains("value") || !valobj.contains("default") || valobj.value("value").isNull())
  346. continue;
  347. QJsonValue defaults = valobj.value("default");
  348. QVariant defaultval = defaults.toVariant();
  349. if (defaults.isArray())
  350. {
  351. defaultval = QVariant();
  352. // Whichever default matches the current platform first is used.
  353. foreach(const auto& v, defaults.toArray())
  354. {
  355. auto vobj = v.toObject();
  356. int defPlatformMask = platformMaskFromObject(vobj);
  357. if ((defPlatformMask & Utils::CurrentPlatform()) == Utils::CurrentPlatform())
  358. {
  359. defaultval = vobj.value("value").toVariant();
  360. break;
  361. }
  362. }
  363. }
  364. int vPlatformMask = platformMaskFromObject(valobj);
  365. SettingsValue* setting = new SettingsValue(valobj.value("value").toString(), defaultval, (quint8)vPlatformMask, this);
  366. setting->setHidden(valobj.value("hidden").toBool(false));
  367. setting->setIndexOrder(order ++);
  368. if (valobj.contains("input_type"))
  369. setting->setInputType(valobj.value("input_type").toString());
  370. if (valobj.contains("possible_values") && valobj.value("possible_values").isArray())
  371. {
  372. auto list = valobj.value("possible_values").toArray();
  373. foreach(const auto& v, list)
  374. {
  375. int platform = PLATFORM_ANY;
  376. auto vl = v.toArray();
  377. if (vl.size() < 2)
  378. continue;
  379. if (vl.size() == 3 && vl.at(2).isObject())
  380. {
  381. // platform options
  382. QJsonObject options = vl.at(2).toObject();
  383. platform = platformMaskFromObject(options);
  384. }
  385. if ((platform & Utils::CurrentPlatform()) == Utils::CurrentPlatform())
  386. setting->addPossibleValue(vl.at(1).toString(), vl.at(0).toVariant());
  387. }
  388. }
  389. section->registerSetting(setting);
  390. }
  391. m_sections.insert(sectionName, section);
  392. }
  393. /////////////////////////////////////////////////////////////////////////////////////////
  394. int SettingsComponent::platformMaskFromObject(const QJsonObject& object)
  395. {
  396. int platformMask = PLATFORM_ANY;
  397. // only include the listed platforms
  398. if (object.contains("platforms"))
  399. {
  400. // start by resetting it to no platforms
  401. platformMask = PLATFORM_UNKNOWN;
  402. QJsonValue platforms = object.value("platforms");
  403. // platforms can be both array or a single string
  404. if (platforms.isArray())
  405. {
  406. foreach(const QJsonValue& pl, platforms.toArray())
  407. {
  408. if (!pl.isString())
  409. continue;
  410. platformMask |= platformFromString(pl.toString());
  411. }
  412. }
  413. else
  414. {
  415. platformMask = platformFromString(platforms.toString());
  416. }
  417. }
  418. else if (object.contains("platforms_excluded"))
  419. {
  420. QJsonValue val = object.value("platforms_excluded");
  421. if (val.isArray())
  422. {
  423. foreach(const QJsonValue& pl, val.toArray())
  424. {
  425. if (!pl.isString())
  426. continue;
  427. platformMask &= ~platformFromString(pl.toString());
  428. }
  429. }
  430. else
  431. {
  432. platformMask &= ~platformFromString(val.toString());
  433. }
  434. }
  435. return platformMask;
  436. }
  437. /////////////////////////////////////////////////////////////////////////////////////////
  438. Platform SettingsComponent::platformFromString(const QString& platformString)
  439. {
  440. if (platformString == "osx")
  441. return PLATFORM_OSX;
  442. else if (platformString == "windows")
  443. return PLATFORM_WINDOWS;
  444. else if (platformString == "linux")
  445. return PLATFORM_LINUX;
  446. else if (platformString == "oe")
  447. return PLATFORM_OE;
  448. else if (platformString == "oe_rpi")
  449. return PLATFORM_OE_RPI;
  450. else if (platformString == "oe_x86")
  451. return PLATFORM_OE_X86;
  452. else if (platformString == "any")
  453. return PLATFORM_ANY;
  454. return PLATFORM_UNKNOWN;
  455. }
  456. /////////////////////////////////////////////////////////////////////////////////////////
  457. bool SettingsComponent::componentInitialize()
  458. {
  459. if (!loadDescription())
  460. return false;
  461. load();
  462. // add our AudioSettingsController that will inspect audio settings and react.
  463. // then run the signal the first time to make sure that we set the proper visibility
  464. // on the items from the start.
  465. //
  466. AudioSettingsController* ctrl = new AudioSettingsController(this);
  467. QVariantMap val;
  468. val.insert("devicetype", value(SETTINGS_SECTION_AUDIO, "devicetype"));
  469. val.insert("advanced", value(SETTINGS_SECTION_AUDIO, "advanced"));
  470. ctrl->valuesUpdated(val);
  471. connect(ctrl, &AudioSettingsController::settingsUpdated, this, &SettingsComponent::groupUpdate);
  472. return true;
  473. }
  474. /////////////////////////////////////////////////////////////////////////////////////////
  475. void SettingsComponent::setUserRoleList(const QStringList& userRoles)
  476. {
  477. QVariantList values;
  478. // stable is always available
  479. QVariantMap stable;
  480. stable.insert("value", 0);
  481. stable.insert("title", "Stable");
  482. values << stable;
  483. foreach(const QString& role, userRoles)
  484. {
  485. QVariantMap channel;
  486. int value = 0;
  487. QString title;
  488. if (role == "ninja")
  489. {
  490. value = 4;
  491. title = "Ninja";
  492. }
  493. else if (role == "plexpass")
  494. {
  495. value = 8;
  496. title = "PlexPass";
  497. }
  498. else if (role == "employee")
  499. {
  500. value = 2;
  501. title = "Employee";
  502. }
  503. channel.insert("value", value);
  504. channel.insert("title", title);
  505. values << channel;
  506. }
  507. updatePossibleValues(SETTINGS_SECTION_MAIN, "updateChannel", values);
  508. }
  509. /////////////////////////////////////////////////////////////////////////////////////////
  510. bool SettingsComponent::resetAndSaveOldConfiguration()
  511. {
  512. QFile settingsFile(Paths::dataDir("plexmediaplayer.conf"));
  513. return settingsFile.rename(Paths::dataDir("plexmediaplayer.conf.old"));
  514. }