|
1 | 1 | 'use strict'
|
2 | 2 |
|
3 | 3 | const Block = require('ipfs-block')
|
4 |
| -const pull = require('pull-stream') |
5 | 4 | const CID = require('cids')
|
6 |
| -const pullDeferSource = require('pull-defer').source |
7 |
| -const pullTraverse = require('pull-traverse') |
8 |
| -const map = require('async/map') |
9 | 5 | const waterfall = require('async/waterfall')
|
10 | 6 | const mergeOptions = require('merge-options')
|
11 | 7 | const ipldDagCbor = require('ipld-dag-cbor')
|
@@ -285,122 +281,6 @@ class IPLDResolver {
|
285 | 281 | return fancyIterator(putIterator)
|
286 | 282 | }
|
287 | 283 |
|
288 |
| - treeStream (cid, path, options) { |
289 |
| - if (typeof path === 'object') { |
290 |
| - options = path |
291 |
| - path = undefined |
292 |
| - } |
293 |
| - |
294 |
| - options = options || {} |
295 |
| - |
296 |
| - let p |
297 |
| - |
298 |
| - if (!options.recursive) { |
299 |
| - p = pullDeferSource() |
300 |
| - |
301 |
| - waterfall([ |
302 |
| - async () => { |
303 |
| - return this._getFormat(cid.codec) |
304 |
| - }, |
305 |
| - (format, cb) => this.bs.get(cid, (err, block) => { |
306 |
| - if (err) return cb(err) |
307 |
| - cb(null, format, block) |
308 |
| - }), |
309 |
| - (format, block, cb) => format.resolver.tree(block.data, cb) |
310 |
| - ], (err, paths) => { |
311 |
| - if (err) { |
312 |
| - p.abort(err) |
313 |
| - return p |
314 |
| - } |
315 |
| - p.resolve(pull.values(paths)) |
316 |
| - }) |
317 |
| - } |
318 |
| - |
319 |
| - // recursive |
320 |
| - if (options.recursive) { |
321 |
| - p = pull( |
322 |
| - pullTraverse.widthFirst({ |
323 |
| - basePath: null, |
324 |
| - cid: cid |
325 |
| - }, (el) => { |
326 |
| - // pass the paths through the pushable pull stream |
327 |
| - // continue traversing the graph by returning |
328 |
| - // the next cids with deferred |
329 |
| - |
330 |
| - if (typeof el === 'string') { |
331 |
| - return pull.empty() |
332 |
| - } |
333 |
| - |
334 |
| - const deferred = pullDeferSource() |
335 |
| - const cid = el.cid |
336 |
| - |
337 |
| - waterfall([ |
338 |
| - async () => { |
339 |
| - return this._getFormat(cid.codec) |
340 |
| - }, |
341 |
| - (format, cb) => this.bs.get(cid, (err, block) => { |
342 |
| - if (err) return cb(err) |
343 |
| - cb(null, format, block) |
344 |
| - }), |
345 |
| - (format, block, cb) => format.resolver.tree(block.data, (err, paths) => { |
346 |
| - if (err) { |
347 |
| - return cb(err) |
348 |
| - } |
349 |
| - map(paths, (p, cb) => { |
350 |
| - format.resolver.isLink(block.data, p, (err, link) => { |
351 |
| - if (err) { |
352 |
| - return cb(err) |
353 |
| - } |
354 |
| - cb(null, { path: p, link: link }) |
355 |
| - }) |
356 |
| - }, cb) |
357 |
| - }) |
358 |
| - ], (err, paths) => { |
359 |
| - if (err) { |
360 |
| - deferred.abort(err) |
361 |
| - return deferred |
362 |
| - } |
363 |
| - |
364 |
| - deferred.resolve(pull.values(paths.map((p) => { |
365 |
| - const base = el.basePath ? el.basePath + '/' + p.path : p.path |
366 |
| - if (p.link) { |
367 |
| - return { |
368 |
| - basePath: base, |
369 |
| - cid: IPLDResolver._maybeCID(p.link) |
370 |
| - } |
371 |
| - } |
372 |
| - return base |
373 |
| - }))) |
374 |
| - }) |
375 |
| - return deferred |
376 |
| - }), |
377 |
| - pull.map((e) => { |
378 |
| - if (typeof e === 'string') { |
379 |
| - return e |
380 |
| - } |
381 |
| - return e.basePath |
382 |
| - }), |
383 |
| - pull.filter(Boolean) |
384 |
| - ) |
385 |
| - } |
386 |
| - |
387 |
| - // filter out by path |
388 |
| - if (path) { |
389 |
| - return pull( |
390 |
| - p, |
391 |
| - pull.map((el) => { |
392 |
| - if (el.indexOf(path) === 0) { |
393 |
| - el = el.slice(path.length + 1) |
394 |
| - return el |
395 |
| - } |
396 |
| - }), |
397 |
| - pull.filter(Boolean) |
398 |
| - ) |
399 |
| - } |
400 |
| - |
401 |
| - return p |
402 |
| - } |
403 |
| - |
404 | 284 | /**
|
405 | 285 | * Remove IPLD Nodes by the given CIDs.
|
406 | 286 | *
|
@@ -437,6 +317,130 @@ class IPLDResolver {
|
437 | 317 | return fancyIterator(removeIterator)
|
438 | 318 | }
|
439 | 319 |
|
| 320 | + /** |
| 321 | + * Returns all the paths that can be resolved into. |
| 322 | + * |
| 323 | + * @param {Object} cid - The ID to get the paths from |
| 324 | + * @param {string} [offsetPath=''] - the path to start to retrieve the other paths from. |
| 325 | + * @param {Object} [userOptions] |
| 326 | + * @param {number} [userOptions.recursive=false] - whether to get the paths recursively or not. `false` resolves only the paths of the given CID. |
| 327 | + * @returns {Iterable.<Promise.<String>>} - Returns an async iterator with paths that can be resolved into |
| 328 | + */ |
| 329 | + tree (cid, offsetPath, userOptions) { |
| 330 | + if (typeof offsetPath === 'object') { |
| 331 | + userOptions = offsetPath |
| 332 | + offsetPath = undefined |
| 333 | + } |
| 334 | + offsetPath = offsetPath || '' |
| 335 | + |
| 336 | + const defaultOptions = { |
| 337 | + recursive: false |
| 338 | + } |
| 339 | + const options = mergeOptions(defaultOptions, userOptions) |
| 340 | + |
| 341 | + // Get available paths from a block |
| 342 | + const getPaths = (cid) => { |
| 343 | + return new Promise(async (resolve, reject) => { |
| 344 | + let format |
| 345 | + try { |
| 346 | + format = await this._getFormat(cid.codec) |
| 347 | + } catch (error) { |
| 348 | + return reject(error) |
| 349 | + } |
| 350 | + this.bs.get(cid, (err, block) => { |
| 351 | + if (err) { |
| 352 | + return reject(err) |
| 353 | + } |
| 354 | + format.resolver.tree(block.data, (err, paths) => { |
| 355 | + if (err) { |
| 356 | + return reject(err) |
| 357 | + } |
| 358 | + return resolve({ paths, block }) |
| 359 | + }) |
| 360 | + }) |
| 361 | + }) |
| 362 | + } |
| 363 | + |
| 364 | + // If a path is a link then follow it and return its CID |
| 365 | + const maybeRecurse = (block, treePath) => { |
| 366 | + return new Promise(async (resolve, reject) => { |
| 367 | + // A treepath we might want to follow recursively |
| 368 | + const format = await this._getFormat(block.cid.codec) |
| 369 | + format.resolver.isLink(block.data, treePath, (err, link) => { |
| 370 | + if (err) { |
| 371 | + return reject(err) |
| 372 | + } |
| 373 | + // Something to follow recusively, hence push it into the queue |
| 374 | + if (link) { |
| 375 | + const cid = IPLDResolver._maybeCID(link) |
| 376 | + resolve(cid) |
| 377 | + } else { |
| 378 | + resolve(null) |
| 379 | + } |
| 380 | + }) |
| 381 | + }) |
| 382 | + } |
| 383 | + |
| 384 | + // The list of paths that will get returned |
| 385 | + let treePaths = [] |
| 386 | + // The current block, needed to call `isLink()` on every interation |
| 387 | + let block |
| 388 | + // The list of items we want to follow recursively. The items are |
| 389 | + // an object consisting of the CID and the currently already resolved |
| 390 | + // path |
| 391 | + const queue = [{ cid, basePath: '' }] |
| 392 | + // The path that was already traversed |
| 393 | + let basePath |
| 394 | + |
| 395 | + const next = async () => { |
| 396 | + // End of iteration if there aren't any paths left to return or |
| 397 | + // if we don't want to traverse recursively and have already |
| 398 | + // returne the first level |
| 399 | + if (treePaths.length === 0 && queue.length === 0) { |
| 400 | + return { done: true } |
| 401 | + } |
| 402 | + |
| 403 | + return new Promise(async (resolve, reject) => { |
| 404 | + // There aren't any paths left, get them from the given CID |
| 405 | + if (treePaths.length === 0 && queue.length > 0) { |
| 406 | + ({ cid, basePath } = queue.shift()) |
| 407 | + |
| 408 | + let paths |
| 409 | + try { |
| 410 | + ({ block, paths } = await getPaths(cid)) |
| 411 | + } catch (error) { |
| 412 | + return reject(error) |
| 413 | + } |
| 414 | + treePaths.push(...paths) |
| 415 | + } |
| 416 | + const treePath = treePaths.shift() |
| 417 | + let fullPath = basePath + treePath |
| 418 | + |
| 419 | + // Only follow links if recursion is intended |
| 420 | + if (options.recursive) { |
| 421 | + cid = await maybeRecurse(block, treePath) |
| 422 | + if (cid !== null) { |
| 423 | + queue.push({ cid, basePath: fullPath + '/' }) |
| 424 | + } |
| 425 | + } |
| 426 | + |
| 427 | + // Return it if it matches the given offset path, but is not the |
| 428 | + // offset path itself |
| 429 | + if (fullPath.startsWith(offsetPath) && |
| 430 | + fullPath.length > offsetPath.length) { |
| 431 | + if (offsetPath.length > 0) { |
| 432 | + fullPath = fullPath.slice(offsetPath.length + 1) |
| 433 | + } |
| 434 | + return resolve({ done: false, value: fullPath }) |
| 435 | + } else { // Else move on to the next iteration before returning |
| 436 | + return resolve(next()) |
| 437 | + } |
| 438 | + }) |
| 439 | + } |
| 440 | + |
| 441 | + return fancyIterator({ next }) |
| 442 | + } |
| 443 | + |
440 | 444 | /* */
|
441 | 445 | /* internals */
|
442 | 446 | /* */
|
|
0 commit comments