]> kaliko git repositories - mpd-sima.git/blobdiff - sima/mpdclient.py
Simplified first loop iteration detection
[mpd-sima.git] / sima / mpdclient.py
index fbd8cd8b32a157d778fd6e920c50f769b12657b3..1347edd64a85dee116c6863572fb4cd824127986 100644 (file)
 from difflib import get_close_matches
 from functools import wraps
 from logging import getLogger
+from select import select
 
 # external module
-from musicpd import MPDClient, MPDError
+from musicpd import MPDClient, MPDError as PlayerError
 
 
 # local import
@@ -32,10 +33,6 @@ from .lib.simastr import SimaStr
 from .utils.leven import levenshtein_ratio
 
 
-class PlayerError(MPDError):
-    """Fatal error in the player."""
-
-
 # Some decorators
 def bl_artist(func):
     def wrapper(*args, **kwargs):
@@ -53,16 +50,21 @@ def bl_artist(func):
         return result
     return wrapper
 
+
 def set_artist_mbid(func):
     def wrapper(*args, **kwargs):
         cls = args[0]
         result = func(*args, **kwargs)
         if Meta.use_mbid:
             if result and not result.mbid:
-                result.mbid = cls._find_musicbrainz_artistid(result)
+                mbid = cls._find_musicbrainz_artistid(result)
+                artist = Artist(name=result.name, mbid=mbid)
+                artist.add_alias(result)
+                return artist
         return result
     return wrapper
 
+
 def tracks_wrapper(func):
     """Convert plain track mapping as returned by MPDClient into :py:obj:`sima.lib.track.Track`
     objects. This decorator accepts single track or list of tracks as input.
@@ -107,6 +109,7 @@ class MPD(MPDClient):
 
     def __init__(self, config):
         super().__init__()
+        self.socket_timeout = 10
         self.use_mbid = True
         self.log = getLogger('sima')
         self.config = config
@@ -116,9 +119,12 @@ class MPD(MPDClient):
     def __getattr__(self, cmd):
         """Wrapper around MPDClient calls for abstract overriding"""
         track_wrapped = {'currentsong', 'find', 'playlistinfo', }
-        if cmd in track_wrapped:
-            return tracks_wrapper(super().__getattr__(cmd))
-        return super().__getattr__(cmd)
+        try:
+            if cmd in track_wrapped:
+                return tracks_wrapper(super().__getattr__(cmd))
+            return super().__getattr__(cmd)
+        except OSError as err:
+            raise PlayerError(err)
 
     def disconnect(self):
         """Overriding explicitly MPDClient.disconnect()"""
@@ -136,20 +142,20 @@ class MPD(MPDClient):
         try:
             super().connect(host, port)
         # Catch socket errors
-        except IOError as err:
+        except OSError as err:
             raise PlayerError('Could not connect to "%s:%s": %s' %
                               (host, port, err.strerror))
         # Catch all other possible errors
         # ConnectionError and ProtocolError are always fatal.  Others may not
         # be, but we don't know how to handle them here, so treat them as if
         # they are instead of ignoring them.
-        except MPDError as err:
+        except PlayerError as err:
             raise PlayerError('Could not connect to "%s:%s": %s' %
                               (host, port, err))
         if password:
             try:
                 self.password(password)
-            except (MPDError, IOError) as err:
+            except (PlayerError, OSError) as err:
                 raise PlayerError("Could not connect to '%s': %s" % (host, err))
         # Controls we have sufficient rights
         available_cmd = self.commands()
@@ -227,15 +233,22 @@ class MPD(MPDClient):
             * skipped   current track skipped
         """
         curr = self.current
-        try:
-            ret = self.idle('database', 'playlist', 'player', 'options')
-        except (MPDError, IOError) as err:
-            raise PlayerError("Couldn't init idle: %s" % err)
-        if self._skipped_track(curr):
-            ret.append('skipped')
-        if 'database' in ret:
-            self._reset_cache()
-        return ret
+        select_timeout = 5
+        while True:
+            self.send_idle('database', 'playlist', 'player', 'options')
+            _read, _, _ = select([self], [], [], select_timeout)
+            if _read:  # tries to read response
+                ret = self.fetch_idle()
+                if self._skipped_track(curr):
+                    ret.append('skipped')
+                if 'database' in ret:
+                    self._reset_cache()
+                return ret
+            else:
+                try:  # noidle cmd does not go through __getattr__, need to catch OSError then
+                    self.noidle()
+                except OSError as err:
+                    raise PlayerError(err)
 
     def clean(self):
         """Clean blocking event (idle) and pending commands
@@ -362,14 +375,15 @@ class MPD(MPDClient):
 # #### Search Methods #####
     def _find_musicbrainz_artistid(self, artist):
         """Find MusicBrainzArtistID when possible.
-        For artist with aliases having a mbid but not the main name, no mbid is
-        fetched…
-        Searching for Artist('Russian Circls') do not reslove the MBID
         """
         if not self.use_mbid:
             return None
-        mbids = self.list('MUSICBRAINZ_ARTISTID',
-                          f'(artist == "{artist.name_sz}")')
+        mbids = None
+        for name in artist.names_sz:
+            filt = f'((artist == "{name}") AND (MUSICBRAINZ_ARTISTID != ""))'
+            mbids = self.list('MUSICBRAINZ_ARTISTID', filt)
+            if mbids:
+                break
         if not mbids:
             return None
         if len(mbids) > 1: