diff --git a/cmd/radioctl/radioctl.go b/cmd/radioctl/radioctl.go index 263b1a308c2fb633a24472609052c5ce9a7e8860..7d7c82ed2b970df3bed388abbce2627b5ee455f7 100644 --- a/cmd/radioctl/radioctl.go +++ b/cmd/radioctl/radioctl.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -9,14 +10,21 @@ import ( "os" "strconv" "strings" + "time" "git.autistici.org/ale/autoradio" + "git.autistici.org/ale/autoradio/client" + pb "git.autistici.org/ale/autoradio/proto" "github.com/gonuts/commander" gonutsflag "github.com/gonuts/flag" + "go.etcd.io/etcd/clientv3" ) // Format for output of structured data. -var outputFormat = flag.String("format", "txt", "Output format for structured data (json, txt)") +var ( + etcdEndpoints = flag.String("etcd", "http://localhost:2379", "Etcd endpoints (comma-separated list of URLs)") + outputFormat = flag.String("format", "txt", "Output format for structured data (json, txt)") +) type optionalValue struct { isSet bool @@ -135,15 +143,27 @@ func (b *BaseCommand) Command() *commander.Command { func (b *BaseCommand) Run(args []string) { } -func getClient() *autoradio.Client { - return autoradio.NewClient(autoradio.NewEtcdClient(false)) +var auClient *client.Client + +func getClient() *client.Client { + if auClient == nil { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: strings.Split(*etcdEndpoints, ","), + DialTimeout: 5 * time.Second, + }) + if err != nil { + log.Fatalf("failed to connect to etcd: %v", err) + } + auClient = client.New(cli) + } + return auClient } -func setRelay(m *autoradio.Mount, relayUrl string) { +func setRelay(m *pb.Mount, relayUrl string) { if relayUrl == "" { // Randomly generate source credentials. - m.Username = autoradio.GenerateUsername(m.Name) - m.Password = autoradio.GeneratePassword() + m.SourceUsername = autoradio.GenerateUsername(m.Path) + m.SourcePassword = autoradio.GeneratePassword() } else { // Validate the given relay URL. u, err := url.Parse(relayUrl) @@ -151,30 +171,30 @@ func setRelay(m *autoradio.Mount, relayUrl string) { log.Fatal(err) } m.RelayUrl = u.String() - m.Username = "" - m.Password = "" + m.SourceUsername = "" + m.SourcePassword = "" } } -func printMount(m *autoradio.Mount) { +func printMount(m *pb.Mount) { switch *outputFormat { case "json": s, _ := json.MarshalIndent(m, "", " ") os.Stdout.Write(s) case "txt": - fmt.Printf("path=%s\n", m.Name) - if m.Username != "" { - fmt.Printf("username=%s\npassword=%s\n", m.Username, m.Password) + fmt.Printf("path=%s\n", m.Path) + if m.SourceUsername != "" { + fmt.Printf("username=%s\npassword=%s\n", m.SourceUsername, m.SourcePassword) } if m.RelayUrl != "" { fmt.Printf("relay_url=%s\n", m.RelayUrl) } - if m.Fallback != "" { - fmt.Printf("fallback_url=%s\n", m.Fallback) + if m.FallbackPath != "" { + fmt.Printf("fallback_url=%s\n", m.FallbackPath) } - if t := m.Transcoding; t != nil { - fmt.Printf("transcode_source_url=%s\n", t.SourceName) + if t := m.TranscodeParams; t != nil { + fmt.Printf("transcode_source_url=%s\n", t.SourcePath) fmt.Printf("transcode_format=%s\n", t.Format) fmt.Printf("transcode_bitrate=%d\n", t.BitRate) fmt.Printf("transcode_quality=%f\n", t.Quality) @@ -186,8 +206,8 @@ func printMount(m *autoradio.Mount) { } } -func mountExists(name string, client *autoradio.Client) bool { - m, _ := client.GetMount(name) +func mountExists(name string, c *client.Client) bool { + m, _ := c.GetMount(context.Background(), name) return m != nil } @@ -229,22 +249,22 @@ func (cmd *createMountCommand) Run(args []string) { } // Check if the mount already exists. - client := getClient() - if mountExists(path, client) { + c := getClient() + if mountExists(path, c) { log.Fatal("ERROR: A mount with that name already exists!") } // Create the new mount and set the relevant fields (depending // on the options passed to the command). - m := &autoradio.Mount{Name: path} + m := &pb.Mount{Path: path} setRelay(m, cmd.relay) - m.Fallback = cmd.fallback + m.FallbackPath = cmd.fallback if err := m.Valid(); err != nil { log.Fatalf("ERROR: mount configuration is invalid: %v", err) } - if err := client.SetMount(m); err != nil { + if err := c.SetMount(context.Background(), m); err != nil { log.Fatalf("ERROR: creating mount: %v", err) } @@ -254,8 +274,15 @@ func (cmd *createMountCommand) Run(args []string) { // Create a submount (transcoded stream). type createTranscodingMountCommand struct { BaseCommand - params *autoradio.EncodingParams - fallback string + + sourcePath string + format string + quality float64 + bitRate int + sampleRate int + channels int + stereoMode string + fallback string } func newCreateTranscodingMountCommand() *createTranscodingMountCommand { @@ -268,22 +295,17 @@ Create a new stream that will transcode the parent stream with different encoding parameters. `, }, - params: autoradio.NewEncodingParams(), } } -func addEncodingFlags(f *gonutsflag.FlagSet, p *autoradio.EncodingParams) { - f.StringVar(&p.SourceName, "source", "", "Source mountpoint") - f.StringVar(&p.Format, "codec", p.Format, "Encoding format") - f.Float64Var(&p.Quality, "quality", p.Quality, "Quality (for VBR encoders)") - f.IntVar(&p.BitRate, "bitrate", p.BitRate, "Bitrate (kbps)") - f.IntVar(&p.SampleRate, "samplerate", p.SampleRate, "Sample rate (Hz)") - f.IntVar(&p.Channels, "channels", p.Channels, "Number of channels") - f.StringVar(&p.StereoMode, "stereo-mode", p.StereoMode, "Stereo mode for mp3 codec (stereo, joint_stereo)") -} - func (cmd *createTranscodingMountCommand) AddFlags(f *gonutsflag.FlagSet) { - addEncodingFlags(f, cmd.params) + f.StringVar(&cmd.sourcePath, "source", "", "Source mountpoint") + f.StringVar(&cmd.format, "codec", "mp3", "Encoding format") + f.Float64Var(&cmd.quality, "quality", 0, "Quality (for VBR encoders)") + f.IntVar(&cmd.bitRate, "bitrate", 32, "Bitrate (kbps)") + f.IntVar(&cmd.sampleRate, "samplerate", 44100, "Sample rate (Hz)") + f.IntVar(&cmd.channels, "channels", 2, "Number of channels") + f.StringVar(&cmd.stereoMode, "stereo-mode", "joint_stereo", "Stereo mode for mp3 codec (stereo, joint_stereo)") f.StringVar(&cmd.fallback, "fallback", "", "Fallback stream URL") } @@ -299,28 +321,37 @@ func (cmd *createTranscodingMountCommand) Run(args []string) { } // The mount path should not exist. - client := getClient() - if mountExists(path, client) { + c := getClient() + if mountExists(path, c) { log.Fatal("ERROR: a mount with that name already exists!") } // The source mount should exist. - if !mountExists(cmd.params.SourceName, client) { + if !mountExists(cmd.sourcePath, c) { log.Fatal("ERROR: the source mount does not exist!") } // Retrieve the parent mount point and add a TranscodingMount. - m := &autoradio.Mount{ - Name: path, - Transcoding: cmd.params, + m := &pb.Mount{ + Path: path, + FallbackPath: cmd.fallback, + Transcode: true, + TranscodeParams: &pb.EncodingParams{ + SourcePath: cmd.sourcePath, + Format: cmd.format, + BitRate: int32(cmd.bitRate), + SampleRate: int32(cmd.sampleRate), + Channels: int32(cmd.channels), + StereoMode: cmd.stereoMode, + Quality: float32(cmd.quality), + }, } setRelay(m, "") - m.Fallback = cmd.fallback if err := m.Valid(); err != nil { log.Fatalf("ERROR: mount configuration is invalid: %v", err) } - if err := client.SetMount(m); err != nil { + if err := c.SetMount(context.Background(), m); err != nil { log.Fatalf("ERROR: creating mount: %v", err) } @@ -402,8 +433,8 @@ func (cmd *editMountCommand) Run(args []string) { log.Fatal("Wrong number of arguments") } - client := getClient() - m, err := client.GetMount(args[0]) + c := getClient() + m, err := c.GetMount(context.Background(), args[0]) if err != nil { log.Fatalf("ERROR: %v", err) } @@ -413,43 +444,43 @@ func (cmd *editMountCommand) Run(args []string) { // Only set those fields that were passed on the command line. if cmd.fallback.IsSet() { - m.Fallback = cmd.fallback.Value() + m.FallbackPath = cmd.fallback.Value() } if cmd.relay.IsSet() { setRelay(m, cmd.relay.Value()) } - if cmd.transcodingOptionsSet() && m.Transcoding == nil { + if cmd.transcodingOptionsSet() && !m.Transcode { log.Fatal("ERROR: can't set transcoding options on a non-transcoding mount (delete and re-create)") } if cmd.transFormat.IsSet() { - m.Transcoding.Format = cmd.transFormat.Value() + m.TranscodeParams.Format = cmd.transFormat.Value() } if cmd.transSource.IsSet() { - m.Transcoding.SourceName = cmd.transSource.Value() + m.TranscodeParams.SourcePath = cmd.transSource.Value() } if cmd.transBitRate.IsSet() { - m.Transcoding.BitRate = cmd.transBitRate.Value() + m.TranscodeParams.BitRate = int32(cmd.transBitRate.Value()) } if cmd.transSampleRate.IsSet() { - m.Transcoding.SampleRate = cmd.transSampleRate.Value() + m.TranscodeParams.SampleRate = int32(cmd.transSampleRate.Value()) } if cmd.transQuality.IsSet() { - m.Transcoding.Quality = cmd.transQuality.Value() + m.TranscodeParams.Quality = float32(cmd.transQuality.Value()) } if cmd.transChannels.IsSet() { - m.Transcoding.Channels = cmd.transChannels.Value() + m.TranscodeParams.Channels = int32(cmd.transChannels.Value()) } if cmd.transStereoMode.IsSet() { - m.Transcoding.StereoMode = cmd.transStereoMode.Value() + m.TranscodeParams.StereoMode = cmd.transStereoMode.Value() } if err := m.Valid(); err != nil { log.Fatalf("ERROR: mount configuration is invalid: %v", err) } - if err := client.SetMount(m); err != nil { + if err := c.SetMount(context.Background(), m); err != nil { log.Fatalf("ERROR: updating mount: %v", err) } @@ -478,25 +509,25 @@ func (cmd *deleteMountCommand) Run(args []string) { log.Fatal("Wrong number of arguments") } path := args[0] - client := getClient() - if !mountExists(path, client) { + c := getClient() + if !mountExists(path, c) { log.Fatal("ERROR: mount not found") } - if err := client.DelMount(path); err != nil { + if err := c.DeleteMount(context.Background(), path); err != nil { log.Fatalf("ERROR: deleting mount: %v", err) } // Delete all the transcoding mounts that have this as a // source. - mounts, err := client.ListMounts() + mounts, err := c.ListMounts(context.Background()) if err != nil { log.Fatalf("ERROR: %v", err) } for _, m := range mounts { - if m.HasTranscoder() && m.Transcoding.SourceName == path { - if err := client.DelMount(m.Name); err != nil { - log.Printf("ERROR: deleting transcoded mount %s: %v", m.Name, err) + if m.HasTranscoder() && m.TranscodeParams.SourcePath == path { + if err := c.DeleteMount(context.Background(), m.Path); err != nil { + log.Printf("ERROR: deleting transcoded mount %s: %v", m.Path, err) } } } @@ -526,13 +557,13 @@ func (cmd *listMountsCommand) Run(args []string) { log.Fatal("Too many arguments") } - mounts, err := getClient().ListMounts() + mounts, err := getClient().ListMounts(context.Background()) if err != nil { log.Fatalf("ERROR: %v", err) } var names []string for _, m := range mounts { - names = append(names, m.Name) + names = append(names, m.Path) } switch *outputFormat { @@ -571,7 +602,7 @@ func (cmd *showMountCommand) Run(args []string) { log.Fatal("Wrong number of arguments") } - m, err := getClient().GetMount(args[0]) + m, err := getClient().GetMount(context.Background(), args[0]) if err != nil { log.Fatalf("ERROR: %v", err) } @@ -605,7 +636,7 @@ func (cmd *backupCommand) Run(args []string) { log.Fatal("Too many arguments") } - mounts, err := getClient().ListMounts() + mounts, err := getClient().ListMounts(context.Background()) if err != nil { log.Fatalf("ERROR: %v", err) } @@ -636,15 +667,15 @@ func (cmd *restoreCommand) Run(args []string) { log.Fatal("Too many arguments") } - var mounts []*autoradio.Mount + var mounts []*pb.Mount if err := json.NewDecoder(os.Stdin).Decode(&mounts); err != nil { log.Fatalf("ERROR: %v", err) } - client := getClient() + c := getClient() for _, m := range mounts { - if err := client.SetMount(m); err != nil { - log.Printf("ERROR: creating mount %s: %v", m.Name, err) + if err := c.SetMount(context.Background(), m); err != nil { + log.Printf("ERROR: creating mount %s: %v", m.Path, err) } } }