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

Commit 5d189a6

Browse files
committed
feat: most of the cp command
License: MIT Signed-off-by: achingbrain <[email protected]>
1 parent db30286 commit 5d189a6

File tree

11 files changed

+355
-14
lines changed

11 files changed

+355
-14
lines changed

src/cli/cp.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
module.exports = {
4+
command: 'cp <source> <dest>',
5+
6+
describe: 'Copy files between locations in the mfs.',
7+
8+
builder: {
9+
recursive: {
10+
alias: 'r',
11+
type: 'boolean',
12+
default: false,
13+
describe: 'Copy directories recursively'
14+
},
15+
parents: {
16+
alias: 'p',
17+
type: 'boolean',
18+
default: false,
19+
describe: 'Create any non-existent intermediate directories'
20+
}
21+
},
22+
23+
handler (argv) {
24+
let {
25+
source,
26+
dest,
27+
ipfs,
28+
recursive,
29+
parents
30+
} = argv
31+
32+
ipfs.mfs.cp(source, dest, {
33+
recursive,
34+
parents
35+
}, (error) => {
36+
if (error) {
37+
throw error
38+
}
39+
})
40+
}
41+
}

src/core/cp.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict'
2+
3+
const promisify = require('promisify-es6')
4+
const waterfall = require('async/waterfall')
5+
const parallel = require('async/parallel')
6+
const series = require('async/series')
7+
const path = require('path')
8+
const {
9+
traverseTo,
10+
addLink,
11+
updateTree,
12+
updateMfsRoot
13+
} = require('./utils')
14+
const stat = require('./stat')
15+
16+
const defaultOptions = {
17+
recursive: false,
18+
parents: false,
19+
flush: true,
20+
format: 'dag-pb',
21+
hashAlg: 'sha2-256'
22+
}
23+
24+
module.exports = function mfsCp (ipfs) {
25+
return promisify(function () {
26+
const args = Array.prototype.slice.call(arguments)
27+
const callback = args.pop()
28+
29+
if (args.length < 2) {
30+
return callback(new Error('Please specify a source(s) and a destination'))
31+
}
32+
33+
let destination = args.pop()
34+
let options = {}
35+
36+
if (typeof destination !== 'string') {
37+
options = destination
38+
destination = args.pop()
39+
}
40+
41+
options = Object.assign({}, defaultOptions, options)
42+
43+
const sources = args.map(source => ({
44+
path: source,
45+
name: path.basename(source),
46+
dir: path.dirname(source)
47+
}))
48+
49+
destination = {
50+
path: destination,
51+
name: path.basename(destination),
52+
dir: path.dirname(destination)
53+
}
54+
55+
if (sources.length < 1) {
56+
return callback(new Error('Please specify a path to copy'))
57+
}
58+
59+
if (sources.length === 1) {
60+
return copyToFile(ipfs, sources.pop(), destination, options, callback)
61+
}
62+
63+
copyToDirectory(ipfs, sources, destination, options, callback)
64+
})
65+
}
66+
67+
const copyToFile = (ipfs, source, destination, options, callback) => {
68+
waterfall([
69+
(cb) => {
70+
parallel([
71+
(next) => stat(ipfs)(source.path, options, next),
72+
(next) => stat(ipfs)(destination.path, options, (error) => {
73+
if (!error) {
74+
return next(new Error('Destination exists'))
75+
}
76+
77+
next()
78+
}),
79+
(next) => traverseTo(ipfs, destination.dir, options, next)
80+
], cb)
81+
},
82+
([sourceStats, _, dest], cb) => {
83+
waterfall([
84+
(next) => addLink(ipfs, {
85+
parent: dest.node,
86+
child: sourceStats, // nb. file size here is not including protobuf wrapper so is wrong
87+
name: destination.name
88+
}, next),
89+
(newParent, next) => {
90+
dest.node = newParent
91+
updateTree(ipfs, dest, next)
92+
},
93+
(newRoot, cb) => updateMfsRoot(ipfs, newRoot.node.multihash, cb)
94+
], cb)
95+
}
96+
], callback)
97+
}
98+
99+
const copyToDirectory = (ipfs, sources, destination, options, callback) => {
100+
waterfall([
101+
(cb) => {
102+
series([
103+
// stat in parallel
104+
(done) => parallel(
105+
sources.map(source => (next) => stat(ipfs)(source.path, options, next)),
106+
done
107+
),
108+
// this could end up changing the root mfs node so do it after parallel
109+
(done) => traverseTo(ipfs, destination.path, Object.assign({}, options, {
110+
createLastComponent: true
111+
}), done)
112+
], cb)
113+
},
114+
(results, cb) => {
115+
const dest = results.pop()
116+
const sourceStats = results[0]
117+
118+
waterfall([
119+
(next) => waterfall([
120+
(done) => done(null, dest.node)
121+
].concat(
122+
sourceStats.map((sourceStat, index) => {
123+
return (dest, done) => {
124+
return addLink(ipfs, {
125+
parent: dest,
126+
child: sourceStat, // nb. file size here is not including protobuf wrapper so is wrong
127+
name: sources[index].name
128+
}, done)
129+
}
130+
})
131+
), next),
132+
(newParent, next) => {
133+
dest.node = newParent
134+
135+
updateTree(ipfs, dest, next)
136+
},
137+
(newRoot, cb) => updateMfsRoot(ipfs, newRoot.node.multihash, cb)
138+
], cb)
139+
}
140+
], callback)
141+
}

src/core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
module.exports = {
4+
cp: require('./cp'),
45
ls: require('./ls'),
56
mkdir: require('./mkdir'),
67
read: require('./read'),

src/core/stat.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
traverseTo
99
} = require('./utils')
1010
const waterfall = require('async/waterfall')
11+
const log = require('debug')('mfs:stat')
1112

1213
const defaultOptions = {
1314
hash: false,
@@ -30,6 +31,8 @@ module.exports = function mfsStat (ipfs) {
3031
return callback(error)
3132
}
3233

34+
log(`Fetching stats for ${path}`)
35+
3336
waterfall([
3437
(done) => traverseTo(ipfs, path, options, done),
3538
({ node }, done) => {

src/core/utils/add-link.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const addLink = (ipfs, options, callback) => {
3939
},
4040
(parent, done) => {
4141
if (!options.flush) {
42-
return done()
42+
return done(null, parent)
4343
}
4444

4545
// Persist the new parent DAGNode

src/core/utils/create-node.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ const {
55
DAGNode
66
} = require('ipld-dag-pb')
77

8+
const defaultOptions = {
9+
format: 'dag-pb',
10+
hashAlg: 'sha2-256'
11+
}
12+
813
const createNode = (ipfs, data, links, options, callback) => {
14+
options = Object.assign({}, defaultOptions, options)
15+
916
waterfall([
1017
// Create a DAGNode with the new data
1118
(cb) => DAGNode.create(data, links, cb),

src/core/utils/traverse-to.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ const bs58 = require('bs58')
44
const CID = require('cids')
55
const log = require('debug')('mfs:utils:traverse-to')
66
const UnixFS = require('ipfs-unixfs')
7-
const dagPb = require('ipld-dag-pb')
8-
const {
9-
DAGNode
10-
} = dagPb
117
const waterfall = require('async/waterfall')
128
const reduce = require('async/reduce')
139
const withMfsRoot = require('./with-mfs-root')
@@ -16,12 +12,16 @@ const addLink = require('./add-link')
1612
const {
1713
FILE_SEPARATOR
1814
} = require('./constants')
15+
const createNode = require('./create-node')
16+
17+
const defaultOptions = {
18+
parents: false,
19+
flush: true,
20+
createLastComponent: false
21+
}
1922

2023
const traverseTo = (ipfs, path, options, callback) => {
21-
options = Object.assign({}, {
22-
parents: false,
23-
flush: true
24-
}, options)
24+
options = Object.assign({}, defaultOptions, options)
2525

2626
waterfall([
2727
(done) => withMfsRoot(ipfs, done),
@@ -57,14 +57,18 @@ const traverseTo = (ipfs, path, options, callback) => {
5757
const existingLink = parent.node.links.find(link => link.name === pathSegment)
5858

5959
if (!existingLink) {
60+
if (index === pathSegments.length - 1 && !options.parents && !this.createLastComponent) {
61+
return done(new Error(`Path ${path} did not exist`))
62+
}
63+
6064
if (!options.parents) {
61-
return done(new Error(`Cannot traverse to ${path} - '${pathSegment}' did not exist: Try again with the --parents flag`))
65+
return done(new Error(`Cannot find ${path} - '${pathSegment}' did not exist: Try again with the --parents flag to create it`))
6266
}
6367

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

6670
return waterfall([
67-
(next) => DAGNode.create(new UnixFS('directory').marshal(), [], next),
71+
(next) => createNode(ipfs, new UnixFS('directory').marshal(), [], options, next),
6872
(emptyDirectory, next) => {
6973
addLink(ipfs, {
7074
parent: parent.node,
@@ -81,7 +85,11 @@ const traverseTo = (ipfs, path, options, callback) => {
8185
})
8286
})
8387
}
84-
], done)
88+
], (error, child) => {
89+
trail.push(child)
90+
91+
done(error, child)
92+
})
8593
}
8694

8795
let hash = existingLink.hash || existingLink.multihash

src/core/write/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ module.exports = function mfsWrite (ipfs) {
173173
)
174174

175175
log('Importing file', fileName)
176-
importNode(ipfs, source, options, next)
176+
importNode(ipfs, source, options, (error, result) => {
177+
log(`Imported file ${fileName}`)
178+
next(error, result)
179+
})
177180
}
178181
},
179182

test/browser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict'
22

3+
require('./cp.spec.js')
34
require('./ls.spec.js')
45
require('./mkdir.spec.js')
56
require('./read.spec.js')

0 commit comments

Comments
 (0)