mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 16:34:30 +01:00
copyurl: add --stdout flag to write to stdout
This commit is contained in:
parent
0b7f959433
commit
422ad38e5b
@ -2,6 +2,8 @@ package copyurl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/rclone/rclone/cmd"
|
"github.com/rclone/rclone/cmd"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
@ -12,37 +14,55 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
autoFilename = false
|
autoFilename = false
|
||||||
|
stdout = 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, &stdout, "stdout", "", stdout, "Write the output to stdout rather than a file")
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandDefinition = &cobra.Command{
|
var commandDefinition = &cobra.Command{
|
||||||
Use: "copyurl https://example.com dest:path",
|
Use: "copyurl https://example.com dest:path",
|
||||||
Short: `Copy url content to dest.`,
|
Short: `Copy url content to dest.`,
|
||||||
Long: `
|
Long: `
|
||||||
Download urls content and copy it to destination
|
Download a URL's content and copy it to the destination without saving
|
||||||
without saving it in tmp storage.
|
it in temporary storage.
|
||||||
|
|
||||||
Setting --auto-filename flag will cause retrieving file name from url and using it in destination path.
|
Setting --auto-filename will cause the file name to be retreived from
|
||||||
|
the from URL (after any redirections) and used in the destination
|
||||||
|
path.
|
||||||
|
|
||||||
|
Setting --stdout or making the output file name "-" will cause the
|
||||||
|
output to be written to standard output.
|
||||||
`,
|
`,
|
||||||
Run: func(command *cobra.Command, args []string) {
|
RunE: func(command *cobra.Command, args []string) (err error) {
|
||||||
cmd.CheckArgs(2, 2, command, args)
|
cmd.CheckArgs(1, 2, command, args)
|
||||||
|
|
||||||
var dstFileName string
|
var dstFileName string
|
||||||
var fsdst fs.Fs
|
var fsdst fs.Fs
|
||||||
if autoFilename {
|
if !stdout {
|
||||||
fsdst = cmd.NewFsDir(args[1:])
|
if len(args) < 2 {
|
||||||
} else {
|
return errors.New("need 2 arguments if not using --stdout")
|
||||||
fsdst, dstFileName = cmd.NewFsDstFile(args[1:])
|
}
|
||||||
|
if args[1] == "-" {
|
||||||
|
stdout = true
|
||||||
|
} else if autoFilename {
|
||||||
|
fsdst = cmd.NewFsDir(args[1:])
|
||||||
|
} else {
|
||||||
|
fsdst, dstFileName = cmd.NewFsDstFile(args[1:])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Run(true, true, command, func() error {
|
cmd.Run(true, true, command, func() error {
|
||||||
_, err := operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename)
|
if stdout {
|
||||||
|
err = operations.CopyURLToWriter(context.Background(), args[0], os.Stdout)
|
||||||
|
} else {
|
||||||
|
_, err = operations.CopyURL(context.Background(), fsdst, dstFileName, args[0], autoFilename)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@ -1616,26 +1617,48 @@ func RcatSize(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadClo
|
|||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyURL copies the data from the url to (fdst, dstFileName)
|
// copyURLFunc is called from CopyURLFn
|
||||||
func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, dstFileNameFromURL bool) (dst fs.Object, err error) {
|
type copyURLFunc func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error)
|
||||||
|
|
||||||
|
// copyURLFn copies the data from the url to the function supplied
|
||||||
|
func copyURLFn(ctx context.Context, dstFileName string, url string, dstFileNameFromURL bool, fn copyURLFunc) (err error) {
|
||||||
client := fshttp.NewClient(fs.Config)
|
client := fshttp.NewClient(fs.Config)
|
||||||
resp, err := client.Get(url)
|
resp, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer fs.CheckClose(resp.Body, &err)
|
defer fs.CheckClose(resp.Body, &err)
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
return nil, errors.Errorf("CopyURL failed: %s", resp.Status)
|
return errors.Errorf("CopyURL failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
modTime, err := http.ParseTime(resp.Header.Get("Last-Modified"))
|
||||||
|
if err != nil {
|
||||||
|
modTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
if dstFileNameFromURL {
|
if dstFileNameFromURL {
|
||||||
dstFileName = path.Base(resp.Request.URL.Path)
|
dstFileName = path.Base(resp.Request.URL.Path)
|
||||||
if dstFileName == "." || dstFileName == "/" {
|
if dstFileName == "." || dstFileName == "/" {
|
||||||
return nil, errors.Errorf("CopyURL failed: file name wasn't found in url")
|
return errors.Errorf("CopyURL failed: file name wasn't found in url")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return fn(ctx, dstFileName, resp.Body, resp.ContentLength, modTime)
|
||||||
|
}
|
||||||
|
|
||||||
return RcatSize(ctx, fdst, dstFileName, resp.Body, resp.ContentLength, time.Now())
|
// 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) {
|
||||||
|
err = copyURLFn(ctx, dstFileName, url, dstFileNameFromURL, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
|
||||||
|
dst, err = RcatSize(ctx, fdst, dstFileName, in, size, modTime)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return dst, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyURLToWriter copies the data from the url to the io.Writer supplied
|
||||||
|
func CopyURLToWriter(ctx context.Context, url string, out io.Writer) (err error) {
|
||||||
|
return copyURLFn(ctx, "", url, false, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) {
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
return err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackupDir returns the correctly configured --backup-dir
|
// BackupDir returns the correctly configured --backup-dir
|
||||||
|
@ -665,6 +665,37 @@ func TestCopyURL(t *testing.T) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCopyURLToWriter(t *testing.T) {
|
||||||
|
contents := "file contents\n"
|
||||||
|
|
||||||
|
// check when reading from regular HTTP server
|
||||||
|
status := 0
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if status != 0 {
|
||||||
|
http.Error(w, "an error ocurred", status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := w.Write([]byte(contents))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
ts := httptest.NewServer(handler)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// test normal fetch
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := operations.CopyURLToWriter(context.Background(), ts.URL, &buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, contents, buf.String())
|
||||||
|
|
||||||
|
// test fetch with error
|
||||||
|
status = http.StatusNotFound
|
||||||
|
buf.Reset()
|
||||||
|
err = operations.CopyURLToWriter(context.Background(), ts.URL, &buf)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "Not Found")
|
||||||
|
assert.Equal(t, 0, len(buf.String()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestMoveFile(t *testing.T) {
|
func TestMoveFile(t *testing.T) {
|
||||||
r := fstest.NewRun(t)
|
r := fstest.NewRun(t)
|
||||||
defer r.Finalise()
|
defer r.Finalise()
|
||||||
|
Loading…
Reference in New Issue
Block a user