"AwesomePlugin" declared here gets its configuration from the
corresponding section "[awesomeplugin]".<sbr />internal plugins
will look for a section named after the lower-cased name of the
- pluglin, ie. RandomFallBack → randomfallback.
+ plugin, ie. Random → random.
</para>
<variablelist>
<varlistentry> <!-- sima.internal -->
- <term><option>internal=</option><replaceable>Crop, RandomFallBack, Lastfm</replaceable></term>
+ <term><option>internal=</option><replaceable>Crop, Random, Lastfm</replaceable></term>
<listitem>
- <para><option>Crop</option> and <option>RandomFallback</option>
+ <para><option>Crop</option> and <option>Random</option>
are utilities plugins while <option>Lastfm</option> is the
actual queue plugin.<sbr /> Another queue plugin is available as
a "techno preview", it relies on EchoNest web services, replace
</listitem>
</varlistentry>
</refsect2>
- <refsect2 id="randomfallback">
- <title>RandomFallback section</title>
- <para>RandomFallback plugin's configuration:</para>
- <varlistentry> <!-- randomfallback -->
- <term><option>[randomfallback]</option></term>
+ <refsect2 id="random">
+ <title>Random section</title>
+ <para>Random plugin's configuration:</para>
+ <varlistentry> <!-- random -->
+ <term><option>[random]</option></term>
</varlistentry>
- <varlistentry> <!-- randomfallback.flavour -->
+ <varlistentry> <!-- random.flavour -->
<term><option>flavour=</option><replaceable>sensible</replaceable></term>
<listitem>
<para>When no similar tracks are found, falling back to
MPD_sima v0.14.0
+ * Add priority feature for plugins
+ * More robust MPD client
* Fixed top track mode
* Fixed default conf file name (back to mpd_sima.cfg)
* Cleanup code (removed users similarities)
- * More robust MPD client
-- kaliko jack <kaliko@azylum.org> UNRELEASED
# contrib = Scrobble, AwesomePlugin,
# ExperimentalTest, AnotherTest
# default:
-# internal = "Crop, Lastfm, RandomFallBack"
+# internal = "Crop, Lastfm, Random"
# contrib =
# description: Plugins list declaration.
# Optional plugin's configuration lays in its own section.
# gets its configuration from the corresponding section:
# "[awesomeplugin]"
# internal plugins will look for a section named after the lower-cased name
-# of the pluglin, ie. RandomFallBack → randomfallback.
+# of the pluglin, ie. Random → random.
#
# Two plugins sources are available, internal and contrib
#
-internal = Crop, Lastfm, RandomFallBack
+internal = Crop, Lastfm, Random
#contrib =
## HISTORY_DURATION
# Leave commented to keep all tracks
#consume = 10
-[randomfallback]
+[random]
## FLAVOUR
# type: string
# default: sensible
-# description: When no similar tracks are found, falling back to random
-# queuing. Different mode, aka random flavour, are available.
+# description: Random queuing, different mode, aka random flavour, are available.
# random flavour :
# * pure: complete random choice among all tracks available in the
# player media library
# chose among the same genre as current track (using genre
# tag). If no genre tag is available "sensible" flavour
# is used instead
-#flavour=sensible
+flavour=sensible
## TRACK_TO_ADD
# type: integer
@property
def artists(self):
- return self._cache.get('artists')
+ return self._cache.get('artists') | self._cache.get('nombid_artists')
@property
def state(self):
# -*- coding: utf-8 -*-
-# Copyright (c) 2009, 2010, 2011, 2013, 2014 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2009, 2010, 2011, 2013, 2014, 2015 Jack Kaliko <kaliko@azylum.org>
#
# This file is part of sima
#
self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
PlayerClient.database = self.sdb
self.log = getLogger('sima')
- self.plugins = list()
+ self._plugins = list()
+ self._core_plugins = list()
self.player = self.__get_player() # Player client
self.short_history = deque(maxlen=60)
return PlayerClient(host, port, pswd)
def add_history(self):
- """Handle local short history"""
+ """Handle local, in memory, short history"""
self.short_history.appendleft(self.player.current)
def register_plugin(self, plugin_class):
"""Registers plugin in Sima instance..."""
- self.plugins.append(plugin_class(self))
+ plgn = plugin_class(self)
+ prio = int(plgn.priority)
+ self._plugins.append((prio, plgn))
+
+ def register_core_plugin(self, plugin_class):
+ """Registers core plugins"""
+ plgn = plugin_class(self)
+ prio = int(plgn.priority)
+ self._core_plugins.append((prio, plgn))
def foreach_plugin(self, method, *args, **kwds):
"""Plugin's callbacks dispatcher"""
+ for plugin in self.core_plugins:
+ getattr(plugin, method)(*args, **kwds)
for plugin in self.plugins:
#self.log.debug('dispatching {0} to {1}'.format(method, plugin))
getattr(plugin, method)(*args, **kwds)
+ @property
+ def core_plugins(self):
+ return [plugin[1] for plugin in
+ sorted(self._core_plugins, key=lambda pl: pl[0], reverse=True)]
+
+ @property
+ def plugins(self):
+ return [plugin[1] for plugin in sorted(self._plugins, key=lambda pl: pl[0], reverse=True)]
+
def need_tracks(self):
"""Is the player in need for tracks"""
if not self.enabled:
def queue(self):
to_add = list()
for plugin in self.plugins:
- pl_callback = getattr(plugin, 'callback_need_track')()
- if pl_callback:
- to_add.extend(pl_callback)
- if not to_add:
- self.log.warning('Queue plugins returned nothing!')
- for plugin in self.plugins:
- pl_callback = getattr(plugin, 'callback_need_track_fb')()
- if pl_callback:
- to_add.extend(pl_callback)
+ self.log.info('running {}'.format(plugin))
+ pl_candidates = getattr(plugin, 'callback_need_track')()
+ if pl_candidates:
+ to_add.extend(pl_candidates)
+ if to_add:
+ break
for track in to_add:
self.player.add(track)
# -*- coding: utf-8 -*-
-# Copyright (c) 2013, 2014 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2013, 2014, 2015 Jack Kaliko <kaliko@azylum.org>
#
# This file is part of sima
#
if not sima.config.get('sima', source):
return
logger = logging.getLogger('sima')
+ # TODO: Sanity check for "sima.config.get('sima', source)" ?
for plugin in sima.config.get('sima', source).split(','):
plugin = plugin.strip(' \n')
module = 'sima.plugins.{0}.{1}'.format(source, plugin.lower())
sima.shutdown()
sys.exit(1)
logger.info('Loading {0} plugin: {name} ({doc})'.format(
- source, **plugin_obj.info()))
+ source, **plugin_obj.info()))
sima.register_plugin(plugin_obj)
core_plugins = [History, MpdOptions, Uniq]
for cplgn in core_plugins:
logger.debug('Register core {name} ({doc})'.format(**cplgn.info()))
- sima.register_plugin(cplgn)
+ sima.register_core_plugin(cplgn)
+ logger.debug('core loaded, prioriy: {}'.format(' > '.join(map(str, sima.core_plugins))))
# Loading internal plugins
load_plugins(sima, 'internal')
# Loading contrib plugins
load_plugins(sima, 'contrib')
-
+ logger.info('plugins loaded, prioriy: {}'.format(' > '.join(map(str, sima.plugins))))
# Set use of MusicBrainzIdentifier
if not config.getboolean('sima', 'musicbrainzid'):
logger.info('Disabling MusicBrainzIdentifier')
Plugin object to derive from
"""
+
class Plugin:
"""
First non-empty line of the docstring is used as description
"""Get plugin's specific configuration from global applications's config
"""
conf = self.__daemon.config
- for sec in conf.sections():
+ for sec in conf.sections(): # Discovering plugin conf
if sec == self.__class__.__name__.lower():
self.plugin_conf = conf[sec]
+ if 'priority' not in self.plugin_conf:
+ self.plugin_conf['priority'] = '80'
+ if not self.plugin_conf:
+ self.plugin_conf = {'priority': '80'}
#if self.plugin_conf:
# self.log.debug('Got config for {0}: {1}'.format(self,
# self.plugin_conf))
+ @property
+ def priority(self):
+ return self.plugin_conf.get('priority')
+
def start(self):
"""
Called when the daemon().run() is called and
# -*- coding: utf-8 -*-
-# Copyright (c) 2013, 2014 Jack Kaliko <kaliko@azylum.org>
+# Copyright (c) 2013, 2014, 2015 Jack Kaliko <kaliko@azylum.org>
#
# This file is part of sima
#
#
#
"""
-Fetching similar artists from last.fm web services
+Add random title
"""
# standard library import
from ...lib.meta import Artist
-class RandomFallBack(Plugin):
- """Add random track as fallback
+class Random(Plugin):
+ """Add random track
TODO: refactor, this plugin does not look good to me.
callback_need_track_fb/get_trk articulation is not elegant at all
"""
artists = [tr[-1] for tr in tracks_from_db]
return set(artists)
- def callback_need_track_fb(self):
+ def callback_need_track(self):
trks = list()
target = self.plugin_conf.getint('track_to_add')
limit = 0
trks = self.player.find_track(Artist(art))
if trks:
trk = random.choice(trks)
- self.log.info('random fallback ({}): {}'.format(self.mode, trk))
+ self.log.info('Random candidate ({}): {}'.format(self.mode, trk))
return trk
'track_to_add': 1,
'album_to_add': 1,
'depth': 1,
+ 'priority': 100,
},
'lastfm': {
'queue_mode': "track", #TODO control values
'album_to_add': 1,
'depth': 1,
'cache': True,
+ 'priority': 100,
},
- 'randomfallback': {
+ 'random': {
'flavour': "sensible", # in pure, sensible
'track_to_add': 1,
- }
+ 'priority': 50,
+ },
}
#
--- /dev/null
+# -*- coding: utf-8 -*-
+
+import unittest
+import configparser
+
+from unittest.mock import Mock
+
+import sima.lib.plugin
+
+class SomePlugin(sima.lib.plugin.Plugin):
+
+ def __init__(self, daemon):
+ sima.lib.plugin.Plugin.__init__(self, daemon)
+
+
+class TestFileAccessControl(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_plugin_conf_discovery(self):
+ config = configparser.ConfigParser()
+ default = {'priority': '42', 'option': 'value'}
+ config.read_dict({'someplugin': default})
+ daemon = Mock(config=config)
+ plugin = SomePlugin(daemon)
+ self.assertEqual(dict(plugin.plugin_conf), default)
+
+ def test_plugin_default_priority(self):
+ config = configparser.ConfigParser()
+ default = {'option': 'value'}
+ config.read_dict({'someplugin': default})
+ daemon = Mock(config=config)
+ plugin = SomePlugin(daemon)
+ self.assertEqual(plugin.plugin_conf.get('priority'), '80')
+ self.assertEqual(plugin.plugin_conf.get('option'), default.get('option'))
+
+ config = configparser.ConfigParser()
+ config.read_dict({})
+ daemon = Mock(config=config)
+ plugin = SomePlugin(daemon)
+ self.assertEqual(plugin.plugin_conf.get('priority'), '80')
+
+
+# VIM MODLINE
+# vim: ai ts=4 sw=4 sts=4 expandtab