FileMiddleware.js 22 KB

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