diff options
Diffstat (limited to 'nephilim/mpclient.py')
-rw-r--r-- | nephilim/mpclient.py | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/nephilim/mpclient.py b/nephilim/mpclient.py new file mode 100644 index 0000000..26b4d13 --- /dev/null +++ b/nephilim/mpclient.py @@ -0,0 +1,364 @@ +from PyQt4 import QtCore +from clSong import Song +from traceback import print_exc +from misc import * +import mpd +from threading import Thread + +class MPClient(QtCore.QObject): + """This class offers another layer above pympd, with usefull events.""" + _client = None # MPD client + _listeners = None # array of listeners: { event: (listeners)* } + + # cached objects + _curLib = None + _curPlaylist = None + _curSong = None + + # objects used for comparison with previous value + _curSongID = None + _curTime = None + _curState = None + _curVolume = None + _updatings_db = None + _cur_plist_id = None + + _timerID=None + + + events={ + 'beforeSongChange':'curSongID', + 'onSongChange':'oldSongID, newSongID', + 'onTimeChange':'oldTime, newTime', + 'onStateChange':'oldState, newState', + 'onVolumeChange':'oldVolume, newVolume', + 'onConnect':'', + 'onDisconnect':'', + 'onReady':'', # when connected, and initialisation is ready + 'onUpdateDBStart':'', # start updating database + 'onUpdateDBFinish':'', # when updating database has finished + 'onPlaylistChange' : '', + } + + def __init__(self): + QtCore.QObject.__init__(self) + self._client=None + self._listeners={} + + self._curSongID=-1 + self._curTime=-1 + self._curState=-1 + self._curVolume=-1 + self._curLib=[] + self._curPlaylist=[] + self._cur_plist_id = -1 + + for event in self.events: + self._listeners[event]=[] + + def connect(self, host, port): + """Connect to MPD@$host:$port. Returns true at success, false otherwise.""" + if self._client: + return + try: + self._client = mpd.MPDClient() + self._client.connect(host, port) + except IOError: + self._client=None + return False + + self._raiseEvent('onConnect', None) + try: + self._updateLib() + self._updatePlaylist() + self._updateCurrentSong() + self._timerID=self.startTimer(500) + except Exception: + print_exc() + self._raiseEvent('onStateChange', {'oldState':'stop', 'newState':self.getStatus()['state']}) + self._raiseEvent('onReady', None) + doEvents() + return True + + def disconnect(self): + """Disconnect from MPD.""" + if self._client: + self._client.close() + self._client.disconnect() + self._client=None + # don't kill timer, as it'll happen in timerEvent + + def isConnected(self): + """Returns true if we're connected to MPD, false otherwise.""" + return self._client != None + + def listPlaylist(self): + """Returns the current playlist.""" + if not self.isConnected(): + return [] + return self._curPlaylist + + def listLibrary(self): + """Returns the library.""" + if not self.isConnected(): + return [] + return self._curLib + + def getCurrentSong(self): + """Returns the current playing song.""" + if self.isConnected()==False: + return None + return self._curSong + + def updateDB(self, paths = None): + if not paths: + return self._client.update() + self._client.command_list_ok_begin() + for path in paths: + self._client.update(path) + self._client.command_list_end() + + def getStatus(self): + """Returns the status.""" + try: + if self.isConnected()==False: + return None + ret = self._retrieve(self._client.status) + if 'time' in ret: + len=int(ret['time'][ret['time'].find(':')+1:]) + cur=int(ret['time'][:ret['time'].find(':')]) + ret['length']=len + ret['time']=cur + else: + ret['length'] = 0 + ret['time'] = 0 + return ret + except Exception, d: + print_exc() + return None + + def get_outputs(self): + """returns audio outputs""" + if self.isConnected(): + return self._retrieve(self._client.outputs) + else: + return [] + def set_output(self, output_id, state): + """set audio output output_id to state""" + if state: + self._client.enableoutput(output_id) + else: + self._client.disableoutput(output_id) + + def urlhandlers(self): + if not self.isConnected(): + return [] + else: + return self._client.urlhandlers() + + def repeat(self, val): + if isinstance(val, bool): + val = 1 if val else 0 + self._client.repeat(val) + def random(self,val): + if isinstance(val, bool): + val = 1 if val else 0 + self._client.random(val) + + _retrMutex=QtCore.QMutex() + def _retrieve(self, method): + """Makes sure only one call is made at a time to MPD.""" + self._retrMutex.lock() + try: + ret=method() + except: + self._retrMutex.unlock() + raise + + self._retrMutex.unlock() + return ret + + def isPlaying(self): + return self.getStatus()['state'] == 'play' + + def play(self, id): + """Play song with ID $id.""" + self._playCalled = True + if id: + self._client.playid(id) + else: + self._client.playid() + + def pause(self): + """Pause playing.""" + self._client.pause(1) + def resume(self): + """Resume playing.""" + self._client.pause(0) + def next(self): + """Move on to the next song in the playlist.""" + self._playCalled = False + self._raiseEvent('beforeSongChange', {'curSongID': self._curSongID}) + # we only switch to the next song, if some of beforeSongChange's listeners + # didn't explicitly call play. If it did, then it ain't good to immediatly + # skip to the next song! + if not self._playCalled: + self._client.next() + def previous(self): + """Move back to the previous song in the playlist.""" + self._client.previous() + def stop(self): + """Stop playing.""" + self._client.stop() + + def seek(self, time): + """Move the current playing time to $time.""" + if self._curSongID > 0: + self._client.seekid(self._curSongID, time) + + def deleteFromPlaylist(self, list): + """Remove all songIDs in $list from the playlist.""" + self._client.command_list_ok_begin() + for id in list: + self._client.deleteid(id) + self._client.command_list_end() + self._updatePlaylist() + def clear_playlist(self): + """Removes all songs from current playlist.""" + self._client.clear() + self._updatePlaylist() + + def addToPlaylist(self, paths): + """Add all files in $paths to the current playlist.""" + try: + self._client.command_list_ok_begin() + for path in paths: + self._client.addid(unicode(path)) + ret = self._client.command_list_end() + self._updatePlaylist() + if self._curState == 'stop': + self.play(ret[0]) + except mpd.CommandError: + logging.error('Cannot add some files, check permissions.') + + def setVolume(self, volume): + """Set volumne to $volume.""" + volume=min(100, max(0, volume)) + self._client.setvol(volume) + def getVolume(self): + return int(self.getStatus()['volume']) + + def add_listener(self, event, callback): + """Add $callback to the listeners for $event.""" + if not(event in self.events): + raise Exception("Unknown event "+event) + self._listeners[event].append(callback) + def removeListener(self, event, callback): + if not(event in self.events): + raise Exception("Unknown event "+event) + self._listeners[event].remove(callback) + + + def _updateLib(self): + """Update the library.""" + self._curLib=self._arrayToSongArray(self._retrieve(self._client.listallinfo)) + id=0 + for song in self._curLib: + song._data['id']=id + id+=1 + def _updatePlaylist(self): + """Update the playlist.""" + self._curPlaylist=self._arrayToSongArray(self._retrieve(self._client.playlistinfo)) + def _arrayToSongArray(self, array): + """Convert an array to an array of Songs.""" + return map(lambda entry: Song(entry) + , filter(lambda entry: not('directory' in entry), array) + ) + def _updateCurrentSong(self): + """Update the current song.""" + song = self._retrieve(self._client.currentsong) + if not song: + self._curSong = None + else: + self._curSong = Song(song) + + class simpleThread(Thread): + callback=None + params=None + def __init__(self,callback,params): + Thread.__init__(self) + self.callback=callback + self.params=params + def run(self): + self.callback(self.params) + + def _raiseEvent(self, event, params): + """Call all listeners for $event with parameters $params.""" + if not(event in self.events): + raise Exception("Unknown raised event "+event) + + for listener in self._listeners[event]: + try: + self.simpleThread(listener, params).run() + except: + print_exc() + + def timerEvent(self, event): + "Check for changes since last check." + status = self.getStatus() + + if status == None: + self._client=None + self._raiseEvent('onDisconnect', None) + self.killTimer(self._timerID) + return + + self._updateCurrentSong() + " check if song has changed" + song = self._curSong + if song: + curID = song.getID() + else: + curID = -1 + + if curID != self._curSongID: + self._raiseEvent('onSongChange', {'oldSongID':self._curSongID, 'newSongID':curID}) + self._curSongID = curID + + " check if the time has changed" + if 'time' in status: + curTime=status['time'] + if curTime!=self._curTime: + self._raiseEvent('onTimeChange', {'oldTime':self._curTime, 'newTime':curTime}) + self._curTime=curTime + if curTime>=status['length']-1: + self._raiseEvent('beforeSongChange', {'curSongID':curID}) + + " check if the playing state has changed" + if 'state' in status: + curState=status['state'] + if curState!=self._curState: + self._raiseEvent('onStateChange', {'oldState':self._curState, 'newState':curState}) + self._curState=curState + + " check if the volume has changed" + if 'volume' in status: + curVolume=int(status['volume']) + if curVolume!=self._curVolume: + self._raiseEvent('onVolumeChange', {'oldVolume':self._curVolume, 'newVolume':curVolume}) + self._curVolume=curVolume + + if 'playlist' in status: + cur_plist_id = int(status['playlist']) + if cur_plist_id != self._cur_plist_id: + self._updatePlaylist() + self._raiseEvent('onPlaylistChange', None) + self._cur_plist_id = cur_plist_id + + " update has started" + if 'updatings_db' in status and self._updatings_db == None: + self._updatings_db = status['updatings_db'] + self._raiseEvent('onUpdateDBStart', {}) + if not('updatings_db' in status) and self._updatings_db: + self._updatings_db = None + self._raiseEvent('onUpdateDBFinish') |