Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 0bb4104

Browse files
committed
feat: implement ipfs dag export <root>
1 parent 54478b0 commit 0bb4104

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

packages/ipfs-cli/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
"build": "aegir build --no-bundle"
3131
},
3232
"dependencies": {
33+
"@ipld/car": "^3.1.0",
34+
"@ipld/dag-cbor": "^6.0.4",
35+
"@ipld/dag-json": "^7.0.4",
36+
"@ipld/dag-pb": "^2.1.0",
3337
"byteman": "^1.3.5",
3438
"cid-tool": "^3.0.0",
3539
"cids": "^1.1.6",
@@ -58,6 +62,7 @@
5862
"multiaddr-to-uri": "^7.0.0",
5963
"multibase": "^4.0.2",
6064
"multihashing-async": "^2.1.2",
65+
"multiformats": "^9.1.0",
6166
"parse-duration": "^1.0.0",
6267
"pretty-bytes": "^5.4.1",
6368
"progress": "^2.0.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use strict'
2+
3+
const { Readable } = require('stream')
4+
const { default: parseDuration } = require('parse-duration')
5+
const LegacyCID = require('cids')
6+
const { CID } = require('multiformats/cid')
7+
const Block = require('multiformats/block')
8+
const { base58 } = require('multiformats/bases/base58')
9+
const { CarWriter } = require('@ipld/car/writer')
10+
const codecs = [
11+
require('@ipld/dag-pb'),
12+
require('@ipld/dag-cbor'),
13+
require('@ipld/dag-json')
14+
].reduce((codecs, codec) => {
15+
codecs[codec.code] = codec
16+
return codecs
17+
}, [])
18+
19+
// blocks that we're OK with not inspecting for links
20+
const NO_LINKS_CODECS = [
21+
0x51, // CBOR
22+
0x55, // raw
23+
0x0200 // JSON
24+
]
25+
26+
/**
27+
* @typedef {import('ipfs-core-types').IPFS} IPFS
28+
* @typedef {import('@ipld/car/api').BlockWriter} BlockWriter
29+
*/
30+
31+
module.exports = {
32+
command: 'export <root cid>',
33+
34+
describe: 'Streams the DAG beginning at the given root CID as a .car stream on stdout.',
35+
36+
builder: {
37+
timeout: {
38+
type: 'string',
39+
coerce: parseDuration
40+
}
41+
},
42+
43+
/**
44+
* @param {object} argv
45+
* @param {import('../../types').Context} argv.ctx
46+
* @param {string} argv.rootcid
47+
* @param {number} argv.timeout
48+
*/
49+
async handler ({ ctx: { ipfs, print }, rootcid, timeout }) {
50+
const options = { timeout }
51+
const cid = CID.parse(rootcid)
52+
53+
const { writer, out } = await CarWriter.create([cid])
54+
Readable.from(out).pipe(process.stdout)
55+
56+
const complete = await traverseWrite(ipfs, options, cid, writer)
57+
writer.close()
58+
59+
if (!complete) {
60+
print('cannot decode links in all blocks, DAG may be incomplete')
61+
}
62+
}
63+
}
64+
65+
/**
66+
* @param {IPFS} ipfs
67+
* @param {{ timeout?: number}} options
68+
* @param {CID} cid
69+
* @returns {Promise<{cid:CID, bytes:Uint8Array, links:CID[]|null}>}
70+
*/
71+
const getBlock = async (ipfs, options, cid) => {
72+
cid = CID.asCID(cid)
73+
const result = await ipfs.block.get(new LegacyCID(cid.bytes), options)
74+
const bytes = result.data
75+
let links = null
76+
const codec = codecs[result.cid.code]
77+
if (codec) {
78+
const block = Block.createUnsafe({ bytes: result.data, cid: CID.asCID(result.cid), codec })
79+
links = [...block.links()].map((l) => l[1])
80+
} else if (NO_LINKS_CODECS.includes(result.cid.code)) {
81+
// these blocks are known not to contain any IPLD links
82+
links = []
83+
} // else we may have a block with links that we can't decode
84+
return { cid, bytes, links }
85+
}
86+
87+
/**
88+
* @param {IPFS} ipfs
89+
* @param {{ timeout?: number}} options
90+
* @param {CID} cid
91+
* @param {BlockWriter} writer
92+
* @param {Set<string>} seen
93+
* @returns {boolean} complete DAG
94+
*/
95+
async function traverseWrite (ipfs, options, cid, writer, seen = new Set()) {
96+
const b58Cid = cid.toString(base58)
97+
if (seen.has(b58Cid)) {
98+
return true
99+
}
100+
const block = await getBlock(ipfs, options, cid)
101+
await writer.put(block)
102+
seen.add(b58Cid)
103+
if (block.links === null) {
104+
return false // potentially incomplete DAG
105+
}
106+
107+
// recursive traversal of all links
108+
let complete = true
109+
for (const link of block.links) {
110+
if (!await traverseWrite(ipfs, options, link, writer, seen)) {
111+
complete = false
112+
}
113+
}
114+
return complete
115+
}

0 commit comments

Comments
 (0)