pin.go 6.99 KB
Newer Older
1 2 3
package commands

import (
Jeromy's avatar
Jeromy committed
4
	"bytes"
5
	"fmt"
Jeromy's avatar
Jeromy committed
6
	"io"
7

8
	key "github.com/ipfs/go-ipfs/blocks/key"
9 10
	cmds "github.com/ipfs/go-ipfs/commands"
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
Jeromy's avatar
Jeromy committed
11
	dag "github.com/ipfs/go-ipfs/merkledag"
12
	u "github.com/ipfs/go-ipfs/util"
13 14
)

15
var PinCmd = &cmds.Command{
16
	Helptext: cmds.HelpText{
Brian Tiger Chow's avatar
Brian Tiger Chow committed
17
		Tagline: "Pin (and unpin) objects to local storage",
18
	},
19

Brian Tiger Chow's avatar
Brian Tiger Chow committed
20 21 22
	Subcommands: map[string]*cmds.Command{
		"add": addPinCmd,
		"rm":  rmPinCmd,
23
		"ls":  listPinCmd,
Brian Tiger Chow's avatar
Brian Tiger Chow committed
24 25 26
	},
}

Jeromy's avatar
Jeromy committed
27
type PinOutput struct {
28
	Pinned []key.Key
Jeromy's avatar
Jeromy committed
29 30
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
31
var addPinCmd = &cmds.Command{
32 33 34 35
	Helptext: cmds.HelpText{
		Tagline: "Pins objects to local storage",
		ShortDescription: `
Retrieves the object named by <ipfs-path> and stores it locally
36
on disk.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
37
`,
38
	},
39

40
	Arguments: []cmds.Argument{
41
		cmds.StringArg("ipfs-path", true, true, "Path to object(s) to be pinned").EnableStdin(),
42 43
	},
	Options: []cmds.Option{
44
		cmds.BoolOption("recursive", "r", "Recursively pin the object linked to by the specified object(s)"),
45
	},
Jeromy's avatar
Jeromy committed
46
	Type: PinOutput{},
47
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
48
		n, err := req.InvocContext().GetNode()
49
		if err != nil {
50 51
			res.SetError(err, cmds.ErrNormal)
			return
52
		}
53

54
		unlock := n.Blockstore.PinLock()
Jeromy's avatar
Jeromy committed
55 56
		defer unlock()

57
		// set recursive flag
58 59
		recursive, found, err := req.Option("recursive").Bool()
		if err != nil {
60 61
			res.SetError(err, cmds.ErrNormal)
			return
62 63
		}
		if !found {
64
			recursive = true
65
		}
66

Jeromy's avatar
Jeromy committed
67
		added, err := corerepo.Pin(n, req.Context(), req.Arguments(), recursive)
68
		if err != nil {
69 70
			res.SetError(err, cmds.ErrNormal)
			return
71 72
		}

73
		res.SetOutput(&PinOutput{added})
Jeromy's avatar
Jeromy committed
74 75 76 77 78 79 80 81
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			added, ok := res.Output().(*PinOutput)
			if !ok {
				return nil, u.ErrCast()
			}

Jeromy's avatar
Jeromy committed
82
			var pintype string
83 84
			rec, found, _ := res.Request().Option("recursive").Bool()
			if rec || !found {
Jeromy's avatar
Jeromy committed
85 86 87 88 89
				pintype = "recursively"
			} else {
				pintype = "directly"
			}

Jeromy's avatar
Jeromy committed
90 91
			buf := new(bytes.Buffer)
			for _, k := range added.Pinned {
Jeromy's avatar
Jeromy committed
92
				fmt.Fprintf(buf, "pinned %s %s\n", k, pintype)
Jeromy's avatar
Jeromy committed
93 94 95
			}
			return buf, nil
		},
96 97 98
	},
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
99
var rmPinCmd = &cmds.Command{
100
	Helptext: cmds.HelpText{
101
		Tagline: "Removes the pinned object from local storage. (By default, recursively. Use -r=false for direct pins)",
102
		ShortDescription: `
103 104
Removes the pin from the given object allowing it to be garbage
collected if needed. (By default, recursively. Use -r=false for direct pins)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
105
`,
106
	},
107

108
	Arguments: []cmds.Argument{
109
		cmds.StringArg("ipfs-path", true, true, "Path to object(s) to be unpinned").EnableStdin(),
110 111
	},
	Options: []cmds.Option{
112
		cmds.BoolOption("recursive", "r", "Recursively unpin the object linked to by the specified object(s)"),
113
	},
Jeromy's avatar
Jeromy committed
114
	Type: PinOutput{},
115
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
116
		n, err := req.InvocContext().GetNode()
117
		if err != nil {
118 119
			res.SetError(err, cmds.ErrNormal)
			return
120
		}
121 122

		// set recursive flag
123 124
		recursive, found, err := req.Option("recursive").Bool()
		if err != nil {
125 126
			res.SetError(err, cmds.ErrNormal)
			return
127 128
		}
		if !found {
129
			recursive = true // default
130
		}
131

Jeromy's avatar
Jeromy committed
132
		removed, err := corerepo.Unpin(n, req.Context(), req.Arguments(), recursive)
133
		if err != nil {
134 135
			res.SetError(err, cmds.ErrNormal)
			return
136 137
		}

138
		res.SetOutput(&PinOutput{removed})
Jeromy's avatar
Jeromy committed
139 140 141 142 143 144 145 146 147 148
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			added, ok := res.Output().(*PinOutput)
			if !ok {
				return nil, u.ErrCast()
			}

			buf := new(bytes.Buffer)
			for _, k := range added.Pinned {
Jeromy's avatar
Jeromy committed
149
				fmt.Fprintf(buf, "unpinned %s\n", k)
Jeromy's avatar
Jeromy committed
150 151 152
			}
			return buf, nil
		},
153 154
	},
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
155

156 157 158 159
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects pinned to local storage",
		ShortDescription: `
160 161
Returns a list of objects that are pinned locally.
By default, only recursively pinned returned, but others may be shown via the '--type' flag.
162 163
`,
		LongDescription: `
164 165
Returns a list of objects that are pinned locally.
By default, only recursively pinned returned, but others may be shown via the '--type' flag.
Jeromy's avatar
Jeromy committed
166 167 168 169 170 171 172

Use --type=<type> to specify the type of pinned keys to list. Valid values are:
    * "direct": pin that specific object.
    * "recursive": pin that specific object, and indirectly pin all its decendants
    * "indirect": pinned indirectly by an ancestor (like a refcount)
    * "all"

173 174 175 176 177 178 179 180 181 182
Example:
	$ echo "hello" | ipfs add -q
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	# now remove the pin, and repin it directly
	$ ipfs pin rm QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin add -r=false QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls --type=direct
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
183 184 185
`,
	},

186
	Options: []cmds.Option{
187
		cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\". Defaults to \"recursive\""),
Jeromy's avatar
Jeromy committed
188
		cmds.BoolOption("count", "n", "Show refcount when listing indirect pins"),
189
		cmds.BoolOption("quiet", "q", "Write just hashes of objects"),
190
	},
191
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
192
		n, err := req.InvocContext().GetNode()
193
		if err != nil {
194 195
			res.SetError(err, cmds.ErrNormal)
			return
196 197
		}

198 199
		typeStr, found, err := req.Option("type").String()
		if err != nil {
200 201
			res.SetError(err, cmds.ErrNormal)
			return
202 203
		}
		if !found {
204
			typeStr = "recursive"
205 206
		}

207 208 209
		switch typeStr {
		case "all", "direct", "indirect", "recursive":
		default:
210 211
			err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr)
			res.SetError(err, cmds.ErrClient)
212 213
		}

214
		keys := make(map[string]RefKeyObject)
215
		if typeStr == "direct" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
216
			for _, k := range n.Pinning.DirectKeys() {
217
				keys[k.B58String()] = RefKeyObject{
Jeromy's avatar
Jeromy committed
218
					Type: "direct",
219
				}
Jeromy's avatar
Jeromy committed
220
			}
221 222
		}
		if typeStr == "indirect" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
			ks := key.NewKeySet()
			for _, k := range n.Pinning.RecursiveKeys() {
				nd, err := n.DAG.Get(n.Context(), k)
				if err != nil {
					res.SetError(err, cmds.ErrNormal)
					return
				}
				err = dag.EnumerateChildren(n.Context(), n.DAG, nd, ks)
				if err != nil {
					res.SetError(err, cmds.ErrNormal)
					return
				}

			}
			for _, k := range ks.Keys() {
238
				keys[k.B58String()] = RefKeyObject{
Jeromy's avatar
Jeromy committed
239
					Type: "indirect",
240
				}
Jeromy's avatar
Jeromy committed
241
			}
242 243
		}
		if typeStr == "recursive" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
244
			for _, k := range n.Pinning.RecursiveKeys() {
245
				keys[k.B58String()] = RefKeyObject{
Jeromy's avatar
Jeromy committed
246
					Type: "recursive",
247
				}
Jeromy's avatar
Jeromy committed
248
			}
249 250
		}

Jeromy's avatar
Jeromy committed
251
		res.SetOutput(&RefKeyList{Keys: keys})
252
	},
Jeromy's avatar
Jeromy committed
253
	Type: RefKeyList{},
254
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
255
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
256 257 258 259 260
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
261 262 263 264 265
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
Jeromy's avatar
Jeromy committed
266 267 268 269 270
			for k, v := range keys.Keys {
				if quiet {
					fmt.Fprintf(out, "%s\n", k)
				} else {
					fmt.Fprintf(out, "%s %s\n", k, v.Type)
Jeromy's avatar
Jeromy committed
271 272 273 274
				}
			}
			return out, nil
		},
275 276
	},
}
Jeromy's avatar
Jeromy committed
277

278
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
279
	Type string
280 281
}

Jeromy's avatar
Jeromy committed
282
type RefKeyList struct {
283
	Keys map[string]RefKeyObject
Jeromy's avatar
Jeromy committed
284
}