pin.go 5.81 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2
// package pin implemnts structures and methods to keep track of
// which objects a user wants to keep stored locally.
3 4 5
package pin

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

Jeromy's avatar
Jeromy committed
10 11
	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"
12 13 14 15 16
	"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
17
var log = util.Logger("pin")
18 19 20 21
var recursePinDatastoreKey = ds.NewKey("/local/pins/recursive/keys")
var directPinDatastoreKey = ds.NewKey("/local/pins/direct/keys")
var indirectPinDatastoreKey = ds.NewKey("/local/pins/indirect/keys")

22 23 24 25 26 27 28 29
type PinMode int

const (
	Recursive PinMode = iota
	Direct
	Indirect
)

30
type Pinner interface {
Jeromy's avatar
Jeromy committed
31
	IsPinned(util.Key) bool
32 33
	Pin(*mdag.Node, bool) error
	Unpin(util.Key, bool) error
34
	Flush() error
35
	GetManual() ManualPinner
36
	Set() set.BlockSet
37 38
}

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

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

Jeromy's avatar
Jeromy committed
57
// NewPinner creates a new pinner using the given datastore as a backend
58
func NewPinner(dstore ds.Datastore, serv mdag.DAGService) Pinner {
59 60

	// Load set from given datastore...
61 62
	rcds := nsds.Wrap(dstore, recursePinDatastoreKey)
	rcset := set.NewDBWrapperSet(rcds, set.NewSimpleBlockSet())
63

64 65 66 67
	dirds := nsds.Wrap(dstore, directPinDatastoreKey)
	dirset := set.NewDBWrapperSet(dirds, set.NewSimpleBlockSet())

	nsdstore := nsds.Wrap(dstore, indirectPinDatastoreKey)
68
	return &pinner{
69 70
		recursePin: rcset,
		directPin:  dirset,
71
		indirPin:   NewIndirectPin(nsdstore),
72
		dserv:      serv,
73
		dstore:     dstore,
74 75 76
	}
}

Jeromy's avatar
Jeromy committed
77
// Pin the given node, optionally recursive
78
func (p *pinner) Pin(node *mdag.Node, recurse bool) error {
Jeromy's avatar
Jeromy committed
79 80
	p.lock.Lock()
	defer p.lock.Unlock()
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	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
103
// Unpin a given key with optional recursive unpinning
104
func (p *pinner) Unpin(k util.Key, recurse bool) error {
Jeromy's avatar
Jeromy committed
105 106
	p.lock.Lock()
	defer p.lock.Unlock()
107 108 109 110 111 112 113 114 115
	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
116
	p.directPin.RemoveBlock(k)
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
	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
		}
	}
139 140 141 142 143 144 145 146 147
	return nil
}

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

148
	p.indirPin.Increment(k)
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
	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
167
// IsPinned returns whether or not the given key is pinned
168
func (p *pinner) IsPinned(key util.Key) bool {
Jeromy's avatar
Jeromy committed
169 170
	p.lock.RLock()
	defer p.lock.RUnlock()
171 172 173 174
	return p.recursePin.HasKey(key) ||
		p.directPin.HasKey(key) ||
		p.indirPin.HasKey(key)
}
175

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

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

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

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
204
	// assign services
Jeromy's avatar
Jeromy committed
205 206 207
	p.dserv = dserv
	p.dstore = d

208 209 210
	return p, nil
}

211 212 213 214 215
// Set returns a blockset of directly pinned keys
func (p *pinner) Set() set.BlockSet {
	return p.directPin
}

Jeromy's avatar
Jeromy committed
216
// Flush encodes and writes pinner keysets to the datastore
217
func (p *pinner) Flush() error {
Jeromy's avatar
Jeromy committed
218 219
	p.lock.RLock()
	defer p.lock.RUnlock()
Jeromy's avatar
flush!  
Jeromy committed
220

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
221
	err := storeSet(p.dstore, directPinDatastoreKey, p.directPin.GetKeys())
222 223 224 225
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
226
	err = storeSet(p.dstore, recursePinDatastoreKey, p.recursePin.GetKeys())
Jeromy's avatar
flush!  
Jeromy committed
227 228 229 230
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
231
	err = storeIndirPin(p.dstore, indirectPinDatastoreKey, p.indirPin)
Jeromy's avatar
flush!  
Jeromy committed
232 233 234
	if err != nil {
		return err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
235 236
	return nil
}
Jeromy's avatar
flush!  
Jeromy committed
237

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
238 239 240
// 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
241 242 243 244
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
245 246 247 248 249
	return d.Put(k, buf)
}

func loadSet(d ds.Datastore, k ds.Key, val interface{}) error {
	buf, err := d.Get(k)
250 251 252 253
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
254 255 256
	bf, ok := buf.([]byte)
	if !ok {
		return errors.New("invalid pin set value in datastore")
257
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
258
	return json.Unmarshal(bf, val)
259
}
260

Jeromy's avatar
Jeromy committed
261 262
// PinWithMode is a method on ManualPinners, allowing the user to have fine
// grained control over pin counts
263 264 265 266 267 268 269 270 271 272
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)
	}
}
273 274 275 276

func (p *pinner) GetManual() ManualPinner {
	return p
}