pin.go 8.68 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
	cmds "github.com/ipfs/go-ipfs/commands"
9
	core "github.com/ipfs/go-ipfs/core"
10
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
Jeromy's avatar
Jeromy committed
11
	dag "github.com/ipfs/go-ipfs/merkledag"
12
	path "github.com/ipfs/go-ipfs/path"
13
	pin "github.com/ipfs/go-ipfs/pin"
Jeromy's avatar
Jeromy committed
14

15 16
	context "context"
	u "gx/ipfs/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/go-ipfs-util"
17
	cid "gx/ipfs/QmcEcrBAMrwMyhSjXt4yfyPpzgSuV8HLHavnfmiKCSRqZU/go-cid"
18 19
)

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

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

Jeromy's avatar
Jeromy committed
32
type PinOutput struct {
Jeromy's avatar
Jeromy committed
33
	Pins []*cid.Cid
Jeromy's avatar
Jeromy committed
34 35
}

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

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

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

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

65 66 67 68 69 70 71
		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
72 73 74
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jeromy's avatar
Jeromy committed
75 76 77 78 79
			added, ok := res.Output().(*PinOutput)
			if !ok {
				return nil, u.ErrCast()
			}

Jeromy's avatar
Jeromy committed
80
			var pintype string
81 82
			rec, found, _ := res.Request().Option("recursive").Bool()
			if rec || !found {
Jeromy's avatar
Jeromy committed
83 84 85 86 87
				pintype = "recursively"
			} else {
				pintype = "directly"
			}

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
97
var rmPinCmd = &cmds.Command{
98
	Helptext: cmds.HelpText{
99
		Tagline: "Remove pinned objects from local storage.",
100
		ShortDescription: `
101
Removes the pin from the given object allowing it to be garbage
Richard Littauer's avatar
Richard Littauer committed
102
collected if needed. (By default, recursively. Use -r=false for direct pins.)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
103
`,
104
	},
105

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

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

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

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

142 143 144
			buf := new(bytes.Buffer)
			for _, k := range added.Pins {
				fmt.Fprintf(buf, "unpinned %s\n", k)
Jeromy's avatar
Jeromy committed
145
			}
146
			return buf, nil
Jeromy's avatar
Jeromy committed
147
		},
148 149
	},
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
150

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

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

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

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
		cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\".").Default("all"),
200
		cmds.BoolOption("quiet", "q", "Write just hashes of objects.").Default(false),
201
	},
202
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
203
		n, err := req.InvocContext().GetNode()
204
		if err != nil {
205 206
			res.SetError(err, cmds.ErrNormal)
			return
207 208
		}

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

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

223 224
		var keys map[string]RefKeyObject

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

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

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

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

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

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

272 273
	mode, ok := pin.StringToPinMode(typeStr)
	if !ok {
Hector Sanjuan's avatar
Hector Sanjuan committed
274
		return nil, fmt.Errorf("invalid pin mode '%s'", typeStr)
275 276
	}

277 278 279
	keys := make(map[string]RefKeyObject)

	for _, p := range args {
Jeromy's avatar
Jeromy committed
280
		pth, err := path.ParsePath(p)
281 282 283 284
		if err != nil {
			return nil, err
		}

285
		c, err := core.ResolveToCid(ctx, n, pth)
286 287 288 289
		if err != nil {
			return nil, err
		}

Jeromy's avatar
Jeromy committed
290
		pinType, pinned, err := n.Pinning.IsPinnedWithType(c, mode)
291 292 293 294 295
		if err != nil {
			return nil, err
		}

		if !pinned {
Hector Sanjuan's avatar
Hector Sanjuan committed
296
			return nil, fmt.Errorf("path '%s' is not pinned", p)
297 298 299 300 301 302 303
		}

		switch pinType {
		case "direct", "indirect", "recursive", "internal":
		default:
			pinType = "indirect through " + pinType
		}
Jeromy's avatar
Jeromy committed
304
		keys[c.String()] = RefKeyObject{
305 306 307 308 309 310
			Type: pinType,
		}
	}

	return keys, nil
}
311 312 313 314 315

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

	keys := make(map[string]RefKeyObject)

Jeromy's avatar
Jeromy committed
316 317 318
	AddToResultKeys := func(keyList []*cid.Cid, typeStr string) {
		for _, c := range keyList {
			keys[c.String()] = RefKeyObject{
319 320 321 322 323 324 325 326 327
				Type: typeStr,
			}
		}
	}

	if typeStr == "direct" || typeStr == "all" {
		AddToResultKeys(n.Pinning.DirectKeys(), "direct")
	}
	if typeStr == "indirect" || typeStr == "all" {
Jeromy's avatar
Jeromy committed
328
		set := cid.NewSet()
329
		for _, k := range n.Pinning.RecursiveKeys() {
330
			err := dag.EnumerateChildren(n.Context(), n.DAG, k, set.Visit, false)
331 332 333 334
			if err != nil {
				return nil, err
			}
		}
Jeromy's avatar
Jeromy committed
335
		AddToResultKeys(set.Keys(), "indirect")
336 337 338 339 340 341 342
	}
	if typeStr == "recursive" || typeStr == "all" {
		AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive")
	}

	return keys, nil
}