UNPKG

ipfs-unixfs

Version:

JavaScript implementation of IPFS' unixfs (a Unix FileSystem representation on top of a MerkleDAG)

286 lines 8.62 kB
/** * @packageDocumentation * * This module contains the protobuf definition of the UnixFS data structure found at the root of all UnixFS DAGs. * * The UnixFS spec can be found in the [ipfs/specs repository](http://github.com/ipfs/specs) * * @example Create a file composed of several blocks * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'file' }) * data.addBlockSize(256n) // add the size of each block * data.addBlockSize(256n) * // ... * ``` * * @example Create a directory that contains several files * * Creating a directory that contains several files is achieve by creating a unixfs element that identifies a MerkleDAG node as a directory. The links of that MerkleDAG node are the files that are contained in this directory. * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'directory' }) * ``` * * @example Create an unixfs Data element * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ * // ...options * }) * ``` * * `options` is an optional object argument that might include the following keys: * * - type (string, default `file`): The type of UnixFS entry. Can be: * - `raw` * - `directory` * - `file` * - `metadata` * - `symlink` * - `hamt-sharded-directory` * - data (Uint8Array): The optional data field for this node * - blockSizes (Array, default: `[]`): If this is a `file` node that is made up of multiple blocks, `blockSizes` is a list numbers that represent the size of the file chunks stored in each child node. It is used to calculate the total file size. * - mode (Number, default `0644` for files, `0755` for directories/hamt-sharded-directories) file mode * - mtime (`Date`, `{ secs, nsecs }`, `{ Seconds, FractionalNanoseconds }`, `[ secs, nsecs ]`): The modification time of this node * * @example Add and remove a block size to the block size list * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'file' }) * const sizeInBytes = 100n * data.addBlockSize(sizeInBytes) * ``` * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'file' }) * * const index = 0 * data.removeBlockSize(index) * ``` * * @example Get total fileSize * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'file' }) * data.fileSize() // => size in bytes * ``` * * @example Marshal and unmarshal * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const data = new UnixFS({ type: 'file' }) * const marshaled = data.marshal() * const unmarshaled = UnixFS.unmarshal(marshaled) * ``` * * @example Is this UnixFS entry a directory? * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const dir = new UnixFS({ type: 'directory' }) * dir.isDirectory() // true * * const file = new UnixFS({ type: 'file' }) * file.isDirectory() // false * ``` * * @example Has an mtime been set? * * If no modification time has been set, no `mtime` property will be present on the `Data` instance: * * ```TypeScript * import { UnixFS } from 'ipfs-unixfs' * * const file = new UnixFS({ type: 'file' }) * file.mtime // undefined * * Object.prototype.hasOwnProperty.call(file, 'mtime') // false * * const dir = new UnixFS({ type: 'directory', mtime: { secs: 5n } }) * dir.mtime // { secs: Number, nsecs: Number } * ``` */ import { InvalidTypeError, InvalidUnixFSMessageError } from './errors.js'; import { Data as PBData } from './unixfs.js'; const types = { Raw: 'raw', Directory: 'directory', File: 'file', Metadata: 'metadata', Symlink: 'symlink', HAMTShard: 'hamt-sharded-directory' }; const dirTypes = [ 'directory', 'hamt-sharded-directory' ]; const DEFAULT_FILE_MODE = parseInt('0644', 8); const DEFAULT_DIRECTORY_MODE = parseInt('0755', 8); // https://github.com/ipfs/boxo/blob/364c5040ec91ec8e2a61446e9921e9225704c34d/ipld/unixfs/hamt/hamt.go#L778 const MAX_FANOUT = BigInt(1 << 10); class UnixFS { /** * Decode from protobuf https://github.com/ipfs/specs/blob/master/UNIXFS.md */ static unmarshal(marshaled) { const message = PBData.decode(marshaled); if (message.fanout != null && message.fanout > MAX_FANOUT) { throw new InvalidUnixFSMessageError(`Fanout size was too large - ${message.fanout} > ${MAX_FANOUT}`); } const data = new UnixFS({ type: types[message.Type != null ? message.Type.toString() : 'File'], data: message.Data, blockSizes: message.blocksizes, mode: message.mode, mtime: message.mtime != null ? { secs: message.mtime.Seconds ?? 0n, nsecs: message.mtime.FractionalNanoseconds } : undefined, fanout: message.fanout }); // make sure we honour the original mode data._originalMode = message.mode ?? 0; return data; } type; data; blockSizes; hashType; fanout; mtime; _mode; _originalMode; constructor(options = { type: 'file' }) { const { type, data, blockSizes, hashType, fanout, mtime, mode } = options; if (type != null && !Object.values(types).includes(type)) { throw new InvalidTypeError('Type: ' + type + ' is not valid'); } this.type = type ?? 'file'; this.data = data; this.hashType = hashType; this.fanout = fanout; this.blockSizes = blockSizes ?? []; this._originalMode = 0; this.mode = mode; this.mtime = mtime; } set mode(mode) { if (mode == null) { this._mode = this.isDirectory() ? DEFAULT_DIRECTORY_MODE : DEFAULT_FILE_MODE; } else { this._mode = (mode & 0xFFF); } } get mode() { return this._mode; } isDirectory() { return dirTypes.includes(this.type); } addBlockSize(size) { this.blockSizes.push(size); } removeBlockSize(index) { this.blockSizes.splice(index, 1); } /** * Returns `0n` for directories or `data.length + sum(blockSizes)` for everything else */ fileSize() { if (this.isDirectory()) { // dirs don't have file size return 0n; } let sum = 0n; this.blockSizes.forEach((size) => { sum += size; }); if (this.data != null) { sum += BigInt(this.data.length); } return sum; } /** * encode to protobuf Uint8Array */ marshal() { let type; switch (this.type) { case 'raw': type = PBData.DataType.Raw; break; case 'directory': type = PBData.DataType.Directory; break; case 'file': type = PBData.DataType.File; break; case 'metadata': type = PBData.DataType.Metadata; break; case 'symlink': type = PBData.DataType.Symlink; break; case 'hamt-sharded-directory': type = PBData.DataType.HAMTShard; break; default: throw new InvalidTypeError(`Type: ${type} is not valid`); } let data = this.data; if (this.data == null || this.data.length === 0) { data = undefined; } let mode; if (this.mode != null) { mode = (this._originalMode & 0xFFFFF000) | (this.mode ?? 0); if (mode === DEFAULT_FILE_MODE && !this.isDirectory()) { mode = undefined; } if (mode === DEFAULT_DIRECTORY_MODE && this.isDirectory()) { mode = undefined; } } let mtime; if (this.mtime != null) { mtime = { Seconds: this.mtime.secs, FractionalNanoseconds: this.mtime.nsecs }; } return PBData.encode({ Type: type, Data: data, filesize: this.isDirectory() ? undefined : this.fileSize(), blocksizes: this.blockSizes, hashType: this.hashType, fanout: this.fanout, mode, mtime }); } } export { UnixFS }; export * from './errors.js'; //# sourceMappingURL=index.js.map