diff --git a/src/mol-util/string.ts b/src/mol-util/string.ts index 9e33986c6bf8f4c11fc773cb6e068df977b9dbaf..62ebf10cf0c16383d93237f2e008d1d1fb48bf5a 100644 --- a/src/mol-util/string.ts +++ b/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); } \ No newline at end of file diff --git a/src/servers/volume/config.ts b/src/servers/volume/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..236c1c7ba32beead71a5d9e7cf49e1eeb394fa42 --- /dev/null +++ b/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) +} \ No newline at end of file diff --git a/src/servers/volume/local.ts b/src/servers/volume/local.ts index 45e379769edf45f3c8d9c303b2d6847010f2d17d..99993cfbcfa10612463f83325ff959db7cd93f43 100644 --- a/src/servers/volume/local.ts +++ b/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); diff --git a/src/servers/volume/pack.ts b/src/servers/volume/pack.ts index ebf43fb45e103a4cfaceb59c0fb24271353f275b..b00d95c5e22b5eab39125e90eb1b1ba0796ea4a0 100644 --- a/src/servers/volume/pack.ts +++ b/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); -} \ No newline at end of file +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); diff --git a/src/servers/volume/server-config.ts b/src/servers/volume/server-config.ts deleted file mode 100644 index 8a69aaed92aa4736214498e3a549556cba9c00c1..0000000000000000000000000000000000000000 --- a/src/servers/volume/server-config.ts +++ /dev/null @@ -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; \ No newline at end of file diff --git a/src/servers/volume/server.ts b/src/servers/volume/server.ts index 5d796f6287477133a8aef1894836214b94eeb40a..d8b6149a265082eca4cccc66e3b03dd8621e930e 100644 --- a/src/servers/volume/server.ts +++ b/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(); } \ No newline at end of file diff --git a/src/servers/volume/server/api.ts b/src/servers/volume/server/api.ts index 42f028cf0f6890cc38cc70d5626e33c7955eab38..6fa227adf6ded744658c075b3eb43e008d925a54 100644 --- a/src/servers/volume/server/api.ts +++ b/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) { diff --git a/src/servers/volume/server/documentation.ts b/src/servers/volume/server/documentation.ts index 8fa43ad817af85f12cbb1c7aa44b28f4a8c034b3..4713359d9342b46c7ca4cc67e2b6433719422ca2 100644 --- a/src/servers/volume/server/documentation.ts +++ b/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">© 2016 – now, David Sehnal | Node ${process.version}</div> </body> -</html> -`; \ No newline at end of file +</html>`; +} \ No newline at end of file diff --git a/src/servers/volume/server/query/execute.ts b/src/servers/volume/server/query/execute.ts index 0e76f5afdbbb25d4a5c4fbce3296894644a731f1..9314cb5217c2aec09ba3bbd0f81f03bec912f5a4 100644 --- a/src/servers/volume/server/query/execute.ts +++ b/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.`; } diff --git a/src/servers/volume/server/web-api.ts b/src/servers/volume/server/web-api.ts index 28a0b33845f65f6d833217651d0498d8f0624da7..0b9dbc1d0f93c7a5cf3ac572f269123725e07b6f 100644 --- a/src/servers/volume/server/web-api.ts +++ b/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,