Skip to content

Commit 20e64ae

Browse files
authored
Merge pull request #6493 from MichaelMure/streamed-pin-ls
pin cmd: stream recursive pins
2 parents cd2611d + 16b4d74 commit 20e64ae

File tree

2 files changed

+173
-96
lines changed

2 files changed

+173
-96
lines changed

core/commands/ls.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ The JSON output contains type information.
6565
cmds.BoolOption(lsHeadersOptionNameTime, "v", "Print table headers (Hash, Size, Name)."),
6666
cmds.BoolOption(lsResolveTypeOptionName, "Resolve linked objects to find out their types.").WithDefault(true),
6767
cmds.BoolOption(lsSizeOptionName, "Resolve linked objects to find out their file size.").WithDefault(true),
68-
cmds.BoolOption(lsStreamOptionName, "s", "Enable exprimental streaming of directory entries as they are traversed."),
68+
cmds.BoolOption(lsStreamOptionName, "s", "Enable experimental streaming of directory entries as they are traversed."),
6969
},
7070
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
7171
api, err := cmdenv.GetApi(env, req)

core/commands/pin.go

Lines changed: 172 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,9 @@ collected if needed. (By default, recursively. Use -r=false for direct pins.)
259259
}
260260

261261
const (
262-
pinTypeOptionName = "type"
263-
pinQuietOptionName = "quiet"
262+
pinTypeOptionName = "type"
263+
pinQuietOptionName = "quiet"
264+
pinStreamOptionName = "stream"
264265
)
265266

266267
var listPinCmd = &cmds.Command{
@@ -313,6 +314,7 @@ Example:
313314
Options: []cmds.Option{
314315
cmds.StringOption(pinTypeOptionName, "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\".").WithDefault("all"),
315316
cmds.BoolOption(pinQuietOptionName, "q", "Write just hashes of objects."),
317+
cmds.BoolOption(pinStreamOptionName, "s", "Enable streaming of pins as they are discovered."),
316318
},
317319
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
318320
n, err := cmdenv.GetNode(env)
@@ -326,9 +328,7 @@ Example:
326328
}
327329

328330
typeStr, _ := req.Options[pinTypeOptionName].(string)
329-
if err != nil {
330-
return err
331-
}
331+
stream, _ := req.Options[pinStreamOptionName].(bool)
332332

333333
switch typeStr {
334334
case "all", "direct", "indirect", "recursive":
@@ -337,34 +337,50 @@ Example:
337337
return err
338338
}
339339

340-
enc, err := cmdenv.GetCidEncoder(req)
341-
if err != nil {
342-
return err
340+
// For backward compatibility, we accumulate the pins in the same output type as before.
341+
emit := res.Emit
342+
lgcList := map[string]PinLsType{}
343+
if !stream {
344+
emit = func(v interface{}) error {
345+
obj := v.(*PinLsOutputWrapper)
346+
lgcList[obj.PinLsObject.Cid] = PinLsType{Type: obj.PinLsObject.Type}
347+
return nil
348+
}
343349
}
344350

345-
var keys map[cid.Cid]RefKeyObject
346351
if len(req.Arguments) > 0 {
347-
keys, err = pinLsKeys(req.Context, req.Arguments, typeStr, n, api)
352+
err = pinLsKeys(req, typeStr, n, api, emit)
348353
} else {
349-
keys, err = pinLsAll(req.Context, typeStr, n)
354+
err = pinLsAll(req, typeStr, n, emit)
350355
}
351356
if err != nil {
352357
return err
353358
}
354359

355-
refKeys := make(map[string]RefKeyObject, len(keys))
356-
for k, v := range keys {
357-
refKeys[enc.Encode(k)] = v
360+
if !stream {
361+
return cmds.EmitOnce(res, &PinLsOutputWrapper{
362+
PinLsList: PinLsList{Keys: lgcList},
363+
})
358364
}
359365

360-
return cmds.EmitOnce(res, &RefKeyList{Keys: refKeys})
366+
return nil
361367
},
362-
Type: RefKeyList{},
368+
Type: &PinLsOutputWrapper{},
363369
Encoders: cmds.EncoderMap{
364-
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RefKeyList) error {
370+
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *PinLsOutputWrapper) error {
365371
quiet, _ := req.Options[pinQuietOptionName].(bool)
372+
stream, _ := req.Options[pinStreamOptionName].(bool)
373+
374+
if stream {
375+
if quiet {
376+
fmt.Fprintf(w, "%s\n", out.PinLsObject.Cid)
377+
} else {
378+
fmt.Fprintf(w, "%s %s\n", out.PinLsObject.Cid, out.PinLsObject.Type)
379+
}
380+
return nil
381+
}
366382

367-
for k, v := range out.Keys {
383+
for k, v := range out.PinLsList.Keys {
368384
if quiet {
369385
fmt.Fprintf(w, "%s\n", k)
370386
} else {
@@ -377,6 +393,144 @@ Example:
377393
},
378394
}
379395

396+
// PinLsOutputWrapper is the output type of the pin ls command.
397+
// Pin ls needs to output two different type depending on if it's streamed or not.
398+
// We use this to bypass the cmds lib refusing to have interface{}
399+
type PinLsOutputWrapper struct {
400+
PinLsList
401+
PinLsObject
402+
}
403+
404+
// PinLsList is a set of pins with their type
405+
type PinLsList struct {
406+
Keys map[string]PinLsType `json:",omitempty"`
407+
}
408+
409+
// PinLsType contains the type of a pin
410+
type PinLsType struct {
411+
Type string
412+
}
413+
414+
// PinLsObject contains the description of a pin
415+
type PinLsObject struct {
416+
Cid string `json:",omitempty"`
417+
Type string `json:",omitempty"`
418+
}
419+
420+
func pinLsKeys(req *cmds.Request, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI, emit func(value interface{}) error) error {
421+
mode, ok := pin.StringToMode(typeStr)
422+
if !ok {
423+
return fmt.Errorf("invalid pin mode '%s'", typeStr)
424+
}
425+
426+
enc, err := cmdenv.GetCidEncoder(req)
427+
if err != nil {
428+
return err
429+
}
430+
431+
for _, p := range req.Arguments {
432+
c, err := api.ResolvePath(req.Context, path.New(p))
433+
if err != nil {
434+
return err
435+
}
436+
437+
pinType, pinned, err := n.Pinning.IsPinnedWithType(c.Cid(), mode)
438+
if err != nil {
439+
return err
440+
}
441+
442+
if !pinned {
443+
return fmt.Errorf("path '%s' is not pinned", p)
444+
}
445+
446+
switch pinType {
447+
case "direct", "indirect", "recursive", "internal":
448+
default:
449+
pinType = "indirect through " + pinType
450+
}
451+
452+
err = emit(&PinLsOutputWrapper{
453+
PinLsObject: PinLsObject{
454+
Type: pinType,
455+
Cid: enc.Encode(c.Cid()),
456+
},
457+
})
458+
if err != nil {
459+
return err
460+
}
461+
}
462+
463+
return nil
464+
}
465+
466+
func pinLsAll(req *cmds.Request, typeStr string, n *core.IpfsNode, emit func(value interface{}) error) error {
467+
enc, err := cmdenv.GetCidEncoder(req)
468+
if err != nil {
469+
return err
470+
}
471+
472+
keys := cid.NewSet()
473+
474+
AddToResultKeys := func(keyList []cid.Cid, typeStr string) error {
475+
for _, c := range keyList {
476+
if keys.Visit(c) {
477+
err := emit(&PinLsOutputWrapper{
478+
PinLsObject: PinLsObject{
479+
Type: typeStr,
480+
Cid: enc.Encode(c),
481+
},
482+
})
483+
if err != nil {
484+
return err
485+
}
486+
}
487+
}
488+
return nil
489+
}
490+
491+
if typeStr == "direct" || typeStr == "all" {
492+
err := AddToResultKeys(n.Pinning.DirectKeys(), "direct")
493+
if err != nil {
494+
return err
495+
}
496+
}
497+
if typeStr == "recursive" || typeStr == "all" {
498+
err := AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive")
499+
if err != nil {
500+
return err
501+
}
502+
}
503+
if typeStr == "indirect" || typeStr == "all" {
504+
for _, k := range n.Pinning.RecursiveKeys() {
505+
var visitErr error
506+
err := dag.EnumerateChildren(req.Context, dag.GetLinksWithDAG(n.DAG), k, func(c cid.Cid) bool {
507+
r := keys.Visit(c)
508+
if r {
509+
err := emit(&PinLsOutputWrapper{
510+
PinLsObject: PinLsObject{
511+
Type: "indirect",
512+
Cid: enc.Encode(c),
513+
},
514+
})
515+
if err != nil {
516+
visitErr = err
517+
}
518+
}
519+
return r
520+
})
521+
522+
if visitErr != nil {
523+
return visitErr
524+
}
525+
if err != nil {
526+
return err
527+
}
528+
}
529+
}
530+
531+
return nil
532+
}
533+
380534
const (
381535
pinUnpinOptionName = "unpin"
382536
)
@@ -491,83 +645,6 @@ var verifyPinCmd = &cmds.Command{
491645
},
492646
}
493647

494-
type RefKeyObject struct {
495-
Type string
496-
}
497-
498-
type RefKeyList struct {
499-
Keys map[string]RefKeyObject
500-
}
501-
502-
func pinLsKeys(ctx context.Context, args []string, typeStr string, n *core.IpfsNode, api coreiface.CoreAPI) (map[cid.Cid]RefKeyObject, error) {
503-
504-
mode, ok := pin.StringToMode(typeStr)
505-
if !ok {
506-
return nil, fmt.Errorf("invalid pin mode '%s'", typeStr)
507-
}
508-
509-
keys := make(map[cid.Cid]RefKeyObject)
510-
511-
for _, p := range args {
512-
c, err := api.ResolvePath(ctx, path.New(p))
513-
if err != nil {
514-
return nil, err
515-
}
516-
517-
pinType, pinned, err := n.Pinning.IsPinnedWithType(c.Cid(), mode)
518-
if err != nil {
519-
return nil, err
520-
}
521-
522-
if !pinned {
523-
return nil, fmt.Errorf("path '%s' is not pinned", p)
524-
}
525-
526-
switch pinType {
527-
case "direct", "indirect", "recursive", "internal":
528-
default:
529-
pinType = "indirect through " + pinType
530-
}
531-
keys[c.Cid()] = RefKeyObject{
532-
Type: pinType,
533-
}
534-
}
535-
536-
return keys, nil
537-
}
538-
539-
func pinLsAll(ctx context.Context, typeStr string, n *core.IpfsNode) (map[cid.Cid]RefKeyObject, error) {
540-
541-
keys := make(map[cid.Cid]RefKeyObject)
542-
543-
AddToResultKeys := func(keyList []cid.Cid, typeStr string) {
544-
for _, c := range keyList {
545-
keys[c] = RefKeyObject{
546-
Type: typeStr,
547-
}
548-
}
549-
}
550-
551-
if typeStr == "direct" || typeStr == "all" {
552-
AddToResultKeys(n.Pinning.DirectKeys(), "direct")
553-
}
554-
if typeStr == "indirect" || typeStr == "all" {
555-
set := cid.NewSet()
556-
for _, k := range n.Pinning.RecursiveKeys() {
557-
err := dag.EnumerateChildren(ctx, dag.GetLinksWithDAG(n.DAG), k, set.Visit)
558-
if err != nil {
559-
return nil, err
560-
}
561-
}
562-
AddToResultKeys(set.Keys(), "indirect")
563-
}
564-
if typeStr == "recursive" || typeStr == "all" {
565-
AddToResultKeys(n.Pinning.RecursiveKeys(), "recursive")
566-
}
567-
568-
return keys, nil
569-
}
570-
571648
// PinVerifyRes is the result returned for each pin checked in "pin verify"
572649
type PinVerifyRes struct {
573650
Cid string

0 commit comments

Comments
 (0)