FileMiddleware.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  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. * @param {Buffer[]} buffers buffers
  41. * @param {HashFunction} 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 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 {HashFunction=} 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. /** @type {number[]} */
  320. const lengths = [];
  321. let lastLengthPositive = false;
  322. for (let i = 0; i < sectionCount; i++) {
  323. const value = readInt32LE();
  324. const valuePositive = value >= 0;
  325. if (lastLengthPositive && valuePositive) {
  326. lengths[lengths.length - 1] += value;
  327. } else {
  328. lengths.push(value);
  329. lastLengthPositive = valuePositive;
  330. }
  331. }
  332. /** @type {BufferSerializableType[]} */
  333. const result = [];
  334. for (let length of lengths) {
  335. if (length < 0) {
  336. const slice = readSlice(-length);
  337. const size = Number(readUInt64LE(slice, 0));
  338. const nameBuffer = slice.slice(8);
  339. const name = nameBuffer.toString();
  340. const lazy =
  341. /** @type {LazyFunction} */
  342. (
  343. SerializerMiddleware.createLazy(
  344. memoize(() => deserialize(middleware, name, readFile)),
  345. middleware,
  346. { name, size },
  347. slice
  348. )
  349. );
  350. result.push(lazy);
  351. } else {
  352. if (contentPosition === contentItemLength) {
  353. nextContent();
  354. } else if (contentPosition !== 0) {
  355. if (length <= contentItemLength - contentPosition) {
  356. result.push(
  357. Buffer.from(
  358. contentItem.buffer,
  359. contentItem.byteOffset + contentPosition,
  360. length
  361. )
  362. );
  363. contentPosition += length;
  364. length = 0;
  365. } else {
  366. const l = contentItemLength - contentPosition;
  367. result.push(
  368. Buffer.from(
  369. contentItem.buffer,
  370. contentItem.byteOffset + contentPosition,
  371. l
  372. )
  373. );
  374. length -= l;
  375. contentPosition = contentItemLength;
  376. }
  377. } else if (length >= contentItemLength) {
  378. result.push(contentItem);
  379. length -= contentItemLength;
  380. contentPosition = contentItemLength;
  381. } else {
  382. result.push(
  383. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  384. );
  385. contentPosition += length;
  386. length = 0;
  387. }
  388. while (length > 0) {
  389. nextContent();
  390. if (length >= contentItemLength) {
  391. result.push(contentItem);
  392. length -= contentItemLength;
  393. contentPosition = contentItemLength;
  394. } else {
  395. result.push(
  396. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  397. );
  398. contentPosition += length;
  399. length = 0;
  400. }
  401. }
  402. }
  403. }
  404. return result;
  405. };
  406. /** @typedef {BufferSerializableType[]} DeserializedType */
  407. /** @typedef {true} SerializedType */
  408. /** @typedef {{ filename: string, extension?: string }} Context */
  409. /**
  410. * @extends {SerializerMiddleware<DeserializedType, SerializedType, Context>}
  411. */
  412. class FileMiddleware extends SerializerMiddleware {
  413. /**
  414. * @param {IntermediateFileSystem} fs filesystem
  415. * @param {HashFunction} hashFunction hash function to use
  416. */
  417. constructor(fs, hashFunction = DEFAULTS.HASH_FUNCTION) {
  418. super();
  419. /** @type {IntermediateFileSystem} */
  420. this.fs = fs;
  421. /** @type {HashFunction} */
  422. this._hashFunction = hashFunction;
  423. }
  424. /**
  425. * @param {DeserializedType} data data
  426. * @param {Context} context context object
  427. * @returns {SerializedType | Promise<SerializedType> | null} serialized data
  428. */
  429. serialize(data, context) {
  430. const { filename, extension = "" } = context;
  431. return new Promise((resolve, reject) => {
  432. mkdirp(this.fs, dirname(this.fs, filename), (err) => {
  433. if (err) return reject(err);
  434. // It's important that we don't touch existing files during serialization
  435. // because serialize may read existing files (when deserializing)
  436. /** @type {Set<string>} */
  437. const allWrittenFiles = new Set();
  438. /**
  439. * @param {string | false} name name
  440. * @param {Buffer[]} content content
  441. * @param {number} size size
  442. * @returns {Promise<void>}
  443. */
  444. const writeFile = async (name, content, size) => {
  445. const file = name
  446. ? join(this.fs, filename, `../${name}${extension}`)
  447. : filename;
  448. await new Promise(
  449. /**
  450. * @param {(value?: undefined) => void} resolve resolve
  451. * @param {(reason?: Error | null) => void} reject reject
  452. */
  453. (resolve, reject) => {
  454. let stream = this.fs.createWriteStream(`${file}_`);
  455. /** @type {undefined | import("zlib").Gzip | import("zlib").BrotliCompress} */
  456. let compression;
  457. if (file.endsWith(".gz")) {
  458. compression = createGzip({
  459. chunkSize: COMPRESSION_CHUNK_SIZE,
  460. level: zConstants.Z_BEST_SPEED
  461. });
  462. } else if (file.endsWith(".br")) {
  463. compression = createBrotliCompress({
  464. chunkSize: COMPRESSION_CHUNK_SIZE,
  465. params: {
  466. [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT,
  467. [zConstants.BROTLI_PARAM_QUALITY]: 2,
  468. [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true,
  469. [zConstants.BROTLI_PARAM_SIZE_HINT]: size
  470. }
  471. });
  472. }
  473. if (compression) {
  474. pipeline(compression, stream, reject);
  475. stream = compression;
  476. stream.on("finish", () => resolve());
  477. } else {
  478. stream.on("error", (err) => reject(err));
  479. stream.on("finish", () => resolve());
  480. }
  481. // split into chunks for WRITE_LIMIT_CHUNK size
  482. /** @type {Buffer[]} */
  483. const chunks = [];
  484. for (const b of content) {
  485. if (b.length < WRITE_LIMIT_CHUNK) {
  486. chunks.push(b);
  487. } else {
  488. for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) {
  489. chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK));
  490. }
  491. }
  492. }
  493. const len = chunks.length;
  494. let i = 0;
  495. /**
  496. * @param {(Error | null)=} err err
  497. */
  498. const batchWrite = (err) => {
  499. // will be handled in "on" error handler
  500. if (err) return;
  501. if (i === len) {
  502. stream.end();
  503. return;
  504. }
  505. // queue up a batch of chunks up to the write limit
  506. // end is exclusive
  507. let end = i;
  508. let sum = chunks[end++].length;
  509. while (end < len) {
  510. sum += chunks[end].length;
  511. if (sum > WRITE_LIMIT_TOTAL) break;
  512. end++;
  513. }
  514. while (i < end - 1) {
  515. stream.write(chunks[i++]);
  516. }
  517. stream.write(chunks[i++], batchWrite);
  518. };
  519. batchWrite();
  520. }
  521. );
  522. if (name) allWrittenFiles.add(file);
  523. };
  524. resolve(
  525. serialize(this, data, false, writeFile, this._hashFunction).then(
  526. async ({ backgroundJob }) => {
  527. await backgroundJob;
  528. // Rename the index file to disallow access during inconsistent file state
  529. await new Promise(
  530. /**
  531. * @param {(value?: undefined) => void} resolve resolve
  532. */
  533. (resolve) => {
  534. this.fs.rename(filename, `${filename}.old`, (_err) => {
  535. resolve();
  536. });
  537. }
  538. );
  539. // update all written files
  540. await Promise.all(
  541. Array.from(
  542. allWrittenFiles,
  543. (file) =>
  544. new Promise(
  545. /**
  546. * @param {(value?: undefined) => void} resolve resolve
  547. * @param {(reason?: Error | null) => void} reject reject
  548. * @returns {void}
  549. */
  550. (resolve, reject) => {
  551. this.fs.rename(`${file}_`, file, (err) => {
  552. if (err) return reject(err);
  553. resolve();
  554. });
  555. }
  556. )
  557. )
  558. );
  559. // As final step automatically update the index file to have a consistent pack again
  560. await new Promise(
  561. /**
  562. * @param {(value?: undefined) => void} resolve resolve
  563. * @returns {void}
  564. */
  565. (resolve) => {
  566. this.fs.rename(`${filename}_`, filename, (err) => {
  567. if (err) return reject(err);
  568. resolve();
  569. });
  570. }
  571. );
  572. return /** @type {true} */ (true);
  573. }
  574. )
  575. );
  576. });
  577. });
  578. }
  579. /**
  580. * @param {SerializedType} data data
  581. * @param {Context} context context object
  582. * @returns {DeserializedType | Promise<DeserializedType>} deserialized data
  583. */
  584. deserialize(data, context) {
  585. const { filename, extension = "" } = context;
  586. /**
  587. * @param {string | boolean} name name
  588. * @returns {Promise<Buffer[]>} result
  589. */
  590. const readFile = (name) =>
  591. new Promise((resolve, reject) => {
  592. const file = name
  593. ? join(this.fs, filename, `../${name}${extension}`)
  594. : filename;
  595. this.fs.stat(file, (err, stats) => {
  596. if (err) {
  597. reject(err);
  598. return;
  599. }
  600. let remaining = /** @type {IStats} */ (stats).size;
  601. /** @type {Buffer | undefined} */
  602. let currentBuffer;
  603. /** @type {number | undefined} */
  604. let currentBufferUsed;
  605. /** @type {Buffer[]} */
  606. const buf = [];
  607. /** @type {import("zlib").Zlib & import("stream").Transform | undefined} */
  608. let decompression;
  609. if (file.endsWith(".gz")) {
  610. decompression = createGunzip({
  611. chunkSize: DECOMPRESSION_CHUNK_SIZE
  612. });
  613. } else if (file.endsWith(".br")) {
  614. decompression = createBrotliDecompress({
  615. chunkSize: DECOMPRESSION_CHUNK_SIZE
  616. });
  617. }
  618. if (decompression) {
  619. /** @typedef {(value: Buffer[] | PromiseLike<Buffer[]>) => void} NewResolve */
  620. /** @typedef {(reason?: Error) => void} NewReject */
  621. /** @type {NewResolve | undefined} */
  622. let newResolve;
  623. /** @type {NewReject | undefined} */
  624. let newReject;
  625. resolve(
  626. Promise.all([
  627. new Promise((rs, rj) => {
  628. newResolve = rs;
  629. newReject = rj;
  630. }),
  631. new Promise(
  632. /**
  633. * @param {(value?: undefined) => void} resolve resolve
  634. * @param {(reason?: Error) => void} reject reject
  635. */
  636. (resolve, reject) => {
  637. decompression.on("data", (chunk) => buf.push(chunk));
  638. decompression.on("end", () => resolve());
  639. decompression.on("error", (err) => reject(err));
  640. }
  641. )
  642. ]).then(() => buf)
  643. );
  644. resolve = /** @type {NewResolve} */ (newResolve);
  645. reject = /** @type {NewReject} */ (newReject);
  646. }
  647. this.fs.open(file, "r", (err, _fd) => {
  648. if (err) {
  649. reject(err);
  650. return;
  651. }
  652. const fd = /** @type {number} */ (_fd);
  653. const read = () => {
  654. if (currentBuffer === undefined) {
  655. currentBuffer = Buffer.allocUnsafeSlow(
  656. Math.min(
  657. constants.MAX_LENGTH,
  658. remaining,
  659. decompression ? DECOMPRESSION_CHUNK_SIZE : Infinity
  660. )
  661. );
  662. currentBufferUsed = 0;
  663. }
  664. let readBuffer = currentBuffer;
  665. let readOffset = /** @type {number} */ (currentBufferUsed);
  666. let readLength =
  667. currentBuffer.length -
  668. /** @type {number} */ (currentBufferUsed);
  669. // values passed to fs.read must be valid int32 values
  670. if (readOffset > 0x7fffffff) {
  671. readBuffer = currentBuffer.slice(readOffset);
  672. readOffset = 0;
  673. }
  674. if (readLength > 0x7fffffff) {
  675. readLength = 0x7fffffff;
  676. }
  677. this.fs.read(
  678. fd,
  679. readBuffer,
  680. readOffset,
  681. readLength,
  682. null,
  683. (err, bytesRead) => {
  684. if (err) {
  685. this.fs.close(fd, () => {
  686. reject(err);
  687. });
  688. return;
  689. }
  690. /** @type {number} */
  691. (currentBufferUsed) += bytesRead;
  692. remaining -= bytesRead;
  693. if (
  694. currentBufferUsed ===
  695. /** @type {Buffer} */
  696. (currentBuffer).length
  697. ) {
  698. if (decompression) {
  699. decompression.write(currentBuffer);
  700. } else {
  701. buf.push(
  702. /** @type {Buffer} */
  703. (currentBuffer)
  704. );
  705. }
  706. currentBuffer = undefined;
  707. if (remaining === 0) {
  708. if (decompression) {
  709. decompression.end();
  710. }
  711. this.fs.close(fd, (err) => {
  712. if (err) {
  713. reject(err);
  714. return;
  715. }
  716. resolve(buf);
  717. });
  718. return;
  719. }
  720. }
  721. read();
  722. }
  723. );
  724. };
  725. read();
  726. });
  727. });
  728. });
  729. return deserialize(this, false, readFile);
  730. }
  731. }
  732. module.exports = FileMiddleware;