func Read(rf codec.Reader) (codec.Songs, error) { r, _, err := rf() if err != nil { return nil, err } // In order to not open the rar file up once per included file, keep around a // pointer to the reader during the inital listing phase and use that if we're // opening it. I would appreciate a better way to do this. var rd *rardecode.Reader var rfh *rardecode.FileHeader defer func() { rd = nil }() readRAR := func(name string, rf codec.Reader) codec.Reader { return func() (rc io.ReadCloser, sz int64, err error) { if rd != nil && rfh.Name == name { return ioutil.NopCloser(rd), rfh.UnPackedSize, nil } r, _, err := rf() if err != nil { return nil, 0, err } f := func(d *rardecode.Reader, fh *rardecode.FileHeader) (stop bool) { if fh.Name != name { return false } rc = &rarReader{r, d} sz = fh.UnPackedSize return true } err = read(r, f) if err == nil && rc == nil { err = fmt.Errorf("rar: %v unfound", name) } return } } defer r.Close() songs := make(codec.Songs) f := func(d *rardecode.Reader, fh *rardecode.FileHeader) (stop bool) { rd = d rfh = fh ss, _, _ := codec.ByExtension(fh.Name, readRAR(fh.Name, rf)) for v, s := range ss { songs[codec.NewID(fh.Name, string(v))] = s } return false } if err := read(r, f); err != nil { return nil, err } return songs, nil }
func (d *Dropbox) Refresh() (protocol.SongList, error) { service, err := d.getService() if err != nil { return nil, err } files := make(map[string]*dropbox.ListContent) songs := make(protocol.SongList) var ss codec.Songs dirs := []string{""} for { if len(dirs) == 0 { break } dir := dirs[0] dirs = dirs[1:] list, err := service.List().Path(dir).Do() if err != nil { return nil, err } for _, f := range list.Contents { if f.IsDir { dirs = append(dirs, f.Path) continue } ss, _, err = codec.ByExtension(f.Path, d.reader(f.Path, f.Bytes)) if err != nil || len(ss) == 0 { continue } files[f.Path] = f for i, v := range ss { info, _ := v.Info() if info.Title == "" { title := path.Base(f.Path) if len(ss) != 1 { title += fmt.Sprintf(":%v", i) } info.Title = title } if info.Album == "" { info.Album = path.Base(dir) } songs[codec.NewID(f.Path, string(i))] = &info } } } d.Songs = songs d.Files = files return songs, err }
func (d *Drive) Refresh() (protocol.SongList, error) { service, _, err := d.getService() if err != nil { return nil, err } files := make(map[string]*drive.File) songs := make(protocol.SongList) var nextPage string var ss codec.Songs for { fl, err := service.Files. List(). PageToken(nextPage). Fields("nextPageToken", "items(id,fileExtension,fileSize,title)"). MaxResults(1000). Do() if err != nil { return nil, err } nextPage = fl.NextPageToken for _, f := range fl.Items { ss, _, err = codec.ByExtension(f.FileExtension, d.reader(f.Id)) if err != nil || len(ss) == 0 { continue } files[f.Id] = f for i, v := range ss { info, _ := v.Info() if info.Title == "" { title := f.Title if len(ss) != 1 { title += fmt.Sprintf(":%v", i) } info.Title = title } songs[codec.NewID(f.Id, string(i))] = &info } } if nextPage == "" { break } } d.Songs = songs d.Files = files return songs, err }
func (f *File) Refresh() (protocol.SongList, error) { songs := make(protocol.SongList) err := filepath.Walk(f.Path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } f, err := os.Open(path) if err != nil { return nil } defer f.Close() ss, _, err := codec.ByExtension(path, fileReader(path)) if err != nil || len(ss) == 0 { return nil } for i, s := range ss { info, _ := s.Info() if info.Title == "" { title := filepath.Base(path) if len(ss) != 1 { title += fmt.Sprintf(":%v", i) } info.Title = title } if info.Album == "" { info.Album = filepath.Base(filepath.Dir(path)) } songs[codec.NewID(path, string(i))] = &info } return nil }) f.Songs = songs return songs, err }
func (srv *Server) commands() { srv.state = stateStop var next, stop, tick, play, pause, prev func() var timer <-chan time.Time waiters := make(map[*websocket.Conn]chan struct{}) broadcastData := func(wd *waitData) { for ws := range waiters { go func(ws *websocket.Conn) { if err := websocket.JSON.Send(ws, wd); err != nil { srv.ch <- cmdDeleteWS(ws) } }(ws) } } broadcast := func(wt waitType) { wd := srv.makeWaitData(wt) broadcastData(wd) } broadcastErr := func(err error) { printErr(err) v := struct { Time time.Time Error string }{ time.Now().UTC(), err.Error(), } broadcastData(&waitData{ Type: waitError, Data: v, }) } newWS := func(c cmdNewWS) { ws := (*websocket.Conn)(c.ws) waiters[ws] = c.done inits := []waitType{ waitPlaylist, waitProtocols, waitStatus, waitTracks, } for _, wt := range inits { data := srv.makeWaitData(wt) go func() { if err := websocket.JSON.Send(ws, data); err != nil { srv.ch <- cmdDeleteWS(ws) return } }() } } deleteWS := func(c cmdDeleteWS) { ws := (*websocket.Conn)(c) ch := waiters[ws] if ch == nil { return } close(ch) delete(waiters, ws) } prev = func() { log.Println("prev") srv.PlaylistIndex-- if srv.elapsed < time.Second*3 { srv.PlaylistIndex-- } if srv.PlaylistIndex < 0 { srv.PlaylistIndex = 0 } next() } pause = func() { log.Println("pause") switch srv.state { case statePause, stateStop: log.Println("pause: resume") srv.audioch <- audioPlay{} srv.state = statePlay case statePlay: log.Println("pause: pause") srv.audioch <- audioStop{} srv.state = statePause } } next = func() { log.Println("next") stop() play() } var forceNext = false stop = func() { log.Println("stop") srv.state = stateStop srv.audioch <- audioStop{} if srv.song != nil || forceNext { if srv.Random && len(srv.Queue) > 1 { n := srv.PlaylistIndex for n == srv.PlaylistIndex { n = rand.Intn(len(srv.Queue)) } srv.PlaylistIndex = n } else { srv.PlaylistIndex++ } } forceNext = false srv.song = nil srv.elapsed = 0 } var inst protocol.Instance var sid SongID sendNext := func() { go func() { srv.ch <- cmdNext }() } nextOpen := time.After(0) tick = func() { const expected = 4096 if false && srv.elapsed > srv.info.Time { log.Println("elapsed time completed", srv.elapsed, srv.info.Time) stop() } if srv.song == nil { <-nextOpen nextOpen = time.After(time.Second / 2) defer broadcast(waitStatus) if len(srv.Queue) == 0 { log.Println("empty queue") stop() return } if srv.PlaylistIndex >= len(srv.Queue) { if srv.Repeat { srv.PlaylistIndex = 0 } else { log.Println("end of queue", srv.PlaylistIndex, len(srv.Queue)) stop() return } } srv.songID = srv.Queue[srv.PlaylistIndex] sid = srv.songID if info, err := srv.getSong(sid); err != nil { broadcastErr(err) forceNext = true sendNext() return } else { srv.info = *info } inst = srv.Protocols[sid.Protocol()][sid.Key()] song, err := inst.GetSong(sid.ID()) if err != nil { forceNext = true broadcastErr(err) sendNext() return } srv.song = song sr, ch, err := srv.song.Init() if err != nil { srv.song.Close() srv.song = nil broadcastErr(err) sendNext() return } params := audioSetParams{ sr: sr, ch: ch, dur: srv.info.Time, play: srv.song.Play, err: make(chan error), } srv.audioch <- params if err := <-params.err; err != nil { broadcastErr(err) sendNext() return } srv.elapsed = 0 log.Println("playing", srv.info.Title, sr, ch) srv.state = statePlay } } infoTimer := func() { timer = time.After(time.Second) if inst == nil { return } // Check for updated song info. if info, err := inst.Info(sid.ID()); err != nil { broadcastErr(err) } else if srv.info != *info { srv.info = *info broadcast(waitStatus) } } restart := func() { log.Println("attempting to restart song") n := srv.PlaylistIndex stop() srv.PlaylistIndex = n play() } play = func() { log.Println("play") if srv.PlaylistIndex > len(srv.Queue) { srv.PlaylistIndex = 0 } tick() } playIdx := func(c cmdPlayIdx) { stop() srv.PlaylistIndex = int(c) play() } playTrack := func(c cmdPlayTrack) { t := SongID(c) info, err := srv.getSong(t) if err != nil { broadcastErr(err) return } album := info.Album p, err := srv.getInstance(t.Protocol(), t.Key()) if err != nil { broadcastErr(err) return } list, err := p.List() if err != nil { broadcastErr(err) return } top := codec.NewID(t.Protocol(), t.Key()) var ids []codec.ID for id, si := range list { if si.Album == album { ids = append(ids, id) } } slice.Sort(ids, func(i, j int) bool { a := list[ids[i]] b := list[ids[j]] return a.Track < b.Track }) plc := PlaylistChange{[]string{"clear"}} for _, v := range ids { plc = append(plc, []string{"add", string(top.Push(string(v)))}) } n, _, err := srv.playlistChange(srv.Queue, plc) if err != nil { broadcastErr(err) return } stop() srv.Queue = n srv.PlaylistIndex = 0 for i, s := range srv.Queue { if s == t { srv.PlaylistIndex = i } } play() broadcast(waitPlaylist) } removeDeleted := func() { for n, p := range srv.Playlists { p = srv.removeDeleted(p) if len(p) == 0 { delete(srv.Playlists, n) } else { srv.Playlists[n] = p } } srv.Queue = srv.removeDeleted(srv.Queue) if info, _ := srv.getSong(srv.songID); info == nil { playing := srv.state == statePlay stop() if playing { srv.PlaylistIndex = 0 play() } } broadcast(waitPlaylist) } protocolRemove := func(c cmdProtocolRemove) { prots, ok := srv.Protocols[c.protocol] if !ok { return } delete(prots, c.key) if srv.Token != "" { d := models.Delete{ Protocol: c.protocol, Name: c.key, } go func() { r, err := srv.request("/api/source/delete", &d) if err != nil { srv.ch <- cmdError(err) return } r.Close() }() } broadcast(waitTracks) broadcast(waitProtocols) } removeInProgress := func(c cmdRemoveInProgress) { delete(srv.inprogress, codec.ID(c)) broadcast(waitProtocols) } protocolAdd := func(c cmdProtocolAdd) { name, key := c.Name, c.Instance.Key() id := codec.NewID(name, key) if srv.inprogress[id] { broadcastErr(fmt.Errorf("already adding %s: %s", name, key)) return } if _, err := srv.getInstance(name, key); err == nil { broadcastErr(fmt.Errorf("already have %s: %s", name, key)) return } srv.inprogress[id] = true broadcast(waitProtocols) go func() { defer func() { srv.ch <- cmdRemoveInProgress(id) }() songs, err := c.Instance.Refresh() if err != nil { srv.ch <- cmdError(err) return } for k, v := range songs { if v.Time > 0 && v.Time < srv.MinDuration { delete(songs, k) } } srv.ch <- cmdProtocolAddInstance(c) }() } protocolAddInstance := func(c cmdProtocolAddInstance) { srv.Protocols[c.Name][c.Instance.Key()] = c.Instance if srv.Token != "" { srv.ch <- cmdPutSource{ protocol: c.Name, key: c.Instance.Key(), } } broadcast(waitTracks) broadcast(waitProtocols) } queueChange := func(c cmdQueueChange) { n, clear, err := srv.playlistChange(srv.Queue, PlaylistChange(c)) if err != nil { broadcastErr(err) return } srv.Queue = n if clear || len(n) == 0 { stop() srv.PlaylistIndex = 0 } broadcast(waitPlaylist) } playlistChange := func(c cmdPlaylistChange) { p := srv.Playlists[c.name] n, _, err := srv.playlistChange(p, c.plc) if err != nil { broadcastErr(err) return } if len(n) == 0 { delete(srv.Playlists, c.name) } else { srv.Playlists[c.name] = n } broadcast(waitPlaylist) } queueSave := func() { if srv.savePending { return } srv.savePending = true time.AfterFunc(time.Second, func() { srv.ch <- cmdDoSave{} }) } doSave := func() { if err := srv.save(); err != nil { broadcastErr(err) } } addOAuth := func(c cmdAddOAuth) { go func() { prot, err := protocol.ByName(c.name) if err != nil { c.done <- err return } t, err := prot.OAuth.Exchange(oauth2.NoContext, c.r.FormValue("code")) if err != nil { c.done <- err return } // "Bearer" was added for dropbox. It happens to work also with Google Music's // OAuth. This may need to be changed to be protocol-specific in the future. t.TokenType = "Bearer" instance, err := prot.NewInstance(nil, t) if err != nil { c.done <- err return } srv.ch <- cmdProtocolAdd{ Name: c.name, Instance: instance, } c.done <- nil }() } setMinDuration := func(c cmdMinDuration) { srv.MinDuration = time.Duration(c) } doSeek := func(c cmdSeek) { if time.Duration(c) > srv.info.Time { return } srv.audioch <- c } setUsername := func(c cmdSetUsername) { srv.Username = string(c) } makeSource := func(protocol, key string) ([]*models.Source, error) { // name and key may be empty to match all var ss []*models.Source for prot, m := range srv.Protocols { if prot != protocol && protocol != "" { continue } for name, p := range m { if name != key && key != "" { continue } buf := new(bytes.Buffer) gw := gzip.NewWriter(buf) if err := gob.NewEncoder(gw).Encode(p); err != nil { return nil, err } if err := gw.Close(); err != nil { return nil, err } ss = append(ss, &models.Source{ Protocol: prot, Name: name, Blob: buf.Bytes(), }) } } return ss, nil } sendSource := func(ss []*models.Source) error { r, err := srv.request("/api/source/set", &ss) if err != nil { return err } r.Close() return nil } putSource := func(c cmdPutSource) { ss, err := makeSource(c.protocol, c.key) if err != nil { broadcastErr(fmt.Errorf("could not set sources: %v", err)) return } if err := sendSource(ss); err != nil { broadcastErr(fmt.Errorf("could not set sources: %v", err)) } } tokenRegister := func(c cmdTokenRegister) { srv.Token = "" if c != "" { srv.Token = string(c) ss, err := makeSource("", "") if err != nil { broadcastErr(err) return } go func() { if err := sendSource(ss); err != nil { srv.ch <- cmdError(err) return } r, err := srv.request("/api/source/get", nil) if err != nil { srv.ch <- cmdError(fmt.Errorf("could not get sources: %v", err)) return } defer r.Close() if err := json.NewDecoder(r).Decode(&ss); err != nil { srv.ch <- cmdError(err) return } srv.ch <- cmdSetSources(ss) }() } go func() { srv.ch <- cmdSetUsername("") if c == "" { return } r, err := srv.request("/api/username", nil) if err != nil { srv.ch <- cmdError(err) return } defer r.Close() var u cmdSetUsername if err := json.NewDecoder(r).Decode(&u); err != nil { srv.ch <- cmdError(err) return } srv.ch <- u }() } setSources := func(c cmdSetSources) { ps := protocol.Map() for _, s := range c { proto, err := protocol.ByName(s.Protocol) if err != nil { broadcastErr(err) return } if _, ok := ps[s.Protocol]; !ok { ps[s.Protocol] = make(map[string]protocol.Instance) } r, err := gzip.NewReader(bytes.NewReader(s.Blob)) if err != nil { broadcastErr(err) return } defer r.Close() p, err := proto.Decode(r) if err != nil { broadcastErr(err) return } ps[s.Protocol][s.Name] = p } srv.Protocols = ps go func() { // protocolRefresh uses srv.Protocols, so for i, s := range c { last := i == len(c)-1 srv.ch <- cmdProtocolRefresh{ protocol: s.Protocol, key: s.Name, list: true, doDelete: last, err: make(chan error, 1), } } }() } sendWaitData := func(c cmdWaitData) { c.done <- srv.makeWaitData(c.wt) } isRefreshing := make(map[codec.ID]bool) protocolRefresh := func(c cmdProtocolRefresh) { id := codec.NewID(c.protocol, c.key) if isRefreshing[id] { c.err <- nil return } inst, err := srv.getInstance(c.protocol, c.key) if err != nil { c.err <- err return } isRefreshing[id] = true go func() { defer func() { delete(isRefreshing, id) }() f := inst.Refresh if c.list { f = inst.List } songs, err := f() if err != nil { c.err <- err return } for k, v := range songs { if v.Time > 0 && v.Time < srv.MinDuration { delete(songs, k) } } if c.doDelete { srv.ch <- cmdRemoveDeleted{} } if srv.Token != "" { srv.ch <- cmdPutSource{ protocol: c.protocol, key: c.key, } } c.err <- nil }() } ch := make(chan interface{}) go func() { for c := range srv.ch { go func(c interface{}) { timer := time.AfterFunc(time.Second*10, func() { log.Printf("%T: %#v\n", c, c) panic("delay timer expired") }) ch <- c timer.Stop() }(c) } }() infoTimer() for { select { case <-timer: infoTimer() case c := <-ch: if c, ok := c.(cmdSetTime); ok { d := time.Duration(c) change := srv.elapsed - d if change < 0 { change = -change } srv.elapsed = d if change > time.Second { broadcast(waitStatus) } continue } save := true log.Printf("%T\n", c) switch c := c.(type) { case controlCmd: switch c { case cmdPlay: save = false play() case cmdStop: save = false stop() case cmdNext: next() case cmdPause: save = false pause() case cmdPrev: prev() case cmdRandom: srv.Random = !srv.Random case cmdRepeat: srv.Repeat = !srv.Repeat case cmdRestartSong: restart() default: panic(c) } case cmdPlayIdx: playIdx(c) case cmdPlayTrack: playTrack(c) case cmdProtocolRemove: protocolRemove(c) case cmdQueueChange: queueChange(c) case cmdPlaylistChange: playlistChange(c) case cmdNewWS: save = false newWS(c) case cmdDeleteWS: save = false deleteWS(c) case cmdDoSave: save = false doSave() case cmdAddOAuth: addOAuth(c) case cmdSeek: doSeek(c) save = false case cmdMinDuration: setMinDuration(c) case cmdTokenRegister: tokenRegister(c) case cmdSetUsername: setUsername(c) case cmdSetSources: setSources(c) case cmdProtocolAdd: protocolAdd(c) case cmdProtocolAddInstance: protocolAddInstance(c) case cmdRemoveDeleted: removeDeleted() case cmdRemoveInProgress: removeInProgress(c) case cmdError: broadcastErr(error(c)) save = false case cmdWaitData: sendWaitData(c) save = false case cmdPutSource: putSource(c) save = false case cmdProtocolRefresh: protocolRefresh(c) default: panic(c) } broadcast(waitStatus) if save { queueSave() } } } }
// makeWaitData should only be called by the commands() function. func (srv *Server) makeWaitData(wt waitType) *waitData { var data interface{} switch wt { case waitProtocols: protos := make(map[string][]string) for p, m := range srv.Protocols { for key := range m { protos[p] = append(protos[p], key) } } data = struct { Available map[string]protocol.Params Current map[string][]string InProgress map[codec.ID]bool }{ protocol.Get(), protos, srv.inprogress, } case waitStatus: hostname, _ := os.Hostname() data = &Status{ State: srv.state, Song: srv.songID, SongInfo: srv.info, Elapsed: srv.elapsed, Time: srv.info.Time, Random: srv.Random, Repeat: srv.Repeat, Username: srv.Username, Hostname: hostname, CentralURL: srv.centralURL, } case waitTracks: var songs []listItem for name, protos := range srv.Protocols { for key, inst := range protos { sl, _ := inst.List() for id, info := range sl { sid := SongID(codec.NewID(name, key, string(id))) songs = append(songs, listItem{ ID: sid, Info: info, }) } } } data = struct { Tracks []listItem }{ Tracks: songs, } case waitPlaylist: d := struct { Queue PlaylistInfo Playlists map[string]PlaylistInfo }{ Queue: srv.playlistInfo(srv.Queue), Playlists: make(map[string]PlaylistInfo), } for name, p := range srv.Playlists { d.Playlists[name] = srv.playlistInfo(p) } data = d default: data = fmt.Errorf("unknown type") } return &waitData{ Type: wt, Data: data, } }