Browse Source

volume-server, refactored handling of config and cmd args

Alexander Rose 6 years ago
parent
commit
25283aae21

+ 7 - 1
src/mol-util/string.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -46,4 +46,10 @@ export function substringStartsWith(str: string, start: number, end: number, tar
         if (str.charCodeAt(start + i) !== target.charCodeAt(i)) return false;
     }
     return true;
+}
+
+export function interpolate(str: string, params: { [k: string]: any }) {
+    const names = Object.keys(params);
+    const values = Object.values(params);
+    return new Function(...names, `return \`${str}\`;`)(...values);
 }

+ 107 - 0
src/servers/volume/config.ts

@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as argparse from 'argparse'
+
+export function addLimitsArgs(parser: argparse.ArgumentParser) {
+    parser.addArgument([ '--maxRequestBlockCount' ], {
+        defaultValue: DefaultLimitsConfig.maxRequestBlockCount,
+        metavar: 'COUNT',
+        help: `Maximum number of blocks that could be read in 1 query.
+This is somewhat tied to the maxOutputSizeInVoxelCountByPrecisionLevel
+in that the <maximum number of voxel> = maxRequestBlockCount * <block size>^3.
+The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks.`
+    });
+    parser.addArgument([ '--maxFractionalBoxVolume' ], {
+        defaultValue: DefaultLimitsConfig.maxFractionalBoxVolume,
+        metavar: 'VOLUME',
+        help: `The maximum fractional volume of the query box (to prevent queries that are too big).`
+    });
+    parser.addArgument([ '--maxOutputSizeInVoxelCountByPrecisionLevel' ], {
+        nargs: '+',
+        defaultValue: DefaultLimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel,
+        metavar: 'LEVEL',
+        help: `What is the (approximate) maximum desired size in voxel count by precision level
+Rule of thumb: <response gzipped size> in [<voxel count> / 8, <voxel count> / 4].
+The maximum number of voxels is tied to maxRequestBlockCount.`
+    });
+}
+
+export function addServerArgs(parser: argparse.ArgumentParser) {
+    parser.addArgument([ '--apiPrefix' ], {
+        defaultValue: DefaultServerConfig.apiPrefix,
+        metavar: 'PREFIX',
+        help: `Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries>`
+    });
+    parser.addArgument([ '--defaultPort' ], {
+        defaultValue: DefaultServerConfig.defaultPort,
+        metavar: 'PORT',
+        help: `Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries>`
+    });
+
+    parser.addArgument([ '--shutdownTimeoutMinutes' ], {
+        defaultValue: DefaultServerConfig.shutdownTimeoutMinutes,
+        metavar: 'TIME',
+        help: `0 for off, server will shut down after this amount of minutes.`
+    });
+    parser.addArgument([ '--shutdownTimeoutVarianceMinutes' ], {
+        defaultValue: DefaultServerConfig.shutdownTimeoutVarianceMinutes,
+        metavar: 'VARIANCE',
+        help: `modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)`
+    });
+    parser.addArgument([ '--idMap' ], {
+        nargs: 2,
+        action: 'append',
+        metavar: ['TYPE', 'PATH'] as any,
+        help: [
+            'Map `id`s for a `type` to a file path.',
+            'Example: x-ray \'../../data/mdb/xray/${id}-ccp4.mdb\'',
+            'Note: Can be specified multiple times.'
+        ].join('\n'),
+    });
+}
+
+const DefaultServerConfig = {
+    apiPrefix: '/VolumeServer',
+    defaultPort: 1337,
+    shutdownTimeoutMinutes: 24 * 60, /* a day */
+    shutdownTimeoutVarianceMinutes: 60,
+    idMap: [] as [string, string][]
+}
+export type ServerConfig = typeof DefaultServerConfig
+export const ServerConfig = { ...DefaultServerConfig }
+export function setServerConfig(config: ServerConfig) {
+    for (const name in DefaultServerConfig) {
+        ServerConfig[name as keyof ServerConfig] = config[name as keyof ServerConfig]
+    }
+}
+
+const DefaultLimitsConfig = {
+    maxRequestBlockCount: 32,
+    maxFractionalBoxVolume: 1024,
+    maxOutputSizeInVoxelCountByPrecisionLevel: [
+        0.5 * 1024 * 1024, // ~ 80*80*80
+        1 * 1024 * 1024,
+        2 * 1024 * 1024,
+        4 * 1024 * 1024,
+        8 * 1024 * 1024,
+        16 * 1024 * 1024, // ~ 256*256*256
+        24 * 1024 * 1024
+    ]
+}
+export type LimitsConfig = typeof DefaultLimitsConfig
+export const LimitsConfig = { ...DefaultLimitsConfig }
+export function setLimitsConfig(config: LimitsConfig) {
+    for (const name in DefaultLimitsConfig) {
+        LimitsConfig[name as keyof LimitsConfig] = config[name as keyof LimitsConfig]
+    }
+}
+
+export function setConfig(config: ServerConfig & LimitsConfig) {
+    setServerConfig(config)
+    setLimitsConfig(config)
+}

+ 19 - 13
src/servers/volume/local.ts

@@ -1,17 +1,19 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import * as argparse from 'argparse'
 import * as LocalApi from './server/local-api'
 import VERSION from './server/version'
-
 import * as fs from 'fs'
+import { LimitsConfig, addLimitsArgs, setLimitsConfig } from './config';
 
-console.log(`VolumeServer ${VERSION}, (c) 2016 - now, David Sehnal`);
+console.log(`VolumeServer Local ${VERSION}, (c) 2018-2019, Mol* contributors`);
 console.log();
 
 function help() {
@@ -48,21 +50,25 @@ function help() {
         outputFolder: 'g:/test/local-test'
     }];
 
-    console.log('Usage: node local jobs.json');
-    console.log();
-    console.log('Example jobs.json:');
-    console.log(JSON.stringify(exampleJobs, null, 2));
+    return `Usage: node local jobs.json\n\nExample jobs.json: ${JSON.stringify(exampleJobs, null, 2)}`
 }
 
-async function run() {
-    if (process.argv.length !== 3) {
-        help();
-        return;
-    }
+const parser = new argparse.ArgumentParser({
+    addHelp: true,
+    description: help()
+});
+addLimitsArgs(parser)
+parser.addArgument(['jobs'], {
+    help: `Path to jobs JSON file.`
+})
+
+const config: LimitsConfig & { jobs: string } = parser.parseArgs()
+setLimitsConfig(config) // sets the config for global use
 
+async function run() {
     let jobs: LocalApi.JobEntry[];
     try {
-        jobs = JSON.parse(fs.readFileSync(process.argv[2], 'utf-8'));
+        jobs = JSON.parse(fs.readFileSync(config.jobs, 'utf-8'));
     } catch (e) {
         console.log('Error:');
         console.error(e);

+ 66 - 80
src/servers/volume/pack.ts

@@ -1,106 +1,92 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import * as argparse from 'argparse'
 import pack from './pack/main'
 import VERSION from './pack/version'
 
+type FileFormat = 'ccp4' | 'dsn6'
+
 interface Config {
     input: { name: string, filename: string }[],
-    format: 'ccp4' | 'dsn6',
+    format: FileFormat,
     isPeriodic: boolean,
     outputFilename: string,
     blockSizeInMB: number
 }
 
-let config: Config = {
-    input: [],
-    format: 'ccp4',
-    isPeriodic: false,
-    outputFilename: '',
-    blockSizeInMB: 96
-};
-
-function getFormat(format: string): Config['format'] {
-    switch (format.toLowerCase()) {
-        case 'ccp4': return 'ccp4'
-        case 'dsn6': return 'dsn6'
+function getConfig(args: Args) {
+    const config: Partial<Config> = {
+        blockSizeInMB: args.blockSizeInMB,
+        format: args.format,
+        outputFilename: args.output
+    }
+    switch (args.mode) {
+        case 'em':
+            config.input = [
+                { name: 'em', filename: args.inputEm }
+            ];
+            config.isPeriodic = false;
+            break
+        case 'xray':
+            config.input = [
+                { name: '2Fo-Fc', filename: args.input2fofc },
+                { name: 'Fo-Fc', filename: args.inputFofc }
+            ];
+            config.isPeriodic = true;
+            break
     }
-    throw new Error(`unsupported format '${format}'`)
+    return config as Config
 }
 
-function printHelp() {
-    let help = [
-        `VolumeServer Packer ${VERSION}, (c) 2016 - now, David Sehnal`,
-        ``,
-        `The input data must be CCP4/MAP mode 2 (32-bit floats) files.`,
-        ``,
-        `Usage: `,
-        ``,
-        `  node pack -v`,
-        `    Print version.`,
-        ``,
-        `  node pack -xray main.ccp4 diff.ccp4 output.mdb [-blockSize 96]`,
-        `    Pack main and diff density into a single block file.`,
-        `    Optionally specify maximum block size.`,
-        ``,
-        `  node pack -em density.map output.mdb [-blockSize 96]`,
-        `    Pack single density into a block file.`,
-        `    Optionally specify maximum block size.`
-    ];
-    console.log(help.join('\n'));
+interface GeneralArgs {
+    blockSizeInMB: number
+    format: FileFormat
+    output: string
+}
+interface XrayArgs extends GeneralArgs {
+    mode: 'xray'
+    input2fofc: string
+    inputFofc: string
+}
+interface EmArgs extends GeneralArgs {
+    mode: 'em'
+    inputEm: string
 }
+type Args = XrayArgs | EmArgs
 
-function parseInput() {
-    let input = false;
+const parser = new argparse.ArgumentParser({
+    addHelp: true,
+    description: `VolumeServer Packer ${VERSION}, (c) 2018-2019, Mol* contributors`
+});
 
-    if (process.argv.length <= 2) {
-        printHelp();
-        process.exit();
-        return false;
-    }
+const subparsers = parser.addSubparsers({
+    title: 'Packing modes',
+    dest: 'mode'
+});
 
-    for (let i = 2; i < process.argv.length; i++) {
-        switch (process.argv[i].toLowerCase()) {
-            case '-blocksize':
-                config.blockSizeInMB = +process.argv[++i];
-                break;
-            case '-format':
-                config.format = getFormat(process.argv[++i]);
-                break;
-            case '-xray':
-                input = true;
-                config.input = [
-                    { name: '2Fo-Fc', filename: process.argv[++i] },
-                    { name: 'Fo-Fc', filename: process.argv[++i] }
-                ];
-                config.isPeriodic = true;
-                config.outputFilename = process.argv[++i];
-                break;
-            case '-em':
-                input = true;
-                config.input = [
-                    { name: 'em', filename: process.argv[++i] }
-                ];
-                config.outputFilename = process.argv[++i];
-                break;
-            case '-v':
-                console.log(VERSION);
-                process.exit();
-                return false;
-            default:
-                printHelp();
-                process.exit();
-                return false;
-        }
-    }
-    return input;
+function addGeneralArgs(parser: argparse.ArgumentParser) {
+    parser.addArgument(['output'], { help: `Output path.` })
+    parser.addArgument(['--blockSizeInMB'], { defaultValue: 96, help: `Maximum block size.`, metavar: 'SIZE' })
+    parser.addArgument(['--format'], { defaultValue: 'ccp4', help: `Input file format.` })
 }
 
-if (parseInput()) {
-    pack(config.input, config.blockSizeInMB, config.isPeriodic, config.outputFilename, config.format);
-}
+const xrayParser = subparsers.addParser('xray', { addHelp: true })
+xrayParser.addArgument(['input2fofc'], { help: `Path to 2fofc file.`, metavar: '2FOFC' })
+xrayParser.addArgument(['inputFofc'], { help: `Path to fofc file.`, metavar: 'FOFC' })
+addGeneralArgs(xrayParser)
+
+const emParser = subparsers.addParser('em', { addHelp: true })
+emParser.addArgument(['inputEm'], { help: `Path to EM density file.`, metavar: 'EM' })
+addGeneralArgs(emParser)
+
+const args: Args = parser.parseArgs();
+const config = getConfig(args)
+
+pack(config.input, config.blockSizeInMB, config.isPeriodic, config.outputFilename, config.format);

+ 0 - 77
src/servers/volume/server-config.ts

@@ -1,77 +0,0 @@
-
-const Config = {
-    limits: {
-        /**
-         * Maximum number of blocks that could be read in 1 query.
-         * This is somewhat tied to the maxOutputSizeInVoxelCountByPrecisionLevel
-         * in that the <maximum number of voxel> = maxRequestBlockCount * <block size>^3.
-         * The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks.
-         */
-        maxRequestBlockCount: 32,
-
-        /**
-         * The maximum fractional volume of the query box (to prevent queries that are too big).
-         */
-        maxFractionalBoxVolume: 1024,
-
-        /**
-         * What is the (approximate) maximum desired size in voxel count by precision level
-         * Rule of thumb: <response gzipped size> \in [<voxel count> / 8, <voxel count> / 4];
-         *
-         * The maximum number of voxels is tied to maxRequestBlockCount.
-         */
-        maxOutputSizeInVoxelCountByPrecisionLevel: [
-            0.5 * 1024 * 1024, // ~ 80*80*80
-            1 * 1024 * 1024,
-            2 * 1024 * 1024,
-            4 * 1024 * 1024,
-            8 * 1024 * 1024,
-            16 * 1024 * 1024, // ~ 256*256*256
-            24 * 1024 * 1024
-        ]
-    },
-
-    /**
-     * Specify the prefix of the API, i.e.
-     * <host>/<apiPrefix>/<API queries>
-     */
-    apiPrefix: '/VolumeServer',
-
-    /**
-     * If not specified otherwise by the 'port' environment variable, use this port.
-     */
-    defaultPort: 1337,
-
-    /**
-     * Node (V8) sometimes exhibits GC related issues  that significantly slow down the execution
-     * (https://github.com/nodejs/node/issues/8670).
-     * 
-     * Therefore an option is provided that automatically shuts down the server.
-     * 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
-    },
-
-    /**
-     * Maps a request identifier to a filename.
-     * 
-     * @param source 
-     *   Source of the data.
-     * @param id
-     *   Id provided in the request. For xray, PDB id, for emd, EMDB id number. 
-     */
-    mapFile(source: string, id: string) {
-        switch (source.toLowerCase()) {
-            case 'x-ray': return `g:/test/mdb/xray-${id.toLowerCase()}.mdb`;
-            case 'emd': return `g:/test/mdb/${id.toLowerCase()}.mdb`;
-            default: return void 0;
-        }
-    }
-}
-
-export default Config;

+ 21 - 10
src/servers/volume/server.ts

@@ -1,9 +1,10 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import * as express from 'express'
@@ -11,19 +12,20 @@ import * as compression from 'compression'
 
 import init from './server/web-api'
 import VERSION from './server/version'
-import ServerConfig from './server-config'
 import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from './server/state'
+import { addServerArgs, addLimitsArgs, LimitsConfig, setConfig, ServerConfig } from './config';
+import * as argparse from 'argparse'
 
 function setupShutdown() {
-    if (ServerConfig.shutdownParams.timeoutVarianceMinutes > ServerConfig.shutdownParams.timeoutMinutes) {
+    if (ServerConfig.shutdownTimeoutVarianceMinutes > ServerConfig.shutdownTimeoutMinutes) {
         ConsoleLogger.log('Server', 'Shutdown timeout variance is greater than the timer itself, ignoring.');
     } else {
         let tVar = 0;
-        if (ServerConfig.shutdownParams.timeoutVarianceMinutes > 0) {
-            tVar = 2 * (Math.random() - 0.5) * ServerConfig.shutdownParams.timeoutVarianceMinutes;
+        if (ServerConfig.shutdownTimeoutVarianceMinutes > 0) {
+            tVar = 2 * (Math.random() - 0.5) * ServerConfig.shutdownTimeoutVarianceMinutes;
         }
-        let tMs = (ServerConfig.shutdownParams.timeoutMinutes + tVar) * 60 * 1000;
+        let tMs = (ServerConfig.shutdownTimeoutMinutes + tVar) * 60 * 1000;
 
         console.log(`----------------------------------------------------------------------------`);
         console.log(`  The server will shut down in ${ConsoleLogger.formatTime(tMs)} to prevent slow performance.`);
@@ -42,20 +44,29 @@ function setupShutdown() {
     }
 }
 
+const parser = new argparse.ArgumentParser({
+    addHelp: true,
+    description: `VolumeServer ${VERSION}, (c) 2018-2019, Mol* contributors`
+});
+addServerArgs(parser)
+addLimitsArgs(parser)
 
-let port = process.env.port || ServerConfig.defaultPort;
+const config: ServerConfig & LimitsConfig = parser.parseArgs()
+setConfig(config) // sets the config for global use
 
-let app = express();
+const port = process.env.port || ServerConfig.defaultPort;
+
+const app = express();
 app.use(compression({ level: 6, memLevel: 9, chunkSize: 16 * 16384, filter: () => true }));
 init(app);
 
 app.listen(port);
 
-console.log(`VolumeServer ${VERSION}, (c) 2016 - now, David Sehnal`);
+console.log(`VolumeServer ${VERSION}, (c) 2018-2019, Mol* contributors`);
 console.log(``);
 console.log(`The server is running on port ${port}.`);
 console.log(``);
 
-if (ServerConfig.shutdownParams && ServerConfig.shutdownParams.timeoutMinutes > 0) {
+if (config.shutdownTimeoutMinutes > 0) {
     setupShutdown();
 }

+ 3 - 3
src/servers/volume/server/api.ts

@@ -11,15 +11,15 @@ import execute from './query/execute'
 import * as Data from './query/data-model'
 import { ConsoleLogger } from 'mol-util/console-logger'
 import * as DataFormat from '../common/data-format'
-import ServerConfig from '../server-config'
 import { FileHandle } from 'mol-io/common/file-handle';
+import { LimitsConfig } from '../config';
 
 export function getOutputFilename(source: string, id: string, { asBinary, box, detail, forcedSamplingLevel }: Data.QueryParams) {
     function n(s: string) { return (s || '').replace(/[ \n\t]/g, '').toLowerCase() }
     function r(v: number) { return Math.round(10 * v) / 10; }
     const det = forcedSamplingLevel !== void 0
         ? `l${forcedSamplingLevel}`
-        : `d${Math.min(Math.max(0, detail | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1)}`;
+        : `d${Math.min(Math.max(0, detail | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1)}`;
     const boxInfo = box.kind === 'Cell'
         ? 'cell'
         : `${box.kind === 'Cartesian' ? 'cartn' : 'frac'}_${r(box.a[0])}_${r(box.a[1])}_${r(box.a[2])}_${r(box.b[0])}_${r(box.b[1])}_${r(box.b[2])}`;
@@ -37,7 +37,7 @@ export async function getHeaderJson(filename: string | undefined, sourceId: stri
         const header = { ...await readHeader(filename, sourceId) } as DataFormat.Header;
         const { sampleCount } = header!.sampling[0];
         const maxVoxelCount = sampleCount[0] * sampleCount[1] * sampleCount[2];
-        const precisions = ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel
+        const precisions = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel
             .map((maxVoxels, precision) => ({ precision, maxVoxels }));
         const availablePrecisions = [];
         for (const p of precisions) {

+ 15 - 12
src/servers/volume/server/documentation.ts

@@ -1,23 +1,26 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import VERSION from './version'
-import ServerConfig from '../server-config'
+import { LimitsConfig } from '../config';
 
-function detail(i: number) {
-     return `<span class='id'>${i}</span><small> (${Math.round(100 * ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel[i] / 1000 / 1000) / 100 }M voxels)</small>`;
-}
-const detailMax = ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1;
-const dataSource = `Specifies the data source (determined by the experiment method). Currently, <span class='id'>x-ray</span> and <span class='id'>em</span> sources are supported.`;
-const entryId = `Id of the entry. For <span class='id'>x-ray</span>, use PDB ID (i.e. <span class='id'>1cbs</span>) and for <span class='id'>em</span> use EMDB id (i.e. <span class='id'>emd-8116</span>).`;
+export function getDocumentation() {
+    function detail(i: number) {
+       return `<span class='id'>${i}</span><small> (${Math.round(100 *      LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel[i] / 1000 / 1000) / 100 }M voxels)</small>`;
+    }
+    const detailMax = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1;
 
-export default `
-<!DOCTYPE html>
+    // TODO get from config
+    const dataSource = `Specifies the data source (determined by the experiment method). Currently, <span class='id'>x-ray</span> and <span class='id'>em</span> sources are supported.`;
+    const entryId = `Id of the entry. For <span class='id'>x-ray</span>, use PDB ID (i.e. <span class='id'>1cbs</span>) and for <span class='id'>em</span> use EMDB id (i.e. <span class='id'>emd-8116</span>).`;
+
+    return `<!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <meta charset="utf-8" />
@@ -170,5 +173,5 @@ span.id  { color: #DE4D4E; font-family: Menlo,Monaco,Consolas,"Courier New",mono
 <div style="color: #999;font-size:smaller;margin: 20px 0; text-align: right">&copy; 2016 &ndash; now, David Sehnal | Node ${process.version}</div>
 
 </body>
-</html>
-`;
+</html>`;
+}

+ 5 - 5
src/servers/volume/server/query/execute.ts

@@ -13,7 +13,6 @@ import * as Coords from '../algebra/coordinate'
 import * as Box from '../algebra/box'
 import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from '../state'
-import ServerConfig from '../../server-config'
 
 import identify from './identify'
 import compose from './compose'
@@ -23,13 +22,14 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { UUID } from 'mol-util';
 import { FileHandle } from 'mol-io/common/file-handle';
 import { createTypedArray, TypedArrayValueType } from 'mol-io/common/typed-array';
+import { LimitsConfig } from 'servers/volume/config';
 
 export default async function execute(params: Data.QueryParams, outputProvider: () => Data.QueryOutputStream) {
     const start = getTime();
     State.pendingQueries++;
 
     const guid = UUID.create22() as any as string;
-    params.detail = Math.min(Math.max(0, params.detail | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1);
+    params.detail = Math.min(Math.max(0, params.detail | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1);
     ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`);
 
     let sourceFile: FileHandle | undefined;
@@ -114,7 +114,7 @@ function pickSampling(data: Data.DataContext, queryBox: Box.Fractional, forcedLe
         return createQuerySampling(data, data.sampling[Math.min(data.sampling.length, forcedLevel) - 1], queryBox);
     }
 
-    const sizeLimit = ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel[precision] || (2 * 1024 * 1024);
+    const sizeLimit = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel[precision] || (2 * 1024 * 1024);
 
     for (const s of data.sampling) {
         const gridBox = Box.fractionalToGrid(queryBox, s.dataDomain);
@@ -122,7 +122,7 @@ function pickSampling(data: Data.DataContext, queryBox: Box.Fractional, forcedLe
 
         if (approxSize <= sizeLimit) {
             const sampling = createQuerySampling(data, s, queryBox);
-            if (sampling.blocks.length <= ServerConfig.limits.maxRequestBlockCount) {
+            if (sampling.blocks.length <= LimitsConfig.maxRequestBlockCount) {
                 return sampling;
             }
         }
@@ -168,7 +168,7 @@ function createQueryContext(data: Data.DataContext, params: Data.QueryParams, gu
         throw `The query box is not defined.`;
     }
 
-    if (dimensions[0] * dimensions[1] * dimensions[2] > ServerConfig.limits.maxFractionalBoxVolume) {
+    if (dimensions[0] * dimensions[1] * dimensions[2] > LimitsConfig.maxFractionalBoxVolume) {
         throw `The query box volume is too big.`;
     }
 

+ 23 - 12
src/servers/volume/server/web-api.ts

@@ -1,9 +1,10 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import * as express from 'express'
@@ -12,12 +13,14 @@ import * as Api from './api'
 
 import * as Data from './query/data-model'
 import * as Coords from './algebra/coordinate'
-import Docs from './documentation'
-import ServerConfig from '../server-config'
+import { getDocumentation } from './documentation'
 import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from './state'
+import { LimitsConfig, ServerConfig } from '../config';
+import { interpolate } from 'mol-util/string';
 
 export default function init(app: express.Express) {
+    app.locals.mapFile = getMapFileFn()
     function makePath(p: string) {
         return ServerConfig.apiPrefix + '/' + p;
     }
@@ -31,16 +34,26 @@ export default function init(app: express.Express) {
 
     app.get('*', (req, res) => {
         res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
-        res.end(Docs);
+        res.end(getDocumentation());
     });
 }
 
-function mapFile(type: string, id: string) {
-    return ServerConfig.mapFile(type || '', id || '');
+function getMapFileFn() {
+    const map = new Function('type', 'id', 'interpolate', [
+        'id = id.toLowerCase()',
+        'switch (type.toLowerCase()) {',
+            ...ServerConfig.idMap.map(mapping => {
+                const [type, path] = mapping
+                return `    case '${type}': return interpolate('${path}', { id });`
+            }),
+        '    default: return void 0;',
+        '}'
+    ].join('\n'))
+    return (type: string, id: string) => map(type, id, interpolate)
 }
 
 function wrapResponse(fn: string, res: express.Response) {
-    const w = {
+    return {
         do404(this: any) {
             if (!this.headerWritten) {
                 res.writeHead(404);
@@ -74,13 +87,11 @@ function wrapResponse(fn: string, res: express.Response) {
         ended: false,
         headerWritten: false
     };
-
-    return w;
 }
 
 function getSourceInfo(req: express.Request) {
     return {
-        filename: mapFile(req.params.source, req.params.id),
+        filename: req.app.locals.mapFile(req.params.source, req.params.id),
         id: `${req.params.source}/${req.params.id}`
     };
 }
@@ -130,7 +141,7 @@ function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams
     const a = [+req.params.a1, +req.params.a2, +req.params.a3];
     const b = [+req.params.b1, +req.params.b2, +req.params.b3];
 
-    const detail = Math.min(Math.max(0, (+req.query.detail) | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1)
+    const detail = Math.min(Math.max(0, (+req.query.detail) | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1)
     const isCartesian = (req.query.space || '').toLowerCase() !== 'fractional';
 
     const box: Data.QueryParamsBox = isCell
@@ -140,7 +151,7 @@ function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams
             : { kind: 'Fractional', a: Coords.fractional(a[0], a[1], a[2]), b: Coords.fractional(b[0], b[1], b[2]) });
 
     const asBinary = (req.query.encoding || '').toLowerCase() !== 'cif';
-    const sourceFilename = mapFile(req.params.source, req.params.id)!;
+    const sourceFilename = req.app.locals.mapFile(req.params.source, req.params.id)!;
 
     return {
         sourceFilename,