Example #1
0
File: server.go Project: shazow/mog
func New(stateFile, central string) (*Server, error) {
	srv := Server{
		ch:          make(chan interface{}),
		audioch:     make(chan interface{}),
		Protocols:   protocol.Map(),
		Playlists:   make(map[string]Playlist),
		MinDuration: time.Second * 30,
		centralURL:  central,
		inprogress:  make(map[codec.ID]bool),
	}
	db, err := bolt.Open(stateFile, 0600, nil)
	if err != nil {
		return nil, err
	}
	srv.db = db
	if err := srv.restore(); err != nil {
		log.Println(err)
	}
	log.Println("started from", stateFile)
	go srv.commands()
	go srv.audio()
	return &srv, nil
}
Example #2
0
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()
			}
		}
	}
}