]> kaliko git repositories - mpd-sima.git/blob - sima/core.py
Use client2client to detect other instance
[mpd-sima.git] / sima / core.py
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2009, 2010, 2011, 2013, 2014 Jack Kaliko <kaliko@azylum.org>
3 #
4 #  This file is part of sima
5 #
6 #  sima is free software: you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation, either version 3 of the License, or
9 #  (at your option) any later version.
10 #
11 #  sima is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with sima.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 #
20 """Core Object dealing with plugins and player client
21 """
22
23 import time
24
25 from collections import deque
26 from logging import getLogger
27
28 from .client import PlayerClient
29 from .client import PlayerError, PlayerUnHandledError
30 from .lib.simadb import SimaDB
31 from .lib.daemon import Daemon
32 from .utils.utils import SigHup
33
34 class Sima(Daemon):
35     """Main class, plugin and player management
36     """
37
38     def __init__(self, conf):
39         ## Set daemon
40         Daemon.__init__(self, conf.get('daemon', 'pidfile'))
41         self.enabled = True
42         self.config = conf
43         self.sdb = SimaDB(db_path=conf.get('sima', 'db_file'))
44         PlayerClient.database = self.sdb
45         self.log = getLogger('sima')
46         self.plugins = list()
47         self.player = self.__get_player()  # Player client
48         try:
49             self.log.info('Connecting MPD: {0}:{1}'.format(*self.player._mpd))
50             self.player.connect()
51         except (PlayerError, PlayerUnHandledError) as err:
52             self.log.warning('Player: {}'.format(err))
53         self.short_history = deque(maxlen=60)
54
55     def __get_player(self):
56         """Instanciate the player"""
57         host = self.config.get('MPD', 'host')
58         port = self.config.get('MPD', 'port')
59         pswd = self.config.get('MPD', 'password', fallback=None)
60         return PlayerClient(host, port, pswd)
61
62     def add_history(self):
63         """Handle local short history"""
64         self.short_history.appendleft(self.player.current)
65
66     def register_plugin(self, plugin_class):
67         """Registers plugin in Sima instance..."""
68         self.plugins.append(plugin_class(self))
69
70     def foreach_plugin(self, method, *args, **kwds):
71         """Plugin's callbacks dispatcher"""
72         for plugin in self.plugins:
73             #self.log.debug('dispatching {0} to {1}'.format(method, plugin))
74             getattr(plugin, method)(*args, **kwds)
75
76     def need_tracks(self):
77         """Is the player in need for tracks"""
78         if not self.enabled:
79             self.log.debug('Queueing disabled!')
80             return False
81         queue = self.player.queue
82         queue_trigger = self.config.getint('sima', 'queue_length')
83         self.log.debug('Currently {0} track(s) ahead. (target {1})'.format(
84                        len(queue), queue_trigger))
85         if len(queue) < queue_trigger:
86             return True
87         return False
88
89     def queue(self):
90         to_add = list()
91         for plugin in self.plugins:
92             pl_callback = getattr(plugin, 'callback_need_track')()
93             if pl_callback:
94                 to_add.extend(pl_callback)
95         if not to_add:
96             self.log.warning('Queue plugins returned nothing!')
97             for plugin in self.plugins:
98                 pl_callback = getattr(plugin, 'callback_need_track_fb')()
99                 if pl_callback:
100                     to_add.extend(pl_callback)
101         for track in to_add:
102             self.player.add(track)
103
104     def reconnect_player(self):
105         """Trying to reconnect cycling through longer timeout
106         cycle : 5s 10s 1m 5m 20m 1h
107         """
108         sleepfor = [5, 10, 60, 300, 1200, 3600]
109         while True:
110             tmp = sleepfor.pop(0)
111             sleepfor.append(tmp)
112             self.log.info('Trying to reconnect in {:>4d} seconds'.format(tmp))
113             time.sleep(tmp)
114             try:
115                 self.player.connect()
116             except PlayerError:
117                 continue
118             except PlayerUnHandledError as err:
119                 #TODO: unhandled Player exceptions
120                 self.log.warning('Unhandled player exception: %s' % err)
121             self.log.info('Got reconnected')
122             break
123
124     def hup_handler(self, signum, frame):
125         self.log.warning('Caught a sighup!')
126         self.player.disconnect()
127         self.foreach_plugin('shutdown')
128         raise SigHup('SIGHUP caught!')
129
130     def shutdown(self):
131         """General shutdown method
132         """
133         self.log.warning('Starting shutdown.')
134         self.foreach_plugin('shutdown')
135         self.player.disconnect()
136
137         self.log.info('The way is shut, it was made by those who are dead. '
138                       'And the dead keep it…')
139         self.log.info('bye...')
140
141     def run(self):
142         """
143         """
144         while 42:
145             try:
146                 self.loop()
147             except PlayerUnHandledError as err:
148                 #TODO: unhandled Player exceptions
149                 self.log.warning('Unhandled player exception: {}'.format(err))
150                 del self.player
151                 self.player = PlayerClient()
152                 time.sleep(10)
153             except PlayerError as err:
154                 self.log.warning('Player error: %s' % err)
155                 self.reconnect_player()
156                 del self.changed
157
158     def loop(self):
159         """Dispatching callbacks to plugins
160         """
161         # hanging here untill a monitored event is raised in the player
162         if getattr(self, 'changed', False): # first iteration exception
163             self.changed = self.player.monitor()
164         else:  # first iteration goes through else
165             self.changed = ['playlist', 'player', 'skipped']
166         self.log.debug('changed: {}'.format(', '.join(self.changed)))
167         if 'playlist' in self.changed:
168             self.foreach_plugin('callback_playlist')
169         if ('player' in self.changed
170             or 'options' in self.changed):
171             self.foreach_plugin('callback_player')
172         if 'database' in self.changed:
173             self.foreach_plugin('callback_player_database')
174         if 'skipped' in self.changed:
175             if self.player.state == 'play':
176                 self.log.info('Playing: {}'.format(self.player.current))
177                 self.add_history()
178                 self.foreach_plugin('callback_next_song')
179         if self.need_tracks():
180             self.queue()
181
182 # VIM MODLINE
183 # vim: ai ts=4 sw=4 sts=4 expandtab