2018-03-05 12:44:16 +01:00
|
|
|
package rc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2018-05-29 11:45:59 +02:00
|
|
|
"io/ioutil"
|
2018-03-05 12:44:16 +01:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ncw/rclone/cmd"
|
|
|
|
"github.com/ncw/rclone/fs"
|
|
|
|
"github.com/ncw/rclone/fs/fshttp"
|
|
|
|
"github.com/ncw/rclone/fs/rc"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/spf13/cobra"
|
2018-04-26 17:59:10 +02:00
|
|
|
"github.com/spf13/pflag"
|
2018-03-05 12:44:16 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
noOutput = false
|
|
|
|
url = "http://localhost:5572/"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
cmd.Root.AddCommand(commandDefintion)
|
|
|
|
commandDefintion.Flags().BoolVarP(&noOutput, "no-output", "", noOutput, "If set don't output the JSON result.")
|
|
|
|
commandDefintion.Flags().StringVarP(&url, "url", "", url, "URL to connect to rclone remote control.")
|
|
|
|
}
|
|
|
|
|
|
|
|
var commandDefintion = &cobra.Command{
|
|
|
|
Use: "rc commands parameter",
|
|
|
|
Short: `Run a command against a running rclone.`,
|
|
|
|
Long: `
|
|
|
|
This runs a command against a running rclone. By default it will use
|
|
|
|
that specified in the --rc-addr command.
|
|
|
|
|
|
|
|
Arguments should be passed in as parameter=value.
|
|
|
|
|
|
|
|
The result will be returned as a JSON object by default.
|
|
|
|
|
2018-05-29 11:45:59 +02:00
|
|
|
Use "rclone rc" to see a list of all possible commands.`,
|
2018-03-05 12:44:16 +01:00
|
|
|
Run: func(command *cobra.Command, args []string) {
|
|
|
|
cmd.CheckArgs(0, 1E9, command, args)
|
|
|
|
cmd.Run(false, false, command, func() error {
|
|
|
|
if len(args) == 0 {
|
|
|
|
return list()
|
|
|
|
}
|
|
|
|
return run(args)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// do a call from (path, in) to (out, err).
|
|
|
|
//
|
|
|
|
// if err is set, out may be a valid error return or it may be nil
|
|
|
|
func doCall(path string, in rc.Params) (out rc.Params, err error) {
|
|
|
|
// Do HTTP request
|
|
|
|
client := fshttp.NewClient(fs.Config)
|
|
|
|
url := url
|
2018-04-26 17:59:10 +02:00
|
|
|
// set the user use --rc-addr as well as --url
|
|
|
|
if rcAddrFlag := pflag.Lookup("rc-addr"); rcAddrFlag != nil && rcAddrFlag.Changed {
|
|
|
|
url = rcAddrFlag.Value.String()
|
|
|
|
if strings.HasPrefix(url, ":") {
|
|
|
|
url = "localhost" + url
|
|
|
|
}
|
|
|
|
url = "http://" + url + "/"
|
|
|
|
}
|
2018-03-05 12:44:16 +01:00
|
|
|
if !strings.HasSuffix(url, "/") {
|
|
|
|
url += "/"
|
|
|
|
}
|
|
|
|
url += path
|
|
|
|
data, err := json.Marshal(in)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to encode JSON")
|
|
|
|
}
|
|
|
|
resp, err := client.Post(url, "application/json", bytes.NewBuffer(data))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "connection failed")
|
|
|
|
}
|
|
|
|
defer fs.CheckClose(resp.Body, &err)
|
|
|
|
|
2018-05-29 11:45:59 +02:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
var body []byte
|
|
|
|
body, err = ioutil.ReadAll(resp.Body)
|
|
|
|
var bodyString string
|
|
|
|
if err == nil {
|
|
|
|
bodyString = string(body)
|
|
|
|
} else {
|
|
|
|
bodyString = err.Error()
|
|
|
|
}
|
|
|
|
bodyString = strings.TrimSpace(bodyString)
|
|
|
|
return nil, errors.Errorf("Failed to read rc response: %s: %s", resp.Status, bodyString)
|
|
|
|
}
|
|
|
|
|
2018-03-05 12:44:16 +01:00
|
|
|
// Parse output
|
|
|
|
out = make(rc.Params)
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&out)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to decode JSON")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check we got 200 OK
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
err = errors.Errorf("operation %q failed: %v", path, out["error"])
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the remote control command passed in
|
|
|
|
func run(args []string) (err error) {
|
|
|
|
path := strings.Trim(args[0], "/")
|
|
|
|
|
|
|
|
// parse input
|
|
|
|
in := make(rc.Params)
|
|
|
|
for _, param := range args[1:] {
|
|
|
|
equals := strings.IndexRune(param, '=')
|
|
|
|
if equals < 0 {
|
|
|
|
return errors.Errorf("No '=' found in parameter %q", param)
|
|
|
|
}
|
|
|
|
key, value := param[:equals], param[equals+1:]
|
|
|
|
in[key] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do the call
|
|
|
|
out, callErr := doCall(path, in)
|
|
|
|
|
|
|
|
// Write the JSON blob to stdout if required
|
|
|
|
if out != nil && !noOutput {
|
2018-03-14 20:48:01 +01:00
|
|
|
err := rc.WriteJSON(os.Stdout, out)
|
2018-03-05 12:44:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to output JSON")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return callErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// List the available commands to stdout
|
|
|
|
func list() error {
|
|
|
|
list, err := doCall("rc/list", nil)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to list")
|
|
|
|
}
|
|
|
|
commands, ok := list["commands"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
return errors.New("bad JSON")
|
|
|
|
}
|
|
|
|
for _, command := range commands {
|
|
|
|
info, ok := command.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return errors.New("bad JSON")
|
|
|
|
}
|
|
|
|
fmt.Printf("### %s: %s\n\n", info["Path"], info["Title"])
|
|
|
|
fmt.Printf("%s\n\n", info["Help"])
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|