1# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
2#
3# Copyright (C) 2009 John Iacona
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 2, or (at your option)
8# any later version.
9#
10# The Rhythmbox authors hereby grant permission for non-GPL compatible
11# GStreamer plugins to be used and distributed together with GStreamer
12# and Rhythmbox. This permission is above and beyond the permissions granted
13# by the GPL license by which Rhythmbox is covered. If you modify this code
14# you may extend this exception to your version of the code, but you are not
15# obligated to do so. If you do not wish to do so, delete this exception
16# statement from your version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
26
27import os
28import cgi
29import urllib.request, urllib.parse
30from mako.template import Template
31import json
32
33import LastFM
34
35import rb
36from gi.repository import RB
37from gi.repository import GObject, Gtk
38from gi.repository import WebKit
39
40import gettext
41gettext.install('rhythmbox', RB.locale_dir())
42
43class AlbumTab (GObject.GObject):
44
45    __gsignals__ = {
46        'switch-tab' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
47                                (GObject.TYPE_STRING,))
48    }
49
50    def __init__ (self, shell, buttons, ds, view):
51        GObject.GObject.__init__ (self)
52        self.shell      = shell
53        self.sp         = shell.props.shell_player
54        self.db         = shell.props.db
55        self.buttons    = buttons
56
57        self.button     = Gtk.ToggleButton (label=_("Albums"))
58        self.ds         = ds
59        self.view       = view
60        self.artist     = None
61        self.active     = False
62
63        self.button.show()
64        self.button.set_relief (Gtk.ReliefStyle.NONE)
65        self.button.set_focus_on_click(False)
66        self.button.connect ('clicked',
67            lambda button: self.emit ('switch-tab', 'album'))
68        buttons.pack_start (self.button, True, True, 0)
69
70    def activate (self):
71        self.button.set_active(True)
72        self.active = True
73        self.reload ()
74
75    def deactivate (self):
76        self.button.set_active(False)
77        self.active = False
78
79    def reload (self):
80        entry = self.sp.get_playing_entry ()
81        if entry is None:
82            return None
83
84        artist = entry.get_string (RB.RhythmDBPropType.ARTIST)
85        album  = entry.get_string (RB.RhythmDBPropType.ALBUM)
86        if self.active and artist != self.artist:
87            self.view.loading(artist)
88            self.ds.fetch_album_list (artist)
89        else:
90            self.view.load_view()
91
92        self.artist = artist
93
94class AlbumView (GObject.GObject):
95
96    def __init__ (self, shell, plugin, webview, ds):
97        GObject.GObject.__init__ (self)
98        self.webview = webview
99        self.ds      = ds
100        self.shell   = shell
101        self.plugin  = plugin
102        self.file    = ""
103
104        plugindir = plugin.plugin_info.get_data_dir()
105        self.basepath = "file://" + urllib.request.pathname2url (plugindir)
106
107        self.load_tmpl ()
108        self.connect_signals ()
109
110    def load_view (self):
111        self.webview.load_string(self.file, 'text/html', 'utf-8', self.basepath)
112
113    def connect_signals (self):
114        self.ds.connect('albums-ready', self.album_list_ready)
115
116    def loading (self, current_artist):
117        self.loading_file = self.loading_template.render (
118            artist   = current_artist,
119            # Translators: 'top' here means 'most popular'.  %s is replaced by the artist name.
120            info     = _("Loading top albums for %s") % current_artist,
121            song     = "",
122            basepath = self.basepath)
123        self.webview.load_string (self.loading_file, 'text/html', 'utf-8', self.basepath)
124
125    def load_tmpl (self):
126        self.path = rb.find_plugin_file (self.plugin, 'tmpl/album-tmpl.html')
127        self.loading_path = rb.find_plugin_file (self.plugin, 'tmpl/loading.html')
128        self.album_template = Template (filename = self.path)
129        self.loading_template = Template (filename = self.loading_path)
130        self.styles = self.basepath + '/tmpl/main.css'
131
132    def album_list_ready (self, ds):
133        self.file = self.album_template.render (error = ds.get_error(),
134                                                albums = ds.get_top_albums(),
135                                                artist = ds.get_artist(),
136                                                datasource = LastFM.datasource_link (self.basepath),
137                                                stylesheet = self.styles)
138        self.load_view ()
139
140
141class AlbumDataSource (GObject.GObject):
142
143    __gsignals__ = {
144        'albums-ready' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, ())
145    }
146
147    def __init__ (self, info_cache, ranking_cache):
148        GObject.GObject.__init__ (self)
149        self.albums = None
150        self.error = None
151        self.artist = None
152        self.max_albums_fetched = 8
153        self.fetching = 0
154        self.info_cache = info_cache
155        self.ranking_cache = ranking_cache
156
157    def get_artist (self):
158        return self.artist
159
160    def get_error (self):
161        return self.error
162
163    def fetch_album_list (self, artist):
164        if LastFM.user_has_account() is False:
165            self.error = LastFM.NO_ACCOUNT_ERROR
166            self.emit ('albums-ready')
167            return
168
169        self.artist = artist
170        qartist = urllib.parse.quote_plus(artist)
171        self.error  = None
172        url = "%sartist.gettopalbums&artist=%s&api_key=%s&format=json" % (
173		LastFM.URL_PREFIX, qartist, LastFM.API_KEY)
174        cachekey = 'lastfm:artist:gettopalbumsjson:%s' % qartist
175        self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
176
177    def parse_album_list (self, data, artist):
178        if data is None:
179            print("Nothing fetched for %s top albums" % artist)
180            return False
181
182        try:
183            parsed = json.loads(data.decode("utf-8"))
184        except Exception as e:
185            print("Error parsing album list: %s" % e)
186            return False
187
188        self.error = parsed.get('error')
189        if self.error:
190            self.emit ('albums-ready')
191            return False
192
193        albums = parsed['topalbums'].get('album', [])
194        if len(albums) == 0:
195            self.error = "No albums found for %s" % artist
196            self.emit('albums-ready')
197            return True
198
199        self.albums = []
200        albums = parsed['topalbums'].get('album', [])[:self.max_albums_fetched]
201        self.fetching = len(albums)
202        for i, a in enumerate(albums):
203            images = [img['#text'] for img in a.get('image', [])]
204            self.albums.append({'title': a.get('name'), 'images': images[:3]})
205            self.fetch_album_info(artist, a.get('name'), i)
206
207        return True
208
209    def get_top_albums (self):
210        return self.albums
211
212    def fetch_album_info (self, artist, album, index):
213        qartist = urllib.parse.quote_plus(artist)
214        qalbum = urllib.parse.quote_plus(album)
215        cachekey = "lastfm:album:getinfojson:%s:%s" % (qartist, qalbum)
216        url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s&format=json" % (
217		LastFM.URL_PREFIX, qartist, qalbum, LastFM.API_KEY)
218        self.info_cache.fetch(cachekey, url, self.parse_album_info, album, index)
219
220    def parse_album_info (self, data, album, index):
221        rv = True
222        try:
223            parsed = json.loads(data.decode('utf-8'))
224            self.albums[index]['id'] = parsed['album']['id']
225
226            for k in ('releasedate', 'summary'):
227                self.albums[index][k] = parsed['album'].get(k)
228
229            tracklist = []
230            tracks = parsed['album']['tracks'].get('track', [])
231            for i, t in enumerate(tracks):
232                title = t['name']
233                duration = int(t['duration'])
234                tracklist.append((i, title, duration))
235
236            self.albums[index]['tracklist'] = tracklist
237            self.albums[index]['duration']  = sum([t[2] for t in tracklist])
238
239        except Exception as e:
240            print("Error parsing album tracklist: %s" % e)
241            rv = False
242
243        self.fetching -= 1
244        print("%s albums left to process" % self.fetching)
245        if self.fetching == 0:
246            self.emit('albums-ready')
247
248        return rv
249