pin.go 5.48 KB
Newer Older
1 2 3
package pin

import (
Jeromy's avatar
flush!  
Jeromy committed
4
	"encoding/json"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"errors"
Jeromy's avatar
Jeromy committed
6 7
	"sync"

Jeromy's avatar
Jeromy committed
8 9
	ds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
	nsds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/namespace"
10 11 12 13 14
	"github.com/jbenet/go-ipfs/blocks/set"
	mdag "github.com/jbenet/go-ipfs/merkledag"
	"github.com/jbenet/go-ipfs/util"
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
15
var log = util.Logger("pin")
16 17 18 19
var recursePinDatastoreKey = ds.NewKey("/local/pins/recursive/keys")
var directPinDatastoreKey = ds.NewKey("/local/pins/direct/keys")
var indirectPinDatastoreKey = ds.NewKey("/local/pins/indirect/keys")

20 21 22 23 24 25 26 27
type PinMode int

const (
	Recursive PinMode = iota
	Direct
	Indirect
)

28
type Pinner interface {
Jeromy's avatar
Jeromy committed
29
	IsPinned(util.Key) bool
30 31
	Pin(*mdag.Node, bool) error
	Unpin(util.Key, bool) error
32
	Flush() error
33 34
}

35
// ManualPinner is for manually editing the pin structure
Jeromy's avatar
Jeromy committed
36 37
// Use with care! If used improperly, garbage collection
// may not be successful
38 39 40 41 42
type ManualPinner interface {
	PinWithMode(util.Key, PinMode)
	Pinner
}

Jeromy's avatar
Jeromy committed
43
// pinner implements the Pinner interface
44
type pinner struct {
Jeromy's avatar
Jeromy committed
45
	lock       sync.RWMutex
46 47
	recursePin set.BlockSet
	directPin  set.BlockSet
48
	indirPin   *indirectPin
49
	dserv      mdag.DAGService
50
	dstore     ds.Datastore
51 52
}

Jeromy's avatar
Jeromy committed
53
// NewPinner creates a new pinner using the given datastore as a backend
54
func NewPinner(dstore ds.Datastore, serv mdag.DAGService) Pinner {
55 56

	// Load set from given datastore...
57 58
	rcds := nsds.Wrap(dstore, recursePinDatastoreKey)
	rcset := set.NewDBWrapperSet(rcds, set.NewSimpleBlockSet())
59

60 61 62 63
	dirds := nsds.Wrap(dstore, directPinDatastoreKey)
	dirset := set.NewDBWrapperSet(dirds, set.NewSimpleBlockSet())

	nsdstore := nsds.Wrap(dstore, indirectPinDatastoreKey)
64
	return &pinner{
65 66
		recursePin: rcset,
		directPin:  dirset,
67
		indirPin:   NewIndirectPin(nsdstore),
68
		dserv:      serv,
69
		dstore:     dstore,
70 71 72
	}
}

Jeromy's avatar
Jeromy committed
73
// Pin the given node, optionally recursive
74
func (p *pinner) Pin(node *mdag.Node, recurse bool) error {
Jeromy's avatar
Jeromy committed
75 76
	p.lock.Lock()
	defer p.lock.Unlock()
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	k, err := node.Key()
	if err != nil {
		return err
	}

	if recurse {
		if p.recursePin.HasKey(k) {
			return nil
		}

		p.recursePin.AddBlock(k)

		err := p.pinLinks(node)
		if err != nil {
			return err
		}
	} else {
		p.directPin.AddBlock(k)
	}
	return nil
}

Jeromy's avatar
Jeromy committed
99
// Unpin a given key with optional recursive unpinning
100
func (p *pinner) Unpin(k util.Key, recurse bool) error {
Jeromy's avatar
Jeromy committed
101 102
	p.lock.Lock()
	defer p.lock.Unlock()
103 104 105 106 107 108 109 110 111
	if recurse {
		p.recursePin.RemoveBlock(k)
		node, err := p.dserv.Get(k)
		if err != nil {
			return err
		}

		return p.unpinLinks(node)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112
	p.directPin.RemoveBlock(k)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	return nil
}

func (p *pinner) unpinLinks(node *mdag.Node) error {
	for _, l := range node.Links {
		node, err := l.GetNode(p.dserv)
		if err != nil {
			return err
		}

		k, err := node.Key()
		if err != nil {
			return err
		}

		p.recursePin.RemoveBlock(k)

		err = p.unpinLinks(node)
		if err != nil {
			return err
		}
	}
135 136 137 138 139 140 141 142 143
	return nil
}

func (p *pinner) pinIndirectRecurse(node *mdag.Node) error {
	k, err := node.Key()
	if err != nil {
		return err
	}

144
	p.indirPin.Increment(k)
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
	return p.pinLinks(node)
}

func (p *pinner) pinLinks(node *mdag.Node) error {
	for _, l := range node.Links {
		subnode, err := l.GetNode(p.dserv)
		if err != nil {
			// TODO: Maybe just log and continue?
			return err
		}
		err = p.pinIndirectRecurse(subnode)
		if err != nil {
			return err
		}
	}
	return nil
}

Jeromy's avatar
Jeromy committed
163
// IsPinned returns whether or not the given key is pinned
164
func (p *pinner) IsPinned(key util.Key) bool {
Jeromy's avatar
Jeromy committed
165 166
	p.lock.RLock()
	defer p.lock.RUnlock()
167 168 169 170
	return p.recursePin.HasKey(key) ||
		p.directPin.HasKey(key) ||
		p.indirPin.HasKey(key)
}
171

Jeromy's avatar
Jeromy committed
172
// LoadPinner loads a pinner and its keysets from the given datastore
173
func LoadPinner(d ds.Datastore, dserv mdag.DAGService) (Pinner, error) {
174 175
	p := new(pinner)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176 177 178 179 180 181
	{ // load recursive set
		var recurseKeys []util.Key
		if err := loadSet(d, recursePinDatastoreKey, &recurseKeys); err != nil {
			return nil, err
		}
		p.recursePin = set.SimpleSetFromKeys(recurseKeys)
182
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
183 184 185 186 187 188 189

	{ // load direct set
		var directKeys []util.Key
		if err := loadSet(d, directPinDatastoreKey, &directKeys); err != nil {
			return nil, err
		}
		p.directPin = set.SimpleSetFromKeys(directKeys)
190 191
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
192 193 194 195 196 197
	{ // load indirect set
		var err error
		p.indirPin, err = loadIndirPin(d, indirectPinDatastoreKey)
		if err != nil {
			return nil, err
		}
198 199
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
200
	// assign services
Jeromy's avatar
Jeromy committed
201 202 203
	p.dserv = dserv
	p.dstore = d

204 205 206
	return p, nil
}

Jeromy's avatar
Jeromy committed
207
// Flush encodes and writes pinner keysets to the datastore
208
func (p *pinner) Flush() error {
Jeromy's avatar
Jeromy committed
209 210
	p.lock.RLock()
	defer p.lock.RUnlock()
Jeromy's avatar
flush!  
Jeromy committed
211

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
212
	err := storeSet(p.dstore, directPinDatastoreKey, p.directPin.GetKeys())
213 214 215 216
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217
	err = storeSet(p.dstore, recursePinDatastoreKey, p.recursePin.GetKeys())
Jeromy's avatar
flush!  
Jeromy committed
218 219 220 221
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
222
	err = storeIndirPin(p.dstore, indirectPinDatastoreKey, p.indirPin)
Jeromy's avatar
flush!  
Jeromy committed
223 224 225
	if err != nil {
		return err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
226 227
	return nil
}
Jeromy's avatar
flush!  
Jeromy committed
228

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
229 230 231
// helpers to marshal / unmarshal a pin set
func storeSet(d ds.Datastore, k ds.Key, val interface{}) error {
	buf, err := json.Marshal(val)
Jeromy's avatar
flush!  
Jeromy committed
232 233 234 235
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
236 237 238 239 240
	return d.Put(k, buf)
}

func loadSet(d ds.Datastore, k ds.Key, val interface{}) error {
	buf, err := d.Get(k)
241 242 243 244
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
245 246 247
	bf, ok := buf.([]byte)
	if !ok {
		return errors.New("invalid pin set value in datastore")
248
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
249
	return json.Unmarshal(bf, val)
250
}
251

Jeromy's avatar
Jeromy committed
252 253
// PinWithMode is a method on ManualPinners, allowing the user to have fine
// grained control over pin counts
254 255 256 257 258 259 260 261 262 263
func (p *pinner) PinWithMode(k util.Key, mode PinMode) {
	switch mode {
	case Recursive:
		p.recursePin.AddBlock(k)
	case Direct:
		p.directPin.AddBlock(k)
	case Indirect:
		p.indirPin.Increment(k)
	}
}