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

Commit 2a5cc5e

Browse files
daviddiasalanshaw
authored andcommitted
feat: pin API (#1045)
* revert: default assets are not added when running on a browser refactor: change pin.flush logging message * feat(test): add tests for failure cases of normalizeHashes fix: don't need to cast the object.get result with toJSON revert: use interface-datastore.Key for datastore pin storage The proper change would be that datastore-level automatically casts operations into Keys fix: do not invoke callback within a try/catch feat(test): make cli pin tests more robust By using files that aren't added on IPFS initialization. Still needs work on files.rm (direct) and ipfs ls (indirect). fix: remove commented code, traced test failures to pin-set Got to go for the night, though, so will checkpoint here and address tomorrow. feat: parseIpfsPath now throws errors for consistency feat: resolveIpfsPaths error message lists the relative path that failed feat: use follow.bind instead of mutating the links Also decided not show relative paths. Less human friendly but probably cleaner otherwise. refactor: resolveIpfsPaths -> resolvePaths feat: promisify resolvePaths test: change parseIpfsPath failure tests to use try/catch docs: edit resolvePath doc revert: accidentally deleted commands/pin.js * feat: jsipfs pin improvements (#1249) * initial sweep through to understand how pin works. Did make some changes but mostly minor. * refactor pb schema to it's own file * fix: don't pin files during files.add if opts.pin === false * feat: add some http qs parsing, http route/resources cleanup, cleanup core/utils.parseIpfsPath * feat: expand pin tests. \nFirst draft. still needs some further work. * feat: Add logging for entry/exit of pins: add/rm/flush/load. Clean some documentation. * feat: add --pin to files.add, fix: improper pin option parsing in core. * feat: Use ipfs.files.add to add init-docs instead of directly using the unix-fs importer. * feat(tests): Add tests for cli --pin option. I know this should be more of an integration test. Should be written in /core. Maybe talk with Victor about testing different layers * feat: use isIPFS to valiate a multihash. * fix: add some changes missed during rebase, syntax fixes, etc I think my original rebase for this branch 2 weeks ago might have changed history for the intervening commits, indirectly causing some of these missed changes. or I just rebase onto the wrong oldparent. fix: some onlyHash and pin tests broke after merging onlyHash and pin interact: shouldn't pin when --only-hash. fix: trim output for 'pin ls when no hash is passed' test: indirect pins supersede direct pins: turns out we had a bug feat: add expectTimeout test utility feat: promisify some additional pin utils * test: initial work testing the core/pin.js implementation I think I'll end up moving most tests here. test: add tests for pin.ls and pin.rm Based tests on other pin fixtures, need to migrate the isPinned* tests to them as well. fix: direct pins are now deleted by a default pin.rm(hash) test: prepare for pin.add tests 'indirect supersedes direct' test exposes a bug in pin.ls feat: switch away from multihashes for isPinned* tests test: impl pin.add tests fix: add fixture files only once test: add test for a potential bug, clean isPinned* tests refactor: remove a test that's no longer needed fix: pin.ls, indirect pins should supersede direct pins test: naive pin.load, pin.flush tests feat: remove most pin cli tests as functionality is tested in pin core tests refactor: rename solarSystem * refactor: move pin http-api tests to http-api/inject fix: attempt to find a way to use http-api/inject test structure for pin tests test: fix pin.rm http-api tests test: fix pin.add http-api tests docs: docs and cleanup of http-api pin tests refactor: renaming fix: lint errors fix: resolvePaths tests are failing on CI, it might be long ops, testing a timeout bump fix: add files explicitly before testing resolvePaths fix: remove mocha.only from resolvePaths. let's hope tests pass, they are passing CI now fix: rename test/core/utils.spec.js -> utils.js so it's not run during browser tests * test: first draft of pin-set tests Need to leave computer, this is a checkpoint. test: add sanity test for walkItems and hasChild, clean others These tests are more descriptive than really pushing the impl. I'd love others' thoughts on what else should be hit and how. I also need to compare go's pinset impl against ours fix: stop daemons feat: documentation and multihash buffer handling for dag.get fix: lint * feat: simplify root dagnode generation for storeItems base case * feat: rename vars, fix _depth default value, add docs fix: pinset.hasChild buffer check feat: hardcode expected length for flush/load tests * feat: parallelize pin.isPinnedWithType * refactor: refactor pinset.storeItems * fix: re-add pin interface tests I must have missed a commit during a rebase. * fix: lint * feat: docs, rename resolvePaths, pin.getIndirectKeys now uses eachLimit * chore: rebase a month of changes, resolve minor issues from that fix: yarg arugment naming for pin cli commands fix: convert file multihashes to a b58 string fix: another way of checking for CID-ness fix: lint fix: toB58String handles non-buffers fix: key-exchange core tests now shutdown daemon. * chore: update big.js version * revert: do not pin content added with a non-default hash algorithm * revert: internalKey recording * refactor: use lodash.flattenDeep refactor: pinset.hasChild -> pinset.hasDescendent fix: invoke someCb if we've seen the hash before refactor: async patterns in dag._getRecursive refactor: pinset.hasDescendant refactor: pinset.storeItems async patterns refactor: pinset.loadSet and pin.walkItem async patterns docs: add link to go-ipfs' fanout bin implementation refactor: async patterns of pin.load/flush refactor: lint refactor: privatize internal pin key storage refactor: change encapsulation of ipfs.pin, fix resulting issues fix: lint fix: 'files add --pin=false' test was giving a false positive refactor: use is-ipfs to check CID-ability of a string refactor: remove last instance of 'once' in the pin code * refactor: do not expose pinTypes They're simple enough, documented elsewhere, and not used by any exposed functionality. * fix: do not destructure node callback results
1 parent 4e51a69 commit 2a5cc5e

34 files changed

+2117
-27
lines changed

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
},
8989
"dependencies": {
9090
"async": "^2.6.0",
91-
"big.js": "^5.0.3",
91+
"big.js": "^5.1.2",
9292
"binary-querystring": "~0.1.2",
9393
"bl": "^1.2.2",
9494
"boom": "^7.2.0",
@@ -98,13 +98,15 @@
9898
"debug": "^3.1.0",
9999
"file-type": "^7.7.1",
100100
"filesize": "^3.6.1",
101+
"fnv1a": "^1.0.1",
101102
"fsm-event": "^2.1.0",
102103
"get-folder-size": "^1.0.1",
103104
"glob": "^7.1.2",
104105
"hapi": "^16.6.2",
105106
"hapi-set-header": "^1.0.2",
106107
"hoek": "^5.0.3",
107108
"human-to-milliseconds": "^1.0.0",
109+
"interface-datastore": "^0.4.1",
108110
"ipfs-api": "^22.0.0",
109111
"ipfs-bitswap": "~0.20.0",
110112
"ipfs-block": "~0.7.1",
@@ -137,6 +139,7 @@
137139
"libp2p-websocket-star": "~0.8.0",
138140
"libp2p-websockets": "~0.12.0",
139141
"lodash.flatmap": "^4.5.0",
142+
"lodash.flattendeep": "^4.4.0",
140143
"lodash.get": "^4.4.2",
141144
"lodash.set": "^4.3.2",
142145
"lodash.sortby": "^4.7.0",
@@ -218,7 +221,7 @@
218221
"Jade Meskill <[email protected]>",
219222
"Johannes Wikner <[email protected]>",
220223
"Jon Schlinkert <[email protected]>",
221-
"Jonathan <[email protected]>",
224+
"Jonathan Krone <[email protected]>",
222225
"João Antunes <[email protected]>",
223226
"João Santos <[email protected]>",
224227
"Kevin Wang <[email protected]>",

src/cli/commands/files/add.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ module.exports = {
173173
type: 'boolean',
174174
default: false,
175175
describe: 'Write no output'
176+
},
177+
pin: {
178+
type: 'boolean',
179+
default: true,
180+
describe: 'Pin this object when adding'
176181
}
177182
},
178183

@@ -188,7 +193,8 @@ module.exports = {
188193
rawLeaves: argv.rawLeaves,
189194
onlyHash: argv.onlyHash,
190195
hashAlg: argv.hash,
191-
wrapWithDirectory: argv.wrapWithDirectory
196+
wrapWithDirectory: argv.wrapWithDirectory,
197+
pin: argv.pin
192198
}
193199

194200
// Temporary restriction on raw-leaves:

src/cli/commands/pin.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
module.exports = {
4+
command: 'pin',
5+
6+
description: 'Pin and unpin objects to local storage.',
7+
8+
builder (yargs) {
9+
return yargs
10+
.commandDir('pin')
11+
},
12+
13+
handler (argv) {
14+
}
15+
}

src/cli/commands/pin/add.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'add <ipfsPath...>',
7+
8+
describe: 'Pins object to local storage.',
9+
10+
builder: {
11+
recursive: {
12+
type: 'boolean',
13+
alias: 'r',
14+
default: true,
15+
describe: 'Recursively pin the object linked to by the specified object(s).'
16+
}
17+
},
18+
19+
handler (argv) {
20+
const recursive = argv.recursive
21+
const type = recursive ? 'recursive' : 'direct'
22+
argv.ipfs.pin.add(argv.ipfsPath, { recursive: recursive }, (err, results) => {
23+
if (err) { throw err }
24+
results.forEach((res) => {
25+
print(`pinned ${res.hash} ${type}ly`)
26+
})
27+
})
28+
}
29+
}

src/cli/commands/pin/ls.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
// bracket syntax with '...' tells yargs to optionally accept a list
7+
command: 'ls [ipfsPath...]',
8+
9+
describe: 'List objects pinned to local storage.',
10+
11+
builder: {
12+
type: {
13+
type: 'string',
14+
alias: 't',
15+
default: 'all',
16+
choices: ['direct', 'indirect', 'recursive', 'all'],
17+
describe: 'The type of pinned keys to list.'
18+
},
19+
quiet: {
20+
type: 'boolean',
21+
alias: 'q',
22+
default: false,
23+
describe: 'Write just hashes of objects.'
24+
}
25+
},
26+
27+
handler: (argv) => {
28+
const paths = argv.ipfsPath
29+
const type = argv.type
30+
const quiet = argv.quiet
31+
32+
argv.ipfs.pin.ls(paths, { type }, (err, results) => {
33+
if (err) { throw err }
34+
results.forEach((res) => {
35+
let line = res.hash
36+
if (!quiet) {
37+
line += ` ${res.type}`
38+
}
39+
print(line)
40+
})
41+
})
42+
}
43+
}

src/cli/commands/pin/rm.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'rm <ipfsPath...>',
7+
8+
describe: 'Removes the pinned object from local storage.',
9+
10+
builder: {
11+
recursive: {
12+
type: 'boolean',
13+
alias: 'r',
14+
default: true,
15+
describe: 'Recursively unpin the objects linked to by the specified object(s).'
16+
}
17+
},
18+
19+
handler: (argv) => {
20+
const recursive = argv.recursive
21+
argv.ipfs.pin.rm(argv.ipfsPath, { recursive: recursive }, (err, results) => {
22+
if (err) { throw err }
23+
results.forEach((res) => {
24+
print(`unpinned ${res.hash}`)
25+
})
26+
})
27+
}
28+
}

src/core/boot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = (self) => {
2929

3030
series([
3131
(cb) => self._repo.open(cb),
32+
(cb) => self.pin._load(cb),
3233
(cb) => self.preStart(cb),
3334
(cb) => {
3435
self.log('initialized')

src/core/components/dag.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const promisify = require('promisify-es6')
44
const CID = require('cids')
55
const pull = require('pull-stream')
6+
const mapAsync = require('async/map')
7+
const flattenDeep = require('lodash.flattendeep')
68

79
module.exports = function dag (self) {
810
return {
@@ -33,6 +35,12 @@ module.exports = function dag (self) {
3335
} else {
3436
path = '/'
3537
}
38+
} else if (Buffer.isBuffer(cid)) {
39+
try {
40+
cid = new CID(cid)
41+
} catch (err) {
42+
return callback(err)
43+
}
3644
}
3745

3846
self._ipld.get(cid, path, options, callback)
@@ -73,6 +81,23 @@ module.exports = function dag (self) {
7381
self._ipld.treeStream(cid, path, options),
7482
pull.collect(callback)
7583
)
84+
}),
85+
86+
// TODO - use IPLD selectors once they are implemented
87+
_getRecursive: promisify((multihash, callback) => {
88+
// gets flat array of all DAGNodes in tree given by multihash
89+
90+
self.dag.get(new CID(multihash), (err, res) => {
91+
if (err) { return callback(err) }
92+
93+
mapAsync(res.value.links, (link, cb) => {
94+
self.dag._getRecursive(link.multihash, cb)
95+
}, (err, nodes) => {
96+
// console.log('nodes:', nodes)
97+
if (err) return callback(err)
98+
callback(null, flattenDeep([res.value, nodes]))
99+
})
100+
})
76101
})
77102
}
78103
}

src/core/components/files.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ function prepareFile (self, opts, file, callback) {
3232
}
3333

3434
waterfall([
35-
(cb) => opts.onlyHash ? cb(null, file) : self.object.get(file.multihash, opts, cb),
35+
(cb) => opts.onlyHash
36+
? cb(null, file)
37+
: self.object.get(file.multihash, opts, cb),
3638
(node, cb) => {
3739
const b58Hash = cid.toBaseEncodedString()
3840

@@ -87,6 +89,19 @@ function normalizeContent (opts, content) {
8789
})
8890
}
8991

92+
function pinFile (self, opts, file, cb) {
93+
// Pin a file if it is the root dir of a recursive add or the single file
94+
// of a direct add.
95+
const pin = 'pin' in opts ? opts.pin : true
96+
const isRootDir = !file.path.includes('/')
97+
const shouldPin = pin && isRootDir && !opts.onlyHash && !opts.hashAlg
98+
if (shouldPin) {
99+
return self.pin.add(file.hash, err => cb(err, file))
100+
} else {
101+
cb(null, file)
102+
}
103+
}
104+
90105
class AddHelper extends Duplex {
91106
constructor (pullStream, push, options) {
92107
super(Object.assign({ objectMode: true }, options))
@@ -130,7 +145,8 @@ module.exports = function files (self) {
130145
}
131146

132147
let total = 0
133-
let prog = opts.progress || (() => {})
148+
149+
const prog = opts.progress || noop
134150
const progress = (bytes) => {
135151
total += bytes
136152
prog(total)
@@ -141,7 +157,8 @@ module.exports = function files (self) {
141157
pull.map(normalizeContent.bind(null, opts)),
142158
pull.flatten(),
143159
importer(self._ipld, opts),
144-
pull.asyncMap(prepareFile.bind(null, self, opts))
160+
pull.asyncMap(prepareFile.bind(null, self, opts)),
161+
pull.asyncMap(pinFile.bind(null, self, opts))
145162
)
146163
}
147164

src/core/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ exports.swarm = require('./swarm')
1818
exports.ping = require('./ping')
1919
exports.pingPullStream = require('./ping-pull-stream')
2020
exports.pingReadableStream = require('./ping-readable-stream')
21+
exports.pin = require('./pin')
2122
exports.files = require('./files')
2223
exports.bitswap = require('./bitswap')
2324
exports.pubsub = require('./pubsub')

src/core/components/init-assets.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
'use strict'
22

33
const path = require('path')
4-
const fs = require('fs')
54
const glob = require('glob')
6-
const importer = require('ipfs-unixfs-engine').importer
75
const pull = require('pull-stream')
86
const file = require('pull-file')
97
const CID = require('cids')
@@ -15,23 +13,20 @@ module.exports = function addDefaultAssets (self, log, callback) {
1513

1614
pull(
1715
pull.values([initDocsPath]),
18-
pull.asyncMap((val, cb) => glob(path.join(val, '/**/*'), cb)),
16+
pull.asyncMap((val, cb) =>
17+
glob(path.join(val, '/**/*'), { nodir: true }, cb)
18+
),
1919
pull.flatten(),
20-
pull.map((element) => {
20+
pull.map(element => {
2121
const addPath = element.substring(index + 1)
22-
23-
if (fs.statSync(element).isDirectory()) { return }
24-
2522
return { path: addPath, content: file(element) }
2623
}),
27-
// Filter out directories, which are undefined from above
28-
pull.filter(Boolean),
29-
importer(self._ipld),
30-
pull.through((el) => {
31-
if (el.path === 'init-docs') {
32-
const cid = new CID(el.multihash)
24+
self.files.addPullStream(),
25+
pull.through(file => {
26+
if (file.path === 'init-docs') {
27+
const cid = new CID(file.hash)
3328
log('to get started, enter:\n')
34-
log(`\t jsipfs files cat /ipfs/${cid.toBaseEncodedString()}/readme\n`)
29+
log(`\tjsipfs files cat /ipfs/${cid.toBaseEncodedString()}/readme\n`)
3530
}
3631
}),
3732
pull.collect((err) => {

src/core/components/init.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,18 @@ module.exports = function init (self) {
8585
return cb(null, true)
8686
}
8787

88-
self.log('adding assets')
8988
const tasks = [
9089
// add empty unixfs dir object (go-ipfs assumes this exists)
9190
(cb) => self.object.new('unixfs-dir', cb)
9291
]
9392

9493
if (typeof addDefaultAssets === 'function') {
94+
// addDefaultAssets is undefined on browsers.
95+
// See package.json browser config
9596
tasks.push((cb) => addDefaultAssets(self, opts.log, cb))
9697
}
9798

99+
self.log('adding assets')
98100
parallel(tasks, (err) => {
99101
if (err) {
100102
cb(err)

0 commit comments

Comments
 (0)