FileMiddleware.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { constants } = require("buffer");
  6. const { pipeline } = require("stream");
  7. const {
  8. createBrotliCompress,
  9. createBrotliDecompress,
  10. createGzip,
  11. createGunzip,
  12. constants: zConstants
  13. } = require("zlib");
  14. const createHash = require("../util/createHash");
  15. const { dirname, join, mkdirp } = require("../util/fs");
  16. const memoize = require("../util/memoize");
  17. const SerializerMiddleware = require("./SerializerMiddleware");
  18. /** @typedef {typeof import("../util/Hash")} Hash */
  19. /** @typedef {import("../util/fs").IStats} IStats */
  20. /** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
  21. /** @typedef {import("./types").BufferSerializableType} BufferSerializableType */
  22. /*
  23. Format:
  24. File -> Header Section*
  25. Version -> u32
  26. AmountOfSections -> u32
  27. SectionSize -> i32 (if less than zero represents lazy value)
  28. Header -> Version AmountOfSections SectionSize*
  29. Buffer -> n bytes
  30. Section -> Buffer
  31. */
  32. // "wpc" + 1 in little-endian
  33. const VERSION = 0x01637077;
  34. const WRITE_LIMIT_TOTAL = 0x7fff0000;
  35. const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024;
  36. /**
  37. * @param {Buffer[]} buffers buffers
  38. * @param {string | Hash} hashFunction hash function to use
  39. * @returns {string} hash
  40. */
  41. const hashForName = (buffers, hashFunction) => {
  42. const hash = createHash(hashFunction);
  43. for (const buf of buffers) hash.update(buf);
  44. return /** @type {string} */ (hash.digest("hex"));
  45. };
  46. const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  47. const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  48. /** @type {function(Buffer, number, number): void} */
  49. const writeUInt64LE = Buffer.prototype.writeBigUInt64LE
  50. ? (buf, value, offset) => {
  51. buf.writeBigUInt64LE(BigInt(value), offset);
  52. }
  53. : (buf, value, offset) => {
  54. const low = value % 0x100000000;
  55. const high = (value - low) / 0x100000000;
  56. buf.writeUInt32LE(low, offset);
  57. buf.writeUInt32LE(high, offset + 4);
  58. };
  59. /** @type {function(Buffer, number): void} */
  60. const readUInt64LE = Buffer.prototype.readBigUInt64LE
  61. ? (buf, offset) => Number(buf.readBigUInt64LE(offset))
  62. : (buf, offset) => {
  63. const low = buf.readUInt32LE(offset);
  64. const high = buf.readUInt32LE(offset + 4);
  65. return high * 0x100000000 + low;
  66. };
  67. /**
  68. * @typedef {object} SerializeResult
  69. * @property {string | false} name
  70. * @property {number} size
  71. * @property {Promise<any>=} backgroundJob
  72. */
  73. /**
  74. * @param {FileMiddleware} middleware this
  75. * @param {BufferSerializableType[] | Promise<BufferSerializableType[]>} data data to be serialized
  76. * @param {string | boolean} name file base name
  77. * @param {function(string | false, Buffer[], number): Promise<void>} writeFile writes a file
  78. * @param {string | Hash} hashFunction hash function to use
  79. * @returns {Promise<SerializeResult>} resulting file pointer and promise
  80. */
  81. const serialize = async (
  82. middleware,
  83. data,
  84. name,
  85. writeFile,
  86. hashFunction = "md4"
  87. ) => {
  88. /** @type {(Buffer[] | Buffer | SerializeResult | Promise<SerializeResult>)[]} */
  89. const processedData = [];
  90. /** @type {WeakMap<SerializeResult, function(): any | Promise<any>>} */
  91. const resultToLazy = new WeakMap();
  92. /** @type {Buffer[] | undefined} */
  93. let lastBuffers;
  94. for (const item of await data) {
  95. if (typeof item === "function") {
  96. if (!SerializerMiddleware.isLazy(item))
  97. throw new Error("Unexpected function");
  98. if (!SerializerMiddleware.isLazy(item, middleware)) {
  99. throw new Error(
  100. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  101. );
  102. }
  103. lastBuffers = undefined;
  104. const serializedInfo = SerializerMiddleware.getLazySerializedValue(item);
  105. if (serializedInfo) {
  106. if (typeof serializedInfo === "function") {
  107. throw new Error(
  108. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  109. );
  110. } else {
  111. processedData.push(serializedInfo);
  112. }
  113. } else {
  114. const content = item();
  115. if (content) {
  116. const options = SerializerMiddleware.getLazyOptions(item);
  117. processedData.push(
  118. serialize(
  119. middleware,
  120. content,
  121. (options && options.name) || true,
  122. writeFile,
  123. hashFunction
  124. ).then(result => {
  125. /** @type {any} */ (item).options.size = result.size;
  126. resultToLazy.set(result, item);
  127. return result;
  128. })
  129. );
  130. } else {
  131. throw new Error(
  132. "Unexpected falsy value returned by lazy value function"
  133. );
  134. }
  135. }
  136. } else if (item) {
  137. if (lastBuffers) {
  138. lastBuffers.push(item);
  139. } else {
  140. lastBuffers = [item];
  141. processedData.push(lastBuffers);
  142. }
  143. } else {
  144. throw new Error("Unexpected falsy value in items array");
  145. }
  146. }
  147. /** @type {Promise<any>[]} */
  148. const backgroundJobs = [];
  149. const resolvedData = (
  150. await Promise.all(
  151. /** @type {Promise<Buffer[] | Buffer | SerializeResult>[]} */
  152. (processedData)
  153. )
  154. ).map(item => {
  155. if (Array.isArray(item) || Buffer.isBuffer(item)) return item;
  156. backgroundJobs.push(item.backgroundJob);
  157. // create pointer buffer from size and name
  158. const name = /** @type {string} */ (item.name);
  159. const nameBuffer = Buffer.from(name);
  160. const buf = Buffer.allocUnsafe(8 + nameBuffer.length);
  161. writeUInt64LE(buf, item.size, 0);
  162. nameBuffer.copy(buf, 8, 0);
  163. const lazy = resultToLazy.get(item);
  164. SerializerMiddleware.setLazySerializedValue(lazy, buf);
  165. return buf;
  166. });
  167. /** @type {number[]} */
  168. const lengths = [];
  169. for (const item of resolvedData) {
  170. if (Array.isArray(item)) {
  171. let l = 0;
  172. for (const b of item) l += b.length;
  173. while (l > 0x7fffffff) {
  174. lengths.push(0x7fffffff);
  175. l -= 0x7fffffff;
  176. }
  177. lengths.push(l);
  178. } else if (item) {
  179. lengths.push(-item.length);
  180. } else {
  181. throw new Error(`Unexpected falsy value in resolved data ${item}`);
  182. }
  183. }
  184. const header = Buffer.allocUnsafe(8 + lengths.length * 4);
  185. header.writeUInt32LE(VERSION, 0);
  186. header.writeUInt32LE(lengths.length, 4);
  187. for (let i = 0; i < lengths.length; i++) {
  188. header.writeInt32LE(lengths[i], 8 + i * 4);
  189. }
  190. /** @type {Buffer[]} */
  191. const buf = [header];
  192. for (const item of resolvedData) {
  193. if (Array.isArray(item)) {
  194. for (const b of item) buf.push(b);
  195. } else if (item) {
  196. buf.push(item);
  197. }
  198. }
  199. if (name === true) {
  200. name = hashForName(buf, hashFunction);
  201. }
  202. let size = 0;
  203. for (const b of buf) size += b.length;
  204. backgroundJobs.push(writeFile(name, buf, size));
  205. return {
  206. size,
  207. name,
  208. backgroundJob:
  209. backgroundJobs.length === 1
  210. ? backgroundJobs[0]
  211. : Promise.all(backgroundJobs)
  212. };
  213. };
  214. /**
  215. * @param {FileMiddleware} middleware this
  216. * @param {string | false} name filename
  217. * @param {function(string | false): Promise<Buffer[]>} readFile read content of a file
  218. * @returns {Promise<BufferSerializableType[]>} deserialized data
  219. */
  220. const deserialize = async (middleware, name, readFile) => {
  221. const contents = await readFile(name);
  222. if (contents.length === 0) throw new Error(`Empty file ${name}`);
  223. let contentsIndex = 0;
  224. let contentItem = contents[0];
  225. let contentItemLength = contentItem.length;
  226. let contentPosition = 0;
  227. if (contentItemLength === 0) throw new Error(`Empty file ${name}`);
  228. const nextContent = () => {
  229. contentsIndex++;
  230. contentItem = contents[contentsIndex];
  231. contentItemLength = contentItem.length;
  232. contentPosition = 0;
  233. };
  234. /**
  235. * @param {number} n number of bytes to ensure
  236. */
  237. const ensureData = n => {
  238. if (contentPosition === contentItemLength) {
  239. nextContent();
  240. }
  241. while (contentItemLength - contentPosition < n) {
  242. const remaining = contentItem.slice(contentPosition);
  243. let lengthFromNext = n - remaining.length;
  244. const buffers = [remaining];
  245. for (let i = contentsIndex + 1; i < contents.length; i++) {
  246. const l = contents[i].length;
  247. if (l > lengthFromNext) {
  248. buffers.push(contents[i].slice(0, lengthFromNext));
  249. contents[i] = contents[i].slice(lengthFromNext);
  250. lengthFromNext = 0;
  251. break;
  252. } else {
  253. buffers.push(contents[i]);
  254. contentsIndex = i;
  255. lengthFromNext -= l;
  256. }
  257. }
  258. if (lengthFromNext > 0) throw new Error("Unexpected end of data");
  259. contentItem = Buffer.concat(buffers, n);
  260. contentItemLength = n;
  261. contentPosition = 0;
  262. }
  263. };
  264. /**
  265. * @returns {number} value value
  266. */
  267. const readUInt32LE = () => {
  268. ensureData(4);
  269. const value = contentItem.readUInt32LE(contentPosition);
  270. contentPosition += 4;
  271. return value;
  272. };
  273. /**
  274. * @returns {number} value value
  275. */
  276. const readInt32LE = () => {
  277. ensureData(4);
  278. const value = contentItem.readInt32LE(contentPosition);
  279. contentPosition += 4;
  280. return value;
  281. };
  282. /**
  283. * @param {number} l length
  284. * @returns {Buffer} buffer
  285. */
  286. const readSlice = l => {
  287. ensureData(l);
  288. if (contentPosition === 0 && contentItemLength === l) {
  289. const result = contentItem;
  290. if (contentsIndex + 1 < contents.length) {
  291. nextContent();
  292. } else {
  293. contentPosition = l;
  294. }
  295. return result;
  296. }
  297. const result = contentItem.slice(contentPosition, contentPosition + l);
  298. contentPosition += l;
  299. // we clone the buffer here to allow the original content to be garbage collected
  300. return l * 2 < contentItem.buffer.byteLength ? Buffer.from(result) : result;
  301. };
  302. const version = readUInt32LE();
  303. if (version !== VERSION) {
  304. throw new Error("Invalid file version");
  305. }
  306. const sectionCount = readUInt32LE();
  307. const lengths = [];
  308. let lastLengthPositive = false;
  309. for (let i = 0; i < sectionCount; i++) {
  310. const value = readInt32LE();
  311. const valuePositive = value >= 0;
  312. if (lastLengthPositive && valuePositive) {
  313. lengths[lengths.length - 1] += value;
  314. } else {
  315. lengths.push(value);
  316. lastLengthPositive = valuePositive;
  317. }
  318. }
  319. const result = [];
  320. for (let length of lengths) {
  321. if (length < 0) {
  322. const slice = readSlice(-length);
  323. const size = Number(readUInt64LE(slice, 0));
  324. const nameBuffer = slice.slice(8);
  325. const name = nameBuffer.toString();
  326. result.push(
  327. SerializerMiddleware.createLazy(
  328. memoize(() => deserialize(middleware, name, readFile)),
  329. middleware,
  330. {
  331. name,
  332. size
  333. },
  334. slice
  335. )
  336. );
  337. } else {
  338. if (contentPosition === contentItemLength) {
  339. nextContent();
  340. } else if (contentPosition !== 0) {
  341. if (length <= contentItemLength - contentPosition) {
  342. result.push(
  343. Buffer.from(
  344. contentItem.buffer,
  345. contentItem.byteOffset + contentPosition,
  346. length
  347. )
  348. );
  349. contentPosition += length;
  350. length = 0;
  351. } else {
  352. const l = contentItemLength - contentPosition;
  353. result.push(
  354. Buffer.from(
  355. contentItem.buffer,
  356. contentItem.byteOffset + contentPosition,
  357. l
  358. )
  359. );
  360. length -= l;
  361. contentPosition = contentItemLength;
  362. }
  363. } else if (length >= contentItemLength) {
  364. result.push(contentItem);
  365. length -= contentItemLength;
  366. contentPosition = contentItemLength;
  367. } else {
  368. result.push(
  369. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  370. );
  371. contentPosition += length;
  372. length = 0;
  373. }
  374. while (length > 0) {
  375. nextContent();
  376. if (length >= contentItemLength) {
  377. result.push(contentItem);
  378. length -= contentItemLength;
  379. contentPosition = contentItemLength;
  380. } else {
  381. result.push(
  382. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  383. );
  384. contentPosition += length;
  385. length = 0;
  386. }
  387. }
  388. }
  389. }
  390. return result;
  391. };
  392. /** @typedef {{ filename: string, extension?: string }} FileMiddlewareContext */
  393. /**
  394. * @typedef {BufferSerializableType[]} DeserializedType
  395. * @typedef {true} SerializedType
  396. * @extends {SerializerMiddleware<DeserializedType, SerializedType>}
  397. */
  398. class FileMiddleware extends SerializerMiddleware {
  399. /**
  400. * @param {IntermediateFileSystem} fs filesystem
  401. * @param {string | Hash} hashFunction hash function to use
  402. */
  403. constructor(fs, hashFunction = "md4") {
  404. super();
  405. this.fs = fs;
  406. this._hashFunction = hashFunction;
  407. }
  408. /**
  409. * @param {DeserializedType} data data
  410. * @param {object} context context object
  411. * @returns {SerializedType|Promise<SerializedType>} serialized data
  412. */
  413. serialize(data, context) {
  414. const { filename, extension = "" } = context;
  415. return new Promise((resolve, reject) => {
  416. mkdirp(this.fs, dirname(this.fs, filename), err => {
  417. if (err) return reject(err);
  418. // It's important that we don't touch existing files during serialization
  419. // because serialize may read existing files (when deserializing)
  420. const allWrittenFiles = new Set();
  421. /**
  422. * @param {string | false} name name
  423. * @param {Buffer[]} content content
  424. * @param {number} size size
  425. * @returns {Promise<void>}
  426. */
  427. const writeFile = async (name, content, size) => {
  428. const file = name
  429. ? join(this.fs, filename, `../${name}${extension}`)
  430. : filename;
  431. await new Promise(
  432. /**
  433. * @param {(value?: undefined) => void} resolve resolve
  434. * @param {(reason?: Error | null) => void} reject reject
  435. */
  436. (resolve, reject) => {
  437. let stream = this.fs.createWriteStream(`${file}_`);
  438. let compression;
  439. if (file.endsWith(".gz")) {
  440. compression = createGzip({
  441. chunkSize: COMPRESSION_CHUNK_SIZE,
  442. level: zConstants.Z_BEST_SPEED
  443. });
  444. } else if (file.endsWith(".br")) {
  445. compression = createBrotliCompress({
  446. chunkSize: COMPRESSION_CHUNK_SIZE,
  447. params: {
  448. [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT,
  449. [zConstants.BROTLI_PARAM_QUALITY]: 2,
  450. [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true,
  451. [zConstants.BROTLI_PARAM_SIZE_HINT]: size
  452. }
  453. });
  454. }
  455. if (compression) {
  456. pipeline(compression, stream, reject);
  457. stream = compression;
  458. stream.on("finish", () => resolve());
  459. } else {
  460. stream.on("error", err => reject(err));
  461. stream.on("finish", () => resolve());
  462. }
  463. // split into chunks for WRITE_LIMIT_CHUNK size
  464. /** @type {TODO[]} */
  465. const chunks = [];
  466. for (const b of content) {
  467. if (b.length < WRITE_LIMIT_CHUNK) {
  468. chunks.push(b);
  469. } else {
  470. for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) {
  471. chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK));
  472. }
  473. }
  474. }
  475. const len = chunks.length;
  476. let i = 0;
  477. /**
  478. * @param {(Error | null)=} err err
  479. */
  480. const batchWrite = err => {
  481. // will be handled in "on" error handler
  482. if (err) return;
  483. if (i === len) {
  484. stream.end();
  485. return;
  486. }
  487. // queue up a batch of chunks up to the write limit
  488. // end is exclusive
  489. let end = i;
  490. let sum = chunks[end++].length;
  491. while (end < len) {
  492. sum += chunks[end].length;
  493. if (sum > WRITE_LIMIT_TOTAL) break;
  494. end++;
  495. }
  496. while (i < end - 1) {
  497. stream.write(chunks[i++]);
  498. }
  499. stream.write(chunks[i++], batchWrite);
  500. };
  501. batchWrite();
  502. }
  503. );
  504. if (name) allWrittenFiles.add(file);
  505. };
  506. resolve(
  507. serialize(this, data, false, writeFile, this._hashFunction).then(
  508. async ({ backgroundJob }) => {
  509. await backgroundJob;
  510. // Rename the index file to disallow access during inconsistent file state
  511. await new Promise(
  512. /**
  513. * @param {(value?: undefined) => void} resolve resolve
  514. */
  515. resolve => {
  516. this.fs.rename(filename, `${filename}.old`, err => {
  517. resolve();
  518. });
  519. }
  520. );
  521. // update all written files
  522. await Promise.all(
  523. Array.from(
  524. allWrittenFiles,
  525. file =>
  526. new Promise(
  527. /**
  528. * @param {(value?: undefined) => void} resolve resolve
  529. * @param {(reason?: Error | null) => void} reject reject
  530. * @returns {void}
  531. */
  532. (resolve, reject) => {
  533. this.fs.rename(`${file}_`, file, err => {
  534. if (err) return reject(err);
  535. resolve();
  536. });
  537. }
  538. )
  539. )
  540. );
  541. // As final step automatically update the index file to have a consistent pack again
  542. await new Promise(
  543. /**
  544. * @param {(value?: undefined) => void} resolve resolve
  545. * @returns {void}
  546. */
  547. resolve => {
  548. this.fs.rename(`${filename}_`, filename, err => {
  549. if (err) return reject(err);
  550. resolve();
  551. });
  552. }
  553. );
  554. return /** @type {true} */ (true);
  555. }
  556. )
  557. );
  558. });
  559. });
  560. }
  561. /**
  562. * @param {SerializedType} data data
  563. * @param {object} context context object
  564. * @returns {DeserializedType|Promise<DeserializedType>} deserialized data
  565. */
  566. deserialize(data, context) {
  567. const { filename, extension = "" } = context;
  568. /**
  569. * @param {string | boolean} name name
  570. * @returns {Promise<TODO>} result
  571. */
  572. const readFile = name =>
  573. new Promise((resolve, reject) => {
  574. const file = name
  575. ? join(this.fs, filename, `../${name}${extension}`)
  576. : filename;
  577. this.fs.stat(file, (err, stats) => {
  578. if (err) {
  579. reject(err);
  580. return;
  581. }
  582. let remaining = /** @type {IStats} */ (stats).size;
  583. /** @type {Buffer | undefined} */
  584. let currentBuffer;
  585. /** @type {number | undefined} */
  586. let currentBufferUsed;
  587. /** @type {any[]} */
  588. const buf = [];
  589. /** @type {import("zlib").Zlib & import("stream").Transform | undefined} */
  590. let decompression;
  591. if (file.endsWith(".gz")) {
  592. decompression = createGunzip({
  593. chunkSize: DECOMPRESSION_CHUNK_SIZE
  594. });
  595. } else if (file.endsWith(".br")) {
  596. decompression = createBrotliDecompress({
  597. chunkSize: DECOMPRESSION_CHUNK_SIZE
  598. });
  599. }
  600. if (decompression) {
  601. let newResolve;
  602. let newReject;
  603. resolve(
  604. Promise.all([
  605. new Promise((rs, rj) => {
  606. newResolve = rs;
  607. newReject = rj;
  608. }),
  609. new Promise((resolve, reject) => {
  610. decompression.on("data", chunk => buf.push(chunk));
  611. decompression.on("end", () => resolve());
  612. decompression.on("error", err => reject(err));
  613. })
  614. ]).then(() => buf)
  615. );
  616. resolve = newResolve;
  617. reject = newReject;
  618. }
  619. this.fs.open(file, "r", (err, _fd) => {
  620. if (err) {
  621. reject(err);
  622. return;
  623. }
  624. const fd = /** @type {number} */ (_fd);
  625. const read = () => {
  626. if (currentBuffer === undefined) {
  627. currentBuffer = Buffer.allocUnsafeSlow(
  628. Math.min(
  629. constants.MAX_LENGTH,
  630. remaining,
  631. decompression ? DECOMPRESSION_CHUNK_SIZE : Infinity
  632. )
  633. );
  634. currentBufferUsed = 0;
  635. }
  636. let readBuffer = currentBuffer;
  637. let readOffset = /** @type {number} */ (currentBufferUsed);
  638. let readLength =
  639. currentBuffer.length -
  640. /** @type {number} */ (currentBufferUsed);
  641. // values passed to fs.read must be valid int32 values
  642. if (readOffset > 0x7fffffff) {
  643. readBuffer = currentBuffer.slice(readOffset);
  644. readOffset = 0;
  645. }
  646. if (readLength > 0x7fffffff) {
  647. readLength = 0x7fffffff;
  648. }
  649. this.fs.read(
  650. fd,
  651. readBuffer,
  652. readOffset,
  653. readLength,
  654. null,
  655. (err, bytesRead) => {
  656. if (err) {
  657. this.fs.close(fd, () => {
  658. reject(err);
  659. });
  660. return;
  661. }
  662. /** @type {number} */
  663. (currentBufferUsed) += bytesRead;
  664. remaining -= bytesRead;
  665. if (
  666. currentBufferUsed ===
  667. /** @type {Buffer} */ (currentBuffer).length
  668. ) {
  669. if (decompression) {
  670. decompression.write(currentBuffer);
  671. } else {
  672. buf.push(currentBuffer);
  673. }
  674. currentBuffer = undefined;
  675. if (remaining === 0) {
  676. if (decompression) {
  677. decompression.end();
  678. }
  679. this.fs.close(fd, err => {
  680. if (err) {
  681. reject(err);
  682. return;
  683. }
  684. resolve(buf);
  685. });
  686. return;
  687. }
  688. }
  689. read();
  690. }
  691. );
  692. };
  693. read();
  694. });
  695. });
  696. });
  697. return deserialize(this, false, readFile);
  698. }
  699. }
  700. module.exports = FileMiddleware;