Browse Source

support loading of zip and gz files

- only zip files with a single entry
Alexander Rose 5 years ago
parent
commit
1c4a397249
2 changed files with 57 additions and 13 deletions
  1. 1 1
      src/mol-plugin/state/actions/data-format.ts
  2. 56 12
      src/mol-util/data-source.ts

+ 1 - 1
src/mol-plugin/state/actions/data-format.ts

@@ -125,7 +125,7 @@ export const OpenFile = StateAction.build({
     params: (a, ctx: PluginContext) => {
         const { extensions, options } = ctx.dataFormat.registry
         return {
-            file: PD.File({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',')}),
+            file: PD.File({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip' }),
             format: PD.Select('auto', options),
             visuals: PD.Boolean(true, { description: 'Add default visuals' }),
         }

+ 56 - 12
src/mol-util/data-source.ts

@@ -8,6 +8,9 @@
  */
 
 import { Task, RuntimeContext } from '../mol-task';
+import { parse, ungzip } from './zip/zip';
+// import { inflate, inflateRaw, parse } from 'uzip-module';
+import { utf8Read } from '../mol-io/common/utf8';
 
 // polyfill XMLHttpRequest in node.js
 const XHR = typeof document === 'undefined' ? require('xhr2') as {
@@ -20,6 +23,12 @@ const XHR = typeof document === 'undefined' ? require('xhr2') as {
     readonly UNSENT: number;
 } : XMLHttpRequest
 
+export enum DataCompressionMethod {
+    None,
+    Gzip,
+    Zip,
+}
+
 type DataType = 'json' | 'xml' | 'string' | 'binary'
 type DataValue = 'string' | any | XMLDocument | Uint8Array
 type DataResponse<T extends DataType> =
@@ -105,18 +114,49 @@ function readData<T extends XMLHttpRequest | FileReader>(ctx: RuntimeContext, ac
     });
 }
 
-function processFile<T extends DataType>(reader: FileReader, type: T): DataResponse<T> {
+function getCompression(name: string) {
+    return /\.gz$/i.test(name) ? DataCompressionMethod.Gzip :
+        /\.zip$/i.test(name) ? DataCompressionMethod.Zip :
+            DataCompressionMethod.None
+}
+
+function decompress(data: Uint8Array, compression: DataCompressionMethod): Uint8Array {
+    switch (compression) {
+        case DataCompressionMethod.None: return data
+        case DataCompressionMethod.Gzip: return ungzip(data)
+        case DataCompressionMethod.Zip:
+            const parsed = parse(data.buffer)
+            const names = Object.keys(parsed)
+            if (names.length !== 1) throw new Error('can only decompress zip files with a single entry')
+            return parsed[names[0]] as Uint8Array
+    }
+}
+
+function processFile<T extends DataType>(reader: FileReader, type: T, compression: DataCompressionMethod): DataResponse<T> {
     const { result } = reader
 
-    if (type === 'binary' && result instanceof ArrayBuffer) {
-        return new Uint8Array(result) as DataResponse<T>
-    } else if (type === 'string' && typeof result === 'string') {
-        return result as DataResponse<T>
-    } else if (type === 'xml' && typeof result === 'string') {
+    let data = result instanceof ArrayBuffer ? new Uint8Array(result) : result
+    if (data === null) throw new Error('no data given')
+
+    if (compression !== DataCompressionMethod.None) {
+        if (!(data instanceof Uint8Array)) throw new Error('need Uint8Array for decompression')
+        const decompressed = decompress(data, compression);
+        if (type === 'string') {
+            data = utf8Read(decompressed, 0, decompressed.length);
+        } else {
+            data = decompressed
+        }
+    }
+
+    if (type === 'binary' && data instanceof Uint8Array) {
+        return data as DataResponse<T>
+    } else if (type === 'string' && typeof data === 'string') {
+        return data as DataResponse<T>
+    } else if (type === 'xml' && typeof data === 'string') {
         const parser = new DOMParser();
-        return parser.parseFromString(result, 'application/xml') as DataResponse<T>
-    } else if (type === 'json' && typeof result === 'string') {
-        return JSON.parse(result) as DataResponse<T>
+        return parser.parseFromString(data, 'application/xml') as DataResponse<T>
+    } else if (type === 'json' && typeof data === 'string') {
+        return JSON.parse(data) as DataResponse<T>
     }
     throw new Error(`could not get requested response data '${type}'`)
 }
@@ -126,15 +166,19 @@ function readFromFileInternal<T extends DataType>(file: File, type: T): Task<Dat
     return Task.create('Read File', async ctx => {
         try {
             reader = new FileReader();
+            const compression = getCompression(file.name)
 
-            if (type === 'binary') reader.readAsArrayBuffer(file)
-            else reader.readAsText(file)
+            if (type === 'binary' || compression !== DataCompressionMethod.None) {
+                reader.readAsArrayBuffer(file)
+            } else {
+                reader.readAsText(file)
+            }
 
             await ctx.update({ message: 'Opening file...', canAbort: true });
             const fileReader = await readData(ctx, 'Reading...', reader);
 
             await ctx.update({ message: 'Parsing file...', canAbort: false });
-            return processFile(fileReader, type);
+            return processFile(fileReader, type, compression);
         } finally {
             reader = void 0;
         }