diff --git a/internal/radio/icecast.go b/internal/radio/icecast.go index 3d99b9b..30e2899 100644 --- a/internal/radio/icecast.go +++ b/internal/radio/icecast.go @@ -19,7 +19,9 @@ const ( IcecastPlaylistDateFormat = "02/Jan/2006:15:04:05 -0700" SongTimeFormat = "2006 15:04-0700" - bufferSizePerLine = 320 + bufferSizePerLine = 512 + separatorOne = 26 + separatorTwo = 38 + 1 ) var ( @@ -96,61 +98,46 @@ func IcecastLastSong(playlistPath string) *Song { return nil } -func icecastLastPlayedSongs(playlistPath string, n int) ([]Song, error) { - var buf []byte - var offset int64 = 0 - var bufferSize int64 = int64(n) * bufferSizePerLine - - playlist, err := os.Open(playlistPath) +func icecastCurrentSong(playlistPath string) (*Song, error) { + fd, err := os.Open(playlistPath) if err != nil { return nil, err } - defer playlist.Close() + defer fd.Close() - playlist_stat, _ := playlist.Stat() - - if playlist_stat.Size() == 0 { + fdSize, _ := fd.Seek(0, io.SeekEnd) + if fdSize == 0 { return nil, nil } - if playlist_stat.Size() < bufferSize { - buf = make([]byte, playlist_stat.Size()) - } else { - buf = make([]byte, bufferSize) - offset = playlist_stat.Size() - bufferSize + var bufSize int64 = bufferSizePerLine + if fdSize < bufferSizePerLine { + bufSize = fdSize } - _, err = playlist.ReadAt(buf, offset) - if err != nil && err != io.EOF { + _, err = fd.Seek(-bufSize, io.SeekEnd) + if err != nil { return nil, err } - lines := bytes.Split(buf, []byte("\n")) + buf := make([]byte, bufSize) - if len(lines) < 2 { - return nil, nil + _, err = fd.Read(buf) + if err != nil { + return nil, err } - lines = lines[:len(lines)-1] + curSongEnd := bytes.LastIndexByte(buf, '\n') + line := buf[bytes.LastIndexByte(buf[:curSongEnd], '\n')+1 : curSongEnd] - if len(lines) > n { - lines = lines[len(lines)-n:] - } + songTime, _ := time.Parse(IcecastPlaylistDateFormat, string(line[:separatorOne])) - songs := make([]Song, 0, len(lines)) + separatorThree := bytes.LastIndexByte(line, '|') - for _, line := range lines { - fields := bytes.Split(line, []byte("|")) - - tim, _ := time.Parse(IcecastPlaylistDateFormat, string(fields[0])) - - songs = append(songs, Song{ - Time: tim.Format(SongTimeFormat), - Listeners: string(fields[2]), - Song: string(fields[3])}) - } - - return songs, nil + return &Song{ + Time: songTime.Format(SongTimeFormat), + Listeners: string(line[separatorTwo:separatorThree]), + Song: string(line[separatorThree+1:])}, nil } type PlaylistLogWatcher struct { @@ -180,13 +167,14 @@ func (pw *PlaylistLogWatcher) Watch(playlistPath string, n int) (err error) { pw.watcher.WatchForMask(pw.changed, watcher.ModIgnMask) if lastPlayedCache == nil { - lastPlayedCache = make([]Song, n) - songs, err := icecastLastPlayedSongs(playlistPath, n) - if err == nil && len(songs) > 0 { - copy(lastPlayedCache, songs) - } else if err != nil { - log.Fatalln("failed to retrieve last songs:", err) - } + lastPlayedCache = make([]Song, 0, n) + } + + cur, err := icecastCurrentSong(playlistPath) + if err != nil { + log.Println("failed to fetch current song:", err) + } else if cur != nil && currentlyPlaying.Time != "" { + currentlyPlaying = *cur } go func() { @@ -195,19 +183,19 @@ func (pw *PlaylistLogWatcher) Watch(playlistPath string, n int) (err error) { if mask&syscall.IN_MODIFY > 0 { lastPlayedCacheMutex.Lock() - songs, err := icecastLastPlayedSongs(playlistPath, 1) - if err == nil && len(songs) > 0 { + song, err := icecastCurrentSong(playlistPath) + if err == nil && song != nil { CheckAndUpdateMostListenedSong(song, ¤tlyPlaying) if currentlyPlaying.Time == "" { - currentlyPlaying = songs[0] + currentlyPlaying = *song } else { - currentlyPlaying.Listeners = songs[0].Listeners + currentlyPlaying.Listeners = song.Listeners if len(lastPlayedCache) == n { lastPlayedCache = append(lastPlayedCache[1:], currentlyPlaying) } else { lastPlayedCache = append(lastPlayedCache, currentlyPlaying) } - currentlyPlaying = songs[0] + currentlyPlaying = *song } } else if err != nil { log.Println("failed to retrieve last songs:", err) diff --git a/internal/radio/icecast_test.go b/internal/radio/icecast_test.go new file mode 100644 index 0000000..d3f0fb6 --- /dev/null +++ b/internal/radio/icecast_test.go @@ -0,0 +1,12 @@ +package radio + +import "testing" + +const playlistPath = "../../p.log" + +func BenchmarkIcecastCurrentSong(b *testing.B) { + for i := 0; i < b.N; i++ { + s, err := icecastCurrentSong(playlistPath) + b.Log(s, err) + } +}