1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009, 2010, 2011, 2013, 2014, 2015 Jack Kaliko <kaliko@azylum.org>
4 # This file is part of sima
6 # sima is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # sima is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with sima. If not, see <http://www.gnu.org/licenses/>.
20 """Core Object dealing with plugins and player client
25 from collections import deque
26 from logging import getLogger
28 from .client import PlayerClient
29 from .client import PlayerError, PlayerUnHandledError
30 from .lib.simadb import SimaDB
31 from .lib.daemon import Daemon
32 from .utils.utils import SigHup
36 """Main class, plugin and player management
39 def __init__(self, conf):
41 Daemon.__init__(self, conf.get('daemon', 'pidfile'))
44 self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
45 PlayerClient.database = self.sdb
46 self.log = getLogger('sima')
47 self._plugins = list()
48 self._core_plugins = list()
49 self.player = self.__get_player() # Player client
50 self.short_history = deque(maxlen=60)
52 def __get_player(self):
53 """Instanciate the player"""
54 host = self.config.get('MPD', 'host')
55 port = self.config.get('MPD', 'port')
56 pswd = self.config.get('MPD', 'password', fallback=None)
57 return PlayerClient(host, port, pswd)
59 def add_history(self):
60 """Handle local, in memory, short history"""
61 self.short_history.appendleft(self.player.current)
63 def register_plugin(self, plugin_class):
64 """Registers plugin in Sima instance..."""
65 plgn = plugin_class(self)
66 prio = int(plgn.priority)
67 self._plugins.append((prio, plgn))
69 def register_core_plugin(self, plugin_class):
70 """Registers core plugins"""
71 plgn = plugin_class(self)
72 prio = int(plgn.priority)
73 self._core_plugins.append((prio, plgn))
75 def foreach_plugin(self, method, *args, **kwds):
76 """Plugin's callbacks dispatcher"""
77 self.log.trace('dispatching %s to plugins', method) # pylint: disable=no-member
78 for plugin in self.core_plugins:
79 getattr(plugin, method)(*args, **kwds)
80 for plugin in self.plugins:
81 getattr(plugin, method)(*args, **kwds)
84 def core_plugins(self):
85 return [plugin[1] for plugin in
86 sorted(self._core_plugins, key=lambda pl: pl[0], reverse=True)]
90 return [plugin[1] for plugin in sorted(self._plugins, key=lambda pl: pl[0], reverse=True)]
92 def need_tracks(self):
93 """Is the player in need for tracks"""
95 self.log.debug('Queueing disabled!')
97 queue_trigger = self.config.getint('sima', 'queue_length')
98 if self.player.playmode.get('random'):
99 queue = self.player.playlist
100 self.log.debug('Currently %s track(s) in the playlist. (target %s)', len(queue), queue_trigger)
102 queue = self.player.queue
103 self.log.debug('Currently %s track(s) ahead. (target %s)', len(queue), queue_trigger)
104 if len(queue) < queue_trigger:
110 for plugin in self.plugins:
111 self.log.info('running %s', plugin)
112 pl_candidates = getattr(plugin, 'callback_need_track')()
114 to_add.extend(pl_candidates)
118 self.player.add(track)
120 def reconnect_player(self):
121 """Trying to reconnect cycling through longer timeout
122 cycle : 5s 10s 1m 5m 20m 1h
124 sleepfor = [5, 10, 60, 300, 1200, 3600]
126 tmp = sleepfor.pop(0)
128 self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
131 self.player.connect()
132 except PlayerError as err:
135 except PlayerUnHandledError as err:
136 #TODO: unhandled Player exceptions
137 self.log.warning('Unhandled player exception: %s', err)
138 self.log.info('Got reconnected')
140 self.foreach_plugin('start')
142 def hup_handler(self, signum, frame):
143 self.log.warning('Caught a sighup!')
144 # Cleaning pending command
146 self.foreach_plugin('shutdown')
147 self.player.disconnect()
148 raise SigHup('SIGHUP caught!')
151 """General shutdown method
153 self.log.warning('Starting shutdown.')
154 # Cleaning pending command
156 self.foreach_plugin('shutdown')
157 self.player.disconnect()
159 self.log.info('The way is shut, it was made by those who are dead. '
160 'And the dead keep it…')
161 self.log.info('bye...')
167 self.log.info('Connecting MPD: {0}:{1}'.format(*self.player._mpd))
168 self.player.connect()
169 self.foreach_plugin('start')
170 except (PlayerError, PlayerUnHandledError) as err:
171 self.log.warning('Player: %s', err)
172 self.reconnect_player()
176 except PlayerUnHandledError as err:
177 #TODO: unhandled Player exceptions
178 self.log.warning('Unhandled player exception: %s', err)
180 self.player = PlayerClient()
182 except PlayerError as err:
183 self.log.warning('Player error: %s', err)
184 self.reconnect_player()
188 """Dispatching callbacks to plugins
190 # hanging here untill a monitored event is raised in the player
191 if getattr(self, 'changed', False): # first iteration exception
192 self.changed = self.player.monitor()
193 else: # first iteration goes through else
194 self.changed = ['playlist', 'player', 'skipped']
195 self.log.debug('changed: %s', ', '.join(self.changed))
196 if 'playlist' in self.changed:
197 self.foreach_plugin('callback_playlist')
198 if 'player' in self.changed or 'options' in self.changed:
199 self.foreach_plugin('callback_player')
200 if 'database' in self.changed:
201 self.foreach_plugin('callback_player_database')
202 if 'skipped' in self.changed:
203 if self.player.state == 'play':
204 self.log.info('Playing: %s', self.player.current)
206 self.foreach_plugin('callback_next_song')
207 if self.need_tracks():
211 # vim: ai ts=4 sw=4 sts=4 expandtab