From 611f98994247324793027cae02d77e5a8efd0f42 Mon Sep 17 00:00:00 2001 From: kaliko Date: Sat, 28 Sep 2013 14:53:19 +0200 Subject: [PATCH] Add handling of external plugins --- doc/examples/all_settings.cfg | 180 ++++++++++++++++++++++++++++ launch | 44 ++++++- sima/core.py | 1 + sima/lib/logger.py | 2 + sima/lib/player.py | 5 +- sima/lib/plugin.py | 39 +++++- sima/lib/track.py | 10 +- sima/plugins/addhist.py | 5 +- sima/plugins/contrib/__init__.py | 0 sima/plugins/contrib/placeholder.py | 25 ++++ sima/plugins/crop.py | 1 + sima/utils/utils.py | 9 +- 12 files changed, 298 insertions(+), 23 deletions(-) create mode 100644 doc/examples/all_settings.cfg create mode 100644 sima/plugins/contrib/__init__.py create mode 100644 sima/plugins/contrib/placeholder.py diff --git a/doc/examples/all_settings.cfg b/doc/examples/all_settings.cfg new file mode 100644 index 0000000..77abb7a --- /dev/null +++ b/doc/examples/all_settings.cfg @@ -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 5c50ef1..192ec39 100755 --- 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: diff --git a/sima/core.py b/sima/core.py index 3710d92..2732540 100644 --- a/sima/core.py +++ b/sima/core.py @@ -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): diff --git a/sima/lib/logger.py b/sima/lib/logger.py index 8c6cd2f..761769b 100644 --- a/sima/lib/logger.py +++ b/sima/lib/logger.py @@ -23,9 +23,11 @@ Logging facility for sima. """ +# standard library import import logging import sys + LOG_FORMATS = { logging.DEBUG: '{asctime} {filename}:{lineno}({funcName}) ' '{levelname}: {message}', diff --git a/sima/lib/player.py b/sima/lib/player.py index c5c69f8..9f2910d 100644 --- a/sima/lib/player.py +++ b/sima/lib/player.py @@ -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) """ diff --git a/sima/lib/plugin.py b/sima/lib/plugin.py index f846e2e..e2764f9 100644 --- a/sima/lib/plugin.py +++ b/sima/lib/plugin.py @@ -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 diff --git a/sima/lib/track.py b/sima/lib/track.py index 93aa350..52d1ce2 100644 --- a/sima/lib/track.py +++ b/sima/lib/track.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko +# Copyright (c) 2009, 2010, 2011, 2013 Jack Kaliko # 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 . +# along with sima. If not, see . # # diff --git a/sima/plugins/addhist.py b/sima/plugins/addhist.py index 8b8da7a..68675fe 100644 --- a/sima/plugins/addhist.py +++ b/sima/plugins/addhist.py @@ -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 index 0000000..e69de29 diff --git a/sima/plugins/contrib/placeholder.py b/sima/plugins/contrib/placeholder.py new file mode 100644 index 0000000..3e04817 --- /dev/null +++ b/sima/plugins/contrib/placeholder.py @@ -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 diff --git a/sima/plugins/crop.py b/sima/plugins/crop.py index 8467ba4..6bfbf6d 100644 --- a/sima/plugins/crop.py +++ b/sima/plugins/crop.py @@ -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): diff --git a/sima/utils/utils.py b/sima/utils/utils.py index 017af9f..e3927fc 100644 --- a/sima/utils/utils.py +++ b/sima/utils/utils.py @@ -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): -- 2.39.2