@helia/unixfs 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.min.js +1 -1
- package/dist/src/commands/utils/add-link.d.ts +0 -7
- package/dist/src/commands/utils/add-link.d.ts.map +1 -1
- package/dist/src/commands/utils/add-link.js +97 -128
- package/dist/src/commands/utils/add-link.js.map +1 -1
- package/dist/src/commands/utils/consumable-hash.d.ts +25 -0
- package/dist/src/commands/utils/consumable-hash.d.ts.map +1 -0
- package/dist/src/commands/utils/consumable-hash.js +136 -0
- package/dist/src/commands/utils/consumable-hash.js.map +1 -0
- package/dist/src/commands/utils/hamt-utils.d.ts +17 -20
- package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -1
- package/dist/src/commands/utils/hamt-utils.js +105 -163
- package/dist/src/commands/utils/hamt-utils.js.map +1 -1
- package/dist/src/commands/utils/remove-link.d.ts.map +1 -1
- package/dist/src/commands/utils/remove-link.js +35 -91
- package/dist/src/commands/utils/remove-link.js.map +1 -1
- package/dist/src/commands/utils/resolve.d.ts.map +1 -1
- package/dist/src/commands/utils/resolve.js +5 -2
- package/dist/src/commands/utils/resolve.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/utils/add-link.ts +107 -170
- package/src/commands/utils/consumable-hash.ts +174 -0
- package/src/commands/utils/hamt-utils.ts +138 -224
- package/src/commands/utils/remove-link.ts +38 -110
- package/src/commands/utils/resolve.ts +8 -3
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import * as dagPB from '@ipld/dag-pb'
|
|
2
|
-
import {
|
|
3
|
-
Bucket,
|
|
4
|
-
createHAMT
|
|
5
|
-
} from 'hamt-sharding'
|
|
6
2
|
import { DirSharded } from './dir-sharded.js'
|
|
7
3
|
import { logger } from '@libp2p/logger'
|
|
8
4
|
import { UnixFS } from 'ipfs-unixfs'
|
|
@@ -13,282 +9,200 @@ import {
|
|
|
13
9
|
hamtHashFn,
|
|
14
10
|
hamtBucketBits
|
|
15
11
|
} from './hamt-constants.js'
|
|
16
|
-
import type { PBLink, PBNode } from '@ipld/dag-pb/interface'
|
|
17
12
|
import type { Blockstore } from 'interface-blockstore'
|
|
18
13
|
import type { Mtime } from 'ipfs-unixfs'
|
|
19
|
-
import type { Directory } from './cid-to-directory.js'
|
|
20
14
|
import type { AbortOptions } from '@libp2p/interfaces'
|
|
21
15
|
import type { ImportResult } from 'ipfs-unixfs-importer'
|
|
22
16
|
import { persist } from './persist.js'
|
|
17
|
+
import { InfiniteHash, wrapHash } from './consumable-hash.js'
|
|
18
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
19
|
+
// @ts-expect-error no types
|
|
20
|
+
import SparseArray from 'sparse-array'
|
|
21
|
+
import type { PersistOptions } from './persist.js'
|
|
23
22
|
|
|
24
23
|
const log = logger('helia:unixfs:commands:utils:hamt-utils')
|
|
25
24
|
|
|
26
|
-
export interface UpdateHamtResult {
|
|
27
|
-
node: PBNode
|
|
28
|
-
cid: CID
|
|
29
|
-
size: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
export interface UpdateHamtDirectoryOptions extends AbortOptions {
|
|
33
26
|
cidVersion: Version
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
export const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
export const toPrefix = (position: number): string => {
|
|
30
|
+
return position
|
|
31
|
+
.toString(16)
|
|
32
|
+
.toUpperCase()
|
|
33
|
+
.padStart(2, '0')
|
|
34
|
+
.substring(0, 2)
|
|
35
|
+
}
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
fanout: BigInt(bucket.tableSize()),
|
|
47
|
-
hashType: hamtHashCode,
|
|
48
|
-
mode: node.mode,
|
|
49
|
-
mtime: node.mtime
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const updatedPbNode = {
|
|
53
|
-
Data: dir.marshal(),
|
|
54
|
-
Links: pbNode.Links
|
|
55
|
-
}
|
|
37
|
+
export interface CreateShardOptions {
|
|
38
|
+
mtime?: Mtime
|
|
39
|
+
mode?: number
|
|
40
|
+
cidVersion: Version
|
|
41
|
+
}
|
|
56
42
|
|
|
57
|
-
|
|
58
|
-
const
|
|
43
|
+
export const createShard = async (blockstore: Blockstore, contents: Array<{ name: string, size: bigint, cid: CID }>, options: CreateShardOptions): Promise<ImportResult> => {
|
|
44
|
+
const shard = new DirSharded({
|
|
45
|
+
root: true,
|
|
46
|
+
dir: true,
|
|
47
|
+
parent: undefined,
|
|
48
|
+
parentKey: undefined,
|
|
49
|
+
path: '',
|
|
50
|
+
dirty: true,
|
|
51
|
+
flat: false,
|
|
52
|
+
mtime: options.mtime,
|
|
53
|
+
mode: options.mode
|
|
54
|
+
}, options)
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
for (let i = 0; i < contents.length; i++) {
|
|
57
|
+
await shard._bucket.put(contents[i].name, {
|
|
58
|
+
size: contents[i].size,
|
|
59
|
+
cid: contents[i].cid
|
|
60
|
+
})
|
|
64
61
|
}
|
|
65
|
-
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
// recreate this level of the HAMT
|
|
69
|
-
const bucket = new Bucket({
|
|
70
|
-
hash: rootBucket._options.hash,
|
|
71
|
-
bits: rootBucket._options.bits
|
|
72
|
-
}, parentBucket, positionAtParent)
|
|
73
|
-
parentBucket._putObjectAt(positionAtParent, bucket)
|
|
63
|
+
const res = await last(shard.flush(blockstore))
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
if (res == null) {
|
|
66
|
+
throw new Error('Flushing shard yielded no result')
|
|
67
|
+
}
|
|
76
68
|
|
|
77
|
-
return
|
|
69
|
+
return res
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// populate sub bucket but do not recurse as we do not want to load the whole shard
|
|
87
|
-
await Promise.all(
|
|
88
|
-
links.map(async link => {
|
|
89
|
-
const linkName = (link.Name ?? '')
|
|
90
|
-
|
|
91
|
-
if (linkName.length === 2) {
|
|
92
|
-
const pos = parseInt(linkName, 16)
|
|
93
|
-
const subBucket = new Bucket({
|
|
94
|
-
hash: bucket._options.hash,
|
|
95
|
-
bits: bucket._options.bits
|
|
96
|
-
}, bucket, pos)
|
|
97
|
-
|
|
98
|
-
bucket._putObjectAt(pos, subBucket)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
await bucket.put(linkName.substring(2), {
|
|
102
|
-
size: link.Tsize,
|
|
103
|
-
cid: link.Hash
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
return bucket
|
|
72
|
+
export interface HAMTPath {
|
|
73
|
+
prefix: string
|
|
74
|
+
children: SparseArray
|
|
75
|
+
node: dagPB.PBNode
|
|
109
76
|
}
|
|
110
77
|
|
|
111
|
-
export const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const linkName = (link.Name ?? '')
|
|
78
|
+
export const updateShardedDirectory = async (path: HAMTPath[], blockstore: Blockstore, options: PersistOptions): Promise<{ cid: CID, node: dagPB.PBNode }> => {
|
|
79
|
+
// persist any metadata on the shard root
|
|
80
|
+
const shardRoot = UnixFS.unmarshal(path[0].node.Data ?? new Uint8Array(0))
|
|
115
81
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const pos = parseInt(linkName, 16)
|
|
119
|
-
const block = await blockstore.get(link.Hash, options)
|
|
120
|
-
const node = dagPB.decode(block)
|
|
82
|
+
// this is always the same
|
|
83
|
+
const fanout = BigInt(Math.pow(2, hamtBucketBits))
|
|
121
84
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
bits: rootBucket._options.bits
|
|
125
|
-
}, bucket, pos)
|
|
126
|
-
bucket._putObjectAt(pos, subBucket)
|
|
127
|
-
|
|
128
|
-
await addLinksToHamtBucket(blockstore, node.Links, subBucket, rootBucket, options)
|
|
129
|
-
}
|
|
85
|
+
// start from the leaf and ascend to the root
|
|
86
|
+
path.reverse()
|
|
130
87
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
cid: link.Hash
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
)
|
|
137
|
-
}
|
|
88
|
+
let cid: CID | undefined
|
|
89
|
+
let node: dagPB.PBNode | undefined
|
|
138
90
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.toUpperCase()
|
|
143
|
-
.padStart(2, '0')
|
|
144
|
-
.substring(0, 2)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface HamtPathSegment {
|
|
148
|
-
bucket?: Bucket<any>
|
|
149
|
-
prefix?: string
|
|
150
|
-
node?: PBNode
|
|
151
|
-
cid?: CID
|
|
152
|
-
size?: number
|
|
153
|
-
}
|
|
91
|
+
for (let i = 0; i < path.length; i++) {
|
|
92
|
+
const isRoot = i === path.length - 1
|
|
93
|
+
const segment = path[i]
|
|
154
94
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let currentBucket = position.bucket
|
|
164
|
-
|
|
165
|
-
while (currentBucket !== rootBucket) {
|
|
166
|
-
path.push({
|
|
167
|
-
bucket: currentBucket,
|
|
168
|
-
prefix: toPrefix(currentBucket._posAtParent)
|
|
95
|
+
// go-ipfs uses little endian, that's why we have to
|
|
96
|
+
// reverse the bit field before storing it
|
|
97
|
+
const data = Uint8Array.from(segment.children.bitField().reverse())
|
|
98
|
+
const dir = new UnixFS({
|
|
99
|
+
type: 'hamt-sharded-directory',
|
|
100
|
+
data,
|
|
101
|
+
fanout,
|
|
102
|
+
hashType: hamtHashCode
|
|
169
103
|
})
|
|
170
104
|
|
|
171
|
-
if (
|
|
172
|
-
|
|
105
|
+
if (isRoot) {
|
|
106
|
+
dir.mtime = shardRoot.mtime
|
|
107
|
+
dir.mode = shardRoot.mode
|
|
173
108
|
}
|
|
174
109
|
|
|
175
|
-
|
|
176
|
-
|
|
110
|
+
node = {
|
|
111
|
+
Data: dir.marshal(),
|
|
112
|
+
Links: segment.node.Links
|
|
113
|
+
}
|
|
177
114
|
|
|
178
|
-
|
|
179
|
-
path.push({
|
|
180
|
-
bucket: rootBucket,
|
|
181
|
-
node: root.node
|
|
182
|
-
})
|
|
115
|
+
const block = dagPB.encode(dagPB.prepare(node))
|
|
183
116
|
|
|
184
|
-
|
|
117
|
+
cid = await persist(block, blockstore, options)
|
|
185
118
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const previousSegment = path[i - 1]
|
|
119
|
+
if (!isRoot) {
|
|
120
|
+
// update link in parent sub-shard
|
|
121
|
+
const nextSegment = path[i + 1]
|
|
190
122
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
123
|
+
if (nextSegment == null) {
|
|
124
|
+
throw new Error('Was not operating on shard root but also had no parent?')
|
|
125
|
+
}
|
|
194
126
|
|
|
195
|
-
|
|
196
|
-
const link = previousSegment.node.Links
|
|
197
|
-
.filter(link => (link.Name ?? '').substring(0, 2) === segment.prefix)
|
|
198
|
-
.pop()
|
|
127
|
+
log('updating link in parent sub-shard with prefix %s', nextSegment.prefix)
|
|
199
128
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
129
|
+
nextSegment.node.Links = nextSegment.node.Links.filter(l => l.Name !== nextSegment.prefix)
|
|
130
|
+
nextSegment.node.Links.push({
|
|
131
|
+
Name: nextSegment.prefix,
|
|
132
|
+
Hash: cid,
|
|
133
|
+
Tsize: segment.node.Links.reduce((acc, curr) => acc + (curr.Tsize ?? 0), block.byteLength)
|
|
134
|
+
})
|
|
206
135
|
}
|
|
136
|
+
}
|
|
207
137
|
|
|
208
|
-
|
|
138
|
+
if (cid == null || node == null) {
|
|
139
|
+
throw new Error('Noting persisted')
|
|
140
|
+
}
|
|
209
141
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
log(`Link ${segment.prefix}${name} will be replaced`)
|
|
213
|
-
// file already existed, file will be added to the current bucket
|
|
214
|
-
// return path
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
142
|
+
return { cid, node }
|
|
143
|
+
}
|
|
217
144
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
145
|
+
export const recreateShardedDirectory = async (cid: CID, fileName: string, blockstore: Blockstore, options: AbortOptions): Promise<{ path: HAMTPath[], hash: InfiniteHash }> => {
|
|
146
|
+
const wrapped = wrapHash(hamtHashFn)
|
|
147
|
+
const hash = wrapped(uint8ArrayFromString(fileName))
|
|
148
|
+
const path: HAMTPath[] = []
|
|
149
|
+
|
|
150
|
+
// descend the HAMT, loading each layer as we head towards the target child
|
|
151
|
+
while (true) {
|
|
152
|
+
const block = await blockstore.get(cid, options)
|
|
221
153
|
const node = dagPB.decode(block)
|
|
154
|
+
const children = new SparseArray()
|
|
155
|
+
const index = await hash.take(hamtBucketBits)
|
|
156
|
+
const prefix = toPrefix(index)
|
|
222
157
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
158
|
+
path.push({
|
|
159
|
+
prefix,
|
|
160
|
+
children,
|
|
161
|
+
node
|
|
162
|
+
})
|
|
226
163
|
|
|
227
|
-
|
|
228
|
-
throw new Error('Shard was invalid')
|
|
229
|
-
}
|
|
164
|
+
let childLink: dagPB.PBLink | undefined
|
|
230
165
|
|
|
231
|
-
|
|
232
|
-
|
|
166
|
+
// update sparsearray child layout - the bitfield is used as the data field for the
|
|
167
|
+
// intermediate DAG node so this is required to generate consistent hashes
|
|
168
|
+
for (const link of node.Links) {
|
|
169
|
+
const linkName = link.Name ?? ''
|
|
233
170
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
prefix: toPrefix(position.pos),
|
|
238
|
-
node
|
|
239
|
-
})
|
|
171
|
+
if (linkName.length < 2) {
|
|
172
|
+
throw new Error('Invalid HAMT - link name was too short')
|
|
173
|
+
}
|
|
240
174
|
|
|
241
|
-
|
|
242
|
-
|
|
175
|
+
const position = parseInt(linkName.substring(0, 2), 16)
|
|
176
|
+
children.set(position, true)
|
|
243
177
|
|
|
244
|
-
|
|
245
|
-
|
|
178
|
+
// we found the child we are looking for
|
|
179
|
+
if (linkName.startsWith(prefix)) {
|
|
180
|
+
childLink = link
|
|
181
|
+
}
|
|
246
182
|
}
|
|
247
183
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await rootBucket.put(name, true)
|
|
255
|
-
|
|
256
|
-
path.reverse()
|
|
257
|
-
|
|
258
|
-
return path
|
|
259
|
-
}
|
|
184
|
+
if (childLink == null) {
|
|
185
|
+
log('no link found with prefix %s for %s', prefix, fileName)
|
|
186
|
+
// hash.untake(hamtBucketBits)
|
|
187
|
+
break
|
|
188
|
+
}
|
|
260
189
|
|
|
261
|
-
|
|
262
|
-
mtime?: Mtime
|
|
263
|
-
mode?: number
|
|
264
|
-
cidVersion: Version
|
|
265
|
-
}
|
|
190
|
+
const linkName = childLink.Name ?? ''
|
|
266
191
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
dir: true,
|
|
271
|
-
parent: undefined,
|
|
272
|
-
parentKey: undefined,
|
|
273
|
-
path: '',
|
|
274
|
-
dirty: true,
|
|
275
|
-
flat: false,
|
|
276
|
-
mtime: options.mtime,
|
|
277
|
-
mode: options.mode
|
|
278
|
-
}, options)
|
|
192
|
+
if (linkName.length < 2) {
|
|
193
|
+
throw new Error('Invalid HAMT - link name was too short')
|
|
194
|
+
}
|
|
279
195
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
})
|
|
285
|
-
}
|
|
196
|
+
if (linkName.length === 2) {
|
|
197
|
+
// found sub-shard
|
|
198
|
+
cid = childLink.Hash
|
|
199
|
+
log('descend into sub-shard with prefix %s', linkName)
|
|
286
200
|
|
|
287
|
-
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
288
203
|
|
|
289
|
-
|
|
290
|
-
throw new Error('Flushing shard yielded no result')
|
|
204
|
+
break
|
|
291
205
|
}
|
|
292
206
|
|
|
293
|
-
return
|
|
207
|
+
return { path, hash }
|
|
294
208
|
}
|
|
@@ -4,10 +4,9 @@ import type { CID, Version } from 'multiformats/cid'
|
|
|
4
4
|
import { logger } from '@libp2p/logger'
|
|
5
5
|
import { UnixFS } from 'ipfs-unixfs'
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
UpdateHamtDirectoryOptions
|
|
7
|
+
recreateShardedDirectory,
|
|
8
|
+
UpdateHamtDirectoryOptions,
|
|
9
|
+
updateShardedDirectory
|
|
11
10
|
} from './hamt-utils.js'
|
|
12
11
|
import type { PBNode } from '@ipld/dag-pb'
|
|
13
12
|
import type { Blockstore } from 'interface-blockstore'
|
|
@@ -77,131 +76,60 @@ const removeFromDirectory = async (parent: Directory, name: string, blockstore:
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
const removeFromShardedDirectory = async (parent: Directory, name: string, blockstore: Blockstore, options: UpdateHamtDirectoryOptions): Promise<{ cid: CID, node: PBNode }> => {
|
|
80
|
-
const path = await
|
|
79
|
+
const { path } = await recreateShardedDirectory(parent.cid, name, blockstore, options)
|
|
80
|
+
const finalSegment = path[path.length - 1]
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (rootBucket == null) {
|
|
86
|
-
throw new Error('Could not generate HAMT path')
|
|
82
|
+
if (finalSegment == null) {
|
|
83
|
+
throw new Error('Invalid HAMT, could not generate path')
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// update all nodes in the shard path
|
|
92
|
-
return await updateShard(path, name, blockstore, options)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The `path` param is a list of HAMT path segments starting with th
|
|
97
|
-
*/
|
|
98
|
-
const updateShard = async (path: HamtPathSegment[], name: string, blockstore: Blockstore, options: UpdateHamtDirectoryOptions): Promise<{ node: PBNode, cid: CID }> => {
|
|
99
|
-
const fileName = `${path[0].prefix}${name}`
|
|
100
|
-
|
|
101
|
-
// skip first path segment as it is the file to remove
|
|
102
|
-
for (let i = 1; i < path.length; i++) {
|
|
103
|
-
const lastPrefix = path[i - 1].prefix
|
|
104
|
-
const segment = path[i]
|
|
105
|
-
|
|
106
|
-
if (segment.node == null) {
|
|
107
|
-
throw new InvalidParametersError('Path segment had no associated PBNode')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const link = segment.node.Links
|
|
111
|
-
.find(link => (link.Name ?? '').substring(0, 2) === lastPrefix)
|
|
86
|
+
const linkName = finalSegment.node.Links.filter(l => (l.Name ?? '').substring(2) === name).map(l => l.Name).pop()
|
|
112
87
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
if (linkName == null) {
|
|
89
|
+
throw new Error('File not found')
|
|
90
|
+
}
|
|
116
91
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
92
|
+
const prefix = linkName.substring(0, 2)
|
|
93
|
+
const index = parseInt(prefix, 16)
|
|
120
94
|
|
|
121
|
-
|
|
122
|
-
|
|
95
|
+
// remove the file from the shard
|
|
96
|
+
finalSegment.node.Links = finalSegment.node.Links.filter(link => link.Name !== linkName)
|
|
97
|
+
finalSegment.children.unset(index)
|
|
123
98
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
throw new Error('Segment bucket was missing')
|
|
99
|
+
if (finalSegment.node.Links.length === 1) {
|
|
100
|
+
// replace the subshard with the last remaining file in the parent
|
|
101
|
+
while (true) {
|
|
102
|
+
if (path.length === 1) {
|
|
103
|
+
break
|
|
130
104
|
}
|
|
131
105
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const result = await updateHamtDirectory({
|
|
135
|
-
Data: segment.node.Data,
|
|
136
|
-
Links: links
|
|
137
|
-
}, blockstore, segment.bucket, options)
|
|
106
|
+
const segment = path[path.length - 1]
|
|
138
107
|
|
|
139
|
-
segment.node
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (link.Name === lastPrefix) {
|
|
145
|
-
log(`updating subshard with prefix ${lastPrefix}`)
|
|
146
|
-
|
|
147
|
-
const lastSegment = path[i - 1]
|
|
148
|
-
|
|
149
|
-
if (lastSegment.node?.Links.length === 1) {
|
|
150
|
-
log(`removing subshard for ${lastPrefix}`)
|
|
151
|
-
|
|
152
|
-
// convert subshard back to normal file entry
|
|
153
|
-
const link = lastSegment.node.Links[0]
|
|
154
|
-
link.Name = `${lastPrefix}${(link.Name ?? '').substring(2)}`
|
|
155
|
-
|
|
156
|
-
// remove existing prefix
|
|
157
|
-
segment.node.Links = segment.node.Links.filter((link) => {
|
|
158
|
-
return link.Name !== lastPrefix
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// add new child
|
|
162
|
-
segment.node.Links.push(link)
|
|
163
|
-
} else {
|
|
164
|
-
// replace subshard entry
|
|
165
|
-
log(`replacing subshard for ${lastPrefix}`)
|
|
108
|
+
if (segment == null || segment.node.Links.length > 1) {
|
|
109
|
+
break
|
|
110
|
+
}
|
|
166
111
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return link.Name !== lastPrefix
|
|
170
|
-
})
|
|
112
|
+
// remove final segment
|
|
113
|
+
path.pop()
|
|
171
114
|
|
|
172
|
-
|
|
173
|
-
throw new Error('Did not persist previous segment')
|
|
174
|
-
}
|
|
115
|
+
const nextSegment = path[path.length - 1]
|
|
175
116
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Name: lastPrefix,
|
|
179
|
-
Hash: lastSegment.cid,
|
|
180
|
-
Tsize: lastSegment.size
|
|
181
|
-
})
|
|
117
|
+
if (nextSegment == null) {
|
|
118
|
+
break
|
|
182
119
|
}
|
|
183
120
|
|
|
184
|
-
|
|
185
|
-
throw new Error('Segment bucket was missing')
|
|
186
|
-
}
|
|
121
|
+
const link = segment.node.Links[0]
|
|
187
122
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
123
|
+
nextSegment.node.Links = nextSegment.node.Links.filter(l => !(l.Name ?? '').startsWith(nextSegment.prefix))
|
|
124
|
+
nextSegment.node.Links.push({
|
|
125
|
+
Hash: link.Hash,
|
|
126
|
+
Name: `${nextSegment.prefix}${(link.Name ?? '').substring(2)}`,
|
|
127
|
+
Tsize: link.Tsize
|
|
128
|
+
})
|
|
192
129
|
}
|
|
193
130
|
}
|
|
194
131
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (rootSegment == null || rootSegment.cid == null || rootSegment.node == null) {
|
|
198
|
-
throw new InvalidParametersError('Failed to update shard')
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
cid: rootSegment.cid,
|
|
203
|
-
node: rootSegment.node
|
|
204
|
-
}
|
|
132
|
+
return await updateShardedDirectory(path, blockstore, options)
|
|
205
133
|
}
|
|
206
134
|
|
|
207
135
|
const convertToFlatDirectory = async (parent: Directory, blockstore: Blockstore, options: RmLinkOptions): Promise<RemoveLinkResult> => {
|
|
@@ -8,7 +8,7 @@ import { cidToDirectory } from './cid-to-directory.js'
|
|
|
8
8
|
import { cidToPBLink } from './cid-to-pblink.js'
|
|
9
9
|
import type { Blockstore } from 'interface-blockstore'
|
|
10
10
|
|
|
11
|
-
const log = logger('helia:unixfs:components:utils:
|
|
11
|
+
const log = logger('helia:unixfs:components:utils:resolve')
|
|
12
12
|
|
|
13
13
|
export interface Segment {
|
|
14
14
|
name: string
|
|
@@ -33,12 +33,12 @@ export interface ResolveResult {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export async function resolve (cid: CID, path: string | undefined, blockstore: Blockstore, options: AbortOptions): Promise<ResolveResult> {
|
|
36
|
-
log('resolve "%s" under %c', path, cid)
|
|
37
|
-
|
|
38
36
|
if (path == null || path === '') {
|
|
39
37
|
return { cid }
|
|
40
38
|
}
|
|
41
39
|
|
|
40
|
+
log('resolve "%s" under %c', path, cid)
|
|
41
|
+
|
|
42
42
|
const parts = path.split('/').filter(Boolean)
|
|
43
43
|
const segments: Segment[] = [{
|
|
44
44
|
name: '',
|
|
@@ -50,6 +50,8 @@ export async function resolve (cid: CID, path: string | undefined, blockstore: B
|
|
|
50
50
|
const part = parts[i]
|
|
51
51
|
const result = await exporter(cid, blockstore, options)
|
|
52
52
|
|
|
53
|
+
log('resolving "%s"', part, result)
|
|
54
|
+
|
|
53
55
|
if (result.type === 'file') {
|
|
54
56
|
if (i < parts.length - 1) {
|
|
55
57
|
throw new InvalidParametersError('Path was invalid')
|
|
@@ -62,6 +64,7 @@ export async function resolve (cid: CID, path: string | undefined, blockstore: B
|
|
|
62
64
|
for await (const entry of result.content()) {
|
|
63
65
|
if (entry.name === part) {
|
|
64
66
|
dirCid = entry.cid
|
|
67
|
+
break
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -81,6 +84,8 @@ export async function resolve (cid: CID, path: string | undefined, blockstore: B
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
log('resolved %s to %c', path, cid)
|
|
88
|
+
|
|
84
89
|
return {
|
|
85
90
|
cid,
|
|
86
91
|
path,
|