pin.go 8.87 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 11
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	core "github.com/ipfs/go-ipfs/core"
12
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
Jeromy's avatar
Jeromy committed
13
	dag "github.com/ipfs/go-ipfs/merkledag"
14
	path "github.com/ipfs/go-ipfs/path"
15
	u "github.com/ipfs/go-ipfs/util"
16 17
)

18
var PinCmd = &cmds.Command{
19
	Helptext: cmds.HelpText{
Brian Tiger Chow's avatar
Brian Tiger Chow 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 36 37 38
	Helptext: cmds.HelpText{
		Tagline: "Pins objects to local storage",
		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
		unlock := n.Blockstore.PinLock()
Jeromy's avatar
Jeromy committed
58 59
		defer unlock()

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

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

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

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

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

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

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

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

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

159 160 161 162
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects pinned to local storage",
		ShortDescription: `
163
Returns a list of objects that are pinned locally.
164
Without arguments, by default, only recursively pinned objects are returned, but others may be shown via the '--type' flag.
165 166
`,
		LongDescription: `
167
Returns a list of objects that are pinned locally.
168
Without arguments, by default, only recursively pinned objects are returned, but others may be shown via the '--type' flag.
Jeromy's avatar
Jeromy committed
169 170 171 172 173 174 175

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"

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

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

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

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

		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
			}
225 226
		}

227 228
		var keys map[string]RefKeyObject

229 230 231 232 233
		if len(req.Arguments()) > 0 {
			if !typeStrFound {
				typeStr = "all"
			}

234 235 236 237
			keys, err = pinLsKeys(req.Arguments(), typeStr, req.Context(), n)
		} else {
			if !typeStrFound {
				typeStr = "recursive"
238 239
			}

240
			keys, err = pinLsAll(typeStr, req.Context(), n)
241
		}
242

243 244 245 246
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		} else {
			res.SetOutput(&RefKeyList{Keys: keys})
247
		}
248
	},
Jeromy's avatar
Jeromy committed
249
	Type: RefKeyList{},
250
	Marshalers: cmds.MarshalerMap{
Jeromy's avatar
Jeromy committed
251
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
252 253 254 255 256
			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
257 258 259 260 261
			keys, ok := res.Output().(*RefKeyList)
			if !ok {
				return nil, u.ErrCast()
			}
			out := new(bytes.Buffer)
Jeromy's avatar
Jeromy committed
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
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
275
	Type string
276 277
}

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

func pinLsKeys(args []string, typeStr string, ctx context.Context, n *core.IpfsNode) (map[string]RefKeyObject, error) {
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 311 312 313 314 315 316 317
	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
}
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 347 348 349 350 351 352 353

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
}