From bb652ade36280fe6b2e992f23801379991a08bda Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sun, 1 Nov 2020 19:47:30 +0100 Subject: [PATCH] Read-Only Configuration-Database support (#1046) --- CHANGELOG.md | 2 + assets/webconfig/content/about.html | 2 + assets/webconfig/js/content_colors.js | 7 +- assets/webconfig/js/content_dashboard.js | 1 - assets/webconfig/js/content_effects.js | 6 +- .../js/content_effectsconfigurator.js | 4 +- assets/webconfig/js/content_general.js | 16 ++- assets/webconfig/js/content_grabber.js | 6 +- assets/webconfig/js/content_index.js | 3 + assets/webconfig/js/content_leds.js | 13 +- assets/webconfig/js/content_logging.js | 3 +- assets/webconfig/js/content_network.js | 12 +- assets/webconfig/js/content_webconfig.js | 2 +- assets/webconfig/js/hyperion.js | 3 - assets/webconfig/js/settings.js | 2 +- assets/webconfig/js/wizard.js | 19 ++- include/api/API.h | 2 +- include/db/AuthTable.h | 3 +- include/db/DBManager.h | 9 ++ include/db/InstanceTable.h | 4 +- include/db/MetaTable.h | 4 +- include/hyperion/AuthManager.h | 2 +- include/hyperion/Hyperion.h | 6 +- include/hyperion/HyperionIManager.h | 5 +- include/hyperion/SettingsManager.h | 4 +- libsrc/api/API.cpp | 13 +- libsrc/api/JsonAPI.cpp | 11 +- libsrc/db/DBManager.cpp | 31 +++++ libsrc/hyperion/AuthManager.cpp | 6 +- libsrc/hyperion/Hyperion.cpp | 5 +- libsrc/hyperion/HyperionIManager.cpp | 7 +- libsrc/hyperion/SettingsManager.cpp | 18 ++- src/hyperiond/hyperiond.cpp | 8 +- src/hyperiond/hyperiond.h | 2 +- src/hyperiond/main.cpp | 117 ++++++++++++++---- 35 files changed, 268 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 542dae87..99c19d8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Allow execution with option "--version", while another hyperion daemon is runnin - Added DirectX SDK to CompileHowto - Hide Systray on exit & Install DirectX Redistributable +- Read-Only configuration database support + ### Changed - boblight: reduce cpu time spent on memcopy and parsing rgb values (#1016) - Windows Installer/Uninstaller notification when Hyperion is running (#1033) diff --git a/assets/webconfig/content/about.html b/assets/webconfig/content/about.html index 20c2adc3..d53eebb0 100644 --- a/assets/webconfig/content/about.html +++ b/assets/webconfig/content/about.html @@ -43,6 +43,8 @@ info += '- UI Access: ' + storedAccess + '\n'; //info += 'Log lvl: ' + window.serverConfig.logger.level + '\n'; info += '- Avail Capt: ' + window.serverInfo.grabbers.available + '\n'; + info += '- Database: ' + (shy.readOnlyMode ? "ready-only" : "read/write") + '\n'; + info += '\n'; info += 'Hyperion Server OS: \n'; diff --git a/assets/webconfig/js/content_colors.js b/assets/webconfig/js/content_colors.js index bb178b1c..87428047 100644 --- a/assets/webconfig/js/content_colors.js +++ b/assets/webconfig/js/content_colors.js @@ -35,7 +35,7 @@ $(document).ready( function() { }, true, true); editor_color.on('change',function() { - editor_color.validate().length ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); + editor_color.validate().length || window.readOnlyMode ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); }); $('#btn_submit_color').off().on('click',function() { @@ -48,7 +48,8 @@ $(document).ready( function() { }, true, true); editor_smoothing.on('change',function() { - editor_smoothing.validate().length ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); + editor_smoothing.validate().length || window.readOnlyMode ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); + }); $('#btn_submit_smoothing').off().on('click',function() { @@ -61,7 +62,7 @@ $(document).ready( function() { }, true, true); editor_blackborder.on('change',function() { - editor_blackborder.validate().length ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); + editor_blackborder.validate().length || window.readOnlyMode ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); }); $('#btn_submit_blackborder').off().on('click',function() { diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index ff5383a1..bcb88a60 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -70,7 +70,6 @@ $(document).ready( function() { }); var instancename = window.currentHyperionInstanceName; - console.log ("instancename: ",instancename); $('#dash_statush').html(hyperion_enabled ? ''+$.i18n('general_btn_on')+'' : ''+$.i18n('general_btn_off')+''); $('#btn_hsc').html(hyperion_enabled ? '' : ''); diff --git a/assets/webconfig/js/content_effects.js b/assets/webconfig/js/content_effects.js index 84102fb6..bf08f85c 100644 --- a/assets/webconfig/js/content_effects.js +++ b/assets/webconfig/js/content_effects.js @@ -43,7 +43,7 @@ $(document).ready( function() { }, true, true); effects_editor.on('change',function() { - effects_editor.validate().length ? $('#btn_submit_effects').attr('disabled', true) : $('#btn_submit_effects').attr('disabled', false); + effects_editor.validate().length || window.readOnlyMode ? $('#btn_submit_effects').attr('disabled', true) : $('#btn_submit_effects').attr('disabled', false); }); $('#btn_submit_effects').off().on('click',function() { @@ -65,11 +65,11 @@ $(document).ready( function() { }); foregroundEffect_editor.on('change',function() { - foregroundEffect_editor.validate().length ? $('#btn_submit_foregroundEffect').attr('disabled', true) : $('#btn_submit_foregroundEffect').attr('disabled', false); + foregroundEffect_editor.validate().length || window.readOnlyMode ? $('#btn_submit_foregroundEffect').attr('disabled', true) : $('#btn_submit_foregroundEffect').attr('disabled', false); }); backgroundEffect_editor.on('change',function() { - backgroundEffect_editor.validate().length ? $('#btn_submit_backgroundEffect').attr('disabled', true) : $('#btn_submit_backgroundEffect').attr('disabled', false); + backgroundEffect_editor.validate().length || window.readOnlyMode ? $('#btn_submit_backgroundEffect').attr('disabled', true) : $('#btn_submit_backgroundEffect').attr('disabled', false); }); $('#btn_submit_foregroundEffect').off().on('click',function() { diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index c23b4a07..feea705c 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -83,7 +83,8 @@ $(document).ready( function() { } if( effects_editor.validate().length == 0 && effectName != "") { - $('#btn_start_test, #btn_write').attr('disabled', false); + $('#btn_start_test').attr('disabled', false); + !window.readOnlyMode ? $('#btn_write').attr('disabled', false) : $('#btn_write').attr('disabled', true); } else { @@ -101,6 +102,7 @@ $(document).ready( function() { } else { effects_editor.enable(); $("#eff_footer").children().attr('disabled',false); + !window.readOnlyMode ? $('#btn_write').attr('disabled', false) : $('#btn_write').attr('disabled', true); } }); diff --git a/assets/webconfig/js/content_general.js b/assets/webconfig/js/content_general.js index f0d459d4..4918a874 100644 --- a/assets/webconfig/js/content_general.js +++ b/assets/webconfig/js/content_general.js @@ -18,7 +18,7 @@ $(document).ready( function() { }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { @@ -28,6 +28,12 @@ $(document).ready( function() { // Instance handling function handleInstanceRename(e) { + + conf_editor.on('change',function() { + window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); + var inst = e.currentTarget.id.split("_")[1]; showInfoDialog('renInst', $.i18n('conf_general_inst_renreq_t'), getInstanceNameByIndex(inst)); @@ -77,6 +83,10 @@ $(document).ready( function() { $('#instren_'+inst[key].instance).off().on('click', handleInstanceRename); $('#inst_'+inst[key].instance).off().on('click', handleInstanceStartStop); $('#instdel_'+inst[key].instance).off().on('click', handleInstanceDelete); + + window.readOnlyMode ? $('#instren_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#inst_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#instdel_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); } } @@ -85,7 +95,7 @@ $(document).ready( function() { buildInstanceList(); $('#inst_name').off().on('input',function(e) { - (e.currentTarget.value.length >= 5) ? $('#btn_create_inst').attr('disabled', false) : $('#btn_create_inst').attr('disabled', true); + (e.currentTarget.value.length >= 5) && !window.readOnlyMode ? $('#btn_create_inst').attr('disabled', false) : $('#btn_create_inst').attr('disabled', true); if(5-e.currentTarget.value.length >= 1 && 5-e.currentTarget.value.length <= 4) $('#inst_chars_needed').html(5-e.currentTarget.value.length + " " + $.i18n('general_chars_needed')) else @@ -105,7 +115,7 @@ $(document).ready( function() { //import function dis_imp_btn(state) { - state ? $('#btn_import_conf').attr('disabled', true) : $('#btn_import_conf').attr('disabled', false); + state || window.readOnlyMode ? $('#btn_import_conf').attr('disabled', true) : $('#btn_import_conf').attr('disabled', false); } function readFile(evt) diff --git a/assets/webconfig/js/content_grabber.js b/assets/webconfig/js/content_grabber.js index ad94831b..8866a3f2 100644 --- a/assets/webconfig/js/content_grabber.js +++ b/assets/webconfig/js/content_grabber.js @@ -213,7 +213,7 @@ $(document).ready( function() { }, true, true); conf_editor_instCapt.on('change',function() { - conf_editor_instCapt.validate().length ? $('#btn_submit_instCapt').attr('disabled', true) : $('#btn_submit_instCapt').attr('disabled', false); + conf_editor_instCapt.validate().length || window.readOnlyMode ? $('#btn_submit_instCapt').attr('disabled', true) : $('#btn_submit_instCapt').attr('disabled', false); }); $('#btn_submit_instCapt').off().on('click',function() { @@ -226,7 +226,7 @@ $(document).ready( function() { }, true, true); conf_editor_fg.on('change',function() { - conf_editor_fg.validate().length ? $('#btn_submit_fg').attr('disabled', true) : $('#btn_submit_fg').attr('disabled', false); + conf_editor_fg.validate().length || window.readOnlyMode ? $('#btn_submit_fg').attr('disabled', true) : $('#btn_submit_fg').attr('disabled', false); }); $('#btn_submit_fg').off().on('click',function() { @@ -239,7 +239,7 @@ $(document).ready( function() { }, true, true); conf_editor_v4l2.on('change',function() { - conf_editor_v4l2.validate().length ? $('#btn_submit_v4l2').attr('disabled', true) : $('#btn_submit_v4l2').attr('disabled', false); + conf_editor_v4l2.validate().length || window.readOnlyMode ? $('#btn_submit_v4l2').attr('disabled', true) : $('#btn_submit_v4l2').attr('disabled', false); }); conf_editor_v4l2.on('ready', function() { diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index 219070f7..c57f1626 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -26,6 +26,9 @@ $(document).ready(function () { $(window.hyperion).on("cmd-serverinfo", function (event) { window.serverInfo = event.response.info; + + window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; + // comps window.comps = event.response.info.components diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index f516623d..7080286c 100644 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -468,6 +468,11 @@ $(document).ready(function() { $('#leds_custom_updsim').attr("disabled", true); $('#leds_custom_save').attr("disabled", true); } + + if ( window.readOnlyMode ) + { + $('#leds_custom_save').attr('disabled', true); + } } }, window.serverConfig.leds); @@ -520,7 +525,13 @@ $(document).ready(function() { }; // change save button state based on validation result - conf_editor.validate().length ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); + + conf_editor.on('change',function() { + window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#leds_custom_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); // led controller sepecific wizards $('#btn_wiz_holder').html(""); diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js index 237a0a89..5faf2b05 100644 --- a/assets/webconfig/js/content_logging.js +++ b/assets/webconfig/js/content_logging.js @@ -22,7 +22,7 @@ $(document).ready(function() { }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { @@ -80,6 +80,7 @@ $(document).ready(function() { info += 'UI Access: '+storedAccess+'\n'; info += 'Log lvl: '+window.serverConfig.logger.level+'\n'; info += 'Avail Capt: '+window.serverInfo.grabbers.available+'\n'; + info += 'Database: '+(shy.readOnlyMode ? "ready-only" : "read/write")+'\n'; info += '\n'; info += 'Distribution:'+sys.prettyName+'\n'; diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 92d51f65..44f60b41 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -62,7 +62,7 @@ $(document).ready( function() { }, true, true); conf_editor_net.on('change',function() { - conf_editor_net.validate().length ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false); + conf_editor_net.validate().length || window.readOnlyMode ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false); }); $('#btn_submit_net').off().on('click',function() { @@ -75,7 +75,7 @@ $(document).ready( function() { }, true, true); conf_editor_json.on('change',function() { - conf_editor_json.validate().length ? $('#btn_submit_jsonserver').attr('disabled', true) : $('#btn_submit_jsonserver').attr('disabled', false); + conf_editor_json.validate().length || window.readOnlyMode ? $('#btn_submit_jsonserver').attr('disabled', true) : $('#btn_submit_jsonserver').attr('disabled', false); }); $('#btn_submit_jsonserver').off().on('click',function() { @@ -88,7 +88,7 @@ $(document).ready( function() { }, true, true); conf_editor_fbs.on('change',function() { - conf_editor_fbs.validate().length ? $('#btn_submit_fbserver').attr('disabled', true) : $('#btn_submit_fbserver').attr('disabled', false); + conf_editor_fbs.validate().length || window.readOnlyMode ? $('#btn_submit_fbserver').attr('disabled', true) : $('#btn_submit_fbserver').attr('disabled', false); }); $('#btn_submit_fbserver').off().on('click',function() { @@ -101,7 +101,7 @@ $(document).ready( function() { }, true, true); conf_editor_proto.on('change',function() { - conf_editor_proto.validate().length ? $('#btn_submit_protoserver').attr('disabled', true) : $('#btn_submit_protoserver').attr('disabled', false); + conf_editor_proto.validate().length || window.readOnlyMode ? $('#btn_submit_protoserver').attr('disabled', true) : $('#btn_submit_protoserver').attr('disabled', false); }); $('#btn_submit_protoserver').off().on('click',function() { @@ -114,7 +114,7 @@ $(document).ready( function() { }, true, true); conf_editor_bobl.on('change',function() { - conf_editor_bobl.validate().length ? $('#btn_submit_boblightserver').attr('disabled', true) : $('#btn_submit_boblightserver').attr('disabled', false); + conf_editor_bobl.validate().length || window.readOnlyMode ? $('#btn_submit_boblightserver').attr('disabled', true) : $('#btn_submit_boblightserver').attr('disabled', false); }); $('#btn_submit_boblightserver').off().on('click',function() { @@ -129,7 +129,7 @@ $(document).ready( function() { }, true, true); conf_editor_forw.on('change',function() { - conf_editor_forw.validate().length ? $('#btn_submit_forwarder').attr('disabled', true) : $('#btn_submit_forwarder').attr('disabled', false); + conf_editor_forw.validate().length || window.readOnlyMode ? $('#btn_submit_forwarder').attr('disabled', true) : $('#btn_submit_forwarder').attr('disabled', false); }); $('#btn_submit_forwarder').off().on('click',function() { diff --git a/assets/webconfig/js/content_webconfig.js b/assets/webconfig/js/content_webconfig.js index 8942a076..ea20ba45 100644 --- a/assets/webconfig/js/content_webconfig.js +++ b/assets/webconfig/js/content_webconfig.js @@ -14,7 +14,7 @@ }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index b06805e7..0df7bc73 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -471,8 +471,5 @@ async function requestLedDeviceProperties(type, params) function requestLedDeviceIdentification(type, params) { sendToHyperion("leddevice", "identify", '"ledDeviceType": "'+type+'","params": '+JSON.stringify(params)+''); - - //let data = {ledDeviceType: type, params: params}; - //sendToHyperion("leddevice", "identify", data ); } diff --git a/assets/webconfig/js/settings.js b/assets/webconfig/js/settings.js index 8a74ba30..961ecc36 100644 --- a/assets/webconfig/js/settings.js +++ b/assets/webconfig/js/settings.js @@ -21,7 +21,7 @@ function changePassword(){ }); $('#newPw, #oldPw').off().on('input',function(e) { - ($('#oldPw').val().length >= 8 && $('#newPw').val().length >= 8) ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true); + ($('#oldPw').val().length >= 8 && $('#newPw').val().length >= 8) && !window.readOnlyMode ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true); }); } diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 0b48b032..1c62e885 100644 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -109,9 +109,13 @@ function beginWizardRGB() { if (redS == "r" && greenS == "g") { $('#btn_wiz_save').toggle(false); $('#btn_wiz_checkok').toggle(true); + + window.readOnlyMode ? $('#btn_wiz_checkok').attr('disabled', true) : $('#btn_wiz_checkok').attr('disabled', false); } else { $('#btn_wiz_save').toggle(true); + window.readOnlyMode ? $('#btn_wiz_save').attr('disabled', true) : $('#btn_wiz_save').attr('disabled', false); + $('#btn_wiz_checkok').toggle(false); } new_rgb_order = rgb_order; @@ -390,6 +394,7 @@ function performAction() { $('#btn_wiz_next').attr("disabled", true); $('#btn_wiz_save').toggle(true); + window.readOnlyMode ? $('#btn_wiz_save').attr('disabled', true) : $('#btn_wiz_save').attr('disabled', false); } else { $('#btn_wiz_next').attr("disabled", false); @@ -407,13 +412,13 @@ function updateWEditor(el, all) { } function startWizardCC() { + // Ensure that Kodi's default REST-API port is not used, as now the Web-Socket port is used [kodiHost, kodiPort] = kodiAddress.split(":", 2); if (kodiPort === "8080") { kodiAddress = kodiHost; kodiPort = undefined; } - //create html $('#wiz_header').html('' + $.i18n('wiz_cc_title')); $('#wizp1_body').html('

' + $.i18n('wiz_cc_title') + '

' + $.i18n('wiz_cc_intro1') + '

'); @@ -429,6 +434,7 @@ function startWizardCC() { }); $('#wiz_cc_kodiip').off().on('change', function () { + kodiAddress = $(this).val().trim(); $('#kodi_status').html(''); @@ -1151,7 +1157,9 @@ function get_hue_lights() { cC++; } } - (cC == 0) ? $('#btn_wiz_save').attr("disabled", true) : $('#btn_wiz_save').attr("disabled", false); + + (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').attr("disabled", true) : $('#btn_wiz_save').attr("disabled", false); + }); } $('.hue_sel_watch').trigger('change'); @@ -1497,7 +1505,8 @@ function assign_yeelight_lights() { cC++; } } - if (cC === 0) + + if (cC === 0 || window.readOnlyMode) $('#btn_wiz_save').attr("disabled", true); else $('#btn_wiz_save').attr("disabled", false); @@ -1527,7 +1536,6 @@ async function getProperties_yeelight(hostname, port) { function identify_yeelight_device(hostname, port) { let params = { hostname: hostname, port: port }; - const res = requestLedDeviceIdentification("yeelight", params); // TODO: error case unhandled // res can be: false (timeout) or res.error (not found) @@ -1759,7 +1767,7 @@ function assign_atmoorb_lights() { cC++; } } - if (cC === 0) + if (cC === 0 || window.readOnlyMode) $('#btn_wiz_save').attr("disabled", true); else $('#btn_wiz_save').attr("disabled", false); @@ -1860,7 +1868,6 @@ async function discover_providerRs232(rs232Type) { //**************************** async function discover_providerHid(hidType) { const res = await requestLedDeviceDiscovery(hidType); - console.log("discover_providerHid", res); // TODO: error case unhandled // res can be: false (timeout) or res.error (not found) diff --git a/include/api/API.h b/include/api/API.h index aa134ccc..04a65676 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -231,7 +231,7 @@ protected: /// @brief Save settings object. Requires ADMIN ACCESS /// @param data The data object /// - void saveSettings(const QJsonObject &data); + bool saveSettings(const QJsonObject &data); /// /// @brief Test if we are authorized to use the interface diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h index ae5918b6..c6495ea5 100644 --- a/include/db/AuthTable.h +++ b/include/db/AuthTable.h @@ -16,9 +16,10 @@ class AuthTable : public DBManager public: /// construct wrapper with auth table - AuthTable(const QString& rootPath = "", QObject* parent = nullptr) + AuthTable(const QString& rootPath = "", QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + setReadonlyMode(readonlyMode); if(!rootPath.isEmpty()){ // Init Hyperion database usage setRootPath(rootPath); diff --git a/include/db/DBManager.h b/include/db/DBManager.h index 8ea6761f..4d36471d 100644 --- a/include/db/DBManager.h +++ b/include/db/DBManager.h @@ -119,6 +119,13 @@ public: /// bool deleteTable(const QString& table) const; + /// + /// @brief Sets a table in read-only mode. + /// Updates will not written to the table + /// @param[in] readOnly True read-only, false - read/write + /// + void setReadonlyMode(bool readOnly) { _readonlyMode = readOnly; }; + private: Logger* _log; @@ -127,6 +134,8 @@ private: /// table in database QString _table; + bool _readonlyMode; + /// addBindValue to query given by QVariantList void doAddBindValue(QSqlQuery& query, const QVariantList& variants) const; }; diff --git a/include/db/InstanceTable.h b/include/db/InstanceTable.h index aff7d73f..912ecbaf 100644 --- a/include/db/InstanceTable.h +++ b/include/db/InstanceTable.h @@ -14,9 +14,11 @@ class InstanceTable : public DBManager { public: - InstanceTable(const QString& rootPath, QObject* parent = nullptr) + InstanceTable(const QString& rootPath, QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + + setReadonlyMode(readonlyMode); // Init Hyperion database usage setRootPath(rootPath); setDatabaseName("hyperion"); diff --git a/include/db/MetaTable.h b/include/db/MetaTable.h index acc27d03..e1861568 100644 --- a/include/db/MetaTable.h +++ b/include/db/MetaTable.h @@ -17,9 +17,11 @@ class MetaTable : public DBManager public: /// construct wrapper with plugins table and columns - MetaTable(QObject* parent = nullptr) + MetaTable(QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + setReadonlyMode(readonlyMode); + setTable("meta"); createTable(QStringList()<<"uuid TEXT"<<"created_at TEXT"); }; diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index 1f22fb3a..c53c1562 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -21,7 +21,7 @@ class AuthManager : public QObject private: friend class HyperionDaemon; /// constructor is private, can be called from HyperionDaemon - AuthManager(QObject *parent = 0); + AuthManager(QObject *parent = 0, bool readonlyMode = false); public: struct AuthDefinition diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 90a76591..a98c6e4f 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -98,6 +98,8 @@ public: /// QString getActiveDeviceType() const; + bool getReadOnlyMode() {return _readOnlyMode; }; + public slots: /// @@ -484,7 +486,7 @@ private: /// @brief Constructs the Hyperion instance, just accessible for HyperionIManager /// @param instance The instance index /// - Hyperion(quint8 instance); + Hyperion(quint8 instance, bool readonlyMode = false); /// instance index const quint8 _instIndex; @@ -541,4 +543,6 @@ private: /// Boblight instance BoblightServer* _boblightServer; + + bool _readOnlyMode; }; diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h index c7b1fca9..85faecab 100644 --- a/include/hyperion/HyperionIManager.h +++ b/include/hyperion/HyperionIManager.h @@ -169,7 +169,7 @@ private: /// @brief Construct the Manager /// @param The root path of all userdata /// - HyperionIManager(const QString& rootPath, QObject* parent = nullptr); + HyperionIManager(const QString& rootPath, QObject* parent = nullptr, bool readonlyMode = false); /// /// @brief Start all instances that are marked as enabled in db. Non blocking @@ -193,6 +193,9 @@ private: const QString _rootPath; QMap _runningInstances; QList _startQueue; + + bool _readonlyMode; + /// All pending requests QMap _pendingRequests; }; diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h index d20e3afb..090e6ee2 100644 --- a/include/hyperion/SettingsManager.h +++ b/include/hyperion/SettingsManager.h @@ -21,7 +21,7 @@ public: /// @params instance Instance index of HyperionInstanceManager /// @params parent The parent hyperion instance /// - SettingsManager(quint8 instance, QObject* parent = nullptr); + SettingsManager(quint8 instance, QObject* parent = nullptr, bool readonlyMode = false); /// /// @brief Save a complete json config @@ -75,4 +75,6 @@ private: /// the current config of this instance QJsonObject _qconfig; + + bool _readonlyMode; }; diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp index 7a22d9cc..b3413318 100644 --- a/libsrc/api/API.cpp +++ b/libsrc/api/API.cpp @@ -378,11 +378,18 @@ QString API::saveEffect(const QJsonObject &data) return NO_AUTH; } -void API::saveSettings(const QJsonObject &data) +bool API::saveSettings(const QJsonObject &data) { + bool rc = true; if (!_adminAuthorized) - return; - QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::QueuedConnection, Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + { + rc = false; + } + else + { + QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + } + return rc; } bool API::updateHyperionPassword(const QString &password, const QString &newPassword) diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 48526e1b..0bb8d228 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -293,6 +293,7 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE); hyperion["time"] = QString(__DATE__ " " __TIME__); hyperion["id"] = _authManager->getID(); + hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode(); info["hyperion"] = hyperion; @@ -875,8 +876,14 @@ void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString & QJsonObject config = message["config"].toObject(); if (API::isHyperionEnabled()) { - API::saveSettings(config); - sendSuccessReply(command, tan); + if ( API::saveSettings(config) ) + { + sendSuccessReply(command, tan); + } + else + { + sendErrorReply("Save settings failed", command, tan); + } } else sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan); diff --git a/libsrc/db/DBManager.cpp b/libsrc/db/DBManager.cpp index 8e0ea575..6336ef0e 100644 --- a/libsrc/db/DBManager.cpp +++ b/libsrc/db/DBManager.cpp @@ -19,6 +19,7 @@ static QThreadStorage _databasePool; DBManager::DBManager(QObject* parent) : QObject(parent) , _log(Logger::getInstance("DB")) + , _readonlyMode (false) { } @@ -58,6 +59,11 @@ QSqlDatabase DBManager::getDB() const bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const { + if ( _readonlyMode ) + { + return false; + } + if(recordExists(conditions)) { // if there is no column data, return @@ -144,6 +150,11 @@ bool DBManager::recordExists(const VectorPair& conditions) const bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const { + if ( _readonlyMode ) + { + return false; + } + QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); @@ -276,6 +287,11 @@ bool DBManager::getRecords(QVector& results, const QStringList& tCo bool DBManager::deleteRecord(const VectorPair& conditions) const { + if ( _readonlyMode ) + { + return false; + } + if(conditions.isEmpty()) { Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table)); @@ -311,6 +327,11 @@ bool DBManager::deleteRecord(const VectorPair& conditions) const bool DBManager::createTable(QStringList& columns) const { + if ( _readonlyMode ) + { + return false; + } + if(columns.isEmpty()) { Error(_log,"Empty tables aren't supported!"); @@ -353,6 +374,11 @@ bool DBManager::createTable(QStringList& columns) const bool DBManager::createColumn(const QString& column) const { + if ( _readonlyMode ) + { + return false; + } + QSqlDatabase idb = getDB(); QSqlQuery query(idb); if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column))) @@ -374,6 +400,11 @@ bool DBManager::tableExists(const QString& table) const bool DBManager::deleteTable(const QString& table) const { + if ( _readonlyMode ) + { + return false; + } + if(tableExists(table)) { QSqlDatabase idb = getDB(); diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index 17f76706..329b387d 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -10,10 +10,10 @@ AuthManager *AuthManager::manager = nullptr; -AuthManager::AuthManager(QObject *parent) +AuthManager::AuthManager(QObject *parent, bool readonlyMode) : QObject(parent) - , _authTable(new AuthTable("", this)) - , _metaTable(new MetaTable(this)) + , _authTable(new AuthTable("", this, readonlyMode)) + , _metaTable(new MetaTable(this, readonlyMode)) , _pendingRequests() , _authRequired(true) , _timer(new QTimer(this)) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 4734cac0..76b8bebc 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -39,10 +39,10 @@ // Boblight #include -Hyperion::Hyperion(quint8 instance) +Hyperion::Hyperion(quint8 instance, bool readonlyMode) : QObject() , _instIndex(instance) - , _settingsManager(new SettingsManager(instance, this)) + , _settingsManager(new SettingsManager(instance, this, readonlyMode)) , _componentRegister(this) , _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) , _imageProcessor(new ImageProcessor(_ledString, this)) @@ -54,6 +54,7 @@ Hyperion::Hyperion(quint8 instance) , _hwLedCount() , _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array())) , _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK) + , _readOnlyMode(readonlyMode) { } diff --git a/libsrc/hyperion/HyperionIManager.cpp b/libsrc/hyperion/HyperionIManager.cpp index 9613af25..307bd073 100644 --- a/libsrc/hyperion/HyperionIManager.cpp +++ b/libsrc/hyperion/HyperionIManager.cpp @@ -9,11 +9,12 @@ HyperionIManager* HyperionIManager::HIMinstance; -HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent) +HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent, bool readonlyMode) : QObject(parent) , _log(Logger::getInstance("HYPERION")) - , _instanceTable( new InstanceTable(rootPath, this) ) + , _instanceTable( new InstanceTable(rootPath, this, readonlyMode) ) , _rootPath( rootPath ) + , _readonlyMode(readonlyMode) { HIMinstance = this; qRegisterMetaType("InstanceState"); @@ -75,7 +76,7 @@ bool HyperionIManager::startInstance(quint8 inst, bool block, QObject* caller, i { QThread* hyperionThread = new QThread(); hyperionThread->setObjectName("HyperionThread"); - Hyperion* hyperion = new Hyperion(inst); + Hyperion* hyperion = new Hyperion(inst, _readonlyMode); hyperion->moveToThread(hyperionThread); // setup thread management connect(hyperionThread, &QThread::started, hyperion, &Hyperion::start); diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index 2f4bcc8e..94a6cbba 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -14,11 +14,13 @@ QJsonObject SettingsManager::schemaJson; -SettingsManager::SettingsManager(quint8 instance, QObject* parent) +SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode) : QObject(parent) , _log(Logger::getInstance("SETTINGSMGR")) , _sTable(new SettingsTable(instance, this)) + , _readonlyMode(readonlyMode) { + _sTable->setReadonlyMode(_readonlyMode); // get schema if(schemaJson.isEmpty()) { @@ -152,18 +154,24 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct) } } + int rc = true; // compare database data with new data to emit/save changes accordingly for(const auto & key : keyList) { QString data = newValueList.takeFirst(); if(_sTable->getSettingsRecordString(key) != data) { - _sTable->createSettingsRecord(key, data); - - emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit())); + if ( ! _sTable->createSettingsRecord(key, data) ) + { + rc = false; + } + else + { + emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit())); + } } } - return true; + return rc; } bool SettingsManager::handleConfigUpgrade(QJsonObject& config) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index c036a07d..b459b115 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -61,10 +61,10 @@ HyperionDaemon *HyperionDaemon::daemon = nullptr; -HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, bool logLvlOverwrite) +HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, bool logLvlOverwrite, bool readonlyMode) : QObject(parent), _log(Logger::getInstance("DAEMON")) - , _instanceManager(new HyperionIManager(rootPath, this)) - , _authManager(new AuthManager(this)) + , _instanceManager(new HyperionIManager(rootPath, this, readonlyMode)) + , _authManager(new AuthManager(this, readonlyMode)) #ifdef ENABLE_AVAHI , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) #endif @@ -97,7 +97,7 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, bool log qRegisterMetaType>("std::vector"); // init settings - _settingsManager = new SettingsManager(0, this); + _settingsManager = new SettingsManager(0, this, readonlyMode); // set inital log lvl if the loglvl wasn't overwritten by arg if (!logLvlOverwrite) diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 1b76b28b..f643e1f4 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -86,7 +86,7 @@ class HyperionDaemon : public QObject friend SysTray; public: - HyperionDaemon(QString rootPath, QObject *parent, bool logLvlOverwrite); + HyperionDaemon(QString rootPath, QObject *parent, bool logLvlOverwrite, bool readonlyMode = false); ~HyperionDaemon(); /// diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index 677d113a..3ddb57e2 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -256,8 +256,14 @@ int main(int argc, char** argv) return 0; } - - + if (parser.isSet(versionOption)) + { + std::cout + << "Hyperion Ambilight Daemon" << std::endl + << "\tVersion : " << HYPERION_VERSION << " (" << HYPERION_BUILD_ID << ")" << std::endl + << "\tBuild Time: " << __DATE__ << " " << __TIME__ << std::endl; + } + if (parser.isSet(exportEfxOption)) { Q_INIT_RESOURCE(EffectEngine); @@ -265,7 +271,7 @@ int main(int argc, char** argv) QDir destDir(exportEfxOption.value(parser)); if (directory.exists() && destDir.exists()) { - std::cout << "extract to folder: " << std::endl; + std::cout << "Extract to folder: " << std::endl; QStringList filenames = directory.entryList(QStringList() << "*", QDir::Files, QDir::Name | QDir::IgnoreCase); QString destFileName; for (const QString & filename : filenames) @@ -278,70 +284,129 @@ int main(int argc, char** argv) if (QFile::copy(QString(":/effects/")+filename, destFileName)) { QFile::setPermissions(destFileName, PERM0664 ); - std::cout << "ok" << std::endl; + std::cout << "OK" << std::endl; } else { - std::cout << "error, aborting" << std::endl; + std::cout << "Error, aborting" << std::endl; return 1; } } return 0; } - Error(log, "can not export to %s",exportEfxOption.getCString(parser)); + Error(log, "Can not export to %s",exportEfxOption.getCString(parser)); return 1; } int rc = 1; + bool readonlyMode = false; try { // handle and create userDataPath for user data, default path is home directory + /.hyperion - // NOTE: No further checks inside Hyperion. FileUtils::writeFile() will resolve permission errors and others that occur during runtime QString userDataPath(userDataOption.value(parser)); QDir mDir(userDataPath); QFileInfo mFi(userDataPath); - if(!mDir.mkpath(userDataPath) || !mFi.isWritable() || !mDir.isReadable()) - throw std::runtime_error("The user data path '"+mDir.absolutePath().toStdString()+"' can't be created or isn't read/writeable. Please setup permissions correctly!"); + if(!mDir.mkpath(userDataPath) || !mFi.isWritable()) + { + if ( !mDir.isReadable() ) + { + throw std::runtime_error("The user data path '"+mDir.absolutePath().toStdString()+"' can't be created or isn't read/writeable. Please setup permissions correctly!"); + } + else + { + QFileInfo mFiDB(userDataPath + "/db/hyperion.db"); + + if ( !mFiDB.exists() ) + { + throw std::runtime_error("Configuration database '"+mFiDB.absoluteFilePath().toStdString()+"' does not exist!"); + } + else + { + if ( !mFiDB.isReadable() ) + { + throw std::runtime_error("Configuration database '"+mFiDB.absoluteFilePath().toStdString()+"' is not readable. Please setup permissions correctly!"); + } + else + { + if ( !mFiDB.isWritable() ) + { + readonlyMode = true; + } + } + } + } + } // reset Password without spawning daemon if(parser.isSet(resetPassword)) { - AuthTable* table = new AuthTable(userDataPath); - if(table->resetHyperionUser()){ - Info(log,"Password reset successfull"); - delete table; - exit(0); - } else { - Error(log,"Failed to reset password!"); - delete table; - exit(1); + if ( readonlyMode ) + { + Error(log,"Password reset is not possible. The user data path '%s' is not writeable.", QSTRING_CSTR(mDir.absolutePath())); + throw std::runtime_error("Password reset failed"); + } + else + { + AuthTable* table = new AuthTable(userDataPath); + if(table->resetHyperionUser()){ + Info(log,"Password reset successful"); + delete table; + exit(0); + } else { + Error(log,"Failed to reset password!"); + delete table; + exit(1); + } } } // delete database before start if(parser.isSet(deleteDB)) { - const QString dbFile = mDir.absolutePath() + "/db/hyperion.db"; - if (QFile::exists(dbFile)) + if ( readonlyMode ) { - if (!QFile::remove(dbFile)) + Error(log,"Deleting the configuration database is not possible. The user data path '%s' is not writeable.", QSTRING_CSTR(mDir.absolutePath())); + throw std::runtime_error("Deleting the configuration database failed"); + } + else + { + const QString dbFile = mDir.absolutePath() + "/db/hyperion.db"; + if (QFile::exists(dbFile)) { - Info(log,"Failed to delete Database!"); - exit(1); + if (!QFile::remove(dbFile)) + { + Info(log,"Failed to delete Database!"); + exit(1); + } + else + { + Info(log,"Configuration database deleted successfully."); + } + } + else + { + Warning(log,"Configuration database [%s] does not exist!", QSTRING_CSTR(dbFile)); } } } Info(log,"Starting Hyperion - %s, %s, built: %s:%s", HYPERION_VERSION, HYPERION_BUILD_ID, __DATE__, __TIME__); - Info(log, "Set user data path to '%s'", QSTRING_CSTR(mDir.absolutePath())); + if ( !readonlyMode ) + { + Info(log, "Set user data path to '%s'", QSTRING_CSTR(mDir.absolutePath())); + } + else + { + Warning(log,"The user data path '%s' is not writeable. Hyperion starts in read-only mode. Configuration updates will not be persisted!", QSTRING_CSTR(mDir.absolutePath())); + } - HyperionDaemon* hyperiond = nullptr; + HyperionDaemon* hyperiond = nullptr; try { - hyperiond = new HyperionDaemon(userDataPath, qApp, bool(logLevelCheck)); + hyperiond = new HyperionDaemon(userDataPath, qApp, bool(logLevelCheck), readonlyMode); } catch (std::exception& e) {