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. createBrotliCompress,
  9. createBrotliDecompress,
  10. createGzip,
  11. createGunzip,
  12. constants: zConstants
  13. } = require("zlib");
  14. const { DEFAULTS } = require("../config/defaults");
  15. const createHash = require("../util/createHash");
  16. const { dirname, join, mkdirp } = require("../util/fs");
  17. const memoize = require("../util/memoize");
  18. const SerializerMiddleware = require("./SerializerMiddleware");
  19. /** @typedef {typeof import("../util/Hash")} Hash */
  20. /** @typedef {import("../util/fs").IStats} IStats */
  21. /** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
  22. /** @typedef {import("./SerializerMiddleware").Context} Context */
  23. /** @typedef {import("./types").BufferSerializableType} BufferSerializableType */
  24. /**
  25. * @template LAZY_RESULT
  26. * @typedef {import("./SerializerMiddleware").LazyFunction<LAZY_RESULT>} LazyFunction
  27. */
  28. /*
  29. Format:
  30. File -> Header Section*
  31. Version -> u32
  32. AmountOfSections -> u32
  33. SectionSize -> i32 (if less than zero represents lazy value)
  34. Header -> Version AmountOfSections SectionSize*
  35. Buffer -> n bytes
  36. Section -> Buffer
  37. */
  38. // "wpc" + 1 in little-endian
  39. const VERSION = 0x01637077;
  40. const WRITE_LIMIT_TOTAL = 0x7fff0000;
  41. const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024;
  42. /**
  43. * @param {Buffer[]} buffers buffers
  44. * @param {string | Hash} hashFunction hash function to use
  45. * @returns {string} hash
  46. */
  47. const hashForName = (buffers, hashFunction) => {
  48. const hash = createHash(hashFunction);
  49. for (const buf of buffers) hash.update(buf);
  50. return /** @type {string} */ (hash.digest("hex"));
  51. };
  52. const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  53. const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  54. /** @type {(buffer: Buffer, value: number, offset: number) => void} */
  55. const writeUInt64LE = Buffer.prototype.writeBigUInt64LE
  56. ? (buf, value, offset) => {
  57. buf.writeBigUInt64LE(BigInt(value), offset);
  58. }
  59. : (buf, value, offset) => {
  60. const low = value % 0x100000000;
  61. const high = (value - low) / 0x100000000;
  62. buf.writeUInt32LE(low, offset);
  63. buf.writeUInt32LE(high, offset + 4);
  64. };
  65. /** @type {(buffer: Buffer, offset: number) => void} */
  66. const readUInt64LE = Buffer.prototype.readBigUInt64LE
  67. ? (buf, offset) => Number(buf.readBigUInt64LE(offset))
  68. : (buf, offset) => {
  69. const low = buf.readUInt32LE(offset);
  70. const high = buf.readUInt32LE(offset + 4);
  71. return high * 0x100000000 + low;
  72. };
  73. /** @typedef {Promise<void | void[]>} BackgroundJob */
  74. /**
  75. * @typedef {object} SerializeResult
  76. * @property {string | false} name
  77. * @property {number} size
  78. * @property {BackgroundJob=} backgroundJob
  79. */
  80. /**
  81. * @param {FileMiddleware} middleware this
  82. * @param {BufferSerializableType[] | Promise<BufferSerializableType[]>} data data to be serialized
  83. * @param {string | boolean} name file base name
  84. * @param {(name: string | false, buffers: Buffer[], size: number) => Promise<void>} writeFile writes a file
  85. * @param {string | Hash} hashFunction hash function to use
  86. * @returns {Promise<SerializeResult>} resulting file pointer and promise
  87. */
  88. const serialize = async (
  89. middleware,
  90. data,
  91. name,
  92. writeFile,
  93. hashFunction = DEFAULTS.HASH_FUNCTION
  94. ) => {
  95. /** @type {(Buffer[] | Buffer | Promise<SerializeResult>)[]} */
  96. const processedData = [];
  97. /** @type {WeakMap<SerializeResult, LazyFunction<BufferSerializableType[]>>} */
  98. const resultToLazy = new WeakMap();
  99. /** @type {Buffer[] | undefined} */
  100. let lastBuffers;
  101. for (const item of await data) {
  102. if (typeof item === "function") {
  103. if (!SerializerMiddleware.isLazy(item))
  104. throw new Error("Unexpected function");
  105. if (!SerializerMiddleware.isLazy(item, middleware)) {
  106. throw new Error(
  107. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  108. );
  109. }
  110. lastBuffers = undefined;
  111. const serializedInfo = SerializerMiddleware.getLazySerializedValue(item);
  112. if (serializedInfo) {
  113. if (typeof serializedInfo === "function") {
  114. throw new Error(
  115. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  116. );
  117. } else {
  118. processedData.push(serializedInfo);
  119. }
  120. } else {
  121. const content = item();
  122. if (content) {
  123. const options = SerializerMiddleware.getLazyOptions(
  124. /** @type {LazyFunction<Buffer[]>} */
  125. (item)
  126. );
  127. processedData.push(
  128. serialize(
  129. middleware,
  130. content,
  131. (options && options.name) || true,
  132. writeFile,
  133. hashFunction
  134. ).then(result => {
  135. /** @type {LazyFunction<Buffer[]>} */
  136. (item).options.size = result.size;
  137. resultToLazy.set(
  138. result,
  139. /** @type {LazyFunction<Buffer[]>} */
  140. (item)
  141. );
  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<BufferSerializableType[]>} */
  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. * @param {FileMiddleware} middleware this
  231. * @param {string | false} name filename
  232. * @param {(name: string | false) => Promise<Buffer[]>} readFile read content of a file
  233. * @returns {Promise<BufferSerializableType[]>} deserialized data
  234. */
  235. const deserialize = async (middleware, name, readFile) => {
  236. const contents = await readFile(name);
  237. if (contents.length === 0) throw new Error(`Empty file ${name}`);
  238. let contentsIndex = 0;
  239. let contentItem = contents[0];
  240. let contentItemLength = contentItem.length;
  241. let contentPosition = 0;
  242. if (contentItemLength === 0) throw new Error(`Empty file ${name}`);
  243. const nextContent = () => {
  244. contentsIndex++;
  245. contentItem = contents[contentsIndex];
  246. contentItemLength = contentItem.length;
  247. contentPosition = 0;
  248. };
  249. /**
  250. * @param {number} n number of bytes to ensure
  251. */
  252. const ensureData = n => {
  253. if (contentPosition === contentItemLength) {
  254. nextContent();
  255. }
  256. while (contentItemLength - contentPosition < n) {
  257. const remaining = contentItem.slice(contentPosition);
  258. let lengthFromNext = n - remaining.length;
  259. const buffers = [remaining];
  260. for (let i = contentsIndex + 1; i < contents.length; i++) {
  261. const l = contents[i].length;
  262. if (l > lengthFromNext) {
  263. buffers.push(contents[i].slice(0, lengthFromNext));
  264. contents[i] = contents[i].slice(lengthFromNext);
  265. lengthFromNext = 0;
  266. break;
  267. } else {
  268. buffers.push(contents[i]);
  269. contentsIndex = i;
  270. lengthFromNext -= l;
  271. }
  272. }
  273. if (lengthFromNext > 0) throw new Error("Unexpected end of data");
  274. contentItem = Buffer.concat(buffers, n);
  275. contentItemLength = n;
  276. contentPosition = 0;
  277. }
  278. };
  279. /**
  280. * @returns {number} value value
  281. */
  282. const readUInt32LE = () => {
  283. ensureData(4);
  284. const value = contentItem.readUInt32LE(contentPosition);
  285. contentPosition += 4;
  286. return value;
  287. };
  288. /**
  289. * @returns {number} value value
  290. */
  291. const readInt32LE = () => {
  292. ensureData(4);
  293. const value = contentItem.readInt32LE(contentPosition);
  294. contentPosition += 4;
  295. return value;
  296. };
  297. /**
  298. * @param {number} l length
  299. * @returns {Buffer} buffer
  300. */
  301. const readSlice = l => {
  302. ensureData(l);
  303. if (contentPosition === 0 && contentItemLength === l) {
  304. const result = contentItem;
  305. if (contentsIndex + 1 < contents.length) {
  306. nextContent();
  307. } else {
  308. contentPosition = l;
  309. }
  310. return result;
  311. }
  312. const result = contentItem.slice(contentPosition, contentPosition + l);
  313. contentPosition += l;
  314. // we clone the buffer here to allow the original content to be garbage collected
  315. return l * 2 < contentItem.buffer.byteLength ? Buffer.from(result) : result;
  316. };
  317. const version = readUInt32LE();
  318. if (version !== VERSION) {
  319. throw new Error("Invalid file version");
  320. }
  321. const sectionCount = readUInt32LE();
  322. const lengths = [];
  323. let lastLengthPositive = false;
  324. for (let i = 0; i < sectionCount; i++) {
  325. const value = readInt32LE();
  326. const valuePositive = value >= 0;
  327. if (lastLengthPositive && valuePositive) {
  328. lengths[lengths.length - 1] += value;
  329. } else {
  330. lengths.push(value);
  331. lastLengthPositive = valuePositive;
  332. }
  333. }
  334. /** @type {(Buffer | LazyFunction<BufferSerializableType[]>)[]} */
  335. const result = [];
  336. for (let length of lengths) {
  337. if (length < 0) {
  338. const slice = readSlice(-length);
  339. const size = Number(readUInt64LE(slice, 0));
  340. const nameBuffer = slice.slice(8);
  341. const name = nameBuffer.toString();
  342. /** @type {LazyFunction<BufferSerializableType[]>} */
  343. const lazy = SerializerMiddleware.createLazy(
  344. memoize(() => deserialize(middleware, name, readFile)),
  345. middleware,
  346. { name, size },
  347. slice
  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 {{ filename: string, extension?: string }} FileMiddlewareContext */
  406. /**
  407. * @typedef {BufferSerializableType[]} DeserializedType
  408. * @typedef {true} SerializedType
  409. * @extends {SerializerMiddleware<DeserializedType, SerializedType>}
  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;