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
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')
47 self.player = self.__get_player() # Player client
48 self.short_history = deque(maxlen=60)
50 def __get_player(self):
51 """Instanciate the player"""
52 host = self.config.get('MPD', 'host')
53 port = self.config.get('MPD', 'port')
54 pswd = self.config.get('MPD', 'password', fallback=None)
55 return PlayerClient(host, port, pswd)
57 def add_history(self):
58 """Handle local short history"""
59 self.short_history.appendleft(self.player.current)
61 def register_plugin(self, plugin_class):
62 """Registers plugin in Sima instance..."""
63 self.plugins.append(plugin_class(self))
65 def foreach_plugin(self, method, *args, **kwds):
66 """Plugin's callbacks dispatcher"""
67 for plugin in self.plugins:
68 #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
69 getattr(plugin, method)(*args, **kwds)
71 def need_tracks(self):
72 """Is the player in need for tracks"""
74 self.log.debug('Queueing disabled!')
76 queue = self.player.queue
77 queue_trigger = self.config.getint('sima', 'queue_length')
78 self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
79 len(queue), queue_trigger))
80 if len(queue) < queue_trigger:
86 for plugin in self.plugins:
87 pl_callback = getattr(plugin, 'callback_need_track')()
89 to_add.extend(pl_callback)
91 self.log.warning('Queue plugins returned nothing!')
92 for plugin in self.plugins:
93 pl_callback = getattr(plugin, 'callback_need_track_fb')()
95 to_add.extend(pl_callback)
97 self.player.add(track)
99 def reconnect_player(self):
100 """Trying to reconnect cycling through longer timeout
101 cycle : 5s 10s 1m 5m 20m 1h
103 sleepfor = [5, 10, 60, 300, 1200, 3600]
105 tmp = sleepfor.pop(0)
107 self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
110 self.player.connect()
111 except PlayerError as err:
114 except PlayerUnHandledError as err:
115 #TODO: unhandled Player exceptions
116 self.log.warning('Unhandled player exception: %s' % err)
117 self.log.info('Got reconnected')
119 self.foreach_plugin('start')
121 def hup_handler(self, signum, frame):
122 self.log.warning('Caught a sighup!')
123 # Cleaning pending command
125 self.foreach_plugin('shutdown')
126 self.player.disconnect()
127 raise SigHup('SIGHUP caught!')
130 """General shutdown method
132 self.log.warning('Starting shutdown.')
133 # Cleaning pending command
135 self.foreach_plugin('shutdown')
136 self.player.disconnect()
138 self.log.info('The way is shut, it was made by those who are dead. '
139 'And the dead keep it…')
140 self.log.info('bye...')
146 self.log.info('Connecting MPD: {0}:{1}'.format(*self.player._mpd))
147 self.player.connect()
148 except (PlayerError, PlayerUnHandledError) as err:
149 self.log.warning('Player: {}'.format(err))
150 self.reconnect_player()
151 self.foreach_plugin('start')
155 except PlayerUnHandledError as err:
156 #TODO: unhandled Player exceptions
157 self.log.warning('Unhandled player exception: {}'.format(err))
159 self.player = PlayerClient()
161 except PlayerError as err:
162 self.log.warning('Player error: %s' % err)
163 self.reconnect_player()
167 """Dispatching callbacks to plugins
169 # hanging here untill a monitored event is raised in the player
170 if getattr(self, 'changed', False): # first iteration exception
171 self.changed = self.player.monitor()
172 else: # first iteration goes through else
173 self.changed = ['playlist', 'player', 'skipped']
174 self.log.debug('changed: {}'.format(', '.join(self.changed)))
175 if 'playlist' in self.changed:
176 self.foreach_plugin('callback_playlist')
177 if ('player' in self.changed
178 or 'options' in self.changed):
179 self.foreach_plugin('callback_player')
180 if 'database' in self.changed:
181 self.foreach_plugin('callback_player_database')
182 if 'skipped' in self.changed:
183 if self.player.state == 'play':
184 self.log.info('Playing: {}'.format(self.player.current))
186 self.foreach_plugin('callback_next_song')
187 if self.need_tracks():
191 # vim: ai ts=4 sw=4 sts=4 expandtab