Icecast-specific code was removed, because now it all is controled inside a service.
This commit is contained in:
parent
0245145a64
commit
5e8e9943f9
@ -1,221 +0,0 @@
|
|||||||
package radio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"dwelling-radio/pkg/watcher"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IcecastPlaylistDateFormat = "02/Jan/2006:15:04:05 -0700"
|
|
||||||
SongTimeFormat = "2006 15:04-0700"
|
|
||||||
|
|
||||||
bufferSizePerLine = 512
|
|
||||||
// Positions of first and second "|" separator on playlist.log line.
|
|
||||||
// The third one may float if amount of listeners will be >= 10.
|
|
||||||
separatorOne = 26
|
|
||||||
// +1 added to a second one just because it is used only at a start pos of
|
|
||||||
// a listeners' field, so there's no need to increment it every time.
|
|
||||||
separatorTwo = 38 + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
currentlyPlaying Song
|
|
||||||
lastPlayedCache []Song
|
|
||||||
lastPlayedCacheMutex sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
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) SongName() string {
|
|
||||||
return is.Icestats.Source.Artist + " - " + is.Icestats.Source.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
type IcecastStatus struct {
|
|
||||||
ListenerPeak int `json:"listener_peak"`
|
|
||||||
Listeners int `json:"listeners"`
|
|
||||||
SongName string `json:"song"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func IcecastGetStatus(icecastURL string) (*IcecastStatus, error) {
|
|
||||||
resp, err := http.Get(icecastURL)
|
|
||||||
if err != nil {
|
|
||||||
return &IcecastStatus{SongName: "Offline"}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iceStatDTO := &IcecastStatusDTO{}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(iceStatDTO); err != nil {
|
|
||||||
return &IcecastStatus{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &IcecastStatus{
|
|
||||||
SongName: iceStatDTO.SongName(),
|
|
||||||
ListenerPeak: iceStatDTO.Icestats.Source.ListenerPeak,
|
|
||||||
Listeners: iceStatDTO.Icestats.Source.Listeners,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Song struct {
|
|
||||||
Time string `json:"time"`
|
|
||||||
Listeners string `json:"listeners"`
|
|
||||||
Song string `json:"song"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func IcecastLastSongs(playlistPath string) []Song {
|
|
||||||
lastPlayedCacheMutex.Lock()
|
|
||||||
defer lastPlayedCacheMutex.Unlock()
|
|
||||||
if lpcLen := len(lastPlayedCache); lpcLen > 0 {
|
|
||||||
ret := make([]Song, lpcLen)
|
|
||||||
copy(ret, lastPlayedCache)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IcecastLastSong(playlistPath string) *Song {
|
|
||||||
lastPlayedCacheMutex.Lock()
|
|
||||||
defer lastPlayedCacheMutex.Unlock()
|
|
||||||
if lpcLen := len(lastPlayedCache); lpcLen > 0 {
|
|
||||||
return &lastPlayedCache[lpcLen-1]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func icecastCurrentSong(playlistPath string) (*Song, error) {
|
|
||||||
fd, err := os.Open(playlistPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
fdSize, _ := fd.Seek(0, io.SeekEnd)
|
|
||||||
if fdSize == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var bufSize int64 = bufferSizePerLine
|
|
||||||
if fdSize < bufferSizePerLine {
|
|
||||||
bufSize = fdSize
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fd.Seek(-bufSize, io.SeekEnd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, bufSize)
|
|
||||||
|
|
||||||
_, err = fd.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
curSongEnd := bytes.LastIndexByte(buf, '\n')
|
|
||||||
line := buf[bytes.LastIndexByte(buf[:curSongEnd], '\n')+1 : curSongEnd]
|
|
||||||
|
|
||||||
songTime, _ := time.Parse(IcecastPlaylistDateFormat, string(line[:separatorOne]))
|
|
||||||
|
|
||||||
separatorThree := bytes.LastIndexByte(line, '|')
|
|
||||||
|
|
||||||
return &Song{
|
|
||||||
Time: songTime.Format(SongTimeFormat),
|
|
||||||
Listeners: string(line[separatorTwo:separatorThree]),
|
|
||||||
Song: string(line[separatorThree+1:])}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlaylistLogWatcher struct {
|
|
||||||
watcher *watcher.InotifyWatcher
|
|
||||||
changed chan uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPlaylistLogWatcher() *PlaylistLogWatcher {
|
|
||||||
return &PlaylistLogWatcher{changed: make(chan uint32)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *PlaylistLogWatcher) Watch(playlistPath string, n int) (err error) {
|
|
||||||
if pw.watcher != nil {
|
|
||||||
pw.watcher.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
pw.watcher, err = watcher.NewInotifyWatcher()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "cannot instantiate inotify watcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pw.watcher.AddWatch(playlistPath, watcher.ModIgnMask)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "cannot set a playlist to watch")
|
|
||||||
}
|
|
||||||
|
|
||||||
pw.watcher.WatchForMask(pw.changed, watcher.ModIgnMask)
|
|
||||||
|
|
||||||
if lastPlayedCache == nil {
|
|
||||||
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() {
|
|
||||||
for {
|
|
||||||
mask := <-pw.changed
|
|
||||||
|
|
||||||
if mask&syscall.IN_MODIFY > 0 {
|
|
||||||
lastPlayedCacheMutex.Lock()
|
|
||||||
song, err := icecastCurrentSong(playlistPath)
|
|
||||||
if err == nil && song != nil {
|
|
||||||
CheckAndUpdateMostListenedSong(song, ¤tlyPlaying)
|
|
||||||
if currentlyPlaying.Time == "" {
|
|
||||||
currentlyPlaying = *song
|
|
||||||
} else {
|
|
||||||
currentlyPlaying.Listeners = song.Listeners
|
|
||||||
if len(lastPlayedCache) == n {
|
|
||||||
lastPlayedCache = append(lastPlayedCache[1:], currentlyPlaying)
|
|
||||||
} else {
|
|
||||||
lastPlayedCache = append(lastPlayedCache, currentlyPlaying)
|
|
||||||
}
|
|
||||||
currentlyPlaying = *song
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
log.Println("failed to retrieve last songs:", err)
|
|
||||||
}
|
|
||||||
lastPlayedCacheMutex.Unlock()
|
|
||||||
} else if mask&syscall.IN_IGNORED > 0 {
|
|
||||||
pw.Close()
|
|
||||||
pw.Watch(playlistPath, n)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pw *PlaylistLogWatcher) Close() {
|
|
||||||
pw.watcher.Close()
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user