package radio import ( "dwelling-radio/pkg/watcher" "encoding/json" "fmt" "net/http" "os/exec" "strings" "time" "github.com/pkg/errors" ) type IcecastStatusDTO struct { Icestats struct { ServerStartISO8601 string `json:"server_start_iso8601"` ServerStartDate string `json:"server_start"` Source struct { Artist string `json:"artist"` Title string `json:"title"` ListenerPeak int `json:"listener_peak"` Listeners int `json:"listeners"` } `json:"source"` } `json:"icestats"` } func (is *IcecastStatusDTO) Song() string { return fmt.Sprintf("%s - %s", is.Icestats.Source.Artist, is.Icestats.Source.Title) } type IcecastStatus struct { ServerStartISO8601 string `json:"server_start_iso8601"` ServerStartDate string `json:"server_start_date"` SongName string `json:"song"` ListenerPeak int `json:"listener_peak"` Listeners int `json:"listeners"` } type Song struct { Time string `json:"time"` Artist string `json:"artist"` Title string `json:"title"` } func IcecastGetStatus(icecastURL string) (*IcecastStatus, error) { resp, err := http.Get(icecastURL) if err != nil { return nil, err } iceStatDTO := &IcecastStatusDTO{} if err := json.NewDecoder(resp.Body).Decode(iceStatDTO); err != nil { return nil, err } iceStat := &IcecastStatus{ ServerStartISO8601: iceStatDTO.Icestats.ServerStartISO8601, ServerStartDate: iceStatDTO.Icestats.ServerStartDate, SongName: iceStatDTO.Song(), ListenerPeak: iceStatDTO.Icestats.Source.ListenerPeak, Listeners: iceStatDTO.Icestats.Source.Listeners, } return iceStat, nil } func IcecastLastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) { if len(lastPlayedCache) > 0 { if lastNSongs > len(lastPlayedCache) { lastNSongs = len(lastPlayedCache) } return lastPlayedCache[len(lastPlayedCache)-lastNSongs:], nil } return lastPlayedSongs(lastNSongs, playlistPath) } func IcecastLastSong(playlistPath string) (Song, error) { if len(lastPlayedCache) > 0 { return lastPlayedCache[len(lastPlayedCache)-1], nil } songs, err := IcecastLastPlayedSongs(1, playlistPath) if len(songs) == 0 { return Song{}, nil } return songs[0], err } func lastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) { songs := make([]Song, 0) cmd := fmt.Sprintf("tail -n%d %s | head -n-1 | cut -d'|' -f1,4", lastNSongs+1, playlistPath) out, err := exec.Command("bash", "-c", cmd).CombinedOutput() if err != nil { return songs, err } if len(out) == 0 { return songs, nil } songs_ := strings.Split(string(out), "\n") for _, song := range songs_ { ts := strings.Split(song, "|") if len(ts) <= 1 { continue } tim, _ := time.Parse("02/Jan/2006:15:04:05 -0700", ts[0]) at := strings.Split(ts[1], " - ") songs = append(songs, Song{ Time: tim.UTC().Format("15:04-0700"), Artist: at[0], Title: at[1]}) } return songs, nil } var playlistWatcher watcher.InotifyWatcher var playlistFired chan uint32 var lastPlayedCache []Song func IcecastWatchPlaylist(playlistPath string, lastNSongs int) error { playlistWatcher, err := watcher.NewInotifyWatcher() if err != nil { return errors.Wrap(err, "cannot instantiate inotify watcher") } err = playlistWatcher.AddWatch(playlistPath, watcher.ModMask) if err != nil { return errors.Wrap(err, "cannot set a playlist to watch") } playlistFired = make(chan uint32) playlistWatcher.WatchForMask(playlistFired, watcher.ModMask) go func() { for { select { case <-playlistFired: songs, err := lastPlayedSongs(lastNSongs, playlistPath) if err == nil && len(songs) > 0 { lastPlayedCache = songs } } } }() songs, err := lastPlayedSongs(lastNSongs, playlistPath) if err == nil && len(songs) > 0 { lastPlayedCache = songs } return nil } func IcecastWatchClose() { playlistWatcher.Close() }