Skip to content

Commit d06d9fe

Browse files
clostaoachingbrain
andauthored
feat: enable custom file/directory builders (#413)
To allow adding building custom file/directory IPLD objects, two additional fields to `DagBuilderOptions` & `ImporterOptions` are added that would override the `dirBuilder` & `fileBuilder` with other implementations. --------- Co-authored-by: achingbrain <[email protected]>
1 parent 28a9c68 commit d06d9fe

File tree

5 files changed

+87
-13
lines changed

5 files changed

+87
-13
lines changed

packages/ipfs-unixfs-importer/src/dag-builder/dir.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ export interface DirBuilderOptions {
99
signal?: AbortSignal
1010
}
1111

12-
export const dirBuilder = async (dir: Directory, blockstore: WritableStorage, options: DirBuilderOptions): Promise<InProgressImportResult> => {
12+
export interface DirBuilder {
13+
(dir: Directory, blockstore: WritableStorage, options: DirBuilderOptions): Promise<InProgressImportResult>
14+
}
15+
16+
export const defaultDirBuilder: DirBuilder = async (dir: Directory, blockstore: WritableStorage, options: DirBuilderOptions): Promise<InProgressImportResult> => {
1317
const unixfs = new UnixFS({
1418
type: 'directory',
1519
mtime: dir.mtime,

packages/ipfs-unixfs-importer/src/dag-builder/file.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async function * buildFileBatch (file: File, blockstore: WritableStorage, option
3030
}
3131

3232
continue
33-
} else if (count === 1 && (previous != null)) {
33+
} else if (count === 1 && previous != null) {
3434
// we have the second block of a multiple block import so yield the first
3535
yield {
3636
...previous,
@@ -131,7 +131,7 @@ const reduce = (file: File, blockstore: WritableStorage, options: ReduceOptions)
131131
return true
132132
}
133133

134-
if ((leaf.unixfs != null) && (leaf.unixfs.data == null) && leaf.unixfs.fileSize() > 0n) {
134+
if (leaf.unixfs != null && leaf.unixfs.data == null && leaf.unixfs.fileSize() > 0n) {
135135
return true
136136
}
137137

@@ -189,10 +189,17 @@ const reduce = (file: File, blockstore: WritableStorage, options: ReduceOptions)
189189
return reducer
190190
}
191191

192+
export interface FileBuilder {
193+
(file: File, blockstore: WritableStorage, options: FileBuilderOptions): Promise<InProgressImportResult>
194+
}
195+
192196
export interface FileBuilderOptions extends BuildFileBatchOptions, ReduceOptions {
193197
layout: FileLayout
194198
}
195199

196-
export const fileBuilder = async (file: File, block: WritableStorage, options: FileBuilderOptions): Promise<InProgressImportResult> => {
197-
return options.layout(buildFileBatch(file, block, options), reduce(file, block, options))
200+
export const defaultFileBuilder: FileBuilder = async (file: File, block: WritableStorage, options: FileBuilderOptions): Promise<InProgressImportResult> => {
201+
return options.layout(
202+
buildFileBatch(file, block, options),
203+
reduce(file, block, options)
204+
)
198205
}

packages/ipfs-unixfs-importer/src/dag-builder/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { CustomProgressEvent } from 'progress-events'
22
import { InvalidContentError } from '../errors.js'
3-
import { dirBuilder } from './dir.js'
4-
import { fileBuilder } from './file.js'
5-
import type { DirBuilderOptions } from './dir.js'
6-
import type { FileBuilderOptions } from './file.js'
3+
import { defaultDirBuilder } from './dir.js'
4+
import { defaultFileBuilder } from './file.js'
5+
import type { DirBuilder, DirBuilderOptions } from './dir.js'
6+
import type { FileBuilder, FileBuilderOptions } from './file.js'
77
import type { ChunkValidator } from './validate-chunks.js'
88
import type { Chunker } from '../chunker/index.js'
99
import type { Directory, File, FileCandidate, ImportCandidate, ImporterProgressEvents, InProgressImportResult, WritableStorage } from '../index.js'
@@ -45,11 +45,11 @@ function contentAsAsyncIterable (content: Uint8Array | AsyncIterable<Uint8Array>
4545
if (content instanceof Uint8Array) {
4646
return (async function * () {
4747
yield content
48-
}())
48+
})()
4949
} else if (isIterable(content)) {
5050
return (async function * () {
5151
yield * content
52-
}())
52+
})()
5353
} else if (isAsyncIterable(content)) {
5454
return content
5555
}
@@ -64,6 +64,8 @@ export interface DagBuilderOptions extends FileBuilderOptions, DirBuilderOptions
6464
chunker: Chunker
6565
chunkValidator: ChunkValidator
6666
wrapWithDirectory: boolean
67+
dirBuilder?: DirBuilder
68+
fileBuilder?: FileBuilder
6769
}
6870

6971
export type ImporterSourceStream = AsyncIterable<ImportCandidate> | Iterable<ImportCandidate>
@@ -109,6 +111,8 @@ export function defaultDagBuilder (options: DagBuilderOptions): DAGBuilder {
109111
originalPath
110112
}
111113

114+
const fileBuilder = options.fileBuilder ?? defaultFileBuilder
115+
112116
yield async () => fileBuilder(file, blockstore, options)
113117
} else if (entry.path != null) {
114118
const dir: Directory = {
@@ -118,6 +122,8 @@ export function defaultDagBuilder (options: DagBuilderOptions): DAGBuilder {
118122
originalPath
119123
}
120124

125+
const dirBuilder = options.dirBuilder ?? defaultDirBuilder
126+
121127
yield async () => dirBuilder(dir, blockstore, options)
122128
} else {
123129
throw new Error('Import candidate must have content or path or both')

packages/ipfs-unixfs-importer/src/index.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ import { balanced } from './layout/index.js'
7373
import { defaultTreeBuilder } from './tree-builder.js'
7474
import type { Chunker } from './chunker/index.js'
7575
import type { BufferImportProgressEvents } from './dag-builder/buffer-importer.js'
76-
import type { ReducerProgressEvents } from './dag-builder/file.js'
76+
import type { DirBuilder } from './dag-builder/dir.js'
77+
import type { FileBuilder, ReducerProgressEvents } from './dag-builder/file.js'
7778
import type { DAGBuilder, DagBuilderProgressEvents } from './dag-builder/index.js'
7879
import type { ChunkValidator } from './dag-builder/validate-chunks.js'
7980
import type { FileLayout } from './layout/index.js'
@@ -276,6 +277,20 @@ export interface ImporterOptions extends ProgressOptions<ImporterProgressEvents>
276277
* `Error`
277278
*/
278279
chunkValidator?: ChunkValidator
280+
281+
/**
282+
* This option can be used to override how a directory IPLD node is built.
283+
*
284+
* This function takes a `Directory` object and returns a `Promise` that resolves to an `InProgressImportResult`.
285+
*/
286+
dirBuilder?: DirBuilder
287+
288+
/**
289+
* This option can be used to override how a file IPLD node is built.
290+
*
291+
* This function takes a `File` object and returns a `Promise` that resolves to an `InProgressImportResult`.
292+
*/
293+
fileBuilder?: FileBuilder
279294
}
280295

281296
export type ImportCandidateStream = AsyncIterable<FileCandidate | DirectoryCandidate> | Iterable<FileCandidate | DirectoryCandidate>
@@ -342,7 +357,9 @@ export async function * importer (source: ImportCandidateStream, blockstore: Wri
342357
blockWriteConcurrency,
343358
reduceSingleLeafToSelf,
344359
cidVersion,
345-
onProgress: options.onProgress
360+
onProgress: options.onProgress,
361+
dirBuilder: options.dirBuilder,
362+
fileBuilder: options.fileBuilder
346363
})
347364
const buildTree: TreeBuilder = options.treeBuilder ?? defaultTreeBuilder({
348365
wrapWithDirectory,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { expect } from 'aegir/chai'
2+
import { MemoryBlockstore } from 'blockstore-core'
3+
import { defaultDirBuilder } from '../src/dag-builder/dir.js'
4+
import { defaultFileBuilder } from '../src/dag-builder/file.js'
5+
import { importer } from '../src/index.js'
6+
import type { DirBuilder } from '../src/dag-builder/dir.js'
7+
import type { FileBuilder } from '../src/dag-builder/file.js'
8+
9+
describe('CustomParamsDagBuilder', () => {
10+
it('should build a dag with custom dir builder', async () => {
11+
const counter = { dirCounter: 0, fileCounter: 0 }
12+
const customDirBuilder: DirBuilder = async (...args) => {
13+
counter.dirCounter++
14+
return defaultDirBuilder(...args)
15+
}
16+
17+
const customFileBuilder: FileBuilder = async (...args) => {
18+
counter.fileCounter++
19+
return defaultFileBuilder(...args)
20+
}
21+
22+
const blockstore = new MemoryBlockstore()
23+
const files = []
24+
for await (const file of importer([{
25+
path: './src/file.txt',
26+
content: new Uint8Array(
27+
'hello world'.split('').map((char) => char.charCodeAt(0))
28+
)
29+
}, {
30+
path: './src'
31+
}], blockstore, {
32+
dirBuilder: customDirBuilder,
33+
fileBuilder: customFileBuilder
34+
})) {
35+
files.push(file)
36+
}
37+
38+
expect(counter.dirCounter).to.equal(1)
39+
})
40+
})

0 commit comments

Comments
 (0)