writer.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as express from 'express';
  7. import * as fs from 'fs';
  8. import * as path from 'path';
  9. import { makeDir } from '../../../mol-util/make-dir';
  10. import { encodeTarHeader, END_OF_TAR } from './tar';
  11. import * as zlib from 'zlib'
  12. export interface ResultWriter {
  13. beginEntry(name: string, size: number): void,
  14. endEntry(): void,
  15. writeBinary(data: Uint8Array): boolean,
  16. writeString(data: string): boolean,
  17. end(): void
  18. }
  19. export interface WebResutlWriter extends ResultWriter {
  20. doError(code?: number, message?: string): void,
  21. writeHeader(): void
  22. }
  23. export class SimpleResponseResultWriter implements WebResutlWriter {
  24. private ended = false;
  25. private headerWritten = false;
  26. beginEntry(name: string) {
  27. throw new Error('Not supported');
  28. }
  29. async endEntry() {
  30. throw new Error('Not supported');
  31. }
  32. doError(code = 404, message = 'Not Found.') {
  33. if (!this.headerWritten) {
  34. this.headerWritten = true;
  35. this.res.status(code).send(message);
  36. }
  37. this.end();
  38. }
  39. writeHeader() {
  40. if (this.headerWritten) return;
  41. this.headerWritten = true;
  42. this.res.writeHead(200, {
  43. 'Content-Type': this.isBinary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
  44. 'Access-Control-Allow-Origin': '*',
  45. 'Access-Control-Allow-Headers': 'X-Requested-With',
  46. 'Content-Disposition': `inline; filename="${this.fn}"`
  47. });
  48. }
  49. writeBinary(data: Uint8Array) {
  50. return this.res.write(Buffer.from(data.buffer));
  51. }
  52. writeString(this: any, data: string) {
  53. return this.res.write(data);
  54. }
  55. end() {
  56. if (this.ended) return;
  57. this.res.end();
  58. this.ended = true;
  59. }
  60. constructor(private fn: string, private res: express.Response, private isBinary: boolean) {
  61. }
  62. }
  63. export class TarballResponseResultWriter implements WebResutlWriter {
  64. private ended = false;
  65. private headerWritten = false;
  66. private stream = zlib.createGzip({ level: 6, memLevel: 9, chunkSize: 16 * 16384 });
  67. private entrySize = 0;
  68. beginEntry(name: string, size: number) {
  69. this.writeHeader();
  70. const header = encodeTarHeader({ name, size });
  71. this.entrySize = size;
  72. this.stream.write(header);
  73. }
  74. endEntry() {
  75. const size = this.entrySize & 511;
  76. if (size) this.stream.write(END_OF_TAR.slice(0, 512 - size));
  77. }
  78. doError(code = 404, message = 'Not Found.') {
  79. if (!this.headerWritten) {
  80. this.headerWritten = true;
  81. this.res.status(code).send(message);
  82. }
  83. this.end();
  84. }
  85. writeHeader() {
  86. if (this.headerWritten) return;
  87. this.stream.pipe(this.res);
  88. this.stream.on('end', () => this.res.end());
  89. this.headerWritten = true;
  90. this.res.writeHead(200, {
  91. 'Content-Type': 'application/tar+gzip',
  92. 'Access-Control-Allow-Origin': '*',
  93. 'Access-Control-Allow-Headers': 'X-Requested-With',
  94. 'Content-Disposition': `inline; filename="${this.fn}"`
  95. });
  96. }
  97. writeBinary(data: Uint8Array) {
  98. this.writeHeader();
  99. return !!this.stream.write(Buffer.from(data.buffer));
  100. }
  101. writeString(data: string) {
  102. this.writeHeader();
  103. return !!this.stream.write(data);
  104. }
  105. end() {
  106. if (this.ended) return;
  107. this.ended = true;
  108. if (!this.headerWritten) {
  109. return;
  110. }
  111. this.stream.write(END_OF_TAR);
  112. this.stream.end();
  113. }
  114. constructor(private fn: string, private res: express.Response) {
  115. }
  116. }
  117. export class FileResultWriter implements ResultWriter {
  118. private file = 0;
  119. private ended = false;
  120. private opened = false;
  121. async beginEntry(name: string) {
  122. throw new Error('Not supported');
  123. }
  124. async endEntry() {
  125. throw new Error('Not supported');
  126. }
  127. open() {
  128. if (this.opened) return;
  129. makeDir(path.dirname(this.fn));
  130. this.file = fs.openSync(this.fn, 'w');
  131. this.opened = true;
  132. }
  133. writeBinary(data: Uint8Array) {
  134. this.open();
  135. fs.writeSync(this.file, Buffer.from(data.buffer));
  136. return true;
  137. }
  138. writeString(data: string) {
  139. this.open();
  140. fs.writeSync(this.file, data);
  141. return true;
  142. }
  143. end() {
  144. if (!this.opened || this.ended) return;
  145. fs.close(this.file, function () { });
  146. this.ended = true;
  147. }
  148. constructor(private fn: string) {
  149. }
  150. }