132 lines
5.1 KiB
JavaScript
132 lines
5.1 KiB
JavaScript
/**
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
|
|
const fs = require('fs-extra');
|
|
const fspath = require("path");
|
|
|
|
const log = require("@node-red/util").log;
|
|
const util = require("./util");
|
|
|
|
const configSections = ['nodes','users','projects','modules'];
|
|
|
|
const settingsCache = {};
|
|
|
|
var globalSettingsFile;
|
|
var globalSettingsBackup;
|
|
var settings;
|
|
|
|
async function migrateToMultipleConfigFiles() {
|
|
const nodesFilename = getSettingsFilename("nodes");
|
|
if (fs.existsSync(nodesFilename)) {
|
|
// We have both .config.json and .config.nodes.json
|
|
// Use the more recently modified. This handles users going back to pre1.2
|
|
// and up again.
|
|
// We can remove this logic in 1.3+ and remove the old .config.json file entirely
|
|
//
|
|
const fsStatNodes = await fs.stat(nodesFilename);
|
|
const fsStatGlobal = await fs.stat(globalSettingsFile);
|
|
if (fsStatNodes.mtimeMs > fsStatGlobal.mtimeMs) {
|
|
// .config.nodes.json is newer than .config.json - no migration needed
|
|
return;
|
|
}
|
|
}
|
|
const data = await util.readFile(globalSettingsFile,globalSettingsBackup,{});
|
|
// In a later release we should remove the old settings file. But don't do
|
|
// that *yet* otherwise users won't be able to downgrade easily.
|
|
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.modules.json - external modules installed by the runtime
|
|
* - .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",{});
|
|
settingsCache["runtime"] = JSON.stringify(result, null ,4);
|
|
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 => {
|
|
settingsCache[key] = JSON.stringify(sectionData, null ,4);
|
|
if (Object.keys(sectionData).length > 0) {
|
|
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) {
|
|
return migrateToMultipleConfigFiles();
|
|
} else {
|
|
return Promise.resolve();
|
|
}
|
|
},
|
|
getSettings: function() {
|
|
return readSettings()
|
|
},
|
|
saveSettings: function(newSettings) {
|
|
if (settings.readOnly) {
|
|
return Promise.resolve();
|
|
}
|
|
return writeSettings(newSettings);
|
|
}
|
|
}
|