Skip to content

Commit 562e302

Browse files
committed
feat(core): add option to specify chunking algorithm
This allows the chunking algorithm, and options to be specified when using the adding files. Specifying chunker and options are identical to go-ipfs and support the following formats: default, size-{size}, rabin, rabin-{avg}, rabin-{min}-{avg}-{max} This is required to achieve parity with go-ipfs. Fixes ipfs#1283 License: MIT Signed-off-by: Dan Ordille <[email protected]>
1 parent f4344b0 commit 562e302

File tree

5 files changed

+146
-3
lines changed

5 files changed

+146
-3
lines changed

src/cli/commands/files/add.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ module.exports = {
135135
default: false,
136136
describe: 'Only chunk and hash, do not write'
137137
},
138+
chunker: {
139+
default: 'default',
140+
describe: 'Chunking algorithm to use, formatted like [default, size-{size}, rabin, rabin-{avg}, rabin-{min}-{avg}-{max}]'
141+
},
138142
'enable-sharding-experiment': {
139143
type: 'boolean',
140144
default: false
@@ -194,7 +198,8 @@ module.exports = {
194198
onlyHash: argv.onlyHash,
195199
hashAlg: argv.hash,
196200
wrapWithDirectory: argv.wrapWithDirectory,
197-
pin: argv.pin
201+
pin: argv.pin,
202+
chunker: argv.chunker
198203
}
199204

200205
// Temporary restriction on raw-leaves:

src/core/components/files.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const Duplex = require('readable-stream').Duplex
1717
const OtherBuffer = require('buffer').Buffer
1818
const CID = require('cids')
1919
const toB58String = require('multihashes').toB58String
20+
const parseChunkerString = require('../utils').parseChunkerString
2021

2122
const WRAPPER = 'wrapper/'
2223

@@ -134,11 +135,12 @@ class AddHelper extends Duplex {
134135

135136
module.exports = function files (self) {
136137
function _addPullStream (options) {
138+
const chunkerOptions = parseChunkerString(options.chunker)
137139
const opts = Object.assign({}, {
138140
shardSplitThreshold: self._options.EXPERIMENTAL.sharding
139141
? 1000
140142
: Infinity
141-
}, options)
143+
}, options, chunkerOptions)
142144

143145
if (opts.hashAlg && opts.cidVersion !== 1) {
144146
opts.cidVersion = 1

src/core/utils.js

+88
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,93 @@ const resolvePath = promisify(function (objectAPI, ipfsPaths, callback) {
107107
}, callback)
108108
})
109109

110+
/**
111+
* Parses chunker string into options used by DAGBuilder in ipfs-unixfs-engine
112+
*
113+
*
114+
* @param {String} chunker Chunker algorithm supported formats:
115+
* "default" ("")
116+
* "size-{size}",
117+
* "rabin"
118+
* "rabin-{avg}"
119+
* "rabin-{min}-{avg}-{max}"
120+
*
121+
* @return {Object} Chunker options for DAGBuilder
122+
*/
123+
function parseChunkerString (chunker) {
124+
if (!chunker || chunker === '' || chunker === 'default') {
125+
return {
126+
chunker: 'fixed'
127+
}
128+
} else if (chunker.startsWith('size-')) {
129+
const sizeStr = chunker.split('-')[1]
130+
const size = parseInt(sizeStr)
131+
if (isNaN(size)) {
132+
throw new Error('Parameter avg must be an integer')
133+
}
134+
return {
135+
chunker: 'fixed',
136+
chunkerOptions: {
137+
maxChunkSize: size
138+
}
139+
}
140+
} else if (chunker.startsWith('rabin')) {
141+
return {
142+
chunker: 'rabin',
143+
chunkerOptions: parseRabinString(chunker)
144+
}
145+
} else {
146+
throw new Error(`unrecognized chunker option: ${chunker}`)
147+
}
148+
}
149+
150+
/**
151+
* Parses rabin chunker string
152+
*
153+
* @param {String} chunker Chunker algorithm supported formats:
154+
* "rabin"
155+
* "rabin-{avg}"
156+
* "rabin-{min}-{avg}-{max}"
157+
*
158+
* @return {Object} rabin chunker options
159+
*/
160+
function parseRabinString (chunker) {
161+
const options = {}
162+
const parts = chunker.split('-')
163+
switch (parts.length) {
164+
case 1:
165+
options.avgChunkSize = 262144
166+
break
167+
case 2:
168+
options.avgChunkSize = parseInt(parts[1])
169+
if (isNaN(options.avgChunkSize)) {
170+
throw new Error('Parameter avg must be an integer')
171+
}
172+
break
173+
case 4:
174+
options.minChunkSize = parseSub(parts[1].split(':'), 'min')
175+
options.avgChunkSize = parseSub(parts[2].split(':'), 'avg')
176+
options.maxChunkSize = parseSub(parts[3].split(':'), 'max')
177+
break
178+
default:
179+
throw new Error('incorrect format (expected "rabin" "rabin-[avg]" or "rabin-[min]-[avg]-[max]"')
180+
}
181+
182+
return options
183+
}
184+
185+
function parseSub (sub, name) {
186+
if (sub.length > 1 && sub[0] !== name) {
187+
throw new Error('Parameter order must be min:avg:max')
188+
}
189+
let size = parseInt(sub[sub.length - 1])
190+
if (isNaN(size)) {
191+
throw new Error(`Parameter ${name} must be an integer`)
192+
}
193+
194+
return size
195+
}
196+
110197
exports.parseIpfsPath = parseIpfsPath
111198
exports.resolvePath = resolvePath
199+
exports.parseChunkerString = parseChunkerString

src/http/api/resources/files.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ exports.add = {
233233
onlyHash: request.query['only-hash'],
234234
hashAlg: request.query['hash'],
235235
wrapWithDirectory: request.query['wrap-with-directory'],
236-
pin: request.query.pin
236+
pin: request.query.pin,
237+
chunker: request.query['chunker']
237238
}
238239

239240
const aborter = abortable()

test/core/utils.js

+47
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,51 @@ describe('utils', () => {
157157
})
158158
})
159159
})
160+
161+
describe('parseChunkerString', () => {
162+
it('handles an empty string', () => {
163+
const options = utils.parseChunkerString('')
164+
expect(options).to.have.property('chunker').to.equal('fixed')
165+
})
166+
167+
it('handles a null chunker string', () => {
168+
const options = utils.parseChunkerString(null)
169+
expect(options).to.have.property('chunker').to.equal('fixed')
170+
})
171+
172+
it('parses a fixed size string', () => {
173+
const options = utils.parseChunkerString('size-512')
174+
expect(options).to.have.property('chunker').to.equal('fixed')
175+
expect(options)
176+
.to.have.property('chunkerOptions')
177+
.to.have.property('maxChunkSize')
178+
.to.equal(512)
179+
})
180+
181+
it('parses a rabin string without size', () => {
182+
const options = utils.parseChunkerString('rabin')
183+
expect(options).to.have.property('chunker').to.equal('rabin')
184+
expect(options)
185+
.to.have.property('chunkerOptions')
186+
.to.have.property('avgChunkSize')
187+
})
188+
189+
it('parses a rabin string with only avg size', () => {
190+
const options = utils.parseChunkerString('rabin-512')
191+
expect(options).to.have.property('chunker').to.equal('rabin')
192+
expect(options)
193+
.to.have.property('chunkerOptions')
194+
.to.have.property('avgChunkSize')
195+
.to.equal(512)
196+
})
197+
198+
it('parses a rabin string with min, avg, and max', () => {
199+
const options = utils.parseChunkerString('rabin-42-92-184')
200+
expect(options).to.have.property('chunker').to.equal('rabin')
201+
expect(options).to.have.property('chunkerOptions')
202+
expect(options.chunkerOptions).to.have.property('minChunkSize').to.equal(42)
203+
expect(options.chunkerOptions).to.have.property('avgChunkSize').to.equal(92)
204+
expect(options.chunkerOptions).to.have.property('maxChunkSize').to.equal(184)
205+
})
206+
})
160207
})

0 commit comments

Comments
 (0)