UNPKG

5.88 kBJavaScriptView Raw
1const pathRoot = require('path')
2const p = pathRoot.posix || pathRoot
3const nanoiterator = require('nanoiterator')
4const toStream = require('nanoiterator/to-stream')
5
6const MountableHypertrie = require('mountable-hypertrie')
7const { Stat } = require('hyperdrive-schemas')
8
9function statIterator (drive, path, opts) {
10 const stack = []
11
12 return nanoiterator({ open, next })
13
14 function open (cb) {
15 drive.ready(err => {
16 if (err) return cb(err)
17 stack.unshift({ path: '/', target: null, iterator: drive.db.iterator(path, opts) })
18 return cb(null)
19 })
20 }
21
22 function next (cb) {
23 if (!stack.length) return cb(null, null)
24 stack[0].iterator.next((err, node) => {
25 if (err) return cb(err)
26 if (!node) {
27 stack.shift()
28 return next(cb)
29 }
30
31 const trie = node[MountableHypertrie.Symbols.TRIE]
32 const mount = node[MountableHypertrie.Symbols.MOUNT]
33 const innerPath = node[MountableHypertrie.Symbols.INNER_PATH]
34
35 try {
36 var st = Stat.decode(node.value)
37 } catch (err) {
38 return cb(err)
39 }
40
41 if (st.linkname && (opts.recursive || stack.length === 1)) {
42 if (p.isAbsolute(st.linkname)) {
43 var linkPath = st.linkname
44 } else {
45 linkPath = p.resolve('/', p.dirname(node.key), st.linkname)
46 }
47 return pushLink(prefix(node.key), linkPath, st, (err, linkStat) => {
48 if (err) return cb(err)
49 if (linkStat) return cb(null, { stat: st, path: prefix(node.key), trie, mount, innerPath })
50 return next(cb)
51 })
52 }
53 linkPath = stack[0].path
54 const resolved = (linkPath === '/') ? node.key : p.join(linkPath, node.key.slice(stack[0].target.length))
55 return cb(null, { stat: st, path: prefix(resolved), trie, mount, innerPath })
56 })
57 }
58
59 function pushLink (nodePath, linkPath, stat, cb) {
60 if (opts && opts.recursive || (nodePath === path)) {
61 return drive.stat(linkPath, (err, targetStat, _, resolvedLink) => {
62 if (err && err.errno !== 2) return cb(err)
63 if (!targetStat) return cb(null)
64 if (targetStat.isDirectory()) {
65 stack.unshift({ path: nodePath, target: resolvedLink, iterator: drive.db.iterator(resolvedLink, { gt: true, ...opts }) })
66 }
67 return cb(null, stat)
68 })
69 }
70 return process.nextTick(cb, null, stat)
71 }
72}
73
74function mountIterator (drive, opts) {
75 const loadContent = !!(opts && opts.content)
76
77 var ite = null
78 var first = drive
79
80 return nanoiterator({
81 open (cb) {
82 drive.ready(function (err) {
83 if (err) return cb(err)
84 ite = drive.db.mountIterator(opts)
85 return cb(null)
86 })
87 },
88 next (cb) {
89 if (first) {
90 first = null
91 if (loadContent || drive.metadata.has(0)) {
92 return drive._getContent(drive.db.feed, err => {
93 if (err) return cb(err)
94 return cb(null, {
95 path: '/',
96 metadata: drive.metadata,
97 content: drive._contentStates.cache.get(drive.db.feed).feed
98 })
99 })
100 } else {
101 return cb(null, {
102 path: '/',
103 metadata: drive.metadata,
104 content: null
105 })
106 }
107 }
108 ite.next((err, val) => {
109 if (err) return cb(err)
110 if (!val) return cb(null, null)
111
112 const contentState = drive._contentStates.cache.get(val.trie.feed)
113 let mountMetadataFeed = val.trie.feed
114 if (contentState) return process.nextTick(oncontent, val.path, mountMetadataFeed, contentState)
115 if (!loadContent && !mountMetadataFeed.has(0)) return oncontent(val.path, mountMetadataFeed, null)
116 return drive._getContent(val.trie.feed, (err, contentState) => {
117 if (err) return cb(err)
118 return oncontent(val.path, mountMetadataFeed, contentState)
119 })
120 })
121
122 function oncontent (path, metadata, contentState) {
123 return cb(null, {
124 path,
125 metadata,
126 content: contentState ? contentState.feed : null
127 })
128 }
129 }
130 })
131}
132
133function readdirIterator (drive, name, opts) {
134 const recursive = !!(opts && opts.recursive)
135 const noMounts = !!(opts && opts.noMounts)
136 const includeStats = !!(opts && opts.includeStats)
137
138 const ite = statIterator(drive, name, { ...opts, recursive, noMounts, gt: false })
139
140 return nanoiterator({ next })
141
142 function next (cb) {
143 ite.next((err, value) => {
144 if (err) return cb(err)
145 if (!value) return cb(null, null)
146 const { path: statPath, stat, mount, innerPath } = value
147 const relativePath = (name === statPath) ? statPath : p.relative(name, statPath)
148 if (relativePath === name) return next(cb)
149 if (recursive) {
150 if (includeStats) return cb(null, { name: relativePath, stat, mount, innerPath })
151 return cb(null, relativePath)
152 }
153 const split = relativePath.split('/')
154 // 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)
155 // If this is a problem, one should follow the readdir with the appropriate stat() calls.
156 if (includeStats) return cb(null, { name: split[0], stat: split.length > 1 ? Stat.directory() : stat, mount, innerPath })
157 return cb(null, split[0])
158 })
159 }
160}
161
162function createReaddirStream (drive, path, opts) {
163 return toStream(readdirIterator(drive, path, opts))
164}
165
166function createStatStream (drive, path, opts) {
167 return toStream(statIterator(drive, path, opts))
168}
169
170function createMountStream (drive, opts) {
171 return toStream(mountIterator(drive, opts))
172}
173
174function prefix (key) {
175 if (key.startsWith('/')) return key
176 return '/' + key
177}
178
179module.exports = {
180 statIterator,
181 createStatStream,
182 mountIterator,
183 createMountStream,
184 readdirIterator,
185 createReaddirStream
186}