1package commands
2
3import (
4	"fmt"
5
6	"github.com/ambientsound/gompd/mpd"
7	"github.com/ambientsound/pms/api"
8	"github.com/ambientsound/pms/input/lexer"
9)
10
11// Play plays songs in the MPD playlist.
12type Play struct {
13	newcommand
14	api       api.API
15	cursor    bool
16	selection bool
17}
18
19// NewPlay returns Play.
20func NewPlay(api api.API) Command {
21	return &Play{
22		api: api,
23	}
24}
25
26// Parse implements Command.
27func (cmd *Play) Parse() error {
28	tok, lit := cmd.ScanIgnoreWhitespace()
29
30	cmd.setTabCompleteVerbs(lit)
31
32	switch tok {
33	case lexer.TokenEnd:
34		// No parameters; just send 'play' command to MPD
35		return nil
36	case lexer.TokenIdentifier:
37	default:
38		return fmt.Errorf("Unexpected '%s', expected identifier", lit)
39	}
40
41	switch lit {
42	// Play song under cursor
43	case "cursor":
44		cmd.cursor = true
45	// Play selected songs
46	case "selection":
47		cmd.selection = true
48	default:
49		return fmt.Errorf("Unexpected '%s', expected identifier", lit)
50	}
51
52	cmd.setTabCompleteEmpty()
53
54	return cmd.ParseEnd()
55}
56
57// Exec implements Command.
58func (cmd *Play) Exec() error {
59
60	// Ensure MPD connection.
61	client := cmd.api.MpdClient()
62	if client == nil {
63		return fmt.Errorf("Cannot play: not connected to MPD")
64	}
65
66	switch {
67	case cmd.cursor:
68		// Play song under cursor.
69		return cmd.playCursor(client)
70	case cmd.selection:
71		// Play selected songs.
72		return cmd.playSelection(client)
73	}
74
75	// If a selection is not given, start playing with default parameters.
76	return client.Play(-1)
77}
78
79// playCursor plays the song under the cursor.
80func (cmd *Play) playCursor(client *mpd.Client) error {
81
82	// Get the song under the cursor.
83	song := cmd.api.Songlist().CursorSong()
84	if song == nil {
85		return fmt.Errorf("Cannot play: no song under cursor")
86	}
87
88	// Check if the currently selected song has an ID. If it doesn't, it's not
89	// from the queue, and the song will have to be added beforehand.
90	id := song.ID
91	if song.NullID() {
92		var err error
93		id, err = client.AddID(song.StringTags["file"], -1)
94		if err != nil {
95			return err
96		}
97	}
98
99	// Play the correct song.
100	return client.PlayID(id)
101}
102
103// playSelection plays the currently selected songs.
104func (cmd *Play) playSelection(client *mpd.Client) error {
105
106	// Get the track selection.
107	selection := cmd.api.Songlist().Selection()
108	if selection.Len() == 0 {
109		return fmt.Errorf("Cannot play: no selection")
110	}
111
112	// Check if the first song has an ID. If it does, just start playing. The
113	// playback order cannot be guaranteed as the selection might be
114	// fragmented, so don't touch the selection.
115	first := selection.Song(0)
116	if !first.NullID() {
117		return client.PlayID(first.ID)
118	}
119
120	// We are not operating directly on the queue; add all songs to the queue now.
121	queue := cmd.api.Queue()
122	queueLen := queue.Len()
123	err := queue.AddList(selection)
124	if err != nil {
125		return err
126	}
127	cmd.api.Songlist().ClearSelection()
128	cmd.api.Message("Playing %d new songs", selection.Len())
129
130	// We haven't got the ID from the first added song, so use positions
131	// instead. In case of simultaneous operation with another client, this
132	// might lead to a race condition. Ignore this for now.
133	return client.Play(queueLen)
134}
135
136// setTabCompleteVerbs sets the tab complete list to the list of available sub-commands.
137func (cmd *Play) setTabCompleteVerbs(lit string) {
138	cmd.setTabComplete(lit, []string{
139		"cursor",
140		"selection",
141	})
142}
143