1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009, 2010, 2011, 2013, 2014 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
26 from collections import deque
27 from logging import getLogger
29 from .client import PlayerClient
30 from .client import PlayerError, PlayerUnHandledError
31 from .lib.simadb import SimaDB
32 from .lib.daemon import Daemon
33 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')
48 self.player = self.__get_player() # Player client
51 except (PlayerError, PlayerUnHandledError) as err:
52 self.log.error('Fails to connect player: {}'.format(err))
55 self.short_history = deque(maxlen=60)
57 def __get_player(self):
58 """Instanciate the player"""
59 host = self.config.get('MPD', 'host')
60 port = self.config.get('MPD', 'port')
61 pswd = self.config.get('MPD', 'password', fallback=None)
62 return PlayerClient(host, port, pswd)
64 def add_history(self):
65 self.short_history.appendleft(self.player.current)
67 def register_plugin(self, plugin_class):
68 """Registers plubin in Sima instance..."""
69 self.plugins.append(plugin_class(self))
71 def foreach_plugin(self, method, *args, **kwds):
72 """Plugin's callbacks dispatcher"""
73 for plugin in self.plugins:
74 #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
75 getattr(plugin, method)(*args, **kwds)
77 def need_tracks(self):
79 self.log.debug('Queueing disabled!')
81 queue = self.player.queue
82 queue_trigger = self.config.getint('sima', 'queue_length')
83 self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
84 len(queue), queue_trigger))
85 if len(queue) < queue_trigger:
91 for plugin in self.plugins:
92 pl_callback = getattr(plugin, 'callback_need_track')()
94 to_add.extend(pl_callback)
96 self.log.warning('Queue plugins returned nothing!')
97 for plugin in self.plugins:
98 pl_callback = getattr(plugin, 'callback_need_track_fb')()
100 to_add.extend(pl_callback)
102 self.player.add(track)
104 def reconnect_player(self):
105 """Trying to reconnect cycling through longer timeout
106 cycle : 5s 10s 1m 5m 20m 1h
108 sleepfor = [5, 10, 60, 300, 1200, 3600]
110 tmp = sleepfor.pop(0)
112 self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
115 self.player.connect()
118 except PlayerUnHandledError as err:
119 #TODO: unhandled Player exceptions
120 self.log.warning('Unhandled player exception: %s' % err)
121 self.log.info('Got reconnected')
124 def hup_handler(self, signum, frame):
125 self.log.warning('Caught a sighup!')
126 self.player.disconnect()
127 self.foreach_plugin('shutdown')
128 raise SigHup('SIGHUP caught!')
131 """General shutdown method
133 self.log.warning('Starting shutdown.')
134 self.player.disconnect()
135 self.foreach_plugin('shutdown')
137 self.log.info('The way is shut, it was made by those who are dead. '
138 'And the dead keep it…')
139 self.log.info('bye...')
147 except PlayerUnHandledError as err:
148 #TODO: unhandled Player exceptions
149 self.log.warning('Unhandled player exception: {}'.format(err))
151 self.player = PlayerClient()
153 except PlayerError as err:
154 self.log.warning('Player error: %s' % err)
155 self.reconnect_player()
159 """Dispatching callbacks to plugins
161 # hanging here untill a monitored event is raised in the player
162 if getattr(self, 'changed', False): # first iteration exception
163 self.changed = self.player.monitor()
164 else: # first iteration goes through else
165 self.changed = ['playlist', 'player', 'skipped']
166 self.log.debug('changed: {}'.format(', '.join(self.changed)))
167 if 'playlist' in self.changed:
168 self.foreach_plugin('callback_playlist')
169 if ('player' in self.changed
170 or 'options' in self.changed):
171 self.foreach_plugin('callback_player')
172 if 'database' in self.changed:
173 self.foreach_plugin('callback_player_database')
174 if 'skipped' in self.changed:
175 if self.player.state == 'play':
176 self.log.info('Playing: {}'.format(self.player.current))
178 self.foreach_plugin('callback_next_song')
179 if self.need_tracks():
183 # vim: ai ts=4 sw=4 sts=4 expandtab