structure-wrapper.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Structure, Model } from '../../../mol-model/structure';
  7. import { PerformanceMonitor } from '../../../mol-util/performance-monitor';
  8. import { Cache } from './cache';
  9. import { ModelServerConfig as Config, mapSourceAndIdToFilename } from '../config';
  10. import { CIF, CifFrame, CifBlock } from '../../../mol-io/reader/cif'
  11. import * as util from 'util'
  12. import * as fs from 'fs'
  13. import * as zlib from 'zlib'
  14. import { Job } from './jobs';
  15. import { ConsoleLogger } from '../../../mol-util/console-logger';
  16. import { ModelPropertiesProvider } from '../property-provider';
  17. import { trajectoryFromMmCIF } from '../../../mol-model-formats/structure/mmcif';
  18. require('util.promisify').shim();
  19. export enum StructureSourceType {
  20. File,
  21. Cache
  22. }
  23. export interface StructureInfo {
  24. sourceType: StructureSourceType;
  25. readTime: number;
  26. parseTime: number;
  27. createModelTime: number;
  28. attachPropsTime: number;
  29. sourceId: string,
  30. entryId: string
  31. }
  32. export interface StructureWrapper {
  33. info: StructureInfo,
  34. isBinary: boolean,
  35. key: string,
  36. approximateSize: number,
  37. models: ArrayLike<Model>,
  38. modelMap: Map<number, Model>,
  39. structureModelMap: Map<number, Structure>,
  40. propertyProvider: ModelPropertiesProvider | undefined,
  41. cifFrame: CifFrame,
  42. cache: object
  43. }
  44. export async function createStructureWrapperFromJob(job: Job, propertyProvider: ModelPropertiesProvider | undefined, allowCache = true): Promise<StructureWrapper> {
  45. if (allowCache && Config.cacheParams.useCache) {
  46. const ret = StructureCache.get(job.key);
  47. if (ret) return ret;
  48. }
  49. const ret = await readStructureWrapper(job.key, job.sourceId, job.entryId, propertyProvider);
  50. if (allowCache && Config.cacheParams.useCache) {
  51. StructureCache.add(ret);
  52. }
  53. return ret;
  54. }
  55. export const StructureCache = new Cache<StructureWrapper>(s => s.key, s => s.approximateSize);
  56. const perf = new PerformanceMonitor();
  57. const readFileAsync = util.promisify(fs.readFile);
  58. const unzipAsync = util.promisify<zlib.InputType, Buffer>(zlib.unzip);
  59. async function readFile(filename: string) {
  60. const isGz = /\.gz$/i.test(filename);
  61. if (filename.match(/\.bcif/)) {
  62. let input = await readFileAsync(filename)
  63. if (isGz) input = await unzipAsync(input);
  64. const data = new Uint8Array(input.byteLength);
  65. for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
  66. return data;
  67. } else {
  68. if (isGz) {
  69. const data = await unzipAsync(await readFileAsync(filename));
  70. return data.toString('utf8');
  71. }
  72. return readFileAsync(filename, 'utf8');
  73. }
  74. }
  75. async function parseCif(data: string|Uint8Array) {
  76. const comp = CIF.parse(data);
  77. const parsed = await comp.run();
  78. if (parsed.isError) throw parsed;
  79. return parsed.result;
  80. }
  81. export async function readDataAndFrame(filename: string, key?: string): Promise<{ data: string | Uint8Array, frame: CifBlock }> {
  82. perf.start('read');
  83. let data;
  84. try {
  85. data = await readFile(filename);
  86. } catch (e) {
  87. ConsoleLogger.error(key || filename, '' + e);
  88. throw new Error(`Could not read the file for '${key || filename}' from disk.`);
  89. }
  90. perf.end('read');
  91. perf.start('parse');
  92. const frame = (await parseCif(data)).blocks[0];
  93. perf.end('parse');
  94. return { data, frame };
  95. }
  96. export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) {
  97. const filename = sourceId === '_local_' ? entryId : mapSourceAndIdToFilename(sourceId, entryId);
  98. if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`);
  99. if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`);
  100. const { data, frame } = await readDataAndFrame(filename, key);
  101. perf.start('createModel');
  102. const models = await trajectoryFromMmCIF(frame).run();
  103. perf.end('createModel');
  104. const modelMap = new Map<number, Model>();
  105. for (const m of models) {
  106. modelMap.set(m.modelNum, m);
  107. }
  108. const ret: StructureWrapper = {
  109. info: {
  110. sourceType: StructureSourceType.File,
  111. readTime: perf.time('read'),
  112. parseTime: perf.time('parse'),
  113. createModelTime: perf.time('createModel'),
  114. attachPropsTime: 0, // perf.time('attachProps'),
  115. sourceId,
  116. entryId
  117. },
  118. isBinary: /\.bcif/.test(filename),
  119. key,
  120. approximateSize: typeof data === 'string' ? 2 * data.length : data.length,
  121. models,
  122. modelMap,
  123. structureModelMap: new Map(),
  124. cifFrame: frame,
  125. propertyProvider,
  126. cache: Object.create(null)
  127. };
  128. return ret;
  129. }
  130. export async function resolveStructure(wrapper: StructureWrapper, modelNum?: number) {
  131. if (typeof modelNum === 'undefined') modelNum = wrapper.models[0].modelNum;
  132. if (wrapper.structureModelMap.has(modelNum)) return wrapper.structureModelMap.get(modelNum)!;
  133. if (!wrapper.modelMap.has(modelNum)) {
  134. return void 0;
  135. }
  136. const model = wrapper.modelMap.get(modelNum)!;
  137. const structure = Structure.ofModel(model);
  138. if (wrapper.propertyProvider) {
  139. const modelProps = wrapper.propertyProvider(model, wrapper.cache);
  140. for (const p of modelProps) {
  141. await tryAttach(wrapper.key, p);
  142. }
  143. }
  144. return structure;
  145. }
  146. export async function resolveStructures(wrapper: StructureWrapper, modelNums?: number[]) {
  147. const ret: Structure[] = [];
  148. for (const n of modelNums || (wrapper.models as Model[]).map(m => m.modelNum)) {
  149. const s = await resolveStructure(wrapper, n);
  150. if (s) ret.push(s);
  151. }
  152. return ret;
  153. }
  154. async function tryAttach(key: string, promise: Promise<any>) {
  155. try {
  156. await promise;
  157. } catch (e) {
  158. ConsoleLogger.errorId(key, 'Custom prop:' + e);
  159. }
  160. }