]> kaliko git repositories - mpd-sima.git/blobdiff - sima/plugins/internal/tags.py
More robust Tags plugin MPD filter configuration
[mpd-sima.git] / sima / plugins / internal / tags.py
index 3b7ffb06a6a41422d5783aa2128864329f01f927..7566e39b0ec68832660be6c4baaeec0642288826 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2020 kaliko <kaliko@azylum.org>
+# Copyright (c) 2020, 2021 kaliko <kaliko@azylum.org>
 #
 #  This file is part of sima
 #
@@ -22,6 +22,7 @@ Add titles based on tags
 """
 
 # standard library import
+import logging
 import random
 
 # third parties components
@@ -33,9 +34,40 @@ from ...lib.meta import Artist, MetaContainer
 from ...utils.utils import PluginException
 
 
-def forge_filter(cfg):
+def control_config(tags_config):
+    log = logging.getLogger('sima')
+    sup_tags = Tags.supported_tags
+    config_tags = {k for k, v in tags_config.items()
+                   if (v and k in Tags.supported_tags)}
+    if not tags_config.get('filter', None) and \
+            config_tags.isdisjoint(sup_tags):
+        log.warning('Found no config for Tags plugin! '
+                    'Need at least "filter" or a supported tag')
+        log.info('Supported Tags are : %s', ', '.join(sup_tags))
+        return False
+    if config_tags.difference(sup_tags):
+        log.error('Found unsupported tag in config: %s',
+                  config_tags.difference(sup_tags))
+        return False
+    return True
+
+
+def forge_filter(cfg, logger):
+    """forge_filter merges tags config and user defined MPD filter into a single
+    MPD filter"""
     tags = set(cfg.keys()) & Tags.supported_tags
     cfg_filter = cfg.get('filter', None)
+    # Remove external enclosing parentheses in user defined MPD filter, for
+    # instance  when there is more than one expression:
+    #     ((genre == 'rock' ) AND (date =~ '198.'))
+    # Even though it's a valid MPD filter, forge_filter will enclose it
+    # properly. We do not want to through a syntax error at users since it's a
+    # valid MPD filter, hence trying to transparently reformat the filter
+    if cfg_filter.startswith('((') and cfg_filter.endswith('))'):
+        logger.debug('Drop external enclosing parentheses in user filter: %s',
+                     cfg_filter[1:-1])
+        cfg['filter'] = cfg_filter[1:-1]
+        cfg_filter = cfg['filter']
     mpd_filter = []
     if cfg_filter:
         mpd_filter.append(cfg_filter)
@@ -57,28 +89,18 @@ class Tags(AdvancedPlugin):
     """Add track based on tags content
     """
     supported_tags = {'comment', 'date', 'genre', 'label', 'originaldate'}
-    options = {'queue_mode', 'priority', 'filter', 'track_to_add', 'album_to_add'}
+    # options = {'queue_mode', 'priority', 'filter', 'track_to_add',
+    #            'album_to_add'}
 
     def __init__(self, daemon):
         super().__init__(daemon)
         self._control_conf()
-        self.mpd_filter = forge_filter(self.plugin_conf)
+        self.mpd_filter = forge_filter(self.plugin_conf, self.log)
         self._setup_tagsneeded()
         self.log.debug('mpd filter: %s', self.mpd_filter)
 
     def _control_conf(self):
-        sup_tags = Tags.supported_tags
-        config_tags = {k for k, v in self.plugin_conf.items()
-                       if (v and k not in Tags.options)}
-        if not self.plugin_conf.get('filter', None) and \
-                config_tags.isdisjoint(sup_tags):
-            self.log.error('Found no config for %s plugin! '
-                           'Need at least "filter" or a supported tag', self)
-            self.log.info('Supported Tags are : %s', ', '.join(sup_tags))
-            raise PluginException('plugin misconfiguration')
-        if config_tags.difference(sup_tags):
-            self.log.error('Found unsupported tag in config: %s',
-                           config_tags.difference(sup_tags))
+        if not control_config(self.plugin_conf):
             raise PluginException('plugin misconfiguration')
 
     def _setup_tagsneeded(self):
@@ -101,22 +123,25 @@ class Tags(AdvancedPlugin):
                 'Need at least MPD 0.21 to use Tags plugin (filters required)')
             self.player.disconnect()
             raise PluginException('MPD >= 0.21 required')
+        if not self.plugin_conf['filter']:
+            return
         # Check filter is valid
         try:
-            if self.plugin_conf['filter']:
-                # Use window to limit response size
-                self.player.find(self.plugin_conf['filter'], "window", (0, 1))
-        except CommandError:
+            # Use window to limit response size
+            self.player.find(self.mpd_filter, "window", (0, 1))
+        except CommandError as err:
+            self.log.warning(err)
             raise PluginException('Badly formated filter in tags plugin configuration: "%s"'
-                                  % self.plugin_conf['filter'])
+                                  % self.plugin_conf['filter']) from err
 
     def callback_need_track(self):
         candidates = []
         queue_mode = self.plugin_conf.get('queue_mode', 'track')
         target = self.plugin_conf.getint(f'{queue_mode}_to_add')
         # look for artists acording to filter
-        artists = MetaContainer([Artist(name=a) for a in self.player.list('artist', self.mpd_filter)])
+        artists = [Artist(name=a) for a in self.player.list('artist', self.mpd_filter)]
         random.shuffle(artists)
+        artists = MetaContainer(artists)
         if not artists:
             self.log.info('Tags plugin found nothing to queue')
             return candidates
@@ -125,11 +150,13 @@ class Tags(AdvancedPlugin):
         for artist in artists:
             self.log.debug('looking for %s', artist)
             tracks = self.player.find_tracks(artist)
-            trk = self.filter_track(tracks)
+            if not tracks:
+                continue
+            trk = self.filter_track(tracks, candidates)
             if not trk:
                 continue
             if queue_mode == 'track':
-                self.log.info('Tags plugin chose: {}'.format(trk))
+                self.log.info('Tags plugin chose: %s', trk)
                 candidates.append(trk)
                 if len(candidates) == target:
                     break