SettingsManager.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. // proj
  2. #include <hyperion/SettingsManager.h>
  3. // util
  4. #include <utils/JsonUtils.h>
  5. #include <utils/QStringUtils.h>
  6. #include <db/SettingsTable.h>
  7. #include "HyperionConfig.h"
  8. // json schema process
  9. #include <utils/jsonschema/QJsonFactory.h>
  10. #include <utils/jsonschema/QJsonSchemaChecker.h>
  11. // write config to filesystem
  12. #include <utils/JsonUtils.h>
  13. #include <utils/version.hpp>
  14. using namespace semver;
  15. // Constants
  16. namespace {
  17. const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
  18. } //End of constants
  19. QJsonObject SettingsManager::schemaJson;
  20. SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode)
  21. : QObject(parent)
  22. , _log(Logger::getInstance("SETTINGSMGR", "I"+QString::number(instance)))
  23. , _instance(instance)
  24. , _sTable(new SettingsTable(instance, this))
  25. , _configVersion(DEFAULT_VERSION)
  26. , _previousVersion(DEFAULT_VERSION)
  27. , _readonlyMode(readonlyMode)
  28. {
  29. _sTable->setReadonlyMode(_readonlyMode);
  30. // get schema
  31. if (schemaJson.isEmpty())
  32. {
  33. Q_INIT_RESOURCE(resource);
  34. try
  35. {
  36. schemaJson = QJsonFactory::readSchema(":/hyperion-schema");
  37. }
  38. catch (const std::runtime_error& error)
  39. {
  40. throw std::runtime_error(error.what());
  41. }
  42. }
  43. // get default config
  44. QJsonObject defaultConfig;
  45. if (!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log))
  46. {
  47. throw std::runtime_error("Failed to read default config");
  48. }
  49. // transform json to string lists
  50. const QStringList keyList = defaultConfig.keys();
  51. QStringList defValueList;
  52. for (const auto& key : keyList)
  53. {
  54. if (defaultConfig[key].isObject())
  55. {
  56. defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact));
  57. }
  58. else if (defaultConfig[key].isArray())
  59. {
  60. defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact));
  61. }
  62. }
  63. // fill database with default data if required
  64. for (const auto& key : keyList)
  65. {
  66. QString val = defValueList.takeFirst();
  67. // prevent overwrite
  68. if (!_sTable->recordExist(key))
  69. {
  70. _sTable->createSettingsRecord(key, val);
  71. }
  72. }
  73. // need to validate all data in database construct the entire data object
  74. // TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry...
  75. QJsonObject dbConfig;
  76. for (const auto& key : keyList)
  77. {
  78. QJsonDocument doc = _sTable->getSettingsRecord(key);
  79. if (doc.isArray())
  80. {
  81. dbConfig[key] = doc.array();
  82. }
  83. else
  84. {
  85. dbConfig[key] = doc.object();
  86. }
  87. }
  88. //Check, if database requires migration
  89. bool isNewRelease = false;
  90. // Use instance independent SettingsManager to track migration status
  91. if (_instance == GLOABL_INSTANCE_ID)
  92. {
  93. if (resolveConfigVersion(dbConfig))
  94. {
  95. QJsonObject newGeneralConfig = dbConfig["general"].toObject();
  96. semver::version BUILD_VERSION(HYPERION_VERSION);
  97. if (!BUILD_VERSION.isValid())
  98. {
  99. Error(_log, "Current Hyperion version [%s] is invalid. Exiting...", BUILD_VERSION.getVersion().c_str());
  100. exit(1);
  101. }
  102. if (_configVersion > BUILD_VERSION)
  103. {
  104. Error(_log, "Database version [%s] is greater than current Hyperion version [%s]", _configVersion.getVersion().c_str(), BUILD_VERSION.getVersion().c_str());
  105. // TODO: Remove version checking and Settingsmanager from components' constructor to be able to stop hyperion.
  106. }
  107. else
  108. {
  109. if (_previousVersion < BUILD_VERSION)
  110. {
  111. if (_configVersion == BUILD_VERSION)
  112. {
  113. newGeneralConfig["previousVersion"] = BUILD_VERSION.getVersion().c_str();
  114. dbConfig["general"] = newGeneralConfig;
  115. isNewRelease = true;
  116. Info(_log, "Migration completed to version [%s]", BUILD_VERSION.getVersion().c_str());
  117. }
  118. else
  119. {
  120. Info(_log, "Migration from current version [%s] to new version [%s] started", _previousVersion.getVersion().c_str(), BUILD_VERSION.getVersion().c_str());
  121. newGeneralConfig["previousVersion"] = _configVersion.getVersion().c_str();
  122. newGeneralConfig["configVersion"] = BUILD_VERSION.getVersion().c_str();
  123. dbConfig["general"] = newGeneralConfig;
  124. isNewRelease = true;
  125. }
  126. }
  127. }
  128. }
  129. }
  130. // possible data upgrade steps to prevent data loss
  131. bool migrated = handleConfigUpgrade(dbConfig);
  132. if (isNewRelease || migrated)
  133. {
  134. saveSettings(dbConfig, true);
  135. }
  136. // validate full dbconfig against schema, on error we need to rewrite entire table
  137. QJsonSchemaChecker schemaChecker;
  138. schemaChecker.setSchema(schemaJson);
  139. QPair<bool, bool> valid = schemaChecker.validate(dbConfig);
  140. // check if our main schema syntax is IO
  141. if (!valid.second)
  142. {
  143. for (auto& schemaError : schemaChecker.getMessages())
  144. {
  145. Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
  146. }
  147. throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!");
  148. }
  149. if (!valid.first)
  150. {
  151. Info(_log, "Table upgrade required...");
  152. dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig);
  153. for (auto& schemaError : schemaChecker.getMessages())
  154. {
  155. Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
  156. }
  157. saveSettings(dbConfig, true);
  158. }
  159. else
  160. {
  161. _qconfig = dbConfig;
  162. }
  163. Debug(_log, "Settings database initialized");
  164. }
  165. QJsonDocument SettingsManager::getSetting(settings::type type) const
  166. {
  167. return _sTable->getSettingsRecord(settings::typeToString(type));
  168. }
  169. QJsonObject SettingsManager::getSettings() const
  170. {
  171. QJsonObject config;
  172. for (const auto& key : _qconfig.keys())
  173. {
  174. //Read all records from database to ensure that global settings are read across instances
  175. QJsonDocument doc = _sTable->getSettingsRecord(key);
  176. if (doc.isArray())
  177. {
  178. config.insert(key, doc.array());
  179. }
  180. else
  181. {
  182. config.insert(key, doc.object());
  183. }
  184. }
  185. return config;
  186. }
  187. bool SettingsManager::restoreSettings(QJsonObject config, bool correct)
  188. {
  189. // optional data upgrades e.g. imported legacy/older configs
  190. handleConfigUpgrade(config);
  191. return saveSettings(config, correct);
  192. }
  193. bool SettingsManager::saveSettings(QJsonObject config, bool correct)
  194. {
  195. // we need to validate data against schema
  196. QJsonSchemaChecker schemaChecker;
  197. schemaChecker.setSchema(schemaJson);
  198. if (!schemaChecker.validate(config).first)
  199. {
  200. if (!correct)
  201. {
  202. Error(_log, "Failed to save configuration, errors during validation");
  203. return false;
  204. }
  205. Warning(_log, "Fixing json data!");
  206. config = schemaChecker.getAutoCorrectedConfig(config);
  207. for (const auto& schemaError : schemaChecker.getMessages())
  208. {
  209. Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
  210. }
  211. }
  212. // store the new config
  213. _qconfig = config;
  214. // extract keys and data
  215. const QStringList keyList = config.keys();
  216. QStringList newValueList;
  217. for (const auto& key : keyList)
  218. {
  219. if (config[key].isObject())
  220. {
  221. newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact));
  222. }
  223. else if (config[key].isArray())
  224. {
  225. newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
  226. }
  227. }
  228. bool rc = true;
  229. // compare database data with new data to emit/save changes accordingly
  230. for (const auto& key : keyList)
  231. {
  232. QString data = newValueList.takeFirst();
  233. if (_sTable->getSettingsRecordString(key) != data)
  234. {
  235. if (!_sTable->createSettingsRecord(key, data))
  236. {
  237. rc = false;
  238. }
  239. else
  240. {
  241. QJsonParseError error;
  242. QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toUtf8(), &error);
  243. if (error.error != QJsonParseError::NoError) {
  244. Error(_log, "Error parsing JSON: %s", QSTRING_CSTR(error.errorString()));
  245. rc = false;
  246. }
  247. else {
  248. emit settingsChanged(settings::stringToType(key), jsonDocument);
  249. }
  250. }
  251. }
  252. }
  253. return rc;
  254. }
  255. inline QString fixVersion(const QString& version)
  256. {
  257. QString newVersion;
  258. //Try fixing version number, remove dot separated pre-release identifiers not supported
  259. QRegularExpression regEx("(\\d+\\.\\d+\\.\\d+-?[a-zA-Z-\\d]*\\.?[\\d]*)", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption);
  260. QRegularExpressionMatch match;
  261. match = regEx.match(version);
  262. if (match.hasMatch())
  263. {
  264. newVersion = match.captured(1);
  265. }
  266. return newVersion;
  267. }
  268. bool SettingsManager::resolveConfigVersion(QJsonObject& config)
  269. {
  270. bool isValid = false;
  271. if (config.contains("general"))
  272. {
  273. QJsonObject generalConfig = config["general"].toObject();
  274. QString configVersion = generalConfig["configVersion"].toString();
  275. QString previousVersion = generalConfig["previousVersion"].toString();
  276. if (!configVersion.isEmpty())
  277. {
  278. isValid = _configVersion.setVersion(configVersion.toStdString());
  279. if (!isValid)
  280. {
  281. isValid = _configVersion.setVersion(fixVersion(configVersion).toStdString());
  282. if (isValid)
  283. {
  284. Info(_log, "Invalid config version [%s] fixed. Updated to [%s]", QSTRING_CSTR(configVersion), _configVersion.getVersion().c_str());
  285. }
  286. }
  287. }
  288. else
  289. {
  290. isValid = true;
  291. }
  292. if (!previousVersion.isEmpty() && isValid)
  293. {
  294. isValid = _previousVersion.setVersion(previousVersion.toStdString());
  295. if (!isValid)
  296. {
  297. isValid = _previousVersion.setVersion(fixVersion(previousVersion).toStdString());
  298. if (isValid)
  299. {
  300. Info(_log, "Invalid previous version [%s] fixed. Updated to [%s]", QSTRING_CSTR(previousVersion), _previousVersion.getVersion().c_str());
  301. }
  302. }
  303. }
  304. else
  305. {
  306. _previousVersion.setVersion(DEFAULT_VERSION);
  307. isValid = true;
  308. }
  309. }
  310. return isValid;
  311. }
  312. bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
  313. {
  314. bool migrated = false;
  315. //Only migrate, if valid versions are available
  316. if (!resolveConfigVersion(config))
  317. {
  318. Warning(_log, "Invalid version information found in configuration. No database migration executed.");
  319. }
  320. else
  321. {
  322. //Do only migrate, if configuration is not up to date
  323. if (_previousVersion < _configVersion)
  324. {
  325. //Migration steps for versions <= alpha 9
  326. semver::version targetVersion{ "2.0.0-alpha.9" };
  327. if (_previousVersion <= targetVersion)
  328. {
  329. Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
  330. // LED LAYOUT UPGRADE
  331. // from { hscan: { minimum: 0.2, maximum: 0.3 }, vscan: { minimum: 0.2, maximum: 0.3 } }
  332. // from { h: { min: 0.2, max: 0.3 }, v: { min: 0.2, max: 0.3 } }
  333. // to { hmin: 0.2, hmax: 0.3, vmin: 0.2, vmax: 0.3}
  334. if (config.contains("leds"))
  335. {
  336. const QJsonArray ledarr = config["leds"].toArray();
  337. const QJsonObject led = ledarr[0].toObject();
  338. if (led.contains("hscan") || led.contains("h"))
  339. {
  340. const bool whscan = led.contains("hscan");
  341. QJsonArray newLedarr;
  342. for (const auto& entry : ledarr)
  343. {
  344. const QJsonObject led = entry.toObject();
  345. QJsonObject hscan;
  346. QJsonObject vscan;
  347. QJsonValue hmin;
  348. QJsonValue hmax;
  349. QJsonValue vmin;
  350. QJsonValue vmax;
  351. QJsonObject nL;
  352. if (whscan)
  353. {
  354. hscan = led["hscan"].toObject();
  355. vscan = led["vscan"].toObject();
  356. hmin = hscan["minimum"];
  357. hmax = hscan["maximum"];
  358. vmin = vscan["minimum"];
  359. vmax = vscan["maximum"];
  360. }
  361. else
  362. {
  363. hscan = led["h"].toObject();
  364. vscan = led["v"].toObject();
  365. hmin = hscan["min"];
  366. hmax = hscan["max"];
  367. vmin = vscan["min"];
  368. vmax = vscan["max"];
  369. }
  370. // append to led object
  371. nL["hmin"] = hmin;
  372. nL["hmax"] = hmax;
  373. nL["vmin"] = vmin;
  374. nL["vmax"] = vmax;
  375. newLedarr.append(nL);
  376. }
  377. // replace
  378. config["leds"] = newLedarr;
  379. migrated = true;
  380. Info(_log, "Instance [%u]: LED Layout migrated", _instance);
  381. }
  382. }
  383. if (config.contains("ledConfig"))
  384. {
  385. QJsonObject oldLedConfig = config["ledConfig"].toObject();
  386. if (!oldLedConfig.contains("classic"))
  387. {
  388. QJsonObject newLedConfig;
  389. newLedConfig.insert("classic", oldLedConfig);
  390. QJsonObject defaultMatrixConfig{ {"ledshoriz", 1}
  391. ,{"ledsvert", 1}
  392. ,{"cabling","snake"}
  393. ,{"start","top-left"}
  394. };
  395. newLedConfig.insert("matrix", defaultMatrixConfig);
  396. config["ledConfig"] = newLedConfig;
  397. migrated = true;
  398. Info(_log, "Instance [%u]: LED-Config migrated", _instance);
  399. }
  400. }
  401. // LED Hardware count is leading for versions after alpha 9
  402. // Setting Hardware LED count to number of LEDs configured via layout, if layout number is greater than number of hardware LEDs
  403. if (config.contains("device"))
  404. {
  405. QJsonObject newDeviceConfig = config["device"].toObject();
  406. if (newDeviceConfig.contains("hardwareLedCount"))
  407. {
  408. int hwLedcount = newDeviceConfig["hardwareLedCount"].toInt();
  409. if (config.contains("leds"))
  410. {
  411. const QJsonArray ledarr = config["leds"].toArray();
  412. int layoutLedCount = ledarr.size();
  413. if (hwLedcount < layoutLedCount)
  414. {
  415. Warning(_log, "Instance [%u]: HwLedCount/Layout mismatch! Setting Hardware LED count to number of LEDs configured via layout", _instance);
  416. hwLedcount = layoutLedCount;
  417. newDeviceConfig["hardwareLedCount"] = hwLedcount;
  418. migrated = true;
  419. }
  420. }
  421. }
  422. if (newDeviceConfig.contains("type"))
  423. {
  424. QString type = newDeviceConfig["type"].toString();
  425. if (type == "atmoorb" || type == "fadecandy" || type == "philipshue")
  426. {
  427. if (newDeviceConfig.contains("output"))
  428. {
  429. newDeviceConfig["host"] = newDeviceConfig["output"].toString();
  430. newDeviceConfig.remove("output");
  431. migrated = true;
  432. }
  433. }
  434. }
  435. if (migrated)
  436. {
  437. config["device"] = newDeviceConfig;
  438. Debug(_log, "LED-Device records migrated");
  439. }
  440. }
  441. if (config.contains("grabberV4L2"))
  442. {
  443. QJsonObject newGrabberV4L2Config = config["grabberV4L2"].toObject();
  444. if (newGrabberV4L2Config.contains("encoding_format"))
  445. {
  446. newGrabberV4L2Config.remove("encoding_format");
  447. newGrabberV4L2Config["grabberV4L2"] = newGrabberV4L2Config;
  448. migrated = true;
  449. }
  450. //Add new element enable
  451. if (!newGrabberV4L2Config.contains("enable"))
  452. {
  453. newGrabberV4L2Config["enable"] = false;
  454. migrated = true;
  455. }
  456. config["grabberV4L2"] = newGrabberV4L2Config;
  457. Debug(_log, "GrabberV4L2 records migrated");
  458. }
  459. if (config.contains("grabberAudio"))
  460. {
  461. QJsonObject newGrabberAudioConfig = config["grabberAudio"].toObject();
  462. //Add new element enable
  463. if (!newGrabberAudioConfig.contains("enable"))
  464. {
  465. newGrabberAudioConfig["enable"] = false;
  466. migrated = true;
  467. }
  468. config["grabberAudio"] = newGrabberAudioConfig;
  469. Debug(_log, "GrabberAudio records migrated");
  470. }
  471. if (config.contains("framegrabber"))
  472. {
  473. QJsonObject newFramegrabberConfig = config["framegrabber"].toObject();
  474. //Align element namings with grabberV4L2
  475. //Rename element type -> device
  476. if (newFramegrabberConfig.contains("type"))
  477. {
  478. newFramegrabberConfig["device"] = newFramegrabberConfig["type"].toString();
  479. newFramegrabberConfig.remove("type");
  480. migrated = true;
  481. }
  482. //Rename element frequency_Hz -> fps
  483. if (newFramegrabberConfig.contains("frequency_Hz"))
  484. {
  485. newFramegrabberConfig["fps"] = newFramegrabberConfig["frequency_Hz"].toInt(25);
  486. newFramegrabberConfig.remove("frequency_Hz");
  487. migrated = true;
  488. }
  489. //Rename element display -> input
  490. if (newFramegrabberConfig.contains("display"))
  491. {
  492. newFramegrabberConfig["input"] = newFramegrabberConfig["display"];
  493. newFramegrabberConfig.remove("display");
  494. migrated = true;
  495. }
  496. //Add new element enable
  497. if (!newFramegrabberConfig.contains("enable"))
  498. {
  499. newFramegrabberConfig["enable"] = false;
  500. migrated = true;
  501. }
  502. config["framegrabber"] = newFramegrabberConfig;
  503. Debug(_log, "Framegrabber records migrated");
  504. }
  505. }
  506. //Migration steps for versions <= 2.0.12
  507. _previousVersion = targetVersion;
  508. targetVersion.setVersion("2.0.12");
  509. if (_previousVersion <= targetVersion)
  510. {
  511. Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
  512. // Have Hostname/IP-address separate from port for LED-Devices
  513. if (config.contains("device"))
  514. {
  515. QJsonObject newDeviceConfig = config["device"].toObject();
  516. if (newDeviceConfig.contains("host"))
  517. {
  518. QString oldHost = newDeviceConfig["host"].toString();
  519. // Resolve hostname and port
  520. QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
  521. newDeviceConfig["host"] = addressparts[0];
  522. if (addressparts.size() > 1)
  523. {
  524. if (!newDeviceConfig.contains("port"))
  525. {
  526. newDeviceConfig["port"] = addressparts[1].toInt();
  527. }
  528. migrated = true;
  529. }
  530. }
  531. if (newDeviceConfig.contains("type"))
  532. {
  533. QString type = newDeviceConfig["type"].toString();
  534. if (type == "apa102")
  535. {
  536. if (newDeviceConfig.contains("colorOrder"))
  537. {
  538. QString colorOrder = newDeviceConfig["colorOrder"].toString();
  539. if (colorOrder == "bgr")
  540. {
  541. newDeviceConfig["colorOrder"] = "rgb";
  542. migrated = true;
  543. }
  544. }
  545. }
  546. }
  547. if (migrated)
  548. {
  549. config["device"] = newDeviceConfig;
  550. Debug(_log, "LED-Device records migrated");
  551. }
  552. }
  553. // Have Hostname/IP-address separate from port for Forwarder
  554. if (config.contains("forwarder"))
  555. {
  556. QJsonObject newForwarderConfig = config["forwarder"].toObject();
  557. QJsonArray json;
  558. if (newForwarderConfig.contains("json"))
  559. {
  560. const QJsonArray oldJson = newForwarderConfig["json"].toArray();
  561. QJsonObject newJsonConfig;
  562. for (const QJsonValue& value : oldJson)
  563. {
  564. if (value.isString())
  565. {
  566. QString oldHost = value.toString();
  567. // Resolve hostname and port
  568. QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
  569. QString host = addressparts[0];
  570. if (host != "127.0.0.1")
  571. {
  572. newJsonConfig["host"] = host;
  573. if (addressparts.size() > 1)
  574. {
  575. newJsonConfig["port"] = addressparts[1].toInt();
  576. }
  577. else
  578. {
  579. newJsonConfig["port"] = 19444;
  580. }
  581. newJsonConfig["name"] = host;
  582. json.append(newJsonConfig);
  583. migrated = true;
  584. }
  585. }
  586. }
  587. if (!json.isEmpty())
  588. {
  589. newForwarderConfig["jsonapi"] = json;
  590. }
  591. newForwarderConfig.remove("json");
  592. migrated = true;
  593. }
  594. QJsonArray flatbuffer;
  595. if (newForwarderConfig.contains("flat"))
  596. {
  597. const QJsonArray oldFlatbuffer = newForwarderConfig["flat"].toArray();
  598. QJsonObject newFlattbufferConfig;
  599. for (const QJsonValue& value : oldFlatbuffer)
  600. {
  601. if (value.isString())
  602. {
  603. QString oldHost = value.toString();
  604. // Resolve hostname and port
  605. QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
  606. QString host = addressparts[0];
  607. if (host != "127.0.0.1")
  608. {
  609. newFlattbufferConfig["host"] = host;
  610. if (addressparts.size() > 1)
  611. {
  612. newFlattbufferConfig["port"] = addressparts[1].toInt();
  613. }
  614. else
  615. {
  616. newFlattbufferConfig["port"] = 19400;
  617. }
  618. newFlattbufferConfig["name"] = host;
  619. flatbuffer.append(newFlattbufferConfig);
  620. migrated = true;
  621. }
  622. }
  623. if (!flatbuffer.isEmpty())
  624. {
  625. newForwarderConfig["flatbuffer"] = flatbuffer;
  626. }
  627. newForwarderConfig.remove("flat");
  628. migrated = true;
  629. }
  630. }
  631. if (json.isEmpty() && flatbuffer.isEmpty())
  632. {
  633. newForwarderConfig["enable"] = false;
  634. }
  635. if (migrated)
  636. {
  637. config["forwarder"] = newForwarderConfig;
  638. Debug(_log, "Forwarder records migrated");
  639. }
  640. }
  641. }
  642. //Migration steps for versions <= 2.0.13
  643. _previousVersion = targetVersion;
  644. targetVersion.setVersion("2.0.13");
  645. if (_previousVersion <= targetVersion)
  646. {
  647. Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
  648. // Have Hostname/IP-address separate from port for LED-Devices
  649. if (config.contains("device"))
  650. {
  651. QJsonObject newDeviceConfig = config["device"].toObject();
  652. if (newDeviceConfig.contains("type"))
  653. {
  654. QString type = newDeviceConfig["type"].toString();
  655. const QStringList serialDevices {"adalight", "dmx", "atmo", "sedu", "tpm2", "karate"};
  656. if ( serialDevices.contains(type ))
  657. {
  658. if (!newDeviceConfig.contains("rateList"))
  659. {
  660. newDeviceConfig["rateList"] = "CUSTOM";
  661. migrated = true;
  662. }
  663. }
  664. if (type == "adalight")
  665. {
  666. if (newDeviceConfig.contains("lightberry_apa102_mode"))
  667. {
  668. bool lightberry_apa102_mode = newDeviceConfig["lightberry_apa102_mode"].toBool();
  669. if (lightberry_apa102_mode)
  670. {
  671. newDeviceConfig["streamProtocol"] = "1";
  672. }
  673. else
  674. {
  675. newDeviceConfig["streamProtocol"] = "0";
  676. }
  677. newDeviceConfig.remove("lightberry_apa102_mode");
  678. migrated = true;
  679. }
  680. }
  681. }
  682. if (migrated)
  683. {
  684. config["device"] = newDeviceConfig;
  685. Debug(_log, "LED-Device records migrated");
  686. }
  687. }
  688. }
  689. //Migration steps for versions <= 2.0.16
  690. _previousVersion = targetVersion;
  691. targetVersion.setVersion("2.0.16");
  692. if (_previousVersion <= targetVersion)
  693. {
  694. Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
  695. // Have Hostname/IP-address separate from port for LED-Devices
  696. if (config.contains("device"))
  697. {
  698. QJsonObject newDeviceConfig = config["device"].toObject();
  699. if (newDeviceConfig.contains("type"))
  700. {
  701. QString type = newDeviceConfig["type"].toString();
  702. if ( type == "philipshue")
  703. {
  704. if (newDeviceConfig.contains("groupId"))
  705. {
  706. if (newDeviceConfig["groupId"].isDouble())
  707. {
  708. int groupID = newDeviceConfig["groupId"].toInt();
  709. newDeviceConfig["groupId"] = QString::number(groupID);
  710. migrated = true;
  711. }
  712. }
  713. if (newDeviceConfig.contains("lightIds"))
  714. {
  715. QJsonArray lightIds = newDeviceConfig.value( "lightIds").toArray();
  716. // Iterate through the JSON array and update integer values to strings
  717. for (int i = 0; i < lightIds.size(); ++i) {
  718. QJsonValue value = lightIds.at(i);
  719. if (value.isDouble())
  720. {
  721. int lightId = value.toInt();
  722. lightIds.replace(i, QString::number(lightId));
  723. migrated = true;
  724. }
  725. }
  726. newDeviceConfig["lightIds"] = lightIds;
  727. }
  728. }
  729. }
  730. if (migrated)
  731. {
  732. config["device"] = newDeviceConfig;
  733. Debug(_log, "LED-Device records migrated");
  734. }
  735. }
  736. }
  737. }
  738. }
  739. return migrated;
  740. }