]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Shuffles track to add
[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.0.b'
6 __author__ = 'kaliko jack'
7 __url__ = 'git://git.kaliko.me/sima.git'
8
9 import random
10 import sys
11 import time
12
13 from collections import deque
14 from logging import getLogger
15
16 from .client import PlayerClient
17 from .client import PlayerError, PlayerUnHandledError
18 from .lib.simadb import SimaDB
19 from .lib.daemon import Daemon
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         self.log = getLogger('sima')
32         self.plugins = list()
33         self.player = self.__get_player()  # Player client
34         try:
35             self.player.connect()
36         except (PlayerError, PlayerUnHandledError) as err:
37             self.log.error('Fails to connect player: {}'.format(err))
38             self.shutdown()
39         self.short_history = deque(maxlen=60)
40
41     def __get_player(self):
42         """Instanciate the player"""
43         host = self.config.get('MPD', 'host')
44         port = self.config.get('MPD', 'port')
45         pswd = self.config.get('MPD', 'password', fallback=None)
46         return PlayerClient(host, port, pswd)
47
48     def add_history(self):
49         self.short_history.appendleft(self.player.current)
50
51     def register_plugin(self, plugin_class):
52         """Registers plubin in Sima instance..."""
53         self.plugins.append(plugin_class(self))
54
55     def foreach_plugin(self, method, *args, **kwds):
56         """Plugin's callbacks dispatcher"""
57         for plugin in self.plugins:
58             #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
59             getattr(plugin, method)(*args, **kwds)
60
61     def need_tracks(self):
62         if not self.enabled:
63             self.log.debug('Queueing disabled!')
64             return False
65         queue = self.player.queue
66         queue_trigger = self.config.getint('sima', 'queue_length')
67         self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
68                        len(queue), queue_trigger))
69         if len(queue) < queue_trigger:
70             return True
71         return False
72
73     def queue(self):
74         to_add = list()
75         for plugin in self.plugins:
76             pl_callback =  getattr(plugin, 'callback_need_track')()
77             if pl_callback:
78                 to_add.extend(pl_callback)
79         if not to_add:
80             self.log.warning('Queue plugins returned nothing!')
81             for plugin in self.plugins:
82                 pl_callback =  getattr(plugin, 'callback_need_track_fb')()
83                 if pl_callback:
84                     to_add.extend(pl_callback)
85         random.shuffle(to_add)
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 shutdown(self):
110         """General shutdown method
111         """
112         self.log.warning('Starting shutdown.')
113         self.player.disconnect()
114         self.foreach_plugin('shutdown')
115
116         self.log.info('The way is shut, it was made by those who are dead. '
117                       'And the dead keep it…')
118         self.log.info('bye...')
119         sys.exit(0)
120
121     def run(self):
122         """
123         """
124         while 42:
125             try:
126                 self.loop()
127             except PlayerUnHandledError as err:
128                 #TODO: unhandled Player exceptions
129                 self.log.warning('Unhandled player exception: {}'.format(err))
130                 del(self.player)
131                 self.player = PlayerClient()
132                 time.sleep(10)
133             except PlayerError as err:
134                 self.log.warning('Player error: %s' % err)
135                 self.reconnect_player()
136
137     def loop(self):
138         """Dispatching callbacks to plugins
139         """
140         # hanging here untill a monitored event is raised in the player
141         if getattr(self, 'changed', False): # first iteration exception
142             self.changed = self.player.monitor()
143         else:  # first iteration goes through else
144             self.changed = ['playlist', 'player', 'skipped']
145         self.log.debug('changed: {}'.format(', '.join(self.changed)))
146         if 'playlist' in self.changed:
147             self.foreach_plugin('callback_playlist')
148         if ('player' in self.changed
149             or 'options' in self.changed):
150             self.foreach_plugin('callback_player')
151         if 'database' in self.changed:
152             self.foreach_plugin('callback_player_database')
153         if 'skipped' in self.changed:
154             if self.player.state == 'play':
155                 self.log.info('Playing: {}'.format(self.player.current))
156                 self.add_history()
157                 self.foreach_plugin('callback_next_song')
158         if self.need_tracks():
159             self.queue()
160
161 # VIM MODLINE
162 # vim: ai ts=4 sw=4 sts=4 expandtab