diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js index d0b6dd512..a4567293d 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js @@ -14,41 +14,99 @@ * limitations under the License. **/ -var when = require('when'); -var fs = require('fs-extra'); -var fspath = require("path"); +const fs = require('fs-extra'); +const fspath = require("path"); -var log = require("@node-red/util").log; // TODO: separate module -var util = require("./util"); +const log = require("@node-red/util").log; +const util = require("./util"); + +const configSections = ['nodes','users','projects']; +let initialisePromise; + +const settingsCache = {}; var globalSettingsFile; var globalSettingsBackup; var settings; +async function migrateToMultipleConfigFiles() { + const data = await util.readFile(globalSettingsFile,globalSettingsBackup,{}); + return writeSettings(data).then( () => fs.remove(globalSettingsFile) ); +} + + +/** + * Takes the single settings object and splits it into separate files. This makes + * it easier to backup selected parts of the settings and also helps reduce the blast + * radius if a file is lost. + * + * The settings are written to four files: + * - .config.nodes.json - the node registry + * - .config.users.json - user specific settings (eg editor settings) + * - .config.projects.json - project settings, including the active project + * - .config.runtime.json - everything else - most notable _credentialSecret + */ +function writeSettings(data) { + const configKeys = Object.keys(data); + const writePromises = []; + configSections.forEach(key => { + const sectionData = data[key] || {}; + delete data[key]; + const sectionFilename = getSettingsFilename(key); + const sectionContent = JSON.stringify(sectionData,null,4); + if (sectionContent !== settingsCache[key]) { + settingsCache[key] = sectionContent; + writePromises.push(util.writeFile(sectionFilename,sectionContent,sectionFilename+".backup")) + } + }) + // Having extracted nodes/users/projects, write whatever is left to the runtime config + const sectionFilename = getSettingsFilename("runtime"); + const sectionContent = JSON.stringify(data,null,4); + if (sectionContent !== settingsCache["runtime"]) { + settingsCache["runtime"] = sectionContent; + writePromises.push(util.writeFile(sectionFilename,sectionContent,sectionFilename+".backup")); + } + return Promise.all(writePromises); +} + +async function readSettings() { + // Read the 'runtime' settings file first + const runtimeFilename = getSettingsFilename("runtime"); + const result = await util.readFile(runtimeFilename,runtimeFilename+".backup",{}); + const readPromises = []; + // Read the other settings files and add them into the runtime settings + configSections.forEach(key => { + const sectionFilename = getSettingsFilename(key); + readPromises.push(util.readFile(sectionFilename,sectionFilename+".backup",{}).then(sectionData => { + result[key] = sectionData; + })) + }); + return Promise.all(readPromises).then(() => result); +} + +function getSettingsFilename(section) { + return fspath.join(settings.userDir,`.config.${section}.json`); +} + module.exports = { init: function(_settings) { settings = _settings; globalSettingsFile = fspath.join(settings.userDir,".config.json"); globalSettingsBackup = fspath.join(settings.userDir,".config.json.backup"); + + if (fs.existsSync(globalSettingsFile) && !settings.readOnly) { + initialisePromise = migrateToMultipleConfigFiles(); + } else { + initialisePromise = Promise.resolve(); + } }, getSettings: function() { - return when.promise(function(resolve,reject) { - fs.readFile(globalSettingsFile,'utf8',function(err,data) { - if (!err) { - try { - return resolve(util.parseJSON(data)); - } catch(err2) { - log.trace("Corrupted config detected - resetting"); - } - } - return resolve({}); - }) - }) + return initialisePromise.then(readSettings) }, saveSettings: function(newSettings) { if (settings.readOnly) { - return when.resolve(); + return Promise.resolve(); } - return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1),globalSettingsBackup); + return initialisePromise.then(() => writeSettings(newSettings)); } } diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js index 65a3c40e4..b91a99129 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js @@ -14,11 +14,10 @@ * limitations under the License. **/ -var fs = require('fs-extra'); -var fspath = require('path'); -var when = require('when'); +const fs = require('fs-extra'); +const fspath = require('path'); -var log = require("@node-red/util").log; // TODO: separate module +const log = require("@node-red/util").log; function parseJSON(data) { if (data.charCodeAt(0) === 0xFEFF) { @@ -27,7 +26,7 @@ function parseJSON(data) { return JSON.parse(data); } function readFile(path,backupPath,emptyResponse,type) { - return when.promise(function(resolve) { + return new Promise(function(resolve) { fs.readFile(path,'utf8',function(err,data) { if (!err) { if (data.length === 0) {