Skip to content
Snippets Groups Projects
Commit 25283aae authored by Alexander Rose's avatar Alexander Rose
Browse files

volume-server, refactored handling of config and cmd args

parent 73e187c5
Branches
Tags
No related merge requests found
/** /**
* 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> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
...@@ -46,4 +46,10 @@ export function substringStartsWith(str: string, start: number, end: number, tar ...@@ -46,4 +46,10 @@ export function substringStartsWith(str: string, start: number, end: number, tar
if (str.charCodeAt(start + i) !== target.charCodeAt(i)) return false; if (str.charCodeAt(start + i) !== target.charCodeAt(i)) return false;
} }
return true; 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
/**
* 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
/** /**
* 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) * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
* *
* @author David Sehnal <david.sehnal@gmail.com> * @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 * as LocalApi from './server/local-api'
import VERSION from './server/version' import VERSION from './server/version'
import * as fs from 'fs' 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(); console.log();
function help() { function help() {
...@@ -48,21 +50,25 @@ function help() { ...@@ -48,21 +50,25 @@ function help() {
outputFolder: 'g:/test/local-test' outputFolder: 'g:/test/local-test'
}]; }];
console.log('Usage: node local jobs.json'); return `Usage: node local jobs.json\n\nExample jobs.json: ${JSON.stringify(exampleJobs, null, 2)}`
console.log();
console.log('Example jobs.json:');
console.log(JSON.stringify(exampleJobs, null, 2));
} }
async function run() { const parser = new argparse.ArgumentParser({
if (process.argv.length !== 3) { addHelp: true,
help(); description: help()
return; });
} 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[]; let jobs: LocalApi.JobEntry[];
try { try {
jobs = JSON.parse(fs.readFileSync(process.argv[2], 'utf-8')); jobs = JSON.parse(fs.readFileSync(config.jobs, 'utf-8'));
} catch (e) { } catch (e) {
console.log('Error:'); console.log('Error:');
console.error(e); console.error(e);
......
/** /**
* 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) * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
* *
* @author David Sehnal <david.sehnal@gmail.com> * @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 pack from './pack/main'
import VERSION from './pack/version' import VERSION from './pack/version'
type FileFormat = 'ccp4' | 'dsn6'
interface Config { interface Config {
input: { name: string, filename: string }[], input: { name: string, filename: string }[],
format: 'ccp4' | 'dsn6', format: FileFormat,
isPeriodic: boolean, isPeriodic: boolean,
outputFilename: string, outputFilename: string,
blockSizeInMB: number blockSizeInMB: number
} }
let config: Config = { function getConfig(args: Args) {
input: [], const config: Partial<Config> = {
format: 'ccp4', blockSizeInMB: args.blockSizeInMB,
isPeriodic: false, format: args.format,
outputFilename: '', outputFilename: args.output
blockSizeInMB: 96 }
}; switch (args.mode) {
case 'em':
function getFormat(format: string): Config['format'] { config.input = [
switch (format.toLowerCase()) { { name: 'em', filename: args.inputEm }
case 'ccp4': return 'ccp4' ];
case 'dsn6': return 'dsn6' 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() { interface GeneralArgs {
let help = [ blockSizeInMB: number
`VolumeServer Packer ${VERSION}, (c) 2016 - now, David Sehnal`, format: FileFormat
``, output: string
`The input data must be CCP4/MAP mode 2 (32-bit floats) files.`, }
``, interface XrayArgs extends GeneralArgs {
`Usage: `, mode: 'xray'
``, input2fofc: string
` node pack -v`, inputFofc: string
` Print version.`, }
``, interface EmArgs extends GeneralArgs {
` node pack -xray main.ccp4 diff.ccp4 output.mdb [-blockSize 96]`, mode: 'em'
` Pack main and diff density into a single block file.`, inputEm: string
` 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'));
} }
type Args = XrayArgs | EmArgs
function parseInput() { const parser = new argparse.ArgumentParser({
let input = false; addHelp: true,
description: `VolumeServer Packer ${VERSION}, (c) 2018-2019, Mol* contributors`
});
if (process.argv.length <= 2) { const subparsers = parser.addSubparsers({
printHelp(); title: 'Packing modes',
process.exit(); dest: 'mode'
return false; });
}
for (let i = 2; i < process.argv.length; i++) { function addGeneralArgs(parser: argparse.ArgumentParser) {
switch (process.argv[i].toLowerCase()) { parser.addArgument(['output'], { help: `Output path.` })
case '-blocksize': parser.addArgument(['--blockSizeInMB'], { defaultValue: 96, help: `Maximum block size.`, metavar: 'SIZE' })
config.blockSizeInMB = +process.argv[++i]; parser.addArgument(['--format'], { defaultValue: 'ccp4', help: `Input file format.` })
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;
} }
if (parseInput()) { const xrayParser = subparsers.addParser('xray', { addHelp: true })
pack(config.input, config.blockSizeInMB, config.isPeriodic, config.outputFilename, config.format); xrayParser.addArgument(['input2fofc'], { help: `Path to 2fofc file.`, metavar: '2FOFC' })
} xrayParser.addArgument(['inputFofc'], { help: `Path to fofc file.`, metavar: 'FOFC' })
\ No newline at end of file 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);
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
/** /**
* 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) * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
* *
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import * as express from 'express' import * as express from 'express'
...@@ -11,19 +12,20 @@ import * as compression from 'compression' ...@@ -11,19 +12,20 @@ import * as compression from 'compression'
import init from './server/web-api' import init from './server/web-api'
import VERSION from './server/version' import VERSION from './server/version'
import ServerConfig from './server-config'
import { ConsoleLogger } from 'mol-util/console-logger' import { ConsoleLogger } from 'mol-util/console-logger'
import { State } from './server/state' import { State } from './server/state'
import { addServerArgs, addLimitsArgs, LimitsConfig, setConfig, ServerConfig } from './config';
import * as argparse from 'argparse'
function setupShutdown() { 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.'); ConsoleLogger.log('Server', 'Shutdown timeout variance is greater than the timer itself, ignoring.');
} else { } else {
let tVar = 0; let tVar = 0;
if (ServerConfig.shutdownParams.timeoutVarianceMinutes > 0) { if (ServerConfig.shutdownTimeoutVarianceMinutes > 0) {
tVar = 2 * (Math.random() - 0.5) * ServerConfig.shutdownParams.timeoutVarianceMinutes; 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(`----------------------------------------------------------------------------`);
console.log(` The server will shut down in ${ConsoleLogger.formatTime(tMs)} to prevent slow performance.`); console.log(` The server will shut down in ${ConsoleLogger.formatTime(tMs)} to prevent slow performance.`);
...@@ -42,20 +44,29 @@ function setupShutdown() { ...@@ -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 })); app.use(compression({ level: 6, memLevel: 9, chunkSize: 16 * 16384, filter: () => true }));
init(app); init(app);
app.listen(port); 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(``);
console.log(`The server is running on port ${port}.`); console.log(`The server is running on port ${port}.`);
console.log(``); console.log(``);
if (ServerConfig.shutdownParams && ServerConfig.shutdownParams.timeoutMinutes > 0) { if (config.shutdownTimeoutMinutes > 0) {
setupShutdown(); setupShutdown();
} }
\ No newline at end of file
...@@ -11,15 +11,15 @@ import execute from './query/execute' ...@@ -11,15 +11,15 @@ import execute from './query/execute'
import * as Data from './query/data-model' import * as Data from './query/data-model'
import { ConsoleLogger } from 'mol-util/console-logger' import { ConsoleLogger } from 'mol-util/console-logger'
import * as DataFormat from '../common/data-format' import * as DataFormat from '../common/data-format'
import ServerConfig from '../server-config'
import { FileHandle } from 'mol-io/common/file-handle'; 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) { 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 n(s: string) { return (s || '').replace(/[ \n\t]/g, '').toLowerCase() }
function r(v: number) { return Math.round(10 * v) / 10; } function r(v: number) { return Math.round(10 * v) / 10; }
const det = forcedSamplingLevel !== void 0 const det = forcedSamplingLevel !== void 0
? `l${forcedSamplingLevel}` ? `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' const boxInfo = box.kind === 'Cell'
? '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])}`; : `${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 ...@@ -37,7 +37,7 @@ export async function getHeaderJson(filename: string | undefined, sourceId: stri
const header = { ...await readHeader(filename, sourceId) } as DataFormat.Header; const header = { ...await readHeader(filename, sourceId) } as DataFormat.Header;
const { sampleCount } = header!.sampling[0]; const { sampleCount } = header!.sampling[0];
const maxVoxelCount = sampleCount[0] * sampleCount[1] * sampleCount[2]; const maxVoxelCount = sampleCount[0] * sampleCount[1] * sampleCount[2];
const precisions = ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel const precisions = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel
.map((maxVoxels, precision) => ({ precision, maxVoxels })); .map((maxVoxels, precision) => ({ precision, maxVoxels }));
const availablePrecisions = []; const availablePrecisions = [];
for (const p of precisions) { for (const p of precisions) {
......
/** /**
* 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) * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
* *
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import VERSION from './version' import VERSION from './version'
import ServerConfig from '../server-config' import { LimitsConfig } from '../config';
function detail(i: number) { export function getDocumentation() {
return `<span class='id'>${i}</span><small> (${Math.round(100 * ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel[i] / 1000 / 1000) / 100 }M voxels)</small>`; 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 = 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 detailMax = LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1;
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 default ` // TODO get from config
<!DOCTYPE html> 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"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
...@@ -170,5 +173,5 @@ span.id { color: #DE4D4E; font-family: Menlo,Monaco,Consolas,"Courier New",mono ...@@ -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> <div style="color: #999;font-size:smaller;margin: 20px 0; text-align: right">&copy; 2016 &ndash; now, David Sehnal | Node ${process.version}</div>
</body> </body>
</html> </html>`;
`; }
\ No newline at end of file \ No newline at end of file
...@@ -13,7 +13,6 @@ import * as Coords from '../algebra/coordinate' ...@@ -13,7 +13,6 @@ import * as Coords from '../algebra/coordinate'
import * as Box from '../algebra/box' import * as Box from '../algebra/box'
import { ConsoleLogger } from 'mol-util/console-logger' import { ConsoleLogger } from 'mol-util/console-logger'
import { State } from '../state' import { State } from '../state'
import ServerConfig from '../../server-config'
import identify from './identify' import identify from './identify'
import compose from './compose' import compose from './compose'
...@@ -23,13 +22,14 @@ import { Vec3 } from 'mol-math/linear-algebra'; ...@@ -23,13 +22,14 @@ import { Vec3 } from 'mol-math/linear-algebra';
import { UUID } from 'mol-util'; import { UUID } from 'mol-util';
import { FileHandle } from 'mol-io/common/file-handle'; import { FileHandle } from 'mol-io/common/file-handle';
import { createTypedArray, TypedArrayValueType } from 'mol-io/common/typed-array'; 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) { export default async function execute(params: Data.QueryParams, outputProvider: () => Data.QueryOutputStream) {
const start = getTime(); const start = getTime();
State.pendingQueries++; State.pendingQueries++;
const guid = UUID.create22() as any as string; 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)}`); ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`);
let sourceFile: FileHandle | undefined; let sourceFile: FileHandle | undefined;
...@@ -114,7 +114,7 @@ function pickSampling(data: Data.DataContext, queryBox: Box.Fractional, forcedLe ...@@ -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); 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) { for (const s of data.sampling) {
const gridBox = Box.fractionalToGrid(queryBox, s.dataDomain); const gridBox = Box.fractionalToGrid(queryBox, s.dataDomain);
...@@ -122,7 +122,7 @@ function pickSampling(data: Data.DataContext, queryBox: Box.Fractional, forcedLe ...@@ -122,7 +122,7 @@ function pickSampling(data: Data.DataContext, queryBox: Box.Fractional, forcedLe
if (approxSize <= sizeLimit) { if (approxSize <= sizeLimit) {
const sampling = createQuerySampling(data, s, queryBox); const sampling = createQuerySampling(data, s, queryBox);
if (sampling.blocks.length <= ServerConfig.limits.maxRequestBlockCount) { if (sampling.blocks.length <= LimitsConfig.maxRequestBlockCount) {
return sampling; return sampling;
} }
} }
...@@ -168,7 +168,7 @@ function createQueryContext(data: Data.DataContext, params: Data.QueryParams, gu ...@@ -168,7 +168,7 @@ function createQueryContext(data: Data.DataContext, params: Data.QueryParams, gu
throw `The query box is not defined.`; 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.`; throw `The query box volume is too big.`;
} }
......
/** /**
* 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) * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
* *
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import * as express from 'express' import * as express from 'express'
...@@ -12,12 +13,14 @@ import * as Api from './api' ...@@ -12,12 +13,14 @@ import * as Api from './api'
import * as Data from './query/data-model' import * as Data from './query/data-model'
import * as Coords from './algebra/coordinate' import * as Coords from './algebra/coordinate'
import Docs from './documentation' import { getDocumentation } from './documentation'
import ServerConfig from '../server-config'
import { ConsoleLogger } from 'mol-util/console-logger' import { ConsoleLogger } from 'mol-util/console-logger'
import { State } from './state' import { State } from './state'
import { LimitsConfig, ServerConfig } from '../config';
import { interpolate } from 'mol-util/string';
export default function init(app: express.Express) { export default function init(app: express.Express) {
app.locals.mapFile = getMapFileFn()
function makePath(p: string) { function makePath(p: string) {
return ServerConfig.apiPrefix + '/' + p; return ServerConfig.apiPrefix + '/' + p;
} }
...@@ -31,16 +34,26 @@ export default function init(app: express.Express) { ...@@ -31,16 +34,26 @@ export default function init(app: express.Express) {
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(Docs); res.end(getDocumentation());
}); });
} }
function mapFile(type: string, id: string) { function getMapFileFn() {
return ServerConfig.mapFile(type || '', id || ''); 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) { function wrapResponse(fn: string, res: express.Response) {
const w = { return {
do404(this: any) { do404(this: any) {
if (!this.headerWritten) { if (!this.headerWritten) {
res.writeHead(404); res.writeHead(404);
...@@ -74,13 +87,11 @@ function wrapResponse(fn: string, res: express.Response) { ...@@ -74,13 +87,11 @@ function wrapResponse(fn: string, res: express.Response) {
ended: false, ended: false,
headerWritten: false headerWritten: false
}; };
return w;
} }
function getSourceInfo(req: express.Request) { function getSourceInfo(req: express.Request) {
return { 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}` id: `${req.params.source}/${req.params.id}`
}; };
} }
...@@ -130,7 +141,7 @@ function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams ...@@ -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 a = [+req.params.a1, +req.params.a2, +req.params.a3];
const b = [+req.params.b1, +req.params.b2, +req.params.b3]; 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 isCartesian = (req.query.space || '').toLowerCase() !== 'fractional';
const box: Data.QueryParamsBox = isCell const box: Data.QueryParamsBox = isCell
...@@ -140,7 +151,7 @@ function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams ...@@ -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]) }); : { 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 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 { return {
sourceFilename, sourceFilename,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment