1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009-2015, 2020 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, MPDError
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(self) # Player client
50 self.short_history = deque(maxlen=60)
52 def add_history(self):
53 """Handle local, in memory, short history"""
54 self.short_history.appendleft(self.player.current)
56 def register_plugin(self, plugin_class):
57 """Registers plugin in Sima instance..."""
58 plgn = plugin_class(self)
59 prio = int(plgn.priority)
60 self._plugins.append((prio, plgn))
62 def register_core_plugin(self, plugin_class):
63 """Registers core plugins"""
64 plgn = plugin_class(self)
65 prio = int(plgn.priority)
66 self._core_plugins.append((prio, plgn))
68 def foreach_plugin(self, method, *args, **kwds):
69 """Plugin's callbacks dispatcher"""
70 self.log.trace('dispatching %s to plugins', method) # pylint: disable=no-member
71 for plugin in self.core_plugins:
72 getattr(plugin, method)(*args, **kwds)
73 for plugin in self.plugins:
74 getattr(plugin, method)(*args, **kwds)
77 def core_plugins(self):
78 return [plugin[1] for plugin in
79 sorted(self._core_plugins, key=lambda pl: pl[0], reverse=True)]
83 return [plugin[1] for plugin in sorted(self._plugins, key=lambda pl: pl[0], reverse=True)]
85 def need_tracks(self):
86 """Is the player in need for tracks"""
88 self.log.debug('Queueing disabled!')
90 queue_trigger = self.config.getint('sima', 'queue_length')
91 if self.player.playmode.get('random'):
92 queue = self.player.playlist
93 self.log.debug('Currently %s track(s) in the playlist. (target %s)', len(queue), queue_trigger)
95 queue = self.player.queue
96 self.log.debug('Currently %s track(s) ahead. (target %s)', len(queue), queue_trigger)
97 if len(queue) < queue_trigger:
103 for plugin in self.plugins:
104 self.log.debug('callback_need_track: %s', plugin)
105 pl_candidates = getattr(plugin, 'callback_need_track')()
107 to_add.extend(pl_candidates)
111 self.player.add(track)
113 def reconnect_player(self):
114 """Trying to reconnect cycling through longer timeout
115 cycle : 5s 10s 1m 5m 20m 1h
117 sleepfor = [5, 10, 60, 300, 1200, 3600]
119 tmp = sleepfor.pop(0)
121 self.log.info('Trying to reconnect in %4d seconds', tmp)
124 self.player.connect()
125 except PlayerError as err:
128 except MPDError as err:
129 #TODO: unhandled Player exceptions
130 self.log.warning('Unhandled player exception: %s', err)
131 self.log.info('Got reconnected')
133 self.foreach_plugin('start')
135 def hup_handler(self, signum, frame):
136 self.log.warning('Caught a sighup!')
137 # Cleaning pending command
139 self.foreach_plugin('shutdown')
140 self.player.disconnect()
141 raise SigHup('SIGHUP caught!')
144 """General shutdown method
146 self.log.warning('Starting shutdown.')
147 # Cleaning pending command
149 self.foreach_plugin('shutdown')
150 self.player.disconnect()
152 self.log.info('The way is shut, it was made by those who are dead. '
153 'And the dead keep it…')
154 self.log.info('bye...')
160 self.log.info('Connecting MPD: %(host)s:%(port)s', self.config['MPD'])
161 self.player.connect()
162 self.foreach_plugin('start')
163 except (PlayerError, MPDError) as err:
164 self.log.warning('Player: %s', err)
165 self.reconnect_player()
169 except (PlayerError, MPDError) as err:
170 self.log.warning('Player error: %s', err)
171 self.reconnect_player()
175 """Dispatching callbacks to plugins
177 # hanging here until a monitored event is raised in the player
178 if getattr(self, 'changed', False): # first iteration exception
179 self.changed = self.player.monitor()
180 else: # first iteration goes through else
181 self.changed = ['playlist', 'player', 'skipped']
182 self.log.debug('changed: %s', ', '.join(self.changed))
183 if 'playlist' in self.changed:
184 self.foreach_plugin('callback_playlist')
185 if 'player' in self.changed or 'options' in self.changed:
186 self.foreach_plugin('callback_player')
187 if 'database' in self.changed:
188 self.foreach_plugin('callback_player_database')
189 if 'skipped' in self.changed:
190 if self.player.state == 'play':
191 self.log.info('Playing: %s', self.player.current)
193 self.foreach_plugin('callback_next_song')
194 if self.need_tracks():
198 # vim: ai ts=4 sw=4 sts=4 expandtab