|
@@ -1,18 +1,19 @@
|
|
|
/**
|
|
|
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
*
|
|
|
* @author David Sehnal <david.sehnal@gmail.com>
|
|
|
*/
|
|
|
|
|
|
+import * as argparse from 'argparse'
|
|
|
+import { ObjectKeys } from '../../mol-util/type-helpers'
|
|
|
+import VERSION from './version'
|
|
|
+import * as fs from 'fs'
|
|
|
+import { ModelPropertyProviderConfig } from './property-provider';
|
|
|
+
|
|
|
const DefaultModelServerConfig = {
|
|
|
- /**
|
|
|
- * Determine if and how long to cache entries after a request.
|
|
|
- */
|
|
|
- cacheParams: {
|
|
|
- useCache: true,
|
|
|
- maxApproximateSizeInBytes: 2 * 1014 * 1024 * 1024, // 2 GB
|
|
|
- entryTimeoutInMs: 10 * 60 * 1000 // 10 minutes
|
|
|
- },
|
|
|
+ // 0 for off
|
|
|
+ cacheMaxSizeInBytes: 2 * 1014 * 1024 * 1024, // 2 GB
|
|
|
+ cacheEntryTimeoutMs: 10 * 60 * 1000, // 10 minutes
|
|
|
|
|
|
/**
|
|
|
* Node (V8) sometimes exhibits GC related issues that significantly slow down the execution
|
|
@@ -22,13 +23,11 @@ const DefaultModelServerConfig = {
|
|
|
* For this to work, the server must be run using a deamon (i.e. forever.js on Linux
|
|
|
* or IISnode on Windows) so that the server is automatically restarted when the shutdown happens.
|
|
|
*/
|
|
|
- shutdownParams: {
|
|
|
- // 0 for off, server will shut down after this amount of minutes.
|
|
|
- timeoutMinutes: 24 * 60 /* a day */,
|
|
|
|
|
|
- // modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)
|
|
|
- timeoutVarianceMinutes: 60
|
|
|
- },
|
|
|
+ // 0 for off, server will shut down after this amount of minutes.
|
|
|
+ shutdownTimeoutMinutes: 24 * 60, /* a day */
|
|
|
+ // modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)
|
|
|
+ shutdownTimeoutVarianceMinutes: 60,
|
|
|
|
|
|
defaultPort: 1337,
|
|
|
|
|
@@ -36,13 +35,13 @@ const DefaultModelServerConfig = {
|
|
|
* Specify the prefix of the API, i.e.
|
|
|
* <host>/<apiPrefix>/<API queries>
|
|
|
*/
|
|
|
- appPrefix: '/ModelServer',
|
|
|
+ apiPrefix: '/ModelServer',
|
|
|
|
|
|
/**
|
|
|
* The maximum time the server dedicates to executing a query.
|
|
|
* Does not include the time it takes to read and export the data.
|
|
|
*/
|
|
|
- maxQueryTimeInMs: 5 * 1000,
|
|
|
+ queryTimeoutMs: 5 * 1000,
|
|
|
|
|
|
/** Maximum number of requests before "server busy" */
|
|
|
maxQueueLength: 30,
|
|
@@ -51,7 +50,7 @@ const DefaultModelServerConfig = {
|
|
|
* Provide a property config or a path a JSON file with the config.
|
|
|
*/
|
|
|
// TODO: finish customProperty support
|
|
|
- customProperties: <import('./property-provider').ModelPropertyProviderConfig | string>{
|
|
|
+ customProperties: <ModelPropertyProviderConfig | string>{
|
|
|
sources: [
|
|
|
// 'pdbe',
|
|
|
// 'rcsb',
|
|
@@ -91,26 +90,164 @@ const DefaultModelServerConfig = {
|
|
|
*
|
|
|
* /static query uses 'pdb-cif' and 'pdb-bcif' source names.
|
|
|
*/
|
|
|
- fileMapping: [
|
|
|
+ sourceMap: [
|
|
|
['pdb-cif', 'e:/test/quick/${id}_updated.cif'],
|
|
|
// ['pdb-bcif', 'e:/test/quick/${id}.bcif'],
|
|
|
] as [string, string][]
|
|
|
};
|
|
|
|
|
|
+export let mapSourceAndIdToFilename: (source: string, id: string) => string = () => {
|
|
|
+ throw new Error('call setupConfig & validateConfigAndSetupSourceMap to initialize this function');
|
|
|
+}
|
|
|
+
|
|
|
+function addServerArgs(parser: argparse.ArgumentParser) {
|
|
|
+ parser.addArgument([ '--apiPrefix' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.apiPrefix,
|
|
|
+ metavar: 'PREFIX',
|
|
|
+ help: `Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries>`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--defaultPort' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.defaultPort,
|
|
|
+ metavar: 'PORT',
|
|
|
+ type: 'int',
|
|
|
+ help: `Specify the port the server is running on`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--cacheMaxSizeInBytes' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.cacheMaxSizeInBytes,
|
|
|
+ metavar: 'CACHE_SIZE',
|
|
|
+ type: 'int',
|
|
|
+ help: `0 for off.`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--cacheEntryTimeoutMs' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.cacheEntryTimeoutMs,
|
|
|
+ metavar: 'CACHE_TIMEOUT',
|
|
|
+ type: 'int',
|
|
|
+ help: `Specify in ms how long to keep entries in cache.`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--queryTimeoutMs' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.queryTimeoutMs,
|
|
|
+ metavar: 'QUERY_TIMEOUT',
|
|
|
+ type: 'int',
|
|
|
+ help: `The maximum time the server dedicates to executing a query in ms.\nDoes not include the time it takes to read and export the data.`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--shutdownTimeoutMinutes' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.shutdownTimeoutMinutes,
|
|
|
+ metavar: 'TIME',
|
|
|
+ type: 'int',
|
|
|
+ help: `0 for off, server will shut down after this amount of minutes.`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--shutdownTimeoutVarianceMinutes' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.shutdownTimeoutVarianceMinutes,
|
|
|
+ metavar: 'VARIANCE',
|
|
|
+ type: 'int',
|
|
|
+ help: `modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--defaultSource' ], {
|
|
|
+ defaultValue: DefaultModelServerConfig.defaultSource,
|
|
|
+ metavar: 'DEFAULT_SOURCE',
|
|
|
+ help: `modifies which 'sourceMap' source to use by default`
|
|
|
+ });
|
|
|
+ parser.addArgument([ '--sourceMap' ], {
|
|
|
+ nargs: 2,
|
|
|
+ action: 'append',
|
|
|
+ metavar: ['SOURCE', 'PATH'] as any,
|
|
|
+ help: [
|
|
|
+ 'Map `id`s for a `source` to a file path.',
|
|
|
+ 'Example: pdb-bcif \'../../data/bcif/${id}.bcif\' ',
|
|
|
+ 'JS expressions can be used inside ${}, e.g. \'${id.substr(1, 2)}/${id}.mdb\'',
|
|
|
+ 'Can be specified multiple times.',
|
|
|
+ 'The `SOURCE` variable (e.g. `pdb-bcif`) is arbitrary and depends on how you plan to use the server.'
|
|
|
+ ].join('\n'),
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
export type ModelServerConfig = typeof DefaultModelServerConfig
|
|
|
-export const ModelServerConfig = DefaultModelServerConfig
|
|
|
+export const ModelServerConfig = { ...DefaultModelServerConfig }
|
|
|
|
|
|
-export let mapSourceAndIdToFilename: (source: string, id: string) => string = () => {
|
|
|
- throw new Error('call setupConfig to initialize this function');
|
|
|
+export const ModelServerConfigTemplate: ModelServerConfig = {
|
|
|
+ ...DefaultModelServerConfig,
|
|
|
+ defaultSource: 'pdb-bcif',
|
|
|
+ sourceMap: [
|
|
|
+ ['pdb-bcif', './path-to-binary-cif/${id.substr(1, 2)}/${id}.bcif'],
|
|
|
+ ['pdb-cif', './path-to-text-cif/${id.substr(1, 2)}/${id}.cif'],
|
|
|
+ ['pdb-updated', './path-to-updated-cif/${id}.bcif']
|
|
|
+ ] as [string, string][]
|
|
|
}
|
|
|
|
|
|
-export function setupConfig(cfg?: ModelServerConfig) {
|
|
|
- if (cfg) Object.assign(ModelServerConfig, cfg);
|
|
|
- if (!ModelServerConfig.fileMapping) return;
|
|
|
+// TODO: include once properly supported
|
|
|
+delete ModelServerConfigTemplate.customProperties
|
|
|
|
|
|
+interface ServerJsonConfig {
|
|
|
+ cfg?: string,
|
|
|
+ printCfg?: any,
|
|
|
+ cfgTemplate?: any
|
|
|
+}
|
|
|
+
|
|
|
+function addJsonConfigArgs(parser: argparse.ArgumentParser) {
|
|
|
+ parser.addArgument(['--cfg'], {
|
|
|
+ help: [
|
|
|
+ 'JSON config file path',
|
|
|
+ 'If a property is not specified, cmd line param/OS variable/default value are used.'
|
|
|
+ ].join('\n'),
|
|
|
+ required: false
|
|
|
+ });
|
|
|
+ parser.addArgument(['--printCfg'], { help: 'Print current config for validation and exit.', required: false, nargs: 0 });
|
|
|
+ parser.addArgument(['--cfgTemplate'], { help: 'Prints default JSON config template to be modified and exits.', required: false, nargs: 0 });
|
|
|
+}
|
|
|
+
|
|
|
+function setConfig(config: ModelServerConfig) {
|
|
|
+ for (const k of ObjectKeys(ModelServerConfig)) {
|
|
|
+ if (config[k] !== void 0) (ModelServerConfig as any)[k] = config[k];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function validateConfigAndSetupSourceMap() {
|
|
|
+ if (!ModelServerConfig.sourceMap || ModelServerConfig.sourceMap.length === 0) {
|
|
|
+ throw new Error(`Please provide 'sourceMap' configuration. See [-h] for available options.`);
|
|
|
+ }
|
|
|
+
|
|
|
mapSourceAndIdToFilename = new Function('source', 'id', [
|
|
|
'switch (source.toLowerCase()) {',
|
|
|
- ...ModelServerConfig.fileMapping.map(([source, path]) => `case '${source.toLowerCase()}': return \`${path}\`;`),
|
|
|
+ ...ModelServerConfig.sourceMap.map(([source, path]) => `case '${source.toLowerCase()}': return \`${path}\`;`),
|
|
|
'}',
|
|
|
].join('\n')) as any;
|
|
|
+}
|
|
|
+
|
|
|
+function parseConfigArguments() {
|
|
|
+ const parser = new argparse.ArgumentParser({
|
|
|
+ version: VERSION,
|
|
|
+ addHelp: true,
|
|
|
+ description: `ModelServer ${VERSION}, (c) 2018-2020, Mol* contributors`
|
|
|
+ });
|
|
|
+ addJsonConfigArgs(parser);
|
|
|
+ addServerArgs(parser);
|
|
|
+ return parser.parseArgs() as ModelServerConfig & ServerJsonConfig;
|
|
|
+}
|
|
|
+
|
|
|
+export function configureServer() {
|
|
|
+ const config = parseConfigArguments();
|
|
|
+
|
|
|
+ if (config.cfgTemplate !== null) {
|
|
|
+ console.log(JSON.stringify(ModelServerConfigTemplate, null, 2));
|
|
|
+ process.exit(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ setConfig(config) // sets the config for global use
|
|
|
+
|
|
|
+ if (config.cfg) {
|
|
|
+ const cfg = JSON.parse(fs.readFileSync(config.cfg, 'utf8')) as ModelServerConfig;
|
|
|
+ setConfig(cfg);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config.printCfg !== null) {
|
|
|
+ console.log(JSON.stringify(ModelServerConfig, null, 2));
|
|
|
+ process.exit(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ validateConfigAndSetupSourceMap();
|
|
|
+ } catch (e) {
|
|
|
+ console.error('' + e);
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
}
|