]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Fixed SIGHUP issue hopefully.
[mpd-sima.git] / sima / core.py
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009, 2010, 2011, 2013, 2014 Jack Kaliko <kaliko@azylum.org>
3 #
4 #  This file is part of sima
5 #
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.
10 #
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.
15 #
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/>.
18 #
19 #
20 """Core Object dealing with plugins and player client
21 """
22
23 import time
24
25 from collections import deque
26 from logging import getLogger
27
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
33
34 class Sima(Daemon):
35     """Main class, plugin and player management
36     """
37
38     def __init__(self, conf):
39         ## Set daemon
40         Daemon.__init__(self, conf.get('daemon', 'pidfile'))
41         self.enabled = True
42         self.config = conf
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.player = self.__get_player()  # Player client
48         try:
49             self.log.info('Connecting MPD: {0}:{1}'.format(*self.player._mpd))
50             self.player.connect()
51         except (PlayerError, PlayerUnHandledError) as err:
52             self.log.warning('Player: {}'.format(err))
53         self.short_history = deque(maxlen=60)
54
55     def __get_player(self):
56         """Instanciate the player"""
57         host = self.config.get('MPD', 'host')
58         port = self.config.get('MPD', 'port')
59         pswd = self.config.get('MPD', 'password', fallback=None)
60         return PlayerClient(host, port, pswd)
61
62     def add_history(self):
63         """Handle local short history"""
64         self.short_history.appendleft(self.player.current)
65
66     def register_plugin(self, plugin_class):
67         """Registers plugin in Sima instance..."""
68         self.plugins.append(plugin_class(self))
69
70     def foreach_plugin(self, method, *args, **kwds):
71         """Plugin's callbacks dispatcher"""
72         for plugin in self.plugins:
73             #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
74             getattr(plugin, method)(*args, **kwds)
75
76     def need_tracks(self):
77         """Is the player in need for tracks"""
78         if not self.enabled:
79             self.log.debug('Queueing disabled!')
80             return False
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:
86             return True
87         return False
88
89     def queue(self):
90         to_add = list()
91         for plugin in self.plugins:
92             pl_callback = getattr(plugin, 'callback_need_track')()
93             if pl_callback:
94                 to_add.extend(pl_callback)
95         if not to_add:
96             self.log.warning('Queue plugins returned nothing!')
97             for plugin in self.plugins:
98                 pl_callback = getattr(plugin, 'callback_need_track_fb')()
99                 if pl_callback:
100                     to_add.extend(pl_callback)
101         for track in to_add:
102             self.player.add(track)
103
104     def reconnect_player(self):
105         """Trying to reconnect cycling through longer timeout
106         cycle : 5s 10s 1m 5m 20m 1h
107         """
108         sleepfor = [5, 10, 60, 300, 1200, 3600]
109         while True:
110             tmp = sleepfor.pop(0)
111             sleepfor.append(tmp)
112             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
113             time.sleep(tmp)
114             try:
115                 self.player.connect()
116             except PlayerError:
117                 continue
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')
122             break
123
124     def hup_handler(self, signum, frame):
125         self.log.warning('Caught a sighup!')
126         # Cleaning pending command
127         self.player.clean()
128         self.foreach_plugin('shutdown')
129         self.player.disconnect()
130         raise SigHup('SIGHUP caught!')
131
132     def shutdown(self):
133         """General shutdown method
134         """
135         self.log.warning('Starting shutdown.')
136         # Cleaning pending command
137         self.player.clean()
138         self.foreach_plugin('shutdown')
139         self.player.disconnect()
140
141         self.log.info('The way is shut, it was made by those who are dead. '
142                       'And the dead keep it…')
143         self.log.info('bye...')
144
145     def run(self):
146         """
147         """
148         while 42:
149             try:
150                 self.loop()
151             except PlayerUnHandledError as err:
152                 #TODO: unhandled Player exceptions
153                 self.log.warning('Unhandled player exception: {}'.format(err))
154                 del self.player
155                 self.player = PlayerClient()
156                 time.sleep(10)
157             except PlayerError as err:
158                 self.log.warning('Player error: %s' % err)
159                 self.reconnect_player()
160                 del self.changed
161
162     def loop(self):
163         """Dispatching callbacks to plugins
164         """
165         # hanging here untill a monitored event is raised in the player
166         if getattr(self, 'changed', False): # first iteration exception
167             self.changed = self.player.monitor()
168         else:  # first iteration goes through else
169             self.changed = ['playlist', 'player', 'skipped']
170         self.log.debug('changed: {}'.format(', '.join(self.changed)))
171         if 'playlist' in self.changed:
172             self.foreach_plugin('callback_playlist')
173         if ('player' in self.changed
174             or 'options' in self.changed):
175             self.foreach_plugin('callback_player')
176         if 'database' in self.changed:
177             self.foreach_plugin('callback_player_database')
178         if 'skipped' in self.changed:
179             if self.player.state == 'play':
180                 self.log.info('Playing: {}'.format(self.player.current))
181                 self.add_history()
182                 self.foreach_plugin('callback_next_song')
183         if self.need_tracks():
184             self.queue()
185
186 # VIM MODLINE
187 # vim: ai ts=4 sw=4 sts=4 expandtab