ipfs-core
Version:
JavaScript implementation of the IPFS specification
223 lines (184 loc) • 5.55 kB
JavaScript
import { importer } from 'ipfs-unixfs-importer'
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-multiple'
import { parseChunkerString } from './utils.js'
import { pipe } from 'it-pipe'
import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'
import mergeOpts from 'merge-options'
const mergeOptions = mergeOpts.bind({ ignoreUndefined: true })
/**
* @typedef {import('multiformats/cid').CID} CID
* @typedef {import('ipfs-unixfs-importer').ImportResult} ImportResult
* @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher
* @typedef {import('ipfs-core-utils/multihashes').Multihashes} Multihashes
*/
/**
* @template T
*
* @typedef {import('it-stream-types').Source<T>} Source<T>
*/
/**
* @typedef {object} Context
* @property {import('ipfs-repo').IPFSRepo} repo
* @property {import('../../types').Preload} preload
* @property {Multihashes} hashers
* @property {import('ipfs-core-types/src/root').ShardingOptions} [options]
* @param {Context} context
*/
export function createAddAll ({ repo, preload, hashers, options }) {
const isShardingEnabled = options && options.sharding
/**
* @type {import('ipfs-core-types/src/root').API<{}>["addAll"]}
*/
async function * addAll (source, options = {}) {
const opts = mergeOptions({
shardSplitThreshold: isShardingEnabled ? 1000 : Infinity,
strategy: 'balanced'
}, options, {
...parseChunkerString(options.chunker)
})
// CID v0 is for multihashes encoded with sha2-256
if (opts.hashAlg && opts.hashAlg !== 'sha2-256' && opts.cidVersion !== 1) {
opts.cidVersion = 1
}
if (opts.trickle) {
opts.strategy = 'trickle'
}
if (opts.strategy === 'trickle') {
opts.leafType = 'raw'
opts.reduceSingleLeafToSelf = false
}
if (opts.cidVersion > 0 && opts.rawLeaves === undefined) {
// if the cid version is 1 or above, use raw leaves as this is
// what go does.
opts.rawLeaves = true
}
if (opts.hashAlg !== undefined && opts.rawLeaves === undefined) {
// if a non-default hash alg has been specified, use raw leaves as this is
// what go does.
opts.rawLeaves = true
}
delete opts.trickle
/** @type {Record<string, number>} */
const totals = {}
if (opts.progress) {
const prog = opts.progress
/**
* @param {number} bytes
* @param {string} path
*/
opts.progress = (bytes, path) => {
if (!totals[path]) {
totals[path] = 0
}
totals[path] += bytes
prog(totals[path], path)
}
}
/** @type {MultihashHasher | undefined} */
let hasher
if (opts.hashAlg != null) {
hasher = await hashers.getHasher(opts.hashAlg)
}
const iterator = pipe(
normaliseInput(source),
/**
* @param {Source<import('ipfs-unixfs-importer').ImportCandidate>} source
*/
source => importer(source, repo.blocks, {
...opts,
hasher,
pin: false
}),
transformFile(opts),
preloadFile(preload, opts),
pinFile(repo, opts)
)
const releaseLock = await repo.gcLock.readLock()
try {
for await (const added of iterator) {
const path = added.path ?? added.cid.toString()
// do not keep file totals around forever
delete totals[path]
yield {
...added,
path
}
}
} finally {
releaseLock()
}
}
return withTimeoutOption(addAll)
}
/**
* @param {import('ipfs-core-types/src/root').AddAllOptions} opts
*/
function transformFile (opts) {
/**
* @param {Source<ImportResult>} source
*/
async function * transformFile (source) {
for await (const file of source) {
let cid = file.cid
if (opts.cidVersion === 1) {
cid = cid.toV1()
}
let path = file.path ? file.path : cid.toString()
if (opts.wrapWithDirectory && !file.path) {
path = ''
}
yield {
path,
cid: cid,
size: file.size,
mode: file.unixfs && file.unixfs.mode,
mtime: file.unixfs && file.unixfs.mtime
}
}
}
return transformFile
}
/**
* @param {(cid: CID) => void} preload
* @param {import('ipfs-core-types/src/root').AddAllOptions} opts
*/
function preloadFile (preload, opts) {
/**
* @param {Source<ImportResult>} source
*/
async function * maybePreloadFile (source) {
for await (const file of source) {
const isRootFile = !file.path || opts.wrapWithDirectory
? file.path === ''
: !file.path.includes('/')
const shouldPreload = isRootFile && !opts.onlyHash && opts.preload !== false
if (shouldPreload) {
preload(file.cid)
}
yield file
}
}
return maybePreloadFile
}
/**
* @param {import('ipfs-repo').IPFSRepo} repo
* @param {import('ipfs-core-types/src/root').AddAllOptions} opts
*/
function pinFile (repo, opts) {
/**
* @param {Source<ImportResult>} source
*/
async function * maybePinFile (source) {
for await (const file of source) {
// Pin a file if it is the root dir of a recursive add or the single file
// of a direct add.
const isRootDir = !(file.path && file.path.includes('/'))
const shouldPin = (opts.pin == null ? true : opts.pin) && isRootDir && !opts.onlyHash
if (shouldPin) {
await repo.pins.pinRecursively(file.cid)
}
yield file
}
}
return maybePinFile
}