]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Avoid randomizing in album queue mode
[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         for track in to_add:
86             self.player.add(track)
87
88     def reconnect_player(self):
89         """Trying to reconnect cycling through longer timeout
90         cycle : 5s 10s 1m 5m 20m 1h
91         """
92         sleepfor = [5, 10, 60, 300, 1200, 3600]
93         while True:
94             tmp = sleepfor.pop(0)
95             sleepfor.append(tmp)
96             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
97             time.sleep(tmp)
98             try:
99                 self.player.connect()
100             except PlayerError:
101                 continue
102             except PlayerUnHandledError as err:
103                 #TODO: unhandled Player exceptions
104                 self.log.warning('Unhandled player exception: %s' % err)
105             self.log.info('Got reconnected')
106             break
107
108     def shutdown(self):
109         """General shutdown method
110         """
111         self.log.warning('Starting shutdown.')
112         self.player.disconnect()
113         self.foreach_plugin('shutdown')
114
115         self.log.info('The way is shut, it was made by those who are dead. '
116                       'And the dead keep it…')
117         self.log.info('bye...')
118         sys.exit(0)
119
120     def run(self):
121         """
122         """
123         while 42:
124             try:
125                 self.loop()
126             except PlayerUnHandledError as err:
127                 #TODO: unhandled Player exceptions
128                 self.log.warning('Unhandled player exception: {}'.format(err))
129                 del(self.player)
130                 self.player = PlayerClient()
131                 time.sleep(10)
132             except PlayerError as err:
133                 self.log.warning('Player error: %s' % err)
134                 self.reconnect_player()
135
136     def loop(self):
137         """Dispatching callbacks to plugins
138         """
139         # hanging here untill a monitored event is raised in the player
140         if getattr(self, 'changed', False): # first iteration exception
141             self.changed = self.player.monitor()
142         else:  # first iteration goes through else
143             self.changed = ['playlist', 'player', 'skipped']
144         self.log.debug('changed: {}'.format(', '.join(self.changed)))
145         if 'playlist' in self.changed:
146             self.foreach_plugin('callback_playlist')
147         if ('player' in self.changed
148             or 'options' in self.changed):
149             self.foreach_plugin('callback_player')
150         if 'database' in self.changed:
151             self.foreach_plugin('callback_player_database')
152         if 'skipped' in self.changed:
153             if self.player.state == 'play':
154                 self.log.info('Playing: {}'.format(self.player.current))
155                 self.add_history()
156                 self.foreach_plugin('callback_next_song')
157         if self.need_tracks():
158             self.queue()
159
160 # VIM MODLINE
161 # vim: ai ts=4 sw=4 sts=4 expandtab