]> kaliko git repositories - mpd-sima.git/commitdiff
Add handling of external plugins
authorkaliko <efrim@azylum.org>
Sat, 28 Sep 2013 12:53:19 +0000 (14:53 +0200)
committerkaliko <efrim@azylum.org>
Sat, 28 Sep 2013 12:53:19 +0000 (14:53 +0200)
12 files changed:
doc/examples/all_settings.cfg [new file with mode: 0644]
launch
sima/core.py
sima/lib/logger.py
sima/lib/player.py
sima/lib/plugin.py
sima/lib/track.py
sima/plugins/addhist.py
sima/plugins/contrib/__init__.py [new file with mode: 0644]
sima/plugins/contrib/placeholder.py [new file with mode: 0644]
sima/plugins/crop.py
sima/utils/utils.py

diff --git a/doc/examples/all_settings.cfg b/doc/examples/all_settings.cfg
new file mode 100644 (file)
index 0000000..77abb7a
--- /dev/null
@@ -0,0 +1,180 @@
+########################################################################
+#
+# If you need special settings, rename this file as sima.cfg within
+# your $XDG_CONFIG_HOME (default is $HOME/.config/sima/)
+# You can also call it with --config option.
+#
+# Pay Attention:
+# * Inline comment are not possible
+#  
+#  WRONG:
+#  host = localhost  # My host
+#
+#  OK:
+#  # My host
+#  host = localhost
+#
+########################################################################
+
+#
+#######################################################################
+
+########################## MPD SECTION ################################
+#
+[MPD]
+## HOST
+# type: string
+host = localhost
+## PORT
+# type: integer
+port = 6600
+## PASSWORD
+# type: string
+#
+# please comment if you don't use
+#password = s3cr3t
+
+#
+#######################################################################
+
+######################## LOGGING FACILITY #############################
+#
+[log]
+# message are logged to console
+#
+## VERBOSITY
+# type: string
+#
+# pick verbosity in : debug, info, warning, error
+# default if not specify is "info"
+# *DEBUG LEVEL MIGHT PRINT OUT YOUR PASSWORD*
+verbosity = info
+##
+#
+#######################################################################
+
+######################### PLUGINS #####################################
+#
+[placeholder]
+key = Value
+##
+#
+#######################################################################
+
+
+######################## SIMA CORE ####################################
+#
+[sima]
+## PLUGINS
+# type: comma separated string list
+# for ex.:
+#          plugins = Scrobble, AwesomePlugin,
+#                    ExperimentalTest,AnotherTest
+#
+# Plugins list declaration.
+# Optional plugin's configuration must be in its own section. For instance an
+# "AwesomePlugin" declared here gets its configuration from the
+# "[AwesomePlugin]" or "[awesomeplugin]" section (case insensitive).
+#
+plugins = PlaceHolder
+
+## HISTORY_DURATION
+# type: integer (in hours)
+#
+# How far to look back in history to avoid to play twice the same track/title
+#
+history_duration = 8
+##
+
+## CONSUME
+# type: integer
+#
+# How many played tracks to keep in the playlist.
+# Allow to maintain a fixed length playlist.
+# set to 0 to keep all played tracks.
+#
+consume = 0
+##
+
+## SINGLE_ALBUM
+# type: boolean
+# scope: "track" and "top" queue modes
+#
+# Prevent from queueing a track from the same album (for instance with OST).
+single_album = false
+##
+
+
+# These settings deal with MPD_sima core behaviour.
+
+## Queue Mode
+##
+# The default is to queue random tracks from similar artists.
+#
+## QUEUE_MODE
+# type: string
+#
+# Possible values:
+#      track : Will queue tracks from similar artists (default).
+#      top   : Will queue top tracks from similar artists.
+#      album : Will queue whole album from similar artists.
+queue_mode = track
+
+## SIMILARITY
+# type: integer in [0 100]
+#
+# Similarity as a percentage of similarity for the artist the code is
+# looking for.
+similarity = 15
+##
+
+## DYNAMIC
+# type: integer
+#
+# Number of similar artist to retrieve from local media library.
+# When set to something superior to zero, MPD_sima tries to get as much similar
+# artists from media library provided artists similarity is superior to
+# similarity value.
+dynamic = 10
+##
+
+## USER_DB
+# type: boolean
+#
+# Load user database to find similar artists
+# User DB is loaded from $XDG_CONFIG_HOME/mpd_sima/sima.db
+# Use simadb_cli to edit/add entries.
+user_db = false
+##
+
+#####################################################################
+# You do not need to set up options below.
+# But well, you got bored of the way MPD_sima is behaving, then go ahead
+# play with it :)
+
+## QUEUE_LENGTH
+# type: integer
+#
+# Queue length triggering tracks addition
+queue_length = 1
+##
+
+## TRACK_TO_ADD
+# type: integer
+# scope: "track" and "top" queue modes
+#
+# Missing Description…
+track_to_add = 1
+##
+
+## ALBUM_TO_ADD
+# type: integer
+# scope: "album" queue mode
+#
+# Missing Description…
+album_to_add = 1
+##
+#
+####################### END OF CONFIGURATION ##########################
+
+# vim: syntax=cfg fileencoding=utf-8
diff --git a/launch b/launch
index 5c50ef12467c0a4cc4078c09a2dbbfff605db9ba..192ec399a486bf492263087bee3a25cc10589edb 100755 (executable)
--- a/launch
+++ b/launch
@@ -3,12 +3,18 @@
 """Sima
 """
 
+# standart library import
 import logging
 import sys
 
-from os.path import isfile
+from importlib import __import__
+from os.path import isfile, basename
 ##
 
+# third parties components
+##
+
+# local import
 from sima import core
 from sima.plugins.crop import Crop
 from sima.plugins.addhist import History
@@ -19,6 +25,32 @@ from sima.utils.startopt import StartOpt
 from sima.utils.utils import exception_log
 ##
 
+# official plugins to start
+PLUGINS = (Crop, History)
+
+
+def load_contrib_plugins(sima):
+    """Handles contrib/external plugins
+    """
+    if not sima.config.has_option('sima', 'plugins'):
+        return
+    logger = logging.getLogger('sima')
+    for plugin in sima.config.get('sima','plugins').split(','):
+        plugin = plugin.strip(' \n')
+        module = 'sima.plugins.contrib.{}'.format(plugin.lower())
+        try:
+            mod_obj = __import__(module, fromlist=[plugin])
+        except ImportError as err:
+            logger.error('Failed to load plugin\'s module: {0} ({1})'.format(module, err))
+            sima.shutdown()
+        try:
+            plugin_obj = getattr(mod_obj, plugin)
+        except AttributeError as err:
+            logger.error('Failed to load plugin {0} ({1})'.format(plugin, err))
+            sima.shutdown()
+        logger.info('Loading contrib plugin: {name} ({doc})'.format(**plugin_obj.info()))
+        sima.register_plugin(plugin_obj)
+
 
 def main():
     """Entry point, deal w/ CLI and starts application
@@ -52,8 +84,14 @@ def main():
 
     logger.info('Starting...')
     sima = core.Sima(config, conf_manager.db_file)
-    sima.register_plugin(Crop)
-    sima.register_plugin(History)
+
+    #  Loading internal plugins
+    for plugin in PLUGINS:
+        logger.info('Loading internal plugin: {name} ({doc})'.format(**plugin.info()))
+        sima.register_plugin(plugin)
+
+    #  Loading contrib plugins
+    load_contrib_plugins(sima)
     try:
         sima.run()
     except KeyboardInterrupt:
index 3710d92f3ac03f991012bef8ec8377872b51d41e..2732540ad297ecbef6c3513408e370b697492305 100644 (file)
@@ -40,6 +40,7 @@ class Sima(object):
     def foreach_plugin(self, method, *args, **kwds):
         """Plugin's callbacks dispatcher"""
         for plugin in self.plugins:
+            #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
             getattr(plugin, method)(*args, **kwds)
 
     def reconnect_player(self):
index 8c6cd2ff0b38ba346d982ed49b2d9093342e21a8..761769bbdfd4066cba7cac4c52da5c01122472fa 100644 (file)
 Logging facility for sima.
 """
 
+# standard library import
 import logging
 import sys
 
+
 LOG_FORMATS = {
         logging.DEBUG: '{asctime} {filename}:{lineno}({funcName}) '
                                  '{levelname}: {message}',
index c5c69f86a29f44f57eed9ee6b892a37c24a767f2..9f2910d048548bd3a1544b1c6ef4a85a7242b269 100644 (file)
@@ -3,14 +3,15 @@
 # TODO:
 # Add decorator to filter through history?
 
-from sima.lib.track import Track
+# local import
+#from sima.lib.track import Track
 
 
 class Player(object):
 
     """Player interface to inherit from.
 
-    When querying palyer music library for tracks, Player instance *must* return
+    When querying player music library for tracks, Player instance *must* return
     Track objects (usually a list of them)
     """
 
index f846e2e9d16565244f7178d32cede6edaa3da49a..e2764f96c849825a9d56a183b679aa51f86f7860 100644 (file)
@@ -1,14 +1,43 @@
 # -*- coding: utf-8 -*-
 
 class Plugin():
+    """
+    First non-empty line of the docstring is used as description
+    Rest of the docstring at your convenience.
+
+    The plugin Name MUST be the same as the module (file name), case
+    insensitive: for instance plugin.py → Plugin
+    It eases plugins discovery and simplifies the code to handle them,
+    IMHO, it's a fair trade-off.
+    """
+
+    @classmethod
+    def info(cls):
+        """self documenting class method
+        """
+        return {'name': cls.__name__,
+                'doc': cls.__doc__.strip(' \n').splitlines()[0]
+                }
+
     def __init__(self, daemon):
         self.log = daemon.log
         self.__daemon = daemon
-        #self.history = daemon.player.history
+        self.plugin_conf = None
+        self.__get_config()
+
+    def __str__(self):
+        return self.__class__.__name__
 
-    @property
-    def name(self):
-        return self.__class__.__name__.lower()
+    def __get_config(self):
+        """Get plugin's specific configuration from global applications's config
+        """
+        conf = self.__daemon.config
+        for sec in conf.sections():
+            if sec.lower() == self.__class__.__name__.lower():
+                self.plugin_conf = dict(conf.items(sec))
+        if self.plugin_conf:
+            self.log.debug('Got config for {0}: {1}'.format(self,
+                                                            self.plugin_conf))
 
     def callback_player(self):
         """
@@ -26,7 +55,7 @@ class Plugin():
 
     def callback_next_song(self):
         """Not returning data,
-        Could be use to scrobble
+        Could be use to scrobble, maintain an history…
         """
         pass
 
index 93aa350bbc4d24d612e2232e8c7a8d1de19d0a90..52d1ce2197ddd7e15d45144efbd9e9502cb492dc 100644 (file)
@@ -1,23 +1,23 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko <efrim@azylum.org>
+# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko <kaliko@azylum.org>
 # Copyright (c) 2009 J. Alexander Treuman (Tag collapse method)
 # Copyright (c) 2008 Rick van Hattem
 #
-#  This file is part of MPD_sima
+#  This file is part of sima
 #
-#  MPD_sima is free software: you can redistribute it and/or modify
+#  sima is free software: you can redistribute it and/or modify
 #  it under the terms of the GNU General Public License as published by
 #  the Free Software Foundation, either version 3 of the License, or
 #  (at your option) any later version.
 #
-#  MPD_sima is distributed in the hope that it will be useful,
+#  sima is distributed in the hope that it will be useful,
 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 #  GNU General Public License for more details.
 #
 #  You should have received a copy of the GNU General Public License
-#  along with MPD_sima.  If not, see <http://www.gnu.org/licenses/>.
+#  along with sima.  If not, see <http://www.gnu.org/licenses/>.
 #
 #
 
index 8b8da7a5a913a824dd9e9897187a0fdeab0991c6..68675fe14afcf2f9e9893f528a773a5aa34bb18c 100644 (file)
@@ -2,10 +2,9 @@
 """Add playing tracks to history
 """
 
-# standart library import
-#from select import select
+# standard library import
 
-# third parties componants
+# third parties components
 
 # local import
 from ..lib.plugin import Plugin
diff --git a/sima/plugins/contrib/__init__.py b/sima/plugins/contrib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sima/plugins/contrib/placeholder.py b/sima/plugins/contrib/placeholder.py
new file mode 100644 (file)
index 0000000..3e04817
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""Crops playlist
+"""
+
+# standart library import
+#from select import select
+
+# third parties componants
+
+# local import
+from sima.lib.plugin import Plugin
+
+class PlaceHolder(Plugin):
+    """
+    Placeholder contrib plugin
+    """
+
+    def callback_player(self):
+        self.log.info(self.plugin_conf)
+        self.log.debug('{0} contrib plugin!!!'.format(self))
+
+
+
+# VIM MODLINE
+# vim: ai ts=4 sw=4 sts=4 expandtab
index 8467ba45c750cba363cbdc6e89545b1a42e4921f..6bfbf6d7367cae7a559901d9e11cee06e649e47c 100644 (file)
@@ -13,6 +13,7 @@ from ..lib.plugin import Plugin
 class Crop(Plugin):
     """
     Crop playlist on next track
+    kinda MPD's consume
     """
 
     def callback_playlist(self):
index 017af9fd4b96ded621c9a31bfec0c8affac98ac3..e3927fcd7cbff823dbda081cb2b5f561b56f25e2 100644 (file)
@@ -24,9 +24,9 @@
 import traceback
 import sys
 
-from argparse import (ArgumentError, Action)
-from os import (environ, access, getcwd, W_OK, R_OK)
-from os.path import (dirname, isabs, join, normpath, exists, isdir, isfile)
+from argparse import ArgumentError, Action
+from os import environ, access, getcwd, W_OK, R_OK
+from os.path import dirname, isabs, join, normpath, exists, isdir, isfile
 
 def get_mpd_environ():
     """
@@ -63,7 +63,6 @@ def exception_log():
     log.info('Quiting now!')
     sys.exit(1)
 
-
 # ArgParse Callbacks
 class Obsolete(Action):
     # pylint: disable=R0903
@@ -73,7 +72,7 @@ class Obsolete(Action):
         raise ArgumentError(self, 'obsolete argument')
 
 class FileAction(Action):
-    """Generic class to inherit from for ARgPArse action on file/dir
+    """Generic class to inherit from for ArgParse action on file/dir
     """
     # pylint: disable=R0903
     def __call__(self, parser, namespace, values, option_string=None):