Skip to content

Commit 0b6a8fa

Browse files
committed
feat: simulate garbage collection
License: MIT Signed-off-by: Oli Evans <[email protected]>
1 parent b6fed30 commit 0b6a8fa

17 files changed

+1567
-771
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
docker:
2323
- image: olizilla/ipfs-dns-deploy:latest
2424
environment:
25-
DOMAIN: dag.ipfs.io
25+
DOMAIN: blocks.ipfs.io
2626
BUILD_DIR: dist
2727
steps:
2828
- attach_workspace:

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# ipfs-dag-builder-vis
1+
# IPFS block party
22

3-
> See how the DAGs get built
3+
> See the blocks in a block store
44
5-
<img width="1320" alt="screenshot" src="https://user-images.githubusercontent.com/152863/57775953-28dbf380-7716-11e9-8b43-3f42b73ccc4c.png">
5+
Drop a file to see it turn into blocks in your local blockstore. Unpin all your blocks, and see them all get deleted when you trigger garbage collection.
6+
7+
![screenshot](./docs/screenshot.png)

docs/screenshot.png

714 KB
Loading

package-lock.json

Lines changed: 1096 additions & 614 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
"dependencies": {
77
"cytoscape": "^3.6.0",
88
"cytoscape-dagre": "^2.2.2",
9-
"ipfs": "^0.36.2",
9+
"idb": "^4.0.3",
10+
"ipfs": "^0.36.4",
1011
"ipfs-css": "^0.12.0",
1112
"ipld-dag-pb": "^0.17.0",
13+
"lodash.difference": "^4.5.0",
1214
"parcel": "^1.12.3",
15+
"prop-types": "^15.7.2",
1316
"react": "^16.8.6",
1417
"react-dom": "^16.8.6",
1518
"tachyons": "^4.11.1"

src/App.js

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,119 @@
22
import React, { useState, useEffect } from 'react'
33
import Header from './Header'
44
import Controls from './Controls'
5-
import Dag from './Dag'
6-
import { ipfsAdd } from './lib/ipfs'
5+
import Blocks from './Blocks'
6+
import { getIpfs, ipfsAdd, ipfsGet, resetRepo, removeUnpinned, unpinAll, getBlockInfo } from './lib/ipfs'
77
import DropTarget from './DropTarget'
88
import NodeInfo from './NodeInfo'
9-
import Spinner from './Spinner'
109

1110
export default function App () {
1211
const [files, setFiles] = useState([])
13-
const [chunker, setChunker] = useState('size-512')
14-
const [rawLeaves, setRawLeaves] = useState(false)
15-
const [strategy, setStrategy] = useState('balanced')
16-
const [maxChildren, setMaxChildren] = useState(11)
17-
const [layerRepeat, setLayerRepeat] = useState(4)
12+
const [chunker, setChunker] = useState('size-1024')
1813
const [rootCid, setRootCid] = useState(null)
1914
const [focusedNode, setFocusedNode] = useState(null)
15+
const [blockInfo, setBlockInfo] = useState(null)
2016
const [loading, setLoading] = useState(false)
17+
const [cidToGet, setCidToGet] = useState('')
2118

2219
useEffect(() => {
2320
if (!files.length) return
2421
const addFiles = async () => {
2522
setRootCid(null)
2623
setLoading(true)
27-
const cid = await ipfsAdd({ files, chunker, rawLeaves, strategy, maxChildren, layerRepeat })
24+
const cid = await ipfsAdd({ files, chunker })
25+
setLoading(false)
2826
setRootCid(cid)
2927
}
3028
addFiles()
31-
}, [files, chunker, rawLeaves, strategy, maxChildren, layerRepeat])
29+
}, [files, chunker])
3230

33-
const onFileChange = file => {
34-
setFiles(files.concat({ path: file.name, content: file }))
31+
// update block info when focusedNode changes
32+
useEffect(() => {
33+
console.log('update block info effect')
34+
if (!focusedNode) return
35+
const fn = async () => {
36+
const res = await getBlockInfo(focusedNode)
37+
console.log('updating info for focusedNode', focusedNode, res.id)
38+
if (focusedNode === res.id) {
39+
setBlockInfo(res)
40+
}
41+
}
42+
fn()
43+
}, [focusedNode])
44+
45+
const onFilesChange = files => {
46+
setFiles(files.map(file => ({ path: file.name, content: file })))
47+
}
48+
49+
const onCidSubmit = async e => {
50+
setRootCid(null)
51+
setLoading(true)
52+
await ipfsGet(cidToGet)
53+
setRootCid(cidToGet)
3554
}
3655

37-
const onReset = () => {
56+
const onReset = async () => {
57+
setLoading(true)
3858
setFiles([])
39-
setChunker('size-512')
40-
setStrategy('balanced')
41-
setMaxChildren(11)
42-
setLayerRepeat(4)
59+
setChunker('size-1024')
4360
setRootCid(null)
4461
setFocusedNode(null)
62+
await resetRepo()
63+
await getIpfs()
64+
setLoading(false)
65+
console.log('reset complete')
66+
}
67+
68+
const onUnpinAll = async () => {
69+
setLoading(true)
70+
await unpinAll()
71+
setLoading(false)
72+
}
73+
74+
const onRemoveUnpinned = async () => {
75+
console.log('remove unpinned')
76+
setLoading(true)
77+
await removeUnpinned()
78+
setLoading(false)
79+
console.log('remove unpinned done')
4580
}
4681

4782
return (
4883
<div className='avenir flex flex-column h-100'>
4984
<div className='flex-none'>
50-
<Header />
85+
<Header>
86+
<div className='dn'>
87+
<form onSubmit={onCidSubmit} className='flex items-center'>
88+
<input type='text' value={cidToGet} className='mh3 br2 ba b--silver pa2 f5 dib w5' placeholder='QmHash' />
89+
<button
90+
type='submit'
91+
className='mr2 transition-all sans-serif dib v-mid fw5 nowrap lh-copy bn br1 ph4 pv1 pointer focus-outline bg-green-muted hover-bg-green white'
92+
title='ipfs.get a CID'>
93+
Get
94+
</button>
95+
</form>
96+
</div>
97+
</Header>
5198
</div>
5299
<div className='flex-none'>
53100
<Controls
54101
chunker={chunker}
55102
onChunkerChange={setChunker}
56-
rawLeaves={rawLeaves}
57-
onRawLeavesChange={setRawLeaves}
58-
strategy={strategy}
59-
onStrategyChange={setStrategy}
60-
maxChildren={maxChildren}
61-
onMaxChildrenChange={setMaxChildren}
62-
layerRepeat={layerRepeat}
63-
onLayerRepeatChange={setLayerRepeat}
64-
onReset={onReset} />
103+
onReset={onReset}
104+
onGc={onRemoveUnpinned}
105+
onUnpinAll={onUnpinAll}
106+
loading={loading} />
65107
</div>
66108
<div className='flex-auto'>
67-
<DropTarget onFileDrop={onFileChange} className='h-100'>
68-
{files.length ? (
69-
<div className='flex flex-column h-100'>
70-
<div className='flex-auto relative'>
71-
<Spinner show={loading} />
72-
<Dag
73-
rootCid={rootCid}
74-
onNodeFocus={setFocusedNode}
75-
onGraphRender={() => setLoading(false)} />
76-
</div>
77-
<div className='flex-none'>
78-
<NodeInfo info={focusedNode} />
79-
</div>
109+
<DropTarget onFileDrop={onFilesChange} className='h-100'>
110+
<div className='flex flex-column h-100'>
111+
<div className='flex-auto relative'>
112+
<Blocks onNodeFocus={setFocusedNode} />
113+
</div>
114+
<div className='flex-none'>
115+
<NodeInfo info={blockInfo} />
80116
</div>
81-
) : null}
117+
</div>
82118
</DropTarget>
83119
</div>
84120
</div>

src/Blocks.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React, { Component, createRef } from 'react'
2+
import PropTypes from 'prop-types'
3+
import cytoscape from 'cytoscape'
4+
import UnixFs from 'ipfs-unixfs'
5+
import { DAGNode } from 'ipld-dag-pb'
6+
import { Buffer } from 'ipfs'
7+
import { getIpfs, getLocalBlockData } from './lib/ipfs'
8+
import DagGraphOptions from './DagGraphOptions'
9+
10+
export default class Blocks extends Component {
11+
constructor () {
12+
super()
13+
this._graphRoot = createRef()
14+
this.cy = null
15+
}
16+
17+
componentDidMount () {
18+
this._updateGraph()
19+
}
20+
21+
componentDidUpdate (prevProps) {
22+
this._updateGraph()
23+
}
24+
25+
async _updateGraph () {
26+
console.log('update graph')
27+
if (!this._cy) {
28+
const container = this._graphRoot.current
29+
window.cy = this._cy = cytoscape({ container, ...DagGraphOptions })
30+
}
31+
const cy = this._cy
32+
const blocks = await getLocalBlockData()
33+
cy.elements().remove()
34+
cy.add(blocks)
35+
cy.layout(DagGraphOptions.layout).run()
36+
37+
const focusElement = node => {
38+
cy.nodes('.focused').removeClass('focused')
39+
node.addClass('focused')
40+
if (this.props.onNodeFocus) {
41+
this.props.onNodeFocus(node.id())
42+
}
43+
}
44+
45+
if (blocks && blocks[0]) {
46+
console.log('focus', blocks[0])
47+
focusElement(cy.getElementById(cy.nodes().first()))
48+
}
49+
50+
cy.on('tap', e => {
51+
if (!e.target.id && !e.target.group) return
52+
if (!this.props.onNodeFocus || e.target.group() !== 'nodes') return
53+
focusElement(e.target)
54+
})
55+
56+
if (this.props.onGraphRender) this.props.onGraphRender()
57+
}
58+
59+
async _getGraphNodes (cid, nodeMap = new Map()) {
60+
if (nodeMap.get(cid)) return
61+
62+
const ipfs = await getIpfs()
63+
const { value: source } = await ipfs.dag.get(cid)
64+
const classes = []
65+
let nodeData = {}
66+
67+
if (DAGNode.isDAGNode(source)) {
68+
try {
69+
// it's a unix system?
70+
const unixfsData = UnixFs.unmarshal(source.Data)
71+
nodeData = {
72+
type: 'unixfs',
73+
isLeaf: Boolean(source.Links.length),
74+
length: (await ipfs.block.get(cid)).data.length,
75+
unixfsData
76+
}
77+
} catch (err) {
78+
// dag-pb but not a unixfs.
79+
console.log(err)
80+
}
81+
82+
for (let i = 0; i < source.Links.length; i++) {
83+
await this._getGraphNodes(source.Links[i].Hash.toString(), nodeMap)
84+
}
85+
86+
if (!source.Links.length) classes.push('leaf')
87+
if (nodeData) classes.push('unixfs', nodeData.unixfsData.type)
88+
} else if (Buffer.isBuffer(source)) {
89+
classes.push('raw')
90+
nodeData = { type: 'raw', isLeaf: true, length: source.length }
91+
} else {
92+
// TODO: What IPLD node is this? How to extract the links?
93+
classes.push('leaf')
94+
nodeData = { type: 'unknown', isLeaf: true }
95+
}
96+
97+
nodeMap.set(cid, {
98+
group: 'nodes',
99+
data: { id: cid, ...nodeData },
100+
classes
101+
})
102+
103+
;(source.Links || []).forEach(link => {
104+
nodeMap.set(cid + '->' + link.Hash, {
105+
group: 'edges',
106+
data: { source: cid, target: link.Hash.toString() }
107+
})
108+
})
109+
110+
return nodeMap
111+
}
112+
113+
render () {
114+
return <div ref={this._graphRoot} className='bg-snow-muted h-100' />
115+
}
116+
}
117+
118+
Blocks.propTypes = {
119+
numObjects: PropTypes.number,
120+
pins: PropTypes.array
121+
}

0 commit comments

Comments
 (0)