]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Improved RandomFallBack and update documentation
[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 sys
24 import time
25
26 from collections import deque
27 from logging import getLogger
28
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
34
35 class Sima(Daemon):
36     """Main class, plugin and player management
37     """
38
39     def __init__(self, conf):
40         ## Set daemon
41         Daemon.__init__(self, conf.get('daemon', 'pidfile'))
42         self.enabled = True
43         self.config = conf
44         self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
45         PlayerClient.database = self.sdb
46         self.log = getLogger('sima')
47         self.plugins = list()
48         self.player = self.__get_player()  # Player client
49         try:
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         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         if not self.enabled:
77             self.log.debug('Queueing disabled!')
78             return False
79         queue = self.player.queue
80         queue_trigger = self.config.getint('sima', 'queue_length')
81         self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
82                        len(queue), queue_trigger))
83         if len(queue) < queue_trigger:
84             return True
85         return False
86
87     def queue(self):
88         to_add = list()
89         for plugin in self.plugins:
90             pl_callback =  getattr(plugin, 'callback_need_track')()
91             if pl_callback:
92                 to_add.extend(pl_callback)
93         if not to_add:
94             self.log.warning('Queue plugins returned nothing!')
95             for plugin in self.plugins:
96                 pl_callback =  getattr(plugin, 'callback_need_track_fb')()
97                 if pl_callback:
98                     to_add.extend(pl_callback)
99         for track in to_add:
100             self.player.add(track)
101
102     def reconnect_player(self):
103         """Trying to reconnect cycling through longer timeout
104         cycle : 5s 10s 1m 5m 20m 1h
105         """
106         sleepfor = [5, 10, 60, 300, 1200, 3600]
107         while True:
108             tmp = sleepfor.pop(0)
109             sleepfor.append(tmp)
110             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
111             time.sleep(tmp)
112             try:
113                 self.player.connect()
114             except PlayerError:
115                 continue
116             except PlayerUnHandledError as err:
117                 #TODO: unhandled Player exceptions
118                 self.log.warning('Unhandled player exception: %s' % err)
119             self.log.info('Got reconnected')
120             break
121
122     def hup_handler(self, signum, frame):
123         self.log.warning('Caught a sighup!')
124         self.player.disconnect()
125         self.foreach_plugin('shutdown')
126         raise SigHup('SIGHUP caught!')
127
128     def shutdown(self):
129         """General shutdown method
130         """
131         self.log.warning('Starting shutdown.')
132         self.player.disconnect()
133         self.foreach_plugin('shutdown')
134
135         self.log.info('The way is shut, it was made by those who are dead. '
136                       'And the dead keep it…')
137         self.log.info('bye...')
138
139     def run(self):
140         """
141         """
142         while 42:
143             try:
144                 self.loop()
145             except PlayerUnHandledError as err:
146                 #TODO: unhandled Player exceptions
147                 self.log.warning('Unhandled player exception: {}'.format(err))
148                 del(self.player)
149                 self.player = PlayerClient()
150                 time.sleep(10)
151             except PlayerError as err:
152                 self.log.warning('Player error: %s' % err)
153                 self.reconnect_player()
154                 del(self.changed)
155
156     def loop(self):
157         """Dispatching callbacks to plugins
158         """
159         # hanging here untill a monitored event is raised in the player
160         if getattr(self, 'changed', False): # first iteration exception
161             self.changed = self.player.monitor()
162         else:  # first iteration goes through else
163             self.changed = ['playlist', 'player', 'skipped']
164         self.log.debug('changed: {}'.format(', '.join(self.changed)))
165         if 'playlist' in self.changed:
166             self.foreach_plugin('callback_playlist')
167         if ('player' in self.changed
168             or 'options' in self.changed):
169             self.foreach_plugin('callback_player')
170         if 'database' in self.changed:
171             self.foreach_plugin('callback_player_database')
172         if 'skipped' in self.changed:
173             if self.player.state == 'play':
174                 self.log.info('Playing: {}'.format(self.player.current))
175                 self.add_history()
176                 self.foreach_plugin('callback_next_song')
177         if self.need_tracks():
178             self.queue()
179
180 # VIM MODLINE
181 # vim: ai ts=4 sw=4 sts=4 expandtab