Skip to content

Commit 261d6d2

Browse files
dordillealanshaw
authored andcommitted
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 3caa1eb commit 261d6d2

File tree

5 files changed

+147
-4
lines changed

5 files changed

+147
-4
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
if (options.enableShardingExperiment && utils.isDaemonOn()) {

src/core/components/files.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const OtherBuffer = require('buffer').Buffer
1818
const CID = require('cids')
1919
const toB58String = require('multihashes').toB58String
2020
const errCode = require('err-code')
21+
const parseChunkerString = require('../utils').parseChunkerString
2122

2223
const WRAPPER = 'wrapper/'
2324

@@ -148,12 +149,13 @@ class AddHelper extends Duplex {
148149
}
149150

150151
module.exports = function files (self) {
151-
function _addPullStream (options) {
152+
function _addPullStream (options = {}) {
153+
const chunkerOptions = parseChunkerString(options.chunker)
152154
const opts = Object.assign({}, {
153155
shardSplitThreshold: self._options.EXPERIMENTAL.sharding
154156
? 1000
155157
: Infinity
156-
}, options)
158+
}, options, chunkerOptions)
157159

158160
if (opts.hashAlg && opts.cidVersion !== 1) {
159161
opts.cidVersion = 1

src/core/utils.js

+88
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,93 @@ const resolvePath = promisify(function (objectAPI, ipfsPaths, callback) {
110110
}, callback)
111111
})
112112

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

src/http/api/resources/files.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ exports.add = {
221221
onlyHash: request.query['only-hash'],
222222
hashAlg: request.query['hash'],
223223
wrapWithDirectory: request.query['wrap-with-directory'],
224-
pin: request.query.pin
224+
pin: request.query.pin,
225+
chunker: request.query['chunker']
225226
}
226227

227228
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)