]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
3532c88e951e1f06e13c563debcc64909f5ed84f
[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         self.short_history = deque(maxlen=60)
49
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)
56
57     def add_history(self):
58         """Handle local short history"""
59         self.short_history.appendleft(self.player.current)
60
61     def register_plugin(self, plugin_class):
62         """Registers plugin in Sima instance..."""
63         self.plugins.append(plugin_class(self))
64
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)
70
71     def need_tracks(self):
72         """Is the player in need for tracks"""
73         if not self.enabled:
74             self.log.debug('Queueing disabled!')
75             return False
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:
81             return True
82         return False
83
84     def queue(self):
85         to_add = list()
86         for plugin in self.plugins:
87             pl_callback = getattr(plugin, 'callback_need_track')()
88             if pl_callback:
89                 to_add.extend(pl_callback)
90         if not to_add:
91             self.log.warning('Queue plugins returned nothing!')
92             for plugin in self.plugins:
93                 pl_callback = getattr(plugin, 'callback_need_track_fb')()
94                 if pl_callback:
95                     to_add.extend(pl_callback)
96         for track in to_add:
97             self.player.add(track)
98
99     def reconnect_player(self):
100         """Trying to reconnect cycling through longer timeout
101         cycle : 5s 10s 1m 5m 20m 1h
102         """
103         sleepfor = [5, 10, 60, 300, 1200, 3600]
104         while True:
105             tmp = sleepfor.pop(0)
106             sleepfor.append(tmp)
107             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
108             time.sleep(tmp)
109             try:
110                 self.player.connect()
111             except PlayerError as err:
112                 self.log.debug(err)
113                 continue
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')
118             break
119         self.foreach_plugin('start')
120
121     def hup_handler(self, signum, frame):
122         self.log.warning('Caught a sighup!')
123         # Cleaning pending command
124         self.player.clean()
125         self.foreach_plugin('shutdown')
126         self.player.disconnect()
127         raise SigHup('SIGHUP caught!')
128
129     def shutdown(self):
130         """General shutdown method
131         """
132         self.log.warning('Starting shutdown.')
133         # Cleaning pending command
134         self.player.clean()
135         self.foreach_plugin('shutdown')
136         self.player.disconnect()
137
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...')
141
142     def run(self):
143         """
144         """
145         try:
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')
152         while 42:
153             try:
154                 self.loop()
155             except PlayerUnHandledError as err:
156                 #TODO: unhandled Player exceptions
157                 self.log.warning('Unhandled player exception: {}'.format(err))
158                 del self.player
159                 self.player = PlayerClient()
160                 time.sleep(10)
161             except PlayerError as err:
162                 self.log.warning('Player error: %s' % err)
163                 self.reconnect_player()
164                 del self.changed
165
166     def loop(self):
167         """Dispatching callbacks to plugins
168         """
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))
185                 self.add_history()
186                 self.foreach_plugin('callback_next_song')
187         if self.need_tracks():
188             self.queue()
189
190 # VIM MODLINE
191 # vim: ai ts=4 sw=4 sts=4 expandtab