X-Git-Url: https://git.kaliko.me/?a=blobdiff_plain;f=sima%2Fclient.py;h=fba50fdaf468c259febb6af304f85c2c4395ba99;hb=1d41464ccb6ff66441947eef0305518e3ce79a77;hp=641fa22166f106ff2adfa32611223e783b091c71;hpb=5fe20b6caffe162afe5be18e77fe40004d00c95e;p=mpd-sima.git diff --git a/sima/client.py b/sima/client.py index 641fa22..fba50fd 100644 --- a/sima/client.py +++ b/sima/client.py @@ -3,8 +3,11 @@ This client is built above python-musicpd a fork of python-mpd """ +# pylint: disable=C0111 # standart library import + +from difflib import get_close_matches from select import select # third parties componants @@ -18,6 +21,7 @@ except ImportError as err: # local import from .lib.player import Player from .lib.track import Track +from .lib.simastr import SimaStr class PlayerError(Exception): @@ -26,7 +30,7 @@ class PlayerError(Exception): class PlayerCommandError(PlayerError): """Command error""" -PlayerUnHandledError = MPDError +PlayerUnHandledError = MPDError # pylint: disable=C0103 class PlayerClient(Player): """MPC Client @@ -44,9 +48,9 @@ class PlayerClient(Player): find_aa, remove…) """ def __init__(self, host="localhost", port="6600", password=None): - self._host = host - self._port = port - self._password = password + super().__init__() + self._comm = self._args = None + self._mpd = host, port, password self._client = MPDClient() self._client.iterate = True @@ -77,13 +81,15 @@ class PlayerClient(Player): return self._track_format(ans) def _track_format(self, ans): + """ + unicode_obj = ["idle", "listplaylist", "list", "sticker list", + "commands", "notcommands", "tagtypes", "urlhandlers",] + """ # TODO: ain't working for "sticker find" and "sticker list" tracks_listing = ["playlistfind", "playlistid", "playlistinfo", "playlistsearch", "plchanges", "listplaylistinfo", "find", "search", "sticker find",] track_obj = ['currentsong'] - unicode_obj = ["idle", "listplaylist", "list", "sticker list", - "commands", "notcommands", "tagtypes", "urlhandlers",] if self._comm in tracks_listing + track_obj: # pylint: disable=w0142 if isinstance(ans, list): @@ -105,6 +111,55 @@ class PlayerClient(Player): return self.find('artist', artist, 'title', title) return self.find('artist', artist) + def fuzzy_find(self, art): + """ + Controls presence of artist in music library. + Crosschecking artist names with SimaStr objects / difflib / levenshtein + + TODO: proceed crosschecking even when an artist matched !!! + Not because we found "The Doors" as "The Doors" that there is no + remaining entries as "Doors" :/ + not straight forward, need probably heavy refactoring. + """ + matching_artists = list() + artist = SimaStr(art) + all_artists = self.list('artist') + + # Check against the actual string in artist list + if artist.orig in all_artists: + self.log.debug('found exact match for "%s"' % artist) + return [artist] + # Then proceed with fuzzy matching if got nothing + match = get_close_matches(artist.orig, all_artists, 50, 0.73) + if not match: + return [] + self.log.debug('found close match for "%s": %s' % + (artist, '/'.join(match))) + # Does not perform fuzzy matching on short and single word strings + # Only lowercased comparison + if ' ' not in artist.orig and len(artist) < 8: + for fuzz_art in match: + # Regular string comparison SimaStr().lower is regular string + if artist.lower() == fuzz_art.lower(): + matching_artists.append(fuzz_art) + self.log.debug('"%s" matches "%s".' % (fuzz_art, artist)) + return matching_artists + for fuzz_art in match: + # Regular string comparison SimaStr().lower is regular string + if artist.lower() == fuzz_art.lower(): + matching_artists.append(fuzz_art) + self.log.debug('"%s" matches "%s".' % (fuzz_art, artist)) + return matching_artists + # SimaStr string __eq__ (not regular string comparison here) + if artist == fuzz_art: + matching_artists.append(fuzz_art) + self.log.info('"%s" quite probably matches "%s" (SimaStr)' % + (fuzz_art, artist)) + else: + self.log.debug('FZZZ: "%s" does not match "%s"' % + (fuzz_art, artist)) + return matching_artists + def find_album(self, artist, album): """ Special wrapper around album search: @@ -130,6 +185,11 @@ class PlayerClient(Player): def remove(self, position=0): self._client.delete(position) + def add(self, track): + """Overriding MPD's add method to accept add signature with a Track + object""" + self._client.add(track.file) + @property def state(self): return str(self._client.status().get('state')) @@ -138,6 +198,12 @@ class PlayerClient(Player): def current(self): return self.currentsong() + @property + def queue(self): + plst = self.playlist + plst.reverse() + return [ trk for trk in plst if int(trk.pos) > int(self.current.pos)] + @property def playlist(self): """ @@ -146,14 +212,15 @@ class PlayerClient(Player): return self.playlistinfo() def connect(self): + host, port, password = self._mpd self.disconnect() try: - self._client.connect(self._host, self._port) + self._client.connect(host, port) # Catch socket errors except IOError as err: raise PlayerError('Could not connect to "%s:%s": %s' % - (self._host, self._port, err.strerror)) + (host, port, err.strerror)) # Catch all other possible errors # ConnectionError and ProtocolError are always fatal. Others may not @@ -161,24 +228,24 @@ class PlayerClient(Player): # they are instead of ignoring them. except MPDError as err: raise PlayerError('Could not connect to "%s:%s": %s' % - (self._host, self._port, err)) + (host, port, err)) - if self._password: + if password: try: - self._client.password(self._password) + self._client.password(password) # Catch errors with the password command (e.g., wrong password) except CommandError as err: raise PlayerError("Could not connect to '%s': " "password command failed: %s" % - (self._host, err)) + (host, err)) # Catch all other possible errors except (MPDError, IOError) as err: raise PlayerError("Could not connect to '%s': " "error with password command: %s" % - (self._host, err)) - # Controls we have sufficient rights for MPD_sima + (host, err)) + # Controls we have sufficient rights needed_cmds = ['status', 'stats', 'add', 'find', \ 'search', 'currentsong', 'ping'] @@ -188,7 +255,7 @@ class PlayerClient(Player): self.disconnect() raise PlayerError('Could connect to "%s", ' 'but command "%s" not available' % - (self._host, nddcmd)) + (host, nddcmd)) def disconnect(self): # Try to tell MPD we're closing the connection first