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{
37
		Tagline:          "Pin 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{
Jeromy's avatar
Jeromy committed
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

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

		res.SetOutput(&PinOutput{added})
Jeromy's avatar
Jeromy committed
71 72 73
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jeromy's avatar
Jeromy committed
74 75 76 77 78
			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"
			}

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
96
var rmPinCmd = &cmds.Command{
97
	Helptext: cmds.HelpText{
98
		Tagline: "Remove pinned objects 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{
Jeromy's avatar
Jeromy committed
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

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

132
		res.SetOutput(&PinOutput{removed})
Jeromy's avatar
Jeromy committed
133 134 135
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
136
			added, ok := res.Output().(*PinOutput)
Jeromy's avatar
Jeromy committed
137 138 139 140
			if !ok {
				return nil, u.ErrCast()
			}

141 142 143
			buf := new(bytes.Buffer)
			for _, k := range added.Pins {
				fmt.Fprintf(buf, "unpinned %s\n", k)
Jeromy's avatar
Jeromy committed
144
			}
145
			return buf, nil
Jeromy's avatar
Jeromy committed
146
		},
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

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
			}
333
			err = dag.EnumerateChildren(n.Context(), n.DAG, nd, ks, false)
334 335 336 337 338 339 340 341 342 343 344 345
			if err != nil {
				return nil, err
			}
		}
		AddToResultKeys(ks.Keys(), "indirect")
	}
	if typeStr == "recursive" || typeStr == "all" {
		AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive")
	}

	return keys, nil
}