pin.go 6.19 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 37 38
	DirectKeys() []util.Key
	IndirectKeys() []util.Key
	RecursiveKeys() []util.Key
39 40
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

210 211 212
	return p, nil
}

213 214 215 216 217 218 219 220 221 222 223 224 225
// DirectKeys returns a slice containing the directly pinned keys
func (p *pinner) DirectKeys() []util.Key {
	return p.directPin.GetKeys()
}

// IndirectKeys returns a slice containing the indirectly pinned keys
func (p *pinner) IndirectKeys() []util.Key {
	return p.indirPin.Set().GetKeys()
}

// RecursiveKeys returns a slice containing the recursively pinned keys
func (p *pinner) RecursiveKeys() []util.Key {
	return p.recursePin.GetKeys()
226 227
}

Jeromy's avatar
Jeromy committed
228
// Flush encodes and writes pinner keysets to the datastore
229
func (p *pinner) Flush() error {
Jeromy's avatar
Jeromy committed
230 231
	p.lock.RLock()
	defer p.lock.RUnlock()
Jeromy's avatar
flush!  
Jeromy committed
232

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
233
	err := storeSet(p.dstore, directPinDatastoreKey, p.directPin.GetKeys())
234 235 236 237
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
238
	err = storeSet(p.dstore, recursePinDatastoreKey, p.recursePin.GetKeys())
Jeromy's avatar
flush!  
Jeromy committed
239 240 241 242
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
243
	err = storeIndirPin(p.dstore, indirectPinDatastoreKey, p.indirPin)
Jeromy's avatar
flush!  
Jeromy committed
244 245 246
	if err != nil {
		return err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
247 248
	return nil
}
Jeromy's avatar
flush!  
Jeromy committed
249

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
250 251 252
// 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
253 254 255 256
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
257 258 259 260 261
	return d.Put(k, buf)
}

func loadSet(d ds.Datastore, k ds.Key, val interface{}) error {
	buf, err := d.Get(k)
262 263 264 265
	if err != nil {
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
266 267 268
	bf, ok := buf.([]byte)
	if !ok {
		return errors.New("invalid pin set value in datastore")
269
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
270
	return json.Unmarshal(bf, val)
271
}
272

Jeromy's avatar
Jeromy committed
273 274
// PinWithMode is a method on ManualPinners, allowing the user to have fine
// grained control over pin counts
275 276 277 278 279 280 281 282 283 284
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)
	}
}
285 286 287 288

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