From f8bb8a31a8008d5b330c21197feb99ef140e1ed6 Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Wed, 9 Dec 2020 19:30:55 -0800 Subject: [PATCH 1/5] fix: ipfs.ls() now correctly yields a single file object when the path is a file --- packages/ipfs-core/src/components/ls.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ipfs-core/src/components/ls.js b/packages/ipfs-core/src/components/ls.js index ae34e1aee9..1fe0234f2c 100644 --- a/packages/ipfs-core/src/components/ls.js +++ b/packages/ipfs-core/src/components/ls.js @@ -34,7 +34,8 @@ module.exports = function ({ ipld, preload }) { } if (file.unixfs.type === 'file') { - return mapFile(file, options) + yield mapFile(file, options) + return } if (file.unixfs.type.includes('dir')) { From c44a0a685a022bbb62bba4e667b071378351c3a8 Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Thu, 10 Dec 2020 20:25:35 -0800 Subject: [PATCH 2/5] tests: add test for ls() on a single file --- packages/interface-ipfs-core/src/ls.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/interface-ipfs-core/src/ls.js b/packages/interface-ipfs-core/src/ls.js index 533c229faf..0c14548f6b 100644 --- a/packages/interface-ipfs-core/src/ls.js +++ b/packages/interface-ipfs-core/src/ls.js @@ -218,5 +218,18 @@ module.exports = (common, options) => { expect(output).to.have.lengthOf(1) expect(output[0]).to.have.property('path', `${path}/${subfile}`) }) + + it('should ls single file', async () => { + const file = randomName('F0') + + const input = { path: file, content: randomName('D1') } + + const res = await ipfs.add(input, { wrapWithDirectory: true }) + const path = `${res.cid}/${file}` + const output = await all(ipfs.ls(path)) + + expect(output).to.have.lengthOf(1) + expect(output[0]).to.have.property('path', path) + }) }) } From 4c89d5ee2229d34af03b1a72f73bcef930f39afa Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Thu, 10 Dec 2020 22:26:43 -0800 Subject: [PATCH 3/5] test: attempt to make ls single file test more compatible with go-ipfs --- packages/interface-ipfs-core/src/ls.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interface-ipfs-core/src/ls.js b/packages/interface-ipfs-core/src/ls.js index 0c14548f6b..96222a8944 100644 --- a/packages/interface-ipfs-core/src/ls.js +++ b/packages/interface-ipfs-core/src/ls.js @@ -220,11 +220,12 @@ module.exports = (common, options) => { }) it('should ls single file', async () => { + const dir = randomName('DIR') const file = randomName('F0') - const input = { path: file, content: randomName('D1') } + const input = { path: `${dir}/${file}`, content: randomName('D1') } - const res = await ipfs.add(input, { wrapWithDirectory: true }) + const res = await ipfs.add(input) const path = `${res.cid}/${file}` const output = await all(ipfs.ls(path)) From 8190329ac1d62ff9195f5d09bf7adbf2b8e64db4 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 11 Dec 2020 12:18:00 +0000 Subject: [PATCH 4/5] chore: fix up tests over http and add test for files with metadata --- packages/interface-ipfs-core/src/ls.js | 53 +++++++++++++ packages/ipfs-http-client/src/ls.js | 78 ++++++++++++------- .../src/api/resources/files-regular.js | 14 ++++ packages/ipfs/test/interface-http-go.js | 8 ++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/packages/interface-ipfs-core/src/ls.js b/packages/interface-ipfs-core/src/ls.js index 96222a8944..ae670e5852 100644 --- a/packages/interface-ipfs-core/src/ls.js +++ b/packages/interface-ipfs-core/src/ls.js @@ -232,5 +232,58 @@ module.exports = (common, options) => { expect(output).to.have.lengthOf(1) expect(output[0]).to.have.property('path', path) }) + + it('should ls single file with metadata', async () => { + const dir = randomName('DIR') + const file = randomName('F0') + + const input = { + path: `${dir}/${file}`, + content: randomName('D1'), + mode: 0o631, + mtime: { + secs: 5000, + nsecs: 100 + } + } + + const res = await ipfs.add(input) + const path = `${res.cid}/${file}` + const output = await all(ipfs.ls(res.cid)) + + expect(output).to.have.lengthOf(1) + expect(output[0]).to.have.property('path', path) + expect(output[0]).to.have.property('mode', input.mode) + expect(output[0]).to.have.deep.property('mtime', input.mtime) + }) + + it('should ls single file without containing directory', async () => { + const input = { content: randomName('D1') } + + const res = await ipfs.add(input) + const output = await all(ipfs.ls(res.cid)) + + expect(output).to.have.lengthOf(1) + expect(output[0]).to.have.property('path', res.cid.toString()) + }) + + it('should ls single file without containing directory with metadata', async () => { + const input = { + content: randomName('D1'), + mode: 0o631, + mtime: { + secs: 5000, + nsecs: 100 + } + } + + const res = await ipfs.add(input) + const output = await all(ipfs.ls(res.cid)) + + expect(output).to.have.lengthOf(1) + expect(output[0]).to.have.property('path', res.cid.toString()) + expect(output[0]).to.have.property('mode', input.mode) + expect(output[0]).to.have.deep.property('mtime', input.mtime) + }) }) } diff --git a/packages/ipfs-http-client/src/ls.js b/packages/ipfs-http-client/src/ls.js index ff9cdbb571..f51a9f640e 100644 --- a/packages/ipfs-http-client/src/ls.js +++ b/packages/ipfs-http-client/src/ls.js @@ -3,14 +3,54 @@ const CID = require('cids') const configure = require('./lib/configure') const toUrlSearchParams = require('./lib/to-url-search-params') +const stat = require('./files/stat') -module.exports = configure(api => { +module.exports = configure((api, opts) => { return async function * ls (path, options = {}) { + const pathStr = `${path instanceof Uint8Array ? new CID(path) : path}` + + async function mapLink (link) { + let hash = link.Hash + + if (hash.includes('/')) { + // the hash is a path, but we need the CID + const ipfsPath = hash.startsWith('/ipfs/') ? hash : `/ipfs/${hash}` + const stats = await stat(opts)(ipfsPath) + + hash = stats.cid + } + + const entry = { + name: link.Name, + path: pathStr + (link.Name ? `/${link.Name}` : ''), + size: link.Size, + cid: new CID(hash), + type: typeOf(link), + depth: link.Depth || 1 + } + + if (link.Mode) { + entry.mode = parseInt(link.Mode, 8) + } + + if (link.Mtime !== undefined && link.Mtime !== null) { + entry.mtime = { + secs: link.Mtime + } + + if (link.MtimeNsecs !== undefined && link.MtimeNsecs !== null) { + entry.mtime.nsecs = link.MtimeNsecs + } + } + + return entry + } + const res = await api.post('ls', { timeout: options.timeout, signal: options.signal, searchParams: toUrlSearchParams({ - arg: `${path instanceof Uint8Array ? new CID(path) : path}`, + arg: pathStr, ...options }), headers: options.headers @@ -28,37 +68,19 @@ module.exports = configure(api => { throw new Error('expected one array in results.Objects') } - result = result.Links - if (!Array.isArray(result)) { + const links = result.Links + if (!Array.isArray(links)) { throw new Error('expected one array in results.Objects[0].Links') } - for (const link of result) { - const entry = { - name: link.Name, - path: path + '/' + link.Name, - size: link.Size, - cid: new CID(link.Hash), - type: typeOf(link), - depth: link.Depth || 1 - } + if (!links.length) { + // no links, this is a file, yield a single result + yield mapLink(result) - if (link.Mode) { - entry.mode = parseInt(link.Mode, 8) - } - - if (link.Mtime !== undefined && link.Mtime !== null) { - entry.mtime = { - secs: link.Mtime - } - - if (link.MtimeNsecs !== undefined && link.MtimeNsecs !== null) { - entry.mtime.nsecs = link.MtimeNsecs - } - } - - yield entry + return } + + yield * links.map(mapLink) } } }) diff --git a/packages/ipfs-http-server/src/api/resources/files-regular.js b/packages/ipfs-http-server/src/api/resources/files-regular.js index b3d27bc688..bc0480b4e2 100644 --- a/packages/ipfs-http-server/src/api/resources/files-regular.js +++ b/packages/ipfs-http-server/src/api/resources/files-regular.js @@ -423,6 +423,20 @@ exports.ls = { return output } + const stat = await ipfs.files.stat(path.startsWith('/ipfs/') ? path : `/ipfs/${path}`) + + if (stat.type === 'file') { + // return single object with metadata + return h.response({ + Objects: [{ + ...mapLink(stat), + Hash: path, + Depth: 1, + Links: [] + }] + }) + } + if (!stream) { try { const links = await all(ipfs.ls(path, { diff --git a/packages/ipfs/test/interface-http-go.js b/packages/ipfs/test/interface-http-go.js index c3a60d6d0d..6d56c3800b 100644 --- a/packages/ipfs/test/interface-http-go.js +++ b/packages/ipfs/test/interface-http-go.js @@ -46,6 +46,14 @@ describe('interface-ipfs-core over ipfs-http-client tests against go-ipfs', () = name: 'should ls with metadata', reason: 'TODO not implemented in go-ipfs yet' }, + { + name: 'should ls single file with metadata', + reason: 'TODO not implemented in go-ipfs yet' + }, + { + name: 'should ls single file without containing directory with metadata', + reason: 'TODO not implemented in go-ipfs yet' + }, { name: 'should override raw leaves when file is smaller than one block and metadata is present', reason: 'TODO not implemented in go-ipfs yet' From e420c99f3c5282e5481a5a1c143ff67c80d030d8 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 11 Dec 2020 12:31:50 +0000 Subject: [PATCH 5/5] chore: fix up http injection tests --- .../src/api/resources/files-regular.js | 5 +- .../ipfs-http-server/test/inject/files.js | 46 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/ipfs-http-server/src/api/resources/files-regular.js b/packages/ipfs-http-server/src/api/resources/files-regular.js index bc0480b4e2..c3d9725bd1 100644 --- a/packages/ipfs-http-server/src/api/resources/files-regular.js +++ b/packages/ipfs-http-server/src/api/resources/files-regular.js @@ -401,13 +401,16 @@ exports.ls = { const mapLink = link => { const output = { - Name: link.name, Hash: cidToString(link.cid, { base: cidBase }), Size: link.size, Type: toTypeCode(link.type), Depth: link.depth } + if (link.name) { + output.Name = link.name + } + if (link.mode != null) { output.Mode = link.mode.toString(8).padStart(4, '0') } diff --git a/packages/ipfs-http-server/test/inject/files.js b/packages/ipfs-http-server/test/inject/files.js index 8cf18c32c4..9b73c2ed7f 100644 --- a/packages/ipfs-http-server/test/inject/files.js +++ b/packages/ipfs-http-server/test/inject/files.js @@ -30,7 +30,10 @@ describe('/files', () => { cat: sinon.stub(), get: sinon.stub(), ls: sinon.stub(), - refs: sinon.stub() + refs: sinon.stub(), + files: { + stat: sinon.stub() + } } ipfs.refs.local = sinon.stub() @@ -352,6 +355,9 @@ describe('/files', () => { }) it('should list directory contents', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({ + type: 'directory' + }) ipfs.ls.withArgs(`${cid}`, defaultOptions).returns([{ name: 'link', cid, @@ -380,7 +386,36 @@ describe('/files', () => { }) }) + it('should list a file', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}/derp`).returns({ + cid, + size: 10, + type: 'file', + depth: 1, + mode: 0o420 + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/ls?arg=${cid}/derp` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Objects[0]', { + Hash: `${cid}/derp`, + Depth: 1, + Mode: '0420', + Size: 10, + Type: 2, + Links: [] + }) + expect(ipfs.ls.called).to.be.false() + }) + it('should list directory contents without unixfs v1.5 fields', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({ + type: 'directory' + }) ipfs.ls.withArgs(`${cid}`, defaultOptions).returns([{ name: 'link', cid, @@ -408,6 +443,9 @@ describe('/files', () => { }) it('should list directory contents recursively', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({ + type: 'directory' + }) ipfs.ls.withArgs(`${cid}`, { ...defaultOptions, recursive: true @@ -456,6 +494,9 @@ describe('/files', () => { }) it('accepts a timeout', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({ + type: 'directory' + }) ipfs.ls.withArgs(`${cid}`, { ...defaultOptions, timeout: 1000 @@ -477,6 +518,9 @@ describe('/files', () => { }) it('accepts a timeout when streaming', async () => { + ipfs.files.stat.withArgs(`/ipfs/${cid}`).returns({ + type: 'directory' + }) ipfs.ls.withArgs(`${cid}`, { ...defaultOptions, timeout: 1000