FileMiddleware.js 21 KB

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