diff --git a/cmd/radioctl/radioctl.go b/cmd/radioctl/radioctl.go index 5105d9cec47de998c42f72144139a2ef16cc25d9..a9e681321d6b860d0e715cd74e53c4bf851530c5 100644 --- a/cmd/radioctl/radioctl.go +++ b/cmd/radioctl/radioctl.go @@ -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) + } } diff --git a/debian/changelog b/debian/changelog index 3fbd8b8a4e5c5811322d8b7a38c61d11471115da..20b64d50cdb2caaa2a4fe0da936846bc48c38171 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +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.