]> kaliko git repositories - mpd-sima.git/blob - sima/lib/simaecho.py
592ea0387f9fd718312cf50c3028e3f70f271f07
[mpd-sima.git] / sima / lib / simaecho.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
4 #
5 #   This program is free software: you can redistribute it and/or modify
6 #   it under the terms of the GNU General Public License as published by
7 #   the Free Software Foundation, either version 3 of the License, or
8 #   (at your option) any later version.
9 #
10 #   This program is distributed in the hope that it will be useful,
11 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #   GNU General Public License for more details.
14 #
15 #   You should have received a copy of the GNU General Public License
16 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 #
19
20 """
21 Consume EchoNest web service
22 """
23
24 __version__ = '0.0.2'
25 __author__ = 'Jack Kaliko'
26
27
28 from datetime import datetime, timedelta
29
30 from requests import Session, Request, Timeout, ConnectionError
31
32 from sima import ECH
33 from sima.lib.meta import Artist
34 from sima.lib.track import Track
35 from sima.lib.httpcli.controller import CacheController
36 from sima.lib.httpcli.cache import FileCache
37 from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
38 from sima.utils.utils import getws, Throttle
39 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
40     getws(ECH)
41
42 # Some definitions
43 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
44 SOCKET_TIMEOUT = 4
45
46
47 class SimaEch:
48     """EchoNest http client
49     """
50     root_url = 'http://{host}/api/{version}'.format(**ECH)
51     timestamp = datetime.utcnow()
52     ratelimit = None
53     name = 'EchoNest'
54     cache = FileCache('/home/kaliko/.local/share/mpd_sima/http')
55
56     def __init__(self):
57         self._ressource = None
58         self.current_element = None
59         self.controller = CacheController(self.cache)
60
61     def _fetch(self, payload):
62         """Use cached elements or proceed http request"""
63         req = Request('GET', self._ressource, params=payload,
64                       ).prepare()
65         if self.cache:
66             cached_response = self.controller.cached_request(req.url, req.headers)
67             if cached_response:
68                 return cached_response.json()
69
70         try:
71             return self._fetch_ws(req)
72         except Timeout:
73             raise WSTimeout('Failed to reach server within {0}s'.format(
74                                SOCKET_TIMEOUT))
75         except ConnectionError as err:
76             raise WSError(err)
77
78     @Throttle(WAIT_BETWEEN_REQUESTS)
79     def _fetch_ws(self, prepreq):
80         """fetch from web service"""
81         sess = Session()
82         resp = sess.send(prepreq, timeout=SOCKET_TIMEOUT)
83         self.__class__.ratelimit = resp.headers.get('x-ratelimit-remaining', None)
84         if resp.status_code is not 200:
85             raise WSHTTPError('{0.status_code}: {0.reason}'.format(resp))
86         ans = resp.json()
87         self._controls_answer(ans)
88         if self.cache:
89             self.controller.cache_response(resp.request, resp)
90         return ans
91
92     def _controls_answer(self, ans):
93         """Controls answer.
94         """
95         status = ans.get('response').get('status')
96         code = status.get('code')
97         if code is 0:
98             return True
99         if code is 5:
100             raise WSNotFound('Artist not found')
101         raise WSError(status.get('message'))
102
103     def _forge_payload(self, artist, top=False):
104         """Build payload
105         """
106         payload = {'api_key': ECH.get('apikey')}
107         if not isinstance(artist, Artist):
108             raise TypeError('"{0!r}" not an Artist object'.format(artist))
109         if artist.mbid:
110             payload.update(
111                     id='musicbrainz:artist:{0}'.format(artist.mbid))
112         else:
113             payload.update(name=artist.name)
114         payload.update(bucket='id:musicbrainz')
115         payload.update(results=100)
116         if top:
117             if artist.mbid:
118                 aid = payload.pop('id')
119                 payload.update(artist_id=aid)
120             else:
121                 name = payload.pop('name')
122                 payload.update(artist=name)
123             payload.update(results=100)
124             payload.update(sort='song_hotttnesss-desc')
125         return payload
126
127     def get_similar(self, artist=None):
128         """Fetch similar artists
129         """
130         payload = self._forge_payload(artist)
131         # Construct URL
132         self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
133         ans = self._fetch(payload)
134         for art in ans.get('response').get('artists'):
135             artist = {}
136             mbid = None
137             if 'foreign_ids' in art:
138                 for frgnid in art.get('foreign_ids'):
139                     if frgnid.get('catalog') == 'musicbrainz':
140                         mbid = frgnid.get('foreign_id'
141                                           ).lstrip('musicbrainz:artist:')
142             yield Artist(mbid=mbid, name=art.get('name'))
143
144     def get_toptrack(self, artist=None):
145         """Fetch artist top tracks
146         """
147         payload = self._forge_payload(artist, top=True)
148         # Construct URL
149         self._ressource = '{0}/song/search'.format(SimaEch.root_url)
150         ans = self._fetch(payload)
151         titles = list()
152         artist = {
153                 'artist': artist.name,
154                 'musicbrainz_artistid': artist.mbid,
155                 }
156         for song in ans.get('response').get('songs'):
157             title = song.get('title')
158             if title not in titles:
159                 titles.append(title)
160                 yield Track(title=title, **artist)
161
162
163 # VIM MODLINE
164 # vim: ai ts=4 sw=4 sts=4 expandtab