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

Commit 4f805d3

Browse files
dordillealanshaw
authored andcommitted
feat: Add option to specify chunking algorithm when adding files (#1469)
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 #1283 License: MIT Signed-off-by: Dan Ordille <[email protected]>
1 parent 1fb71f2 commit 4f805d3

File tree

5 files changed

+167
-5
lines changed

5 files changed

+167
-5
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: 'size-262144',
140+
describe: 'Chunking algorithm to use, formatted like [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

+9-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,18 @@ class AddHelper extends Duplex {
148149
}
149150

150151
module.exports = function files (self) {
151-
function _addPullStream (options) {
152+
function _addPullStream (options = {}) {
153+
let chunkerOptions
154+
try {
155+
chunkerOptions = parseChunkerString(options.chunker)
156+
} catch (err) {
157+
return pull.map(() => { throw err })
158+
}
152159
const opts = Object.assign({}, {
153160
shardSplitThreshold: self._options.EXPERIMENTAL.sharding
154161
? 1000
155162
: Infinity
156-
}, options)
163+
}, options, chunkerOptions)
157164

158165
if (opts.hashAlg && opts.cidVersion !== 1) {
159166
opts.cidVersion = 1

src/core/utils.js

+81
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,86 @@ 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+
* "size-{size}"
119+
* "rabin"
120+
* "rabin-{avg}"
121+
* "rabin-{min}-{avg}-{max}"
122+
*
123+
* @return {Object} Chunker options for DAGBuilder
124+
*/
125+
function parseChunkerString (chunker) {
126+
if (!chunker) {
127+
return {
128+
chunker: 'fixed'
129+
}
130+
} else if (chunker.startsWith('size-')) {
131+
const sizeStr = chunker.split('-')[1]
132+
const size = parseInt(sizeStr)
133+
if (isNaN(size)) {
134+
throw new Error('Chunker parameter size must be an integer')
135+
}
136+
return {
137+
chunker: 'fixed',
138+
chunkerOptions: {
139+
maxChunkSize: size
140+
}
141+
}
142+
} else if (chunker.startsWith('rabin')) {
143+
return {
144+
chunker: 'rabin',
145+
chunkerOptions: parseRabinString(chunker)
146+
}
147+
} else {
148+
throw new Error(`Unrecognized chunker option: ${chunker}`)
149+
}
150+
}
151+
152+
/**
153+
* Parses rabin chunker string
154+
*
155+
* @param {String} chunker Chunker algorithm supported formats:
156+
* "rabin"
157+
* "rabin-{avg}"
158+
* "rabin-{min}-{avg}-{max}"
159+
*
160+
* @return {Object} rabin chunker options
161+
*/
162+
function parseRabinString (chunker) {
163+
const options = {}
164+
const parts = chunker.split('-')
165+
switch (parts.length) {
166+
case 1:
167+
options.avgChunkSize = 262144
168+
break
169+
case 2:
170+
options.avgChunkSize = parseChunkSize(parts[1], 'avg')
171+
break
172+
case 4:
173+
options.minChunkSize = parseChunkSize(parts[1], 'min')
174+
options.avgChunkSize = parseChunkSize(parts[2], 'avg')
175+
options.maxChunkSize = parseChunkSize(parts[3], 'max')
176+
break
177+
default:
178+
throw new Error('Incorrect chunker format (expected "rabin" "rabin-[avg]" or "rabin-[min]-[avg]-[max]"')
179+
}
180+
181+
return options
182+
}
183+
184+
function parseChunkSize (str, name) {
185+
let size = parseInt(str)
186+
if (isNaN(size)) {
187+
throw new Error(`Chunker parameter ${name} must be an integer`)
188+
}
189+
190+
return size
191+
}
192+
113193
exports.parseIpfsPath = parseIpfsPath
114194
exports.resolvePath = resolvePath
195+
exports.parseChunkerString = parseChunkerString

src/http/api/resources/files.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ exports.add = {
157157
'raw-leaves': Joi.boolean(),
158158
'only-hash': Joi.boolean(),
159159
pin: Joi.boolean().default(true),
160-
'wrap-with-directory': Joi.boolean()
160+
'wrap-with-directory': Joi.boolean(),
161+
chunker: Joi.string()
161162
})
162163
// TODO: Necessary until validate "recursive", "stream-channels" etc.
163164
.options({ allowUnknown: true })
@@ -221,7 +222,8 @@ exports.add = {
221222
onlyHash: request.query['only-hash'],
222223
hashAlg: request.query['hash'],
223224
wrapWithDirectory: request.query['wrap-with-directory'],
224-
pin: request.query.pin
225+
pin: request.query.pin,
226+
chunker: request.query.chunker
225227
}
226228

227229
const aborter = abortable()

test/core/utils.js

+67
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,71 @@ 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+
207+
it('throws an error for unsupported chunker type', () => {
208+
const fn = () => utils.parseChunkerString('fake-512')
209+
expect(fn).to.throw(Error)
210+
})
211+
212+
it('throws an error for incorrect format string', () => {
213+
const fn = () => utils.parseChunkerString('fixed-abc')
214+
expect(fn).to.throw(Error)
215+
})
216+
217+
it('throws an error for incorrect rabin format string', () => {
218+
let fn = () => utils.parseChunkerString('rabin-1-2-3-4')
219+
expect(fn).to.throw(Error)
220+
})
221+
222+
it('throws an error for non integer rabin parameters', () => {
223+
const fn = () => utils.parseChunkerString('rabin-abc')
224+
expect(fn).to.throw(Error)
225+
})
226+
})
160227
})

0 commit comments

Comments
 (0)