@web4/bitdrive
Version:
Bitdrive is a secure, real time distributed file system
194 lines (170 loc) • 6.15 kB
JavaScript
const pathRoot = require('path')
const p = pathRoot.posix || pathRoot
const nanoiterator = require('nanoiterator')
const toStream = require('nanoiterator/to-stream')
const MountableBittrie = require('@web4/mountable-bittrie')
const { Stat } = require('@web4/bitdrive-schemas')
function statIterator (drive, path, opts) {
const stack = []
return nanoiterator({ open, next })
function open (cb) {
drive.ready(err => {
if (err) return cb(err)
stack.unshift({ path: '/', target: null, iterator: drive.db.iterator(path, opts) })
return cb(null)
})
}
function next (cb) {
if (!stack.length) return cb(null, null)
stack[0].iterator.next((err, node) => {
if (err) return cb(err)
if (!node) {
stack.shift()
return next(cb)
}
const trie = node[MountableBittrie.Symbols.TRIE]
const mount = node[MountableBittrie.Symbols.MOUNT]
const innerPath = node[MountableBittrie.Symbols.INNER_PATH]
try {
var st = Stat.decode(node.value)
} catch (err) {
return cb(err)
}
if (st.linkname && (opts.recursive || stack.length === 1)) {
if (p.isAbsolute(st.linkname)) {
var linkPath = st.linkname
} else {
linkPath = p.resolve('/', p.dirname(node.key), st.linkname)
}
return pushLink(prefix(node.key), linkPath, st, (err, linkStat) => {
if (err) return cb(err)
if (linkStat) return cb(null, { stat: st, path: prefix(node.key), trie, mount, innerPath })
return next(cb)
})
}
linkPath = stack[0].path
const resolved = (linkPath === '/') ? node.key : p.join(linkPath, node.key.slice(stack[0].target.length))
return cb(null, { stat: st, path: prefix(resolved), trie, mount, innerPath })
})
}
function pushLink (nodePath, linkPath, stat, cb) {
if (opts && opts.recursive || (nodePath === path)) {
return drive.stat(linkPath, (err, targetStat, _, resolvedLink) => {
if (err && err.errno !== 2) return cb(err)
if (!targetStat) return cb(null)
if (targetStat.isDirectory()) {
stack.unshift({ path: nodePath, target: resolvedLink, iterator: drive.db.iterator(resolvedLink, { gt: true, ...opts }) })
}
return cb(null, stat)
})
}
return process.nextTick(cb, null, stat)
}
}
function mountIterator (drive, opts) {
const loadContent = !!(opts && opts.content)
var ite = null
var first = drive
return nanoiterator({
open (cb) {
drive.ready(function (err) {
if (err) return cb(err)
ite = drive.db.mountIterator(opts)
return cb(null)
})
},
next (cb) {
if (first) return onfirst()
ite.next((err, val) => {
if (err) return cb(err)
if (!val) return cb(null, null)
const contentState = drive._contentStates.cache.get(val.trie.feed)
let mountMetadataFeed = val.trie.feed
if (contentState) return process.nextTick(oncontent, val.path, mountMetadataFeed, contentState)
mountMetadataFeed.has(0, (err, hasMetadataBlock) => {
if (err) return cb(err)
if (!(loadContent || hasMetadataBlock)) return oncontent(val.path, mountMetadataFeed, null)
return drive._getContent(val.trie.feed, (err, contentState) => {
if (err) return cb(err)
return oncontent(val.path, mountMetadataFeed, contentState)
})
})
})
function onfirst () {
first = null
drive.metadata.has(0, (err, hasMetadataBlock) => {
if (err) return cb(err)
if (loadContent || hasMetadataBlock) {
return drive._getContent(drive.db.feed, err => {
if (err) return cb(err)
return cb(null, {
path: '/',
metadata: drive.metadata,
content: drive._contentStates.cache.get(drive.db.feed).feed
})
})
} else {
return cb(null, {
path: '/',
metadata: drive.metadata,
content: null
})
}
})
}
function oncontent (path, metadata, contentState) {
return cb(null, {
path,
metadata,
content: contentState ? contentState.feed : null
})
}
}
})
}
function readdirIterator (drive, name, opts) {
const recursive = !!(opts && opts.recursive)
const noMounts = !!(opts && opts.noMounts)
const includeStats = !!(opts && opts.includeStats)
const ite = statIterator(drive, name, { ...opts, recursive, noMounts, gt: false })
return nanoiterator({ next })
function next (cb) {
ite.next((err, value) => {
if (err) return cb(err)
if (!value) return cb(null, null)
const { path, stat, mount, innerPath } = value
const relativePath = (name === path) ? path : p.relative(name, path)
if (relativePath === name) return next(cb)
if (recursive) {
if (includeStats) return cb(null, { name: relativePath, path, stat, mount, innerPath })
return cb(null, relativePath)
}
const split = relativePath.split('/')
// Note: When doing a non-recursive readdir, we need to create a fake directory Stat (since the returned Stat might be a child file here)
// If this is a problem, one should follow the readdir with the appropriate stat() calls.
if (includeStats) return cb(null, { name: split[0], path, stat: split.length > 1 ? Stat.directory() : stat, mount, innerPath })
return cb(null, split[0])
})
}
}
function createReaddirStream (drive, path, opts) {
return toStream(readdirIterator(drive, path, opts))
}
function createStatStream (drive, path, opts) {
return toStream(statIterator(drive, path, opts))
}
function createMountStream (drive, opts) {
return toStream(mountIterator(drive, opts))
}
function prefix (key) {
if (key.startsWith('/')) return key
return '/' + key
}
module.exports = {
statIterator,
createStatStream,
mountIterator,
createMountStream,
readdirIterator,
createReaddirStream
}