Move modules to a separate dir.
[nephilim.git] / nephilim / mpclient.py
blob26b4d13b83da75e42f6f96a568d813a933009dbb
1 from PyQt4 import QtCore
2 from clSong import Song
3 from traceback import print_exc
4 from misc import *
5 import mpd
6 from threading import Thread
8 class MPClient(QtCore.QObject):
9 """This class offers another layer above pympd, with usefull events."""
10 _client = None # MPD client
11 _listeners = None # array of listeners: { event: (listeners)* }
13 # cached objects
14 _curLib = None
15 _curPlaylist = None
16 _curSong = None
18 # objects used for comparison with previous value
19 _curSongID = None
20 _curTime = None
21 _curState = None
22 _curVolume = None
23 _updatings_db = None
24 _cur_plist_id = None
26 _timerID=None
29 events={
30 'beforeSongChange':'curSongID',
31 'onSongChange':'oldSongID, newSongID',
32 'onTimeChange':'oldTime, newTime',
33 'onStateChange':'oldState, newState',
34 'onVolumeChange':'oldVolume, newVolume',
35 'onConnect':'',
36 'onDisconnect':'',
37 'onReady':'', # when connected, and initialisation is ready
38 'onUpdateDBStart':'', # start updating database
39 'onUpdateDBFinish':'', # when updating database has finished
40 'onPlaylistChange' : '',
43 def __init__(self):
44 QtCore.QObject.__init__(self)
45 self._client=None
46 self._listeners={}
48 self._curSongID=-1
49 self._curTime=-1
50 self._curState=-1
51 self._curVolume=-1
52 self._curLib=[]
53 self._curPlaylist=[]
54 self._cur_plist_id = -1
56 for event in self.events:
57 self._listeners[event]=[]
59 def connect(self, host, port):
60 """Connect to MPD@$host:$port. Returns true at success, false otherwise."""
61 if self._client:
62 return
63 try:
64 self._client = mpd.MPDClient()
65 self._client.connect(host, port)
66 except IOError:
67 self._client=None
68 return False
70 self._raiseEvent('onConnect', None)
71 try:
72 self._updateLib()
73 self._updatePlaylist()
74 self._updateCurrentSong()
75 self._timerID=self.startTimer(500)
76 except Exception:
77 print_exc()
78 self._raiseEvent('onStateChange', {'oldState':'stop', 'newState':self.getStatus()['state']})
79 self._raiseEvent('onReady', None)
80 doEvents()
81 return True
83 def disconnect(self):
84 """Disconnect from MPD."""
85 if self._client:
86 self._client.close()
87 self._client.disconnect()
88 self._client=None
89 # don't kill timer, as it'll happen in timerEvent
91 def isConnected(self):
92 """Returns true if we're connected to MPD, false otherwise."""
93 return self._client != None
95 def listPlaylist(self):
96 """Returns the current playlist."""
97 if not self.isConnected():
98 return []
99 return self._curPlaylist
101 def listLibrary(self):
102 """Returns the library."""
103 if not self.isConnected():
104 return []
105 return self._curLib
107 def getCurrentSong(self):
108 """Returns the current playing song."""
109 if self.isConnected()==False:
110 return None
111 return self._curSong
113 def updateDB(self, paths = None):
114 if not paths:
115 return self._client.update()
116 self._client.command_list_ok_begin()
117 for path in paths:
118 self._client.update(path)
119 self._client.command_list_end()
121 def getStatus(self):
122 """Returns the status."""
123 try:
124 if self.isConnected()==False:
125 return None
126 ret = self._retrieve(self._client.status)
127 if 'time' in ret:
128 len=int(ret['time'][ret['time'].find(':')+1:])
129 cur=int(ret['time'][:ret['time'].find(':')])
130 ret['length']=len
131 ret['time']=cur
132 else:
133 ret['length'] = 0
134 ret['time'] = 0
135 return ret
136 except Exception, d:
137 print_exc()
138 return None
140 def get_outputs(self):
141 """returns audio outputs"""
142 if self.isConnected():
143 return self._retrieve(self._client.outputs)
144 else:
145 return []
146 def set_output(self, output_id, state):
147 """set audio output output_id to state"""
148 if state:
149 self._client.enableoutput(output_id)
150 else:
151 self._client.disableoutput(output_id)
153 def urlhandlers(self):
154 if not self.isConnected():
155 return []
156 else:
157 return self._client.urlhandlers()
159 def repeat(self, val):
160 if isinstance(val, bool):
161 val = 1 if val else 0
162 self._client.repeat(val)
163 def random(self,val):
164 if isinstance(val, bool):
165 val = 1 if val else 0
166 self._client.random(val)
168 _retrMutex=QtCore.QMutex()
169 def _retrieve(self, method):
170 """Makes sure only one call is made at a time to MPD."""
171 self._retrMutex.lock()
172 try:
173 ret=method()
174 except:
175 self._retrMutex.unlock()
176 raise
178 self._retrMutex.unlock()
179 return ret
181 def isPlaying(self):
182 return self.getStatus()['state'] == 'play'
184 def play(self, id):
185 """Play song with ID $id."""
186 self._playCalled = True
187 if id:
188 self._client.playid(id)
189 else:
190 self._client.playid()
192 def pause(self):
193 """Pause playing."""
194 self._client.pause(1)
195 def resume(self):
196 """Resume playing."""
197 self._client.pause(0)
198 def next(self):
199 """Move on to the next song in the playlist."""
200 self._playCalled = False
201 self._raiseEvent('beforeSongChange', {'curSongID': self._curSongID})
202 # we only switch to the next song, if some of beforeSongChange's listeners
203 # didn't explicitly call play. If it did, then it ain't good to immediatly
204 # skip to the next song!
205 if not self._playCalled:
206 self._client.next()
207 def previous(self):
208 """Move back to the previous song in the playlist."""
209 self._client.previous()
210 def stop(self):
211 """Stop playing."""
212 self._client.stop()
214 def seek(self, time):
215 """Move the current playing time to $time."""
216 if self._curSongID > 0:
217 self._client.seekid(self._curSongID, time)
219 def deleteFromPlaylist(self, list):
220 """Remove all songIDs in $list from the playlist."""
221 self._client.command_list_ok_begin()
222 for id in list:
223 self._client.deleteid(id)
224 self._client.command_list_end()
225 self._updatePlaylist()
226 def clear_playlist(self):
227 """Removes all songs from current playlist."""
228 self._client.clear()
229 self._updatePlaylist()
231 def addToPlaylist(self, paths):
232 """Add all files in $paths to the current playlist."""
233 try:
234 self._client.command_list_ok_begin()
235 for path in paths:
236 self._client.addid(unicode(path))
237 ret = self._client.command_list_end()
238 self._updatePlaylist()
239 if self._curState == 'stop':
240 self.play(ret[0])
241 except mpd.CommandError:
242 logging.error('Cannot add some files, check permissions.')
244 def setVolume(self, volume):
245 """Set volumne to $volume."""
246 volume=min(100, max(0, volume))
247 self._client.setvol(volume)
248 def getVolume(self):
249 return int(self.getStatus()['volume'])
251 def add_listener(self, event, callback):
252 """Add $callback to the listeners for $event."""
253 if not(event in self.events):
254 raise Exception("Unknown event "+event)
255 self._listeners[event].append(callback)
256 def removeListener(self, event, callback):
257 if not(event in self.events):
258 raise Exception("Unknown event "+event)
259 self._listeners[event].remove(callback)
262 def _updateLib(self):
263 """Update the library."""
264 self._curLib=self._arrayToSongArray(self._retrieve(self._client.listallinfo))
265 id=0
266 for song in self._curLib:
267 song._data['id']=id
268 id+=1
269 def _updatePlaylist(self):
270 """Update the playlist."""
271 self._curPlaylist=self._arrayToSongArray(self._retrieve(self._client.playlistinfo))
272 def _arrayToSongArray(self, array):
273 """Convert an array to an array of Songs."""
274 return map(lambda entry: Song(entry)
275 , filter(lambda entry: not('directory' in entry), array)
277 def _updateCurrentSong(self):
278 """Update the current song."""
279 song = self._retrieve(self._client.currentsong)
280 if not song:
281 self._curSong = None
282 else:
283 self._curSong = Song(song)
285 class simpleThread(Thread):
286 callback=None
287 params=None
288 def __init__(self,callback,params):
289 Thread.__init__(self)
290 self.callback=callback
291 self.params=params
292 def run(self):
293 self.callback(self.params)
295 def _raiseEvent(self, event, params):
296 """Call all listeners for $event with parameters $params."""
297 if not(event in self.events):
298 raise Exception("Unknown raised event "+event)
300 for listener in self._listeners[event]:
301 try:
302 self.simpleThread(listener, params).run()
303 except:
304 print_exc()
306 def timerEvent(self, event):
307 "Check for changes since last check."
308 status = self.getStatus()
310 if status == None:
311 self._client=None
312 self._raiseEvent('onDisconnect', None)
313 self.killTimer(self._timerID)
314 return
316 self._updateCurrentSong()
317 " check if song has changed"
318 song = self._curSong
319 if song:
320 curID = song.getID()
321 else:
322 curID = -1
324 if curID != self._curSongID:
325 self._raiseEvent('onSongChange', {'oldSongID':self._curSongID, 'newSongID':curID})
326 self._curSongID = curID
328 " check if the time has changed"
329 if 'time' in status:
330 curTime=status['time']
331 if curTime!=self._curTime:
332 self._raiseEvent('onTimeChange', {'oldTime':self._curTime, 'newTime':curTime})
333 self._curTime=curTime
334 if curTime>=status['length']-1:
335 self._raiseEvent('beforeSongChange', {'curSongID':curID})
337 " check if the playing state has changed"
338 if 'state' in status:
339 curState=status['state']
340 if curState!=self._curState:
341 self._raiseEvent('onStateChange', {'oldState':self._curState, 'newState':curState})
342 self._curState=curState
344 " check if the volume has changed"
345 if 'volume' in status:
346 curVolume=int(status['volume'])
347 if curVolume!=self._curVolume:
348 self._raiseEvent('onVolumeChange', {'oldVolume':self._curVolume, 'newVolume':curVolume})
349 self._curVolume=curVolume
351 if 'playlist' in status:
352 cur_plist_id = int(status['playlist'])
353 if cur_plist_id != self._cur_plist_id:
354 self._updatePlaylist()
355 self._raiseEvent('onPlaylistChange', None)
356 self._cur_plist_id = cur_plist_id
358 " update has started"
359 if 'updatings_db' in status and self._updatings_db == None:
360 self._updatings_db = status['updatings_db']
361 self._raiseEvent('onUpdateDBStart', {})
362 if not('updatings_db' in status) and self._updatings_db:
363 self._updatings_db = None
364 self._raiseEvent('onUpdateDBFinish')