diff --git a/OKR.md b/OKR.md new file mode 100644 index 0000000000..f2908bc776 --- /dev/null +++ b/OKR.md @@ -0,0 +1,11 @@ +# Quarterly Objectives and Key Results + +We try to frame our ongoing work using a process based on quarterly Objectives and Key Results (OKRs). Objectives reflect outcomes that are challenging, but realistic. Results are tangible and measurable. + +## 2018 Q2 + +Find the **js-ipfs OKRs** for 2018 Q2 at the [2018 Q2 IPFS OKRs Spreadsheet](https://docs.google.com/spreadsheets/d/1xIhKROxFlsY9M9on37D5rkbSsm4YtjRQvG2unHScApA/edit#gid=274358435) + +## 2018 Q1 + +Find the **js-ipfs OKRs** for 2018 Q1 at the [2018 Q1 IPFS OKRs Spreadsheet](https://docs.google.com/spreadsheets/u/1/d/1clB-W489rJpbOEs2Q7Q2Jf1WMXHQxXgccBcUJS9QTiI/edit#gid=2079514081) diff --git a/README.md b/README.md index 45efb54179..fd67cc66ae 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,19 @@ You can check the development status at the [Waffle Board](https://waffle.io/ipf - [IPFS CLI](#ipfs-cli) - [IPFS Daemon](#ipfs-daemon) - [IPFS Module (use IPFS as a module in Node.js or in the Browser)](#ipfs-module) - - [How to create a IPFS node instance](#create-a-ipfs-node-instance) - [Tutorials and Examples](#tutorials-and-examples) - [API Docs](#api) - - [Files](#files) - - [Graph](#graph) - - [Network](#network) - - [Node Management](#node-management) - - [Domain data types](#domain-data-types) + - [Constructor](#ipfs-constructor) + - [Events](#events) + - [start](#nodestartcallback) + - [stop](#nodestopcallback) + - [Core API](#core-api) + - [Files](#files) + - [Graph](#graph) + - [Crypto and Key Management](#crypto-and-key-management) + - [Network](#network) + - [Node Management](#node-management) + - [Domain data types](#domain-data-types) - [FAQ](#faq) - [Running js-ipfs with Docker](#running-js-ipfs-with-docker) - [Packages](#packages) @@ -163,11 +168,7 @@ If you want a programmatic way to spawn a IPFS Daemon using JavaScript, check ou ### IPFS Module -Use the IPFS Module as a dependency of a project to __spawn in process instances of IPFS__. - -#### Create a IPFS node instance - -Creating an IPFS instance couldn't be easier, all you have to do is: +Use the IPFS Module as a dependency of a project to __spawn in process instances of IPFS__. Create an instance by calling `new IPFS()` and waiting for its `ready` event: ```JavaScript // Create the IPFS node instance @@ -183,70 +184,158 @@ node.on('ready', () => { }) ``` -#### Advanced options when creating an IPFS node. +### [Tutorials and Examples](/examples) -When starting a node, you can: +You can find some examples and tutorials in the [examples](/examples) folder, these exist to help you get started using `js-ipfs`. -```JavaScript -// IPFS will need a repo, it can create one for you or you can pass -// it a repo instance of the type IPFS Repo -// https://github.com/ipfs/js-ipfs-repo -const repo = +### API -const node = new IPFS({ - repo: repo, - init: true, // default - // init: false, // You will need to set init: false after time you start instantiate a node as - // // the repo will be already initiated then. - // init: { - // bits: 1024 // size of the RSA key generated - // }, - start: true, // default - // start: false, - pass: undefined // default - // pass: 'pass phrase for key access', - EXPERIMENTAL: { // enable experimental features - pubsub: true, - sharding: true, // enable dir sharding - dht: true, // enable KadDHT, currently not interopable with go-ipfs - relay: { - enabled: true, // enable circuit relay dialer and listener - hop: { - enabled: true // enable circuit relay HOP (make this node a relay) - } - } - }, - config: { // overload the default IPFS node config, find defaults at https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime - Addresses: { - Swarm: [ - '/ip4/127.0.0.1/tcp/1337' - ] - } - }, - libp2p: { // add custom modules to the libp2p stack of your node - modules: {} +#### IPFS Constructor + +```js +const node = new IPFS([options]) +``` + +Creates and returns an instance of an IPFS node. Use the `options` argument to specify advanced configuration. It is an object with any of these properties: + +- `repo` (string or [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance): The file path at which to store the IPFS node’s data. Alternatively, you can set up a customized storage system by providing an [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance. (Default: `'~/.jsipfs'` in Node.js, `'ipfs'` in browsers.) + + Example: + + ```js + // Store data outside your user directory + const node = new IPFS({ repo: '/var/ipfs/data' }) + ``` + +- `init` (boolean or object): Initialize the repo when creating the IPFS node. (Default: `true`) + + If you have already initialized a repo before creating your IPFS node (e.g. you are loading a repo that was saved to disk from a previous run of your program), you must make sure to set this to `false`. Note that *initializing* a repo is different from creating an instance of [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo). The IPFS constructor sets many special properties when initializing a repo, so you should usually not try and call `repoInstance.init()` yourself. + + Instead of a boolean, you may provide an object with custom initialization options. + +- `start` (boolean): If `false`, do not automatically start the IPFS node. Instead, you’ll need to manually call `node.start()` yourself. (Default: `true`) + +- `pass` (string): A passphrase to encrypt your keys. + +- `EXPERIMENTAL` (object): Enable and configure experimental features. + - `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`) + - `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`) + - `dht` (boolean): Enable KadDHT. **This is currently not interopable with `go-ipfs`.** + - `relay` (object): Configure circuit relay (see the [circuit relay tutorial](https://github.com/ipfs/js-ipfs/tree/master/examples/circuit-relaying) to learn more). + - `enabled` (boolean): Enable circuit relay dialer and listener. (Default: `false`) + - `hop` (object) + - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`) + - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`) + +- `config` (object) Modify the default IPFS node config. Find the Node.js defaults at [`src/core/runtime/config-nodejs.json`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-nodejs.json) and the browser defaults at [`src/core/runtime/config-browser.json`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-browser.json). This object will be *merged* with the default config; it will not replace it. + +- `libp2p` (object) add custom modules to the libp2p stack of your node + - `modules` (object): + - `transport` (Array<[libp2p.Transport](https://github.com/libp2p/interface-transport)>): An array of additional Libp2p transport instances to use. See [libp2p/interface-transport](https://github.com/libp2p/interface-transport) for details. + - `discovery` (Array<[libp2p.PeerDiscovery](https://github.com/libp2p/interface-peer-discovery)>): An array of additional Libp2p peer discovery instances to use. See [libp2p/peer-discovery](https://github.com/libp2p/interface-peer-discovery) for details. + +#### Events + +IPFS instances are Node.js [EventEmitters](https://nodejs.org/dist/latest-v8.x/docs/api/events.html#events_class_eventemitter). You can listen for events by calling `node.on('event', handler)`: + +```js +const node = new IPFS({ repo: '/var/ipfs/data' }) +node.on('error', errorObject => console.error(errorObject)) +``` + +- `error` is always accompanied by an `Error` object with information about the error that ocurred. + + ```js + node.on('error', error => { + console.error(error.message) + }) + ``` + +- `init` is emitted after a new repo has been initialized. It will not be emitted if you set the `init: false` option on the constructor. + +- `ready` is emitted when a node is ready to use. This is the final event you will receive when creating a node (after `init` and `start`). + + When creating a new IPFS node, you should almost always wait for the `ready` event before calling methods or interacting with the node. + +- `start` is emitted when a node has started listening for connections. It will not be emitted if you set the `start: false` option on the constructor. + +- `stop` is emitted when a node has closed all connections and released access to its repo. This is usually the result of calling [`node.stop()`](#nodestopcallback). + +#### `node.start([callback])` + +Start listening for connections with other IPFS nodes on the network. In most cases, you do not need to call this method — `new IPFS()` will automatically do it for you. + +This method is asynchronous. There are several ways to be notified when the node has finished starting: + +1. If you call `node.start()` with no arguments, it returns a promise. +2. If you pass a function as the final argument, it will be called when the node is started. *(Note: this method will **not** return a promise if you use a callback function.)* +3. You can listen for the [`start` event](#events). + +```js +const node = new IPFS({ start: false }) + +// Use a promise: +node.start() + .then(() => console.log('Node started!')) + .catch(error => console.error('Node failed to start!', error)) + +// OR use a callback: +node.start(error => { + if (error) { + console.error('Node failed to start!', error) + return } + console.log('Node started!') }) -// Events +// OR use events: +node.on('error', error => console.error('Something went terribly wrong!', error)) +node.on('start', () => console.log('Node started!')) +node.start() +``` -node.on('ready', () => {}) // Node is ready to use when you first create it -node.on('error', (err) => {}) // Node has hit some error while initing/starting +#### `node.stop([callback])` -node.on('init', () => {}) // Node has successfully finished initing the repo -node.on('start', () => {}) // Node has started -node.on('stop', () => {}) // Node has stopped -``` +Close and stop listening for connections with other IPFS nodes, then release access to the node’s repo. -### [Tutorials and Examples](/examples) +This method is asynchronous. There are several ways to be notified when the node has completely stopped: -You can find some examples and tutorials in the [examples](/examples) folder, these exist to help you get started using `js-ipfs`. +1. If you call `node.stop()` with no arguments, it returns a promise. +2. If you pass a function as the final argument, it will be called when the node is stopped. *(Note: this method will **not** return a promise if you use a callback function.)* +3. You can listen for the [`stop` event](#events). -### API +```js +const node = new IPFS() +node.on('ready', () => { + console.log('Node is ready to use!') + + // Stop with a promise: + node.stop() + .then(() => console.log('Node stopped!')) + .catch(error => console.error('Node failed to stop cleanly!', error)) + + // OR use a callback: + node.stop(error => { + if (error) { + console.error('Node failed to stop cleanly!', error) + return + } + console.log('Node stopped!') + }) + + // OR use events: + node.on('error', error => console.error('Something went terribly wrong!', error)) + node.stop() +}) +``` + +#### Core API [![](https://github.com/ipfs/interface-ipfs-core/raw/master/img/badge.png)](https://github.com/ipfs/interface-ipfs-core) -A complete API definition is in the works. Meanwhile, you can learn how to you use js-ipfs through the standard interface at [![](https://img.shields.io/badge/interface--ipfs--core-API%20Docs-blue.svg)](https://github.com/ipfs/interface-ipfs-core). +The IPFS core API provides all functionality that is not specific to setting up and starting or stopping a node. This API is available directly on an IPFS instance, on the command line (when using the CLI interface), and as an HTTP REST API. For a complete reference, see [![](https://img.shields.io/badge/interface--ipfs--core-API%20Docs-blue.svg)](https://github.com/ipfs/interface-ipfs-core). + +The core API is grouped into several areas: #### `Files` @@ -523,6 +612,10 @@ HOME=~/.electron-gyp npm install If you find any other issue, please check the [`Electron Support` issue](https://github.com/ipfs/js-ipfs/issues/843). +#### Have more questions? + +Ask for help in our forum at https://discuss.ipfs.io or in IRC (#ipfs on Freenode). + ## Running js-ipfs with Docker We have automatic Docker builds setup with Docker Hub: https://hub.docker.com/r/ipfs/js-ipfs/ @@ -728,7 +821,6 @@ IPFS implementation in JavaScript is a work in progress. As such, there's a few * **Perform code reviews**. More eyes will help (a) speed the project along, (b) ensure quality, and (c) reduce possible future bugs. * Take a look at go-ipfs and some of the planning repositories or issues: for instance, the [libp2p spec](https://github.com/ipfs/specs/pull/19). Contributions here that would be most helpful are **top-level comments** about how it should look based on our understanding. Again, the more eyes the better. * **Add tests**. There can never be enough tests. - * **Contribute to the [FAQ repository](https://github.com/ipfs/faq/issues)** with any questions you have about IPFS or any of the relevant technology. A good example would be asking, 'What is a merkledag tree?'. If you don't know a term, odds are, someone else doesn't either. Eventually, we should have a good understanding of where we need to improve communications and teaching together to make IPFS and IPN better. ### Want to hack on IPFS? diff --git a/package.json b/package.json index aab0fbce62..8894d51f6d 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,8 @@ "expose-loader": "^0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "ipfsd-ctl": "~0.31.0", - "interface-ipfs-core": "~0.58.0", + "interface-ipfs-core": "^0.61.0", + "ipfsd-ctl": "^0.32.1", "lodash": "^4.17.5", "mocha": "^5.0.5", "ncp": "^2.0.0", @@ -106,51 +106,50 @@ "hapi-set-header": "^1.0.2", "hoek": "^5.0.3", "human-to-milliseconds": "^1.0.0", - "ipfs-api": "^19.0.0", - "ipfs-bitswap": "~0.19.0", - "ipfs-block": "~0.6.1", - "ipfs-block-service": "~0.13.0", + "ipfs-api": "^20.0.1", + "ipfs-bitswap": "~0.20.0", + "ipfs-block": "^0.7.1", + "ipfs-block-service": "~0.14.0", "ipfs-multipart": "~0.1.0", - "ipfs-repo": "~0.18.7", + "ipfs-repo": "^0.19.0", "ipfs-unixfs": "~0.1.14", - "ipfs-unixfs-engine": "~0.27.0", - "ipld": "^0.15.0", "ipld-dag-cbor": "^0.12.0", "ipld-dag-pb": "^0.13.1", + "ipfs-unixfs-engine": "~0.28.1", + "ipld": "^0.17.0", "is-ipfs": "^0.3.2", "is-stream": "^1.1.0", "joi": "^13.1.2", "joi-browser": "^13.0.1", - "joi-multiaddr": "^1.0.1", - "libp2p": "~0.19.2", - "libp2p-circuit": "~0.1.5", - "libp2p-crypto": "^0.12.1", - "libp2p-floodsub": "~0.14.1", - "libp2p-kad-dht": "~0.9.0", + "joi-multiaddr": "^2.0.0", + "libp2p": "^0.20.2", + "libp2p-circuit": "^0.2.0", + "libp2p-floodsub": "~0.15.0", + "libp2p-kad-dht": "~0.10.0", "libp2p-keychain": "~0.3.1", - "libp2p-mdns": "~0.9.2", - "libp2p-mplex": "^0.6.0", - "libp2p-railing": "~0.7.1", - "libp2p-secio": "~0.9.4", - "libp2p-tcp": "~0.11.6", - "libp2p-webrtc-star": "~0.13.4", - "libp2p-websocket-star": "~0.7.7", - "libp2p-websockets": "~0.10.5", + "libp2p-mdns": "~0.11.0", + "libp2p-mplex": "^0.7.0", + "libp2p-railing": "~0.8.0", + "libp2p-secio": "~0.10.0", + "libp2p-tcp": "^0.12.0", + "libp2p-webrtc-star": "0.14.0", + "libp2p-websocket-star": "^0.8.0", + "libp2p-websockets": "^0.11.0", "lodash.flatmap": "^4.5.0", "lodash.get": "^4.4.2", "lodash.sortby": "^4.7.0", "lodash.values": "^4.3.0", - "mafmt": "^4.0.0", + "mafmt": "^6.0.0", "mime-types": "^2.1.18", "mkdirp": "~0.5.1", - "multiaddr": "^3.1.0", "multibase": "^0.4.0", + "multiaddr": "^4.0.0", "multihashes": "~0.4.13", "once": "^1.4.0", "path-exists": "^3.0.0", - "peer-book": "~0.5.4", - "peer-id": "~0.10.6", - "peer-info": "~0.11.6", + "peer-book": "~0.7.0", + "peer-id": "~0.10.7", + "peer-info": "~0.14.1", "progress": "^2.0.0", "promisify-es6": "^1.0.3", "pull-abortable": "^4.1.1", diff --git a/src/cli/bin.js b/src/cli/bin.js index 67e60f8362..4e666d2a20 100755 --- a/src/cli/bin.js +++ b/src/cli/bin.js @@ -14,6 +14,8 @@ updateNotifier({ updateCheckInterval: 1000 * 60 * 60 * 24 * 7 // 1 week }).notify() +const args = process.argv.slice(2) + const cli = yargs .option('silent', { desc: 'Write no output', @@ -27,11 +29,17 @@ const cli = yargs default: '' }) .commandDir('commands') + .epilog(utils.ipfsPathHelp) .demandCommand(1) .fail((msg, err, yargs) => { if (err) { throw err // preserve stack } + + if (args.length > 0) { + print(msg) + } + yargs.showHelp() }) @@ -46,14 +54,12 @@ aliases.forEach((alias) => { cli.command(alias.command, alias.describe, alias.builder, alias.handler) }) -const args = process.argv.slice(2) - // Need to skip to avoid locking as these commands // don't require a daemon if (args[0] === 'daemon' || args[0] === 'init') { cli .help() - .strict(false) + .strict() .completion() .parse(args) } else { @@ -69,7 +75,7 @@ if (args[0] === 'daemon' || args[0] === 'init') { cli .help() - .strict(false) + .strict() .completion() .parse(args, { ipfs: ipfs }, (err, argv, output) => { if (output) { print(output) } diff --git a/src/cli/commands/daemon.js b/src/cli/commands/daemon.js index ba9ca6ad6a..457dbc4c9d 100644 --- a/src/cli/commands/daemon.js +++ b/src/cli/commands/daemon.js @@ -11,15 +11,17 @@ module.exports = { describe: 'Start a long-running daemon process', - builder: { - 'enable-sharding-experiment': { - type: 'boolean', - default: false - }, - 'enable-pubsub-experiment': { - type: 'boolean', - default: false - } + builder (yargs) { + return yargs + .epilog(utils.ipfsPathHelp) + .option('enable-sharding-experiment', { + type: 'boolean', + default: false + }) + .option('enable-pubsub-experiment', { + type: 'boolean', + default: false + }) }, handler (argv) { diff --git a/src/cli/commands/files/add.js b/src/cli/commands/files/add.js index 23c9fdb60d..87cdbbdf35 100644 --- a/src/cli/commands/files/add.js +++ b/src/cli/commands/files/add.js @@ -10,6 +10,7 @@ const zip = require('pull-zip') const getFolderSize = require('get-folder-size') const byteman = require('byteman') const waterfall = require('async/waterfall') +const mh = require('multihashes') const utils = require('../../utils') const print = require('../../utils').print const createProgressBar = require('../../utils').createProgressBar @@ -162,6 +163,11 @@ module.exports = { type: 'integer', describe: 'Cid version. Non-zero value will change default of \'raw-leaves\' to true. (experimental)' }, + hash: { + type: 'string', + choices: Object.keys(mh.names), + describe: 'Hash function to use. Will set Cid version to 1 if used. (experimental)' + }, quiet: { alias: 'q', type: 'boolean', @@ -191,7 +197,8 @@ module.exports = { : Infinity, cidVersion: argv.cidVersion, rawLeaves: argv.rawLeaves, - onlyHash: argv.onlyHash + onlyHash: argv.onlyHash, + hashAlg: argv.hash } // Temporary restriction on raw-leaves: @@ -206,6 +213,18 @@ module.exports = { throw new Error('Implied argument raw-leaves must be passed and set to false when cid-version is > 0') } + // Temporary restriction on raw-leaves: + // When hash != undefined then raw-leaves MUST be present and false. + // + // This is because raw-leaves is not yet implemented in js-ipfs, + // and go-ipfs changes the value of raw-leaves to true when + // hash != undefined unless explicitly set to false. + // + // This retains feature parity without having to implement raw-leaves. + if (options.hash && options.rawLeaves !== false) { + throw new Error('Implied argument raw-leaves must be passed and set to false when hash argument is specified') + } + if (options.rawLeaves) { throw new Error('Not implemented: raw-leaves') } diff --git a/src/cli/commands/files/cat.js b/src/cli/commands/files/cat.js index c9cc3037d6..43c22ceb21 100644 --- a/src/cli/commands/files/cat.js +++ b/src/cli/commands/files/cat.js @@ -1,14 +1,14 @@ 'use strict' module.exports = { - command: 'cat ', + command: 'cat ', describe: 'Fetch and cat an IPFS path referencing a file', builder: {}, handler (argv) { - let path = argv['ipfs-path'] + let path = argv['ipfsPath'] if (path.indexOf('/ipfs/') !== 1) { path = path.replace('/ipfs/', '') } diff --git a/src/cli/commands/files/get.js b/src/cli/commands/files/get.js index 85ee810671..c177fb8719 100644 --- a/src/cli/commands/files/get.js +++ b/src/cli/commands/files/get.js @@ -45,7 +45,7 @@ function fileHandler (dir) { } module.exports = { - command: 'get ', + command: 'get ', describe: 'Fetch a file or directory with files references from an IPFS Path', @@ -58,7 +58,8 @@ module.exports = { }, handler (argv) { - const ipfsPath = argv['ipfs-path'] + const ipfsPath = argv['ipfsPath'] + const dir = checkArgs(ipfsPath, argv.output) const stream = argv.ipfs.files.getReadableStream(ipfsPath) diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js index 920519f90c..36525419b4 100644 --- a/src/cli/commands/init.js +++ b/src/cli/commands/init.js @@ -5,19 +5,14 @@ const IPFS = require('../../core') const utils = require('../utils') const print = utils.print -const ipfsPathHelp = 'ipfs uses a repository in the local file system. By default, the repo is ' + - 'located at ~/.ipfs.\nTo change the repo location, set the $IPFS_PATH environment variable:\n\n' + - '\texport IPFS_PATH=/path/to/ipfsrepo\n' - module.exports = { command: 'init', describe: 'Initialize a local IPFS node', builder (yargs) { - print(ipfsPathHelp) - return yargs + .epilog(utils.ipfsPathHelp) .option('bits', { type: 'number', alias: 'b', diff --git a/src/cli/utils.js b/src/cli/utils.js index 37cd49010f..b0fd5ff742 100644 --- a/src/cli/utils.js +++ b/src/cli/utils.js @@ -111,3 +111,7 @@ exports.rightpad = (val, n) => { } return result } + +exports.ipfsPathHelp = 'ipfs uses a repository in the local file system. By default, the repo is ' + + 'located at ~/.jsipfs. To change the repo location, set the $IPFS_PATH environment variable:\n\n' + + 'export IPFS_PATH=/path/to/ipfsrepo\n' diff --git a/src/core/components/files.js b/src/core/components/files.js index 3ec915065d..ee7eca713b 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -22,15 +22,15 @@ function noop () {} function prepareFile (self, opts, file, callback) { opts = opts || {} - waterfall([ - (cb) => opts.onlyHash ? cb(null, file) : self.object.get(file.multihash, cb), - (node, cb) => { - let cid = new CID(node.multihash) + let cid = new CID(file.multihash) - if (opts['cid-version'] === 1) { - cid = cid.toV1() - } + if (opts.cidVersion === 1) { + cid = cid.toV1() + } + waterfall([ + (cb) => opts.onlyHash ? cb(null, file) : self.object.get(file.multihash, opts, cb), + (node, cb) => { const b58Hash = cid.toBaseEncodedString() cb(null, { @@ -110,6 +110,10 @@ module.exports = function files (self) { : Infinity }, options) + if (opts.hashAlg && opts.cidVersion !== 1) { + opts.cidVersion = 1 + } + let total = 0 let prog = opts.progress || (() => {}) const progress = (bytes) => { @@ -182,7 +186,7 @@ module.exports = function files (self) { } return { - add: promisify((data, options, callback) => { + add: promisify((data, options = {}, callback) => { if (typeof options === 'function') { callback = options options = {} @@ -200,6 +204,11 @@ module.exports = function files (self) { return callback(new Error('first arg must be a buffer, readable stream, an object or array of objects')) } + // CID v0 is for multihashes encoded with sha2-256 + if (options.hashAlg && options.cidVersion !== 1) { + options.cidVersion = 1 + } + pull( pull.values([data]), _addPullStream(options), diff --git a/src/core/components/object.js b/src/core/components/object.js index 5ce053128f..5e8be00e4a 100644 --- a/src/core/components/object.js +++ b/src/core/components/object.js @@ -191,7 +191,11 @@ module.exports = function object (self) { } catch (err) { return callback(err) } - const cid = new CID(mh) + let cid = new CID(mh) + + if (options.cidVersion === 1) { + cid = cid.toV1() + } self._ipld.get(cid, (err, result) => { if (err) { @@ -214,6 +218,7 @@ module.exports = function object (self) { if (err) { return callback(err) } + callback(null, node.data) }) }), @@ -275,8 +280,7 @@ module.exports = function object (self) { rmLink (multihash, linkRef, options, callback) { editAndSave((node, cb) => { - if (linkRef.constructor && - linkRef.constructor.name === 'DAGLink') { + if (DAGLink.isDAGLink(linkRef)) { linkRef = linkRef._name } DAGNode.rmLink(node, linkRef, cb) diff --git a/src/http/api/resources/files.js b/src/http/api/resources/files.js index 91861965f3..f74cf0a7fe 100644 --- a/src/http/api/resources/files.js +++ b/src/http/api/resources/files.js @@ -207,7 +207,8 @@ exports.add = { cidVersion: request.query['cid-version'], rawLeaves: request.query['raw-leaves'], progress: request.query.progress ? progressHandler : null, - onlyHash: request.query['only-hash'] + onlyHash: request.query['only-hash'], + hashAlg: request.query['hash'] } const aborter = abortable() diff --git a/test/cli/daemon.js b/test/cli/daemon.js index 3245400320..4d5716df41 100644 --- a/test/cli/daemon.js +++ b/test/cli/daemon.js @@ -120,4 +120,13 @@ describe('daemon', () => { done() }) }) + + it('should present ipfs path help when option help is received', function (done) { + this.timeout(100 * 1000) + + ipfs('daemon --help').then((res) => { + expect(res).to.have.string('export IPFS_PATH=/path/to/ipfsrepo') + done() + }) + }) }) diff --git a/test/cli/files.js b/test/cli/files.js index 2e9fec39b5..be9a6d96a5 100644 --- a/test/cli/files.js +++ b/test/cli/files.js @@ -7,8 +7,22 @@ const expect = require('chai').expect const path = require('path') const compareDir = require('dir-compare').compareSync const rimraf = require('rimraf').sync +const CID = require('cids') +const mh = require('multihashes') const runOnAndOff = require('../utils/on-and-off') +// TODO: Test against all algorithms Object.keys(mh.names) +// This subset is known to work with both go-ipfs and js-ipfs as of 2017-09-05 +const HASH_ALGS = [ + 'sha1', + 'sha2-256', + 'sha2-512', + 'keccak-224', + 'keccak-256', + 'keccak-384', + 'keccak-512' +] + describe('files', () => runOnAndOff((thing) => { let ipfs const readme = fs.readFileSync(path.join(process.cwd(), '/src/init-files/init-docs/readme')) @@ -300,6 +314,19 @@ describe('files', () => runOnAndOff((thing) => { }) }) + HASH_ALGS.forEach((name) => { + it(`add with hash=${name} and raw-leaves=false`, function () { + this.timeout(30 * 1000) + + return ipfs(`add src/init-files/init-docs/readme --hash=${name} --raw-leaves=false`) + .then((out) => { + const hash = out.split(' ')[1] + const cid = new CID(hash) + expect(mh.decode(cid.multihash).name).to.equal(name) + }) + }) + }) + it('cat', function () { this.timeout(30 * 1000) diff --git a/test/cli/general.js b/test/cli/general.js index 5a36e1529b..0f20466fcd 100644 --- a/test/cli/general.js +++ b/test/cli/general.js @@ -10,4 +10,12 @@ describe('general cli options', () => runOnAndOff.off((thing) => { expect(out).to.be.empty() }) }) + + it('should handle unknown arguments correctly', () => { + return thing.ipfs('random --again').then((out) => { + expect(out).to.include('Unknown arguments: again, random') + expect(out).to.include('random') + expect(out).to.include('again') + }) + }) })) diff --git a/test/cli/init.js b/test/cli/init.js index 1c5fcf1a1d..86e0dbacda 100644 --- a/test/cli/init.js +++ b/test/cli/init.js @@ -66,4 +66,13 @@ describe('init', function () { expect(repoExistsSync('version')).to.equal(true) }) }) + + it('should present ipfs path help when option help is received', function (done) { + this.timeout(100 * 1000) + + ipfs('init --help').then((res) => { + expect(res).to.have.string('export IPFS_PATH=/path/to/ipfsrepo') + done() + }) + }) })