pin.go 8.91 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.",
37 38
		ShortDescription: `
Retrieves the object named by <ipfs-path> and stores it locally
39
on disk.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
40
`,
41
	},
42

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

57
		defer n.Blockstore.PinLock().Unlock()
Jeromy's avatar
Jeromy committed
58

59
		// set recursive flag
60 61
		recursive, found, err := req.Option("recursive").Bool()
		if err != nil {
62 63
			res.SetError(err, cmds.ErrNormal)
			return
64 65
		}
		if !found {
66
			recursive = true
67
		}
68

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

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

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

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

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

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

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

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

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

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"

175 176 177
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.

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

195
	Arguments: []cmds.Argument{
196
		cmds.StringArg("ipfs-path", false, true, "Path to object(s) to be listed."),
197
	},
198
	Options: []cmds.Option{
199 200 201
		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."),
202
	},
203
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
204
		n, err := req.InvocContext().GetNode()
205
		if err != nil {
206 207
			res.SetError(err, cmds.ErrNormal)
			return
208 209
		}

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

		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
			}
224 225
		} else {
			typeStr = "all"
226 227
		}

228 229
		var keys map[string]RefKeyObject

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

236 237 238 239
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		} else {
			res.SetOutput(&RefKeyList{Keys: keys})
240
		}
241
	},
Jeromy's avatar
Jeromy committed
242
	Type: RefKeyList{},
243
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
244
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
245 246 247 248 249
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
250 251 252 253 254
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
Jeromy's avatar
Jeromy committed
255 256 257 258 259
			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
260 261 262 263
				}
			}
			return out, nil
		},
264 265
	},
}
Jeromy's avatar
Jeromy committed
266

267
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
268
	Type string
269 270
}

Jeromy's avatar
Jeromy committed
271
type RefKeyList struct {
272
	Keys map[string]RefKeyObject
Jeromy's avatar
Jeromy committed
273
}
274 275

func pinLsKeys(args []string, typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) {
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 308 309 310
	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
}
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 344 345 346

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
}