@helia/unixfs 7.0.4 → 7.1.0
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 +4 -4
- package/dist/index.min.js.map +4 -4
- package/dist/src/commands/add.d.ts +7 -7
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +12 -4
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/cat.d.ts +3 -3
- package/dist/src/commands/cat.d.ts.map +1 -1
- package/dist/src/commands/cat.js +2 -2
- package/dist/src/commands/cat.js.map +1 -1
- package/dist/src/commands/chmod.d.ts +3 -3
- package/dist/src/commands/chmod.d.ts.map +1 -1
- package/dist/src/commands/chmod.js +3 -3
- package/dist/src/commands/chmod.js.map +1 -1
- package/dist/src/commands/cp.d.ts +3 -3
- package/dist/src/commands/cp.d.ts.map +1 -1
- package/dist/src/commands/cp.js +4 -4
- package/dist/src/commands/cp.js.map +1 -1
- package/dist/src/commands/ls.d.ts +3 -3
- 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 +3 -3
- package/dist/src/commands/mkdir.d.ts.map +1 -1
- package/dist/src/commands/mkdir.js +4 -4
- package/dist/src/commands/mkdir.js.map +1 -1
- package/dist/src/commands/rm.d.ts +3 -3
- package/dist/src/commands/rm.d.ts.map +1 -1
- package/dist/src/commands/rm.js +3 -3
- package/dist/src/commands/rm.js.map +1 -1
- package/dist/src/commands/stat.d.ts +2 -2
- package/dist/src/commands/stat.js +2 -2
- package/dist/src/commands/touch.d.ts +3 -3
- package/dist/src/commands/touch.d.ts.map +1 -1
- package/dist/src/commands/touch.js +3 -3
- package/dist/src/commands/touch.js.map +1 -1
- package/dist/src/commands/utils/add-link.d.ts +4 -6
- package/dist/src/commands/utils/add-link.d.ts.map +1 -1
- package/dist/src/commands/utils/add-link.js +23 -17
- package/dist/src/commands/utils/add-link.js.map +1 -1
- package/dist/src/commands/utils/cid-to-directory.d.ts +1 -1
- package/dist/src/commands/utils/cid-to-directory.js +1 -1
- package/dist/src/commands/utils/cid-to-pblink.d.ts +1 -1
- package/dist/src/commands/utils/cid-to-pblink.js +1 -1
- package/dist/src/commands/utils/hamt-utils.d.ts +9 -8
- package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -1
- package/dist/src/commands/utils/hamt-utils.js +18 -16
- package/dist/src/commands/utils/hamt-utils.js.map +1 -1
- package/dist/src/commands/utils/is-over-shard-threshold.d.ts +6 -2
- package/dist/src/commands/utils/is-over-shard-threshold.d.ts.map +1 -1
- package/dist/src/commands/utils/is-over-shard-threshold.js +58 -13
- package/dist/src/commands/utils/is-over-shard-threshold.js.map +1 -1
- package/dist/src/commands/utils/persist.d.ts +3 -3
- package/dist/src/commands/utils/persist.d.ts.map +1 -1
- package/dist/src/commands/utils/remove-link.d.ts +5 -6
- package/dist/src/commands/utils/remove-link.d.ts.map +1 -1
- package/dist/src/commands/utils/remove-link.js +10 -9
- package/dist/src/commands/utils/remove-link.js.map +1 -1
- package/dist/src/commands/utils/resolve.d.ts +1 -1
- package/dist/src/commands/utils/resolve.js +4 -4
- package/dist/src/index.d.ts +24 -54
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/unixfs.d.ts +1 -1
- package/dist/src/unixfs.js +9 -9
- package/dist/src/utils/glob-source.js +2 -2
- package/dist/src/utils/url-source.d.ts +1 -1
- package/dist/src/utils/url-source.js +1 -1
- package/package.json +4 -5
- package/src/commands/add.ts +19 -11
- package/src/commands/cat.ts +5 -5
- package/src/commands/chmod.ts +6 -6
- package/src/commands/cp.ts +7 -7
- package/src/commands/ls.ts +4 -4
- package/src/commands/mkdir.ts +7 -7
- package/src/commands/rm.ts +6 -6
- package/src/commands/stat.ts +4 -4
- package/src/commands/touch.ts +6 -6
- package/src/commands/utils/add-link.ts +30 -24
- package/src/commands/utils/cid-to-directory.ts +2 -2
- package/src/commands/utils/cid-to-pblink.ts +2 -2
- package/src/commands/utils/hamt-utils.ts +29 -23
- package/src/commands/utils/is-over-shard-threshold.ts +88 -13
- package/src/commands/utils/persist.ts +3 -3
- package/src/commands/utils/remove-link.ts +17 -17
- package/src/commands/utils/resolve.ts +5 -5
- package/src/index.ts +25 -61
- package/src/unixfs.ts +10 -10
- package/src/utils/glob-source.ts +2 -2
- package/src/utils/url-source.ts +2 -2
- package/dist/src/commands/utils/dir-sharded.d.ts +0 -74
- package/dist/src/commands/utils/dir-sharded.d.ts.map +0 -1
- package/dist/src/commands/utils/dir-sharded.js +0 -235
- package/dist/src/commands/utils/dir-sharded.js.map +0 -1
- package/src/commands/utils/dir-sharded.ts +0 -321
|
@@ -7,23 +7,21 @@ import { sha256 } from 'multiformats/hashes/sha2'
|
|
|
7
7
|
// @ts-expect-error no types
|
|
8
8
|
import SparseArray from 'sparse-array'
|
|
9
9
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { hamtBucketBits, hamtHashFn } from './hamt-constants.js'
|
|
10
|
+
import { AlreadyExistsError, InvalidParametersError, InvalidPBNodeError } from '../../errors.ts'
|
|
11
|
+
import { wrapHash } from './consumable-hash.ts'
|
|
12
|
+
import { hamtBucketBits, hamtHashFn } from './hamt-constants.ts'
|
|
14
13
|
import {
|
|
15
14
|
createShard,
|
|
16
15
|
recreateShardedDirectory,
|
|
17
16
|
toPrefix,
|
|
18
17
|
updateShardedDirectory
|
|
19
|
-
} from './hamt-utils.
|
|
20
|
-
import { isOverShardThreshold } from './is-over-shard-threshold.
|
|
21
|
-
import type { Directory } from './cid-to-directory.
|
|
22
|
-
import type { GetStore, PutStore } from '../../unixfs.
|
|
18
|
+
} from './hamt-utils.ts'
|
|
19
|
+
import { isOverShardThreshold } from './is-over-shard-threshold.ts'
|
|
20
|
+
import type { Directory } from './cid-to-directory.ts'
|
|
21
|
+
import type { GetStore, PutStore } from '../../unixfs.ts'
|
|
23
22
|
import type { PBNode, PBLink } from '@ipld/dag-pb'
|
|
24
23
|
import type { AbortOptions } from '@libp2p/interface'
|
|
25
|
-
import type { ImportResult } from 'ipfs-unixfs-importer'
|
|
26
|
-
import type { Version } from 'multiformats/cid'
|
|
24
|
+
import type { ImporterOptions, ImportResult } from 'ipfs-unixfs-importer'
|
|
27
25
|
|
|
28
26
|
const log = logger('helia:unixfs:components:utils:add-link')
|
|
29
27
|
|
|
@@ -32,10 +30,8 @@ export interface AddLinkResult {
|
|
|
32
30
|
cid: CID
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
export interface AddLinkOptions extends AbortOptions {
|
|
33
|
+
export interface AddLinkOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits' | 'cidVersion'> {
|
|
36
34
|
allowOverwriting?: boolean
|
|
37
|
-
shardSplitThresholdBytes?: number
|
|
38
|
-
cidVersion?: Version
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
export async function addLink (parent: Directory, child: Required<PBLink>, blockstore: GetStore & PutStore, options: AddLinkOptions): Promise<AddLinkResult> {
|
|
@@ -55,10 +51,10 @@ export async function addLink (parent: Directory, child: Required<PBLink>, block
|
|
|
55
51
|
|
|
56
52
|
const result = await addToDirectory(parent, child, blockstore, options)
|
|
57
53
|
|
|
58
|
-
if (await isOverShardThreshold(result.node, blockstore, options
|
|
54
|
+
if (await isOverShardThreshold(result.node, blockstore, options)) {
|
|
59
55
|
log('converting directory to sharded directory')
|
|
60
56
|
|
|
61
|
-
const converted = await convertToShardedDirectory(result, blockstore)
|
|
57
|
+
const converted = await convertToShardedDirectory(result, blockstore, options)
|
|
62
58
|
result.cid = converted.cid
|
|
63
59
|
result.node = dagPB.decode(await toBuffer(blockstore.get(converted.cid, options)))
|
|
64
60
|
}
|
|
@@ -66,7 +62,7 @@ export async function addLink (parent: Directory, child: Required<PBLink>, block
|
|
|
66
62
|
return result
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore): Promise<ImportResult> => {
|
|
65
|
+
const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore, options: AddLinkOptions): Promise<ImportResult> => {
|
|
70
66
|
if (parent.node.Data == null) {
|
|
71
67
|
throw new InvalidParametersError('Invalid parent passed to convertToShardedDirectory')
|
|
72
68
|
}
|
|
@@ -78,6 +74,7 @@ const convertToShardedDirectory = async (parent: Directory, blockstore: PutStore
|
|
|
78
74
|
size: BigInt(link.Tsize ?? 0),
|
|
79
75
|
cid: link.Hash
|
|
80
76
|
})), {
|
|
77
|
+
...options,
|
|
81
78
|
mode: unixfs.mode,
|
|
82
79
|
mtime: unixfs.mtime,
|
|
83
80
|
cidVersion: parent.cid.version
|
|
@@ -130,9 +127,11 @@ const addToDirectory = async (parent: Directory, child: PBLink, blockstore: PutS
|
|
|
130
127
|
// Persist the new parent PbNode
|
|
131
128
|
const buf = dagPB.encode(parent.node)
|
|
132
129
|
const hash = await sha256.digest(buf)
|
|
130
|
+
options?.signal?.throwIfAborted()
|
|
131
|
+
|
|
133
132
|
const cid = CID.create(parent.cid.version, dagPB.code, hash)
|
|
134
133
|
|
|
135
|
-
await blockstore.put(cid, buf)
|
|
134
|
+
await blockstore.put(cid, buf, options)
|
|
136
135
|
|
|
137
136
|
return {
|
|
138
137
|
node: parent.node,
|
|
@@ -157,6 +156,7 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
|
|
|
157
156
|
|
|
158
157
|
const linkName = `${prefix}${child.Name}`
|
|
159
158
|
const existingLink = finalSegment.node.Links.find(l => (l.Name ?? '').startsWith(prefix))
|
|
159
|
+
const prefixLength = prefix.length
|
|
160
160
|
|
|
161
161
|
if (existingLink != null) {
|
|
162
162
|
log('link %s was present in shard', linkName)
|
|
@@ -175,7 +175,7 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
|
|
|
175
175
|
Hash: child.Hash,
|
|
176
176
|
Tsize: child.Tsize
|
|
177
177
|
})
|
|
178
|
-
} else if (existingLink.Name?.length ===
|
|
178
|
+
} else if (existingLink.Name?.length === prefixLength) {
|
|
179
179
|
throw new Error('Existing link was sub-shard?!')
|
|
180
180
|
} else {
|
|
181
181
|
// conflict, add a new HAMT segment
|
|
@@ -185,23 +185,29 @@ const addToShardedDirectory = async (parent: Directory, child: Required<PBLink>,
|
|
|
185
185
|
const sibling = finalSegment.node.Links.splice(index, 1)[0]
|
|
186
186
|
|
|
187
187
|
// give the sibling a new HAMT prefix
|
|
188
|
-
const siblingName = (sibling.Name ?? '').substring(
|
|
188
|
+
const siblingName = (sibling.Name ?? '').substring(prefixLength)
|
|
189
189
|
const wrapped = wrapHash(hamtHashFn)
|
|
190
190
|
const siblingHash = wrapped(uint8ArrayFromString(siblingName))
|
|
191
|
+
const hashBits = options.shardFanoutBits ?? hamtBucketBits
|
|
191
192
|
|
|
192
193
|
// discard hash bits until we reach the sub-shard depth
|
|
193
194
|
for (let i = 0; i < path.length; i++) {
|
|
194
|
-
await siblingHash.take(
|
|
195
|
+
await siblingHash.take(hashBits)
|
|
196
|
+
options?.signal?.throwIfAborted()
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
while (true) {
|
|
198
|
-
const siblingIndex = await siblingHash.take(
|
|
199
|
-
|
|
200
|
+
const siblingIndex = await siblingHash.take(hashBits)
|
|
201
|
+
options?.signal?.throwIfAborted()
|
|
202
|
+
|
|
203
|
+
const siblingPrefix = toPrefix(siblingIndex, hashBits)
|
|
200
204
|
sibling.Name = `${siblingPrefix}${siblingName}`
|
|
201
205
|
|
|
202
206
|
// calculate the target file's HAMT prefix in the new sub-shard
|
|
203
|
-
const newIndex = await hash.take(
|
|
204
|
-
|
|
207
|
+
const newIndex = await hash.take(hashBits)
|
|
208
|
+
options?.signal?.throwIfAborted()
|
|
209
|
+
|
|
210
|
+
const newPrefix = toPrefix(newIndex, hashBits)
|
|
205
211
|
|
|
206
212
|
if (siblingPrefix === newPrefix) {
|
|
207
213
|
// the two sibling names have caused another conflict - add an intermediate node to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { exporter } from 'ipfs-unixfs-exporter'
|
|
2
|
-
import { NotADirectoryError } from '../../errors.
|
|
3
|
-
import type { GetStore } from '../../unixfs.
|
|
2
|
+
import { NotADirectoryError } from '../../errors.ts'
|
|
3
|
+
import type { GetStore } from '../../unixfs.ts'
|
|
4
4
|
import type { PBNode } from '@ipld/dag-pb'
|
|
5
5
|
import type { ExporterOptions } from 'ipfs-unixfs-exporter'
|
|
6
6
|
import type { CID } from 'multiformats/cid'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as dagPb from '@ipld/dag-pb'
|
|
2
2
|
import { exporter } from 'ipfs-unixfs-exporter'
|
|
3
|
-
import { NotUnixFSError } from '../../errors.
|
|
4
|
-
import type { GetStore } from '../../unixfs.
|
|
3
|
+
import { NotUnixFSError } from '../../errors.ts'
|
|
4
|
+
import type { GetStore } from '../../unixfs.ts'
|
|
5
5
|
import type { PBNode, PBLink } from '@ipld/dag-pb'
|
|
6
6
|
import type { ExporterOptions } from 'ipfs-unixfs-exporter'
|
|
7
7
|
import type { CID } from 'multiformats/cid'
|
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
import * as dagPB from '@ipld/dag-pb'
|
|
2
2
|
import { logger } from '@libp2p/logger'
|
|
3
3
|
import { UnixFS } from 'ipfs-unixfs'
|
|
4
|
+
import { DirSharded } from 'ipfs-unixfs-importer/utils'
|
|
4
5
|
import last from 'it-last'
|
|
5
6
|
import toBuffer from 'it-to-buffer'
|
|
6
7
|
// @ts-expect-error no types
|
|
7
8
|
import SparseArray from 'sparse-array'
|
|
8
9
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
9
|
-
import { wrapHash } from './consumable-hash.
|
|
10
|
-
import { DirSharded } from './dir-sharded.js'
|
|
10
|
+
import { wrapHash } from './consumable-hash.ts'
|
|
11
11
|
import {
|
|
12
12
|
hamtHashCode,
|
|
13
13
|
hamtHashFn,
|
|
14
14
|
hamtBucketBits
|
|
15
|
-
} from './hamt-constants.
|
|
16
|
-
import { persist } from './persist.
|
|
17
|
-
import type { InfiniteHash } from './consumable-hash.
|
|
18
|
-
import type {
|
|
19
|
-
import type { GetStore, PutStore } from '../../unixfs.js'
|
|
15
|
+
} from './hamt-constants.ts'
|
|
16
|
+
import { persist } from './persist.ts'
|
|
17
|
+
import type { InfiniteHash } from './consumable-hash.ts'
|
|
18
|
+
import type { GetStore, PutStore } from '../../unixfs.ts'
|
|
20
19
|
import type { AbortOptions } from '@libp2p/interface'
|
|
21
20
|
import type { Blockstore } from 'interface-blockstore'
|
|
22
21
|
import type { Mtime } from 'ipfs-unixfs'
|
|
23
|
-
import type { ImportResult } from 'ipfs-unixfs-importer'
|
|
22
|
+
import type { ImporterOptions, ImportResult } from 'ipfs-unixfs-importer'
|
|
24
23
|
import type { CID, Version } from 'multiformats/cid'
|
|
25
24
|
|
|
26
25
|
const log = logger('helia:unixfs:commands:utils:hamt-utils')
|
|
27
26
|
|
|
28
|
-
export interface UpdateHamtDirectoryOptions extends
|
|
27
|
+
export interface UpdateHamtDirectoryOptions extends HAMTOptions {
|
|
29
28
|
cidVersion?: Version
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
export const toPrefix = (position: number): string => {
|
|
31
|
+
export const toPrefix = (position: number, bits = hamtBucketBits): string => {
|
|
32
|
+
const length = (Math.pow(2, bits) - 1).toString(16).length
|
|
33
|
+
|
|
33
34
|
return position
|
|
34
35
|
.toString(16)
|
|
35
36
|
.toUpperCase()
|
|
36
|
-
.padStart(
|
|
37
|
-
.substring(0,
|
|
37
|
+
.padStart(length, '0')
|
|
38
|
+
.substring(0, length)
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface CreateShardOptions {
|
|
@@ -57,7 +58,7 @@ export const createShard = async (blockstore: PutStore, contents: Array<{ name:
|
|
|
57
58
|
}, options)
|
|
58
59
|
|
|
59
60
|
for (let i = 0; i < contents.length; i++) {
|
|
60
|
-
await shard.
|
|
61
|
+
await shard.bucket.put(contents[i].name, {
|
|
61
62
|
size: contents[i].size,
|
|
62
63
|
cid: contents[i].cid
|
|
63
64
|
})
|
|
@@ -78,12 +79,12 @@ export interface HAMTPath {
|
|
|
78
79
|
node: dagPB.PBNode
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetStore & PutStore, options:
|
|
82
|
+
export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetStore & PutStore, options: HAMTOptions): Promise<{ cid: CID, node: dagPB.PBNode }> => {
|
|
82
83
|
// persist any metadata on the shard root
|
|
83
84
|
const shardRoot = UnixFS.unmarshal(path[0].node.Data ?? new Uint8Array(0))
|
|
84
85
|
|
|
85
86
|
// this is always the same
|
|
86
|
-
const fanout = BigInt(Math.pow(2, hamtBucketBits))
|
|
87
|
+
const fanout = BigInt(Math.pow(2, options.shardFanoutBits ?? hamtBucketBits))
|
|
87
88
|
|
|
88
89
|
// start from the leaf and ascend to the root
|
|
89
90
|
path.reverse()
|
|
@@ -145,18 +146,23 @@ export const updateShardedDirectory = async (path: HAMTPath[], blockstore: GetSt
|
|
|
145
146
|
return { cid, node }
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
export
|
|
149
|
+
export interface HAMTOptions extends AbortOptions, Pick<ImporterOptions, 'shardFanoutBits' | 'shardSplitStrategy' | 'shardSplitThresholdBytes'> {
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const recreateShardedDirectory = async (cid: CID, fileName: string, blockstore: Pick<Blockstore, 'get'>, options: HAMTOptions): Promise<{ path: HAMTPath[], hash: InfiniteHash }> => {
|
|
149
154
|
const wrapped = wrapHash(hamtHashFn)
|
|
150
155
|
const hash = wrapped(uint8ArrayFromString(fileName))
|
|
151
156
|
const path: HAMTPath[] = []
|
|
157
|
+
const hashBits = options.shardFanoutBits ?? hamtBucketBits
|
|
152
158
|
|
|
153
159
|
// descend the HAMT, loading each layer as we head towards the target child
|
|
154
160
|
while (true) {
|
|
155
161
|
const block = await toBuffer(blockstore.get(cid, options))
|
|
156
162
|
const node = dagPB.decode(block)
|
|
157
163
|
const children = new SparseArray()
|
|
158
|
-
const index = await hash.take(
|
|
159
|
-
const prefix = toPrefix(index)
|
|
164
|
+
const index = await hash.take(hashBits)
|
|
165
|
+
const prefix = toPrefix(index, hashBits)
|
|
160
166
|
|
|
161
167
|
path.push({
|
|
162
168
|
prefix,
|
|
@@ -171,11 +177,11 @@ export const recreateShardedDirectory = async (cid: CID, fileName: string, block
|
|
|
171
177
|
for (const link of node.Links) {
|
|
172
178
|
const linkName = link.Name ?? ''
|
|
173
179
|
|
|
174
|
-
if (linkName.length <
|
|
180
|
+
if (linkName.length < prefix.length) {
|
|
175
181
|
throw new Error('Invalid HAMT - link name was too short')
|
|
176
182
|
}
|
|
177
183
|
|
|
178
|
-
const position = parseInt(linkName.substring(0,
|
|
184
|
+
const position = parseInt(linkName.substring(0, prefix.length), 16)
|
|
179
185
|
children.set(position, true)
|
|
180
186
|
|
|
181
187
|
// we found the child we are looking for
|
|
@@ -186,17 +192,17 @@ export const recreateShardedDirectory = async (cid: CID, fileName: string, block
|
|
|
186
192
|
|
|
187
193
|
if (childLink == null) {
|
|
188
194
|
log('no link found with prefix %s for %s', prefix, fileName)
|
|
189
|
-
// hash.untake(
|
|
195
|
+
// hash.untake(hashBits)
|
|
190
196
|
break
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
const linkName = childLink.Name ?? ''
|
|
194
200
|
|
|
195
|
-
if (linkName.length <
|
|
201
|
+
if (linkName.length < prefix.length) {
|
|
196
202
|
throw new Error('Invalid HAMT - link name was too short')
|
|
197
203
|
}
|
|
198
204
|
|
|
199
|
-
if (linkName.length ===
|
|
205
|
+
if (linkName.length === prefix.length) {
|
|
200
206
|
// found sub-shard
|
|
201
207
|
cid = childLink.Hash
|
|
202
208
|
log('descend into sub-shard with prefix %s', linkName)
|
|
@@ -1,31 +1,54 @@
|
|
|
1
1
|
import * as dagPb from '@ipld/dag-pb'
|
|
2
|
+
import { InvalidParametersError } from '@libp2p/interface'
|
|
2
3
|
import { UnixFS } from 'ipfs-unixfs'
|
|
4
|
+
import { dataFieldSerializedSize, linkSerializedSize, utf8ByteLength } from 'ipfs-unixfs-importer/utils'
|
|
3
5
|
import toBuffer from 'it-to-buffer'
|
|
4
|
-
import {
|
|
6
|
+
import { DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES } from '../../constants.ts'
|
|
7
|
+
import { hamtBucketBits } from './hamt-constants.ts'
|
|
5
8
|
import type { GetStore } from '../../unixfs.js'
|
|
6
9
|
import type { PBNode } from '@ipld/dag-pb'
|
|
7
10
|
import type { AbortOptions } from '@libp2p/interface'
|
|
11
|
+
import type { ImporterOptions, ShardSplitStrategy } from 'ipfs-unixfs-importer'
|
|
12
|
+
|
|
13
|
+
export interface IsOverShardThresholdOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits'> {
|
|
14
|
+
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
18
|
* Estimate node size only based on DAGLink name and CID byte lengths
|
|
11
19
|
* https://github.com/ipfs/go-unixfsnode/blob/37b47f1f917f1b2f54c207682f38886e49896ef9/data/builder/directory.go#L81-L96
|
|
12
20
|
*
|
|
13
|
-
* If the node is a hamt sharded directory the calculation is based on if it was
|
|
21
|
+
* If the node is a hamt sharded directory the calculation is based on if it was
|
|
22
|
+
* a regular directory.
|
|
14
23
|
*/
|
|
15
|
-
export async function isOverShardThreshold (node: PBNode, blockstore: GetStore,
|
|
24
|
+
export async function isOverShardThreshold (node: PBNode, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<boolean> {
|
|
16
25
|
if (node.Data == null) {
|
|
17
26
|
throw new Error('DagPB node had no data')
|
|
18
27
|
}
|
|
19
28
|
|
|
29
|
+
const threshold = options.shardSplitThresholdBytes ?? DEFAULT_SHARD_SPLIT_THRESHOLD_BYTES
|
|
30
|
+
let strategy: ShardSplitStrategy = options.shardSplitStrategy ?? 'links-bytes'
|
|
31
|
+
|
|
32
|
+
if (options.profile === 'unixfs-v0-2015') {
|
|
33
|
+
strategy = 'links-bytes'
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
const unixfs = UnixFS.unmarshal(node.Data)
|
|
21
|
-
|
|
37
|
+
|
|
38
|
+
if (!unixfs.isDirectory()) {
|
|
39
|
+
throw new Error('Can only estimate the size of directories or shards')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (strategy !== 'links-bytes' && strategy !== 'block-bytes') {
|
|
43
|
+
throw new InvalidParametersError(`Invalid shard split threshold "${strategy}"`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let size: number = 0
|
|
22
47
|
|
|
23
48
|
if (unixfs.type === 'directory') {
|
|
24
|
-
size = estimateNodeSize(node)
|
|
49
|
+
size = strategy === 'links-bytes' ? estimateNodeSize(node) : calculateNodeSize(unixfs, node)
|
|
25
50
|
} else if (unixfs.type === 'hamt-sharded-directory') {
|
|
26
|
-
size = await estimateShardSize(node, 0, threshold, blockstore, options)
|
|
27
|
-
} else {
|
|
28
|
-
throw new Error('Can only estimate the size of directories or shards')
|
|
51
|
+
size = strategy === 'links-bytes' ? await estimateShardSize(node, 0, threshold, blockstore, options) : await calculateShardSize(node, dataFieldSerializedSize(unixfs.mode, unixfs.mtime), threshold, blockstore, options)
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
return size > threshold
|
|
@@ -37,14 +60,26 @@ function estimateNodeSize (node: PBNode): number {
|
|
|
37
60
|
// estimate size only based on DAGLink name and CID byte lengths
|
|
38
61
|
// https://github.com/ipfs/go-unixfsnode/blob/37b47f1f917f1b2f54c207682f38886e49896ef9/data/builder/directory.go#L81-L96
|
|
39
62
|
for (const link of node.Links) {
|
|
40
|
-
size += (link.Name ?? '')
|
|
41
|
-
size += link.Hash.
|
|
63
|
+
size += utf8ByteLength(link.Name ?? '')
|
|
64
|
+
size += link.Hash.byteLength
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
return size
|
|
45
68
|
}
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
function calculateNodeSize (unixfs: UnixFS, node: PBNode): number {
|
|
71
|
+
let size = dataFieldSerializedSize(unixfs.mode, unixfs.mtime)
|
|
72
|
+
|
|
73
|
+
for (const link of node.Links) {
|
|
74
|
+
size += linkSerializedSize(
|
|
75
|
+
utf8ByteLength(link.Name ?? ''), link.Hash.byteLength, Number(link.Tsize ?? 0)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return size
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function estimateShardSize (node: PBNode, current: number, max: number, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<number> {
|
|
48
83
|
if (current > max) {
|
|
49
84
|
return max
|
|
50
85
|
}
|
|
@@ -59,13 +94,15 @@ async function estimateShardSize (node: PBNode, current: number, max: number, bl
|
|
|
59
94
|
return current
|
|
60
95
|
}
|
|
61
96
|
|
|
97
|
+
const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
|
|
98
|
+
|
|
62
99
|
for (const link of node.Links) {
|
|
63
100
|
let name = link.Name ?? ''
|
|
64
101
|
|
|
65
102
|
// remove hamt hash prefix from name
|
|
66
|
-
name = name.substring(
|
|
103
|
+
name = name.substring(prefixLength)
|
|
67
104
|
|
|
68
|
-
current += name
|
|
105
|
+
current += utf8ByteLength(name)
|
|
69
106
|
current += link.Hash.bytes.byteLength
|
|
70
107
|
|
|
71
108
|
if (link.Hash.code === dagPb.code) {
|
|
@@ -78,3 +115,41 @@ async function estimateShardSize (node: PBNode, current: number, max: number, bl
|
|
|
78
115
|
|
|
79
116
|
return current
|
|
80
117
|
}
|
|
118
|
+
|
|
119
|
+
async function calculateShardSize (node: PBNode, current: number, max: number, blockstore: GetStore, options: IsOverShardThresholdOptions): Promise<number> {
|
|
120
|
+
if (current > max) {
|
|
121
|
+
return max
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (node.Data == null) {
|
|
125
|
+
return current
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const unixfs = UnixFS.unmarshal(node.Data)
|
|
129
|
+
|
|
130
|
+
if (!unixfs.isDirectory()) {
|
|
131
|
+
return current
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
|
|
135
|
+
|
|
136
|
+
for (const link of node.Links) {
|
|
137
|
+
let name = link.Name ?? ''
|
|
138
|
+
|
|
139
|
+
// remove hamt hash prefix from name
|
|
140
|
+
name = name.substring(prefixLength)
|
|
141
|
+
|
|
142
|
+
current += linkSerializedSize(
|
|
143
|
+
utf8ByteLength(name), link.Hash.byteLength, Number(link.Tsize ?? 0)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if (link.Hash.code === dagPb.code) {
|
|
147
|
+
const block = await toBuffer(blockstore.get(link.Hash, options))
|
|
148
|
+
const node = dagPb.decode(block)
|
|
149
|
+
|
|
150
|
+
current += await calculateShardSize(node, current, max, blockstore, options)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return current
|
|
155
|
+
}
|
|
@@ -2,14 +2,14 @@ import * as dagPb from '@ipld/dag-pb'
|
|
|
2
2
|
import { CID } from 'multiformats/cid'
|
|
3
3
|
import { sha256 } from 'multiformats/hashes/sha2'
|
|
4
4
|
import { DEFAULT_CID_VERSION } from '../../constants.ts'
|
|
5
|
-
import type { PutStore } from '../../unixfs.
|
|
5
|
+
import type { PutStore } from '../../unixfs.ts'
|
|
6
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
6
7
|
import type { Version as CIDVersion } from 'multiformats/cid'
|
|
7
8
|
import type { BlockCodec } from 'multiformats/codecs/interface'
|
|
8
9
|
|
|
9
|
-
export interface PersistOptions {
|
|
10
|
+
export interface PersistOptions extends AbortOptions {
|
|
10
11
|
codec?: BlockCodec<any, any>
|
|
11
12
|
cidVersion?: CIDVersion
|
|
12
|
-
signal?: AbortSignal
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const persist = async (buffer: Uint8Array, blockstore: PutStore, options: PersistOptions): Promise<CID> => {
|
|
@@ -2,27 +2,26 @@ import * as dagPB from '@ipld/dag-pb'
|
|
|
2
2
|
import { logger } from '@libp2p/logger'
|
|
3
3
|
import { UnixFS } from 'ipfs-unixfs'
|
|
4
4
|
import { exporter } from 'ipfs-unixfs-exporter'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { InvalidParametersError, InvalidPBNodeError } from '../../errors.ts'
|
|
6
|
+
import { hamtBucketBits } from './hamt-constants.ts'
|
|
7
7
|
import {
|
|
8
8
|
recreateShardedDirectory,
|
|
9
|
-
|
|
10
9
|
updateShardedDirectory
|
|
11
|
-
} from './hamt-utils.
|
|
12
|
-
import { isOverShardThreshold } from './is-over-shard-threshold.
|
|
13
|
-
import { persist } from './persist.
|
|
14
|
-
import type { Directory } from './cid-to-directory.
|
|
15
|
-
import type { UpdateHamtDirectoryOptions } from './hamt-utils.
|
|
16
|
-
import type { GetStore, PutStore } from '../../unixfs.
|
|
10
|
+
} from './hamt-utils.ts'
|
|
11
|
+
import { isOverShardThreshold } from './is-over-shard-threshold.ts'
|
|
12
|
+
import { persist } from './persist.ts'
|
|
13
|
+
import type { Directory } from './cid-to-directory.ts'
|
|
14
|
+
import type { UpdateHamtDirectoryOptions } from './hamt-utils.ts'
|
|
15
|
+
import type { GetStore, PutStore } from '../../unixfs.ts'
|
|
17
16
|
import type { PBNode } from '@ipld/dag-pb'
|
|
18
17
|
import type { AbortOptions } from '@libp2p/interface'
|
|
19
|
-
import type {
|
|
18
|
+
import type { ImporterOptions } from 'ipfs-unixfs-importer'
|
|
19
|
+
import type { CID } from 'multiformats/cid'
|
|
20
20
|
|
|
21
21
|
const log = logger('helia:unixfs:utils:remove-link')
|
|
22
22
|
|
|
23
|
-
export interface RmLinkOptions extends AbortOptions {
|
|
24
|
-
|
|
25
|
-
cidVersion?: Version
|
|
23
|
+
export interface RmLinkOptions extends AbortOptions, Pick<ImporterOptions, 'profile' | 'shardSplitThresholdBytes' | 'shardSplitStrategy' | 'shardFanoutBits' | 'cidVersion'> {
|
|
24
|
+
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
export interface RemoveLinkResult {
|
|
@@ -42,7 +41,7 @@ export async function removeLink (parent: Directory, name: string, blockstore: P
|
|
|
42
41
|
|
|
43
42
|
const result = await removeFromShardedDirectory(parent, name, blockstore, options)
|
|
44
43
|
|
|
45
|
-
if (!(await isOverShardThreshold(result.node, blockstore, options
|
|
44
|
+
if (!(await isOverShardThreshold(result.node, blockstore, options))) {
|
|
46
45
|
log('converting shard to flat directory %c', parent.cid)
|
|
47
46
|
|
|
48
47
|
return convertToFlatDirectory(result, blockstore, options)
|
|
@@ -84,13 +83,14 @@ const removeFromShardedDirectory = async (parent: Directory, name: string, block
|
|
|
84
83
|
throw new Error('Invalid HAMT, could not generate path')
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
const
|
|
86
|
+
const prefixLength = (Math.pow(2, options.shardFanoutBits ?? hamtBucketBits) - 1).toString(16).length
|
|
87
|
+
const linkName = finalSegment.node.Links.filter(l => (l.Name ?? '').substring(prefixLength) === name).map(l => l.Name).pop()
|
|
88
88
|
|
|
89
89
|
if (linkName == null) {
|
|
90
90
|
throw new Error('File not found')
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const prefix = linkName.substring(0,
|
|
93
|
+
const prefix = linkName.substring(0, prefixLength)
|
|
94
94
|
const index = parseInt(prefix, 16)
|
|
95
95
|
|
|
96
96
|
// remove the file from the shard
|
|
@@ -124,7 +124,7 @@ const removeFromShardedDirectory = async (parent: Directory, name: string, block
|
|
|
124
124
|
nextSegment.node.Links = nextSegment.node.Links.filter(l => !(l.Name ?? '').startsWith(nextSegment.prefix))
|
|
125
125
|
nextSegment.node.Links.push({
|
|
126
126
|
Hash: link.Hash,
|
|
127
|
-
Name: `${nextSegment.prefix}${(link.Name ?? '').substring(
|
|
127
|
+
Name: `${nextSegment.prefix}${(link.Name ?? '').substring(prefixLength)}`,
|
|
128
128
|
Tsize: link.Tsize
|
|
129
129
|
})
|
|
130
130
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { logger } from '@libp2p/logger'
|
|
2
2
|
import { walkPath } from 'ipfs-unixfs-exporter'
|
|
3
3
|
import all from 'it-all'
|
|
4
|
-
import { DoesNotExistError } from '../../errors.
|
|
5
|
-
import { addLink } from './add-link.
|
|
6
|
-
import { cidToDirectory } from './cid-to-directory.
|
|
7
|
-
import { cidToPBLink } from './cid-to-pblink.
|
|
8
|
-
import type { GetStore, PutStore } from '../../unixfs.
|
|
4
|
+
import { DoesNotExistError } from '../../errors.ts'
|
|
5
|
+
import { addLink } from './add-link.ts'
|
|
6
|
+
import { cidToDirectory } from './cid-to-directory.ts'
|
|
7
|
+
import { cidToPBLink } from './cid-to-pblink.ts'
|
|
8
|
+
import type { GetStore, PutStore } from '../../unixfs.ts'
|
|
9
9
|
import type { AbortOptions } from '@libp2p/interface'
|
|
10
10
|
import type { PathEntry } from 'ipfs-unixfs-exporter'
|
|
11
11
|
import type { CID } from 'multiformats/cid'
|