data.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { PluginStateTransform } from '../objects';
  8. import { PluginStateObject as SO } from '../objects';
  9. import { Task } from '../../mol-task';
  10. import { CIF } from '../../mol-io/reader/cif'
  11. import { PluginContext } from '../../mol-plugin/context';
  12. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  13. import { StateTransformer, StateObject } from '../../mol-state';
  14. import { readFromFile, ajaxGetMany } from '../../mol-util/data-source';
  15. import * as CCP4 from '../../mol-io/reader/ccp4/parser'
  16. import * as DSN6 from '../../mol-io/reader/dsn6/parser'
  17. import * as PLY from '../../mol-io/reader/ply/parser'
  18. import { parsePsf } from '../../mol-io/reader/psf/parser';
  19. import { isTypedArray } from '../../mol-data/db/column-helpers';
  20. export { Download }
  21. type Download = typeof Download
  22. const Download = PluginStateTransform.BuiltIn({
  23. name: 'download',
  24. display: { name: 'Download', description: 'Download string or binary data from the specified URL' },
  25. from: [SO.Root],
  26. to: [SO.Data.String, SO.Data.Binary],
  27. params: {
  28. url: PD.Text('https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif', { description: 'Resource URL. Must be the same domain or support CORS.' }),
  29. label: PD.Optional(PD.Text('')),
  30. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' })),
  31. body: PD.Optional(PD.Text(''))
  32. }
  33. })({
  34. apply({ params: p }, globalCtx: PluginContext) {
  35. return Task.create('Download', async ctx => {
  36. const data = await globalCtx.fetch({ url: p.url, type: p.isBinary ? 'binary' : 'string', body: p.body }).runInContext(ctx);
  37. return p.isBinary
  38. ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.url })
  39. : new SO.Data.String(data as string, { label: p.label ? p.label : p.url });
  40. });
  41. },
  42. update({ oldParams, newParams, b }) {
  43. if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  44. if (oldParams.label !== newParams.label) {
  45. b.label = newParams.label || newParams.url;
  46. return StateTransformer.UpdateResult.Updated;
  47. }
  48. return StateTransformer.UpdateResult.Unchanged;
  49. }
  50. });
  51. export { DownloadBlob }
  52. type DownloadBlob = typeof DownloadBlob
  53. const DownloadBlob = PluginStateTransform.BuiltIn({
  54. name: 'download-blob',
  55. display: { name: 'Download Blob', description: 'Download multiple string or binary data from the specified URLs.' },
  56. from: SO.Root,
  57. to: SO.Data.Blob,
  58. params: {
  59. sources: PD.ObjectList({
  60. id: PD.Text('', { label: 'Unique ID' }),
  61. url: PD.Text('https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif', { description: 'Resource URL. Must be the same domain or support CORS.' }),
  62. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' })),
  63. body: PD.Optional(PD.Text('')),
  64. canFail: PD.Optional(PD.Boolean(false, { description: 'Indicate whether the download can fail and not be included in the blob as a result.' }))
  65. }, e => `${e.id}: ${e.url}`),
  66. maxConcurrency: PD.Optional(PD.Numeric(4, { min: 1, max: 12, step: 1 }, { description: 'The maximum number of concurrent downloads.' }))
  67. }
  68. })({
  69. apply({ params }, plugin: PluginContext) {
  70. return Task.create('Download Blob', async ctx => {
  71. const entries: SO.Data.BlobEntry[] = [];
  72. const data = await ajaxGetMany(ctx, params.sources, params.maxConcurrency || 4);
  73. for (let i = 0; i < data.length; i++) {
  74. const r = data[i], src = params.sources[i];
  75. if (r.kind === 'error') plugin.log.warn(`Download ${r.id} (${src.url}) failed: ${r.error}`);
  76. else {
  77. entries.push(src.isBinary
  78. ? { id: r.id, kind: 'binary', data: r.result as Uint8Array }
  79. : { id: r.id, kind: 'string', data: r.result as string });
  80. }
  81. }
  82. return new SO.Data.Blob(entries, { label: 'Data Blob', description: `${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}` });
  83. });
  84. },
  85. // TODO: ??
  86. // update({ oldParams, newParams, b }) {
  87. // return 0 as any;
  88. // // if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  89. // // if (oldParams.label !== newParams.label) {
  90. // // (b.label as string) = newParams.label || newParams.url;
  91. // // return StateTransformer.UpdateResult.Updated;
  92. // // }
  93. // // return StateTransformer.UpdateResult.Unchanged;
  94. // }
  95. });
  96. export { RawData }
  97. type RawData = typeof RawData
  98. const RawData = PluginStateTransform.BuiltIn({
  99. name: 'raw-data',
  100. display: { name: 'Raw Data', description: 'Raw data supplied by value.' },
  101. from: [SO.Root],
  102. to: [SO.Data.String, SO.Data.Binary],
  103. params: {
  104. data: PD.Value<string | number[]>('', { isHidden: true }),
  105. label: PD.Optional(PD.Text(''))
  106. }
  107. })({
  108. apply({ params: p }) {
  109. return Task.create('Raw Data', async () => {
  110. if (typeof p.data !== 'string' && isTypedArray(p.data)) {
  111. throw new Error('Supplied binary data must be a plain array.');
  112. }
  113. return typeof p.data === 'string'
  114. ? new SO.Data.String(p.data as string, { label: p.label ? p.label : 'String' })
  115. : new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
  116. });
  117. },
  118. update({ oldParams, newParams, b }) {
  119. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  120. if (oldParams.label !== newParams.label) {
  121. b.label = newParams.label || b.label;
  122. return StateTransformer.UpdateResult.Updated;
  123. }
  124. return StateTransformer.UpdateResult.Unchanged;
  125. }
  126. });
  127. export { ReadFile }
  128. type ReadFile = typeof ReadFile
  129. const ReadFile = PluginStateTransform.BuiltIn({
  130. name: 'read-file',
  131. display: { name: 'Read File', description: 'Read string or binary data from the specified file' },
  132. from: SO.Root,
  133. to: [SO.Data.String, SO.Data.Binary],
  134. params: {
  135. file: PD.File(),
  136. label: PD.Optional(PD.Text('')),
  137. isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)' }))
  138. }
  139. })({
  140. apply({ params: p }, plugin: PluginContext) {
  141. return Task.create('Open File', async ctx => {
  142. if (p.file === null) {
  143. plugin.log.error('No file(s) selected')
  144. return StateObject.Null;
  145. }
  146. const data = await readFromFile(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx);
  147. return p.isBinary
  148. ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.file.name })
  149. : new SO.Data.String(data as string, { label: p.label ? p.label : p.file.name });
  150. });
  151. },
  152. update({ oldParams, newParams, b }) {
  153. if (oldParams.label !== newParams.label) {
  154. (b.label as string) = newParams.label || oldParams.file?.name || '';
  155. return StateTransformer.UpdateResult.Updated;
  156. }
  157. return StateTransformer.UpdateResult.Unchanged;
  158. },
  159. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user loaded files.' })
  160. });
  161. export { ParseBlob }
  162. type ParseBlob = typeof ParseBlob
  163. const ParseBlob = PluginStateTransform.BuiltIn({
  164. name: 'parse-blob',
  165. display: { name: 'Parse Blob', description: 'Parse multiple data enties' },
  166. from: SO.Data.Blob,
  167. to: SO.Format.Blob,
  168. params: {
  169. formats: PD.ObjectList({
  170. id: PD.Text('', { label: 'Unique ID' }),
  171. format: PD.Select<'cif'>('cif', [['cif', 'cif']])
  172. }, e => `${e.id}: ${e.format}`)
  173. }
  174. })({
  175. apply({ a, params }, plugin: PluginContext) {
  176. return Task.create('Parse Blob', async ctx => {
  177. const map = new Map<string, string>();
  178. for (const f of params.formats) map.set(f.id, f.format);
  179. const entries: SO.Format.BlobEntry[] = [];
  180. for (const e of a.data) {
  181. if (!map.has(e.id)) continue;
  182. const parsed = await (e.kind === 'string' ? CIF.parse(e.data) : CIF.parseBinary(e.data)).runInContext(ctx);
  183. if (parsed.isError) throw new Error(`${e.id}: ${parsed.message}`);
  184. entries.push({ id: e.id, kind: 'cif', data: parsed.result });
  185. }
  186. return new SO.Format.Blob(entries, { label: 'Format Blob', description: `${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}` });
  187. });
  188. },
  189. // TODO: ??
  190. // update({ oldParams, newParams, b }) {
  191. // return 0 as any;
  192. // // if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
  193. // // if (oldParams.label !== newParams.label) {
  194. // // (b.label as string) = newParams.label || newParams.url;
  195. // // return StateTransformer.UpdateResult.Updated;
  196. // // }
  197. // // return StateTransformer.UpdateResult.Unchanged;
  198. // }
  199. });
  200. export { ParseCif }
  201. type ParseCif = typeof ParseCif
  202. const ParseCif = PluginStateTransform.BuiltIn({
  203. name: 'parse-cif',
  204. display: { name: 'Parse CIF', description: 'Parse CIF from String or Binary data' },
  205. from: [SO.Data.String, SO.Data.Binary],
  206. to: SO.Format.Cif
  207. })({
  208. apply({ a }) {
  209. return Task.create('Parse CIF', async ctx => {
  210. const parsed = await (SO.Data.String.is(a) ? CIF.parse(a.data) : CIF.parseBinary(a.data)).runInContext(ctx);
  211. if (parsed.isError) throw new Error(parsed.message);
  212. return new SO.Format.Cif(parsed.result);
  213. });
  214. }
  215. });
  216. export { ParsePsf }
  217. type ParsePsf = typeof ParsePsf
  218. const ParsePsf = PluginStateTransform.BuiltIn({
  219. name: 'parse-psf',
  220. display: { name: 'Parse PSF', description: 'Parse PSF from String data' },
  221. from: [SO.Data.String],
  222. to: SO.Format.Psf
  223. })({
  224. apply({ a }) {
  225. return Task.create('Parse PSF', async ctx => {
  226. const parsed = await parsePsf(a.data).runInContext(ctx);
  227. if (parsed.isError) throw new Error(parsed.message);
  228. return new SO.Format.Psf(parsed.result);
  229. });
  230. }
  231. });
  232. export { ParsePly }
  233. type ParsePly = typeof ParsePly
  234. const ParsePly = PluginStateTransform.BuiltIn({
  235. name: 'parse-ply',
  236. display: { name: 'Parse PLY', description: 'Parse PLY from String data' },
  237. from: [SO.Data.String],
  238. to: SO.Format.Ply
  239. })({
  240. apply({ a }) {
  241. return Task.create('Parse PLY', async ctx => {
  242. const parsed = await PLY.parse(a.data).runInContext(ctx);
  243. if (parsed.isError) throw new Error(parsed.message);
  244. return new SO.Format.Ply(parsed.result, { label: parsed.result.comments[0] || 'PLY Data' });
  245. });
  246. }
  247. });
  248. export { ParseCcp4 }
  249. type ParseCcp4 = typeof ParseCcp4
  250. const ParseCcp4 = PluginStateTransform.BuiltIn({
  251. name: 'parse-ccp4',
  252. display: { name: 'Parse CCP4/MRC/MAP', description: 'Parse CCP4/MRC/MAP from Binary data' },
  253. from: [SO.Data.Binary],
  254. to: SO.Format.Ccp4
  255. })({
  256. apply({ a }) {
  257. return Task.create('Parse CCP4/MRC/MAP', async ctx => {
  258. const parsed = await CCP4.parse(a.data).runInContext(ctx);
  259. if (parsed.isError) throw new Error(parsed.message);
  260. return new SO.Format.Ccp4(parsed.result);
  261. });
  262. }
  263. });
  264. export { ParseDsn6 }
  265. type ParseDsn6 = typeof ParseDsn6
  266. const ParseDsn6 = PluginStateTransform.BuiltIn({
  267. name: 'parse-dsn6',
  268. display: { name: 'Parse DSN6/BRIX', description: 'Parse CCP4/BRIX from Binary data' },
  269. from: [SO.Data.Binary],
  270. to: SO.Format.Dsn6
  271. })({
  272. apply({ a }) {
  273. return Task.create('Parse DSN6/BRIX', async ctx => {
  274. const parsed = await DSN6.parse(a.data).runInContext(ctx);
  275. if (parsed.isError) throw new Error(parsed.message);
  276. return new SO.Format.Dsn6(parsed.result);
  277. });
  278. }
  279. });
  280. export { ImportString }
  281. type ImportString = typeof ImportString
  282. const ImportString = PluginStateTransform.BuiltIn({
  283. name: 'import-string',
  284. display: { name: 'Import String', description: 'Import given data as a string' },
  285. from: SO.Root,
  286. to: SO.Data.String,
  287. params: {
  288. data: PD.Value(''),
  289. label: PD.Optional(PD.Text('')),
  290. }
  291. })({
  292. apply({ params: { data, label } }) {
  293. return new SO.Data.String(data, { label: label || '' });
  294. },
  295. update({ oldParams, newParams, b }) {
  296. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  297. if (oldParams.label !== newParams.label) {
  298. b.label = newParams.label || '';
  299. return StateTransformer.UpdateResult.Updated;
  300. }
  301. return StateTransformer.UpdateResult.Unchanged;
  302. },
  303. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported strings.' })
  304. });
  305. export { ImportJson }
  306. type ImportJson = typeof ImportJson
  307. const ImportJson = PluginStateTransform.BuiltIn({
  308. name: 'import-json',
  309. display: { name: 'Import JSON', description: 'Import given data as a JSON' },
  310. from: SO.Root,
  311. to: SO.Format.Json,
  312. params: {
  313. data: PD.Value<any>({}),
  314. label: PD.Optional(PD.Text('')),
  315. }
  316. })({
  317. apply({ params: { data, label } }) {
  318. return new SO.Format.Json(data, { label: label || '' });
  319. },
  320. update({ oldParams, newParams, b }) {
  321. if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
  322. if (oldParams.label !== newParams.label) {
  323. b.label = newParams.label || '';
  324. return StateTransformer.UpdateResult.Updated;
  325. }
  326. return StateTransformer.UpdateResult.Unchanged;
  327. },
  328. isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported JSON.' })
  329. });
  330. export { ParseJson }
  331. type ParseJson = typeof ParseJson
  332. const ParseJson = PluginStateTransform.BuiltIn({
  333. name: 'parse-json',
  334. display: { name: 'Parse JSON', description: 'Parse JSON from String data' },
  335. from: [SO.Data.String],
  336. to: SO.Format.Json
  337. })({
  338. apply({ a }) {
  339. return Task.create('Parse JSON', async ctx => {
  340. const json = await (new Response(a.data)).json(); // async JSON parsing via fetch API
  341. return new SO.Format.Json(json)
  342. });
  343. }
  344. });