@helia/unixfs 0.0.0 → 1.0.1
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/README.md +10 -4
- package/dist/index.min.js +1 -1
- package/dist/src/commands/cat.d.ts +1 -1
- package/dist/src/commands/cat.d.ts.map +1 -1
- package/dist/src/commands/cat.js +1 -1
- package/dist/src/commands/cat.js.map +1 -1
- package/dist/src/commands/chmod.d.ts.map +1 -1
- package/dist/src/commands/chmod.js +14 -12
- package/dist/src/commands/chmod.js.map +1 -1
- package/dist/src/commands/cp.d.ts +1 -1
- package/dist/src/commands/cp.d.ts.map +1 -1
- package/dist/src/commands/cp.js +5 -2
- package/dist/src/commands/cp.js.map +1 -1
- package/dist/src/commands/ls.d.ts +2 -1
- package/dist/src/commands/ls.d.ts.map +1 -1
- package/dist/src/commands/ls.js +1 -1
- package/dist/src/commands/ls.js.map +1 -1
- package/dist/src/commands/mkdir.d.ts.map +1 -1
- package/dist/src/commands/mkdir.js +4 -2
- package/dist/src/commands/mkdir.js.map +1 -1
- package/dist/src/commands/rm.d.ts +1 -1
- package/dist/src/commands/rm.d.ts.map +1 -1
- package/dist/src/commands/rm.js +9 -3
- package/dist/src/commands/rm.js.map +1 -1
- package/dist/src/commands/stat.d.ts +1 -1
- package/dist/src/commands/stat.d.ts.map +1 -1
- package/dist/src/commands/stat.js +19 -15
- package/dist/src/commands/stat.js.map +1 -1
- package/dist/src/commands/touch.d.ts.map +1 -1
- package/dist/src/commands/touch.js +15 -13
- package/dist/src/commands/touch.js.map +1 -1
- package/dist/src/commands/utils/add-link.d.ts +4 -2
- package/dist/src/commands/utils/add-link.d.ts.map +1 -1
- package/dist/src/commands/utils/add-link.js +24 -24
- package/dist/src/commands/utils/add-link.js.map +1 -1
- package/dist/src/commands/utils/cid-to-directory.d.ts +3 -3
- package/dist/src/commands/utils/cid-to-directory.d.ts.map +1 -1
- package/dist/src/commands/utils/cid-to-directory.js +1 -1
- package/dist/src/commands/utils/cid-to-directory.js.map +1 -1
- package/dist/src/commands/utils/cid-to-pblink.d.ts +3 -3
- package/dist/src/commands/utils/cid-to-pblink.d.ts.map +1 -1
- package/dist/src/commands/utils/cid-to-pblink.js.map +1 -1
- package/dist/src/commands/utils/constants.d.ts +2 -0
- package/dist/src/commands/utils/constants.d.ts.map +1 -0
- package/dist/src/commands/utils/constants.js +2 -0
- package/dist/src/commands/utils/constants.js.map +1 -0
- package/dist/src/commands/utils/dir-sharded.d.ts +47 -41
- package/dist/src/commands/utils/dir-sharded.d.ts.map +1 -1
- package/dist/src/commands/utils/dir-sharded.js +99 -15
- package/dist/src/commands/utils/dir-sharded.js.map +1 -1
- package/dist/src/commands/utils/errors.d.ts +22 -6
- package/dist/src/commands/utils/errors.d.ts.map +1 -1
- package/dist/src/commands/utils/errors.js +34 -8
- package/dist/src/commands/utils/errors.js.map +1 -1
- package/dist/src/commands/utils/hamt-constants.d.ts +1 -1
- package/dist/src/commands/utils/hamt-constants.d.ts.map +1 -1
- package/dist/src/commands/utils/hamt-constants.js +1 -1
- package/dist/src/commands/utils/hamt-constants.js.map +1 -1
- package/dist/src/commands/utils/hamt-utils.d.ts +15 -12
- package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -1
- package/dist/src/commands/utils/hamt-utils.js +40 -39
- package/dist/src/commands/utils/hamt-utils.js.map +1 -1
- package/dist/src/commands/utils/is-over-shard-threshold.d.ts +10 -0
- package/dist/src/commands/utils/is-over-shard-threshold.d.ts.map +1 -0
- package/dist/src/commands/utils/is-over-shard-threshold.js +62 -0
- package/dist/src/commands/utils/is-over-shard-threshold.js.map +1 -0
- package/dist/src/commands/utils/persist.d.ts +7 -6
- package/dist/src/commands/utils/persist.d.ts.map +1 -1
- package/dist/src/commands/utils/persist.js +6 -3
- package/dist/src/commands/utils/persist.js.map +1 -1
- package/dist/src/commands/utils/remove-link.d.ts +6 -2
- package/dist/src/commands/utils/remove-link.d.ts.map +1 -1
- package/dist/src/commands/utils/remove-link.js +143 -55
- package/dist/src/commands/utils/remove-link.js.map +1 -1
- package/dist/src/commands/utils/resolve.d.ts +6 -3
- package/dist/src/commands/utils/resolve.d.ts.map +1 -1
- package/dist/src/commands/utils/resolve.js +4 -4
- package/dist/src/commands/utils/resolve.js.map +1 -1
- package/dist/src/index.d.ts +14 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +0 -7
- package/dist/src/index.js.map +1 -1
- package/dist/typedoc-urls.json +14 -0
- package/package.json +8 -7
- package/src/commands/cat.ts +3 -2
- package/src/commands/chmod.ts +13 -10
- package/src/commands/cp.ts +7 -4
- package/src/commands/ls.ts +4 -3
- package/src/commands/mkdir.ts +5 -3
- package/src/commands/rm.ts +9 -5
- package/src/commands/stat.ts +22 -17
- package/src/commands/touch.ts +15 -12
- package/src/commands/utils/add-link.ts +32 -31
- package/src/commands/utils/cid-to-directory.ts +4 -4
- package/src/commands/utils/cid-to-pblink.ts +3 -3
- package/src/commands/utils/constants.ts +2 -0
- package/src/commands/utils/dir-sharded.ts +162 -63
- package/src/commands/utils/errors.ts +42 -8
- package/src/commands/utils/hamt-constants.ts +1 -1
- package/src/commands/utils/hamt-utils.ts +59 -50
- package/src/commands/utils/is-over-shard-threshold.ts +78 -0
- package/src/commands/utils/persist.ts +13 -8
- package/src/commands/utils/remove-link.ts +178 -77
- package/src/commands/utils/resolve.ts +12 -7
- package/src/index.ts +15 -17
- package/dist/src/commands/add.d.ts +0 -6
- package/dist/src/commands/add.d.ts.map +0 -1
- package/dist/src/commands/add.js +0 -38
- package/dist/src/commands/add.js.map +0 -1
- package/src/commands/add.ts +0 -46
|
@@ -1,39 +1,28 @@
|
|
|
1
|
-
import { encode, prepare } from '@ipld/dag-pb'
|
|
1
|
+
import { encode, PBLink, prepare } from '@ipld/dag-pb'
|
|
2
2
|
import { UnixFS } from 'ipfs-unixfs'
|
|
3
|
-
import { persist } from './persist.js'
|
|
3
|
+
import { persist, PersistOptions } from './persist.js'
|
|
4
4
|
import { createHAMT, Bucket, BucketChild } from 'hamt-sharding'
|
|
5
5
|
import {
|
|
6
6
|
hamtHashCode,
|
|
7
|
-
hamtHashFn
|
|
8
|
-
hamtBucketBits
|
|
7
|
+
hamtHashFn
|
|
9
8
|
} from './hamt-constants.js'
|
|
10
|
-
import
|
|
11
|
-
import type { PBNode } from '@ipld/dag-pb/interface'
|
|
9
|
+
import { CID } from 'multiformats/cid'
|
|
12
10
|
import type { Mtime } from 'ipfs-unixfs'
|
|
13
|
-
import type {
|
|
14
|
-
import type { Blockstore } from 'ipfs-unixfs-importer'
|
|
11
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
size: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DirContents {
|
|
23
|
-
cid?: CID
|
|
24
|
-
size?: number
|
|
13
|
+
interface InProgressImportResult extends ImportResult {
|
|
14
|
+
single?: boolean
|
|
15
|
+
originalPath?: string
|
|
25
16
|
}
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
onlyHash?: boolean
|
|
33
|
-
signal?: AbortSignal
|
|
18
|
+
interface ImportResult {
|
|
19
|
+
cid: CID
|
|
20
|
+
size: bigint
|
|
21
|
+
path?: string
|
|
22
|
+
unixfs?: UnixFS
|
|
34
23
|
}
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
interface DirProps {
|
|
37
26
|
root: boolean
|
|
38
27
|
dir: boolean
|
|
39
28
|
path: string
|
|
@@ -46,23 +35,25 @@ export interface DirProps {
|
|
|
46
35
|
mtime?: Mtime
|
|
47
36
|
}
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
abstract class Dir {
|
|
39
|
+
public options: PersistOptions
|
|
40
|
+
public root: boolean
|
|
41
|
+
public dir: boolean
|
|
42
|
+
public path: string
|
|
43
|
+
public dirty: boolean
|
|
44
|
+
public flat: boolean
|
|
45
|
+
public parent?: Dir
|
|
46
|
+
public parentKey?: string
|
|
47
|
+
public unixfs?: UnixFS
|
|
48
|
+
public mode?: number
|
|
60
49
|
public mtime?: Mtime
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
public cid?: CID
|
|
51
|
+
public size?: number
|
|
52
|
+
public nodeSize?: number
|
|
63
53
|
|
|
64
|
-
constructor (props: DirProps, options:
|
|
54
|
+
constructor (props: DirProps, options: PersistOptions) {
|
|
65
55
|
this.options = options ?? {}
|
|
56
|
+
|
|
66
57
|
this.root = props.root
|
|
67
58
|
this.dir = props.dir
|
|
68
59
|
this.path = props.path
|
|
@@ -74,26 +65,36 @@ export abstract class Dir {
|
|
|
74
65
|
this.mode = props.mode
|
|
75
66
|
this.mtime = props.mtime
|
|
76
67
|
}
|
|
68
|
+
|
|
69
|
+
abstract put (name: string, value: InProgressImportResult | Dir): Promise<void>
|
|
70
|
+
abstract get (name: string): Promise<InProgressImportResult | Dir | undefined>
|
|
71
|
+
abstract eachChildSeries (): AsyncIterable<{ key: string, child: InProgressImportResult | Dir }>
|
|
72
|
+
abstract flush (blockstore: Blockstore): AsyncGenerator<ImportResult>
|
|
73
|
+
abstract estimateNodeSize (): number
|
|
74
|
+
abstract childCount (): number
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
export class DirSharded extends Dir {
|
|
80
|
-
public _bucket: Bucket<
|
|
78
|
+
public _bucket: Bucket<InProgressImportResult | Dir>
|
|
81
79
|
|
|
82
|
-
constructor (props: DirProps, options:
|
|
80
|
+
constructor (props: DirProps, options: PersistOptions) {
|
|
83
81
|
super(props, options)
|
|
84
82
|
|
|
85
|
-
/** @type {Bucket<DirContents>} */
|
|
86
83
|
this._bucket = createHAMT({
|
|
87
84
|
hashFn: hamtHashFn,
|
|
88
|
-
bits:
|
|
85
|
+
bits: 8
|
|
89
86
|
})
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
async put (name: string, value:
|
|
89
|
+
async put (name: string, value: InProgressImportResult | Dir): Promise<void> {
|
|
90
|
+
this.cid = undefined
|
|
91
|
+
this.size = undefined
|
|
92
|
+
this.nodeSize = undefined
|
|
93
|
+
|
|
93
94
|
await this._bucket.put(name, value)
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
async get (name: string): Promise<
|
|
97
|
+
async get (name: string): Promise<InProgressImportResult | Dir | undefined> {
|
|
97
98
|
return await this._bucket.get(name)
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -105,11 +106,11 @@ export class DirSharded extends Dir {
|
|
|
105
106
|
return this._bucket.childrenCount()
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
onlyChild (): Bucket<
|
|
109
|
+
onlyChild (): Bucket<InProgressImportResult | Dir> | BucketChild<InProgressImportResult | Dir> {
|
|
109
110
|
return this._bucket.onlyChild()
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
async * eachChildSeries (): AsyncGenerator<{ key: string, child:
|
|
113
|
+
async * eachChildSeries (): AsyncGenerator<{ key: string, child: InProgressImportResult | Dir }> {
|
|
113
114
|
for await (const { key, value } of this._bucket.eachLeafSeries()) {
|
|
114
115
|
yield {
|
|
115
116
|
key,
|
|
@@ -118,15 +119,30 @@ export class DirSharded extends Dir {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
estimateNodeSize (): number {
|
|
123
|
+
if (this.nodeSize !== undefined) {
|
|
124
|
+
return this.nodeSize
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.nodeSize = calculateSize(this._bucket, this, this.options)
|
|
128
|
+
|
|
129
|
+
return this.nodeSize
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async * flush (blockstore: Blockstore): AsyncGenerator<ImportResult> {
|
|
133
|
+
for await (const entry of flush(this._bucket, blockstore, this, this.options)) {
|
|
134
|
+
yield {
|
|
135
|
+
...entry,
|
|
136
|
+
path: this.path
|
|
137
|
+
}
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
async function * flush (bucket: Bucket<
|
|
142
|
+
async function * flush (bucket: Bucket<Dir | InProgressImportResult>, blockstore: Blockstore, shardRoot: DirSharded | null, options: PersistOptions): AsyncIterable<ImportResult> {
|
|
127
143
|
const children = bucket._children
|
|
128
|
-
const links = []
|
|
129
|
-
let childrenSize =
|
|
144
|
+
const links: PBLink[] = []
|
|
145
|
+
let childrenSize = 0n
|
|
130
146
|
|
|
131
147
|
for (let i = 0; i < children.length; i++) {
|
|
132
148
|
const child = children.get(i)
|
|
@@ -138,7 +154,7 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
138
154
|
const labelPrefix = i.toString(16).toUpperCase().padStart(2, '0')
|
|
139
155
|
|
|
140
156
|
if (child instanceof Bucket) {
|
|
141
|
-
let shard
|
|
157
|
+
let shard
|
|
142
158
|
|
|
143
159
|
for await (const subShard of flush(child, blockstore, null, options)) {
|
|
144
160
|
shard = subShard
|
|
@@ -150,13 +166,13 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
150
166
|
|
|
151
167
|
links.push({
|
|
152
168
|
Name: labelPrefix,
|
|
153
|
-
Tsize: shard.size,
|
|
169
|
+
Tsize: Number(shard.size),
|
|
154
170
|
Hash: shard.cid
|
|
155
171
|
})
|
|
156
172
|
childrenSize += shard.size
|
|
157
|
-
} else if (
|
|
173
|
+
} else if (isDir(child.value)) {
|
|
158
174
|
const dir = child.value
|
|
159
|
-
let flushedDir
|
|
175
|
+
let flushedDir: ImportResult | undefined
|
|
160
176
|
|
|
161
177
|
for await (const entry of dir.flush(blockstore)) {
|
|
162
178
|
flushedDir = entry
|
|
@@ -164,14 +180,18 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
164
180
|
yield flushedDir
|
|
165
181
|
}
|
|
166
182
|
|
|
183
|
+
if (flushedDir == null) {
|
|
184
|
+
throw new Error('Did not flush dir')
|
|
185
|
+
}
|
|
186
|
+
|
|
167
187
|
const label = labelPrefix + child.key
|
|
168
188
|
links.push({
|
|
169
189
|
Name: label,
|
|
170
|
-
Tsize: flushedDir.size,
|
|
190
|
+
Tsize: Number(flushedDir.size),
|
|
171
191
|
Hash: flushedDir.cid
|
|
172
192
|
})
|
|
173
193
|
|
|
174
|
-
childrenSize += flushedDir.size
|
|
194
|
+
childrenSize += flushedDir.size
|
|
175
195
|
} else {
|
|
176
196
|
const value = child.value
|
|
177
197
|
|
|
@@ -184,10 +204,10 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
184
204
|
|
|
185
205
|
links.push({
|
|
186
206
|
Name: label,
|
|
187
|
-
Tsize: size,
|
|
207
|
+
Tsize: Number(size),
|
|
188
208
|
Hash: value.cid
|
|
189
209
|
})
|
|
190
|
-
childrenSize += size ?? 0
|
|
210
|
+
childrenSize += BigInt(size ?? 0)
|
|
191
211
|
}
|
|
192
212
|
}
|
|
193
213
|
|
|
@@ -197,7 +217,7 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
197
217
|
const dir = new UnixFS({
|
|
198
218
|
type: 'hamt-sharded-directory',
|
|
199
219
|
data,
|
|
200
|
-
fanout: bucket.tableSize(),
|
|
220
|
+
fanout: BigInt(bucket.tableSize()),
|
|
201
221
|
hashType: hamtHashCode,
|
|
202
222
|
mtime: shardRoot?.mtime,
|
|
203
223
|
mode: shardRoot?.mode
|
|
@@ -209,11 +229,90 @@ async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot:
|
|
|
209
229
|
}
|
|
210
230
|
const buffer = encode(prepare(node))
|
|
211
231
|
const cid = await persist(buffer, blockstore, options)
|
|
212
|
-
const size = buffer.
|
|
232
|
+
const size = BigInt(buffer.byteLength) + childrenSize
|
|
213
233
|
|
|
214
234
|
yield {
|
|
215
235
|
cid,
|
|
216
|
-
|
|
236
|
+
unixfs: dir,
|
|
217
237
|
size
|
|
218
238
|
}
|
|
219
239
|
}
|
|
240
|
+
|
|
241
|
+
function isDir (obj: any): obj is Dir {
|
|
242
|
+
return typeof obj.flush === 'function'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function calculateSize (bucket: Bucket<any>, shardRoot: DirSharded | null, options: PersistOptions): number {
|
|
246
|
+
const children = bucket._children
|
|
247
|
+
const links: PBLink[] = []
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < children.length; i++) {
|
|
250
|
+
const child = children.get(i)
|
|
251
|
+
|
|
252
|
+
if (child == null) {
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const labelPrefix = i.toString(16).toUpperCase().padStart(2, '0')
|
|
257
|
+
|
|
258
|
+
if (child instanceof Bucket) {
|
|
259
|
+
const size = calculateSize(child, null, options)
|
|
260
|
+
|
|
261
|
+
links.push({
|
|
262
|
+
Name: labelPrefix,
|
|
263
|
+
Tsize: Number(size),
|
|
264
|
+
Hash: options.cidVersion === 0 ? CID_V0 : CID_V1
|
|
265
|
+
})
|
|
266
|
+
} else if (typeof child.value.flush === 'function') {
|
|
267
|
+
const dir = child.value
|
|
268
|
+
const size = dir.nodeSize()
|
|
269
|
+
|
|
270
|
+
links.push({
|
|
271
|
+
Name: labelPrefix + child.key,
|
|
272
|
+
Tsize: Number(size),
|
|
273
|
+
Hash: options.cidVersion === 0 ? CID_V0 : CID_V1
|
|
274
|
+
})
|
|
275
|
+
} else {
|
|
276
|
+
const value = child.value
|
|
277
|
+
|
|
278
|
+
if (value.cid == null) {
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const label = labelPrefix + child.key
|
|
283
|
+
const size = value.size
|
|
284
|
+
|
|
285
|
+
links.push({
|
|
286
|
+
Name: label,
|
|
287
|
+
Tsize: Number(size),
|
|
288
|
+
Hash: value.cid
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// go-ipfs uses little endian, that's why we have to
|
|
294
|
+
// reverse the bit field before storing it
|
|
295
|
+
const data = Uint8Array.from(children.bitField().reverse())
|
|
296
|
+
const dir = new UnixFS({
|
|
297
|
+
type: 'hamt-sharded-directory',
|
|
298
|
+
data,
|
|
299
|
+
fanout: BigInt(bucket.tableSize()),
|
|
300
|
+
hashType: hamtHashCode,
|
|
301
|
+
mtime: shardRoot?.mtime,
|
|
302
|
+
mode: shardRoot?.mode
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const buffer = encode(prepare({
|
|
306
|
+
Data: dir.marshal(),
|
|
307
|
+
Links: links
|
|
308
|
+
}))
|
|
309
|
+
|
|
310
|
+
return buffer.length
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// we use these to calculate the node size to use as a check for whether a directory
|
|
314
|
+
// should be sharded or not. Since CIDs have a constant length and We're only
|
|
315
|
+
// interested in the data length and not the actual content identifier we can use
|
|
316
|
+
// any old CID instead of having to hash the data which is expensive.
|
|
317
|
+
export const CID_V0 = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
|
|
318
|
+
export const CID_V1 = CID.parse('zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi')
|
|
@@ -1,31 +1,65 @@
|
|
|
1
|
-
|
|
1
|
+
export abstract class UnixFSError extends Error {
|
|
2
|
+
public readonly name: string
|
|
3
|
+
public readonly code: string
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
constructor (message: string, name: string, code: string) {
|
|
6
|
+
super(message)
|
|
7
|
+
|
|
8
|
+
this.name = name
|
|
9
|
+
this.code = code
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class NotUnixFSError extends UnixFSError {
|
|
4
14
|
constructor (message = 'not a Unixfs node') {
|
|
5
15
|
super(message, 'NotUnixFSError', 'ERR_NOT_UNIXFS')
|
|
6
16
|
}
|
|
7
17
|
}
|
|
8
18
|
|
|
9
|
-
export class InvalidPBNodeError extends
|
|
19
|
+
export class InvalidPBNodeError extends UnixFSError {
|
|
10
20
|
constructor (message = 'invalid PBNode') {
|
|
11
21
|
super(message, 'InvalidPBNodeError', 'ERR_INVALID_PBNODE')
|
|
12
22
|
}
|
|
13
23
|
}
|
|
14
24
|
|
|
15
|
-
export class UnknownError extends
|
|
25
|
+
export class UnknownError extends UnixFSError {
|
|
16
26
|
constructor (message = 'unknown error') {
|
|
17
27
|
super(message, 'InvalidPBNodeError', 'ERR_UNKNOWN_ERROR')
|
|
18
28
|
}
|
|
19
29
|
}
|
|
20
30
|
|
|
21
|
-
export class AlreadyExistsError extends
|
|
31
|
+
export class AlreadyExistsError extends UnixFSError {
|
|
22
32
|
constructor (message = 'path already exists') {
|
|
23
|
-
super(message, '
|
|
33
|
+
super(message, 'AlreadyExistsError', 'ERR_ALREADY_EXISTS')
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
export class DoesNotExistError extends
|
|
37
|
+
export class DoesNotExistError extends UnixFSError {
|
|
28
38
|
constructor (message = 'path does not exist') {
|
|
29
|
-
super(message, '
|
|
39
|
+
super(message, 'DoesNotExistError', 'ERR_DOES_NOT_EXIST')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class NoContentError extends UnixFSError {
|
|
44
|
+
constructor (message = 'no content') {
|
|
45
|
+
super(message, 'NoContentError', 'ERR_NO_CONTENT')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class NotAFileError extends UnixFSError {
|
|
50
|
+
constructor (message = 'not a file') {
|
|
51
|
+
super(message, 'NotAFileError', 'ERR_NOT_A_FILE')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class NotADirectoryError extends UnixFSError {
|
|
56
|
+
constructor (message = 'not a directory') {
|
|
57
|
+
super(message, 'NotADirectoryError', 'ERR_NOT_A_DIRECTORY')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class InvalidParametersError extends UnixFSError {
|
|
62
|
+
constructor (message = 'invalid parameters') {
|
|
63
|
+
super(message, 'InvalidParametersError', 'ERR_INVALID_PARAMETERS')
|
|
30
64
|
}
|
|
31
65
|
}
|
|
@@ -7,19 +7,19 @@ import { DirSharded } from './dir-sharded.js'
|
|
|
7
7
|
import { logger } from '@libp2p/logger'
|
|
8
8
|
import { UnixFS } from 'ipfs-unixfs'
|
|
9
9
|
import last from 'it-last'
|
|
10
|
-
import { CID } from 'multiformats/cid'
|
|
10
|
+
import type { CID, Version } from 'multiformats/cid'
|
|
11
11
|
import {
|
|
12
12
|
hamtHashCode,
|
|
13
13
|
hamtHashFn,
|
|
14
14
|
hamtBucketBits
|
|
15
15
|
} from './hamt-constants.js'
|
|
16
16
|
import type { PBLink, PBNode } from '@ipld/dag-pb/interface'
|
|
17
|
-
import { sha256 } from 'multiformats/hashes/sha2'
|
|
18
17
|
import type { Blockstore } from 'interface-blockstore'
|
|
19
18
|
import type { Mtime } from 'ipfs-unixfs'
|
|
20
19
|
import type { Directory } from './cid-to-directory.js'
|
|
21
20
|
import type { AbortOptions } from '@libp2p/interfaces'
|
|
22
21
|
import type { ImportResult } from 'ipfs-unixfs-importer'
|
|
22
|
+
import { persist } from './persist.js'
|
|
23
23
|
|
|
24
24
|
const log = logger('helia:unixfs:commands:utils:hamt-utils')
|
|
25
25
|
|
|
@@ -29,37 +29,38 @@ export interface UpdateHamtResult {
|
|
|
29
29
|
size: number
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export
|
|
33
|
-
|
|
32
|
+
export interface UpdateHamtDirectoryOptions extends AbortOptions {
|
|
33
|
+
cidVersion: Version
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const updateHamtDirectory = async (pbNode: PBNode, blockstore: Blockstore, bucket: Bucket<any>, options: UpdateHamtDirectoryOptions): Promise<UpdateHamtResult> => {
|
|
37
|
+
if (pbNode.Data == null) {
|
|
34
38
|
throw new Error('Could not update HAMT directory because parent had no data')
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
// update parent with new bit field
|
|
38
|
-
const
|
|
39
|
-
const node = UnixFS.unmarshal(parent.node.Data)
|
|
42
|
+
const node = UnixFS.unmarshal(pbNode.Data)
|
|
40
43
|
const dir = new UnixFS({
|
|
41
44
|
type: 'hamt-sharded-directory',
|
|
42
|
-
data,
|
|
43
|
-
fanout: bucket.tableSize(),
|
|
45
|
+
data: Uint8Array.from(bucket._children.bitField().reverse()),
|
|
46
|
+
fanout: BigInt(bucket.tableSize()),
|
|
44
47
|
hashType: hamtHashCode,
|
|
45
48
|
mode: node.mode,
|
|
46
49
|
mtime: node.mtime
|
|
47
50
|
})
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
const updatedPbNode = {
|
|
50
53
|
Data: dir.marshal(),
|
|
51
|
-
Links:
|
|
54
|
+
Links: pbNode.Links
|
|
52
55
|
}
|
|
53
|
-
const buf = dagPB.encode(parent.node)
|
|
54
|
-
const hash = await sha256.digest(buf)
|
|
55
|
-
const cid = CID.create(parent.cid.version, dagPB.code, hash)
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
const buf = dagPB.encode(dagPB.prepare(updatedPbNode))
|
|
58
|
+
const cid = await persist(buf, blockstore, options)
|
|
58
59
|
|
|
59
60
|
return {
|
|
60
|
-
node:
|
|
61
|
+
node: updatedPbNode,
|
|
61
62
|
cid,
|
|
62
|
-
size:
|
|
63
|
+
size: pbNode.Links.reduce((sum, link) => sum + (link.Tsize ?? 0), buf.byteLength)
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -77,26 +78,24 @@ export const recreateHamtLevel = async (blockstore: Blockstore, links: PBLink[],
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export const recreateInitialHamtLevel = async (links: PBLink[]): Promise<Bucket<any>> => {
|
|
80
|
-
const bucket = createHAMT({
|
|
81
|
+
const bucket = createHAMT<any>({
|
|
81
82
|
hashFn: hamtHashFn,
|
|
82
83
|
bits: hamtBucketBits
|
|
83
84
|
})
|
|
84
85
|
|
|
85
|
-
// populate sub bucket but do not recurse as we do not want to
|
|
86
|
+
// populate sub bucket but do not recurse as we do not want to load the whole shard
|
|
86
87
|
await Promise.all(
|
|
87
88
|
links.map(async link => {
|
|
88
89
|
const linkName = (link.Name ?? '')
|
|
89
90
|
|
|
90
91
|
if (linkName.length === 2) {
|
|
91
92
|
const pos = parseInt(linkName, 16)
|
|
92
|
-
|
|
93
93
|
const subBucket = new Bucket({
|
|
94
94
|
hash: bucket._options.hash,
|
|
95
95
|
bits: bucket._options.bits
|
|
96
96
|
}, bucket, pos)
|
|
97
|
-
bucket._putObjectAt(pos, subBucket)
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
bucket._putObjectAt(pos, subBucket)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
await bucket.put(linkName.substring(2), {
|
|
@@ -127,8 +126,6 @@ export const addLinksToHamtBucket = async (blockstore: Blockstore, links: PBLink
|
|
|
127
126
|
bucket._putObjectAt(pos, subBucket)
|
|
128
127
|
|
|
129
128
|
await addLinksToHamtBucket(blockstore, node.Links, subBucket, rootBucket, options)
|
|
130
|
-
|
|
131
|
-
await Promise.resolve(); return
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
await rootBucket.put(linkName.substring(2), {
|
|
@@ -147,22 +144,19 @@ export const toPrefix = (position: number): string => {
|
|
|
147
144
|
.substring(0, 2)
|
|
148
145
|
}
|
|
149
146
|
|
|
150
|
-
export interface
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}>
|
|
147
|
+
export interface HamtPathSegment {
|
|
148
|
+
bucket?: Bucket<any>
|
|
149
|
+
prefix?: string
|
|
150
|
+
node?: PBNode
|
|
151
|
+
cid?: CID
|
|
152
|
+
size?: number
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
export const generatePath = async (
|
|
155
|
+
export const generatePath = async (root: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<HamtPathSegment[]> => {
|
|
160
156
|
// start at the root bucket and descend, loading nodes as we go
|
|
161
|
-
const rootBucket = await recreateInitialHamtLevel(
|
|
157
|
+
const rootBucket = await recreateInitialHamtLevel(root.node.Links)
|
|
162
158
|
const position = await rootBucket._findNewBucketAndPos(name)
|
|
163
|
-
|
|
164
|
-
// the path to the root bucket
|
|
165
|
-
const path: Array<{ bucket: Bucket<any>, prefix: string, node?: PBNode }> = [{
|
|
159
|
+
const path: HamtPathSegment[] = [{
|
|
166
160
|
bucket: position.bucket,
|
|
167
161
|
prefix: toPrefix(position.pos)
|
|
168
162
|
}]
|
|
@@ -174,23 +168,32 @@ export const generatePath = async (parent: Directory, name: string, blockstore:
|
|
|
174
168
|
prefix: toPrefix(currentBucket._posAtParent)
|
|
175
169
|
})
|
|
176
170
|
|
|
177
|
-
|
|
171
|
+
if (currentBucket._parent == null) {
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
|
|
178
175
|
currentBucket = currentBucket._parent
|
|
179
176
|
}
|
|
180
177
|
|
|
178
|
+
// add the root bucket to the path
|
|
179
|
+
path.push({
|
|
180
|
+
bucket: rootBucket,
|
|
181
|
+
node: root.node
|
|
182
|
+
})
|
|
183
|
+
|
|
181
184
|
path.reverse()
|
|
182
|
-
path[0].node = parent.node
|
|
183
185
|
|
|
184
186
|
// load PbNode for each path segment
|
|
185
|
-
for (let i =
|
|
187
|
+
for (let i = 1; i < path.length; i++) {
|
|
186
188
|
const segment = path[i]
|
|
189
|
+
const previousSegment = path[i - 1]
|
|
187
190
|
|
|
188
|
-
if (
|
|
191
|
+
if (previousSegment.node == null) {
|
|
189
192
|
throw new Error('Could not generate HAMT path')
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
// find prefix in links
|
|
193
|
-
const link =
|
|
196
|
+
const link = previousSegment.node.Links
|
|
194
197
|
.filter(link => (link.Name ?? '').substring(0, 2) === segment.prefix)
|
|
195
198
|
.pop()
|
|
196
199
|
|
|
@@ -202,8 +205,10 @@ export const generatePath = async (parent: Directory, name: string, blockstore:
|
|
|
202
205
|
continue
|
|
203
206
|
}
|
|
204
207
|
|
|
208
|
+
const linkName = link.Name ?? ''
|
|
209
|
+
|
|
205
210
|
// found entry
|
|
206
|
-
if (
|
|
211
|
+
if (linkName === `${segment.prefix}${name}`) {
|
|
207
212
|
log(`Link ${segment.prefix}${name} will be replaced`)
|
|
208
213
|
// file already existed, file will be added to the current bucket
|
|
209
214
|
// return path
|
|
@@ -212,13 +217,17 @@ export const generatePath = async (parent: Directory, name: string, blockstore:
|
|
|
212
217
|
|
|
213
218
|
// found subshard
|
|
214
219
|
log(`Found subshard ${segment.prefix}`)
|
|
215
|
-
const block = await blockstore.get(link.Hash
|
|
220
|
+
const block = await blockstore.get(link.Hash)
|
|
216
221
|
const node = dagPB.decode(block)
|
|
217
222
|
|
|
218
223
|
// subshard hasn't been loaded, descend to the next level of the HAMT
|
|
219
224
|
if (path[i + 1] == null) {
|
|
220
225
|
log(`Loaded new subshard ${segment.prefix}`)
|
|
221
226
|
|
|
227
|
+
if (segment.bucket == null || segment.prefix == null) {
|
|
228
|
+
throw new Error('Shard was invalid')
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
await recreateHamtLevel(blockstore, node.Links, rootBucket, segment.bucket, parseInt(segment.prefix, 16), options)
|
|
223
232
|
const position = await rootBucket._findNewBucketAndPos(name)
|
|
224
233
|
|
|
@@ -232,30 +241,30 @@ export const generatePath = async (parent: Directory, name: string, blockstore:
|
|
|
232
241
|
continue
|
|
233
242
|
}
|
|
234
243
|
|
|
235
|
-
|
|
244
|
+
if (segment.bucket == null) {
|
|
245
|
+
throw new Error('Shard was invalid')
|
|
246
|
+
}
|
|
236
247
|
|
|
237
248
|
// add intermediate links to bucket
|
|
238
|
-
await addLinksToHamtBucket(blockstore, node.Links,
|
|
249
|
+
await addLinksToHamtBucket(blockstore, node.Links, segment.bucket, rootBucket, options)
|
|
239
250
|
|
|
240
|
-
|
|
251
|
+
segment.node = node
|
|
241
252
|
}
|
|
242
253
|
|
|
243
254
|
await rootBucket.put(name, true)
|
|
244
255
|
|
|
245
256
|
path.reverse()
|
|
246
257
|
|
|
247
|
-
return
|
|
248
|
-
rootBucket,
|
|
249
|
-
path
|
|
250
|
-
}
|
|
258
|
+
return path
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
export interface CreateShardOptions {
|
|
254
262
|
mtime?: Mtime
|
|
255
263
|
mode?: number
|
|
264
|
+
cidVersion: Version
|
|
256
265
|
}
|
|
257
266
|
|
|
258
|
-
export const createShard = async (blockstore: Blockstore, contents: Array<{ name: string, size:
|
|
267
|
+
export const createShard = async (blockstore: Blockstore, contents: Array<{ name: string, size: bigint, cid: CID }>, options: CreateShardOptions): Promise<ImportResult> => {
|
|
259
268
|
const shard = new DirSharded({
|
|
260
269
|
root: true,
|
|
261
270
|
dir: true,
|