pin.go 6.61 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 60 61
		}
		if !found {
			recursive = false
		}
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 79 80 81 82 83 84 85
			var pintype string
			rec, _, _ := res.Request().Option("recursive").Bool()
			if rec {
				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 97 98 99
	Helptext: cmds.HelpText{
		Tagline: "Unpin an object from local storage",
		ShortDescription: `
Removes the pin from the given object allowing it to be garbage
100
collected if needed.
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 125 126
		}
		if !found {
			recursive = false // default
		}
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 156 157
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects pinned to local storage",
		ShortDescription: `
Returns a list of hashes of objects being pinned. Objects that are indirectly
or recursively pinned are not included in the list.
158 159 160 161 162 163
`,
		LongDescription: `
Returns a list of hashes of objects being pinned. Objects that are indirectly
or recursively pinned are not included in the list.

Use --type=<type> to specify the type of pinned keys to list. Valid values are:
164 165 166
    * "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)
167
    * "all"
168

Jeromy's avatar
Jeromy committed
169
To see the ref count on indirect pins, pass the -count option flag.
170
Defaults to "direct".
171 172 173
`,
	},

174
	Options: []cmds.Option{
175
		cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\". Defaults to \"direct\""),
Jeromy's avatar
Jeromy committed
176
		cmds.BoolOption("count", "n", "Show refcount when listing indirect pins"),
177
		cmds.BoolOption("quiet", "q", "Write just hashes of objects"),
178
	},
179
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
180
		n, err := req.InvocContext().GetNode()
181
		if err != nil {
182 183
			res.SetError(err, cmds.ErrNormal)
			return
184 185
		}

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

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

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

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

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

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

275 276 277 278 279
type RefKeyObject struct {
	Type  string
	Count int
}

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