pin.go 8.9 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
	Pinned []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{
rht's avatar
rht 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)."),
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 58
		recursive, found, err := req.Option("recursive").Bool()
		if err != nil {
59 60
			res.SetError(err, cmds.ErrNormal)
			return
61 62
		}
		if !found {
63
			recursive = true
64
		}
65

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

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

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

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

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

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

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

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

155 156
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
157
		Tagline: "List objects pinned to local storage.",
158
		ShortDescription: `
159
Returns a list of objects that are pinned locally.
160
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.
161 162
`,
		LongDescription: `
163
Returns a list of objects that are pinned locally.
164
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
165 166 167 168 169 170 171

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"

172 173 174
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.

175 176 177 178
Example:
	$ echo "hello" | ipfs add -q
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls
179
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN recursive
180 181
	# now remove the pin, and repin it directly
	$ ipfs pin rm QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
182
	unpinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
183
	$ ipfs pin add -r=false QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
184
	pinned QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN directly
185
	$ ipfs pin ls --type=direct
186 187 188
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct
	$ ipfs pin ls QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN direct
189 190 191
`,
	},

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

207
		typeStr, typeStrFound, err := req.Option("type").String()
208
		if err != nil {
209 210
			res.SetError(err, cmds.ErrNormal)
			return
211
		}
212 213 214 215 216 217 218 219 220

		if typeStrFound {
			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
			}
221 222
		} else {
			typeStr = "all"
223 224
		}

225 226
		var keys map[string]RefKeyObject

227
		if len(req.Arguments()) > 0 {
228 229 230
			keys, err = pinLsKeys(req.Arguments(), typeStr, req.Context(), n)
		} else {
			keys, err = pinLsAll(typeStr, req.Context(), n)
231
		}
232

233 234 235 236
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		} else {
			res.SetOutput(&RefKeyList{Keys: keys})
237
		}
238
	},
Jeromy's avatar
Jeromy committed
239
	Type: RefKeyList{},
240
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
241
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
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
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
Jeromy's avatar
Jeromy committed
252 253 254 255 256
			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
257 258 259 260
				}
			}
			return out, nil
		},
261 262
	},
}
Jeromy's avatar
Jeromy committed
263

264
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
265
	Type string
266 267
}

Jeromy's avatar
Jeromy committed
268
type RefKeyList struct {
269
	Keys map[string]RefKeyObject
Jeromy's avatar
Jeromy committed
270
}
271 272

func pinLsKeys(args []string, typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) {
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 298 299 300 301 302 303 304 305 306 307
	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
}
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 334 335 336 337 338 339 340 341 342 343

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
}