pin.go 6.8 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Jeromy's avatar
Jeromy committed
229
		res.SetOutput(&RefKeyList{Keys: keys})
230
	},
Jeromy's avatar
Jeromy committed
231
	Type: RefKeyList{},
232
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
233 234 235 236 237 238 239 240 241 242 243
		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
			}

244 245 246 247 248
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
249 250 251 252 253 254 255
			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 {
256
					if quiet {
rht's avatar
rht committed
257
						fmt.Fprintf(out, "%s %d\n", k, v.Count)
258 259 260
					} else {
						fmt.Fprintf(out, "%s %s %d\n", k, v.Type, v.Count)
					}
Jeromy's avatar
Jeromy committed
261 262
				}
			} else {
263 264 265 266 267 268
				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
269 270 271 272
				}
			}
			return out, nil
		},
273 274
	},
}
Jeromy's avatar
Jeromy committed
275

276 277
type RefKeyObject struct {
	Type  string
278
	Count uint64
279 280
}

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