@helia/unixfs 1.0.3 → 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 -164
- 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 +140 -226
- 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,131 +9,23 @@ 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 updateHamtDirectory = async (pbNode: PBNode, blockstore: Blockstore, bucket: Bucket<any>, options: UpdateHamtDirectoryOptions): Promise<UpdateHamtResult> => {
|
|
37
|
-
if (pbNode.Data == null) {
|
|
38
|
-
throw new Error('Could not update HAMT directory because parent had no data')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// update parent with new bit field
|
|
42
|
-
const node = UnixFS.unmarshal(pbNode.Data)
|
|
43
|
-
const dir = new UnixFS({
|
|
44
|
-
type: 'hamt-sharded-directory',
|
|
45
|
-
data: Uint8Array.from(bucket._children.bitField().reverse()),
|
|
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
|
-
}
|
|
56
|
-
|
|
57
|
-
const buf = dagPB.encode(dagPB.prepare(updatedPbNode))
|
|
58
|
-
const cid = await persist(buf, blockstore, options)
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
node: updatedPbNode,
|
|
62
|
-
cid,
|
|
63
|
-
size: pbNode.Links.reduce((sum, link) => sum + (link.Tsize ?? 0), buf.byteLength)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const recreateHamtLevel = async (blockstore: Blockstore, links: PBLink[], rootBucket: Bucket<any>, parentBucket: Bucket<any>, positionAtParent: number, options: AbortOptions): Promise<Bucket<any>> => {
|
|
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)
|
|
74
|
-
|
|
75
|
-
await addLinksToHamtBucket(blockstore, links, bucket, rootBucket, options)
|
|
76
|
-
|
|
77
|
-
return bucket
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const recreateInitialHamtLevel = async (links: PBLink[]): Promise<Bucket<any>> => {
|
|
81
|
-
const bucket = createHAMT<any>({
|
|
82
|
-
hashFn: hamtHashFn,
|
|
83
|
-
bits: hamtBucketBits
|
|
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
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await bucket.put(linkName.substring(2), {
|
|
103
|
-
size: link.Tsize,
|
|
104
|
-
cid: link.Hash
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
return bucket
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export const addLinksToHamtBucket = async (blockstore: Blockstore, links: PBLink[], bucket: Bucket<any>, rootBucket: Bucket<any>, options: AbortOptions): Promise<void> => {
|
|
113
|
-
await Promise.all(
|
|
114
|
-
links.map(async link => {
|
|
115
|
-
const linkName = (link.Name ?? '')
|
|
116
|
-
|
|
117
|
-
if (linkName.length === 2) {
|
|
118
|
-
log('Populating sub bucket', linkName)
|
|
119
|
-
const pos = parseInt(linkName, 16)
|
|
120
|
-
const block = await blockstore.get(link.Hash, options)
|
|
121
|
-
const node = dagPB.decode(block)
|
|
122
|
-
|
|
123
|
-
const subBucket = new Bucket({
|
|
124
|
-
hash: rootBucket._options.hash,
|
|
125
|
-
bits: rootBucket._options.bits
|
|
126
|
-
}, bucket, pos)
|
|
127
|
-
bucket._putObjectAt(pos, subBucket)
|
|
128
|
-
|
|
129
|
-
await addLinksToHamtBucket(blockstore, node.Links, subBucket, rootBucket, options)
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
await rootBucket.put(linkName.substring(2), {
|
|
134
|
-
size: link.Tsize,
|
|
135
|
-
cid: link.Hash
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
29
|
export const toPrefix = (position: number): string => {
|
|
142
30
|
return position
|
|
143
31
|
.toString(16)
|
|
@@ -146,149 +34,175 @@ export const toPrefix = (position: number): string => {
|
|
|
146
34
|
.substring(0, 2)
|
|
147
35
|
}
|
|
148
36
|
|
|
149
|
-
export interface
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
cid?: CID
|
|
154
|
-
size?: number
|
|
37
|
+
export interface CreateShardOptions {
|
|
38
|
+
mtime?: Mtime
|
|
39
|
+
mode?: number
|
|
40
|
+
cidVersion: Version
|
|
155
41
|
}
|
|
156
42
|
|
|
157
|
-
export const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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)
|
|
55
|
+
|
|
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
|
|
171
60
|
})
|
|
61
|
+
}
|
|
172
62
|
|
|
173
|
-
|
|
174
|
-
break
|
|
175
|
-
}
|
|
63
|
+
const res = await last(shard.flush(blockstore))
|
|
176
64
|
|
|
177
|
-
|
|
65
|
+
if (res == null) {
|
|
66
|
+
throw new Error('Flushing shard yielded no result')
|
|
178
67
|
}
|
|
179
68
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
69
|
+
return res
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface HAMTPath {
|
|
73
|
+
prefix: string
|
|
74
|
+
children: SparseArray
|
|
75
|
+
node: dagPB.PBNode
|
|
76
|
+
}
|
|
77
|
+
|
|
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))
|
|
185
81
|
|
|
82
|
+
// this is always the same
|
|
83
|
+
const fanout = BigInt(Math.pow(2, hamtBucketBits))
|
|
84
|
+
|
|
85
|
+
// start from the leaf and ascend to the root
|
|
186
86
|
path.reverse()
|
|
187
87
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const segment = path[i]
|
|
191
|
-
const previousSegment = path[i - 1]
|
|
88
|
+
let cid: CID | undefined
|
|
89
|
+
let node: dagPB.PBNode | undefined
|
|
192
90
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
91
|
+
for (let i = 0; i < path.length; i++) {
|
|
92
|
+
const isRoot = i === path.length - 1
|
|
93
|
+
const segment = path[i]
|
|
196
94
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
103
|
+
})
|
|
201
104
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
log(`Link ${segment.prefix}${name} will be added`)
|
|
206
|
-
// return path
|
|
207
|
-
continue
|
|
105
|
+
if (isRoot) {
|
|
106
|
+
dir.mtime = shardRoot.mtime
|
|
107
|
+
dir.mode = shardRoot.mode
|
|
208
108
|
}
|
|
209
109
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (linkName === `${segment.prefix}${name}`) {
|
|
214
|
-
log(`Link ${segment.prefix}${name} will be replaced`)
|
|
215
|
-
// file already existed, file will be added to the current bucket
|
|
216
|
-
// return path
|
|
217
|
-
continue
|
|
110
|
+
node = {
|
|
111
|
+
Data: dir.marshal(),
|
|
112
|
+
Links: segment.node.Links
|
|
218
113
|
}
|
|
219
114
|
|
|
220
|
-
|
|
221
|
-
log(`Found subshard ${segment.prefix}`)
|
|
222
|
-
const block = await blockstore.get(link.Hash)
|
|
223
|
-
const node = segment.node = dagPB.decode(block)
|
|
115
|
+
const block = dagPB.encode(dagPB.prepare(node))
|
|
224
116
|
|
|
225
|
-
|
|
226
|
-
if (path[i + 1] == null) {
|
|
227
|
-
log(`Loaded new subshard ${segment.prefix}`)
|
|
117
|
+
cid = await persist(block, blockstore, options)
|
|
228
118
|
|
|
229
|
-
|
|
230
|
-
|
|
119
|
+
if (!isRoot) {
|
|
120
|
+
// update link in parent sub-shard
|
|
121
|
+
const nextSegment = path[i + 1]
|
|
122
|
+
|
|
123
|
+
if (nextSegment == null) {
|
|
124
|
+
throw new Error('Was not operating on shard root but also had no parent?')
|
|
231
125
|
}
|
|
232
126
|
|
|
233
|
-
|
|
234
|
-
const position = await rootBucket._findNewBucketAndPos(name)
|
|
127
|
+
log('updating link in parent sub-shard with prefix %s', nextSegment.prefix)
|
|
235
128
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
node
|
|
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)
|
|
241
134
|
})
|
|
242
|
-
|
|
243
|
-
continue
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (segment.bucket == null) {
|
|
247
|
-
throw new Error('Shard was invalid')
|
|
248
135
|
}
|
|
249
|
-
|
|
250
|
-
// add intermediate links to bucket
|
|
251
|
-
await addLinksToHamtBucket(blockstore, node.Links, segment.bucket, rootBucket, options)
|
|
252
136
|
}
|
|
253
137
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
138
|
+
if (cid == null || node == null) {
|
|
139
|
+
throw new Error('Noting persisted')
|
|
140
|
+
}
|
|
257
141
|
|
|
258
|
-
return
|
|
142
|
+
return { cid, node }
|
|
259
143
|
}
|
|
260
144
|
|
|
261
|
-
export
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
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[] = []
|
|
266
149
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
dirty: true,
|
|
275
|
-
flat: false,
|
|
276
|
-
mtime: options.mtime,
|
|
277
|
-
mode: options.mode
|
|
278
|
-
}, options)
|
|
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)
|
|
153
|
+
const node = dagPB.decode(block)
|
|
154
|
+
const children = new SparseArray()
|
|
155
|
+
const index = await hash.take(hamtBucketBits)
|
|
156
|
+
const prefix = toPrefix(index)
|
|
279
157
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
158
|
+
path.push({
|
|
159
|
+
prefix,
|
|
160
|
+
children,
|
|
161
|
+
node
|
|
284
162
|
})
|
|
285
|
-
}
|
|
286
163
|
|
|
287
|
-
|
|
164
|
+
let childLink: dagPB.PBLink | undefined
|
|
288
165
|
|
|
289
|
-
|
|
290
|
-
|
|
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 ?? ''
|
|
170
|
+
|
|
171
|
+
if (linkName.length < 2) {
|
|
172
|
+
throw new Error('Invalid HAMT - link name was too short')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const position = parseInt(linkName.substring(0, 2), 16)
|
|
176
|
+
children.set(position, true)
|
|
177
|
+
|
|
178
|
+
// we found the child we are looking for
|
|
179
|
+
if (linkName.startsWith(prefix)) {
|
|
180
|
+
childLink = link
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (childLink == null) {
|
|
185
|
+
log('no link found with prefix %s for %s', prefix, fileName)
|
|
186
|
+
// hash.untake(hamtBucketBits)
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const linkName = childLink.Name ?? ''
|
|
191
|
+
|
|
192
|
+
if (linkName.length < 2) {
|
|
193
|
+
throw new Error('Invalid HAMT - link name was too short')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (linkName.length === 2) {
|
|
197
|
+
// found sub-shard
|
|
198
|
+
cid = childLink.Hash
|
|
199
|
+
log('descend into sub-shard with prefix %s', linkName)
|
|
200
|
+
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
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,
|