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