pin.go 5.6 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 36
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

206 207 208
	return p, nil
}

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

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

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

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
231 232 233
// 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
234 235 236 237
	if err != nil {
		return err
	}

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

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

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

Jeromy's avatar
Jeromy committed
254 255
// PinWithMode is a method on ManualPinners, allowing the user to have fine
// grained control over pin counts
256 257 258 259 260 261 262 263 264 265
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)
	}
}