data-format.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 { PluginContext } from 'mol-plugin/context';
  7. import { State, StateBuilder, StateAction } from 'mol-state';
  8. import { Task } from 'mol-task';
  9. import { FileInfo, getFileInfo } from 'mol-util/file-info';
  10. import { PluginStateObject } from '../objects';
  11. import { ParamDefinition as PD } from 'mol-util/param-definition';
  12. import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume';
  13. import { StateTransforms } from '../transforms';
  14. import { MmcifProvider, PdbProvider } from './structure';
  15. export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
  16. private _list: { name: string, provider: DataFormatProvider<D> }[] = []
  17. private _map = new Map<string, DataFormatProvider<D>>()
  18. private _extensions: Set<string> | undefined = undefined
  19. private _binaryExtensions: Set<string> | undefined = undefined
  20. private _options: [string, string][] | undefined = undefined
  21. get types(): [string, string][] {
  22. return this._list.map(e => [e.name, e.provider.label] as [string, string]);
  23. }
  24. get extensions() {
  25. if (this._extensions) return this._extensions
  26. const extensions = new Set<string>()
  27. this._list.forEach(({ provider }) => {
  28. provider.stringExtensions.forEach(ext => extensions.add(ext))
  29. provider.binaryExtensions.forEach(ext => extensions.add(ext))
  30. })
  31. this._extensions = extensions
  32. return extensions
  33. }
  34. get binaryExtensions() {
  35. if (this._binaryExtensions) return this._binaryExtensions
  36. const binaryExtensions = new Set<string>()
  37. this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext)))
  38. this._binaryExtensions = binaryExtensions
  39. return binaryExtensions
  40. }
  41. get options() {
  42. if (this._options) return this._options
  43. const options: [string, string][] = [['auto', 'Automatic']]
  44. this._list.forEach(({ name, provider }) => options.push([ name, provider.label ]))
  45. this._options = options
  46. return options
  47. }
  48. constructor() {
  49. this.add('ccp4', Ccp4Provider)
  50. this.add('dscif', DscifProvider)
  51. this.add('dsn6', Dsn6Provider)
  52. this.add('mmcif', MmcifProvider)
  53. this.add('pdb', PdbProvider)
  54. };
  55. private _clear() {
  56. this._extensions = undefined
  57. this._binaryExtensions = undefined
  58. this._options = undefined
  59. }
  60. add(name: string, provider: DataFormatProvider<D>) {
  61. this._clear()
  62. this._list.push({ name, provider })
  63. this._map.set(name, provider)
  64. }
  65. remove(name: string) {
  66. this._clear()
  67. this._list.splice(this._list.findIndex(e => e.name === name), 1)
  68. this._map.delete(name)
  69. }
  70. auto(info: FileInfo, dataStateObject: D) {
  71. for (let i = 0, il = this.list.length; i < il; ++i) {
  72. const { provider } = this._list[i]
  73. if (provider.isApplicable(info, dataStateObject.data)) return provider
  74. }
  75. throw new Error('no compatible data format provider available')
  76. }
  77. get(name: string): DataFormatProvider<D> {
  78. if (this._map.has(name)) {
  79. return this._map.get(name)!
  80. } else {
  81. throw new Error(`unknown data format name '${name}'`)
  82. }
  83. }
  84. get list() {
  85. return this._list
  86. }
  87. }
  88. export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
  89. label: string
  90. description: string
  91. stringExtensions: string[]
  92. binaryExtensions: string[]
  93. isApplicable(info: FileInfo, data: string | Uint8Array): boolean
  94. getDefaultBuilder(ctx: PluginContext, data: StateBuilder.To<D>, state?: State): Task<void>
  95. }
  96. //
  97. export const OpenFile = StateAction.build({
  98. display: { name: 'Open File', description: 'Load a file and create its default visual' },
  99. from: PluginStateObject.Root,
  100. params: (a, ctx: PluginContext) => {
  101. const { extensions, options } = ctx.dataFormat.registry
  102. return {
  103. file: PD.File({ accept: Array.from(extensions).map(e => `.${e}`).join(',')}),
  104. format: PD.Select('auto', options),
  105. }
  106. }
  107. })(({ params, state }, ctx: PluginContext) => Task.create('Open File', async taskCtx => {
  108. const info = getFileInfo(params.file)
  109. const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: ctx.dataFormat.registry.binaryExtensions.has(info.ext) });
  110. const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
  111. // Alternative for more complex states where the builder is not a simple StateBuilder.To<>:
  112. /*
  113. const dataRef = dataTree.ref;
  114. await state.updateTree(dataTree).runInContext(taskCtx);
  115. const dataCell = state.select(dataRef)[0];
  116. */
  117. // const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
  118. const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(info, dataStateObject) : ctx.dataFormat.registry.get(params.format)
  119. const b = state.build().to(data.ref);
  120. // need to await the 2nd update the so that the enclosing Task finishes after the update is done.
  121. await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx)
  122. }));