mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-28 09:51:06 -06:00
ec98374bcf
- change logic in handling user config selection on install - fix log issues - Update user config by validating execPath or is mac - Remove post install from aip config - Update user config by validating execPath or is mac - Fix lint issues - use native isNaN method - Remove unwanted code - PR review
416 lines
14 KiB
JavaScript
416 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
const electron = require('electron');
|
|
const app = electron.app;
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const omit = require('lodash.omit');
|
|
const pick = require('lodash.pick');
|
|
const difference = require('lodash.difference');
|
|
|
|
const isDevEnv = require('./utils/misc.js').isDevEnv;
|
|
const isMac = require('./utils/misc.js').isMac;
|
|
const getRegistry = require('./utils/getRegistry.js');
|
|
const log = require('./log.js');
|
|
const logLevels = require('./enums/logLevels.js');
|
|
|
|
const configFileName = 'Symphony.config';
|
|
|
|
// cached config when first reading files. initially undefined and will be
|
|
// updated when read from disk.
|
|
let userConfig;
|
|
let globalConfig;
|
|
|
|
let ignoreSettings = ['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'url', 'memoryRefresh', 'bringToFront'];
|
|
|
|
/**
|
|
* Tries to read given field from user config file, if field doesn't exist
|
|
* then tries reading from global config. User config is stord in directory:
|
|
* app.getPath('userData') and file called Symphony.config. Global config is
|
|
* stored in file Symphony.config in directory where executable gets installed.
|
|
*
|
|
* Config is a flat key/value file.
|
|
* e.g. { url: 'https://my.symphony.com', }
|
|
*
|
|
* @param {String} fieldName Name of field to try fetching
|
|
* @return {Promise} Returns promise that will succeed with field
|
|
* value if found in either user or global config. Otherwise will fail promise.
|
|
*/
|
|
function getConfigField(fieldName) {
|
|
return getUserConfigField(fieldName)
|
|
.then((value) => {
|
|
// got value from user config
|
|
return value;
|
|
}, () => {
|
|
// failed to get value from user config, so try global config
|
|
return getGlobalConfigField(fieldName);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets a specific user config value for a field
|
|
* @param fieldName
|
|
* @returns {Promise}
|
|
*/
|
|
function getUserConfigField(fieldName) {
|
|
return readUserConfig().then((config) => {
|
|
if (typeof fieldName === 'string' && fieldName in config) {
|
|
return config[fieldName];
|
|
}
|
|
|
|
throw new Error('field does not exist in user config: ' + fieldName);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reads the user config file and returns all the attributes
|
|
* @param customConfigPath
|
|
* @returns {Promise}
|
|
*/
|
|
function readUserConfig(customConfigPath) {
|
|
|
|
log.send(logLevels.INFO, `custom config path ${customConfigPath}`);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
if (userConfig) {
|
|
resolve(userConfig);
|
|
return;
|
|
}
|
|
|
|
let configPath = customConfigPath;
|
|
|
|
if (!configPath) {
|
|
configPath = path.join(app.getPath('userData'), configFileName);
|
|
}
|
|
|
|
log.send(logLevels.INFO, `config path ${configPath}`);
|
|
|
|
fs.readFile(configPath, 'utf8', (err, data) => {
|
|
|
|
if (err) {
|
|
log.send(logLevels.INFO, `cannot open user config file ${configPath}, error is ${err}`);
|
|
reject(`cannot open user config file ${configPath}, error is ${err}`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// data is the contents of the text file we just read
|
|
userConfig = JSON.parse(data);
|
|
log.send(logLevels.INFO, `user config data is ${JSON.stringify(userConfig)}`);
|
|
resolve(userConfig);
|
|
} catch (e) {
|
|
log.send(logLevels.INFO, `cannot parse user config data ${data}, error is ${e}`);
|
|
reject(`cannot parse user config data ${data}, error is ${e}`);
|
|
}
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets a specific global config value for a field
|
|
* @param fieldName
|
|
* @returns {Promise}
|
|
*/
|
|
function getGlobalConfigField(fieldName) {
|
|
return readGlobalConfig().then((config) => {
|
|
if (typeof fieldName === 'string' && fieldName in config) {
|
|
return config[fieldName];
|
|
}
|
|
|
|
throw new Error('field does not exist in global config: ' + fieldName);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* reads global configuration file: config/Symphony.config. this file is
|
|
* hold items (such as the start url) that are intended to be used as
|
|
* global (or default) values for all users running this app. for production
|
|
* this file is located relative to the executable - it is placed there by
|
|
* the installer. this makes the file easily modifable by admin (or person who
|
|
* installed app). for dev env, the file is read directly from packed asar file.
|
|
*/
|
|
function readGlobalConfig() {
|
|
return new Promise((resolve, reject) => {
|
|
if (globalConfig) {
|
|
resolve(globalConfig);
|
|
return;
|
|
}
|
|
|
|
let configPath;
|
|
let globalConfigFileName = path.join('config', configFileName);
|
|
if (isDevEnv) {
|
|
// for dev env, get config file from asar
|
|
configPath = path.join(app.getAppPath(), globalConfigFileName);
|
|
} else {
|
|
// for non-dev, config file is placed by installer relative to exe.
|
|
// this is so the config can be easily be changed post install.
|
|
let execPath = path.dirname(app.getPath('exe'));
|
|
// for mac exec is stored in subdir, for linux/windows config
|
|
// dir is in the same location.
|
|
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
|
|
}
|
|
|
|
fs.readFile(configPath, 'utf8', (err, data) => {
|
|
if (err) {
|
|
reject('cannot open global config file: ' + configPath + ', error: ' + err);
|
|
} else {
|
|
try {
|
|
// data is the contents of the text file we just read
|
|
globalConfig = JSON.parse(data);
|
|
} catch (e) {
|
|
reject('can not parse config file data: ' + data + ', error: ' + err);
|
|
}
|
|
getRegistry('PodUrl')
|
|
.then((url) => {
|
|
globalConfig.url = url;
|
|
resolve(globalConfig);
|
|
}).catch(() => {
|
|
resolve(globalConfig);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates user config with given field with new value
|
|
* @param {String} fieldName Name of field in config to be added/changed.
|
|
* @param {Object} newValue Object to replace given value
|
|
* @return {Promise} Promise that resolves/rejects when file write is complete.
|
|
*/
|
|
function updateConfigField(fieldName, newValue) {
|
|
return readUserConfig()
|
|
.then((config) => {
|
|
return saveUserConfig(fieldName, newValue, config);
|
|
}, () => {
|
|
// in case config doesn't exist, can't read or is corrupted.
|
|
// add configVersion - just in case in future we need to provide
|
|
// upgrade capabilities.
|
|
return saveUserConfig(fieldName, newValue, {
|
|
configVersion: '1.0.0'
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Saves an updated value to the user config
|
|
* @param fieldName
|
|
* @param newValue
|
|
* @param oldConfig
|
|
* @returns {Promise}
|
|
*/
|
|
function saveUserConfig(fieldName, newValue, oldConfig) {
|
|
return new Promise((resolve, reject) => {
|
|
let configPath = path.join(app.getPath('userData'), configFileName);
|
|
|
|
if (!oldConfig || !fieldName) {
|
|
reject('can not save config, invalid input');
|
|
return;
|
|
}
|
|
|
|
// clone and set new value
|
|
let newConfig = Object.assign({}, oldConfig);
|
|
newConfig[fieldName] = newValue;
|
|
|
|
let jsonNewConfig = JSON.stringify(newConfig, null, ' ');
|
|
|
|
fs.writeFile(configPath, jsonNewConfig, 'utf8', (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
userConfig = newConfig;
|
|
resolve(newConfig);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates the existing user config settings by removing
|
|
* 'minimizeOnClose', 'launchOnStartup', 'url' and 'alwaysOnTop'
|
|
* @param {Object} oldUserConfig the old user config object
|
|
*/
|
|
function updateUserConfig(oldUserConfig) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// create a new object from the old user config
|
|
// by ommitting the user related settings from
|
|
// the old user config
|
|
log.send(logLevels.INFO, `old user config string ${JSON.stringify(oldUserConfig)}`);
|
|
let newUserConfig = omit(oldUserConfig, ignoreSettings);
|
|
let newUserConfigString = JSON.stringify(newUserConfig, null, 2);
|
|
|
|
log.send(logLevels.INFO, `new config string ${newUserConfigString}`);
|
|
|
|
// get the user config path
|
|
let userConfigFile;
|
|
userConfigFile = path.join(app.getPath('userData'), configFileName);
|
|
|
|
if (!userConfigFile) {
|
|
return reject(`user config file doesn't exist`);
|
|
}
|
|
|
|
// write the new user config changes to the user config file
|
|
fs.writeFileSync(userConfigFile, newUserConfigString, 'utf-8');
|
|
|
|
return resolve();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Manipulates user config on first time launch
|
|
* @returns {Promise}
|
|
*/
|
|
function updateUserConfigOnLaunch() {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// we get the user config path using electron
|
|
const userConfigFile = path.join(app.getPath('userData'), configFileName);
|
|
|
|
// if it's not a per user installation or if the
|
|
// user config file doesn't exist, we simple move on
|
|
if (!fs.existsSync(userConfigFile)) {
|
|
log.send(logLevels.WARN, 'config: Could not find the user config file!');
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
// In case the file exists, we remove it so that all the
|
|
// values are fetched from the global config
|
|
// https://perzoinc.atlassian.net/browse/ELECTRON-126
|
|
readUserConfig(userConfigFile).then((data) => {
|
|
// Add version info to the user config data
|
|
const version = app.getVersion().toString();
|
|
const updatedData = Object.assign(data, { version });
|
|
|
|
resolve(updateUserConfig(updatedData));
|
|
}).catch((err) => {
|
|
reject(err);
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Method that tries to grab multiple config field from user config
|
|
* if field doesn't exist tries reading from global config
|
|
*
|
|
* @param {Array} fieldNames - array of config filed names
|
|
* @returns {Promise} - object all the config data from user and global config
|
|
*/
|
|
function getMultipleConfigField(fieldNames) {
|
|
return new Promise((resolve, reject) => {
|
|
let userConfigData;
|
|
|
|
if (!fieldNames && fieldNames.length < 0) {
|
|
reject('cannot read config file, invalid fields');
|
|
return;
|
|
}
|
|
|
|
// reads user config data
|
|
readUserConfig().then((config) => {
|
|
userConfigData = pick(config, fieldNames);
|
|
let userConfigKeys = userConfigData ? Object.keys(userConfigData) : undefined;
|
|
|
|
/**
|
|
* Condition to validate data from user config,
|
|
* if all the required fields are not present
|
|
* this tries to fetch the remaining fields from global config
|
|
*/
|
|
if (!userConfigKeys || userConfigKeys.length < fieldNames.length) {
|
|
|
|
// remainingConfig - config field that are not present in the user config
|
|
let remainingConfig = difference(fieldNames, userConfigKeys);
|
|
|
|
if (remainingConfig && Object.keys(remainingConfig).length > 0) {
|
|
readGlobalConfig().then((globalConfigData) => {
|
|
// assigns the remaining fields from global config to the user config
|
|
userConfigData = Object.assign(userConfigData, pick(globalConfigData, remainingConfig));
|
|
resolve(userConfigData);
|
|
}).catch((err) => {
|
|
reject(err);
|
|
});
|
|
}
|
|
|
|
} else {
|
|
resolve(userConfigData);
|
|
}
|
|
}).catch(() => {
|
|
// This reads global config if there was any
|
|
// error while reading user config
|
|
readGlobalConfig().then((config) => {
|
|
userConfigData = pick(config, fieldNames);
|
|
resolve(userConfigData);
|
|
}).catch((err) => {
|
|
reject(err);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Clears the cached config
|
|
*/
|
|
function clearCachedConfigs() {
|
|
userConfig = null;
|
|
globalConfig = null;
|
|
}
|
|
|
|
function readConfigFileSync() {
|
|
|
|
let configPath;
|
|
let globalConfigFileName = path.join('config', configFileName);
|
|
if (isDevEnv) {
|
|
// for dev env, get config file from asar
|
|
configPath = path.join(app.getAppPath(), globalConfigFileName);
|
|
} else {
|
|
// for non-dev, config file is placed by installer relative to exe.
|
|
// this is so the config can be easily be changed post install.
|
|
let execPath = path.dirname(app.getPath('exe'));
|
|
// for mac exec is stored in subdir, for linux/windows config
|
|
// dir is in the same location.
|
|
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
|
|
}
|
|
|
|
let data = fs.readFileSync(configPath);
|
|
|
|
try {
|
|
return JSON.parse(data);
|
|
} catch (err) {
|
|
log.send(logLevels.ERROR, 'config: parsing config failed: ' + err);
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
module.exports = {
|
|
|
|
configFileName,
|
|
|
|
getConfigField,
|
|
|
|
updateConfigField,
|
|
updateUserConfigOnLaunch,
|
|
|
|
getMultipleConfigField,
|
|
|
|
// items below here are only exported for testing, do NOT use!
|
|
saveUserConfig,
|
|
clearCachedConfigs,
|
|
|
|
readConfigFileSync,
|
|
|
|
// use only if you specifically need to read global config fields
|
|
getGlobalConfigField,
|
|
getUserConfigField
|
|
};
|