pin.go 8.36 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 164
Returns a list of objects that are pinned locally.
By default, only recursively pinned returned, but others may be shown via the '--type' flag.
165 166
`,
		LongDescription: `
167 168
Returns a list of objects that are pinned locally.
By default, only recursively pinned 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 179 180 181 182 183 184 185
Example:
	$ echo "hello" | ipfs add -q
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	# now remove the pin, and repin it directly
	$ ipfs pin rm QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin add -r=false QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
	$ ipfs pin ls --type=direct
	QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
186 187 188
`,
	},

189 190 191
	Arguments: []cmds.Argument{
		cmds.StringArg("ipfs-path", false, true, "Path to object(s) to be listed"),
	},
192
	Options: []cmds.Option{
193
		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
194
		cmds.BoolOption("count", "n", "Show refcount when listing indirect pins"),
195
		cmds.BoolOption("quiet", "q", "Write just hashes of objects"),
196
	},
197
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
198
		n, err := req.InvocContext().GetNode()
199
		if err != nil {
200 201
			res.SetError(err, cmds.ErrNormal)
			return
202 203
		}

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

		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
			}
218 219
		}

220 221
		var keys map[string]RefKeyObject

222 223 224 225 226
		if len(req.Arguments()) > 0 {
			if !typeStrFound {
				typeStr = "all"
			}

227 228 229 230
			keys, err = pinLsKeys(req.Arguments(), typeStr, req.Context(), n)
		} else {
			if !typeStrFound {
				typeStr = "recursive"
231 232
			}

233
			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
}