From f22dab76b500a22109a734fa7dafca9d50a24725 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 9 Aug 2010 18:47:03 +0200 Subject: switch to the new MPD interaction layer remove the old mpclient and our bundled copy of mpd.py --- nephilim/mpclient.py | 491 +-------------------------------------------------- 1 file changed, 3 insertions(+), 488 deletions(-) (limited to 'nephilim/mpclient.py') diff --git a/nephilim/mpclient.py b/nephilim/mpclient.py index c118cdb..b3cd027 100644 --- a/nephilim/mpclient.py +++ b/nephilim/mpclient.py @@ -1,6 +1,5 @@ # -# Copyright (C) 2008 jerous -# Copyright (C) 2009 Anton Khirnov +# Copyright (C) 2010 Anton Khirnov # # Nephilim is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,497 +17,13 @@ from PyQt4 import QtCore, QtNetwork from PyQt4.QtCore import pyqtSignal as Signal, pyqtSlot as Slot -import mpd -import socket import logging -from song import Song, PlaylistEntryRef +from song import Song from mpdsocket import MPDSocket -class MPClient(QtCore.QObject): - """This class offers another layer above pympd, with usefull events.""" - # public, read-only - logger = None - - # these don't change while mpd is running - outputs = None - tagtypes = None - urlhandlers = None - commands = None - - # private - __password = None - _client = None - _cur_song = None - _status = {'volume' : 0, 'repeat' : 0, 'random' : 0, - 'songid' : 0, 'playlist' : 0, 'playlistlength' : 0, - 'time' : 0, 'length' : 0, 'xfade' : 0, - 'state' : 'stop', 'single' : 0, - 'consume' : 0} - - _timer_id = None #for querying status changes - _db_timer_id = None #for querying db updates - _db_update = None #time of last db update - - __stats = {'artists': '0', 'albums' : '0', 'songs' : '0', 'uptime' : '0', - 'playtime' : '0', 'db_playtime' : '0', 'db_update' : '0'} - - # SIGNALS - connect_changed = QtCore.pyqtSignal(bool) - db_updated = QtCore.pyqtSignal() - song_changed = QtCore.pyqtSignal(object) - time_changed = QtCore.pyqtSignal(int) - state_changed = QtCore.pyqtSignal(str) - volume_changed = QtCore.pyqtSignal(int) - repeat_changed = QtCore.pyqtSignal(bool) - random_changed = QtCore.pyqtSignal(bool) - single_changed = QtCore.pyqtSignal(bool) - consume_changed = QtCore.pyqtSignal(bool) - playlist_changed = QtCore.pyqtSignal() - - - def __init__(self): - QtCore.QObject.__init__(self) - self.logger = logging.getLogger('mpclient') - self.__update_static() - self._status = dict(MPClient._status) - - def connect_mpd(self, host, port, password = None): - """Connect to MPD@host:port, optionally using password.""" - self.logger.info('Connecting to MPD...') - if self._client: - self.logger.warning('Attempted to connect when already connected.') - return - - self._client = mpd.MPDClient() - self._client.connect_changed.connect(lambda val:self.__finish_connect() if val else self.__finish_disconnect()) - self._client.connect_mpd(host, port) - self.__password = password - - def disconnect_mpd(self): - """Disconnect from MPD.""" - self.logger.info('Disconnecting from MPD...') - if self._client: - self._client.disconnect_mpd() - - def password(self, password): - """Use the password to authenticate with MPD.""" - self.logger.info('Authenticating with MPD.') - if not self.__check_command_ok('password'): - return - try: - self._client.password(password) - self.logger.info('Successfully authenticated') - self.__update_static() - except mpd.CommandError: - self.logger.error('Incorrect MPD password.') - def is_connected(self): - """Returns True if connected to MPD, False otherwise.""" - return self._client != None - - def status(self): - """Get current MPD status.""" - return self._status - def playlistinfo(self): - """Returns a list of songs in current playlist.""" - self.logger.info('Listing current playlist.') - if not self.__check_command_ok('playlistinfo'): - raise StopIteration - for song in self._client.playlistinfo(): - yield Song(song) - raise StopIteration - def library(self): - """Returns a list of all songs in library.""" - self.logger.info('Listing library.') - if not self.__check_command_ok('listallinfo'): - raise StopIteration - for song in self._client.listallinfo(): - if 'file' in song: - yield Song(song) - - raise StopIteration - def current_song(self): - """Returns the current playing song.""" - return self._cur_song - def is_playing(self): - """Returns True if MPD is playing, False otherwise.""" - return self._status['state'] == 'play' - def find(self, *args): - if not self.__check_command_ok('find'): - raise StopIteration - for song in self._client.find(*args): - yield Song(song) - raise StopIteration - def findadd(self, *args): - """Find tracks with given tags and add them to playlist. Takes - a list of (tag, value).""" - self.logger.info('Findadd %s.'%unicode(args)) - if not self.__check_command_ok('findadd'): - return - return self._client.findadd(*args) - def playlistid(self, plid): - """Return a song with a given playlist id.""" - self.logger.info('Getting id %s.'%('of id %s'%(plid) if plid else '')) - if not self.__check_command_ok('play'): - return - ret = None - for it in self._client.playlistid(plid): - ret = Song(it) - return ret - - def update_db(self, paths = None): - """Starts MPD database update.""" - self.logger.info('Updating database %s'%(paths if paths else '.')) - if not self.__check_command_ok('update'): - return - if not paths: - return self._client.update() - self._client.command_list_ok_begin() - for path in paths: - self._client.update(path) - list(self._client.command_list_end()) - - def volume(self): - """Get current volume.""" - return int(self._status['volume']) - def set_volume(self, volume): - """Set volume to volume.""" - self.logger.info('Setting volume to %d.'%volume) - if not self.__check_command_ok('setvol'): - return - volume = min(100, max(0, volume)) - try: - self._client.setvol(volume) - except mpd.CommandError, e: - self.logger.warning('Error setting volume (probably no outputs enabled): %s.'%e) - - def stats(self): - """Get MPD statistics.""" - if not self.__check_command_ok('stats'): - return self.__stats - return self._client.stats() - - def repeat(self, val): - """Set repeat playlist to val (True/False).""" - self.logger.info('Setting repeat to %d.'%val) - if not self.__check_command_ok('repeat'): - return - if isinstance(val, bool): - val = 1 if val else 0 - self._client.repeat(val) - def random(self, val): - """Set random playback to val (True, False).""" - self.logger.info('Setting random to %d.'%val) - if not self.__check_command_ok('random'): - return - if isinstance(val, bool): - val = 1 if val else 0 - self._client.random(val) - def crossfade(self, time): - """Set crossfading between songs.""" - self.logger.info('Setting crossfade to %d'%time) - if not self.__check_command_ok('crossfade'): - return - self._client.crossfade(time) - def single(self, val): - """Set single playback to val (True, False)""" - self.logger.info('Setting single to %d.'%val) - if not self.__check_command_ok('single'): - return - if isinstance(val, bool): - val = 1 if val else 0 - self._client.single(val) - def consume(self, val): - """Set consume mode to val (True, False)""" - self.logger.info('Setting consume to %d.'%val) - if not self.__check_command_ok('consume'): - return - if isinstance(val, bool): - val = 1 if val else 0 - self._client.consume(val) - - def play(self, id = None): - """Play song with ID id or next song if id is None.""" - self.logger.info('Starting playback %s.'%('of id %s'%(id) if id else '')) - if not self.__check_command_ok('play'): - return - if id: - self._client.playid(id) - else: - self._client.playid() - def pause(self): - """Pause playing.""" - self.logger.info('Pausing playback.') - if not self.__check_command_ok('pause'): - return - self._client.pause(1) - def resume(self): - """Resume playing.""" - self.logger.info('Resuming playback.') - if not self.__check_command_ok('pause'): - return - self._client.pause(0) - def next(self): - """Move on to the next song in the playlist.""" - self.logger.info('Skipping to next song.') - if not self.__check_command_ok('next'): - return - self._client.next() - def previous(self): - """Move back to the previous song in the playlist.""" - self.logger.info('Moving to previous song.') - if not self.__check_command_ok('previous'): - return - self._client.previous() - def stop(self): - """Stop playing.""" - self.logger.info('Stopping playback.') - if not self.__check_command_ok('stop'): - return - self._client.stop() - def seek(self, time): - """Seek to time (in seconds).""" - self.logger.info('Seeking to %d.'%time) - if not self.__check_command_ok('seekid'): - return - if self._status['songid'] > 0: - self._client.seekid(self._status['songid'], time) - - def delete(self, ids): - """Remove all song IDs in list from the playlist.""" - if not self.__check_command_ok('deleteid'): - return - self._client.command_list_ok_begin() - try: - for id in ids: - self.logger.info('Deleting id %s from playlist.'%id) - self._client.deleteid(id) - list(self._client.command_list_end()) - except mpd.CommandError, e: - self.logger.error('Error deleting files: %s.'%e) - def clear(self): - """Clear current playlist.""" - self.logger.info('Clearing playlist.') - if not self.__check_command_ok('clear'): - return - self._client.clear() - def add(self, paths, pos = -1): - """Add all files in paths to the current playlist.""" - if not self.__check_command_ok('addid'): - return - ret = None - self._client.command_list_ok_begin() - for path in paths: - self.logger.info('Adding %s to playlist'%path) - if pos < 0: - self._client.addid(path) - else: - self._client.addid(path, pos) - pos += 1 - try: - ret = list(self._client.command_list_end()) - except mpd.CommandError, e: - self.logger.error('Error adding files: %s.'%e) - if self._status['state'] == 'stop' and ret: - self.play(ret[0]) - def move(self, source, target): - """Move the songs in playlist. Takes one source id and one target position.""" - self.logger.info('Moving %s to %s.'%(source, target)) - if not self.__check_command_ok('moveid'): - return - self._client.moveid(source, target) - - #### private #### - def __finish_connect(self): - if self.__password: - self.password(self.__password) - else: - self.__update_static() - - if not self.__check_command_ok('listallinfo'): - self.logger.error('Don\'t have MPD read permission, diconnecting.') - return self.disconnect_mpd() - - self.__update_current_song() - self._db_update = self.stats()['db_update'] - - self.connect_changed.emit(True) - self.logger.info('Successfully connected to MPD.') - self._timer_id = self.startTimer(500) - self._db_timer_id = self.startTimer(1000) - def __finish_disconnect(self): - self._client = None - - if self._timer_id: - self.killTimer(self._timer_id) - self._timer_id = None - if self._db_timer_id: - self.killTimer(self._db_timer_id) - self._db_timer_id = None - self._status = dict(MPClient._status) - self._cur_song = None - self.__update_static() - self.connect_changed.emit(False) - self.logger.info('Disconnected from MPD.') - def __update_current_song(self): - """Update the current song.""" - song = self._client.currentsong() - if not song: - self._cur_song = None - else: - self._cur_song = Song(song) - def _update_status(self): - """Get current status""" - if not self._client: - return None - ret = self._client.status() - if not ret: - return None - - ret['repeat'] = int(ret['repeat']) - ret['random'] = int(ret['random']) - ret['single'] = int(ret['single']) - ret['consume'] = int(ret['consume']) - ret['volume'] = int(ret['volume']) - if 'time' in ret: - cur, len = ret['time'].split(':') - ret['length'] = int(len) - ret['time'] = int(cur) - else: - ret['length'] = 0 - ret['time'] = 0 - - if not 'songid' in ret: - ret['songid'] = '-1' - - return ret - def __check_command_ok(self, cmd): - if not self._client: - return self.logger.info('Not connected.') - if not cmd in self.commands: - return self.logger.error('Command %s not accessible'%cmd) - return True - - def __update_static(self): - """Update static values, called on connect/disconnect.""" - if self._client: - self.commands = list(self._client.commands()) - else: - self.commands = [] - - if self.__check_command_ok('outputs'): - outputs = [] - for output in self._client.outputs(): - outputs.append(AudioOutput(self, output['outputname'], output['outputid'], - bool(output['outputenabled']))) - self.outputs = outputs - else: - self.outputs = [] - - if self.__check_command_ok('tagtypes'): - self.tagtypes = map(unicode.lower, self._client.tagtypes()) + ['file'] - else: - self.tagtypes = [] - - if self.__check_command_ok('urlhandlers'): - self.urlhandlers = list(self._client.urlhandlers()) - else: - self.urlhandlers = [] - - def set_output(self, output_id, state): - """Set audio output output_id to state (0/1). Called only by AudioOutput.""" - if not self.__check_command_ok('enableoutput'): - return - if state: - self._client.enableoutput(output_id) - else: - self._client.disableoutput(output_id) - - def timerEvent(self, event): - """Check for changes since last check.""" - if event.timerId() == self._db_timer_id: - #timer for monitoring db changes - db_update = self.stats()['db_update'] - if db_update > self._db_update: - self.logger.info('Database updated.') - self._db_update = db_update - self.db_updated.emit() - return - - - old_status = self._status - self._status = self._update_status() - - if not self._status: - self.logger.error('Error reading status.') - return self.disconnect_mpd() - - if self._status['songid'] != old_status['songid']: - self.__update_current_song() - self.song_changed.emit(PlaylistEntryRef(self, self._status['songid'])) - - if self._status['time'] != old_status['time']: - self.time_changed.emit(self._status['time']) - - if self._status['state'] != old_status['state']: - self.state_changed.emit(self._status['state']) - - if self._status['volume'] != old_status['volume']: - self.volume_changed.emit( int(self._status['volume'])) - - if self._status['repeat'] != old_status['repeat']: - self.repeat_changed.emit(bool(self._status['repeat'])) - - if self._status['random'] != old_status['random']: - self.random_changed.emit(bool(self._status['random'])) - - if self._status['single'] != old_status['single']: - self.single_changed.emit(bool(self._status['single'])) - - if self._status['consume'] != old_status['consume']: - self.consume_changed.emit(bool(self._status['consume'])) - - if self._status['playlist'] != old_status['playlist']: - self.playlist_changed.emit() - - outputs = list(self._client.outputs()) - for i in range(len(outputs)): - if int(outputs[i]['outputenabled']) != int(self.outputs[i].state): - self.outputs[i].mpd_toggle_state() - - class AudioOutput(QtCore.QObject): """This class represents an MPD audio output.""" - # public, const - mpclient = None - name = None - id = None - state = None - - # SIGNALS - state_changed = QtCore.pyqtSignal(bool) - - #### public #### - def __init__(self, mpclient, name, id, state): - QtCore.QObject.__init__(self) - - self.mpclient = mpclient - self.name = name - self.id = id - self.state = state - - @QtCore.pyqtSlot(bool) - def set_state(self, state): - self.mpclient.set_output(self.id, state) - - #### private #### - def mpd_toggle_state(self): - """This is called by mpclient to inform about output state change.""" - self.state = not self.state - self.state_changed.emit(self.state) - -class AudioOutput2(QtCore.QObject): - """This class represents an MPD audio output.""" #### PUBLIC #### # constants @@ -566,7 +81,7 @@ class MPDStatus(dict): except ValueError: self['audio'] = (0, 0, 0) -class MPClient2(QtCore.QObject): +class MPClient(QtCore.QObject): """ A high-level MPD interface. It is mostly asynchronous -- all responses from MPD are read via callbacks. A callback may be None, in which case the data -- cgit v1.2.3