diff --git a/fs/rc/cache.go b/fs/rc/cache.go index 062a9ce3e..6ed5e848b 100644 --- a/fs/rc/cache.go +++ b/fs/rc/cache.go @@ -5,27 +5,71 @@ package rc import ( "context" "errors" + "fmt" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/cache" "github.com/rclone/rclone/fs/config/configmap" + "github.com/rclone/rclone/fs/filter" + "github.com/rclone/rclone/fs/fspath" ) -// GetFsNamed gets an fs.Fs named fsName either from the cache or creates it afresh -func GetFsNamed(ctx context.Context, in Params, fsName string) (f fs.Fs, err error) { - fsString, err := in.GetString(fsName) +// getFsName gets an fs name from fsName either from the cache or direct +func getFsName(in Params, fsName string) (fsString string, err error) { + fsString, err = in.GetString(fsName) if err != nil { if !IsErrParamInvalid(err) { - return nil, err + return fsString, err } fsString, err = getConfigMap(in, fsName) if err != nil { - return nil, err + return fsString, err } } + return fsString, err +} + +// GetFsNamed gets an fs.Fs named fsName either from the cache or creates it afresh +func GetFsNamed(ctx context.Context, in Params, fsName string) (f fs.Fs, err error) { + fsString, err := getFsName(in, fsName) + if err != nil { + return nil, err + } return cache.Get(ctx, fsString) } +// GetFsNamedFileOK gets an fs.Fs named fsName either from the cache or creates it afresh +// +// If the fs.Fs points to a single file then it returns a new ctx with +// filters applied to make the listings return only that file. +func GetFsNamedFileOK(ctx context.Context, in Params, fsName string) (newCtx context.Context, f fs.Fs, err error) { + fsString, err := getFsName(in, fsName) + if err != nil { + return ctx, nil, err + } + f, err = cache.Get(ctx, fsString) + if err == nil { + return ctx, f, nil + } else if !errors.Is(err, fs.ErrorIsFile) { + return ctx, nil, err + } + // f points to the directory above the file so find the remote name + _, fileName, err := fspath.Split(fsString) + if err != nil { + return ctx, f, err + } + ctx, fi := filter.AddConfig(ctx) + if !fi.InActive() { + return ctx, f, fmt.Errorf("can't limit to single files when using filters: %q", fileName) + } + // Limit transfers to this file + err = fi.AddFile(fileName) + if err != nil { + return ctx, f, fmt.Errorf("failed to limit to single file: %w", err) + } + return ctx, f, nil +} + // getConfigMap gets the config as a map from in and converts it to a // config string // diff --git a/fs/rc/cache_test.go b/fs/rc/cache_test.go index 6d7c7730e..0a351cee7 100644 --- a/fs/rc/cache_test.go +++ b/fs/rc/cache_test.go @@ -5,18 +5,26 @@ import ( "fmt" "testing" + "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/cache" + "github.com/rclone/rclone/fs/filter" "github.com/rclone/rclone/fstest/mockfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func mockNewFs(t *testing.T) func() { - f, err := mockfs.NewFs(context.Background(), "mock", "mock", nil) + ctx := context.Background() + f, err := mockfs.NewFs(ctx, "/", "", nil) require.NoError(t, err) cache.Put("/", f) + f, err = mockfs.NewFs(ctx, "mock", "/", nil) + require.NoError(t, err) cache.Put("mock:/", f) cache.Put(":mock:/", f) + f, err = mockfs.NewFs(ctx, "mock", "dir/file.txt", nil) + require.NoError(t, err) + cache.PutErr("mock:dir/file.txt", f, fs.ErrorIsFile) return func() { cache.Clear() } @@ -64,6 +72,40 @@ func TestGetFsNamedStruct(t *testing.T) { assert.NotNil(t, f) } +func TestGetFsNamedFileOK(t *testing.T) { + defer mockNewFs(t)() + ctx := context.Background() + + in := Params{ + "potato": "/", + } + newCtx, f, err := GetFsNamedFileOK(ctx, in, "potato") + require.NoError(t, err) + assert.NotNil(t, f) + assert.Equal(t, ctx, newCtx) + + in = Params{ + "sausage": "/", + } + newCtx, f, err = GetFsNamedFileOK(ctx, in, "potato") + require.Error(t, err) + assert.Nil(t, f) + assert.Equal(t, ctx, newCtx) + + in = Params{ + "potato": "mock:dir/file.txt", + } + newCtx, f, err = GetFsNamedFileOK(ctx, in, "potato") + assert.Nil(t, err) + assert.NotNil(t, f) + assert.NotEqual(t, ctx, newCtx) + + fi := filter.GetConfig(newCtx) + assert.False(t, fi.InActive()) + assert.True(t, fi.IncludeRemote("file.txt")) + assert.False(t, fi.IncludeRemote("other.txt")) +} + func TestGetConfigMap(t *testing.T) { for _, test := range []struct { in Params