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

Commit 2ea064b

Browse files
committed
feat: Happy path mfs.write command
1 parent 5ff85c6 commit 2ea064b

File tree

8 files changed

+408
-17
lines changed

8 files changed

+408
-17
lines changed

src/core/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
module.exports = {
44
ls: require('./ls'),
55
mkdir: require('./mkdir'),
6-
stat: require('./stat')
6+
stat: require('./stat'),
7+
write: require('./write')
78
}

src/core/stat.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,24 @@ module.exports = function mfsStat (ipfs) {
7171
(next) => ipfs.dag.get(new CID(result.hash), next),
7272
(result, next) => next(null, result.value),
7373
(node, next) => {
74-
const data = unmarshal(node.data)
74+
const meta = unmarshal(node.data)
75+
76+
let size = 0
77+
78+
if (meta.data && meta.data.length) {
79+
size = meta.data.length
80+
}
81+
82+
if (meta.blockSizes && meta.blockSizes.length) {
83+
size = meta.blockSizes.reduce((acc, curr) => acc + curr, 0)
84+
}
7585

7686
next(null, {
7787
hash: node.multihash,
78-
size: data.blockSizes.reduce((acc, curr) => acc + curr, 0),
88+
size: size,
7989
cumulativeSize: node.size,
80-
childBlocks: node.links.length,
81-
type: data.type
90+
childBlocks: meta.blockSizes.length,
91+
type: meta.type
8292
})
8393
}
8494
], done)

src/core/utils.js

+173-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
'use strict'
22

3-
const waterfall = require('async/waterfall')
43
const Key = require('interface-datastore').Key
54
const bs58 = require('bs58')
65
const CID = require('cids')
76
const log = require('debug')('mfs:utils')
7+
const UnixFS = require('ipfs-unixfs')
8+
const dagPb = require('ipld-dag-pb')
9+
const {
10+
DAGNode,
11+
DAGLink
12+
} = dagPb
13+
const {
14+
waterfall,
15+
reduce,
16+
doWhilst
17+
} = require('async')
818

919
const MFS_ROOT_KEY = new Key('/local/filesroot')
1020
const FILE_SEPARATOR = '/'
@@ -84,9 +94,171 @@ const updateMfsRoot = (ipfs, buffer, callback) => {
8494
], (error) => callback(error, buffer))
8595
}
8696

97+
const addLink = (ipfs, options, callback) => {
98+
options = Object.assign({}, {
99+
parent: undefined,
100+
child: undefined,
101+
name: undefined,
102+
flush: true
103+
}, options)
104+
105+
if (!options.parent) {
106+
return callback(new Error('No parent passed to addLink'))
107+
}
108+
109+
if (!options.child) {
110+
return callback(new Error('No child passed to addLink'))
111+
}
112+
113+
if (!options.name) {
114+
return callback(new Error('No name passed to addLink'))
115+
}
116+
117+
waterfall([
118+
(done) => {
119+
// remove the old link if necessary
120+
DAGNode.rmLink(options.parent, options.name, done)
121+
},
122+
(parent, done) => {
123+
// add the new link
124+
DAGNode.addLink(parent, new DAGLink(options.name, options.child.size, options.child.hash || options.child.multihash), done)
125+
},
126+
(parent, done) => {
127+
if (!options.flush) {
128+
return done()
129+
}
130+
131+
// add the new parent DAGNode
132+
ipfs.dag.put(parent, {
133+
cid: new CID(parent.hash || parent.multihash)
134+
}, (error) => done(error, parent))
135+
}
136+
], callback)
137+
}
138+
139+
const traverseTo = (ipfs, path, options, callback) => {
140+
options = Object.assign({}, {
141+
parents: false,
142+
flush: true
143+
}, options)
144+
145+
waterfall([
146+
(done) => withMfsRoot(ipfs, done),
147+
(root, done) => {
148+
const pathSegments = validatePath(path)
149+
.split(FILE_SEPARATOR)
150+
.filter(Boolean)
151+
152+
const trail = []
153+
154+
waterfall([
155+
(cb) => ipfs.dag.get(root, cb),
156+
(result, cb) => {
157+
const rootNode = result.value
158+
159+
trail.push({
160+
name: FILE_SEPARATOR,
161+
node: rootNode,
162+
parent: null
163+
})
164+
165+
reduce(pathSegments.map((pathSegment, index) => ({pathSegment, index})), {
166+
name: FILE_SEPARATOR,
167+
node: rootNode,
168+
parent: null
169+
}, (parent, {pathSegment, index}, done) => {
170+
const lastPathSegment = index === pathSegments.length - 1
171+
const existingLink = parent.node.links.find(link => link.name === pathSegment)
172+
173+
log(`Looking for ${pathSegment} in ${parent.name}`)
174+
175+
if (!existingLink) {
176+
if (!lastPathSegment && !options.parents) {
177+
return done(new Error(`Cannot create ${path} - intermediate directory '${pathSegment}' did not exist: Try again with the --parents flag`))
178+
}
179+
180+
log(`Adding empty directory '${pathSegment}' to parent ${parent.name}`)
181+
182+
return waterfall([
183+
(next) => DAGNode.create(new UnixFS('directory').marshal(), [], next),
184+
(emptyDirectory, next) => {
185+
addLink(ipfs, {
186+
parent: parent.node,
187+
child: emptyDirectory,
188+
name: pathSegment,
189+
flush: options.flush
190+
}, (error, updatedParent) => {
191+
parent.node = updatedParent
192+
193+
next(error, {
194+
name: pathSegment,
195+
node: emptyDirectory,
196+
parent: parent
197+
})
198+
})
199+
}
200+
], done)
201+
}
202+
203+
let hash = existingLink.hash || existingLink.multihash
204+
205+
if (Buffer.isBuffer(hash)) {
206+
hash = bs58.encode(hash)
207+
}
208+
209+
// child existed, fetch it
210+
ipfs.dag.get(hash, (error, result) => {
211+
const child = {
212+
name: pathSegment,
213+
node: result && result.value,
214+
parent: parent
215+
}
216+
217+
trail.push(child)
218+
219+
done(error, child)
220+
})
221+
}, cb)
222+
}
223+
], done)
224+
}
225+
], callback)
226+
}
227+
228+
const updateTree = (ipfs, child, callback) => {
229+
doWhilst(
230+
(next) => {
231+
if (!child.parent) {
232+
const lastChild = child
233+
child = null
234+
return next(null, lastChild)
235+
}
236+
237+
addLink(ipfs, {
238+
parent: child.parent.node,
239+
child: child.node,
240+
name: child.name,
241+
flush: true
242+
}, (error, updatedParent) => {
243+
child.parent.node = updatedParent
244+
245+
const lastChild = child
246+
child = child.parent
247+
248+
next(error, lastChild)
249+
})
250+
},
251+
() => Boolean(child),
252+
callback
253+
)
254+
}
255+
87256
module.exports = {
88257
validatePath,
89258
withMfsRoot,
90259
updateMfsRoot,
260+
traverseTo,
261+
addLink,
262+
updateTree,
91263
FILE_SEPARATOR
92264
}

src/core/write.js

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
'use strict'
2+
3+
const importer = require('ipfs-unixfs-engine').importer
4+
const promisify = require('promisify-es6')
5+
const CID = require('cids')
6+
const pull = require('pull-stream')
7+
const {
8+
collect,
9+
values
10+
} = pull
11+
const {
12+
waterfall
13+
} = require('async')
14+
const {
15+
updateMfsRoot,
16+
validatePath,
17+
traverseTo,
18+
addLink,
19+
updateTree,
20+
FILE_SEPARATOR
21+
} = require('./utils')
22+
23+
const defaultOptions = {
24+
offset: 0,
25+
count: undefined,
26+
create: false,
27+
truncate: false,
28+
length: undefined,
29+
rawLeaves: false,
30+
cidVersion: undefined,
31+
hash: undefined,
32+
parents: false,
33+
progress: undefined,
34+
strategy: 'balanced',
35+
flush: true
36+
}
37+
38+
module.exports = function mfsWrite (ipfs) {
39+
return promisify((path, buffer, options, callback) => {
40+
if (typeof options === 'function') {
41+
callback = options
42+
options = {}
43+
}
44+
45+
options = Object.assign({}, defaultOptions, options)
46+
47+
try {
48+
path = validatePath(path)
49+
} catch (error) {
50+
return callback(error)
51+
}
52+
53+
if (options.count === 0) {
54+
return callback()
55+
}
56+
57+
if (options.count) {
58+
buffer = buffer.slice(0, options.count)
59+
}
60+
61+
const parts = path
62+
.split(FILE_SEPARATOR)
63+
.filter(Boolean)
64+
const fileName = parts.pop()
65+
66+
waterfall([
67+
// walk the mfs tree to the containing folder node
68+
(done) => traverseTo(ipfs, `${FILE_SEPARATOR}${parts.join(FILE_SEPARATOR)}`, options, done),
69+
(containingFolder, done) => {
70+
waterfall([
71+
(next) => {
72+
const existingChild = containingFolder.node.links.reduce((last, child) => {
73+
if (child.name === fileName) {
74+
return child
75+
}
76+
77+
return last
78+
}, null)
79+
80+
if (existingChild) {
81+
// overwrite the existing file or part of it
82+
return next(new Error('Not implemented yet!'))
83+
} else {
84+
// import the file to IPFS and add it as a child of the containing directory
85+
return pull(
86+
values([{
87+
content: buffer
88+
}]),
89+
importer(ipfs._ipld, {
90+
progress: options.progress,
91+
hashAlg: options.hash,
92+
cidVersion: options.cidVersion,
93+
strategy: options.strategy
94+
}),
95+
collect(next)
96+
)
97+
}
98+
},
99+
// load the DAGNode corresponding to the newly added/updated file
100+
(results, next) => ipfs.dag.get(new CID(results[0].multihash), next),
101+
(result, next) => {
102+
// link the newly added DAGNode to the containing older
103+
waterfall([
104+
(cb) => addLink(ipfs, {
105+
parent: containingFolder.node,
106+
child: result.value,
107+
name: fileName
108+
}, cb),
109+
(newContainingFolder, cb) => {
110+
containingFolder.node = newContainingFolder
111+
112+
// update all the parent node CIDs
113+
updateTree(ipfs, containingFolder, cb)
114+
}
115+
], next)
116+
},
117+
(result, next) => {
118+
// update new MFS root CID
119+
updateMfsRoot(ipfs, result.node.multihash, next)
120+
}
121+
], done)
122+
}
123+
], callback)
124+
})
125+
}

test/fixtures/index.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ const {
88
race,
99
waterfall
1010
} = require('async')
11-
const {
12-
ls,
13-
mkdir,
14-
stat
15-
} = require('../../src/core')
11+
const core = require('../../src/core')
1612

1713
const createMfs = promisify((cb) => {
1814
let node = ipfs.createNode({
@@ -25,12 +21,17 @@ const createMfs = promisify((cb) => {
2521
(next) => node.once('ready', next)
2622
], (error) => done(error, node)),
2723
(node, done) => {
28-
done(null, {
29-
ls: ls(node),
30-
mkdir: mkdir(node),
31-
stat: stat(node),
32-
node: node
33-
})
24+
const mfs = {
25+
node
26+
}
27+
28+
for (let key in core) {
29+
if (core.hasOwnProperty(key)) {
30+
mfs[key] = core[key](node)
31+
}
32+
}
33+
34+
done(null, mfs)
3435
}
3536
], cb)
3637
})

test/fixtures/large-file.jpg

479 KB
Loading

test/fixtures/small-file.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello world!

0 commit comments

Comments
 (0)