summaryrefslogtreecommitdiff
path: root/nephilim/mpclient.py
diff options
context:
space:
mode:
Diffstat (limited to 'nephilim/mpclient.py')
-rw-r--r--nephilim/mpclient.py364
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')