1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009-2015, 2020, 2021 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 .mpdclient import MPD as PlayerClient
29 from .mpdclient import PlayerError
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 = PlayerClient(conf) # MPD client
50 self.short_history = deque(maxlen=60)
53 def add_history(self):
54 """Handle local, in memory, short history"""
55 self.short_history.appendleft(self.player.current)
57 def register_plugin(self, plugin_class):
58 """Registers plugin in Sima instance..."""
59 plgn = plugin_class(self)
60 prio = int(plgn.priority)
61 self._plugins.append((prio, plgn))
63 def register_core_plugin(self, plugin_class):
64 """Registers core plugins"""
65 plgn = plugin_class(self)
66 prio = int(plgn.priority)
67 self._core_plugins.append((prio, plgn))
69 def foreach_plugin(self, method, *args, **kwds):
70 """Plugin's callbacks dispatcher"""
71 self.log.trace('dispatching %s to plugins', method) # pylint: disable=no-member
72 for plugin in self.core_plugins:
73 getattr(plugin, method)(*args, **kwds)
74 for plugin in self.plugins:
75 getattr(plugin, method)(*args, **kwds)
78 def core_plugins(self):
79 return [plugin[1] for plugin in
80 sorted(self._core_plugins, key=lambda pl: pl[0], reverse=True)]
84 return [plugin[1] for plugin in
85 sorted(self._plugins, key=lambda pl: pl[0], reverse=True)]
87 def need_tracks(self):
88 """Is the player in need for tracks"""
90 self.log.debug('Queueing disabled!')
92 queue_trigger = self.config.getint('sima', 'queue_length')
93 if self.player.playmode.get('random'):
94 queue = self.player.playlist
95 self.log.debug('Currently %s track(s) in the playlist. (target %s)', len(queue), queue_trigger)
97 queue = self.player.queue
98 self.log.debug('Currently %s track(s) ahead. (target %s)', len(queue), queue_trigger)
99 if len(queue) < queue_trigger:
105 for plugin in self.plugins:
106 self.log.debug('callback_need_track: %s', plugin)
107 pl_candidates = getattr(plugin, 'callback_need_track')()
109 to_add.extend(pl_candidates)
113 self.player.add(track)
115 def reconnect_player(self):
116 """Trying to reconnect cycling through longer timeout
117 cycle : 5s 10s 1m 5m 20m 1h
119 sleepfor = [5, 10, 60, 300, 1200, 3600]
123 tmp = sleepfor.pop(0)
125 self.log.info('Trying to reconnect in %4d seconds', tmp)
128 self.player.connect()
129 except PlayerError as err:
132 except PlayerError as err:
133 # TODO: unhandled Player exceptions
134 self.log.warning('Unhandled player exception: %s', err)
135 self.log.info('Got reconnected')
137 self.foreach_plugin('start')
139 def hup_handler(self, signum, frame):
140 self.log.warning('Caught a sighup!')
141 # Cleaning pending command
143 self.foreach_plugin('shutdown')
144 self.player.disconnect()
145 raise SigHup('SIGHUP caught!')
148 """General shutdown method
150 self.log.warning('Starting shutdown.')
151 # Cleaning pending command
154 self.foreach_plugin('shutdown')
155 self.player.disconnect()
156 except PlayerError as err:
157 self.log.error('Player error during shutdown: %s', err)
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: %(host)s:%(port)s', self.config['MPD'])
167 self.player.connect()
168 self.foreach_plugin('start')
169 except PlayerError as err:
170 self.log.warning('Player: %s', err)
171 self.reconnect_player()
175 except PlayerError as err:
176 self.log.warning('Player error: %s', err)
177 self.reconnect_player()
180 """Dispatching callbacks to plugins
182 # hanging here until a monitored event is raised in the player
183 if self.changed is None: # first iteration goes through else
184 self.changed = ['playlist', 'player', 'skipped']
185 else: # Wait for a change
186 self.changed = self.player.monitor()
187 self.log.debug('changed: %s', ', '.join(self.changed))
188 if 'playlist' in self.changed:
189 self.foreach_plugin('callback_playlist')
190 if 'player' in self.changed or 'options' in self.changed:
191 self.foreach_plugin('callback_player')
192 if 'database' in self.changed:
193 self.foreach_plugin('callback_player_database')
194 if 'skipped' in self.changed:
195 if self.player.state == 'play':
196 self.log.info('Playing: %s', self.player.current)
198 self.foreach_plugin('callback_next_song')
199 if self.need_tracks():
203 # vim: ai ts=4 sw=4 sts=4 expandtab