From a8df3a22a07b5f6e47fac3b7f74a894632a843ad Mon Sep 17 00:00:00 2001
From: Jeromy <jeromyj@gmail.com>
Date: Thu, 30 Mar 2017 18:20:36 -0700
Subject: [PATCH] implement ipfs pin update

License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
---
 pin.go      | 27 +++++++++++++++++++++++++++
 pin_test.go | 35 ++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/pin.go b/pin.go
index 8c742d1..8de1780 100644
--- a/pin.go
+++ b/pin.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	mdag "github.com/ipfs/go-ipfs/merkledag"
+	dutils "github.com/ipfs/go-ipfs/merkledag/utils"
 
 	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
 	logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
@@ -86,6 +87,11 @@ type Pinner interface {
 	Pin(context.Context, node.Node, bool) error
 	Unpin(context.Context, *cid.Cid, bool) error
 
+	// Update updates a recursive pin from one cid to another
+	// this is more efficient than simply pinning the new one and unpinning the
+	// old one
+	Update(context.Context, *cid.Cid, *cid.Cid, bool) error
+
 	// Check if a set of keys are pinned, more efficient than
 	// calling IsPinned for each key
 	CheckIfPinned(cids ...*cid.Cid) ([]Pinned, error)
@@ -94,6 +100,7 @@ type Pinner interface {
 	// care! If used improperly, garbage collection may not be
 	// successful.
 	PinWithMode(*cid.Cid, PinMode)
+
 	// RemovePinWithMode is for manually editing the pin structure.
 	// Use with care! If used improperly, garbage collection may not
 	// be successful.
@@ -447,6 +454,26 @@ func (p *pinner) RecursiveKeys() []*cid.Cid {
 	return p.recursePin.Keys()
 }
 
+func (p *pinner) Update(ctx context.Context, from, to *cid.Cid, unpin bool) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if !p.recursePin.Has(from) {
+		return fmt.Errorf("'from' cid was not recursively pinned already")
+	}
+
+	err := dutils.DiffEnumerate(ctx, p.dserv, from, to)
+	if err != nil {
+		return err
+	}
+
+	p.recursePin.Add(to)
+	if unpin {
+		p.recursePin.Remove(from)
+	}
+	return nil
+}
+
 // Flush encodes and writes pinner keysets to the datastore
 func (p *pinner) Flush() error {
 	p.lock.Lock()
diff --git a/pin_test.go b/pin_test.go
index cbf89c6..bb90ea0 100644
--- a/pin_test.go
+++ b/pin_test.go
@@ -1,6 +1,7 @@
 package pin
 
 import (
+	"context"
 	"testing"
 	"time"
 
@@ -9,7 +10,6 @@ import (
 	"github.com/ipfs/go-ipfs/exchange/offline"
 	mdag "github.com/ipfs/go-ipfs/merkledag"
 
-	context "context"
 	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
 	dssync "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/sync"
 	"gx/ipfs/QmWbjfz3u6HkAdPh34dgPchGbQjob6LXLhAeCGii2TX69n/go-ipfs-util"
@@ -367,3 +367,36 @@ func TestPinRecursiveFail(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestPinUpdate(t *testing.T) {
+	dstore := dssync.MutexWrap(ds.NewMapDatastore())
+	bstore := blockstore.NewBlockstore(dstore)
+	bserv := bs.New(bstore, offline.Exchange(bstore))
+
+	dserv := mdag.NewDAGService(bserv)
+	p := NewPinner(dstore, dserv, dserv)
+	n1, c1 := randNode()
+	n2, c2 := randNode()
+
+	dserv.Add(n1)
+	dserv.Add(n2)
+
+	ctx := context.Background()
+	if err := p.Pin(ctx, n1, true); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := p.Update(ctx, c1, c2, true); err != nil {
+		t.Fatal(err)
+	}
+
+	assertPinned(t, p, c2, "c2 should be pinned now")
+	assertUnpinned(t, p, c1, "c1 should no longer be pinned")
+
+	if err := p.Update(ctx, c2, c1, false); err != nil {
+		t.Fatal(err)
+	}
+
+	assertPinned(t, p, c2, "c2 should be pinned still")
+	assertPinned(t, p, c1, "c1 should be pinned now")
+}
-- 
GitLab