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