@ipld/car
Version:
Content Addressable aRchive format reader and writer
82 lines (76 loc) • 3.22 kB
JavaScript
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