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

Commit 80986f9

Browse files
committed
feat: store pins in datastore instead of DAG
Adds a `.pins` datastore to `ipfs-repo` and uses that to store pins as cbor binary keyed by b58 stringified multihashes. Each pin has several fields: ```javascript { cid: // buffer, the full CID pinned type: // string, 'recursive' or 'direct' name: // string, a human-readable name for the pin } ``` BREAKING CHANGES: * pins are now stored in a datastore, a repo miration will be necessary * ipfs.pins.add now returns an async generator * ipfs.pins.rm now returns an async generator Depends on: - [ ] ipfs/js-ipfs-repo#221 - [ ] ipfs-inactive/interface-js-ipfs-core#594
1 parent 60fe851 commit 80986f9

15 files changed

+270
-1334
lines changed

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"bl": "^4.0.0",
8080
"bs58": "^4.0.1",
8181
"byteman": "^1.3.5",
82+
"cbor": "^4.1.4",
8283
"cid-tool": "~0.4.0",
8384
"cids": "^0.7.2",
8485
"class-is": "^1.1.0",
@@ -102,7 +103,7 @@
102103
"ipfs-http-response": "^0.5.0",
103104
"ipfs-mfs": "^1.0.0",
104105
"ipfs-multipart": "^0.3.0",
105-
"ipfs-repo": "^0.30.0",
106+
"ipfs-repo": "github:ipfs/js-ipfs-repo#store-pins-in-datastore",
106107
"ipfs-unixfs": "^0.3.0",
107108
"ipfs-unixfs-exporter": "^0.41.0",
108109
"ipfs-unixfs-importer": "^0.44.0",
@@ -120,8 +121,10 @@
120121
"is-ipfs": "~0.6.1",
121122
"it-all": "^1.0.1",
122123
"it-concat": "^1.0.0",
124+
"it-first": "^1.0.1",
123125
"it-glob": "0.0.7",
124126
"it-last": "^1.0.1",
127+
"it-parallel-batch": "^1.0.3",
125128
"it-pipe": "^1.1.0",
126129
"it-tar": "^1.2.1",
127130
"it-to-stream": "^0.1.1",
@@ -184,7 +187,7 @@
184187
"form-data": "^3.0.0",
185188
"go-ipfs-dep": "0.4.23-3",
186189
"hat": "0.0.3",
187-
"interface-ipfs-core": "^0.132.0",
190+
"interface-ipfs-core": "github:ipfs/interface-js-ipfs-core#store-pins-in-datastore",
188191
"ipfs-interop": "^1.0.0",
189192
"ipfsd-ctl": "^3.0.0",
190193
"ncp": "^2.0.0",

src/core/components/init.js

-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ module.exports = ({
110110
}
111111

112112
const pinManager = new PinManager(repo, dag)
113-
await pinManager.load()
114113

115114
const pin = {
116115
add: Components.pin.add({ pinManager, gcLock, dag }),

src/core/components/pin/add.js

+18-39
Original file line numberDiff line numberDiff line change
@@ -2,71 +2,50 @@
22
'use strict'
33

44
const { resolvePath, withTimeoutOption } = require('../../utils')
5+
const PinManager = require('./pin-manager')
6+
const { PinTypes } = PinManager
57

68
module.exports = ({ pinManager, gcLock, dag }) => {
7-
return withTimeoutOption(async function add (paths, options) {
9+
return withTimeoutOption(async function * add (paths, options) {
810
options = options || {}
911

1012
const recursive = options.recursive !== false
1113
const cids = await resolvePath(dag, paths, { signal: options.signal })
12-
const pinAdd = async () => {
13-
const results = []
14-
14+
const pinAdd = async function * () {
1515
// verify that each hash can be pinned
1616
for (const cid of cids) {
17-
const key = cid.toBaseEncodedString()
18-
19-
if (recursive) {
20-
if (pinManager.recursivePins.has(key)) {
21-
// it's already pinned recursively
22-
results.push(cid)
17+
const isPinned = await pinManager.isPinnedWithType(cid, [PinTypes.recursive, PinTypes.direct])
18+
const pinned = isPinned.pinned
2319

24-
continue
25-
}
26-
27-
// entire graph of nested links should be pinned,
28-
// so make sure we have all the objects
29-
await pinManager.fetchCompleteDag(key, { preload: options.preload, signal: options.signal })
20+
if (pinned) {
21+
throw new Error(`${cid} already pinned with type ${isPinned.reason}`)
22+
}
3023

31-
// found all objects, we can add the pin
32-
results.push(cid)
24+
if (recursive) {
25+
await pinManager.pinRecursively(cid)
3326
} else {
34-
if (pinManager.recursivePins.has(key)) {
35-
// recursive supersedes direct, can't have both
36-
throw new Error(`${key} already pinned recursively`)
37-
}
38-
39-
if (!pinManager.directPins.has(key)) {
40-
// make sure we have the object
41-
await dag.get(cid, { preload: options.preload })
42-
}
43-
44-
results.push(cid)
27+
await pinManager.pinDirectly(cid)
4528
}
46-
}
4729

48-
// update the pin sets in memory
49-
const pinset = recursive ? pinManager.recursivePins : pinManager.directPins
50-
results.forEach(cid => pinset.add(cid.toString()))
30+
yield { cid }
5131

52-
// persist updated pin sets to datastore
53-
await pinManager.flushPins()
54-
55-
return results.map(cid => ({ cid }))
32+
continue
33+
}
5634
}
5735

5836
// When adding a file, we take a lock that gets released after pinning
5937
// is complete, so don't take a second lock here
6038
const lock = Boolean(options.lock)
6139

6240
if (!lock) {
63-
return pinAdd()
41+
yield * pinAdd()
42+
return
6443
}
6544

6645
const release = await gcLock.readLock()
6746

6847
try {
69-
await pinAdd()
48+
yield * pinAdd()
7049
} finally {
7150
release()
7251
}

src/core/components/pin/ls.js

+54-44
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
/* eslint max-nested-callbacks: ["error", 8] */
22
'use strict'
33

4-
const { parallelMap } = require('streaming-iterables')
5-
const CID = require('cids')
64
const { resolvePath } = require('../../utils')
75
const PinManager = require('./pin-manager')
86
const { PinTypes } = PinManager
97

10-
const PIN_LS_CONCURRENCY = 8
8+
function toPin (type, cid, name) {
9+
const output = {
10+
type,
11+
cid
12+
}
13+
14+
if (name) {
15+
output.name = name
16+
}
17+
18+
return output
19+
}
1120

1221
module.exports = ({ pinManager, dag }) => {
1322
return async function * ls (paths, options) {
@@ -25,67 +34,68 @@ module.exports = ({ pinManager, dag }) => {
2534
if (typeof options.type === 'string') {
2635
type = options.type.toLowerCase()
2736
}
28-
const err = PinManager.checkPinType(type)
29-
if (err) {
30-
throw err
31-
}
37+
38+
PinManager.checkPinType(type)
39+
} else {
40+
options.type = PinTypes.all
3241
}
3342

3443
if (paths) {
35-
paths = Array.isArray(paths) ? paths : [paths]
36-
3744
// check the pinned state of specific hashes
38-
const cids = await resolvePath(dag, paths)
45+
const cids = await resolvePath(dag, paths, { signal: options.signal })
46+
let noMatch = true
3947

40-
yield * parallelMap(PIN_LS_CONCURRENCY, async cid => {
41-
const { reason, pinned } = await pinManager.isPinnedWithType(cid, type)
48+
for (const cid of cids) {
49+
const { reason, pinned, parent } = await pinManager.isPinnedWithType(cid, type)
4250

4351
if (!pinned) {
44-
throw new Error(`path '${paths[cids.indexOf(cid)]}' is not pinned`)
52+
throw new Error(`path '${paths}' is not pinned`)
4553
}
4654

47-
if (reason === PinTypes.direct || reason === PinTypes.recursive) {
48-
return { cid, type: reason }
55+
switch (reason) {
56+
case PinTypes.direct:
57+
case PinTypes.recursive:
58+
noMatch = false
59+
yield {
60+
type: reason,
61+
cid
62+
}
63+
break
64+
default:
65+
noMatch = false
66+
yield {
67+
type: `${PinTypes.indirect} through ${parent}`,
68+
cid
69+
}
4970
}
71+
}
5072

51-
return { cid, type: `${PinTypes.indirect} through ${reason}` }
52-
}, cids)
73+
if (noMatch) {
74+
throw new Error('No match found')
75+
}
5376

5477
return
5578
}
5679

57-
// show all pinned items of type
58-
let pins = []
59-
60-
if (type === PinTypes.direct || type === PinTypes.all) {
61-
pins = pins.concat(
62-
Array.from(pinManager.directPins).map(cid => ({
63-
type: PinTypes.direct,
64-
cid: new CID(cid)
65-
}))
66-
)
67-
}
68-
6980
if (type === PinTypes.recursive || type === PinTypes.all) {
70-
pins = pins.concat(
71-
Array.from(pinManager.recursivePins).map(cid => ({
72-
type: PinTypes.recursive,
73-
cid: new CID(cid)
74-
}))
75-
)
81+
for await (const { cid, name } of pinManager.recursiveKeys()) {
82+
yield toPin(PinTypes.recursive, cid, name)
83+
}
7684
}
7785

7886
if (type === PinTypes.indirect || type === PinTypes.all) {
79-
const indirects = await pinManager.getIndirectKeys(options)
80-
81-
pins = pins
82-
// if something is pinned both directly and indirectly,
83-
// report the indirect entry
84-
.filter(({ cid }) => !indirects.includes(cid.toString()) || !pinManager.directPins.has(cid.toString()))
85-
.concat(indirects.map(cid => ({ type: PinTypes.indirect, cid: new CID(cid) })))
87+
for await (const cid of pinManager.indirectKeys(options)) {
88+
yield {
89+
type: PinTypes.indirect,
90+
cid
91+
}
92+
}
8693
}
8794

88-
// FIXME: https://github.com/ipfs/js-ipfs/issues/2244
89-
yield * pins
95+
if (type === PinTypes.direct || type === PinTypes.all) {
96+
for await (const { cid, name } of pinManager.directKeys()) {
97+
yield toPin(PinTypes.direct, cid, name)
98+
}
99+
}
90100
}
91101
}

0 commit comments

Comments
 (0)