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

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

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

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

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

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

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

62
		added, err := corerepo.Pin(n, req.Arguments(), recursive)
63
		if err != nil {
64 65
			res.SetError(err, cmds.ErrNormal)
			return
66 67
		}

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

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
94
var rmPinCmd = &cmds.Command{
95 96 97 98
	Helptext: cmds.HelpText{
		Tagline: "Unpin an object from local storage",
		ShortDescription: `
Removes the pin from the given object allowing it to be garbage
99
collected if needed.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
100
`,
101
	},
102

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

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

127
		removed, err := corerepo.Unpin(n, req.Arguments(), recursive)
128
		if err != nil {
129 130
			res.SetError(err, cmds.ErrNormal)
			return
131 132
		}

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

151 152 153 154 155 156
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.
157 158 159 160 161 162
`,
		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:
163 164 165
    * "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)
166
    * "all"
167

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

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

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

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

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

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

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

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

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

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