copyurl: add no-clobber flag - fixes #3950

This commit is contained in:
Denis 2020-04-19 14:40:17 +03:00 committed by GitHub
parent 9d4b3580a5
commit 31a1cc46b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 43 additions and 8 deletions

View file

@ -15,12 +15,14 @@ import (
var ( var (
autoFilename = false autoFilename = false
stdout = false stdout = false
noClobber = false
) )
func init() { func init() {
cmd.Root.AddCommand(commandDefinition) cmd.Root.AddCommand(commandDefinition)
cmdFlags := commandDefinition.Flags() cmdFlags := commandDefinition.Flags()
flags.BoolVarP(cmdFlags, &autoFilename, "auto-filename", "a", autoFilename, "Get the file name from the URL and use it for destination file path") flags.BoolVarP(cmdFlags, &autoFilename, "auto-filename", "a", autoFilename, "Get the file name from the URL and use it for destination file path")
flags.BoolVarP(cmdFlags, &noClobber, "no-clobber", "", noClobber, "Prevent overwriting file with same name")
flags.BoolVarP(cmdFlags, &stdout, "stdout", "", stdout, "Write the output to stdout rather than a file") flags.BoolVarP(cmdFlags, &stdout, "stdout", "", stdout, "Write the output to stdout rather than a file")
} }
@ -35,6 +37,9 @@ Setting --auto-filename will cause the file name to be retreived from
the from URL (after any redirections) and used in the destination the from URL (after any redirections) and used in the destination
path. path.
Setting --no-clobber will prevent overwriting file on the
destination if there is one with the same name.
Setting --stdout or making the output file name "-" will cause the Setting --stdout or making the output file name "-" will cause the
output to be written to standard output. output to be written to standard output.
`, `,
@ -59,7 +64,7 @@ output to be written to standard output.
if stdout { if stdout {
err = operations.CopyURLToWriter(context.Background(), args[0], os.Stdout) err = operations.CopyURLToWriter(context.Background(), args[0], os.Stdout)
} else { } else {
_, err = operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename) _, err = operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename, noClobber)
} }
return err return err
}) })

View file

@ -19,6 +19,9 @@ Setting --auto-filename will cause the file name to be retreived from
the from URL (after any redirections) and used in the destination the from URL (after any redirections) and used in the destination
path. path.
Setting --no-clobber will prevent overwriting file on the
destination if there is one with the same name.
Setting --stdout or making the output file name "-" will cause the Setting --stdout or making the output file name "-" will cause the
output to be written to standard output. output to be written to standard output.
@ -31,6 +34,7 @@ rclone copyurl https://example.com dest:path [flags]
``` ```
-a, --auto-filename Get the file name from the URL and use it for destination file path -a, --auto-filename Get the file name from the URL and use it for destination file path
--no-clobber Prevent file overwriting on destination
-h, --help help for copyurl -h, --help help for copyurl
--stdout Write the output to stdout rather than a file --stdout Write the output to stdout rather than a file
``` ```

View file

@ -1718,8 +1718,15 @@ func copyURLFn(ctx context.Context, dstFileName string, url string, dstFileNameF
} }
// CopyURL copies the data from the url to (fdst, dstFileName) // CopyURL copies the data from the url to (fdst, dstFileName)
func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, dstFileNameFromURL bool) (dst fs.Object, err error) { func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, dstFileNameFromURL bool, noClobber bool) (dst fs.Object, err error) {
err = copyURLFn(ctx, dstFileName, url, dstFileNameFromURL, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) { err = copyURLFn(ctx, dstFileName, url, dstFileNameFromURL, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
if noClobber {
_, err = fdst.NewObject(ctx, dstFileName)
if err == nil {
return errors.New("CopyURL failed: file already exist")
}
}
dst, err = RcatSize(ctx, fdst, dstFileName, in, size, modTime) dst, err = RcatSize(ctx, fdst, dstFileName, in, size, modTime)
return err return err
}) })

View file

@ -663,27 +663,31 @@ func TestCopyURL(t *testing.T) {
ts := httptest.NewServer(handler) ts := httptest.NewServer(handler)
defer ts.Close() defer ts.Close()
o, err := operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, false) o, err := operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, false, false)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size()) assert.Equal(t, int64(len(contents)), o.Size())
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, nil, fs.ModTimeNotSupported) fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, nil, fs.ModTimeNotSupported)
// Check file clobbering
o, err = operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, false, true)
require.Error(t, err)
// Check auto file naming // Check auto file naming
status = 0 status = 0
urlFileName := "filename.txt" urlFileName := "filename.txt"
o, err = operations.CopyURL(context.Background(), r.Fremote, "", ts.URL+"/"+urlFileName, true) o, err = operations.CopyURL(context.Background(), r.Fremote, "", ts.URL+"/"+urlFileName, true, false)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size()) assert.Equal(t, int64(len(contents)), o.Size())
assert.Equal(t, urlFileName, o.Remote()) assert.Equal(t, urlFileName, o.Remote())
// Check auto file naming when url without file name // Check auto file naming when url without file name
o, err = operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, true) o, err = operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, true, false)
require.Error(t, err) require.Error(t, err)
// Check an error is returned for a 404 // Check an error is returned for a 404
status = http.StatusNotFound status = http.StatusNotFound
o, err = operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, false) o, err = operations.CopyURL(context.Background(), r.Fremote, "file1", ts.URL, false, false)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), "Not Found") assert.Contains(t, err.Error(), "Not Found")
assert.Nil(t, o) assert.Nil(t, o)
@ -699,7 +703,7 @@ func TestCopyURL(t *testing.T) {
tss := httptest.NewTLSServer(handler) tss := httptest.NewTLSServer(handler)
defer tss.Close() defer tss.Close()
o, err = operations.CopyURL(context.Background(), r.Fremote, "file2", tss.URL, false) o, err = operations.CopyURL(context.Background(), r.Fremote, "file2", tss.URL, false, false)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int64(len(contents)), o.Size()) assert.Equal(t, int64(len(contents)), o.Size())
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file2, fstest.NewItem(urlFileName, contents, t1)}, nil, fs.ModTimeNotSupported) fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file2, fstest.NewItem(urlFileName, contents, t1)}, nil, fs.ModTimeNotSupported)

View file

@ -215,8 +215,9 @@ func rcSingleCommand(ctx context.Context, in rc.Params, name string, noRemote bo
return nil, err return nil, err
} }
autoFilename, _ := in.GetBool("autoFilename") autoFilename, _ := in.GetBool("autoFilename")
noClobber, _ := in.GetBool("noClobber")
_, err = CopyURL(ctx, f, remote, url, autoFilename) _, err = CopyURL(ctx, f, remote, url, autoFilename, noClobber)
return nil, err return nil, err
case "cleanup": case "cleanup":
return nil, CleanUp(ctx, f) return nil, CleanUp(ctx, f)

View file

@ -109,17 +109,30 @@ func TestRcCopyurl(t *testing.T) {
"remote": "file1", "remote": "file1",
"url": ts.URL, "url": ts.URL,
"autoFilename": false, "autoFilename": false,
"noClobber": false,
} }
out, err := call.Fn(context.Background(), in) out, err := call.Fn(context.Background(), in)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, rc.Params(nil), out) assert.Equal(t, rc.Params(nil), out)
in = rc.Params{
"fs": r.FremoteName,
"remote": "file1",
"url": ts.URL,
"autoFilename": false,
"noClobber": true,
}
out, err = call.Fn(context.Background(), in)
require.Error(t, err)
assert.Equal(t, rc.Params(nil), out)
urlFileName := "filename.txt" urlFileName := "filename.txt"
in = rc.Params{ in = rc.Params{
"fs": r.FremoteName, "fs": r.FremoteName,
"remote": "", "remote": "",
"url": ts.URL + "/" + urlFileName, "url": ts.URL + "/" + urlFileName,
"autoFilename": true, "autoFilename": true,
"noClobber": false,
} }
out, err = call.Fn(context.Background(), in) out, err = call.Fn(context.Background(), in)
require.NoError(t, err) require.NoError(t, err)
@ -130,6 +143,7 @@ func TestRcCopyurl(t *testing.T) {
"remote": "", "remote": "",
"url": ts.URL, "url": ts.URL,
"autoFilename": true, "autoFilename": true,
"noClobber": false,
} }
out, err = call.Fn(context.Background(), in) out, err = call.Fn(context.Background(), in)
require.Error(t, err) require.Error(t, err)