diff --git a/options/pin.go b/options/pin.go index 6b211bb..5014a2d 100644 --- a/options/pin.go +++ b/options/pin.go @@ -1,31 +1,49 @@ package options +import "fmt" + +// PinAddSettings represent the settings for PinAPI.Add type PinAddSettings struct { Recursive bool } +// PinLsSettings represent the settings for PinAPI.Ls type PinLsSettings struct { Type string } -// PinRmSettings represents the settings of pin rm command +// PinIsPinnedSettings represent the settings for PinAPI.IsPinned +type PinIsPinnedSettings struct { + WithType string +} + +// PinRmSettings represents the settings for PinAPI.Rm type PinRmSettings struct { Recursive bool } +// PinUpdateSettings represent the settings for PinAPI.Update type PinUpdateSettings struct { Unpin bool } +// PinAddOption is the signature of an option for PinAPI.Add type PinAddOption func(*PinAddSettings) error -// PinRmOption pin rm option func +// PinLsOption is the signature of an option for PinAPI.Ls +type PinLsOption func(*PinLsSettings) error + +// PinIsPinnedOption is the signature of an option for PinAPI.IsPinned +type PinIsPinnedOption func(*PinIsPinnedSettings) error + +// PinRmOption is the signature of an option for PinAPI.Rm type PinRmOption func(*PinRmSettings) error -// PinLsOption pin ls option func -type PinLsOption func(*PinLsSettings) error +// PinUpdateOption is the signature of an option for PinAPI.Update type PinUpdateOption func(*PinUpdateSettings) error +// PinAddOptions compile a series of PinAddOption into a ready to use +// PinAddSettings and set the default values. func PinAddOptions(opts ...PinAddOption) (*PinAddSettings, error) { options := &PinAddSettings{ Recursive: true, @@ -41,14 +59,16 @@ func PinAddOptions(opts ...PinAddOption) (*PinAddSettings, error) { return options, nil } -// PinRmOptions pin rm options -func PinRmOptions(opts ...PinRmOption) (*PinRmSettings, error) { - options := &PinRmSettings{ - Recursive: true, +// PinLsOptions compile a series of PinLsOption into a ready to use +// PinLsSettings and set the default values. +func PinLsOptions(opts ...PinLsOption) (*PinLsSettings, error) { + options := &PinLsSettings{ + Type: "all", } for _, opt := range opts { - if err := opt(options); err != nil { + err := opt(options) + if err != nil { return nil, err } } @@ -56,9 +76,11 @@ func PinRmOptions(opts ...PinRmOption) (*PinRmSettings, error) { return options, nil } -func PinLsOptions(opts ...PinLsOption) (*PinLsSettings, error) { - options := &PinLsSettings{ - Type: "all", +// PinIsPinnedOptions compile a series of PinIsPinnedOption into a ready to use +// PinIsPinnedSettings and set the default values. +func PinIsPinnedOptions(opts ...PinIsPinnedOption) (*PinIsPinnedSettings, error) { + options := &PinIsPinnedSettings{ + WithType: "all", } for _, opt := range opts { @@ -71,6 +93,24 @@ func PinLsOptions(opts ...PinLsOption) (*PinLsSettings, error) { return options, nil } +// PinRmOptions compile a series of PinRmOption into a ready to use +// PinRmSettings and set the default values. +func PinRmOptions(opts ...PinRmOption) (*PinRmSettings, error) { + options := &PinRmSettings{ + Recursive: true, + } + + for _, opt := range opts { + if err := opt(options); err != nil { + return nil, err + } + } + + return options, nil +} + +// PinUpdateOptions compile a series of PinUpdateOption into a ready to use +// PinUpdateSettings and set the default values. func PinUpdateOptions(opts ...PinUpdateOption) (*PinUpdateSettings, error) { options := &PinUpdateSettings{ Unpin: true, @@ -86,36 +126,132 @@ func PinUpdateOptions(opts ...PinUpdateOption) (*PinUpdateSettings, error) { return options, nil } -type pinType struct{} - type pinOpts struct { - Type pinType + Ls pinLsOpts + IsPinned pinIsPinnedOpts } +// Pin provide an access to all the options for the Pin API. var Pin pinOpts +type pinLsOpts struct{} + // All is an option for Pin.Ls which will make it return all pins. It is // the default -func (pinType) All() PinLsOption { - return Pin.pinType("all") +func (pinLsOpts) All() PinLsOption { + return Pin.Ls.pinType("all") } // Recursive is an option for Pin.Ls which will make it only return recursive // pins -func (pinType) Recursive() PinLsOption { - return Pin.pinType("recursive") +func (pinLsOpts) Recursive() PinLsOption { + return Pin.Ls.pinType("recursive") } // Direct is an option for Pin.Ls which will make it only return direct (non // recursive) pins -func (pinType) Direct() PinLsOption { - return Pin.pinType("direct") +func (pinLsOpts) Direct() PinLsOption { + return Pin.Ls.pinType("direct") } // Indirect is an option for Pin.Ls which will make it only return indirect pins // (objects referenced by other recursively pinned objects) -func (pinType) Indirect() PinLsOption { - return Pin.pinType("indirect") +func (pinLsOpts) Indirect() PinLsOption { + return Pin.Ls.pinType("indirect") +} + +// Type is an option for Pin.Ls which will make it only return pins of the given +// type. +// +// Supported values: +// * "direct" - directly pinned objects +// * "recursive" - roots of recursive pins +// * "indirect" - indirectly pinned objects (referenced by recursively pinned +// objects) +// * "all" - all pinned objects (default) +func (pinLsOpts) Type(typeStr string) (PinLsOption, error) { + switch typeStr { + case "all", "direct", "indirect", "recursive": + return Pin.Ls.pinType(typeStr), nil + default: + return nil, fmt.Errorf("invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr) + } +} + +// pinType is an option for Pin.Ls which allows to specify which pin types should +// be returned +// +// Supported values: +// * "direct" - directly pinned objects +// * "recursive" - roots of recursive pins +// * "indirect" - indirectly pinned objects (referenced by recursively pinned +// objects) +// * "all" - all pinned objects (default) +func (pinLsOpts) pinType(t string) PinLsOption { + return func(settings *PinLsSettings) error { + settings.Type = t + return nil + } +} + +type pinIsPinnedOpts struct{} + +// All is an option for Pin.IsPinned which will make it search in all type of pins. +// It is the default +func (pinIsPinnedOpts) All() PinIsPinnedOption { + return Pin.IsPinned.pinType("all") +} + +// Recursive is an option for Pin.IsPinned which will make it only search in +// recursive pins +func (pinIsPinnedOpts) Recursive() PinIsPinnedOption { + return Pin.IsPinned.pinType("recursive") +} + +// Direct is an option for Pin.IsPinned which will make it only search in direct +// (non recursive) pins +func (pinIsPinnedOpts) Direct() PinIsPinnedOption { + return Pin.IsPinned.pinType("direct") +} + +// Indirect is an option for Pin.IsPinned which will make it only search indirect +// pins (objects referenced by other recursively pinned objects) +func (pinIsPinnedOpts) Indirect() PinIsPinnedOption { + return Pin.IsPinned.pinType("indirect") +} + +// Type is an option for Pin.IsPinned which will make it only search pins of the given +// type. +// +// Supported values: +// * "direct" - directly pinned objects +// * "recursive" - roots of recursive pins +// * "indirect" - indirectly pinned objects (referenced by recursively pinned +// objects) +// * "all" - all pinned objects (default) +func (pinIsPinnedOpts) Type(typeStr string) (PinIsPinnedOption, error) { + switch typeStr { + case "all", "direct", "indirect", "recursive": + return Pin.IsPinned.pinType(typeStr), nil + default: + return nil, fmt.Errorf("invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr) + } +} + +// pinType is an option for Pin.IsPinned which allows to specify which pin type the given +// pin is expected to be, speeding up the research. +// +// Supported values: +// * "direct" - directly pinned objects +// * "recursive" - roots of recursive pins +// * "indirect" - indirectly pinned objects (referenced by recursively pinned +// objects) +// * "all" - all pinned objects (default) +func (pinIsPinnedOpts) pinType(t string) PinIsPinnedOption { + return func(settings *PinIsPinnedSettings) error { + settings.WithType = t + return nil + } } // Recursive is an option for Pin.Add which specifies whether to pin an entire @@ -137,22 +273,6 @@ func (pinOpts) RmRecursive(recursive bool) PinRmOption { } } -// Type is an option for Pin.Ls which allows to specify which pin types should -// be returned -// -// Supported values: -// * "direct" - directly pinned objects -// * "recursive" - roots of recursive pins -// * "indirect" - indirectly pinned objects (referenced by recursively pinned -// objects) -// * "all" - all pinned objects (default) -func (pinOpts) pinType(t string) PinLsOption { - return func(settings *PinLsSettings) error { - settings.Type = t - return nil - } -} - // Unpin is an option for Pin.Update which specifies whether to remove the old pin. // Default is true. func (pinOpts) Unpin(unpin bool) PinUpdateOption { diff --git a/pin.go b/pin.go index 27f9355..4c1788c 100644 --- a/pin.go +++ b/pin.go @@ -46,6 +46,10 @@ type PinAPI interface { // Ls returns list of pinned objects on this node Ls(context.Context, ...options.PinLsOption) (<-chan Pin, error) + // IsPinned returns whether or not the given cid is pinned + // and an explanation of why its pinned + IsPinned(context.Context, path.Path, ...options.PinIsPinnedOption) (string, bool, error) + // Rm removes pin for object specified by the path Rm(context.Context, path.Path, ...options.PinRmOption) error diff --git a/tests/pin.go b/tests/pin.go index 58e8120..476bbea 100644 --- a/tests/pin.go +++ b/tests/pin.go @@ -28,6 +28,7 @@ func (tp *TestSuite) TestPin(t *testing.T) { t.Run("TestPinRecursive", tp.TestPinRecursive) t.Run("TestPinLsIndirect", tp.TestPinLsIndirect) t.Run("TestPinLsPrecedence", tp.TestPinLsPrecedence) + t.Run("TestPinIsPinned", tp.TestPinIsPinned) } func (tp *TestSuite) TestPinAdd(t *testing.T) { @@ -84,6 +85,8 @@ func (tp *TestSuite) TestPinSimple(t *testing.T) { t.Error("unexpected pin type") } + assertIsPinned(t, ctx, api, p, "recursive") + err = api.Pin().Rm(ctx, p) if err != nil { t.Fatal(err) @@ -150,7 +153,7 @@ func (tp *TestSuite) TestPinRecursive(t *testing.T) { t.Errorf("unexpected pin list len: %d", len(list)) } - list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Type.Direct())) + list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Direct())) if err != nil { t.Fatal(err) } @@ -163,7 +166,7 @@ func (tp *TestSuite) TestPinRecursive(t *testing.T) { t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.IpfsPath(nd3.Cid()).String()) } - list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Type.Recursive())) + list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Recursive())) if err != nil { t.Fatal(err) } @@ -176,7 +179,7 @@ func (tp *TestSuite) TestPinRecursive(t *testing.T) { t.Errorf("unexpected path, %s != %s", list[0].Path().String(), path.IpldPath(nd2.Cid()).String()) } - list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Type.Indirect())) + list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Indirect())) if err != nil { t.Fatal(err) } @@ -360,6 +363,39 @@ func (tp *TestSuite) TestPinLsPrecedenceRecursiveDirect(t *testing.T) { assertPinTypes(t, ctx, api, []cidContainer{grandparent, parent}, []cidContainer{}, []cidContainer{leaf}) } +func (tp *TestSuite) TestPinIsPinned(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + api, err := tp.makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + leaf, parent, grandparent := getThreeChainedNodes(t, ctx, api, "foofoo") + + assertNotPinned(t, ctx, api, path.IpldPath(grandparent.Cid())) + assertNotPinned(t, ctx, api, path.IpldPath(parent.Cid())) + assertNotPinned(t, ctx, api, path.IpldPath(leaf.Cid())) + + err = api.Pin().Add(ctx, path.IpldPath(parent.Cid()), opt.Pin.Recursive(true)) + if err != nil { + t.Fatal(err) + } + + assertNotPinned(t, ctx, api, path.IpldPath(grandparent.Cid())) + assertIsPinned(t, ctx, api, path.IpldPath(parent.Cid()), "recursive") + assertIsPinned(t, ctx, api, path.IpldPath(leaf.Cid()), "indirect") + + err = api.Pin().Add(ctx, path.IpldPath(grandparent.Cid()), opt.Pin.Recursive(false)) + if err != nil { + t.Fatal(err) + } + + assertIsPinned(t, ctx, api, path.IpldPath(grandparent.Cid()), "direct") + assertIsPinned(t, ctx, api, path.IpldPath(parent.Cid()), "recursive") + assertIsPinned(t, ctx, api, path.IpldPath(leaf.Cid()), "indirect") +} + type cidContainer interface { Cid() cid.Cid } @@ -390,21 +426,21 @@ func getThreeChainedNodes(t *testing.T, ctx context.Context, api iface.CoreAPI, func assertPinTypes(t *testing.T, ctx context.Context, api iface.CoreAPI, recusive, direct, indirect []cidContainer) { assertPinLsAllConsistency(t, ctx, api) - list, err := accPins(api.Pin().Ls(ctx, opt.Pin.Type.Recursive())) + list, err := accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Recursive())) if err != nil { t.Fatal(err) } assertPinCids(t, list, recusive...) - list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Type.Direct())) + list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Direct())) if err != nil { t.Fatal(err) } assertPinCids(t, list, direct...) - list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Type.Indirect())) + list, err = accPins(api.Pin().Ls(ctx, opt.Pin.Ls.Indirect())) if err != nil { t.Fatal(err) } @@ -466,9 +502,9 @@ func assertPinLsAllConsistency(t *testing.T, ctx context.Context, api iface.Core all, recursive, direct, indirect := cid.NewSet(), cid.NewSet(), cid.NewSet(), cid.NewSet() typeMap := map[string]*pinTypeProps{ - "recursive": {recursive, opt.Pin.Type.Recursive()}, - "direct": {direct, opt.Pin.Type.Direct()}, - "indirect": {indirect, opt.Pin.Type.Indirect()}, + "recursive": {recursive, opt.Pin.Ls.Recursive()}, + "direct": {direct, opt.Pin.Ls.Direct()}, + "indirect": {indirect, opt.Pin.Ls.Indirect()}, } for _, p := range allPins { @@ -506,6 +542,47 @@ func assertPinLsAllConsistency(t *testing.T, ctx context.Context, api iface.Core } } +func assertIsPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path, typeStr string) { + t.Helper() + withType, err := opt.Pin.IsPinned.Type(typeStr) + if err != nil { + t.Fatal("unhandled pin type") + } + + whyPinned, pinned, err := api.Pin().IsPinned(ctx, p, withType) + if err != nil { + t.Fatal(err) + } + + if !pinned { + t.Fatalf("%s expected to be pinned with type %s", p, typeStr) + } + + switch typeStr { + case "recursive", "direct": + if typeStr != whyPinned { + t.Fatalf("reason for pinning expected to be %s for %s, got %s", typeStr, p, whyPinned) + } + case "indirect": + if whyPinned == "" { + t.Fatalf("expected to have a pin reason for %s", p) + } + } +} + +func assertNotPinned(t *testing.T, ctx context.Context, api iface.CoreAPI, p path.Path) { + t.Helper() + + _, pinned, err := api.Pin().IsPinned(ctx, p) + if err != nil { + t.Fatal(err) + } + + if pinned { + t.Fatalf("%s expected to not be pinned", p) + } +} + func accPins(pins <-chan iface.Pin, err error) ([]iface.Pin, error) { if err != nil { return nil, err