1 | const pathRoot = require('path')
|
2 | const p = pathRoot.posix || pathRoot
|
3 | const nanoiterator = require('nanoiterator')
|
4 | const toStream = require('nanoiterator/to-stream')
|
5 |
|
6 | const MountableHypertrie = require('mountable-hypertrie')
|
7 | const { Stat } = require('hyperdrive-schemas')
|
8 |
|
9 | function 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 |
|
74 | function 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 |
|
133 | function 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 |
|
155 |
|
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 |
|
162 | function createReaddirStream (drive, path, opts) {
|
163 | return toStream(readdirIterator(drive, path, opts))
|
164 | }
|
165 |
|
166 | function createStatStream (drive, path, opts) {
|
167 | return toStream(statIterator(drive, path, opts))
|
168 | }
|
169 |
|
170 | function createMountStream (drive, opts) {
|
171 | return toStream(mountIterator(drive, opts))
|
172 | }
|
173 |
|
174 | function prefix (key) {
|
175 | if (key.startsWith('/')) return key
|
176 | return '/' + key
|
177 | }
|
178 |
|
179 | module.exports = {
|
180 | statIterator,
|
181 | createStatStream,
|
182 | mountIterator,
|
183 | createMountStream,
|
184 | readdirIterator,
|
185 | createReaddirStream
|
186 | }
|