--- /dev/null
+########################################################################
+#
+# 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
"""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
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
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:
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):
Logging facility for sima.
"""
+# standard library import
import logging
import sys
+
LOG_FORMATS = {
logging.DEBUG: '{asctime} {filename}:{lineno}({funcName}) '
'{levelname}: {message}',
# 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)
"""
# -*- 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):
"""
def callback_next_song(self):
"""Not returning data,
- Could be use to scrobble
+ Could be use to scrobble, maintain an history…
"""
pass
# -*- 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/>.
#
#
"""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
--- /dev/null
+# -*- 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
class Crop(Plugin):
"""
Crop playlist on next track
+ kinda MPD's consume
"""
def callback_playlist(self):
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():
"""
log.info('Quiting now!')
sys.exit(1)
-
# ArgParse Callbacks
class Obsolete(Action):
# pylint: disable=R0903
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):