pin.go 6.85 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 11
	cmds "github.com/ipfs/go-ipfs/commands"
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
	u "github.com/ipfs/go-ipfs/util"
12 13
)

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

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

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

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

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

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

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

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

72
		res.SetOutput(&PinOutput{added})
Jeromy's avatar
Jeromy committed
73 74 75 76 77 78 79 80
	},
	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
81
			var pintype string
82 83
			rec, found, _ := res.Request().Option("recursive").Bool()
			if rec || !found {
Jeromy's avatar
Jeromy committed
84 85 86 87 88
				pintype = "recursively"
			} else {
				pintype = "directly"
			}

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
98
var rmPinCmd = &cmds.Command{
99
	Helptext: cmds.HelpText{
100
		Tagline: "Removes the pinned object from local storage. (By default, recursively. Use -r=false for direct pins)",
101
		ShortDescription: `
102 103
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
104
`,
105
	},
106

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

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

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

137
		res.SetOutput(&PinOutput{removed})
Jeromy's avatar
Jeromy committed
138 139 140 141 142 143 144 145 146 147
	},
	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
148
				fmt.Fprintf(buf, "unpinned %s\n", k)
Jeromy's avatar
Jeromy committed
149 150 151
			}
			return buf, nil
		},
152 153
	},
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
154

155 156 157 158
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects pinned to local storage",
		ShortDescription: `
159 160
Returns a list of objects that are pinned locally.
By default, only recursively pinned returned, but others may be shown via the '--type' flag.
161 162
`,
		LongDescription: `
163 164 165 166 167 168 169 170 171 172 173 174
Returns a list of objects that are pinned locally.
By default, only recursively pinned returned, but others may be shown via the '--type' flag.
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
175 176 177
`,
	},

178
	Options: []cmds.Option{
179
		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
180
		cmds.BoolOption("count", "n", "Show refcount when listing indirect pins"),
181
		cmds.BoolOption("quiet", "q", "Write just hashes of objects"),
182
	},
183
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
184
		n, err := req.InvocContext().GetNode()
185
		if err != nil {
186 187
			res.SetError(err, cmds.ErrNormal)
			return
188 189
		}

190 191
		typeStr, found, err := req.Option("type").String()
		if err != nil {
192 193
			res.SetError(err, cmds.ErrNormal)
			return
194 195
		}
		if !found {
196
			typeStr = "recursive"
197 198
		}

199 200 201
		switch typeStr {
		case "all", "direct", "indirect", "recursive":
		default:
202 203
			err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr)
			res.SetError(err, cmds.ErrClient)
204 205
		}

206
		keys := make(map[string]RefKeyObject)
207
		if typeStr == "direct" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
208
			for _, k := range n.Pinning.DirectKeys() {
209 210 211 212
				keys[k.B58String()] = RefKeyObject{
					Type:  "direct",
					Count: 1,
				}
Jeromy's avatar
Jeromy committed
213
			}
214 215
		}
		if typeStr == "indirect" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
216
			for k, v := range n.Pinning.IndirectKeys() {
217 218 219 220
				keys[k.B58String()] = RefKeyObject{
					Type:  "indirect",
					Count: v,
				}
Jeromy's avatar
Jeromy committed
221
			}
222 223
		}
		if typeStr == "recursive" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
224
			for _, k := range n.Pinning.RecursiveKeys() {
225 226 227 228
				keys[k.B58String()] = RefKeyObject{
					Type:  "recursive",
					Count: 1,
				}
Jeromy's avatar
Jeromy committed
229
			}
230 231
		}

Jeromy's avatar
Jeromy committed
232
		res.SetOutput(&RefKeyList{Keys: keys})
233
	},
Jeromy's avatar
Jeromy committed
234
	Type: RefKeyList{},
235
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
236 237 238 239 240 241 242 243 244 245 246
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			typeStr, _, err := res.Request().Option("type").String()
			if err != nil {
				return nil, err
			}

			count, _, err := res.Request().Option("count").Bool()
			if err != nil {
				return nil, err
			}

247 248 249 250 251
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
252 253 254 255 256 257 258
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
			if typeStr == "indirect" && count {
				for k, v := range keys.Keys {
259
					if quiet {
rht's avatar
rht committed
260
						fmt.Fprintf(out, "%s %d\n", k, v.Count)
261 262 263
					} else {
						fmt.Fprintf(out, "%s %s %d\n", k, v.Type, v.Count)
					}
Jeromy's avatar
Jeromy committed
264 265
				}
			} else {
266 267 268 269 270 271
				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
272 273 274 275
				}
			}
			return out, nil
		},
276 277
	},
}
Jeromy's avatar
Jeromy committed
278

279 280
type RefKeyObject struct {
	Type  string
281
	Count uint64
282 283
}

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