winMain: correctly update state messages.
[nephilim.git] / clMonty.py
blobaf0f74d14e4a326b6fccdf88d2628a4c14f78ded
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 Monty(QtCore.QObject):
9 """The Monty 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
25 _timerID=None
28 events={
29 'beforeSongChange':'curSongID',
30 'onSongChange':'oldSongID, newSongID',
31 'onTimeChange':'oldTime, newTime',
32 'onStateChange':'oldState, newState',
33 'onVolumeChange':'oldVolume, newVolume',
34 'onConnect':'',
35 'onDisconnect':'',
36 'onReady':'', # when connected, and initialisation is ready
37 'onUpdateDBStart':'', # start updating database
38 'onUpdateDBFinish':'', # when updating database has finished
41 def __init__(self):
42 QtCore.QObject.__init__(self)
43 self._client=None
44 self._listeners={}
46 self._curSongID=-1
47 self._curTime=-1
48 self._curState=-1
49 self._curVolume=-1
50 self._curLib=[]
51 self._curPlaylist=[]
53 for event in self.events:
54 self._listeners[event]=[]
56 def connect(self, host, port):
57 """Connect to MPD@$host:$port. Returns true at success, false otherwise."""
58 if self._client:
59 return
60 try:
61 self._client = mpd.MPDClient()
62 self._client.connect(host, port)
63 except:
64 self._client=None
65 return False
67 self._raiseEvent('onConnect', None)
68 try:
69 self._updateLib()
70 self._updatePlaylist()
71 self._updateCurrentSong()
72 self._timerID=self.startTimer(500)
73 except Exception:
74 print_exc()
75 self._raiseEvent('onStateChange', {'oldState':'stop', 'newState':self.getStatus()['state']})
76 self._raiseEvent('onReady', None)
77 doEvents()
78 return True
80 def disconnect(self):
81 """Disconnect from MPD."""
82 if self._client:
83 self._client.close()
84 self._client.disconnect()
85 self._client=None
86 # don't kill timer, as it'll happen in timerEvent
88 def isConnected(self):
89 """Returns true if we're connected to MPD, false otherwise."""
90 return self._client!=None
92 def listPlaylist(self):
93 """Returns the current playlist."""
94 if self.isConnected()==False:
95 return None
96 return self._curPlaylist
98 def listLibrary(self):
99 """Returns the library."""
100 if self.isConnected()==False:
101 return None
102 return self._curLib
104 def getCurrentSong(self):
105 """Returns the current playing song."""
106 if self.isConnected()==False:
107 return None
108 return self._curSong
110 def updateDB(self, paths):
111 self._client.command_list_ok_begin()
112 for path in paths:
113 self._client.update(path)
114 self._client.command_list_end()
116 def getStatus(self):
117 """Returns the status."""
118 try:
119 if self.isConnected()==False:
120 return None
121 ret=self._retrieve(self._client.status)
122 if 'time' in ret:
123 len=int(ret['time'][ret['time'].find(':')+1:])
124 cur=int(ret['time'][:ret['time'].find(':')])
125 ret['length']=len
126 ret['time']=cur
127 return ret
128 except Exception, d:
129 print_exc()
130 return None
132 def repeat(self,val):
133 self._client.repeat(val)
134 def random(self,val):
135 self._client.random(val)
137 _retrMutex=QtCore.QMutex()
138 def _retrieve(self, method):
139 """Makes sure only one call is made at a time to MPD."""
140 self._retrMutex.lock()
141 try:
142 ret=method()
143 except:
144 self._retrMutex.unlock()
145 raise
147 self._retrMutex.unlock()
148 return ret
150 def isPlaying(self):
151 return self.getStatus()['state']=='play'
153 def play(self, id):
154 """Play song with ID $id."""
155 self._playCalled=True
156 if id!=None:
157 self._client.playid(id)
158 else:
159 self._client.playid()
161 def pause(self):
162 """Pause playing."""
163 self._client.pause(1)
164 def resume(self):
165 """Resume playing."""
166 self._client.pause(0)
167 def next(self):
168 """Move on to the next song in the playlist."""
169 self._playCalled=False
170 self._raiseEvent('beforeSongChange', {'curSongID': self._curSongID})
171 # we only switch to the next song, if some of beforeSongChange's listeners
172 # didn't explicitly call play. If it did, then it ain't good to immediatly
173 # skip to the next song!
174 if not self._playCalled:
175 self._client.next()
176 def previous(self):
177 """Move back to the previous song in the playlist."""
178 self._client.previous()
179 def stop(self):
180 """Stop playing."""
181 self._client.stop()
183 def seek(self, time):
184 """Move the current playing time to $time."""
185 self._client.seekid(self._curSongID, time)
187 def deleteFromPlaylist(self, list):
188 """Remove all songIDs in $list from the playlist."""
189 self._client.command_list_ok_begin()
190 for id in list:
191 self._client.deleteid(id)
192 self._client.command_list_end()
193 self._updatePlaylist()
194 def clear_playlist(self):
195 """Removes all songs from current playlist."""
196 self._client.clear()
197 self._updatePlaylist()
199 def addToPlaylist(self, paths):
200 """Add all files in $paths to the current playlist."""
201 self._client.command_list_ok_begin()
202 for path in paths:
203 self._client.addid(path)
204 ret = self._client.command_list_end()
205 self._updatePlaylist()
206 return ret
208 def setVolume(self, volume):
209 """Set volumne to $volume."""
210 volume=min(100, max(0, volume))
211 self._client.setvol(volume)
212 def getVolume(self):
213 return int(self.getStatus()['volume'])
215 def addListener(self, event, callback):
216 """Add $callback to the listeners for $event."""
217 if not(event in self.events):
218 raise Exception("Unknown event "+event)
219 self._listeners[event].append(callback)
220 def removeListener(self, event, callback):
221 if not(event in self.events):
222 raise Exception("Unknown event "+event)
223 self._listeners[event].remove(callback)
226 def _updateLib(self):
227 """Update the library."""
228 self._curLib=self._arrayToSongArray(self._retrieve(self._client.listallinfo))
229 id=0
230 for song in self._curLib:
231 song._data['id']=id
232 id+=1
233 def _updatePlaylist(self):
234 """Update the playlist."""
235 self._curPlaylist=self._arrayToSongArray(self._retrieve(self._client.playlistinfo))
236 def _arrayToSongArray(self, array):
237 """Convert an array to an array of Songs."""
238 return map(lambda entry: Song(entry)
239 , filter(lambda entry: not('directory' in entry), array)
241 def _updateCurrentSong(self):
242 """Update the current song."""
243 song = self._retrieve(self._client.currentsong)
244 if not song:
245 self._curSong = None
246 else:
247 self._curSong = Song(song)
249 class simpleThread(Thread):
250 callback=None
251 params=None
252 def __init__(self,callback,params):
253 Thread.__init__(self)
254 self.callback=callback
255 self.params=params
256 def run(self):
257 self.callback(self.params)
259 def _raiseEvent(self, event, params):
260 """Call all listeners for $event with parameters $params."""
261 if not(event in self.events):
262 raise Exception("Unknown raised event "+event)
264 for listener in self._listeners[event]:
265 try:
266 self.simpleThread(listener, params).run()
267 except:
268 print_exc()
270 def timerEvent(self, event):
271 "Check for changes since last check."
272 try:
273 self._updateCurrentSong()
274 status = self.getStatus()
275 except:
276 self._curSong=None
277 status = None
279 if status == None:
280 self._client=None
281 self._raiseEvent('onDisconnect', None)
282 self.killTimer(self._timerID)
283 return
285 " check if song has changed"
286 song = self._curSong
287 if song:
288 curID = song.getID()
289 else:
290 curID = -1
292 if curID != self._curSongID:
293 self._raiseEvent('onSongChange', {'oldSongID':self._curSongID, 'newSongID':curID})
294 self._curSongID = curID
296 " check if the time has changed"
297 if 'time' in status:
298 curTime=status['time']
299 if curTime!=self._curTime:
300 self._raiseEvent('onTimeChange', {'oldTime':self._curTime, 'newTime':curTime})
301 self._curTime=curTime
302 if curTime>=status['length']-1:
303 self._raiseEvent('beforeSongChange', {'curSongID':curID})
305 " check if the playing state has changed"
306 if 'state' in status:
307 curState=status['state']
308 if curState!=self._curState:
309 self._raiseEvent('onStateChange', {'oldState':self._curState, 'newState':curState})
310 self._curState=curState
312 " check if the volume has changed"
313 if 'volume' in status:
314 curVolume=int(status['volume'])
315 if curVolume!=self._curVolume:
316 self._raiseEvent('onVolumeChange', {'oldVolume':self._curVolume, 'newVolume':curVolume})
317 self._curVolume=curVolume
320 " update has started"
321 if 'updatings_db' in status and self._updatings_db==None:
322 self._updatings_db=status['updatings_db']
323 self._raiseEvent('onUpdateDBStart', {})
324 if not('updatings_db' in status) and self._updatings_db:
325 self._updatings_db=None
326 self._raiseEvent('onUpdateDBFinish')