package pin import ( //ds "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/datastore.go" "encoding/json" "errors" "sync" 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" "github.com/jbenet/go-ipfs/blocks/set" mdag "github.com/jbenet/go-ipfs/merkledag" "github.com/jbenet/go-ipfs/util" ) var log = util.Logger("pin") var recursePinDatastoreKey = ds.NewKey("/local/pins/recursive/keys") var directPinDatastoreKey = ds.NewKey("/local/pins/direct/keys") var indirectPinDatastoreKey = ds.NewKey("/local/pins/indirect/keys") type PinMode int const ( Recursive PinMode = iota Direct Indirect ) type Pinner interface { IsPinned(util.Key) bool Pin(*mdag.Node, bool) error Unpin(util.Key, bool) error Flush() error } // ManualPinner is for manually editing the pin structure // Use with care type ManualPinner interface { PinWithMode(util.Key, PinMode) Pinner } type pinner struct { lock sync.RWMutex recursePin set.BlockSet directPin set.BlockSet indirPin *indirectPin dserv mdag.DAGService dstore ds.Datastore } func NewPinner(dstore ds.Datastore, serv mdag.DAGService) Pinner { // Load set from given datastore... rcds := nsds.Wrap(dstore, recursePinDatastoreKey) rcset := set.NewDBWrapperSet(rcds, set.NewSimpleBlockSet()) dirds := nsds.Wrap(dstore, directPinDatastoreKey) dirset := set.NewDBWrapperSet(dirds, set.NewSimpleBlockSet()) nsdstore := nsds.Wrap(dstore, indirectPinDatastoreKey) return &pinner{ recursePin: rcset, directPin: dirset, indirPin: NewIndirectPin(nsdstore), dserv: serv, dstore: dstore, } } func (p *pinner) Pin(node *mdag.Node, recurse bool) error { p.lock.Lock() defer p.lock.Unlock() 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 } func (p *pinner) Unpin(k util.Key, recurse bool) error { p.lock.Lock() defer p.lock.Unlock() if recurse { p.recursePin.RemoveBlock(k) node, err := p.dserv.Get(k) if err != nil { return err } return p.unpinLinks(node) } p.directPin.RemoveBlock(k) 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 } } return nil } func (p *pinner) pinIndirectRecurse(node *mdag.Node) error { k, err := node.Key() if err != nil { return err } p.indirPin.Increment(k) 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 } func (p *pinner) IsPinned(key util.Key) bool { p.lock.RLock() defer p.lock.RUnlock() return p.recursePin.HasKey(key) || p.directPin.HasKey(key) || p.indirPin.HasKey(key) } func LoadPinner(d ds.Datastore, dserv mdag.DAGService) (Pinner, error) { p := new(pinner) { // load recursive set var recurseKeys []util.Key if err := loadSet(d, recursePinDatastoreKey, &recurseKeys); err != nil { return nil, err } p.recursePin = set.SimpleSetFromKeys(recurseKeys) } { // load direct set var directKeys []util.Key if err := loadSet(d, directPinDatastoreKey, &directKeys); err != nil { return nil, err } p.directPin = set.SimpleSetFromKeys(directKeys) } { // load indirect set var err error p.indirPin, err = loadIndirPin(d, indirectPinDatastoreKey) if err != nil { return nil, err } } // assign services p.dserv = dserv p.dstore = d return p, nil } func (p *pinner) Flush() error { p.lock.RLock() defer p.lock.RUnlock() err := storeSet(p.dstore, directPinDatastoreKey, p.directPin.GetKeys()) if err != nil { return err } err = storeSet(p.dstore, recursePinDatastoreKey, p.recursePin.GetKeys()) if err != nil { return err } err = storeIndirPin(p.dstore, indirectPinDatastoreKey, p.indirPin) if err != nil { return err } return nil } // helpers to marshal / unmarshal a pin set func storeSet(d ds.Datastore, k ds.Key, val interface{}) error { buf, err := json.Marshal(val) if err != nil { return err } return d.Put(k, buf) } func loadSet(d ds.Datastore, k ds.Key, val interface{}) error { buf, err := d.Get(k) if err != nil { return err } bf, ok := buf.([]byte) if !ok { return errors.New("invalid pin set value in datastore") } return json.Unmarshal(bf, val) } 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) } }