Browse Source

Merge pull request #116 from JonStargaryen/modelserverfixes

ModelServer: Add option to download text files
David Sehnal 4 years ago
parent
commit
03aa2be978

+ 3 - 0
src/servers/model/CHANGELOG.md

@@ -1,3 +1,6 @@
+# 0.9.6
+* optional download parameter
+
 # 0.9.5
 * Support molstar_global_model_transform_info category.
 

+ 2 - 1
src/servers/model/server/api-web-multiple.ts

@@ -18,7 +18,8 @@ export interface MultipleQueryEntry<Name extends QueryName = QueryName> {
 export interface MultipleQuerySpec {
     queries: MultipleQueryEntry[],
     encoding?: Encoding,
-    asTarGz?: boolean
+    asTarGz?: boolean,
+    download?: boolean
 }
 
 export function getMultiQuerySpecFilename() {

+ 8 - 7
src/servers/model/server/api-web.ts

@@ -11,7 +11,7 @@ import * as bodyParser from 'body-parser';
 import { ModelServerConfig as Config, ModelServerConfig, mapSourceAndIdToFilename } from '../config';
 import { ConsoleLogger } from '../../../mol-util/console-logger';
 import { resolveJob } from './query';
-import { JobManager, JobEntry } from './jobs';
+import { JobManager, JobEntry, ResultWriterParams } from './jobs';
 import { UUID } from '../../../mol-util';
 import { QueryDefinition, normalizeRestQueryParams, normalizeRestCommonParams, QueryList } from './api';
 import { getApiSchema, shortcutIconLink } from './api-schema';
@@ -45,17 +45,18 @@ async function processNextJob() {
     }
 }
 
-export function createResultWriter(response: express.Response, encoding: string, entryId?: string, queryName?: string) {
-    const filenameBase = entryId && queryName
-        ? `${entryId}_${splitCamelCase(queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
+export function createResultWriter(response: express.Response, params: ResultWriterParams) {
+    const filenameBase = params.entryId && params.queryName
+        ? `${params.entryId}_${splitCamelCase(params.queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
         : `result`;
-    return new SimpleResponseResultWriter(`${filenameBase}.${encoding}`, response, encoding === 'bcif');
+    return new SimpleResponseResultWriter(`${filenameBase}.${params.encoding}`, response, params.encoding === 'bcif', params.download);
 }
 
 function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
     function createJob(queryParams: any, req: express.Request, res: express.Response) {
         const entryId = req.params.id;
         const commonParams = normalizeRestCommonParams(req.query);
+        const resultWriterParams = { encoding: commonParams.encoding!, download: !!commonParams.download, entryId, queryName };
         const jobId = JobManager.add({
             entries: [JobEntry({
                 sourceId: commonParams.data_source || ModelServerConfig.defaultSource,
@@ -66,7 +67,7 @@ function mapQuery(app: express.Express, queryName: string, queryDefinition: Quer
                 copyAllCategories: !!commonParams.copy_all_categories,
                 transform: commonParams.transform
             })],
-            writer: createResultWriter(res, commonParams.encoding!, entryId, queryName),
+            writer: createResultWriter(res, resultWriterParams),
             options: { binary: commonParams.encoding === 'bcif', encoding: commonParams.encoding }
         });
         responseMap.set(jobId, res);
@@ -122,7 +123,7 @@ function serveStatic(req: express.Request, res: express.Response) {
 function createMultiJob(spec: MultipleQuerySpec, res: express.Response) {
     const writer = spec.asTarGz
         ? new TarballResponseResultWriter(getMultiQuerySpecFilename(), res)
-        : createResultWriter(res, spec.encoding!);
+        : createResultWriter(res, { encoding: spec.encoding!, download: !!spec.download });
 
     if (spec.queries.length > ModelServerConfig.maxQueryManyQueries) {
         writer.doError(400, `query-many queries limit (${ModelServerConfig.maxQueryManyQueries}) exceeded.`);

+ 14 - 4
src/servers/model/server/api.ts

@@ -48,7 +48,8 @@ export const CommonQueryParamsInfo: QueryParamInfo[] = [
     { name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF', 'MOL', or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
     { name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
     { name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' },
-    { name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` }
+    { name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` },
+    { name: 'download', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, browser will download text files.' }
 ];
 
 export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
@@ -57,7 +58,8 @@ export interface CommonQueryParamsInfo {
     encoding?: Encoding,
     copy_all_categories?: boolean
     data_source?: string,
-    transform?: Mat4
+    transform?: Mat4,
+    download?: boolean
 }
 
 export const AtomSiteSchemaElement = {
@@ -290,12 +292,20 @@ export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
     return {
         model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
         data_source: params.data_source,
-        copy_all_categories: Boolean(params.copy_all_categories),
+        copy_all_categories: isTrue(params.copy_all_categories),
         encoding: mapEncoding(('' + params.encoding).toLocaleLowerCase()),
-        transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity()
+        transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity(),
+        download: isTrue(params.download)
     };
 }
 
+function isTrue(val: any): boolean {
+    const b = Boolean(val);
+    if (!b) return false;
+    if (typeof val === 'string') return val !== '0' && val.toLowerCase() !== 'false';
+    return b;
+}
+
 function mapEncoding(value: string) {
     switch (value) {
         case 'bcif':

+ 7 - 0
src/servers/model/server/jobs.ts

@@ -56,6 +56,13 @@ interface JobEntryDefinition<Name extends QueryName> {
     transform?: Mat4
 }
 
+export interface ResultWriterParams {
+    encoding: Encoding,
+    download: boolean,
+    entryId?: string,
+    queryName?: string
+}
+
 export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<Name>): JobEntry {
     const queryDefinition = getQueryByName(definition.queryName);
     if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);

+ 3 - 2
src/servers/model/utils/writer.ts

@@ -48,10 +48,11 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
         this.headerWritten = true;
 
         this.res.writeHead(200, {
+            // TODO there seems to be a bug in swagger-ui - front-end will freeze for cif delivered as text/plain (forcing binary is a hack to circumvent this)
             'Content-Type': this.isBinary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
             'Access-Control-Allow-Origin': '*',
             'Access-Control-Allow-Headers': 'X-Requested-With',
-            'Content-Disposition': `inline; filename="${this.fn}"`
+            'Content-Disposition': `${this.isDownload ? 'attachment' : 'inline'}; filename="${this.fn}"`
         });
     }
 
@@ -71,7 +72,7 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
         this.ended = true;
     }
 
-    constructor(private fn: string, private res: express.Response, private isBinary: boolean) {
+    constructor(private fn: string, private res: express.Response, private isBinary: boolean, private isDownload: boolean) {
 
     }
 }

+ 1 - 1
src/servers/model/version.ts

@@ -4,4 +4,4 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-export default '0.9.5';
+export default '0.9.6';