pin.go 6.69 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) {
47 48
		n, err := req.Context().GetNode()
		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 64 65 66 67 68
		go func() {
			<-req.Context().Context.Done()
			log.Error("CONTEXT IS OVER!")
		}()

		added, err := corerepo.Pin(n, req.Context().Context, req.Arguments(), recursive)
69
		if err != nil {
70 71
			res.SetError(err, cmds.ErrNormal)
			return
72 73
		}

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

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
100
var rmPinCmd = &cmds.Command{
101 102 103 104
	Helptext: cmds.HelpText{
		Tagline: "Unpin an object from local storage",
		ShortDescription: `
Removes the pin from the given object allowing it to be garbage
105
collected if needed.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
106
`,
107
	},
108

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

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

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

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

157 158 159 160 161 162
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.
163 164 165 166 167 168
`,
		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:
169 170 171
    * "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)
172
    * "all"
173

Jeromy's avatar
Jeromy committed
174
To see the ref count on indirect pins, pass the -count option flag.
175
Defaults to "direct".
176 177 178
`,
	},

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

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

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

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

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

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

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

280 281 282 283 284
type RefKeyObject struct {
	Type  string
	Count int
}

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