Commit 6f388f6e authored by ale's avatar ale
Browse files

refactor radioctl command line interface

parent 541150ec
......@@ -5,66 +5,39 @@ import (
"fmt"
"hash/crc32"
"log"
"os"
"path/filepath"
"net/url"
"os"
"strings"
"git.autistici.org/ale/autoradio"
"github.com/gonuts/commander"
gonutsflag "github.com/gonuts/flag"
)
// Describes the syntax of a command.
type Command struct {
Name string
UsageStr string
Help string
Fn func([]string)
MinArgs int
MaxArgs int
type HasAddFlags interface {
AddFlags(*gonutsflag.FlagSet)
}
func (c Command) PrintHelp() {
fmt.Fprintf(os.Stderr, "%s %s\n %s\n\n", c.Name, c.UsageStr, c.Help)
type CommandInterface interface {
Run([]string)
Command() *commander.Command
}
func (c Command) Run(args []string) {
c.Fn(args)
type BaseCommand struct {
UsageLine string
Short string
Long string
}
// List of all known commands.
type CommandList []Command
func (cl CommandList) PrintHelp() {
fmt.Fprintf(os.Stderr, "Usage: %s <COMMAND> [<ARGS>...]\n", filepath.Base(os.Args[0]))
fmt.Fprintf(os.Stderr, "Known commands:\n\n")
for _, cmd := range cl {
cmd.PrintHelp()
func (b *BaseCommand) Command() *commander.Command {
return &commander.Command{
UsageLine: b.UsageLine,
Short: b.Short,
Long: b.Long,
}
}
func Run(cl CommandList) {
flag.Parse()
args := flag.Args()
if len(args) < 1 {
cl.PrintHelp()
return
}
cmdArg := args[0]
args = args[1:]
for _, cmd := range cl {
if cmd.Name == cmdArg {
if (cmd.MinArgs > 0 && len(args) < cmd.MinArgs) || (cmd.MaxArgs >= 0 && cmd.MaxArgs >= cmd.MinArgs && len(args) > cmd.MaxArgs) {
fmt.Fprintf(os.Stderr, "Wrong number of arguments!\n")
cmd.PrintHelp()
} else {
cmd.Run(args)
}
return
}
}
log.Fatalf("Unknown command '%s'", cmdArg)
func (b *BaseCommand) Run(args []string) {
}
func getClient() *autoradio.RadioAPI {
......@@ -72,42 +45,123 @@ func getClient() *autoradio.RadioAPI {
return autoradio.NewRadioAPI(etc)
}
func deleteMount(args []string) {
if err := getClient().DelMount(args[0]); err != nil {
log.Fatal(err)
}
func generateUsername(path string) string {
return fmt.Sprintf("source%d", crc32.ChecksumIEEE([]byte(path)))
}
func listMounts(args []string) {
mounts, err := getClient().ListMounts()
if err != nil {
log.Fatal(err)
func setRelay(m *autoradio.Mount, relayUrl string) {
if relayUrl == "" {
// Randomly generate source credentials.
m.Username = generateUsername(m.Name)
m.Password = autoradio.GeneratePassword()
} else {
// Validate the given relay URL.
u, err := url.Parse(relayUrl)
if err != nil {
log.Fatal(err)
}
m.RelayUrl = u.String()
m.Username = ""
m.Password = ""
}
for _, m := range mounts {
fmt.Printf("%s\n", m.Name)
}
// Edit a mountpoint.
type editMountCommand struct {
BaseCommand
relay string
fallback string
}
var UNSET = "UNSET"
func newEditMountCommand() *editMountCommand {
return &editMountCommand{
BaseCommand: BaseCommand{
UsageLine: "edit-mount <path>",
Short: "Edit an existing mountpoint",
Long: `
Modify parameters of the specified mountpoint, such as the relay
and the fallback URL. If the relay option is set, the mountpoint
will not accept source connections anymore. To revert to the
default, non-relay behavior, set the relay to the empty string
(with --relay="").
`,
},
}
}
func showMount(args []string) {
mount, err := getClient().GetMount(args[0])
func (cmd *editMountCommand) AddFlags(f *gonutsflag.FlagSet) {
// Note that we use special values to figure out whether a
// flag has been specified or not. There might be better way
// to do this.
f.StringVar(&cmd.relay, "relay", UNSET, "Upstream URL to relay")
f.StringVar(&cmd.fallback, "fallback", UNSET, "Fallback stream URL")
}
func (cmd *editMountCommand) Run(args []string) {
if len(args) != 1 {
log.Fatal("Wrong number of arguments")
}
client := getClient()
mount, err := client.GetMount(args[0])
if err != nil {
log.Fatal(err)
}
if mount == nil {
log.Fatal("Mount not found")
}
if cmd.fallback != UNSET {
mount.Fallback = cmd.fallback
}
if cmd.relay != UNSET {
setRelay(mount, cmd.relay)
}
if err := client.SetMount(mount); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", mount)
}
func generateUsername(path string) string {
return fmt.Sprintf("source%d", crc32.ChecksumIEEE([]byte(path)))
// Create a new mountpoint.
type createMountCommand struct {
BaseCommand
relay string
fallback string
}
func newCreateMountCommand() *createMountCommand {
return &createMountCommand{
BaseCommand: BaseCommand{
UsageLine: "create-mount <path>",
Short: "Create a new mountpoint",
Long: `
Create a new mountpoint at the specified PATH (which will become the
absolute URL of the stream).
`,
},
}
}
func doCreateMount(path, relayUrl string) {
if strings.Contains(path, "/") {
log.Fatal("Mount points should not contain a slash ('/').")
func (cmd *createMountCommand) AddFlags(f *gonutsflag.FlagSet) {
f.StringVar(&cmd.relay, "relay", "", "Upstream URL to relay")
f.StringVar(&cmd.fallback, "fallback", "", "Fallback stream URL")
}
func (cmd *createMountCommand) Run(args []string) {
if len(args) != 1 {
log.Fatal("Wrong number of arguments")
}
fmt.Printf("args: %v\n", args)
path := args[0]
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
path = "/" + path
// Check if the mount already exists.
client := getClient()
......@@ -116,19 +170,11 @@ func doCreateMount(path, relayUrl string) {
}
// Create the new mount and set the relevant fields (depending
// on the relayUrl value).
// on the options passed to the command).
m := &autoradio.Mount{Name: path}
if relayUrl == "" {
// Randomly generate source credentials.
m.Username = generateUsername(path)
m.Password = autoradio.GeneratePassword()
} else {
// Validate the given relay URL.
u, err := url.Parse(relayUrl)
if err != nil {
log.Fatal(err)
}
m.RelayUrl = u.String()
setRelay(m, cmd.relay)
if cmd.fallback != "" {
m.Fallback = cmd.fallback
}
if err := client.SetMount(m); err != nil {
......@@ -138,23 +184,132 @@ func doCreateMount(path, relayUrl string) {
fmt.Printf("%+v\n", m)
}
func createMount(args []string) {
doCreateMount(args[0], "")
// Delete an existing mountpoint.
type deleteMountCommand struct {
BaseCommand
}
func newDeleteMountCommand() *deleteMountCommand {
return &deleteMountCommand{
BaseCommand{
UsageLine: "delete-mount <path>",
Short: "Delete a mountpoint",
Long: `
Delete the specified mountpoint.
`,
},
}
}
func (cmd *deleteMountCommand) Run(args []string) {
if len(args) != 1 {
log.Fatal("Wrong number of arguments")
}
if err := getClient().DelMount(args[0]); err != nil {
log.Fatal(err)
}
log.Printf("mountpoint %s removed", args[0])
}
// List known mountpoints.
type listMountsCommand struct {
BaseCommand
}
func newListMountsCommand() *listMountsCommand {
return &listMountsCommand{
BaseCommand{
UsageLine: "list-mounts",
Short: "List all configured mountpoints",
Long: `
Outputs a list of all the currently configured mountpoints.
`,
},
}
}
func (cmd *listMountsCommand) Run(args []string) {
if len(args) != 0 {
log.Fatal("Too many arguments")
}
mounts, err := getClient().ListMounts()
if err != nil {
log.Fatal(err)
}
for _, m := range mounts {
fmt.Println(m.Name)
}
}
// Show mountpoint information.
type showMountCommand struct {
BaseCommand
}
func newShowMountCommand() *showMountCommand {
return &showMountCommand{
BaseCommand{
UsageLine: "show-mount <path>",
Short: "Show mountpoint information",
Long: `
Print information about the specified mountpoint (including
the source credentials).
`,
},
}
}
func (cmd *showMountCommand) Run(args []string) {
if len(args) != 1 {
log.Fatal("Wrong nubmer of arguments")
}
mount, err := getClient().GetMount(args[0])
if err != nil {
log.Fatal(err)
}
if mount == nil {
log.Fatal("Mount not found")
}
fmt.Printf("%+v\n", mount)
}
var cmdr = &commander.Command{
UsageLine: "radioctl <command> [<args>...]",
Short: "Manage `autoradio' configuration",
}
func createRelay(args []string) {
doCreateMount(args[0], args[1])
func addCommand(c CommandInterface) {
cmd := c.Command()
cmd.Run = func(unused *commander.Command, args []string) error {
c.Run(args)
return nil
}
if afc, ok := c.(HasAddFlags); ok {
afc.AddFlags(&cmd.Flag)
}
cmdr.Subcommands = append(cmdr.Subcommands, cmd)
}
func init() {
addCommand(newCreateMountCommand())
addCommand(newEditMountCommand())
addCommand(newDeleteMountCommand())
addCommand(newListMountsCommand())
addCommand(newShowMountCommand())
flag.Usage = usage
}
var commands = CommandList{
{"create-mount", "<path>", "Create a new mountpoint", createMount, 1, 1},
{"create-relay", "<path> <source_url>", "Create a new relay", createRelay, 2, 2},
{"delete-mount", "<path>", "Delete a mountpoint", deleteMount, 1, 1},
{"list-mounts", "", "List all known mountpoints", listMounts, 0, 0},
{"show-mount", "<path>", "Show configuration of a mount", showMount, 1, 1},
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s\n\nGlobal options:\n", cmdr.UsageLine)
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\nType \"radioctl help\" for more documentation.\n")
}
func main() {
flag.Parse()
log.SetFlags(0)
Run(commands)
if err := cmdr.Dispatch(flag.Args()); err != nil {
log.Fatal(err)
}
}
autoradio (0.3.4) unstable; urgency=medium
* Updated radioctl.
-- ale <ale@incal.net> Sun, 12 Oct 2014 12:54:11 +0100
autoradio (0.3.3) unstable; urgency=medium
* Serve 302 redirects for direct stream URLs instead of M3Us.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment