Skip to content
This repository has been archived by the owner on Mar 10, 2020. It is now read-only.

Commit

Permalink
feat: mfs ls and mkdir commands
Browse files Browse the repository at this point in the history
  • Loading branch information
achingbrain committed Apr 12, 2018
1 parent b232a09 commit bad24b3
Show file tree
Hide file tree
Showing 13 changed files with 656 additions and 14 deletions.
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,21 @@
"aegir": "^13.0.6",
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"ipfs": "^0.28.2",
"pre-commit": "^1.2.2",
"safe-buffer": "^5.1.1"
"safe-buffer": "^5.1.1",
"tmp": "0.0.33"
},
"dependencies": {

"async": "^2.6.0",
"bs58": "^4.0.1",
"cids": "~0.5.3",
"debug": "^3.1.0",
"interface-datastore": "^0.4.2",
"ipfs-unixfs": "^0.1.14",
"ipfs-unixfs-engine": "~0.27.0",
"promisify-es6": "^1.0.3",
"pull-stream": "^3.6.7"
},
"pre-commit": [
"lint",
Expand Down
Empty file added src/cli/index.js
Empty file.
44 changes: 44 additions & 0 deletions src/cli/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

const {
print
} = require('./utils')

module.exports = {
command: 'ls <path>',

describe: 'List directories in the local mutable namespace.',

builder: {
long: {
alias: 'l',
type: 'boolean',
default: false,
describe: 'Use long listing format.'
}
},

handler (argv) {
let {
path,
ipfs,
long
} = argv

ipfs.mfs.ls(path, {
long
}, (error, files) => {
if (error) {
throw error
}

files.forEach(link => {
if (long) {
return print(`${link.name} ${link.hash} ${link.size}`)
}

print(link.name)
})
})
}
}
58 changes: 58 additions & 0 deletions src/cli/mkdir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

const {
print
} = require('./utils')

module.exports = {
command: 'mkdir <path>',

describe: 'Make directories.',

builder: {
parents: {
alias: 'p',
type: 'boolean',
default: false,
describe: 'No error if existing, make parent directories as needed.'
},
cidVersion: {
alias: ['cid-ver', 'cid-version'],
type: 'integer',
describe: 'Cid version to use. (experimental).'
},
hash: {
type: 'string',
describe: 'Hash function to use. Will set Cid version to 1 if used. (experimental).'
},
flush: {
alias: 'f',
type: 'boolean',
describe: 'Weird undocumented option'
}
},

handler (argv) {
let {
path,
ipfs,
parents,
cidVersion,
hash,
flush
} = argv

ipfs.mfs.mkdir(path, {
parents,
cidVersion,
hash,
flush
}, (error, result) => {
if (error) {
throw error
}

print(result)
})
}
}
21 changes: 21 additions & 0 deletions src/cli/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

let visible = true

const disablePrinting = () => {
visible = false
}

const print = (msg = '', newline = true) => {
if (!visible) {
return
}

msg = newline ? msg + '\n' : msg
process.stdout.write(msg)
}

module.exports = {
disablePrinting,
print
}
6 changes: 6 additions & 0 deletions src/core/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict'

module.exports = {
ls: require('./ls'),
mkdir: require('./mkdir')
}
62 changes: 62 additions & 0 deletions src/core/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict'

const exporter = require('ipfs-unixfs-engine').exporter
const promisify = require('promisify-es6')
const pull = require('pull-stream')
const {
collect
} = pull
const {
withMfsRoot,
validatePath
} = require('./utils')

const defaultOptions = {
long: false
}

module.exports = function mfsLs (ipfs) {
return promisify((path, options, callback) => {
withMfsRoot(ipfs, (error, root) => {
if (error) {
return callback(error)
}

if (typeof options === 'function') {
callback = options
options = {}
}

options = Object.assign({}, defaultOptions, options)

try {
path = validatePath(path)
root = root.toBaseEncodedString()
} catch (error) {
return callback(error)
}

pull(
exporter(`/ipfs/${root}${path}`, ipfs._ipld),
collect((error, results) => {
if (error) {
return callback(error)
}

if (!results || !results.length) {
return callback(new Error('file does not exist'))
}

const files = (results[0].links || []).map(link => ({
name: link.name,
type: link.type,
size: link.size,
hash: link.multihash
}))

callback(null, files)
})
)
})
})
}
159 changes: 159 additions & 0 deletions src/core/mkdir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
'use strict'

const UnixFS = require('ipfs-unixfs')
const promisify = require('promisify-es6')
const CID = require('cids')
const bs58 = require('bs58')
const log = require('debug')('mfs:mkdir')
const dagPb = require('ipld-dag-pb')
const {
DAGNode,
DAGLink
} = dagPb
const {
waterfall,
reduce
} = require('async')
const {
withMfsRoot,
updateMfsRoot,
validatePath,
FILE_SEPARATOR
} = require('./utils')

const defaultOptions = {
parents: true,
hash: undefined,
cidVersion: undefined
}

const addLink = (ipfs, parent, child, name, callback) => {
waterfall([
(done) => {
DAGNode.rmLink(parent, name, done)
},
(parent, done) => {
DAGNode.addLink(parent, new DAGLink(name, child.size, child.hash || child.multihash), done)
},
(parent, done) => {
ipfs.dag.put(parent, {
cid: new CID(parent.hash || parent.multihash)
}, (error) => done(error, parent))
}
], callback)
}

module.exports = function mfsMkdir (ipfs) {
return promisify((path, options, callback) => {
withMfsRoot(ipfs, (error, root) => {
if (error) {
return callback(error)
}

if (typeof options === 'function') {
callback = options
options = {}
}

options = Object.assign({}, defaultOptions, options)

if (!path) {
return callback(new Error('no path given to Mkdir'))
}

const pathSegments = validatePath(path)
.split(FILE_SEPARATOR)
.filter(Boolean)

if (pathSegments.length === 0) {
return callback(options.parents ? null : new Error(`cannot create directory '${FILE_SEPARATOR}': Already exists`))
}

const trail = []

waterfall([
(cb) => ipfs.dag.get(root, cb),
(result, cb) => {
const rootNode = result.value

trail.push({
name: FILE_SEPARATOR,
node: rootNode,
parent: null
})

reduce(pathSegments.map((pathSegment, index) => ({pathSegment, index})), {
name: FILE_SEPARATOR,
node: rootNode,
parent: null
}, (parent, {pathSegment, index}, done) => {
const lastPathSegment = index === pathSegments.length - 1
const existingLink = parent.node.links.find(link => link.name === pathSegment)

log(`Looking for ${pathSegment} in ${parent.name}`)

if (!existingLink) {
if (!lastPathSegment && !options.parents) {
return done(new Error(`Cannot create ${path} - intermediate directory '${pathSegment}' did not exist: Try again with the --parents flag`))
}

log(`Adding empty directory '${pathSegment}' to parent ${parent.name}`)

return waterfall([
(next) => DAGNode.create(new UnixFS('directory').marshal(), [], next),
(emptyDirectory, next) => {
addLink(ipfs, parent.node, emptyDirectory, pathSegment, (error, updatedParent) => {
parent.node = updatedParent

next(error, {
name: pathSegment,
node: emptyDirectory,
parent: parent
})
})
}
], done)
}

if (lastPathSegment && existingLink && !options.parents) {
return done(new Error(`Cannot create directory '${path}': Already exists`))
}

let hash = existingLink.hash || existingLink.multihash

if (Buffer.isBuffer(hash)) {
hash = bs58.encode(hash)
}

// child existed, fetch it
ipfs.dag.get(hash, (error, result) => {
const child = {
name: pathSegment,
node: result && result.value,
parent: parent
}

trail.push(child)

done(error, child)
})
}, cb)
},
(result, cb) => {
// replace all links and store in the repo
reduce(pathSegments, result, (child, pathSegment, next) => {
addLink(ipfs, child.parent.node, child.node, child.name, (error, updatedParent) => {
child.parent.node = updatedParent

next(error, child.parent)
})
}, cb)
},
(result, cb) => {
// update new MFS root CID
updateMfsRoot(ipfs, result.node.multihash, cb)
}
], callback)
})
})
}
Loading

0 comments on commit bad24b3

Please sign in to comment.