diff --git a/backend/config.js b/backend/config.js index a5ee5df..c981867 100644 --- a/backend/config.js +++ b/backend/config.js @@ -1,22 +1,26 @@ const logger = require('./logger'); const fs = require('fs'); +const { BehaviorSubject } = require('rxjs'); + +exports.CONFIG_ITEMS = require('./consts.js')['CONFIG_ITEMS']; +exports.descriptors = {}; // to get rid of file locks when needed, TODO: move to youtube-dl.js -let CONFIG_ITEMS = require('./consts.js')['CONFIG_ITEMS']; const debugMode = process.env.YTDL_MODE === 'debug'; let configPath = debugMode ? '../src/assets/default.json' : 'appdata/default.json'; +exports.config_updated = new BehaviorSubject(); -function initialize() { +exports.initialize = () => { ensureConfigFileExists(); ensureConfigItemsExist(); } function ensureConfigItemsExist() { - const config_keys = Object.keys(CONFIG_ITEMS); + const config_keys = Object.keys(exports.CONFIG_ITEMS); for (let i = 0; i < config_keys.length; i++) { const config_key = config_keys[i]; - getConfigItem(config_key); + exports.getConfigItem(config_key); } } @@ -57,17 +61,17 @@ function getElementNameInConfig(path) { /** * Check if config exists. If not, write default config to config path */ -function configExistsCheck() { +exports.configExistsCheck = () => { let exists = fs.existsSync(configPath); if (!exists) { - setConfigFile(DEFAULT_CONFIG); + exports.setConfigFile(DEFAULT_CONFIG); } } /* * Gets config file and returns as a json */ -function getConfigFile() { +exports.getConfigFile = () => { try { let raw_data = fs.readFileSync(configPath); let parsed_data = JSON.parse(raw_data); @@ -78,8 +82,13 @@ function getConfigFile() { } } -function setConfigFile(config) { +exports.setConfigFile = (config) => { try { + const old_config = exports.getConfigFile(); + const changes = exports.findChangedConfigItems(old_config, config); + if (changes.length > 0) { + for (const change of changes) exports.config_updated.next(change); + } fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); return true; } catch(e) { @@ -87,26 +96,26 @@ function setConfigFile(config) { } } -function getConfigItem(key) { - let config_json = getConfigFile(); - if (!CONFIG_ITEMS[key]) { +exports.getConfigItem = (key) => { + let config_json = exports.getConfigFile(); + if (!exports.CONFIG_ITEMS[key]) { logger.error(`Config item with key '${key}' is not recognized.`); return null; } - let path = CONFIG_ITEMS[key]['path']; + let path = exports.CONFIG_ITEMS[key]['path']; const val = Object.byString(config_json, path); if (val === undefined && Object.byString(DEFAULT_CONFIG, path) !== undefined) { logger.warn(`Cannot find config with key '${key}'. Creating one with the default value...`); - setConfigItem(key, Object.byString(DEFAULT_CONFIG, path)); + exports.setConfigItem(key, Object.byString(DEFAULT_CONFIG, path)); return Object.byString(DEFAULT_CONFIG, path); } return Object.byString(config_json, path); } -function setConfigItem(key, value) { +exports.setConfigItem = (key, value) => { let success = false; - let config_json = getConfigFile(); - let path = CONFIG_ITEMS[key]['path']; + let config_json = exports.getConfigFile(); + let path = exports.CONFIG_ITEMS[key]['path']; let element_name = getElementNameInConfig(path); let parent_path = getParentPath(path); let parent_object = Object.byString(config_json, parent_path); @@ -118,20 +127,18 @@ function setConfigItem(key, value) { parent_parent_object[parent_parent_single_key] = {}; parent_object = Object.byString(config_json, parent_path); } + if (value === 'false') value = false; + if (value === 'true') value = true; + parent_object[element_name] = value; - if (value === 'false' || value === 'true') { - parent_object[element_name] = (value === 'true'); - } else { - parent_object[element_name] = value; - } - success = setConfigFile(config_json); + success = exports.setConfigFile(config_json); return success; } -function setConfigItems(items) { +exports.setConfigItems = (items) => { let success = false; - let config_json = getConfigFile(); + let config_json = exports.getConfigFile(); for (let i = 0; i < items.length; i++) { let key = items[i].key; let value = items[i].value; @@ -141,7 +148,7 @@ function setConfigItems(items) { value = (value === 'true'); } - let item_path = CONFIG_ITEMS[key]['path']; + let item_path = exports.CONFIG_ITEMS[key]['path']; let item_parent_path = getParentPath(item_path); let item_element_name = getElementNameInConfig(item_path); @@ -149,28 +156,34 @@ function setConfigItems(items) { item_parent_object[item_element_name] = value; } - success = setConfigFile(config_json); + success = exports.setConfigFile(config_json); return success; } -function globalArgsRequiresSafeDownload() { - const globalArgs = getConfigItem('ytdl_custom_args').split(',,'); +exports.globalArgsRequiresSafeDownload = () => { + const globalArgs = exports.getConfigItem('ytdl_custom_args').split(',,'); const argsThatRequireSafeDownload = ['--write-sub', '--write-srt', '--proxy']; const failedArgs = globalArgs.filter(arg => argsThatRequireSafeDownload.includes(arg)); return failedArgs && failedArgs.length > 0; } -module.exports = { - getConfigItem: getConfigItem, - setConfigItem: setConfigItem, - setConfigItems: setConfigItems, - getConfigFile: getConfigFile, - setConfigFile: setConfigFile, - configExistsCheck: configExistsCheck, - CONFIG_ITEMS: CONFIG_ITEMS, - initialize: initialize, - descriptors: {}, - globalArgsRequiresSafeDownload: globalArgsRequiresSafeDownload +exports.findChangedConfigItems = (old_config, new_config, key = '', changedConfigItems = [], depth = 0) => { + if (typeof old_config === 'object' && typeof new_config === 'object' && depth < 3) { + for (const key in old_config) { + if (Object.prototype.hasOwnProperty.call(new_config, key)) { + exports.findChangedConfigItems(old_config[key], new_config[key], key, changedConfigItems, depth + 1); + } + } + } else { + if (JSON.stringify(old_config) !== JSON.stringify(new_config)) { + changedConfigItems.push({ + key: key, + old_value: JSON.parse(JSON.stringify(old_config)), + new_value: JSON.parse(JSON.stringify(new_config)) + }); + } + } + return changedConfigItems; } const DEFAULT_CONFIG = { diff --git a/backend/test/tests.js b/backend/test/tests.js index b926b95..b069fe2 100644 --- a/backend/test/tests.js +++ b/backend/test/tests.js @@ -1037,6 +1037,66 @@ describe('Categories', async function() { }); }); +describe('Config', async function() { + it('findChangedConfigItems', async function() { + const old_config = { + "YoutubeDLMaterial": { + "test_object1": { + "test_prop1": true, + "test_prop2": false + }, + "test_object2": { + "test_prop3": { + "test_prop3_1": true, + "test_prop3_2": false + }, + "test_prop4": false + }, + "test_object3": { + "test_prop5": { + "test_prop5_1": true, + "test_prop5_2": false + }, + "test_prop6": false + } + } + }; + + const new_config = { + "YoutubeDLMaterial": { + "test_object1": { + "test_prop1": false, + "test_prop2": false + }, + "test_object2": { + "test_prop3": { + "test_prop3_1": false, + "test_prop3_2": false + }, + "test_prop4": true + }, + "test_object3": { + "test_prop5": { + "test_prop5_1": true, + "test_prop5_2": false + }, + "test_prop6": true + } + } + }; + + const changes = config_api.findChangedConfigItems(old_config, new_config); + assert(changes[0]['key'] === 'test_prop1' && changes[0]['old_value'] === true && changes[0]['new_value'] === false); + assert(changes[1]['key'] === 'test_prop3' && + changes[1]['old_value']['test_prop3_1'] === true && + changes[1]['new_value']['test_prop3_1'] === false && + changes[1]['old_value']['test_prop3_2'] === false && + changes[1]['new_value']['test_prop3_2'] === false); + assert(changes[2]['key'] === 'test_prop4' && changes[2]['old_value'] === false && changes[2]['new_value'] === true); + assert(changes[3]['key'] === 'test_prop6' && changes[3]['old_value'] === false && changes[3]['new_value'] === true); + }); +}); + const generateEmptyVideoFile = async (file_path) => { if (fs.existsSync(file_path)) fs.unlinkSync(file_path); return await exec(`ffmpeg -t 1 -f lavfi -i color=c=black:s=640x480 -c:v libx264 -tune stillimage -pix_fmt yuv420p "${file_path}"`);