package radio import ( "dwelling-radio/pkg/watcher" "log" "os" "strings" "sync" "syscall" ) // Playlist holds a list of paths to a song files. type Playlist struct { mut sync.Mutex filePath string playlist []string cur int repeat bool watcher *watcher.InotifyWatcher changed chan uint32 } // NewPlaylist returns an instance of a Playlist struct with a loaded playlist. // Returns an error if failed to load a playlist file. func NewPlaylist(filePath string, repeat bool) (*Playlist, error) { p := &Playlist{filePath: filePath, repeat: repeat} return p, p.load() } // Next returns the next song to play. Returns an empty string if repeat is // false and the end of a playlist was reached. func (p *Playlist) Next() (song string) { p.mut.Lock() defer p.mut.Unlock() if p.cur == len(p.playlist) { // If the end of a playlist was reached and repeat is set to true, // then go back to the head of it, thus repeating it. Return an empty // string otherwise. if p.repeat { p.cur = 0 } else { return "" } } song = p.playlist[p.cur] p.cur++ return } // Load reads a playlist file. func (p *Playlist) load() error { data, err := os.ReadFile(p.filePath) if err != nil { return err } p.mut.Lock() p.playlist = strings.Split(string(data), "\n") p.cur = 0 p.mut.Unlock() return nil } func (p *Playlist) Reload() error { return p.load() } func (p *Playlist) Watch() (err error) { p.watcher, err = watcher.NewInotifyWatcher() if err != nil { return err } err = p.watcher.AddWatch(p.filePath, watcher.ModIgnMask) if err != nil { return err } p.watcher.WatchForMask(p.changed, watcher.ModIgnMask) go func() { for { mask := <-p.changed if mask&syscall.IN_MODIFY > 0 { if err := p.load(); err != nil { log.Fatalln("cannot reload a changed playlist:", err) } } else if mask&syscall.IN_IGNORED > 0 { p.watcher.Close() p.Watch() } } }() return nil }