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
35 """Main class, plugin and player management
38 def __init__(self, conf):
40 Daemon.__init__(self, conf.get('daemon', 'pidfile'))
43 self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
44 PlayerClient.database = self.sdb
45 self.log = getLogger('sima')
46 self._plugins = list()
47 self._core_plugins = list()
48 self.player = self.__get_player() # Player client
49 self.short_history = deque(maxlen=60)
51 def __get_player(self):
52 """Instanciate the player"""
53 host = self.config.get('MPD', 'host')
54 port = self.config.get('MPD', 'port')
55 pswd = self.config.get('MPD', 'password', fallback=None)
56 return PlayerClient(host, port, pswd)
58 def add_history(self):
59 """Handle local, in memory, short history"""
60 self.short_history.appendleft(self.player.current)
62 def register_plugin(self, plugin_class):
63 """Registers plugin in Sima instance..."""
64 plgn = plugin_class(self)
65 prio = int(plgn.priority)
66 self._plugins.append((prio, plgn))
68 def register_core_plugin(self, plugin_class):
69 """Registers core plugins"""
70 plgn = plugin_class(self)
71 prio = int(plgn.priority)
72 self._core_plugins.append((prio, plgn))
74 def foreach_plugin(self, method, *args, **kwds):
75 """Plugin's callbacks dispatcher"""
76 self.log.trace('dispatching %s to plugins', method) # pylint: disable=no-member
77 for plugin in self.core_plugins:
78 getattr(plugin, method)(*args, **kwds)
79 for plugin in self.plugins:
80 getattr(plugin, method)(*args, **kwds)
83 def core_plugins(self):
84 return [plugin[1] for plugin in
85 sorted(self._core_plugins, key=lambda pl: pl[0], reverse=True)]
89 return [plugin[1] for plugin in sorted(self._plugins, key=lambda pl: pl[0], reverse=True)]
91 def need_tracks(self):
92 """Is the player in need for tracks"""
94 self.log.debug('Queueing disabled!')
96 queue_trigger = self.config.getint('sima', 'queue_length')
97 if self.player.playmode.get('random'):
98 queue = self.player.playlist
99 self.log.debug('Currently %s track(s) in the playlist. (target %s)', len(queue), queue_trigger)
101 queue = self.player.queue
102 self.log.debug('Currently %s track(s) ahead. (target %s)', len(queue), queue_trigger)
103 if len(queue) < queue_trigger:
109 for plugin in self.plugins:
110 self.log.info('running %s', plugin)
111 pl_candidates = getattr(plugin, 'callback_need_track')()
113 to_add.extend(pl_candidates)
117 self.player.add(track)
119 def reconnect_player(self):
120 """Trying to reconnect cycling through longer timeout
121 cycle : 5s 10s 1m 5m 20m 1h
123 sleepfor = [5, 10, 60, 300, 1200, 3600]
125 tmp = sleepfor.pop(0)
127 self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
130 self.player.connect()
131 except PlayerError as err:
134 except PlayerUnHandledError as err:
135 #TODO: unhandled Player exceptions
136 self.log.warning('Unhandled player exception: %s', err)
137 self.log.info('Got reconnected')
139 self.foreach_plugin('start')
141 def hup_handler(self, signum, frame):
142 self.log.warning('Caught a sighup!')
143 # Cleaning pending command
145 self.foreach_plugin('shutdown')
146 self.player.disconnect()
147 raise SigHup('SIGHUP caught!')
150 """General shutdown method
152 self.log.warning('Starting shutdown.')
153 # Cleaning pending command
155 self.foreach_plugin('shutdown')
156 self.player.disconnect()
158 self.log.info('The way is shut, it was made by those who are dead. '
159 'And the dead keep it…')
160 self.log.info('bye...')
166 self.log.info('Connecting MPD: {0}:{1}'.format(*self.player._mpd))
167 self.player.connect()
168 self.foreach_plugin('start')
169 except (PlayerError, PlayerUnHandledError) as err:
170 self.log.warning('Player: %s', err)
171 self.reconnect_player()
175 except PlayerUnHandledError as err:
176 #TODO: unhandled Player exceptions
177 self.log.warning('Unhandled player exception: %s', err)
179 self.player = PlayerClient()
181 except PlayerError as err:
182 self.log.warning('Player error: %s', err)
183 self.reconnect_player()
187 """Dispatching callbacks to plugins
189 # hanging here untill a monitored event is raised in the player
190 if getattr(self, 'changed', False): # first iteration exception
191 self.changed = self.player.monitor()
192 else: # first iteration goes through else
193 self.changed = ['playlist', 'player', 'skipped']
194 self.log.debug('changed: %s', ', '.join(self.changed))
195 if 'playlist' in self.changed:
196 self.foreach_plugin('callback_playlist')
197 if 'player' in self.changed or 'options' in self.changed:
198 self.foreach_plugin('callback_player')
199 if 'database' in self.changed:
200 self.foreach_plugin('callback_player_database')
201 if 'skipped' in self.changed:
202 if self.player.state == 'play':
203 self.log.info('Playing: %s', self.player.current)
205 self.foreach_plugin('callback_next_song')
206 if self.need_tracks():
210 # vim: ai ts=4 sw=4 sts=4 expandtab