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

Commit e0d0beb

Browse files
committed
chore: return empty buffer for files with no data
Part of making the hases emitted by js-ipfs the same as go-ipfs meant unsetting the data attribute of UnixFS metadata objects if the data buffer had 0 length. ipfs.files.get converts a pull stream of files into a readable stream of files and all of the content pull streams too. If an empty buffer is not emitted by the file content pull stream for file with no data then no file is written out to the filesystem. This was a fun bug to track down. This module used to emit empty buffers for UnixFS metadata with no data property but I removed it as part of #208 as there was no test that failed without it so here we reinstate it and add a test.
1 parent 524e22d commit e0d0beb

File tree

4 files changed

+105
-6
lines changed

4 files changed

+105
-6
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"cids": "~0.5.3",
6161
"deep-extend": "~0.6.0",
6262
"ipfs-unixfs": "~0.1.14",
63-
"ipld": "^0.17.2",
63+
"ipld": "~0.17.2",
6464
"ipld-dag-pb": "~0.14.4",
6565
"left-pad": "^1.3.0",
6666
"lodash": "^4.17.10",

src/exporter/file.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,11 @@ function streamBytes (dag, node, fileSize, offset, length) {
6363
let streamPosition = 0
6464

6565
function getData ({ node, start }) {
66-
if (!node || !node.data) {
67-
return
68-
}
69-
7066
try {
7167
const file = UnixFS.unmarshal(node.data)
7268

7369
if (!file.data) {
74-
return
70+
return Buffer.alloc(0)
7571
}
7672

7773
const block = extractDataFromBlock(file.data, start, offset, end)

test/exporter.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ const CID = require('cids')
1414
const loadFixture = require('aegir/fixtures')
1515
const doUntil = require('async/doUntil')
1616
const waterfall = require('async/waterfall')
17+
const series = require('async/series')
18+
const fs = require('fs')
19+
const path = require('path')
20+
const push = require('pull-pushable')
21+
const toPull = require('stream-to-pull-stream')
22+
const toStream = require('pull-stream-to-stream')
1723

1824
const unixFSEngine = require('./../src')
1925
const exporter = unixFSEngine.exporter
@@ -64,6 +70,54 @@ module.exports = (repo) => {
6470
})
6571
}
6672

73+
function addTestDirectory ({directory, strategy = 'balanced', maxChunkSize}, callback) {
74+
const input = push()
75+
const dirName = path.basename(directory)
76+
77+
pull(
78+
input,
79+
pull.map((file) => {
80+
const ipfsPath = `${path.join(dirName, file.substring(directory.length))}`
81+
82+
return {
83+
path: ipfsPath,
84+
content: toPull.source(fs.createReadStream(file))
85+
}
86+
}),
87+
importer(ipld, {
88+
strategy,
89+
maxChunkSize
90+
}),
91+
pull.collect(callback)
92+
)
93+
94+
const listFiles = (directory, depth, stream, cb) => {
95+
waterfall([
96+
(done) => fs.stat(directory, done),
97+
(stats, done) => {
98+
if (stats.isDirectory()) {
99+
return waterfall([
100+
(done) => fs.readdir(directory, done),
101+
(children, done) => {
102+
series(
103+
children.map(child => (next) => listFiles(path.join(directory, child), depth + 1, stream, next)),
104+
done
105+
)
106+
}
107+
], done)
108+
}
109+
110+
stream.push(directory)
111+
done()
112+
}
113+
], cb)
114+
}
115+
116+
listFiles(directory, 0, input, () => {
117+
input.end()
118+
})
119+
}
120+
67121
function checkBytesThatSpanBlocks (strategy, cb) {
68122
const bytesInABlock = 262144
69123
const bytes = Buffer.alloc(bytesInABlock + 100, 0)
@@ -517,6 +571,55 @@ module.exports = (repo) => {
517571
checkBytesThatSpanBlocks('trickle', done)
518572
})
519573

574+
it('exports a directory containing an empty file whose content gets turned into a ReadableStream', function (done) {
575+
// replicates the behaviour of ipfs.files.get
576+
waterfall([
577+
(cb) => addTestDirectory({
578+
directory: path.join(__dirname, 'fixtures', 'dir-with-empty-files')
579+
}, cb),
580+
(result, cb) => {
581+
const dir = result.pop()
582+
583+
pull(
584+
exporter(dir.multihash, ipld),
585+
pull.map((file) => {
586+
if (file.content) {
587+
file.content = toStream.source(file.content)
588+
file.content.pause()
589+
}
590+
591+
return file
592+
}),
593+
pull.collect((error, files) => {
594+
if (error) {
595+
return cb(error)
596+
}
597+
598+
series(
599+
files
600+
.filter(file => Boolean(file.content))
601+
.map(file => {
602+
return (done) => {
603+
if (file.content) {
604+
file.content
605+
.pipe(toStream.sink(pull.collect((error, bufs) => {
606+
expect(error).to.not.exist()
607+
expect(bufs.length).to.equal(1)
608+
expect(bufs[0].length).to.equal(0)
609+
610+
done()
611+
})))
612+
}
613+
}
614+
}),
615+
cb
616+
)
617+
})
618+
)
619+
}
620+
], done)
621+
})
622+
520623
// TODO: This needs for the stores to have timeouts,
521624
// otherwise it is impossible to predict if a file doesn't
522625
// really exist

test/fixtures/dir-with-empty-files/empty-file.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)