UNPKG

ipfs-unixfs-importer

Version:

JavaScript implementation of the UnixFs importer used by IPFS

198 lines (165 loc) 5.75 kB
import { UnixFS } from 'ipfs-unixfs' import { persist } from '../utils/persist.js' import { encode, PBLink, PBNode, prepare } from '@ipld/dag-pb' import parallelBatch from 'it-parallel-batch' import * as rawCodec from 'multiformats/codecs/raw' import type { BufferImporter, File, InProgressImportResult, WritableStorage, SingleBlockImportResult, ImporterProgressEvents } from '../index.js' import type { FileLayout, Reducer } from '../layout/index.js' import type { CID, Version } from 'multiformats/cid' import { CustomProgressEvent } from 'progress-events' import type { ProgressOptions, ProgressEvent } from 'progress-events' interface BuildFileBatchOptions { bufferImporter: BufferImporter blockWriteConcurrency: number } async function * buildFileBatch (file: File, blockstore: WritableStorage, options: BuildFileBatchOptions): AsyncGenerator<InProgressImportResult> { let count = -1 let previous: SingleBlockImportResult | undefined for await (const entry of parallelBatch(options.bufferImporter(file, blockstore), options.blockWriteConcurrency)) { count++ if (count === 0) { // cache the first entry if case there aren't any more previous = { ...entry, single: true } continue } else if (count === 1 && (previous != null)) { // we have the second block of a multiple block import so yield the first yield { ...previous, block: undefined, single: undefined } previous = undefined } // yield the second or later block of a multiple block import yield { ...entry, block: undefined } } if (previous != null) { yield previous } } export interface LayoutLeafProgress { /** * The CID of the leaf being written */ cid: CID /** * The path of the file being imported, if one was specified */ path?: string } export type ReducerProgressEvents = ProgressEvent<'unixfs:importer:progress:file:layout', LayoutLeafProgress> interface ReduceOptions extends ProgressOptions<ImporterProgressEvents> { reduceSingleLeafToSelf: boolean cidVersion: Version signal?: AbortSignal } function isSingleBlockImport (result: any): result is SingleBlockImportResult { return result.single === true } const reduce = (file: File, blockstore: WritableStorage, options: ReduceOptions): Reducer => { const reducer: Reducer = async function (leaves) { if (leaves.length === 1 && isSingleBlockImport(leaves[0]) && options.reduceSingleLeafToSelf) { const leaf = leaves[0] let node: Uint8Array | PBNode = leaf.block if (isSingleBlockImport(leaf) && (file.mtime !== undefined || file.mode !== undefined)) { // only one leaf node which is a raw leaf - we have metadata so convert it into a // UnixFS entry otherwise we'll have nowhere to store the metadata leaf.unixfs = new UnixFS({ type: 'file', mtime: file.mtime, mode: file.mode, data: leaf.block }) node = { Data: leaf.unixfs.marshal(), Links: [] } leaf.block = encode(prepare(node)) leaf.cid = await persist(leaf.block, blockstore, { ...options, cidVersion: options.cidVersion }) leaf.size = BigInt(leaf.block.length) } options.onProgress?.(new CustomProgressEvent<LayoutLeafProgress>('unixfs:importer:progress:file:layout', { cid: leaf.cid, path: leaf.originalPath })) return { cid: leaf.cid, path: file.path, unixfs: leaf.unixfs, size: leaf.size, originalPath: leaf.originalPath } } // create a parent node and add all the leaves const f = new UnixFS({ type: 'file', mtime: file.mtime, mode: file.mode }) const links: PBLink[] = leaves .filter(leaf => { if (leaf.cid.code === rawCodec.code && leaf.size > 0) { return true } if ((leaf.unixfs != null) && (leaf.unixfs.data == null) && leaf.unixfs.fileSize() > 0n) { return true } return Boolean(leaf.unixfs?.data?.length) }) .map((leaf) => { if (leaf.cid.code === rawCodec.code) { // node is a leaf buffer f.addBlockSize(leaf.size) return { Name: '', Tsize: Number(leaf.size), Hash: leaf.cid } } if ((leaf.unixfs == null) || (leaf.unixfs.data == null)) { // node is an intermediate node f.addBlockSize(leaf.unixfs?.fileSize() ?? 0n) } else { // node is a unixfs 'file' leaf node f.addBlockSize(BigInt(leaf.unixfs.data.length)) } return { Name: '', Tsize: Number(leaf.size), Hash: leaf.cid } }) const node = { Data: f.marshal(), Links: links } const block = encode(prepare(node)) const cid = await persist(block, blockstore, options) options.onProgress?.(new CustomProgressEvent<LayoutLeafProgress>('unixfs:importer:progress:file:layout', { cid, path: file.originalPath })) return { cid, path: file.path, unixfs: f, size: BigInt(block.length + node.Links.reduce((acc, curr) => acc + (curr.Tsize ?? 0), 0)), originalPath: file.originalPath, block } } return reducer } export interface FileBuilderOptions extends BuildFileBatchOptions, ReduceOptions { layout: FileLayout } export const fileBuilder = async (file: File, block: WritableStorage, options: FileBuilderOptions): Promise<InProgressImportResult> => { return await options.layout(buildFileBatch(file, block, options), reduce(file, block, options)) }