]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Catches SIGHUP/SIGUSR1 to trigger conf reload
[mpd-sima.git] / sima / core.py
1 # -*- coding: utf-8 -*-
2 """Core Object dealing with plugins and player client
3 """
4
5 __version__ = '0.12.0pr1'
6 __author__ = 'kaliko jack'
7 __url__ = 'git://git.kaliko.me/sima.git'
8
9 import sys
10 import time
11
12 from collections import deque
13 from logging import getLogger
14
15 from .client import PlayerClient
16 from .client import PlayerError, PlayerUnHandledError
17 from .lib.simadb import SimaDB
18 from .lib.daemon import Daemon
19 from .utils.utils import SigHup
20
21 class Sima(Daemon):
22     """Main class, plugin and player management
23     """
24
25     def __init__(self, conf):
26         ## Set daemon
27         Daemon.__init__(self, conf.get('daemon', 'pidfile'))
28         self.enabled = True
29         self.config = conf
30         self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
31         PlayerClient.database = self.sdb
32         self.log = getLogger('sima')
33         self.plugins = list()
34         self.player = self.__get_player()  # Player client
35         try:
36             self.player.connect()
37         except (PlayerError, PlayerUnHandledError) as err:
38             self.log.error('Fails to connect player: {}'.format(err))
39             self.shutdown()
40         self.short_history = deque(maxlen=60)
41
42     def __get_player(self):
43         """Instanciate the player"""
44         host = self.config.get('MPD', 'host')
45         port = self.config.get('MPD', 'port')
46         pswd = self.config.get('MPD', 'password', fallback=None)
47         return PlayerClient(host, port, pswd)
48
49     def add_history(self):
50         self.short_history.appendleft(self.player.current)
51
52     def register_plugin(self, plugin_class):
53         """Registers plubin in Sima instance..."""
54         self.plugins.append(plugin_class(self))
55
56     def foreach_plugin(self, method, *args, **kwds):
57         """Plugin's callbacks dispatcher"""
58         for plugin in self.plugins:
59             #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
60             getattr(plugin, method)(*args, **kwds)
61
62     def need_tracks(self):
63         if not self.enabled:
64             self.log.debug('Queueing disabled!')
65             return False
66         queue = self.player.queue
67         queue_trigger = self.config.getint('sima', 'queue_length')
68         self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
69                        len(queue), queue_trigger))
70         if len(queue) < queue_trigger:
71             return True
72         return False
73
74     def queue(self):
75         to_add = list()
76         for plugin in self.plugins:
77             pl_callback =  getattr(plugin, 'callback_need_track')()
78             if pl_callback:
79                 to_add.extend(pl_callback)
80         if not to_add:
81             self.log.warning('Queue plugins returned nothing!')
82             for plugin in self.plugins:
83                 pl_callback =  getattr(plugin, 'callback_need_track_fb')()
84                 if pl_callback:
85                     to_add.extend(pl_callback)
86         for track in to_add:
87             self.player.add(track)
88
89     def reconnect_player(self):
90         """Trying to reconnect cycling through longer timeout
91         cycle : 5s 10s 1m 5m 20m 1h
92         """
93         sleepfor = [5, 10, 60, 300, 1200, 3600]
94         while True:
95             tmp = sleepfor.pop(0)
96             sleepfor.append(tmp)
97             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
98             time.sleep(tmp)
99             try:
100                 self.player.connect()
101             except PlayerError:
102                 continue
103             except PlayerUnHandledError as err:
104                 #TODO: unhandled Player exceptions
105                 self.log.warning('Unhandled player exception: %s' % err)
106             self.log.info('Got reconnected')
107             break
108
109     def hup_handler(self, signum, frame):
110         self.log.warning('Caught a sighup!')
111         self.player.disconnect()
112         self.foreach_plugin('shutdown')
113         raise SigHup('SIGHUP caught!')
114
115     def shutdown(self):
116         """General shutdown method
117         """
118         self.log.warning('Starting shutdown.')
119         self.player.disconnect()
120         self.foreach_plugin('shutdown')
121
122         self.log.info('The way is shut, it was made by those who are dead. '
123                       'And the dead keep it…')
124         self.log.info('bye...')
125
126     def run(self):
127         """
128         """
129         while 42:
130             try:
131                 self.loop()
132             except PlayerUnHandledError as err:
133                 #TODO: unhandled Player exceptions
134                 self.log.warning('Unhandled player exception: {}'.format(err))
135                 del(self.player)
136                 self.player = PlayerClient()
137                 time.sleep(10)
138             except PlayerError as err:
139                 self.log.warning('Player error: %s' % err)
140                 self.reconnect_player()
141                 del(self.changed)
142
143     def loop(self):
144         """Dispatching callbacks to plugins
145         """
146         # hanging here untill a monitored event is raised in the player
147         if getattr(self, 'changed', False): # first iteration exception
148             self.changed = self.player.monitor()
149         else:  # first iteration goes through else
150             self.changed = ['playlist', 'player', 'skipped']
151         self.log.debug('changed: {}'.format(', '.join(self.changed)))
152         if 'playlist' in self.changed:
153             self.foreach_plugin('callback_playlist')
154         if ('player' in self.changed
155             or 'options' in self.changed):
156             self.foreach_plugin('callback_player')
157         if 'database' in self.changed:
158             self.foreach_plugin('callback_player_database')
159         if 'skipped' in self.changed:
160             if self.player.state == 'play':
161                 self.log.info('Playing: {}'.format(self.player.current))
162                 self.add_history()
163                 self.foreach_plugin('callback_next_song')
164         if self.need_tracks():
165             self.queue()
166
167 # VIM MODLINE
168 # vim: ai ts=4 sw=4 sts=4 expandtab