data-format.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import msgpackDecode from '../../mol-io/common/msgpack/decode';
  7. import { PluginContext } from '../../mol-plugin/context';
  8. import { State, StateAction, StateObjectRef } from '../../mol-state';
  9. import { Task } from '../../mol-task';
  10. import { FileInfo, getFileInfo } from '../../mol-util/file-info';
  11. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  12. import { PluginStateObject } from '../objects';
  13. import { PlyProvider } from './shape';
  14. import { DcdProvider, GroProvider, MmcifProvider, PdbProvider, Provider3dg, PsfProvider, MolProvider } from './structure';
  15. import { Ccp4Provider, DscifProvider, Dsn6Provider } from './volume';
  16. export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
  17. private _list: { name: string, provider: DataFormatProvider<D> }[] = []
  18. private _map = new Map<string, DataFormatProvider<D>>()
  19. private _extensions: Set<string> | undefined = undefined
  20. private _binaryExtensions: Set<string> | undefined = undefined
  21. private _options: [string, string][] | undefined = undefined
  22. get types(): [string, string][] {
  23. return this._list.map(e => [e.name, e.provider.label] as [string, string]);
  24. }
  25. get extensions() {
  26. if (this._extensions) return this._extensions
  27. const extensions = new Set<string>()
  28. this._list.forEach(({ provider }) => {
  29. provider.stringExtensions.forEach(ext => extensions.add(ext))
  30. provider.binaryExtensions.forEach(ext => extensions.add(ext))
  31. })
  32. this._extensions = extensions
  33. return extensions
  34. }
  35. get binaryExtensions() {
  36. if (this._binaryExtensions) return this._binaryExtensions
  37. const binaryExtensions = new Set<string>()
  38. this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext)))
  39. this._binaryExtensions = binaryExtensions
  40. return binaryExtensions
  41. }
  42. get options() {
  43. if (this._options) return this._options
  44. const options: [string, string][] = [['auto', 'Automatic']]
  45. this._list.forEach(({ name, provider }) => options.push([ name, provider.label ]))
  46. this._options = options
  47. return options
  48. }
  49. constructor() {
  50. this.add('3dg', Provider3dg)
  51. this.add('ccp4', Ccp4Provider)
  52. this.add('dcd', DcdProvider)
  53. this.add('dscif', DscifProvider)
  54. this.add('dsn6', Dsn6Provider)
  55. this.add('gro', GroProvider)
  56. this.add('mol', MolProvider)
  57. this.add('mmcif', MmcifProvider)
  58. this.add('pdb', PdbProvider)
  59. this.add('ply', PlyProvider)
  60. this.add('psf', PsfProvider)
  61. };
  62. private _clear() {
  63. this._extensions = undefined
  64. this._binaryExtensions = undefined
  65. this._options = undefined
  66. }
  67. add(name: string, provider: DataFormatProvider<D>) {
  68. this._clear()
  69. this._list.push({ name, provider })
  70. this._map.set(name, provider)
  71. }
  72. remove(name: string) {
  73. this._clear()
  74. this._list.splice(this._list.findIndex(e => e.name === name), 1)
  75. this._map.delete(name)
  76. }
  77. auto(info: FileInfo, dataStateObject: D) {
  78. for (let i = 0, il = this.list.length; i < il; ++i) {
  79. const { provider } = this._list[i]
  80. if (provider.isApplicable(info, dataStateObject.data)) return provider
  81. }
  82. throw new Error('no compatible data format provider available')
  83. }
  84. get(name: string): DataFormatProvider<D> {
  85. if (this._map.has(name)) {
  86. return this._map.get(name)!
  87. } else {
  88. throw new Error(`unknown data format name '${name}'`)
  89. }
  90. }
  91. get list() {
  92. return this._list
  93. }
  94. }
  95. export type DataFormatBuilderOptions = { visuals: boolean }
  96. export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
  97. label: string
  98. description: string
  99. stringExtensions: string[]
  100. binaryExtensions: string[]
  101. isApplicable(info: FileInfo, data: string | Uint8Array): boolean
  102. getDefaultBuilder(ctx: PluginContext, data: StateObjectRef<D>, options: DataFormatBuilderOptions, state: State): Task<void>
  103. }
  104. //
  105. export const OpenFiles = StateAction.build({
  106. display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' },
  107. from: PluginStateObject.Root,
  108. params: (a, ctx: PluginContext) => {
  109. const { extensions, options } = ctx.dataFormat.registry
  110. return {
  111. files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }),
  112. format: PD.Select('auto', options),
  113. visuals: PD.Boolean(true, { description: 'Add default visuals' }),
  114. }
  115. }
  116. })(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => {
  117. await state.transaction(async () => {
  118. for (let i = 0, il = params.files.length; i < il; ++i) {
  119. try {
  120. const file = params.files[i]
  121. const info = getFileInfo(file)
  122. const isBinary = plugin.dataFormat.registry.binaryExtensions.has(info.ext)
  123. const { data } = await plugin.builders.data.readFile({ file, isBinary });
  124. // const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file, isBinary });
  125. // const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
  126. const provider = params.format === 'auto'
  127. ? plugin.dataFormat.registry.auto(info, data.cell?.obj!)
  128. : plugin.dataFormat.registry.get(params.format)
  129. // need to await so that the enclosing Task finishes after the update is done.
  130. await provider.getDefaultBuilder(plugin, data, { visuals: params.visuals }, state).runInContext(taskCtx)
  131. } catch (e) {
  132. plugin.log.error(e)
  133. }
  134. }
  135. }).runInContext(taskCtx);
  136. }));
  137. //
  138. type cifVariants = 'dscif' | -1
  139. export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants {
  140. if (info.ext === 'bcif') {
  141. try {
  142. // TODO: find a way to run msgpackDecode only once
  143. // now it is run twice, here and during file parsing
  144. if (msgpackDecode(data as Uint8Array).encoder.startsWith('VolumeServer')) return 'dscif'
  145. } catch { }
  146. } else if (info.ext === 'cif') {
  147. if ((data as string).startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif'
  148. }
  149. return -1
  150. }