@helia/unixfs 0.0.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/LICENSE +4 -0
- package/README.md +53 -0
- package/dist/index.min.js +3 -0
- package/dist/src/commands/add.d.ts +6 -0
- package/dist/src/commands/add.d.ts.map +1 -0
- package/dist/src/commands/add.js +38 -0
- package/dist/src/commands/add.js.map +1 -0
- package/dist/src/commands/cat.d.ts +5 -0
- package/dist/src/commands/cat.d.ts.map +1 -0
- package/dist/src/commands/cat.js +22 -0
- package/dist/src/commands/cat.js.map +1 -0
- package/dist/src/commands/chmod.d.ts +5 -0
- package/dist/src/commands/chmod.d.ts.map +1 -0
- package/dist/src/commands/chmod.js +108 -0
- package/dist/src/commands/chmod.js.map +1 -0
- package/dist/src/commands/cp.d.ts +5 -0
- package/dist/src/commands/cp.d.ts.map +1 -0
- package/dist/src/commands/cp.js +28 -0
- package/dist/src/commands/cp.js.map +1 -0
- package/dist/src/commands/ls.d.ts +5 -0
- package/dist/src/commands/ls.d.ts.map +1 -0
- package/dist/src/commands/ls.js +26 -0
- package/dist/src/commands/ls.js.map +1 -0
- package/dist/src/commands/mkdir.d.ts +5 -0
- package/dist/src/commands/mkdir.d.ts.map +1 -0
- package/dist/src/commands/mkdir.js +53 -0
- package/dist/src/commands/mkdir.js.map +1 -0
- package/dist/src/commands/rm.d.ts +5 -0
- package/dist/src/commands/rm.d.ts.map +1 -0
- package/dist/src/commands/rm.js +19 -0
- package/dist/src/commands/rm.js.map +1 -0
- package/dist/src/commands/stat.d.ts +5 -0
- package/dist/src/commands/stat.d.ts.map +1 -0
- package/dist/src/commands/stat.js +108 -0
- package/dist/src/commands/stat.js.map +1 -0
- package/dist/src/commands/touch.d.ts +5 -0
- package/dist/src/commands/touch.d.ts.map +1 -0
- package/dist/src/commands/touch.js +111 -0
- package/dist/src/commands/touch.js.map +1 -0
- package/dist/src/commands/utils/add-link.d.ts +21 -0
- package/dist/src/commands/utils/add-link.d.ts.map +1 -0
- package/dist/src/commands/utils/add-link.js +224 -0
- package/dist/src/commands/utils/add-link.js.map +1 -0
- package/dist/src/commands/utils/cid-to-directory.d.ts +10 -0
- package/dist/src/commands/utils/cid-to-directory.d.ts.map +1 -0
- package/dist/src/commands/utils/cid-to-directory.js +13 -0
- package/dist/src/commands/utils/cid-to-directory.js.map +1 -0
- package/dist/src/commands/utils/cid-to-pblink.d.ts +6 -0
- package/dist/src/commands/utils/cid-to-pblink.d.ts.map +1 -0
- package/dist/src/commands/utils/cid-to-pblink.js +19 -0
- package/dist/src/commands/utils/cid-to-pblink.js.map +1 -0
- package/dist/src/commands/utils/dir-sharded.d.ts +67 -0
- package/dist/src/commands/utils/dir-sharded.d.ts.map +1 -0
- package/dist/src/commands/utils/dir-sharded.js +136 -0
- package/dist/src/commands/utils/dir-sharded.js.map +1 -0
- package/dist/src/commands/utils/errors.d.ts +17 -0
- package/dist/src/commands/utils/errors.d.ts.map +1 -0
- package/dist/src/commands/utils/errors.js +27 -0
- package/dist/src/commands/utils/errors.js.map +1 -0
- package/dist/src/commands/utils/hamt-constants.d.ts +4 -0
- package/dist/src/commands/utils/hamt-constants.d.ts.map +1 -0
- package/dist/src/commands/utils/hamt-constants.js +13 -0
- package/dist/src/commands/utils/hamt-constants.js.map +1 -0
- package/dist/src/commands/utils/hamt-utils.d.ts +37 -0
- package/dist/src/commands/utils/hamt-utils.d.ts.map +1 -0
- package/dist/src/commands/utils/hamt-utils.js +202 -0
- package/dist/src/commands/utils/hamt-utils.js.map +1 -0
- package/dist/src/commands/utils/persist.d.ts +10 -0
- package/dist/src/commands/utils/persist.d.ts.map +1 -0
- package/dist/src/commands/utils/persist.js +12 -0
- package/dist/src/commands/utils/persist.js.map +1 -0
- package/dist/src/commands/utils/remove-link.d.ts +11 -0
- package/dist/src/commands/utils/remove-link.d.ts.map +1 -0
- package/dist/src/commands/utils/remove-link.js +92 -0
- package/dist/src/commands/utils/remove-link.js.map +1 -0
- package/dist/src/commands/utils/resolve.d.ts +28 -0
- package/dist/src/commands/utils/resolve.d.ts.map +1 -0
- package/dist/src/commands/utils/resolve.js +85 -0
- package/dist/src/commands/utils/resolve.js.map +1 -0
- package/dist/src/index.d.ts +97 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +48 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +166 -0
- package/src/commands/add.ts +46 -0
- package/src/commands/cat.ts +31 -0
- package/src/commands/chmod.ts +133 -0
- package/src/commands/cp.ts +41 -0
- package/src/commands/ls.ts +36 -0
- package/src/commands/mkdir.ts +71 -0
- package/src/commands/rm.ts +31 -0
- package/src/commands/stat.ts +137 -0
- package/src/commands/touch.ts +136 -0
- package/src/commands/utils/add-link.ts +319 -0
- package/src/commands/utils/cid-to-directory.ts +23 -0
- package/src/commands/utils/cid-to-pblink.ts +26 -0
- package/src/commands/utils/dir-sharded.ts +219 -0
- package/src/commands/utils/errors.ts +31 -0
- package/src/commands/utils/hamt-constants.ts +14 -0
- package/src/commands/utils/hamt-utils.ts +285 -0
- package/src/commands/utils/persist.ts +22 -0
- package/src/commands/utils/remove-link.ts +151 -0
- package/src/commands/utils/resolve.ts +130 -0
- package/src/index.ts +174 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { encode, prepare } from '@ipld/dag-pb'
|
|
2
|
+
import { UnixFS } from 'ipfs-unixfs'
|
|
3
|
+
import { persist } from './persist.js'
|
|
4
|
+
import { createHAMT, Bucket, BucketChild } from 'hamt-sharding'
|
|
5
|
+
import {
|
|
6
|
+
hamtHashCode,
|
|
7
|
+
hamtHashFn,
|
|
8
|
+
hamtBucketBits
|
|
9
|
+
} from './hamt-constants.js'
|
|
10
|
+
import type { CID, Version } from 'multiformats/cid'
|
|
11
|
+
import type { PBNode } from '@ipld/dag-pb/interface'
|
|
12
|
+
import type { Mtime } from 'ipfs-unixfs'
|
|
13
|
+
import type { BlockCodec } from 'multiformats/codecs/interface'
|
|
14
|
+
import type { Blockstore } from 'ipfs-unixfs-importer'
|
|
15
|
+
|
|
16
|
+
export interface ImportResult {
|
|
17
|
+
cid: CID
|
|
18
|
+
node: PBNode
|
|
19
|
+
size: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DirContents {
|
|
23
|
+
cid?: CID
|
|
24
|
+
size?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DirOptions {
|
|
28
|
+
mtime?: Mtime
|
|
29
|
+
mode?: number
|
|
30
|
+
codec?: BlockCodec<any, any>
|
|
31
|
+
cidVersion?: Version
|
|
32
|
+
onlyHash?: boolean
|
|
33
|
+
signal?: AbortSignal
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DirProps {
|
|
37
|
+
root: boolean
|
|
38
|
+
dir: boolean
|
|
39
|
+
path: string
|
|
40
|
+
dirty: boolean
|
|
41
|
+
flat: boolean
|
|
42
|
+
parent?: Dir
|
|
43
|
+
parentKey?: string
|
|
44
|
+
unixfs?: UnixFS
|
|
45
|
+
mode?: number
|
|
46
|
+
mtime?: Mtime
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export abstract class Dir {
|
|
50
|
+
protected options: DirOptions
|
|
51
|
+
protected root: boolean
|
|
52
|
+
protected dir: boolean
|
|
53
|
+
protected path: string
|
|
54
|
+
protected dirty: boolean
|
|
55
|
+
protected flat: boolean
|
|
56
|
+
protected parent?: Dir
|
|
57
|
+
protected parentKey?: string
|
|
58
|
+
protected unixfs?: UnixFS
|
|
59
|
+
protected mode?: number
|
|
60
|
+
public mtime?: Mtime
|
|
61
|
+
protected cid?: CID
|
|
62
|
+
protected size?: number
|
|
63
|
+
|
|
64
|
+
constructor (props: DirProps, options: DirOptions) {
|
|
65
|
+
this.options = options ?? {}
|
|
66
|
+
this.root = props.root
|
|
67
|
+
this.dir = props.dir
|
|
68
|
+
this.path = props.path
|
|
69
|
+
this.dirty = props.dirty
|
|
70
|
+
this.flat = props.flat
|
|
71
|
+
this.parent = props.parent
|
|
72
|
+
this.parentKey = props.parentKey
|
|
73
|
+
this.unixfs = props.unixfs
|
|
74
|
+
this.mode = props.mode
|
|
75
|
+
this.mtime = props.mtime
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class DirSharded extends Dir {
|
|
80
|
+
public _bucket: Bucket<DirContents>
|
|
81
|
+
|
|
82
|
+
constructor (props: DirProps, options: DirOptions) {
|
|
83
|
+
super(props, options)
|
|
84
|
+
|
|
85
|
+
/** @type {Bucket<DirContents>} */
|
|
86
|
+
this._bucket = createHAMT({
|
|
87
|
+
hashFn: hamtHashFn,
|
|
88
|
+
bits: hamtBucketBits
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async put (name: string, value: DirContents): Promise<void> {
|
|
93
|
+
await this._bucket.put(name, value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async get (name: string): Promise<DirContents | undefined> {
|
|
97
|
+
return await this._bucket.get(name)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
childCount (): number {
|
|
101
|
+
return this._bucket.leafCount()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
directChildrenCount (): number {
|
|
105
|
+
return this._bucket.childrenCount()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onlyChild (): Bucket<DirContents> | BucketChild<DirContents> {
|
|
109
|
+
return this._bucket.onlyChild()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async * eachChildSeries (): AsyncGenerator<{ key: string, child: DirContents }> {
|
|
113
|
+
for await (const { key, value } of this._bucket.eachLeafSeries()) {
|
|
114
|
+
yield {
|
|
115
|
+
key,
|
|
116
|
+
child: value
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async * flush (blockstore: Blockstore): AsyncIterable<ImportResult> {
|
|
122
|
+
yield * flush(this._bucket, blockstore, this, this.options)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function * flush (bucket: Bucket<any>, blockstore: Blockstore, shardRoot: any, options: DirOptions): AsyncIterable<ImportResult> {
|
|
127
|
+
const children = bucket._children
|
|
128
|
+
const links = []
|
|
129
|
+
let childrenSize = 0
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < children.length; i++) {
|
|
132
|
+
const child = children.get(i)
|
|
133
|
+
|
|
134
|
+
if (child == null) {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const labelPrefix = i.toString(16).toUpperCase().padStart(2, '0')
|
|
139
|
+
|
|
140
|
+
if (child instanceof Bucket) {
|
|
141
|
+
let shard: ImportResult | undefined
|
|
142
|
+
|
|
143
|
+
for await (const subShard of flush(child, blockstore, null, options)) {
|
|
144
|
+
shard = subShard
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (shard == null) {
|
|
148
|
+
throw new Error('Could not flush sharded directory, no subshard found')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
links.push({
|
|
152
|
+
Name: labelPrefix,
|
|
153
|
+
Tsize: shard.size,
|
|
154
|
+
Hash: shard.cid
|
|
155
|
+
})
|
|
156
|
+
childrenSize += shard.size
|
|
157
|
+
} else if (typeof child.value.flush === 'function') {
|
|
158
|
+
const dir = child.value
|
|
159
|
+
let flushedDir
|
|
160
|
+
|
|
161
|
+
for await (const entry of dir.flush(blockstore)) {
|
|
162
|
+
flushedDir = entry
|
|
163
|
+
|
|
164
|
+
yield flushedDir
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const label = labelPrefix + child.key
|
|
168
|
+
links.push({
|
|
169
|
+
Name: label,
|
|
170
|
+
Tsize: flushedDir.size,
|
|
171
|
+
Hash: flushedDir.cid
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
childrenSize += flushedDir.size // eslint-disable-line @typescript-eslint/restrict-plus-operands
|
|
175
|
+
} else {
|
|
176
|
+
const value = child.value
|
|
177
|
+
|
|
178
|
+
if (value.cid == null) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const label = labelPrefix + child.key
|
|
183
|
+
const size = value.size
|
|
184
|
+
|
|
185
|
+
links.push({
|
|
186
|
+
Name: label,
|
|
187
|
+
Tsize: size,
|
|
188
|
+
Hash: value.cid
|
|
189
|
+
})
|
|
190
|
+
childrenSize += size ?? 0 // eslint-disable-line @typescript-eslint/restrict-plus-operands
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// go-ipfs uses little endian, that's why we have to
|
|
195
|
+
// reverse the bit field before storing it
|
|
196
|
+
const data = Uint8Array.from(children.bitField().reverse())
|
|
197
|
+
const dir = new UnixFS({
|
|
198
|
+
type: 'hamt-sharded-directory',
|
|
199
|
+
data,
|
|
200
|
+
fanout: bucket.tableSize(),
|
|
201
|
+
hashType: hamtHashCode,
|
|
202
|
+
mtime: shardRoot?.mtime,
|
|
203
|
+
mode: shardRoot?.mode
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const node = {
|
|
207
|
+
Data: dir.marshal(),
|
|
208
|
+
Links: links
|
|
209
|
+
}
|
|
210
|
+
const buffer = encode(prepare(node))
|
|
211
|
+
const cid = await persist(buffer, blockstore, options)
|
|
212
|
+
const size = buffer.length + childrenSize
|
|
213
|
+
|
|
214
|
+
yield {
|
|
215
|
+
cid,
|
|
216
|
+
node,
|
|
217
|
+
size
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HeliaError } from '@helia/interface/errors'
|
|
2
|
+
|
|
3
|
+
export class NotUnixFSError extends HeliaError {
|
|
4
|
+
constructor (message = 'not a Unixfs node') {
|
|
5
|
+
super(message, 'NotUnixFSError', 'ERR_NOT_UNIXFS')
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class InvalidPBNodeError extends HeliaError {
|
|
10
|
+
constructor (message = 'invalid PBNode') {
|
|
11
|
+
super(message, 'InvalidPBNodeError', 'ERR_INVALID_PBNODE')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class UnknownError extends HeliaError {
|
|
16
|
+
constructor (message = 'unknown error') {
|
|
17
|
+
super(message, 'InvalidPBNodeError', 'ERR_UNKNOWN_ERROR')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class AlreadyExistsError extends HeliaError {
|
|
22
|
+
constructor (message = 'path already exists') {
|
|
23
|
+
super(message, 'NotUnixFSError', 'ERR_ALREADY_EXISTS')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class DoesNotExistError extends HeliaError {
|
|
28
|
+
constructor (message = 'path does not exist') {
|
|
29
|
+
super(message, 'NotUnixFSError', 'ERR_DOES_NOT_EXIST')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { murmur3128 } from '@multiformats/murmur3'
|
|
2
|
+
|
|
3
|
+
export const hamtHashCode = murmur3128.code
|
|
4
|
+
export const hamtBucketBits = 8
|
|
5
|
+
|
|
6
|
+
export async function hamtHashFn (buf: Uint8Array): Promise<Uint8Array> {
|
|
7
|
+
return (await murmur3128.encode(buf))
|
|
8
|
+
// Murmur3 outputs 128 bit but, accidentally, IPFS Go's
|
|
9
|
+
// implementation only uses the first 64, so we must do the same
|
|
10
|
+
// for parity..
|
|
11
|
+
.subarray(0, 8)
|
|
12
|
+
// Invert buffer because that's how Go impl does it
|
|
13
|
+
.reverse()
|
|
14
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as dagPB from '@ipld/dag-pb'
|
|
2
|
+
import {
|
|
3
|
+
Bucket,
|
|
4
|
+
createHAMT
|
|
5
|
+
} from 'hamt-sharding'
|
|
6
|
+
import { DirSharded } from './dir-sharded.js'
|
|
7
|
+
import { logger } from '@libp2p/logger'
|
|
8
|
+
import { UnixFS } from 'ipfs-unixfs'
|
|
9
|
+
import last from 'it-last'
|
|
10
|
+
import { CID } from 'multiformats/cid'
|
|
11
|
+
import {
|
|
12
|
+
hamtHashCode,
|
|
13
|
+
hamtHashFn,
|
|
14
|
+
hamtBucketBits
|
|
15
|
+
} from './hamt-constants.js'
|
|
16
|
+
import type { PBLink, PBNode } from '@ipld/dag-pb/interface'
|
|
17
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
18
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
19
|
+
import type { Mtime } from 'ipfs-unixfs'
|
|
20
|
+
import type { Directory } from './cid-to-directory.js'
|
|
21
|
+
import type { AbortOptions } from '@libp2p/interfaces'
|
|
22
|
+
import type { ImportResult } from 'ipfs-unixfs-importer'
|
|
23
|
+
|
|
24
|
+
const log = logger('helia:unixfs:commands:utils:hamt-utils')
|
|
25
|
+
|
|
26
|
+
export interface UpdateHamtResult {
|
|
27
|
+
node: PBNode
|
|
28
|
+
cid: CID
|
|
29
|
+
size: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const updateHamtDirectory = async (parent: Directory, blockstore: Blockstore, links: PBLink[], bucket: Bucket<any>, options: AbortOptions): Promise<UpdateHamtResult> => {
|
|
33
|
+
if (parent.node.Data == null) {
|
|
34
|
+
throw new Error('Could not update HAMT directory because parent had no data')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// update parent with new bit field
|
|
38
|
+
const data = Uint8Array.from(bucket._children.bitField().reverse())
|
|
39
|
+
const node = UnixFS.unmarshal(parent.node.Data)
|
|
40
|
+
const dir = new UnixFS({
|
|
41
|
+
type: 'hamt-sharded-directory',
|
|
42
|
+
data,
|
|
43
|
+
fanout: bucket.tableSize(),
|
|
44
|
+
hashType: hamtHashCode,
|
|
45
|
+
mode: node.mode,
|
|
46
|
+
mtime: node.mtime
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
parent.node = {
|
|
50
|
+
Data: dir.marshal(),
|
|
51
|
+
Links: links.sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''))
|
|
52
|
+
}
|
|
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
|
+
|
|
57
|
+
await blockstore.put(cid, buf, options)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
node: parent.node,
|
|
61
|
+
cid,
|
|
62
|
+
size: links.reduce((sum, link) => sum + (link.Tsize ?? 0), buf.length)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const recreateHamtLevel = async (blockstore: Blockstore, links: PBLink[], rootBucket: Bucket<any>, parentBucket: Bucket<any>, positionAtParent: number, options: AbortOptions): Promise<Bucket<any>> => {
|
|
67
|
+
// recreate this level of the HAMT
|
|
68
|
+
const bucket = new Bucket({
|
|
69
|
+
hash: rootBucket._options.hash,
|
|
70
|
+
bits: rootBucket._options.bits
|
|
71
|
+
}, parentBucket, positionAtParent)
|
|
72
|
+
parentBucket._putObjectAt(positionAtParent, bucket)
|
|
73
|
+
|
|
74
|
+
await addLinksToHamtBucket(blockstore, links, bucket, rootBucket, options)
|
|
75
|
+
|
|
76
|
+
return bucket
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const recreateInitialHamtLevel = async (links: PBLink[]): Promise<Bucket<any>> => {
|
|
80
|
+
const bucket = createHAMT({
|
|
81
|
+
hashFn: hamtHashFn,
|
|
82
|
+
bits: hamtBucketBits
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// populate sub bucket but do not recurse as we do not want to pull whole shard in
|
|
86
|
+
await Promise.all(
|
|
87
|
+
links.map(async link => {
|
|
88
|
+
const linkName = (link.Name ?? '')
|
|
89
|
+
|
|
90
|
+
if (linkName.length === 2) {
|
|
91
|
+
const pos = parseInt(linkName, 16)
|
|
92
|
+
|
|
93
|
+
const subBucket = new Bucket({
|
|
94
|
+
hash: bucket._options.hash,
|
|
95
|
+
bits: bucket._options.bits
|
|
96
|
+
}, bucket, pos)
|
|
97
|
+
bucket._putObjectAt(pos, subBucket)
|
|
98
|
+
|
|
99
|
+
await Promise.resolve(); 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
|
+
|
|
131
|
+
await Promise.resolve(); return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await rootBucket.put(linkName.substring(2), {
|
|
135
|
+
size: link.Tsize,
|
|
136
|
+
cid: link.Hash
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const toPrefix = (position: number): string => {
|
|
143
|
+
return position
|
|
144
|
+
.toString(16)
|
|
145
|
+
.toUpperCase()
|
|
146
|
+
.padStart(2, '0')
|
|
147
|
+
.substring(0, 2)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface HamtPath {
|
|
151
|
+
rootBucket: Bucket<any>
|
|
152
|
+
path: Array<{
|
|
153
|
+
bucket: Bucket<any>
|
|
154
|
+
prefix: string
|
|
155
|
+
node?: PBNode
|
|
156
|
+
}>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const generatePath = async (parent: Directory, name: string, blockstore: Blockstore, options: AbortOptions): Promise<HamtPath> => {
|
|
160
|
+
// start at the root bucket and descend, loading nodes as we go
|
|
161
|
+
const rootBucket = await recreateInitialHamtLevel(parent.node.Links)
|
|
162
|
+
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 }> = [{
|
|
166
|
+
bucket: position.bucket,
|
|
167
|
+
prefix: toPrefix(position.pos)
|
|
168
|
+
}]
|
|
169
|
+
let currentBucket = position.bucket
|
|
170
|
+
|
|
171
|
+
while (currentBucket !== rootBucket) {
|
|
172
|
+
path.push({
|
|
173
|
+
bucket: currentBucket,
|
|
174
|
+
prefix: toPrefix(currentBucket._posAtParent)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// @ts-expect-error - only the root bucket's parent will be undefined
|
|
178
|
+
currentBucket = currentBucket._parent
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
path.reverse()
|
|
182
|
+
path[0].node = parent.node
|
|
183
|
+
|
|
184
|
+
// load PbNode for each path segment
|
|
185
|
+
for (let i = 0; i < path.length; i++) {
|
|
186
|
+
const segment = path[i]
|
|
187
|
+
|
|
188
|
+
if (segment.node == null) {
|
|
189
|
+
throw new Error('Could not generate HAMT path')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// find prefix in links
|
|
193
|
+
const link = segment.node.Links
|
|
194
|
+
.filter(link => (link.Name ?? '').substring(0, 2) === segment.prefix)
|
|
195
|
+
.pop()
|
|
196
|
+
|
|
197
|
+
// entry was not in shard
|
|
198
|
+
if (link == null) {
|
|
199
|
+
// reached bottom of tree, file will be added to the current bucket
|
|
200
|
+
log(`Link ${segment.prefix}${name} will be added`)
|
|
201
|
+
// return path
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// found entry
|
|
206
|
+
if (link.Name === `${segment.prefix}${name}`) {
|
|
207
|
+
log(`Link ${segment.prefix}${name} will be replaced`)
|
|
208
|
+
// file already existed, file will be added to the current bucket
|
|
209
|
+
// return path
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// found subshard
|
|
214
|
+
log(`Found subshard ${segment.prefix}`)
|
|
215
|
+
const block = await blockstore.get(link.Hash, options)
|
|
216
|
+
const node = dagPB.decode(block)
|
|
217
|
+
|
|
218
|
+
// subshard hasn't been loaded, descend to the next level of the HAMT
|
|
219
|
+
if (path[i + 1] == null) {
|
|
220
|
+
log(`Loaded new subshard ${segment.prefix}`)
|
|
221
|
+
|
|
222
|
+
await recreateHamtLevel(blockstore, node.Links, rootBucket, segment.bucket, parseInt(segment.prefix, 16), options)
|
|
223
|
+
const position = await rootBucket._findNewBucketAndPos(name)
|
|
224
|
+
|
|
225
|
+
// i--
|
|
226
|
+
path.push({
|
|
227
|
+
bucket: position.bucket,
|
|
228
|
+
prefix: toPrefix(position.pos),
|
|
229
|
+
node
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
continue
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const nextSegment = path[i + 1]
|
|
236
|
+
|
|
237
|
+
// add intermediate links to bucket
|
|
238
|
+
await addLinksToHamtBucket(blockstore, node.Links, nextSegment.bucket, rootBucket, options)
|
|
239
|
+
|
|
240
|
+
nextSegment.node = node
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await rootBucket.put(name, true)
|
|
244
|
+
|
|
245
|
+
path.reverse()
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
rootBucket,
|
|
249
|
+
path
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface CreateShardOptions {
|
|
254
|
+
mtime?: Mtime
|
|
255
|
+
mode?: number
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const createShard = async (blockstore: Blockstore, contents: Array<{ name: string, size: number, cid: CID }>, options: CreateShardOptions = {}): Promise<ImportResult> => {
|
|
259
|
+
const shard = new DirSharded({
|
|
260
|
+
root: true,
|
|
261
|
+
dir: true,
|
|
262
|
+
parent: undefined,
|
|
263
|
+
parentKey: undefined,
|
|
264
|
+
path: '',
|
|
265
|
+
dirty: true,
|
|
266
|
+
flat: false,
|
|
267
|
+
mtime: options.mtime,
|
|
268
|
+
mode: options.mode
|
|
269
|
+
}, options)
|
|
270
|
+
|
|
271
|
+
for (let i = 0; i < contents.length; i++) {
|
|
272
|
+
await shard._bucket.put(contents[i].name, {
|
|
273
|
+
size: contents[i].size,
|
|
274
|
+
cid: contents[i].cid
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const res = await last(shard.flush(blockstore))
|
|
279
|
+
|
|
280
|
+
if (res == null) {
|
|
281
|
+
throw new Error('Flushing shard yielded no result')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return res
|
|
285
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CID, Version } from 'multiformats/cid'
|
|
2
|
+
import * as dagPB from '@ipld/dag-pb'
|
|
3
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
4
|
+
import type { BlockCodec } from 'multiformats/codecs/interface'
|
|
5
|
+
import type { AbortOptions } from '@libp2p/interfaces'
|
|
6
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
7
|
+
|
|
8
|
+
export interface PersistOptions extends AbortOptions {
|
|
9
|
+
codec?: BlockCodec<any, any>
|
|
10
|
+
cidVersion?: Version
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const persist = async (buffer: Uint8Array, blockstore: Blockstore, options: PersistOptions = {}): Promise<CID> => {
|
|
14
|
+
const multihash = await sha256.digest(buffer)
|
|
15
|
+
const cid = CID.create(options.cidVersion ?? 1, dagPB.code, multihash)
|
|
16
|
+
|
|
17
|
+
await blockstore.put(cid, buffer, {
|
|
18
|
+
signal: options.signal
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return cid
|
|
22
|
+
}
|