pin.go 8.75 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
	cmds "github.com/ipfs/go-ipfs/commands"
10
	core "github.com/ipfs/go-ipfs/core"
11
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
Jeromy's avatar
Jeromy committed
12
	dag "github.com/ipfs/go-ipfs/merkledag"
13
	path "github.com/ipfs/go-ipfs/path"
14
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
15
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
16 17
)

18
var PinCmd = &cmds.Command{
19
	Helptext: cmds.HelpText{
rht's avatar
rht committed
20
		Tagline: "Pin (and unpin) objects to local storage.",
21
	},
22

Brian Tiger Chow's avatar
Brian Tiger Chow committed
23 24 25
	Subcommands: map[string]*cmds.Command{
		"add": addPinCmd,
		"rm":  rmPinCmd,
26
		"ls":  listPinCmd,
Brian Tiger Chow's avatar
Brian Tiger Chow committed
27 28 29
	},
}

Jeromy's avatar
Jeromy committed
30
type PinOutput struct {
31
	Pins []key.Key
Jeromy's avatar
Jeromy committed
32 33
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
34
var addPinCmd = &cmds.Command{
35
	Helptext: cmds.HelpText{
Jeromy's avatar
Jeromy committed
36
		Tagline:          "Pins objects to local storage.",
Richard Littauer's avatar
Richard Littauer committed
37
		ShortDescription: "Stores an IPFS object(s) from a given path locally to disk.",
38
	},
39

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

54
		defer n.Blockstore.PinLock().Unlock()
Jeromy's avatar
Jeromy committed
55

56
		// set recursive flag
57
		recursive, _, err := req.Option("recursive").Bool()
58
		if err != nil {
59 60
			res.SetError(err, cmds.ErrNormal)
			return
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
			buf := new(bytes.Buffer)
87
			for _, k := range added.Pins {
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{
rht's avatar
rht committed
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).").Default(true),
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
		recursive, _, err := req.Option("recursive").Bool()
120
		if err != nil {
121 122
			res.SetError(err, cmds.ErrNormal)
			return
123
		}
124

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

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

149 150
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
151
		Tagline: "List objects pinned to local storage.",
152
		ShortDescription: `
153
Returns a list of objects that are pinned locally.
154
By default, all pinned objects are returned, but the '--type' flag or arguments can restrict that to a specific pin type or to some specific objects respectively.
155 156
`,
		LongDescription: `
157
Returns a list of objects that are pinned locally.
158
By default, all pinned objects are returned, but the '--type' flag or arguments can restrict that to a specific pin type or to some specific objects respectively.
Jeromy's avatar
Jeromy committed
159 160 161 162 163 164 165

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

166 167 168
With arguments, the command fails if any of the arguments is not a pinned object.
And if --type=<type> is additionally used, the command will also fail if any of the arguments is not of the specified type.

169 170 171 172
Example:
	$ echo "hello" | ipfs add -q
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls
173
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN recursive
174 175
	# now remove the pin, and repin it directly
	$ ipfs pin rm QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
176
	unpinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
177
	$ ipfs pin add -r=false QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
178
	pinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN directly
179
	$ ipfs pin ls --type=direct
180 181 182
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct
	$ ipfs pin ls QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct
183 184 185
`,
	},

186
	Arguments: []cmds.Argument{
187
		cmds.StringArg("ipfs-path", false, true, "Path to object(s) to be listed."),
188
	},
189
	Options: []cmds.Option{
190
		cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\".").Default("all"),
191 192
		cmds.BoolOption("count", "n", "Show refcount when listing indirect pins."),
		cmds.BoolOption("quiet", "q", "Write just hashes of objects."),
193
	},
194
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
195
		n, err := req.InvocContext().GetNode()
196
		if err != nil {
197 198
			res.SetError(err, cmds.ErrNormal)
			return
199 200
		}

201
		typeStr, _, err := req.Option("type").String()
202
		if err != nil {
203 204
			res.SetError(err, cmds.ErrNormal)
			return
205
		}
206

207 208 209 210 211 212
		switch typeStr {
		case "all", "direct", "indirect", "recursive":
		default:
			err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr)
			res.SetError(err, cmds.ErrClient)
			return
213 214
		}

215 216
		var keys map[string]RefKeyObject

217
		if len(req.Arguments()) > 0 {
218 219 220
			keys, err = pinLsKeys(req.Arguments(), typeStr, req.Context(), n)
		} else {
			keys, err = pinLsAll(typeStr, req.Context(), n)
221
		}
222

223 224 225 226
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		} else {
			res.SetOutput(&RefKeyList{Keys: keys})
227
		}
228
	},
Jeromy's avatar
Jeromy committed
229
	Type: RefKeyList{},
230
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
231
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
232 233 234 235 236
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
237 238 239 240 241
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
Jeromy's avatar
Jeromy committed
242 243 244 245 246
			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
247 248 249 250
				}
			}
			return out, nil
		},
251 252
	},
}
Jeromy's avatar
Jeromy committed
253

254
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
255
	Type string
256 257
}

Jeromy's avatar
Jeromy committed
258
type RefKeyList struct {
259
	Keys map[string]RefKeyObject
Jeromy's avatar
Jeromy committed
260
}
261 262

func pinLsKeys(args []string, typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) {
263

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
	keys := make(map[string]RefKeyObject)

	for _, p := range args {
		dagNode, err := core.Resolve(ctx, n, path.Path(p))
		if err != nil {
			return nil, err
		}

		k, err := dagNode.Key()
		if err != nil {
			return nil, err
		}

		pinType, pinned, err := n.Pinning.IsPinnedWithType(k, typeStr)
		if err != nil {
			return nil, err
		}

		if !pinned {
			return nil, fmt.Errorf("Path '%s' is not pinned", p)
		}

		switch pinType {
		case "direct", "indirect", "recursive", "internal":
		default:
			pinType = "indirect through " + pinType
		}
		keys[k.B58String()] = RefKeyObject{
			Type: pinType,
		}
	}

	return keys, nil
}
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

func pinLsAll(typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) {

	keys := make(map[string]RefKeyObject)

	AddToResultKeys := func(keyList []key.Key, typeStr string) {
		for _, k := range keyList {
			keys[k.B58String()] = RefKeyObject{
				Type: typeStr,
			}
		}
	}

	if typeStr == "direct" || typeStr == "all" {
		AddToResultKeys(n.Pinning.DirectKeys(), "direct")
	}
	if typeStr == "indirect" || typeStr == "all" {
		ks := key.NewKeySet()
		for _, k := range n.Pinning.RecursiveKeys() {
			nd, err := n.DAG.Get(ctx, k)
			if err != nil {
				return nil, err
			}
			err = dag.EnumerateChildren(n.Context(), n.DAG, nd, ks)
			if err != nil {
				return nil, err
			}
		}
		AddToResultKeys(ks.Keys(), "indirect")
	}
	if typeStr == "recursive" || typeStr == "all" {
		AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive")
	}

	return keys, nil
}