This repository has been archived by the owner on Feb 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
files-mfs.js
374 lines (336 loc) · 15.2 KB
/
files-mfs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
'use strict'
const mfs = require('ipfs-mfs/core')
const isPullStream = require('is-pull-stream')
const toPullStream = require('async-iterator-to-pull-stream')
const toReadableStream = require('async-iterator-to-stream')
const pullStreamToAsyncIterator = require('pull-stream-to-async-iterator')
const all = require('async-iterator-all')
const nodeify = require('promise-nodeify')
const PassThrough = require('stream').PassThrough
const pull = require('pull-stream/pull')
const map = require('pull-stream/throughs/map')
const isIpfs = require('is-ipfs')
const { cidToString } = require('../../utils/cid')
/**
* @typedef { import("readable-stream").Readable } ReadableStream
* @typedef { import("pull-stream") } PullStream
*/
const mapLsFile = (options) => {
options = options || {}
const long = options.long || options.l
return (file) => {
return {
hash: long ? cidToString(file.cid, { base: options.cidBase }) : '',
name: file.name,
type: long ? file.type : 0,
size: long ? file.size || 0 : 0
}
}
}
module.exports = (/** @type { import("../index") } */ ipfs) => {
const methodsOriginal = mfs({
ipld: ipfs._ipld,
blocks: ipfs._blockService,
datastore: ipfs._repo.root,
repoOwner: ipfs._options.repoOwner
})
const withPreload = fn => (...args) => {
const paths = args.filter(arg => isIpfs.ipfsPath(arg) || isIpfs.cid(arg))
if (paths.length) {
const options = args[args.length - 1]
if (options.preload !== false) {
paths.forEach(path => ipfs._preload(path))
}
}
return fn(...args)
}
const methods = {
...methodsOriginal,
cp: withPreload(methodsOriginal.cp),
ls: withPreload(methodsOriginal.ls),
mv: withPreload(methodsOriginal.mv),
read: withPreload(methodsOriginal.read),
stat: withPreload(methodsOriginal.stat)
}
return {
/**
* Copy files
*
* @param {String | Array<String>} from - The path(s) of the source to copy.
* @param {String} to - The path of the destination to copy to.
* @param {Object} [opts] - Options for copy.
* @param {boolean} [opts.parents=false] - Whether or not to make the parent directories if they don't exist. (default: false)
* @param {String} [opts.format=dag-pb] - Format of nodes to write any newly created directories as. (default: dag-pb)
* @param {String} [opts.hashAlg=sha2-256] - Algorithm to use when creating CIDs for newly created directories. (default: sha2-256) {@link https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343 The list of all possible values}
* @param {boolean} [opts.flush=true] - Whether or not to immediately flush MFS changes to disk (default: true).
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<string> | void} When callback is provided nothing is returned.
*/
cp: (from, to, opts, cb) => {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(methods.cp(from, to, opts), cb)
},
/**
* Make a directory
*
* @param {String} path - The path to the directory to make.
* @param {Object} [opts] - Options for mkdir.
* @param {boolean} [opts.parents=false] - Value to decide whether or not to make the parent directories if they don't exist. (default: false)
* @param {String} [opts.format=dag-pb] - Format of nodes to write any newly created directories as. (default: dag-pb).
* @param {String} [opts.hashAlg] - Algorithm to use when creating CIDs for newly created directories. (default: sha2-256) {@link https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343 The list of all possible values}
* @param {boolean} [opts.flush=true] - Whether or not to immediately flush MFS changes to disk (default: true).
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<undefined> | void} When callback is provided nothing is returned.
*/
mkdir: (path, opts, cb) => {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(methods.mkdir(path, opts), cb)
},
/**
* @typedef {Object} StatOutput
* @prop {String} hash - Output hash.
* @prop {number} size - File size in bytes.
* @prop {number} cumulativeSize - Integer with the size of the DAGNodes making up the file in Bytes.
* @prop {string} type - Output type either 'directory' or 'file'.
* @prop {number} blocks - If type is directory, this is the number of files in the directory. If it is file it is the number of blocks that make up the file.
* @prop {boolean} withLocality - Indicate if locality information is present.
* @prop {boolean} local - Indicate if the queried dag is fully present locally.
* @prop {number} sizeLocal - Integer indicating the cumulative size of the data present locally.
*/
/**
* Get file or directory status.
*
* @param {String} path - Path to the file or directory to stat.
* @param {Object} [opts] - Options for stat.
* @param {boolean} [opts.hash=false] - Return only the hash. (default: false)
* @param {boolean} [opts.size=false] - Return only the size. (default: false)
* @param {boolean} [opts.withLocal=false] - Compute the amount of the dag that is local, and if possible the total size. (default: false)
* @param {String} [opts.cidBase=base58btc] - Which number base to use to format hashes - e.g. base32, base64 etc. (default: base58btc)
* @param {function(Error, StatOutput): void} [cb] - Callback function.
* @returns {Promise<StatOutput> | void} When callback is provided nothing is returned.
*/
stat: (path, opts, cb) => {
const stat = async (path, opts = {}) => {
const stats = await methods.stat(path, opts)
stats.hash = stats.cid.toBaseEncodedString(opts && opts.cidBase)
delete stats.cid
return stats
}
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(stat(path, opts), cb)
},
/**
* Remove a file or directory.
*
* @param {String | Array<String>} paths - One or more paths to remove.
* @param {Object} [opts] - Options for remove.
* @param {boolean} [opts.recursive=false] - Whether or not to remove directories recursively. (default: false)
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<undefined> | void} When callback is provided nothing is returned.
*/
rm: (paths, opts, cb) => {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(methods.rm(paths, opts), cb)
},
/**
* @typedef {Object} ReadOptions
* @prop {number} [opts.offset=0] - Integer with the byte offset to begin reading from (default: 0).
* @prop {number} [opts.length] - Integer with the maximum number of bytes to read (default: Read to the end of stream).
*/
/**
* Read a file into a Buffer.
*
* @param {string} path - Path of the file to read and must point to a file (and not a directory).
* @param {ReadOptions} [opts] - Object for read.
* @param {function(Error, Buffer): void} [cb] - Callback function.
* @returns {Promise<Buffer> | void} When callback is provided nothing is returned.
*/
read: (path, opts, cb) => {
const read = async (path, opts = {}) => {
return Buffer.concat(await all(methods.read(path, opts)))
}
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(read(path, opts), cb)
},
/**
* Read a file into a ReadableStream.
*
* @param {string} path - Path of the file to read and must point to a file (and not a directory).
* @param {ReadOptions} [opts] - Object for read.
* @returns {ReadableStream} Returns a ReadableStream with the contents of path.
*/
readReadableStream: (path, opts = {}) => toReadableStream(methods.read(path, opts)),
/**
* Read a file into a PullStrean.
*
* @param {string} path - Path of the file to read and must point to a file (and not a directory).
* @param {ReadOptions} [opts] - Object for read.
* @returns {PullStream} Returns a PullStream with the contents of path.
*/
readPullStream: (path, opts = {}) => toPullStream.source(methods.read(path, opts)),
/**
* Write to a file.
*
* @param {string} path - Path of the file to write.
* @param {Buffer | PullStream | ReadableStream | Blob | string} content - Content to write.
* @param {Object} opts - Options for write.
* @param {number} [opts.offset=0] - Integer with the byte offset to begin writing at. (default: 0)
* @param {boolean} [opts.create=false] - Indicate to create the file if it doesn't exist. (default: false)
* @param {boolean} [opts.truncate=false] - Indicate if the file should be truncated after writing all the bytes from content. (default: false)
* @param {boolena} [opts.parents=false] - Value to decide whether or not to make the parent directories if they don't exist. (default: false)
* @param {number} [opts.length] - Maximum number of bytes to read. (default: Read all bytes from content)
* @param {boolean} [opts.rawLeaves=false] - If true, DAG leaves will contain raw file data and not be wrapped in a protobuf. (default: false)
* @param {number} [opts.cidVersion=0] - The CID version to use when storing the data (storage keys are based on the CID, including its version). (default: 0)
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<undefined> | void} When callback is provided nothing is returned.
*/
write: (path, content, opts, cb) => {
const write = async (path, content, opts = {}) => {
if (isPullStream.isSource(content)) {
content = pullStreamToAsyncIterator(content)
}
await methods.write(path, content, opts)
}
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(write(path, content, opts), cb)
},
/**
* Move files.
*
* @param {string | Array<string>} from - Path(s) of the source to move.
* @param {string} to - Path of the destination to move to.
* @param {Object} opts - Options for mv.
* @param {boolean} [opts.parents=false] - Value to decide whether or not to make the parent directories if they don't exist. (default: false)
* @param {String} [opts.format=dag-pb] - Format of nodes to write any newly created directories as. (default: dag-pb).
* @param {String} [opts.hashAlg] - Algorithm to use when creating CIDs for newly created directories. (default: sha2-256) {@link https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343 The list of all possible values}
* @param {boolean} [opts.flush=true] - Value to decide whether or not to immediately flush MFS changes to disk. (default: true)
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<undefined> | void} When callback is provided nothing is returned.
* @description
* If from has multiple values then to must be a directory.
*
* If from has a single value and to exists and is a directory, from will be moved into to.
*
* If from has a single value and to exists and is a file, from must be a file and the contents of to will be replaced with the contents of from otherwise an error will be returned.
*
* If from is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.
*
* All values of from will be removed after the operation is complete unless they are an IPFS path.
*/
mv: (from, to, opts, cb) => {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(methods.mv(from, to, opts), cb)
},
/**
* Flush a given path's data to the disk.
*
* @param {string | Array<string>} [paths] - String paths to flush. (default: /)
* @param {function(Error): void} [cb] - Callback function.
* @returns {Promise<undefined> | void} When callback is provided nothing is returned.
*/
flush: (paths, cb) => {
if (typeof paths === 'function') {
cb = paths
paths = undefined
}
return nodeify(methods.flush(paths), cb)
},
/**
* @typedef {Object} ListOutputFile
* @prop {string} name - Which is the file's name.
* @prop {string} type - Which is the object's type (directory or file).
* @prop {number} size - The size of the file in bytes.
* @prop {string} hash - The hash of the file.
*/
/**
* @typedef {Object} ListOptions
* @prop {boolean} [long=false] - Value to decide whether or not to populate type, size and hash. (default: false)
* @prop {string} [cidBase=base58btc] - Which number base to use to format hashes - e.g. base32, base64 etc. (default: base58btc)
* @prop {boolean} [sort=false] - If true entries will be sorted by filename. (default: false)
*/
/**
* List directories in the local mutable namespace.
*
* @param {string} [path="/"] - String to show listing for. (default: /)
* @param {ListOptions} [opts] - Options for list.
* @param {function(Error, Array<ListOutputFile>): void} [cb] - Callback function.
* @returns {Promise<Array<ListOutputFile>> | void} When callback is provided nothing is returned.
*/
ls: (path, opts, cb) => {
const ls = async (path, opts = {}) => {
const files = await all(methods.ls(path, opts))
return files.map(mapLsFile(opts))
}
if (typeof path === 'function') {
cb = path
path = '/'
opts = {}
}
if (typeof opts === 'function') {
cb = opts
opts = {}
}
return nodeify(ls(path, opts), cb)
},
/**
* Lists a directory from the local mutable namespace that is addressed by a valid IPFS Path. The list will be yielded as Readable Streams.
*
* @param {string} [path="/"] - String to show listing for. (default: /)
* @param {ListOptions} [opts] - Options for list.
* @returns {ReadableStream} It returns a Readable Stream in Object mode that will yield {@link ListOutputFile}
*/
lsReadableStream: (path, opts = {}) => {
const stream = toReadableStream.obj(methods.ls(path, opts))
const through = new PassThrough({
objectMode: true
})
stream.on('data', (file) => {
through.write(mapLsFile(opts)(file))
})
stream.on('error', (err) => {
through.destroy(err)
})
stream.on('end', (file, enc, cb) => {
if (file) {
file = mapLsFile(opts)(file)
}
through.end(file, enc, cb)
})
return through
},
/**
* Lists a directory from the local mutable namespace that is addressed by a valid IPFS Path. The list will be yielded as PullStreams.
*
* @param {string} [path="/"] - String to show listing for. (default: /)
* @param {ListOptions} [opts] - Options for list.
* @returns {PullStream} It returns a PullStream that will yield {@link ListOutputFile}
*/
lsPullStream: (path, opts = {}) => {
return pull(
toPullStream.source(methods.ls(path, opts)),
map(mapLsFile(opts))
)
}
}
}