]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Fixed blacklisting in track mode
[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.player.connect()
50         except (PlayerError, PlayerUnHandledError) as err:
51             self.log.warning('Player: {}'.format(err))
52         self.short_history = deque(maxlen=60)
53
54     def __get_player(self):
55         """Instanciate the player"""
56         host = self.config.get('MPD', 'host')
57         port = self.config.get('MPD', 'port')
58         pswd = self.config.get('MPD', 'password', fallback=None)
59         return PlayerClient(host, port, pswd)
60
61     def add_history(self):
62         """Handle local short history"""
63         self.short_history.appendleft(self.player.current)
64
65     def register_plugin(self, plugin_class):
66         """Registers plubin in Sima instance..."""
67         self.plugins.append(plugin_class(self))
68
69     def foreach_plugin(self, method, *args, **kwds):
70         """Plugin's callbacks dispatcher"""
71         for plugin in self.plugins:
72             #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
73             getattr(plugin, method)(*args, **kwds)
74
75     def need_tracks(self):
76         """Is the player in need for tracks"""
77         if not self.enabled:
78             self.log.debug('Queueing disabled!')
79             return False
80         queue = self.player.queue
81         queue_trigger = self.config.getint('sima', 'queue_length')
82         self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
83                        len(queue), queue_trigger))
84         if len(queue) < queue_trigger:
85             return True
86         return False
87
88     def queue(self):
89         to_add = list()
90         for plugin in self.plugins:
91             pl_callback = getattr(plugin, 'callback_need_track')()
92             if pl_callback:
93                 to_add.extend(pl_callback)
94         if not to_add:
95             self.log.warning('Queue plugins returned nothing!')
96             for plugin in self.plugins:
97                 pl_callback = getattr(plugin, 'callback_need_track_fb')()
98                 if pl_callback:
99                     to_add.extend(pl_callback)
100         for track in to_add:
101             self.player.add(track)
102
103     def reconnect_player(self):
104         """Trying to reconnect cycling through longer timeout
105         cycle : 5s 10s 1m 5m 20m 1h
106         """
107         sleepfor = [5, 10, 60, 300, 1200, 3600]
108         while True:
109             tmp = sleepfor.pop(0)
110             sleepfor.append(tmp)
111             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
112             time.sleep(tmp)
113             try:
114                 self.player.connect()
115             except PlayerError:
116                 continue
117             except PlayerUnHandledError as err:
118                 #TODO: unhandled Player exceptions
119                 self.log.warning('Unhandled player exception: %s' % err)
120             self.log.info('Got reconnected')
121             break
122
123     def hup_handler(self, signum, frame):
124         self.log.warning('Caught a sighup!')
125         self.player.disconnect()
126         self.foreach_plugin('shutdown')
127         raise SigHup('SIGHUP caught!')
128
129     def shutdown(self):
130         """General shutdown method
131         """
132         self.log.warning('Starting shutdown.')
133         self.player.disconnect()
134         self.foreach_plugin('shutdown')
135
136         self.log.info('The way is shut, it was made by those who are dead. '
137                       'And the dead keep it…')
138         self.log.info('bye...')
139
140     def run(self):
141         """
142         """
143         while 42:
144             try:
145                 self.loop()
146             except PlayerUnHandledError as err:
147                 #TODO: unhandled Player exceptions
148                 self.log.warning('Unhandled player exception: {}'.format(err))
149                 del self.player
150                 self.player = PlayerClient()
151                 time.sleep(10)
152             except PlayerError as err:
153                 self.log.warning('Player error: %s' % err)
154                 self.reconnect_player()
155                 del self.changed
156
157     def loop(self):
158         """Dispatching callbacks to plugins
159         """
160         # hanging here untill a monitored event is raised in the player
161         if getattr(self, 'changed', False): # first iteration exception
162             self.changed = self.player.monitor()
163         else:  # first iteration goes through else
164             self.changed = ['playlist', 'player', 'skipped']
165         self.log.debug('changed: {}'.format(', '.join(self.changed)))
166         if 'playlist' in self.changed:
167             self.foreach_plugin('callback_playlist')
168         if ('player' in self.changed
169             or 'options' in self.changed):
170             self.foreach_plugin('callback_player')
171         if 'database' in self.changed:
172             self.foreach_plugin('callback_player_database')
173         if 'skipped' in self.changed:
174             if self.player.state == 'play':
175                 self.log.info('Playing: {}'.format(self.player.current))
176                 self.add_history()
177                 self.foreach_plugin('callback_next_song')
178         if self.need_tracks():
179             self.queue()
180
181 # VIM MODLINE
182 # vim: ai ts=4 sw=4 sts=4 expandtab