pin.go 8.79 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
	pin "github.com/ipfs/go-ipfs/pin"
15
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
16
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
17 18
)

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

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

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

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

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

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

57
		// set recursive flag
58
		recursive, _, err := req.Option("recursive").Bool()
59
		if err != nil {
60 61
			res.SetError(err, cmds.ErrNormal)
			return
62
		}
63

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

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

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
96
var rmPinCmd = &cmds.Command{
97
	Helptext: cmds.HelpText{
98
		Tagline: "Removes the pinned object from local storage.",
99
		ShortDescription: `
100 101
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
102
`,
103
	},
104

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

		// set recursive flag
120
		recursive, _, err := req.Option("recursive").Bool()
121
		if err != nil {
122 123
			res.SetError(err, cmds.ErrNormal)
			return
124
		}
125

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

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

150 151
var listPinCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
152
		Tagline: "List objects pinned to local storage.",
153
		ShortDescription: `
154
Returns a list of objects that are pinned locally.
155 156 157
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.
158 159
`,
		LongDescription: `
160
Returns a list of objects that are pinned locally.
161 162 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.
Jeromy's avatar
Jeromy committed
164

165 166
Use --type=<type> to specify the type of pinned keys to list.
Valid values are:
Jeromy's avatar
Jeromy committed
167
    * "direct": pin that specific object.
168 169
    * "recursive": pin that specific object, and indirectly pin all its
    	descendants
Jeromy's avatar
Jeromy committed
170 171 172
    * "indirect": pinned indirectly by an ancestor (like a refcount)
    * "all"

173 174 175
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.
176

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

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

208
		typeStr, _, err := req.Option("type").String()
209
		if err != nil {
210 211
			res.SetError(err, cmds.ErrNormal)
			return
212
		}
213

214 215 216 217 218 219
		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
220 221
		}

222 223
		var keys map[string]RefKeyObject

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

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

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

261
type RefKeyObject struct {
Jeromy's avatar
Jeromy committed
262
	Type string
263 264
}

Jeromy's avatar
Jeromy committed
265
type RefKeyList struct {
266
	Keys map[string]RefKeyObject
Jeromy's avatar
Jeromy committed
267
}
268 269

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

271 272 273 274 275 276 277 278 279 280 281 282 283
	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
		}

284 285 286 287 288 289
		mode, ok := pin.StringToPinMode(typeStr)
		if !ok {
			return nil, fmt.Errorf("Invalid pin mode '%s'", typeStr)
		}

		pinType, pinned, err := n.Pinning.IsPinnedWithType(k, mode)
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
		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
}
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 344 345

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
}