UNPKG

@ipld/car

Version:

Content Addressable aRchive format reader and writer

82 lines (76 loc) 3.22 kB
import { readHeader, chunkReader } from './decoder.js' import { createHeader } from './encoder.js' import { fsread, fswrite, hasFS } from './promise-fs-opts.js' import { CarWriter as BrowserCarWriter } from './writer-browser.js' /** * @typedef {import('multiformats/cid').CID} CID * @typedef {import('./api').BlockWriter} BlockWriter * @typedef {import('fs').promises.FileHandle} FileHandle */ /** * @class * @implements {BlockWriter} */ export class CarWriter extends BrowserCarWriter { /** * Update the list of roots in the header of an existing CAR file. The first * argument must be a file descriptor for CAR file that is open in read and * write mode (not append), e.g. `fs.open` or `fs.promises.open` with `'r+'` * mode. * * This operation is an _overwrite_, the total length of the CAR will not be * modified. A rejection will occur if the new header will not be the same * length as the existing header, in which case the CAR will not be modified. * It is the responsibility of the user to ensure that the roots being * replaced encode as the same length as the new roots. * * This function is **only available in Node.js** and not a browser * environment. * * @async * @static * @memberof CarWriter * @param {FileHandle | number} fd - A file descriptor from the * Node.js `fs` module. Either an integer, from `fs.open()` or a `FileHandle` * from `fs.promises.open()`. * @param {CID[]} roots - A new list of roots to replace the existing list in * the CAR header. The new header must take up the same number of bytes as the * existing header, so the roots should collectively be the same byte length * as the existing roots. * @returns {Promise<void>} */ static async updateRootsInFile (fd, roots) { const chunkSize = 256 /** @type {Uint8Array} */ let bytes let offset = 0 /** @type {() => Promise<number>} */ let readChunk if (typeof fd === 'number') { readChunk = async () => (await fsread(fd, bytes, 0, chunkSize, offset)).bytesRead } else if (typeof fd === 'object' && typeof fd.read === 'function') { // FileDescriptor readChunk = async () => (await fd.read(bytes, 0, chunkSize, offset)).bytesRead } else { throw new TypeError('Bad fd') } const fdReader = chunkReader(async () => { bytes = new Uint8Array(chunkSize) // need a new chunk each time, can't reuse old const read = await readChunk() offset += read /* eslint no-warning-comments: 0 */ // TODO: test header > 256 bytes return read < chunkSize ? bytes.subarray(0, read) : bytes }) await readHeader(fdReader) const newHeader = createHeader(roots) if (fdReader.pos !== newHeader.length) { throw new Error(`updateRoots() can only overwrite a header of the same length (old header is ${fdReader.pos} bytes, new header is ${newHeader.length} bytes)`) } if (typeof fd === 'number') { await fswrite(fd, newHeader, 0, newHeader.length, 0) } else if (typeof fd === 'object' && typeof fd.read === 'function') { // FileDescriptor await fd.write(newHeader, 0, newHeader.length, 0) } } } export const __browser = !hasFS