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

Commit 16b788c

Browse files
authored
feat: subtree support (#175)
* feat: subtree support
1 parent 8054189 commit 16b788c

14 files changed

+357
-126
lines changed

README.md

+39-34
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ IPFS unixFS Engine
1212
![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square)
1313
![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square)
1414

15-
> JavaScript implementation of the layout and chunking mechanisms used by IPFS
15+
> JavaScript implementation of the layout and chunking mechanisms used by IPFS to handle Files
1616
1717
## Table of Contents
1818

@@ -35,9 +35,12 @@ IPFS unixFS Engine
3535

3636
## Usage
3737

38-
### Example Importer
38+
### Importer
39+
40+
#### Importer example
3941

4042
Let's create a little directory to import:
43+
4144
```sh
4245
> cd /tmp
4346
> mkdir foo
@@ -46,6 +49,7 @@ Let's create a little directory to import:
4649
```
4750

4851
And write the importing logic:
52+
4953
```js
5054
const Importer = require('ipfs-unixfs-engine').Importer
5155
const filesAddStream = new Importer(<dag or ipld-resolver instance)
@@ -74,7 +78,8 @@ filesAddStream.end()
7478
```
7579

7680
When run, the stat of DAG Node is outputted for each file on data event until the root:
77-
```
81+
82+
```js
7883
{ multihash: <Buffer 12 20 bd e2 2b 57 3f 6f bd 7c cc 5a 11 7f 28 6c a2 9a 9f c0 90 e1 d4 16 d0 5f 42 81 ec 0c 2a 7f 7f 93>,
7984
size: 39243,
8085
path: '/tmp/foo/bar' }
@@ -93,15 +98,15 @@ When run, the stat of DAG Node is outputted for each file on data event until th
9398

9499
```
95100

96-
### Importer API
101+
#### Importer API
97102

98103
```js
99104
const Importer = require('ipfs-unixfs-engine').Importer
100105
```
101106

102-
#### const add = new Importer(dag)
107+
#### const import = new Importer(dag [, options])
103108

104-
The importer is a object Transform stream that accepts objects of the form
109+
The `import` object is a duplex pull stream that takes objects of the form:
105110

106111
```js
107112
{
@@ -110,50 +115,50 @@ The importer is a object Transform stream that accepts objects of the form
110115
}
111116
```
112117

113-
The stream will output IPFS DAG Node stats for the nodes as they are added to
114-
the DAG Service. When stats on a node are emitted they are guaranteed to have
115-
been written into the [DAG Service][]'s storage mechanism.
118+
`import` will outoyt file info objects as files get stored in IPFS. When stats on a node are emitted they are guaranteed to have been written.
116119

117-
The input's file paths and directory structure will be preserved in the DAG
118-
Nodes.
120+
`dag` is an instance of the [`IPLD Resolver`](https://github.com/ipld/js-ipld-resolver) or the [`js-ipfs` `dag api`](https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag)
119121

120-
### Importer options
122+
The input's file paths and directory structure will be preserved in the [`dag-pb`](https://github.com/ipld/js-ipld-dag-pb) created nodes.
121123

122-
In the second argument of the importer constructor you can specify the following options:
124+
`options` is an JavaScript option that might include the following keys:
123125

124-
* `wrap` (boolean, defaults to false): if true, a wrapping node will be created
125-
* `shardSplitThreshold` (positive integer, defaults to 1000): the number of directory entries above which we decide to use a sharding directory builder (instead of the default flat one)
126-
* `chunker` (string, defaults to `"fixed"`): the chunking strategy. Now only supports `"fixed"`
127-
* `chunkerOptions` (object, optional): the options for the chunker. Defaults to an object with the following properties:
128-
* `maxChunkSize` (positive integer, defaults to `262144`): the maximum chunk size for the `fixed` chunker.
129-
* `strategy` (string, defaults to `"balanced"`): the DAG builder strategy name. Supports:
130-
* `flat`: flat list of chunks
131-
* `balanced`: builds a balanced tree
132-
* `trickle`: builds [a trickle tree](https://github.com/ipfs/specs/pull/57#issuecomment-265205384)
133-
* `maxChildrenPerNode` (positive integer, defaults to `174`): the maximum children per node for the `balanced` and `trickle` DAG builder strategies
134-
* `layerRepeat` (positive integer, defaults to 4): (only applicable to the `trickle` DAG builder strategy). The maximum repetition of parent nodes for each layer of the tree.
135-
* `reduceSingleLeafToSelf` (boolean, defaults to `false`): optimization for, when reducing a set of nodes with one node, reduce it to that node.
136-
* `dirBuilder` (object): the options for the directory builder
137-
* `hamt` (object): the options for the HAMT sharded directory builder
138-
* bits (positive integer, defaults to `5`): the number of bits at each bucket of the HAMT
126+
- `wrap` (boolean, defaults to false): if true, a wrapping node will be created
127+
- `shardSplitThreshold` (positive integer, defaults to 1000): the number of directory entries above which we decide to use a sharding directory builder (instead of the default flat one)
128+
- `chunker` (string, defaults to `"fixed"`): the chunking strategy. Now only supports `"fixed"`
129+
- `chunkerOptions` (object, optional): the options for the chunker. Defaults to an object with the following properties:
130+
- `maxChunkSize` (positive integer, defaults to `262144`): the maximum chunk size for the `fixed` chunker.
131+
- `strategy` (string, defaults to `"balanced"`): the DAG builder strategy name. Supports:
132+
- `flat`: flat list of chunks
133+
- `balanced`: builds a balanced tree
134+
- `trickle`: builds [a trickle tree](https://github.com/ipfs/specs/pull/57#issuecomment-265205384)
135+
- `maxChildrenPerNode` (positive integer, defaults to `174`): the maximum children per node for the `balanced` and `trickle` DAG builder strategies
136+
- `layerRepeat` (positive integer, defaults to 4): (only applicable to the `trickle` DAG builder strategy). The maximum repetition of parent nodes for each layer of the tree.
137+
- `reduceSingleLeafToSelf` (boolean, defaults to `false`): optimization for, when reducing a set of nodes with one node, reduce it to that node.
138+
- `dirBuilder` (object): the options for the directory builder
139+
- `hamt` (object): the options for the HAMT sharded directory builder
140+
- bits (positive integer, defaults to `5`): the number of bits at each bucket of the HAMT
139141

140-
### Example Exporter
142+
### Exporter
141143

142-
```
143-
// Create an export readable object stream with the hash you want to export and a dag service
144-
const filesStream = Exporter(<multihash>, <dag or ipld-resolver instance>)
144+
#### Exporter example
145+
146+
```js
147+
// Create an export source pull-stream cid or ipfs path you want to export and a
148+
// <dag or ipld-resolver instance> to fetch the file from
149+
const filesStream = Exporter(<cid or ipfsPath>, <dag or ipld-resolver instance>)
145150

146151
// Pipe the return stream to console
147152
filesStream.on('data', (file) => file.content.pipe(process.stdout))
148153
```
149154

150-
### Exporter: API
155+
#### Exporter API
151156

152157
```js
153158
const Exporter = require('ipfs-unixfs-engine').Exporter
154159
```
155160

156-
### new Exporter(<hash>, <dag or ipld-resolver>)
161+
### new Exporter(<cid or ipfsPath>, <dag or ipld-resolver>)
157162

158163
Uses the given [dag API or an ipld-resolver instance][] to fetch an IPFS [UnixFS][] object(s) by their multiaddress.
159164

circle.yml

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ dependencies:
88
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
99
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
1010
- sudo apt-get update
11+
- sudo apt-get install libpango-1.0-0=1.40.1-1ubuntu1 libpangocairo-1.0-0=1.40.1-1ubuntu1 libpangoft2-1.0-0=1.40.1-1ubuntu1 libpangoxft-1.0-0=1.40.1-1ubuntu1
1112
- sudo apt-get --only-upgrade install google-chrome-stable
1213
- google-chrome --version

package.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
"homepage": "https://github.com/ipfs/js-ipfs-unixfs-engine#readme",
4141
"devDependencies": {
4242
"aegir": "^11.0.2",
43-
"chai": "^3.5.0",
43+
"chai": "^4.0.1",
4444
"dirty-chai": "^1.2.2",
45-
"ipfs": "^0.24.0",
45+
"ipfs": "^0.24.1",
4646
"ipfs-block-service": "^0.9.1",
4747
"ipfs-repo": "^0.13.1",
4848
"ncp": "^2.0.0",
@@ -54,26 +54,28 @@
5454
},
5555
"dependencies": {
5656
"async": "^2.4.1",
57+
"bs58": "^4.0.1",
5758
"cids": "^0.5.0",
5859
"deep-extend": "^0.5.0",
5960
"ipfs-unixfs": "^0.1.11",
6061
"ipld-dag-pb": "^0.11.0",
6162
"ipld-resolver": "^0.11.1",
62-
"is-ipfs": "^0.3.0",
6363
"left-pad": "^1.1.3",
6464
"lodash": "^4.17.4",
6565
"multihashes": "^0.4.5",
6666
"multihashing-async": "^0.4.5",
6767
"pull-batch": "^1.0.0",
6868
"pull-block": "^1.2.0",
6969
"pull-cat": "^1.1.11",
70+
"pull-defer": "^0.2.2",
7071
"pull-pair": "^1.1.0",
7172
"pull-paramap": "^1.2.2",
7273
"pull-pause": "0.0.1",
7374
"pull-pushable": "^2.1.1",
7475
"pull-stream": "^3.6.0",
7576
"pull-traverse": "^1.0.3",
7677
"pull-write": "^1.1.2",
78+
"safe-buffer": "^5.1.0",
7779
"sparse-array": "^1.3.1"
7880
},
7981
"contributors": [
@@ -88,4 +90,4 @@
8890
"jbenet <[email protected]>",
8991
"nginnever <[email protected]>"
9092
]
91-
}
93+
}

src/exporter/dir-flat.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,40 @@ const cat = require('pull-cat')
99
// Logic to export a unixfs directory.
1010
module.exports = dirExporter
1111

12-
function dirExporter (node, name, ipldResolver, resolve, parent) {
12+
function dirExporter (node, name, pathRest, ipldResolver, resolve, parent) {
13+
const accepts = pathRest[0]
14+
1315
const dir = {
1416
path: name,
1517
hash: node.multihash
1618
}
1719

18-
return cat([
19-
pull.values([dir]),
20+
const streams = [
2021
pull(
2122
pull.values(node.links),
2223
pull.map((link) => ({
24+
linkName: link.name,
2325
path: path.join(name, link.name),
2426
hash: link.multihash
2527
})),
28+
pull.filter((item) => accepts === undefined || item.linkName === accepts),
2629
paramap((item, cb) => ipldResolver.get(new CID(item.hash), (err, n) => {
2730
if (err) {
2831
return cb(err)
2932
}
3033

31-
cb(null, resolve(n.value, item.path, ipldResolver, name, parent))
34+
cb(null, resolve(n.value, accepts || item.path, pathRest, ipldResolver, name, parent))
3235
})),
3336
pull.flatten()
3437
)
35-
])
38+
]
39+
40+
// place dir before if not specifying subtree
41+
if (!pathRest.length) {
42+
streams.unshift(pull.values([dir]))
43+
}
44+
45+
pathRest.shift()
46+
47+
return cat(streams)
3648
}

src/exporter/dir-hamt-sharded.js

+37-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const cleanHash = require('./clean-multihash')
1010
// Logic to export a unixfs directory.
1111
module.exports = shardedDirExporter
1212

13-
function shardedDirExporter (node, name, ipldResolver, resolve, parent) {
13+
function shardedDirExporter (node, name, pathRest, ipldResolver, resolve, parent) {
1414
let dir
1515
if (!parent || parent.path !== name) {
1616
dir = [{
@@ -19,30 +19,54 @@ function shardedDirExporter (node, name, ipldResolver, resolve, parent) {
1919
}]
2020
}
2121

22-
return cat([
23-
pull.values(dir),
22+
const streams = [
2423
pull(
2524
pull.values(node.links),
2625
pull.map((link) => {
2726
// remove the link prefix (2 chars for the bucket index)
28-
let p = link.name.substring(2)
29-
// another sharded dir or file?
30-
p = p ? path.join(name, p) : name
31-
32-
return {
33-
name: link.name,
34-
path: p,
35-
hash: link.multihash
27+
const p = link.name.substring(2)
28+
const pp = p ? path.join(name, p) : name
29+
let accept = true
30+
let fromPathRest = false
31+
32+
if (p && pathRest.length) {
33+
fromPathRest = true
34+
accept = (p === pathRest[0])
35+
}
36+
if (accept) {
37+
return {
38+
fromPathRest: fromPathRest,
39+
name: p,
40+
path: pp,
41+
hash: link.multihash,
42+
pathRest: p ? pathRest.slice(1) : pathRest
43+
}
44+
} else {
45+
return ''
3646
}
3747
}),
48+
pull.filter(Boolean),
3849
paramap((item, cb) => ipldResolver.get(new CID(item.hash), (err, n) => {
3950
if (err) {
4051
return cb(err)
4152
}
4253

43-
cb(null, resolve(n.value, item.path, ipldResolver, (dir && dir[0]) || parent))
54+
cb(
55+
null,
56+
resolve(
57+
n.value,
58+
item.fromPathRest ? item.name : item.path,
59+
item.pathRest,
60+
ipldResolver,
61+
(dir && dir[0]) || parent))
4462
})),
4563
pull.flatten()
4664
)
47-
])
65+
]
66+
67+
if (!pathRest.length) {
68+
streams.unshift(pull.values(dir))
69+
}
70+
71+
return cat(streams)
4872
}

src/exporter/dir.js

-53
This file was deleted.

src/exporter/file.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const pull = require('pull-stream')
77
const paramap = require('pull-paramap')
88

99
// Logic to export a single (possibly chunked) unixfs file.
10-
module.exports = (node, name, ipldResolver) => {
10+
module.exports = (node, name, pathRest, ipldResolver) => {
1111
function getData (node) {
1212
try {
1313
const file = UnixFS.unmarshal(node.data)
@@ -25,6 +25,12 @@ module.exports = (node, name, ipldResolver) => {
2525
)
2626
}
2727

28+
const accepts = pathRest.shift()
29+
30+
if (accepts !== undefined && accepts !== name) {
31+
return pull.empty()
32+
}
33+
2834
let content = pull(
2935
traverse.depthFirst(node, visitor),
3036
pull.map(getData)

0 commit comments

Comments
 (0)