Skip to content

Commit 08e68b3

Browse files
dignifiedquiredaviddias
authored andcommitted
feat(pull + api): migration to pull streams + rename datastore ->
blockstore and datastore-legacy -> datastore + use base32 encoding for blocks BREAKING: - Rename `datastore` to `blockstore` and `datastore-legacy` to `datastore`. - Stores now need to adhere to the [interface-pull-blob-store](https://github.com/ipfs/interface-pull-blob-store) definition.
1 parent d6b956f commit 08e68b3

File tree

46 files changed

+602
-509
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+602
-509
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ language: node_js
33
node_js:
44
- 4
55
- 5
6+
- stable
67

78
# Make sure we have new NPM.
89
before_install:

README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs
3030
- [repo.config.get(cb(err, config))](#repoconfiggetcberr-config)
3131
- [repo.config.set(config, cb(err))](#repoconfigsetconfig-cberr)
3232
- [repo.keys](#repokeys)
33-
- [repo.datastore.read(key, cb(err, buffer))](#repodatastorereadkey-cberr-buffer)
34-
- [repo.datastore.write(buffer, cb(err, buffer))](#repodatastorewritebuffer-cberr-buffer)
35-
- [repo.datastoreLegacy](#repodatastorelegacy)
33+
- [repo.blockstore.putStream()](#)
34+
- [repo.blockstore.getStream(key, extension)](#)
35+
- [repo.datastore](#repodatastore)
3636
- [Contribute](#contribute)
3737
- [License](#license)
3838

@@ -45,7 +45,7 @@ Here is the architectural reasoning for this repo:
4545
│ interface defined by Repo Spec │
4646
├─────────────────────────────────┤
4747
│ │ ┌──────────────────────┐
48-
│ │ │ abstract-blob-store │
48+
│ │ │ interface-pull-blob-store │
4949
│ IPFS REPO │─────────────────────────────────▶│ interface │
5050
│ │ ├──────────────────────┤
5151
│ │ │ locks │
@@ -60,15 +60,15 @@ Here is the architectural reasoning for this repo:
6060
│ interface │ │ interface │ │ interface │ │ interface │ │ interface │ │ interface │
6161
├───────────┤ ├───────────┤ ├───────────┤ ├───────────┤ ├───────────┤ ├───────────┤
6262
│ │ │ │ │ │ │ │ │ │ │ │
63-
│ keys │ │ config │ │ datastore │ │ datastore │ │ logs │ │ version │
64-
│ │ │ │ │ │ │ -legacy │ │ │ │ │
63+
│ keys │ │ config │ │ blockstore │ │ datastore │ │ logs │ │ version │
64+
│ │ │ │ │ │ │ │ │ │ │ │
6565
└───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘
6666
```
6767

6868
This provides a well defined interface for creating and interacting with an IPFS
6969
Repo backed by a group of abstract backends for keys, configuration, logs, and
7070
more. Each of the individual repos has an interface defined by
71-
[abstract-blob-store](https://github.com/maxogden/abstract-blob-store): this
71+
[interface-pull-blob-store](https://github.com/ipfs/interface-pull-blob-store): this
7272
enables us to make IPFS Repo portable (running on Node.js vs the browser) and
7373
accept different types of storage mechanisms for each repo (fs, levelDB, etc).
7474

@@ -136,7 +136,7 @@ Creates a **reference** to an IPFS repository at the path `path`. This does
136136
Valid keys for `opts` include:
137137

138138
- `stores`: either an
139-
[abstract-blob-store](https://github.com/maxogden/abstract-blob-store), or a
139+
[interface-pull-blob-store](https://github.com/ipfs/interface-pull-blob-store), or a
140140
map of the form
141141

142142
```js
@@ -173,12 +173,14 @@ Read/write keys inside the repo. This feature will be expanded once
173173
[IPRS](https://github.com/ipfs/specs/tree/master/records) and
174174
[KeyChain](https://github.com/ipfs/specs/tree/master/keychain) are finalized and implemented on go-ipfs.
175175

176-
### repo.datastore.read(key, cb(err, buffer))
177-
### repo.datastore.write(buffer, cb(err, buffer))
176+
### repo.blockstore.putStream()
177+
### repo.datastore.getStream(key, extension)
178+
### repo.datastore.has(key, extension, cb)
179+
### repo.datastore.delete(key, extension, cb)
178180

179181
Read and write buffers to/from the repo's block store.
180182

181-
### repo.datastoreLegacy
183+
### repo.datastore
182184

183185
**WIP**
184186

package.json

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,30 @@
2929
],
3030
"homepage": "https://github.com/ipfs/js-ipfs-repo",
3131
"devDependencies": {
32-
"abstract-blob-store": "^3.2.0",
33-
"aegir": "^5.0.1",
34-
"async": "^2.0.1",
32+
"aegir": "^8.0.0",
3533
"buffer-loader": "^0.0.1",
3634
"chai": "^3.5.0",
37-
"fs-blob-store": "^5.2.1",
38-
"idb-plus-blob-store": "^1.1.2",
39-
"lodash": "^4.13.1",
35+
"fs-pull-blob-store": "^0.3.0",
36+
"idb-pull-blob-store": "^0.4.0",
37+
"interface-pull-blob-store": "^0.5.0",
38+
"lodash": "^4.15.0",
39+
"multihashes": "^0.2.2",
4040
"ncp": "^2.0.0",
41-
"pre-commit": "^1.1.2",
42-
"rimraf": "^2.5.2"
41+
"pre-commit": "^1.1.3",
42+
"rimraf": "^2.5.4"
4343
},
4444
"dependencies": {
45-
"babel-runtime": "^6.6.1",
46-
"bl": "^1.1.2",
47-
"concat-stream": "^1.5.1",
45+
"babel-runtime": "^6.11.6",
46+
"base32.js": "^0.1.0",
4847
"ipfs-block": "^0.3.0",
49-
"lock": "^0.1.2",
50-
"lockfile": "^1.0.1",
51-
"multihashes": "^0.2.1",
52-
"xtend": "^4.0.1"
48+
"lock": "^0.1.3",
49+
"multihashes": "^0.2.2",
50+
"pull-stream": "^3.4.5",
51+
"pull-through": "^1.0.18",
52+
"pull-write": "^1.1.0",
53+
"run-parallel": "^1.1.6",
54+
"run-series": "^1.1.4",
55+
"safe-buffer": "^5.0.1"
5356
},
5457
"license": "MIT",
5558
"contributors": [
@@ -61,4 +64,4 @@
6164
"Stephen Whitmore <stephen.whitmore@gmail.com>",
6265
"greenkeeperio-bot <support@greenkeeper.io>"
6366
]
64-
}
67+
}

src/index.js

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,56 @@
11
'use strict'
22

3+
const assert = require('assert')
4+
35
const stores = require('./stores')
46

5-
function Repo (repoPath, options) {
6-
if (!(this instanceof Repo)) {
7-
return new Repo(repoPath, options)
7+
module.exports = class Repo {
8+
constructor (repoPath, options) {
9+
assert.equal(typeof repoPath, 'string', 'missing repoPath')
10+
assert(options, 'missing options')
11+
assert(options.stores, 'missing options.stores')
12+
13+
this.path = repoPath
14+
15+
const blobStores = initializeBlobStores(options.stores)
16+
17+
const setup = (name, needs) => {
18+
needs = needs || {}
19+
const args = [repoPath, blobStores[name]]
20+
if (needs.locks) {
21+
args.push(this.locks)
22+
}
23+
24+
if (needs.config) {
25+
args.push(this.config)
26+
}
27+
28+
return stores[name].setUp.apply(stores[name], args)
29+
}
30+
31+
this.locks = setup('locks')
32+
this.version = setup('version', {locks: true})
33+
this.config = setup('config', {locks: true})
34+
this.keys = setup('keys', {locks: true, config: true})
35+
this.blockstore = setup('blockstore', {locks: true})
36+
}
37+
38+
exists (callback) {
39+
this.version.exists(callback)
840
}
9-
if (!options) { throw new Error('missing options param') }
10-
if (!options.stores) { throw new Error('missing options.stores param') }
11-
12-
// If options.stores is an abstract-blob-store instead of a map, use it for
13-
// all stores.
14-
if (options.stores.prototype && options.stores.prototype.createWriteStream) {
15-
const store = options.stores
16-
options.stores = {
41+
}
42+
43+
function initializeBlobStores (store) {
44+
if (store.constructor) {
45+
return {
1746
keys: store,
1847
config: store,
19-
datastore: store,
48+
blockstore: store,
2049
logs: store,
2150
locks: store,
2251
version: store
2352
}
2453
}
2554

26-
this.path = repoPath
27-
28-
this.locks = stores
29-
.locks
30-
.setUp(repoPath, options.stores.locks)
31-
32-
this.exists = (callback) => {
33-
this.version.exists(callback)
34-
}
35-
36-
this.version = stores
37-
.version
38-
.setUp(repoPath, options.stores.version, this.locks)
39-
40-
this.config = stores
41-
.config
42-
.setUp(repoPath, options.stores.config, this.locks)
43-
44-
this.keys = stores
45-
.keys
46-
.setUp(repoPath, options.stores.keys, this.locks, this.config)
47-
48-
this.datastore = stores
49-
.datastore
50-
.setUp(repoPath, options.stores.datastore, this.locks)
55+
return store
5156
}
52-
53-
exports = module.exports = Repo

src/stores/blockstore.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use strict'
2+
3+
const Block = require('ipfs-block')
4+
const pull = require('pull-stream')
5+
const Lock = require('lock')
6+
const base32 = require('base32.js')
7+
const path = require('path')
8+
const write = require('pull-write')
9+
const parallel = require('run-parallel')
10+
const through = require('pull-through')
11+
12+
const PREFIX_LENGTH = 5
13+
14+
exports = module.exports
15+
16+
function multihashToPath (multihash, extension) {
17+
extension = extension || 'data'
18+
const encoder = new base32.Encoder()
19+
const hash = encoder.write(multihash).finalize()
20+
const filename = `${hash}.${extension}`
21+
const folder = filename.slice(0, PREFIX_LENGTH)
22+
23+
return path.join(folder, filename)
24+
}
25+
26+
exports.setUp = (basePath, BlobStore, locks) => {
27+
const store = new BlobStore(basePath + '/blocks')
28+
const lock = new Lock()
29+
30+
function writeBlock (block, cb) {
31+
if (!block || !block.data) {
32+
return cb(new Error('Invalid block'))
33+
}
34+
35+
const key = multihashToPath(block.key, block.extension)
36+
37+
lock(key, (release) => pull(
38+
pull.values([block.data]),
39+
store.write(key, release((err) => {
40+
if (err) {
41+
return cb(err)
42+
}
43+
cb(null, {key})
44+
}))
45+
))
46+
}
47+
48+
return {
49+
getStream (key, extension) {
50+
if (!key) {
51+
return pull.error(new Error('Invalid key'))
52+
}
53+
54+
const p = multihashToPath(key, extension)
55+
56+
const ext = extension === 'data' ? 'protobuf' : extension
57+
let data = []
58+
59+
return pull(
60+
store.read(p),
61+
through(function (values) {
62+
data = data.concat(values)
63+
}, function () {
64+
this.queue(new Block(Buffer.concat(data), ext))
65+
this.queue(null)
66+
})
67+
)
68+
},
69+
70+
putStream () {
71+
let ended = false
72+
let written = []
73+
let push = null
74+
75+
const sink = write((blocks, cb) => {
76+
parallel(blocks.map((block) => (cb) => {
77+
writeBlock(block, (err, meta) => {
78+
if (err) return cb(err)
79+
if (push) {
80+
const read = push
81+
push = null
82+
read(null, meta)
83+
return cb()
84+
}
85+
86+
written.push(meta)
87+
cb()
88+
})
89+
}), cb)
90+
}, null, 100, (err) => {
91+
ended = err || true
92+
if (push) push(ended)
93+
})
94+
95+
const source = (end, cb) => {
96+
if (end) ended = end
97+
if (ended) return cb(ended)
98+
99+
if (written.length) {
100+
return cb(null, written.shift())
101+
}
102+
103+
push = cb
104+
}
105+
106+
return {source, sink}
107+
},
108+
109+
has (key, extension, cb) {
110+
if (typeof extension === 'function') {
111+
cb = extension
112+
extension = undefined
113+
}
114+
115+
if (!key) {
116+
return cb(new Error('Invalid key'))
117+
}
118+
119+
const p = multihashToPath(key, extension)
120+
store.exists(p, cb)
121+
},
122+
123+
delete (key, extension, cb) {
124+
if (typeof extension === 'function') {
125+
cb = extension
126+
extension = undefined
127+
}
128+
129+
if (!key) {
130+
return cb(new Error('Invalid key'))
131+
}
132+
133+
const p = multihashToPath(key, extension)
134+
store.remove(p, cb)
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)