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

Commit 35687cb

Browse files
pgtedaviddias
authored andcommitted
feat: ipfs.ls (#1073)
1 parent 7dd4e01 commit 35687cb

File tree

9 files changed

+190
-8
lines changed

9 files changed

+190
-8
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@
7575
"form-data": "^2.3.1",
7676
"gulp": "^3.9.1",
7777
"hat": "0.0.3",
78-
"interface-ipfs-core": "~0.33.2",
79-
"ipfsd-ctl": "~0.24.0",
78+
"interface-ipfs-core": "~0.34.3",
79+
"ipfsd-ctl": "~0.24.1",
8080
"left-pad": "^1.1.3",
8181
"lodash": "^4.17.4",
8282
"mocha": "^4.0.1",
@@ -105,18 +105,18 @@
105105
"hapi": "^16.6.2",
106106
"hapi-set-header": "^1.0.2",
107107
"hoek": "^5.0.2",
108-
"ipfs-api": "^15.0.1",
108+
"ipfs-api": "^15.1.0",
109109
"ipfs-bitswap": "~0.17.4",
110110
"ipfs-block": "~0.6.1",
111111
"ipfs-block-service": "~0.13.0",
112112
"ipfs-multipart": "~0.1.0",
113113
"ipfs-repo": "~0.18.3",
114114
"ipfs-unixfs": "~0.1.14",
115-
"ipfs-unixfs-engine": "~0.23.1",
115+
"ipfs-unixfs-engine": "~0.24.1",
116116
"ipld-resolver": "~0.14.1",
117117
"is-ipfs": "^0.3.2",
118118
"is-stream": "^1.1.0",
119-
"joi": "^13.0.1",
119+
"joi": "^13.0.2",
120120
"libp2p": "~0.13.1",
121121
"libp2p-circuit": "~0.1.4",
122122
"libp2p-floodsub": "~0.11.1",
@@ -157,7 +157,7 @@
157157
"readable-stream": "2.3.3",
158158
"safe-buffer": "^5.1.1",
159159
"stream-to-pull-stream": "^1.7.2",
160-
"tar-stream": "^1.5.4",
160+
"tar-stream": "^1.5.5",
161161
"temp": "~0.8.3",
162162
"through2": "^2.0.3",
163163
"update-notifier": "^2.3.0",

src/cli/commands/ls.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict'
2+
3+
const utils = require('../utils')
4+
const Unixfs = require('ipfs-unixfs')
5+
const pull = require('pull-stream')
6+
7+
module.exports = {
8+
command: 'ls <key>',
9+
10+
describe: 'List files for the given directory',
11+
12+
builder: {
13+
v: {
14+
alias: 'headers',
15+
desc: 'Print table headers (Hash, Size, Name).',
16+
type: 'boolean',
17+
default: false
18+
},
19+
'resolve-type': {
20+
desc: 'Resolve linked objects to find out their types. (not implemented yet)',
21+
type: 'boolean',
22+
default: false // should be true when implemented
23+
}
24+
},
25+
26+
handler (argv) {
27+
let path = argv.key
28+
if (path.startsWith('/ipfs/')) {
29+
path = path.replace('/ipfs/', '')
30+
}
31+
32+
argv.ipfs.ls(path, (err, links) => {
33+
if (err) {
34+
throw err
35+
}
36+
37+
if (argv.headers) {
38+
links = [{hash: 'Hash', size: 'Size', name: 'Name'}].concat(links)
39+
}
40+
41+
links = links.filter((link) => link.path !== path)
42+
links.forEach((link) => {
43+
if (link.type === 'dir') {
44+
// directory: add trailing "/"
45+
link.name = (link.name || '') + '/'
46+
}
47+
})
48+
const multihashWidth = Math.max.apply(null, links.map((file) => file.hash.length))
49+
const sizeWidth = Math.max.apply(null, links.map((file) => String(file.size).length))
50+
51+
links.forEach((file) => {
52+
utils.print(utils.rightpad(file.hash, multihashWidth + 1) +
53+
utils.rightpad(file.size || '', sizeWidth + 1) +
54+
file.name)
55+
})
56+
})
57+
}
58+
}

src/cli/utils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,11 @@ exports.createProgressBar = (totalBytes) => {
100100
total: totalBytes
101101
})
102102
}
103+
104+
exports.rightpad = (val, n) => {
105+
let result = String(val)
106+
for (let i = result.length; i < n; ++i) {
107+
result += ' '
108+
}
109+
return result
110+
}

src/core/components/files.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const waterfall = require('async/waterfall')
1313
const isStream = require('is-stream')
1414
const Duplex = require('stream').Duplex
1515
const CID = require('cids')
16+
const toB58String = require('multihashes').toB58String
1617

1718
module.exports = function files (self) {
1819
const createAddPullStream = (options) => {
@@ -118,7 +119,20 @@ module.exports = function files (self) {
118119

119120
getPull: promisify((ipfsPath, callback) => {
120121
callback(null, exporter(ipfsPath, self._ipldResolver))
121-
})
122+
}),
123+
124+
immutableLs: promisify((ipfsPath, callback) => {
125+
pull(
126+
self.files.immutableLsPullStream(ipfsPath),
127+
pull.collect(callback))
128+
}),
129+
130+
immutableLsPullStream: (ipfsPath) => {
131+
return pull(
132+
exporter(ipfsPath, self._ipldResolver, { maxDepth: 1 }),
133+
pull.filter((node) => node.depth === 1),
134+
pull.map((node) => Object.assign({}, node, { hash: toB58String(node.hash) })))
135+
}
122136
}
123137
}
124138

src/core/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ class IPFS extends EventEmitter {
112112

113113
this.state = require('./state')(this)
114114

115+
// ipfs.ls
116+
this.ls = this.files.immutableLs
117+
115118
boot(this)
116119
}
117120
}

src/http/api/resources/files.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,47 @@ exports.add = {
274274
)
275275
}
276276
}
277+
278+
exports.immutableLs = {
279+
// uses common parseKey method that returns a `key`
280+
parseArgs: exports.parseKey,
281+
282+
// main route handler which is called after the above `parseArgs`, but only if the args were valid
283+
handler: (request, reply) => {
284+
const key = request.pre.args.key
285+
const ipfs = request.server.app.ipfs
286+
287+
ipfs.ls(key, (err, files) => {
288+
if (err) {
289+
reply({
290+
Message: 'Failed to list dir: ' + err.message,
291+
Code: 0
292+
}).code(500)
293+
}
294+
295+
reply({
296+
Objects: [{
297+
Hash: key,
298+
Links: files.map((file) => ({
299+
Name: file.name,
300+
Hash: file.hash,
301+
Size: file.size,
302+
Type: toTypeCode(file.type)
303+
}))
304+
}]
305+
})
306+
})
307+
}
308+
}
309+
310+
function toTypeCode (type) {
311+
switch (type) {
312+
case 'dir':
313+
return 1
314+
case 'file':
315+
return 2
316+
default:
317+
return 0
318+
}
319+
}
320+

src/http/api/routes/files.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,16 @@ module.exports = (server) => {
4242
validate: resources.files.add.validate
4343
}
4444
})
45+
46+
api.route({
47+
// TODO fix method
48+
method: '*',
49+
path: '/api/v0/ls',
50+
config: {
51+
pre: [
52+
{ method: resources.files.immutableLs.parseArgs, assign: 'args' }
53+
],
54+
handler: resources.files.immutableLs.handler
55+
}
56+
})
4557
}

test/cli/commands.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const expect = require('chai').expect
55
const runOnAndOff = require('../utils/on-and-off')
66

7-
const commandCount = 56
7+
const commandCount = 57
88

99
describe('commands', () => runOnAndOff((thing) => {
1010
let ipfs

test/cli/files.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,49 @@ describe('files', () => runOnAndOff((thing) => {
254254
})
255255
})
256256

257+
it('ls', () => {
258+
return ipfs('ls QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2')
259+
.then((out) => {
260+
expect(out).to.eql(
261+
'QmQQHYDwAQms78fPcvx1uFFsfho23YJNoewfLbi9AtdyJ9 123530 blocks/\n' +
262+
'QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3939 config\n' +
263+
'Qma13ZrhKG52MWnwtZ6fMD8jGj8d4Q9sJgn5xtKgeZw5uz 5503 datastore/\n' +
264+
'QmUhUuiTKkkK8J6JZ9zmj8iNHPuNfGYcszgRumzhHBxEEU 7397 init-docs/\n' +
265+
'QmR56UJmAaZLXLdTT1ALrE9vVqV8soUEekm9BMd4FnuYqV 10 version\n')
266+
})
267+
})
268+
269+
it('ls -v', () => {
270+
return ipfs('ls /ipfs/QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2 -v')
271+
.then((out) => {
272+
expect(out).to.eql(
273+
'Hash Size Name\n' +
274+
'QmQQHYDwAQms78fPcvx1uFFsfho23YJNoewfLbi9AtdyJ9 123530 blocks/\n' +
275+
'QmPkWYfSLCEBLZu7BZt4kigGDMe3cpogMbeVf97gN2xJDN 3939 config\n' +
276+
'Qma13ZrhKG52MWnwtZ6fMD8jGj8d4Q9sJgn5xtKgeZw5uz 5503 datastore/\n' +
277+
'QmUhUuiTKkkK8J6JZ9zmj8iNHPuNfGYcszgRumzhHBxEEU 7397 init-docs/\n' +
278+
'QmR56UJmAaZLXLdTT1ALrE9vVqV8soUEekm9BMd4FnuYqV 10 version\n')
279+
})
280+
})
281+
282+
it('ls --help', () => {
283+
return ipfs('ls --help')
284+
.then((out) => {
285+
expect(out.split('\n').slice(1)).to.eql(['',
286+
'List files for the given directory',
287+
'',
288+
'Options:',
289+
' -v, --version Show version number [boolean]',
290+
' --silent Show no output. [boolean]',
291+
' --help Show help [boolean]',
292+
' -v, --headers Print table headers (Hash, Size, Name).',
293+
' [boolean] [default: false]',
294+
' --resolve-type Resolve linked objects to find out their types. (not',
295+
' implemented yet) [boolean] [default: false]',
296+
'', ''])
297+
})
298+
})
299+
257300
it('get', () => {
258301
return ipfs('files get QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')
259302
.then((out) => {

0 commit comments

Comments
 (0)