diff --git a/internal/radio/icecast.go b/internal/radio/icecast.go index fee6418..40a2dff 100644 --- a/internal/radio/icecast.go +++ b/internal/radio/icecast.go @@ -1,11 +1,12 @@ package radio import ( + "bytes" "dwelling-radio/pkg/watcher" "encoding/json" - "fmt" + "io" "net/http" - "os/exec" + "os" "strings" "sync" "syscall" @@ -17,6 +18,8 @@ import ( const ( IcecastPlaylistDateFormat = "02/Jan/2006:15:04:05 -0700" SongTimeFormat = "2006 15:04-0700" + + bufferSize = 8192 ) type IcecastStatusDTO struct { @@ -85,7 +88,7 @@ func IcecastLastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) } } - return lastPlayedSongs(lastNSongs, playlistPath) + return make([]Song, 0), nil } func IcecastLastSong(playlistPath string) (Song, error) { @@ -97,45 +100,65 @@ func IcecastLastSong(playlistPath string) (Song, error) { } } - songs, err := lastPlayedSongs(1, playlistPath) - if len(songs) == 0 { - return Song{}, nil + song, err := lastPlayedSong(playlistPath) + if err != nil { + return Song{}, err } - return songs[0], err + return *song, nil } -func lastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) { - songs := make([]Song, 0) +func lastPlayedSong(playlistPath string) (*Song, error) { + buf := make([]byte, bufferSize) + var last_song_line string - cmd := fmt.Sprintf("tail -n%d %s | head -n-1 | cut -d'|' -f1,3,4", lastNSongs+1, playlistPath) - out, err := exec.Command("bash", "-c", cmd).CombinedOutput() + playlist, err := os.Open(playlistPath) if err != nil { - return songs, err + return nil, err } - if len(out) == 0 { - return songs, nil + playlist_stat, _ := playlist.Stat() + + if playlist_stat.Size() == 0 { + return nil, nil } - for _, song := range strings.Split(string(out), "\n") { - ts := strings.Split(song, "|") - if len(ts) <= 1 { - continue - } - tim, _ := time.Parse(IcecastPlaylistDateFormat, ts[0]) - songs = append(songs, Song{ - Time: tim.Format(SongTimeFormat), - Listeners: ts[1], - Song: ts[2]}) + playlist.Seek(-bufferSize, os.SEEK_END) + + _, err = playlist.Read(buf) + if err != nil && err != io.EOF { + return nil, err } - return songs, nil + newline_end_pos := bytes.LastIndexByte(buf, '\n') + + if newline_end_pos == -1 && !bytes.ContainsRune(buf, '|') { + return nil, nil + } else if newline_end_pos == -1 { + newline_end_pos = len(buf) + } + + newline_start_pos := bytes.LastIndexByte(buf[:newline_end_pos-1], '\n') + + last_song_line = string(buf[newline_start_pos+1 : newline_end_pos-1]) + + if strings.Count(last_song_line, "|") != 3 { + return nil, nil + } + + fields := strings.Split(last_song_line, "|") + + tim, _ := time.Parse(IcecastPlaylistDateFormat, fields[0]) + + return &Song{ + Time: tim.Format(SongTimeFormat), + Listeners: fields[2], + Song: fields[3]}, nil } var playlistWatcher watcher.InotifyWatcher var playlistFired chan uint32 = make(chan uint32) -var lastPlayedCache []Song +var lastPlayedCache []Song = make([]Song, 10) var lastPlayedCacheMutex sync.Mutex func IcecastWatchPlaylist(playlistPath string, lastNSongs int) error { @@ -157,9 +180,11 @@ func IcecastWatchPlaylist(playlistPath string, lastNSongs int) error { case mask := <-playlistFired: if mask&syscall.IN_MODIFY > 0 { lastPlayedCacheMutex.Lock() - songs, err := lastPlayedSongs(lastNSongs, playlistPath) - if err == nil && len(songs) > 0 { - lastPlayedCache = songs + if song, err := lastPlayedSong(playlistPath); err == nil { + lastPlayedCache = append(lastPlayedCache, *song) + if len(lastPlayedCache) > lastNSongs { + lastPlayedCache = lastPlayedCache[1:] + } } lastPlayedCacheMutex.Unlock() } else if mask&syscall.IN_IGNORED > 0 { @@ -171,13 +196,6 @@ func IcecastWatchPlaylist(playlistPath string, lastNSongs int) error { } }() - lastPlayedCacheMutex.Lock() - songs, err := lastPlayedSongs(lastNSongs, playlistPath) - if err == nil && len(songs) > 0 { - lastPlayedCache = songs - } - lastPlayedCacheMutex.Unlock() - return nil }