some failsafe fixes
authormarcelveldt <marcelvanderveldt@MacBook-Pro.local>
Wed, 13 Nov 2019 20:50:33 +0000 (21:50 +0100)
committermarcelveldt <marcelvanderveldt@MacBook-Pro.local>
Wed, 13 Nov 2019 20:50:33 +0000 (21:50 +0100)
music_assistant/homeassistant.py
music_assistant/http_streamer.py
music_assistant/music_manager.py
music_assistant/musicproviders/file.py
music_assistant/musicproviders/qobuz.py
music_assistant/musicproviders/spotify.py
music_assistant/player_manager.py
music_assistant/utils.py
music_assistant/web.py

index 3ec5f741f4384e8d6ca3275de80b071e5eb64a8b..5c246652fb3b7469940708116ce1e1ba03dbab24 100644 (file)
@@ -139,6 +139,8 @@ class HomeAssistant():
                 # call is for one of our players so handle it
                 player_id = self._published_players[entity_id]
                 player = await self.mass.players.get_player(player_id)
+                if not player:
+                    return
                 if service == 'turn_on':
                     await player.power_on()
                 elif service == 'turn_off':
index ebc7e9f222215b7e2e3887ab2b77fd6bb4e28594..73bdd199857a5c5102f539b7ad0fd3413c007346 100755 (executable)
@@ -444,6 +444,9 @@ class HTTPStreamer():
             elif streamdetails['type'] == 'executable':
                 audio_data = subprocess.check_output(streamdetails["path"],
                                                      shell=True)
+            elif streamdetails['type'] == 'file':
+                with open(streamdetails['path'], 'rb') as f:
+                    audio_data = f.read()
             # calculate BS.1770 R128 integrated loudness
             with io.BytesIO(audio_data) as tmpfile:
                 data, rate = soundfile.read(tmpfile)
index f8b7d43fe47700a4b5bfd0d676a5027bca221ccb..d5e66244ae3acfd52300c951c58b74da2fbd2851 100755 (executable)
@@ -57,16 +57,17 @@ class MusicManager():
         # schedule sync task
         self.mass.event_loop.create_task(self.__sync_music_providers())
 
-    async def load_modules(self):
+    async def load_modules(self, reload_module=None):
         """Dynamically (un)load musicprovider modules."""
-        prev_ids = list(self.providers.keys())
+        if reload_module and reload_module in self.providers:
+            # unload existing module
+            for player in self.providers[reload_module].players:
+                await self.mass.players.remove_player(player.player_id)
+            self.providers.pop(reload_module, None)
+            LOGGER.info('Unloaded %s module', reload_module)
+        # load all modules (that are not already loaded)
         await load_provider_modules(self.mass, 
                 self.providers, CONF_KEY_MUSICPROVIDERS)
-        # schedule sync for any newly added providers
-        for prov_id in self.providers:
-            if prov_id not in prev_ids:
-                self.mass.event_loop.create_task(
-                        self.sync_music_provider(prov_id))
 
     async def item(self,
                    item_id,
index c577007974535cfa7e1ee694fbc96838cd033d64..cc01fdc1405376366c3fe3eb81028cde61c6ca9e 100644 (file)
@@ -32,15 +32,18 @@ class FileProvider(MusicProvider):
         Supports having URI's from streaming providers within m3u playlist
         Should be compatible with LMS
     '''
+    _music_dir = None
+    _playlists_dir = None
 
     async def setup(self, conf):
         """ setup the provider, return True if succesfull"""
-        self._music_dir = conf["music_dir"]
-        self._playlists_dir = conf["playlists_dir"]
         if not os.path.isdir(conf["music_dir"]):
             raise FileNotFoundError(f"Directory {conf['music_dir']} does not exist")
-        if not os.path.isdir(conf["playlists_dir"]):
-            raise FileNotFoundError(f"Directory {conf['playlists_dir']} does not exist")
+        self._music_dir = conf["music_dir"]
+        if os.path.isdir(conf["playlists_dir"]):
+            self._playlists_dir = conf["playlists_dir"]
+        else:
+            self._playlists_dir = None
 
     async def search(self, searchstring, media_types=List[MediaType], limit=5):
         ''' perform search on the provider '''
@@ -67,14 +70,14 @@ class FileProvider(MusicProvider):
     
     async def get_library_albums(self) -> List[Album]:
         ''' get album folders recursively '''
-        async for artist in await self.get_library_artists():
+        async for artist in self.get_library_artists():
             async for album in self.get_artist_albums(artist.item_id):
                 yield album
 
     async def get_library_tracks(self) -> List[Track]:
         ''' get all tracks recursively '''
         #TODO: support disk subfolders
-        async for album in await self.get_library_albums():
+        async for album in self.get_library_albums():
             async for track in self.get_album_tracks(album.item_id):
                 yield track
     
@@ -230,7 +233,7 @@ class FileProvider(MusicProvider):
 
     async def get_artist_toptracks(self, prov_artist_id) -> List[Track]:
         ''' get a list of random tracks as we have no clue about preference '''
-        async for album in await self.get_artist_albums(prov_artist_id):
+        async for album in self.get_artist_albums(prov_artist_id):
             async for track in self.get_album_tracks(album.item_id):
                 yield track
 
index 1c488a6631fa564d279e6d19fa957bcddb351dc7..80b86d296f346acb9647585fde533313a781b765 100644 (file)
@@ -329,7 +329,7 @@ class QobuzProvider(MusicProvider):
     async def __parse_artist(self, artist_obj):
         ''' parse qobuz artist object to generic layout '''
         artist = Artist()
-        if not artist_obj.get('id'):
+        if not artist_obj or not artist_obj.get('id'):
             return None
         artist.item_id = artist_obj['id']
         artist.provider = self.prov_id
@@ -355,7 +355,7 @@ class QobuzProvider(MusicProvider):
     async def __parse_album(self, album_obj):
         ''' parse qobuz album object to generic layout '''
         album = Album()
-        if not album_obj.get('id') or not album_obj[
+        if not album_obj or not album_obj.get('id') or not album_obj[
                 "streamable"] or not album_obj["displayable"]:
             # do not return unavailable items
             return None
@@ -426,7 +426,7 @@ class QobuzProvider(MusicProvider):
     async def __parse_track(self, track_obj):
         ''' parse qobuz track object to generic layout '''
         track = Track()
-        if not track_obj.get('id') or not track_obj[
+        if not track_obj or not track_obj.get('id') or not track_obj[
                 "streamable"] or not track_obj["displayable"]:
             # do not return unavailable items
             return None
@@ -505,7 +505,7 @@ class QobuzProvider(MusicProvider):
     async def __parse_playlist(self, playlist_obj):
         ''' parse qobuz playlist object to generic layout '''
         playlist = Playlist()
-        if not playlist_obj.get('id'):
+        if not playlist_obj or not playlist_obj.get('id'):
             return None
         playlist.item_id = playlist_obj['id']
         playlist.provider = self.prov_id
index 85597e17099970416949bef34383295c6758ebca..f094ad676ecabc4883ce0e3c6ff565069aa22ecb 100644 (file)
@@ -253,6 +253,8 @@ class SpotifyProvider(MusicProvider):
 
     async def __parse_artist(self, artist_obj):
         ''' parse spotify artist object to generic layout '''
+        if not artist_obj:
+            return None
         artist = Artist()
         artist.item_id = artist_obj['id']
         artist.provider = self.prov_id
@@ -276,6 +278,8 @@ class SpotifyProvider(MusicProvider):
 
     async def __parse_album(self, album_obj):
         ''' parse spotify album object to generic layout '''
+        if not album_obj:
+            return None
         if 'album' in album_obj:
             album_obj = album_obj['album']
         if not album_obj['id'] or not album_obj.get('is_playable', True):
@@ -321,6 +325,8 @@ class SpotifyProvider(MusicProvider):
 
     async def __parse_track(self, track_obj):
         ''' parse spotify track object to generic layout '''
+        if not track_obj:
+            return None
         if 'track' in track_obj:
             track_obj = track_obj['track']
         if track_obj['is_local'] or not track_obj['id'] or not track_obj[
index 9b61a98b0e3c4949dbd76a72cea2cf8493f87904..fb26d56488746dd0663d6972a1e5ef5aaed73bc5 100755 (executable)
@@ -39,6 +39,13 @@ class PlayerManager():
 
     async def load_modules(self):
         """Dynamically (un)load musicprovider modules."""
+        if reload_module and reload_module in self.providers:
+            # unload existing module
+            if hasattr(self.providers[reload_module], 'http_session'):
+                await self.providers[reload_module].http_session.close()
+            self.providers.pop(reload_module, None)
+            LOGGER.info('Unloaded %s module', reload_module)
+        # load all modules (that are not already loaded)
         await load_provider_modules(self.mass, 
                 self.providers, CONF_KEY_PLAYERPROVIDERS)
     
index 7b420417bdfc2bd3bd2d69663db01d5e058af85d..594b38558cde6b4de94b00211c5cad7d48dd94b9 100755 (executable)
@@ -216,19 +216,6 @@ async def load_provider_modules(mass, provider_modules, prov_type=CONF_KEY_MUSIC
                 prov_mod = await load_provider_module(mass, module_name, prov_type)
                 if prov_mod:
                     provider_modules[module_name] = prov_mod
-    # unload modules (if needed)
-    removed_modules = []
-    for prov_id, prov in provider_modules.items():
-        if not mass.config[prov_type][prov_id][CONF_ENABLED]:
-            removed_modules.append(prov_id)
-            if hasattr(prov, 'http_session'):
-                await prov.http_session.close()
-            if prov_type == CONF_KEY_PLAYERPROVIDERS:
-                for player in prov.players:
-                    await mass.players.remove_player(player.player_id)
-    for prov_id in removed_modules:
-        provider_modules.pop(prov_id, None)
-        LOGGER.info('Unloaded %s module', prov_id)
 
 async def load_provider_module(mass, module_name, prov_type):
     ''' dynamically load music/player provider '''
@@ -253,4 +240,5 @@ async def load_provider_module(mass, module_name, prov_type):
         else:
             return None
     except Exception as exc:
-        LOGGER.exception("Error loading module %s: %s", module_name, exc)
+        LOGGER.error("Error loading module %s: %s", module_name, exc)
+        LOGGER.debug(exc_info=exc)
index 2a50babe311cda2fe1f6bc2aa8ecf5dd05963e1d..605507ca5289523046cd4be3a8322ae3dfb44d26 100755 (executable)
@@ -496,13 +496,13 @@ class Web():
                 self.mass.event_loop.create_task(
                     self.mass.players.trigger_update(conf_subkey))
             elif conf_key == CONF_KEY_MUSICPROVIDERS:
-                # (re)load music provider modules
+                # (re)load music provider module
                 self.mass.event_loop.create_task(
-                    self.mass.music.load_modules())
+                    self.mass.music.load_modules(conf_subkey))
             elif conf_key == CONF_KEY_PLAYERPROVIDERS:
-                # (re)load player provider modules
+                # (re)load player provider module
                 self.mass.event_loop.create_task(
-                    self.mass.players.load_modules())
+                    self.mass.players.load_modules(conf_subkey))
             else:
                 # other settings need restart
                 result["restart_required"] = True