neo_doxygen: Add a class to manage brief descriptions.
[nit.git] / lib / mpd.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Client for a MPD server
18 module mpd
19
20 import socket
21
22 # Connection to a MPD server
23 class MPDConnection
24
25 # Socket connection to server.
26 var socket: nullable Socket = null
27
28 # Server hostname.
29 var host: String
30
31 # Server port.
32 var port: Int
33
34 # Server password.
35 var password: nullable String
36
37 # Last occured error.
38 var error: nullable String = null
39
40 # Connect to the MPD server
41 fun connect
42 do
43 var p: nullable Socket = null
44
45 p = new Socket.client(host, port)
46
47 sys.nanosleep(0,5000)
48
49 var rep = p.read(1024)
50 assert not rep.is_empty
51 if not rep.has_prefix("OK") then
52 print "MPD responded {rep}"
53 abort
54 end
55
56 socket = p
57
58 var password = password
59 if password != null then
60 write_and_check("password {password}\n")
61 end
62 end
63
64 # Write a command to the MPD server
65 protected fun write_and_check(msg: String)
66 do
67 if socket == null then connect
68
69 socket.write(msg)
70 sys.nanosleep(0,5000)
71 var rep = socket.read(1024)
72 if not rep.has_prefix("OK") then
73 print "Error: MPD responded {rep}"
74 socket.close
75 socket = null
76 end
77 end
78
79 # Get MPD server status
80 fun status: nullable ServerStatus
81 do
82 if socket == null then connect
83
84 var volume: nullable Int = null
85 var state: nullable String = null
86 var elapsed: nullable Int = null
87 var time_at: nullable Int = null
88 var time_total: nullable Int = null
89
90 # get current status
91 socket.write("status\n")
92 var rep = socket.read(1024)
93 for l in rep.split_with("\n") do
94 var words = l.split_with(" ")
95 if words.length > 1 then
96 var key = words[0].to_lower
97 var first_space = l.chars.index_of(' ')
98 var rest = l.substring_from(first_space+1)
99 if key == "volume:" then
100 volume = rest.to_i
101 if volume == -1 then volume = null
102 else if key == "state:" then
103 state = rest
104 else if key == "elapsed:" then
105 elapsed = rest.to_i
106 else if key == "time:" then
107 var times = rest.split(":")
108 time_at = times[0].to_i
109 time_total = times[1].to_i
110 end
111 end
112 end
113
114 if state != null then
115 var status = new ServerStatus(volume, state, elapsed, time_at, time_total)
116 return status
117 else
118 return null
119 end
120 end
121
122 # Set the volume relatively
123 fun relative_volume=(diff: Int)
124 do
125 if socket == null then connect
126
127 var status = status
128 if status != null then
129 var vol = status.volume
130 if vol != null then
131 var new_vol = vol + diff
132 new_vol = new_vol.max(0).min(100)
133 volume = new_vol
134 return
135 end
136 end
137
138 error = "Cannot get volume"
139 end
140
141 # Set MPD server volume.
142 fun volume=(vol: Int) do write_and_check("setvol {vol}\n")
143
144 # Pause music playing on the MPD server
145 fun pause do write_and_check("pause\n")
146
147 # Stop music playing on the MPD server
148 fun stop do write_and_check("stop\n")
149
150 # Play music playing on the MPD server
151 fun play do write_and_check("play\n")
152
153 # Get information on the currently playing song on the MPD server
154 fun current_song: nullable SongInfo
155 do
156 if socket == null then connect
157
158 var album: nullable String = null
159 var artist: nullable String = null
160 var title: nullable String = null
161 var time: nullable Int = null
162
163 socket.write("currentsong\n")
164 var rep = socket.read(1024)
165 for l in rep.split_with("\n") do
166 var words = l.split_with(" ")
167 if words.length > 1 then
168 var key = words[0].to_lower
169 var first_space = l.chars.index_of(' ')
170 var rest = l.substring_from(first_space+1)
171 if key == "album:" then
172 album = rest
173 else if key == "artist:" then
174 artist = rest
175 else if key == "title:" then
176 title = rest
177 else if key == "time:" then
178 time = rest.to_i
179 end
180 end
181 end
182
183 if album != null and artist != null and
184 title != null and time != null then
185 return new SongInfo(album, artist, title, time)
186 else
187 return null
188 end
189 end
190
191 # Load playlist named `name`.
192 fun load_playlist(name: String)
193 do
194 write_and_check "load \"{name}\""
195 end
196 end
197
198 # MPD song info
199 class SongInfo
200 # Song album.
201 var album: String
202
203 # Song artist.
204 var artist: String
205
206 # Song title.
207 var title: String
208
209 # Song total duration.
210 var time: Int
211 end
212
213 # MPD server status
214 class ServerStatus
215
216 # MPD server volume.
217 var volume: nullable Int
218
219 # Playback state (play/stop/pause).
220 var state: String
221
222 # Is MPD server playing?
223 fun playing: Bool do return state == "play"
224
225 # Is MPD server stopped?
226 fun stopped: Bool do return state == "stop"
227
228 # Time elapsed within the current song.
229 var elapsed: nullable Int
230
231 # TODO comment
232 var time_at: nullable Int
233
234 # Total time of the current song.
235 var time_total: nullable Int
236
237 # Get the cursor position on the song duration.
238 fun time_ratio: nullable Float do
239 if time_at == null or time_total == null then return null
240 return time_at.to_f / time_total.to_f
241 end
242
243 redef fun to_s do
244 var vol = "unknown"
245 if volume != null then vol = volume.to_s
246
247 var time_at = time_at
248 var time_total = time_total
249 var elapsed = elapsed
250 if time_at != null and time_total != null and elapsed != null then
251 return "volume: {vol}\nstate: {state}\nelapsed: {elapsed}\ntime_[at|total]: {time_at}:{time_total}\ntime_ratio: {time_ratio.as(not null)}"
252 else
253 return "volume: {vol}\nstate: {state}"
254 end
255 end
256 end