~mikaelsahlstrom/onehundredscopes/spotify

« back to all changes in this revision

Viewing changes to src/unity-scope-spotify

  • Committer: Mikael Sahlström
  • Date: 2012-01-02 17:48:48 UTC
  • Revision ID: mick.sahlstrom@gmail.com-20120102174848-oo1e2xg6px29atzu
Prepaired for packaging.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python
 
2
# coding=UTF-8
 
3
 
 
4
# Copyright (c) 2012 Mikael Sahlström <mick.sahlstrom@gmail.com>
 
5
 
 
6
# This program 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
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
import sys, dbus, json
 
20
from gi.repository import GLib, GObject, Gio
 
21
from gi.repository import Dee
 
22
 
 
23
_m = dir(Dee.SequenceModel)
 
24
 
 
25
from gi.repository import Unity
 
26
from urllib2 import urlopen, URLError
 
27
from urllib2 import Request, urlopen, HTTPError
 
28
 
 
29
class Daemon:
 
30
        def __init__(self):
 
31
                self.scope = Unity.Scope.new("/net/launchpad/scope/music/spotify")
 
32
                self.scope.search_in_global = False
 
33
                self.scope.connect("notify::active-search", self.on_search_changed)
 
34
                self.scope.connect("filters-changed", self.on_search_changed)
 
35
                self.scope.export()
 
36
 
 
37
        def get_search_string(self):
 
38
                search = self.scope.props.active_search
 
39
                return search.props.search_string if search else None
 
40
 
 
41
        def on_search_changed(self, scope, param_spec=None):
 
42
                search = self.get_search_string()
 
43
                results = self.scope.props.results_model
 
44
                results.clear()
 
45
                self.update_results_model(search, results)
 
46
                results.flush_revision_queue()
 
47
 
 
48
        def update_results_model(self, search, model):
 
49
                if search is None or search == "":
 
50
                        return
 
51
                filters = ""
 
52
                f = self.scope.get_filter("decade")
 
53
                if f.get_first_active() is not None and f.get_last_active() is not None:
 
54
                        filters = "year:%s-%s+" % (f.get_first_active().props.id, int(f.get_last_active().props.id) + 9)
 
55
                f = self.scope.get_filter("genre")
 
56
                i = 0
 
57
                for option in f.options:
 
58
                        if option.props.active:
 
59
                                if option.props.id != "other":
 
60
                                        if i == 0:
 
61
                                                filters = filters + self._genre(option.props.id)
 
62
                                                i = 1
 
63
                                        else:
 
64
                                                filters = filters + "%20OR%20" + self._genre(option.props.id)
 
65
                search = filters + "+" + search
 
66
                
 
67
                result = self._search_track(search)
 
68
 
 
69
                if result is not None and result.get("info").get("num_results") != 0:
 
70
                        sorted_results = sorted(result.get("tracks"), key=lambda track: float(track.get("popularity")), reverse=True)
 
71
                        for track, i in zip(sorted_results, range(18)):
 
72
                                uri = track.get("href")
 
73
                                icon = self._get_thumb_path(track.get("album").get("href"))
 
74
                                name = track.get("name")
 
75
                                artist = dict(track.get("artists")[0]).get("name")
 
76
                                model.append(uri, icon, 0, "x-scheme-handler/spotify", "%s\n%s" % (name, artist), artist, uri)
 
77
                                if i % 3 == 0:
 
78
                                        model.flush_revision_queue()
 
79
                        model.flush_revision_queue()
 
80
 
 
81
                result = self._search_album(search)
 
82
 
 
83
                if result is not None and result.get("info").get("num_results") != 0:
 
84
                        sorted_results = sorted(result.get("albums"), key=lambda album: float(album.get("popularity")), reverse=True)
 
85
                        for album, i in zip(sorted_results, range(18)):
 
86
                                uri = album.get("href")
 
87
                                icon = self._get_thumb_path(uri)
 
88
                                name = album.get("name")
 
89
                                artist = dict(album.get("artists")[0]).get("name")
 
90
                                model.append(uri, icon, 1, "x-scheme-handler/spotify", "%s\n%s" % (name, artist), artist, uri)
 
91
                                if i % 3 == 0:
 
92
                                        model.flush_revision_queue()
 
93
 
 
94
                if self.scope.props.active_search:
 
95
                        self.scope.props.active_search.emit("finished")
 
96
 
 
97
 
 
98
        def _search_artist(self, query, page = 1):
 
99
                url = "http://ws.spotify.com/search/%s/artist.json?q=%s" % (page, query)
 
100
                result = self._do_request(url)
 
101
                if result is None:
 
102
                        return None
 
103
                return json.loads(result)
 
104
 
 
105
        def _search_track(self, query, page = 1):
 
106
                url = "http://ws.spotify.com/search/%s/track.json?q=%s" % (page, query)
 
107
                result = self._do_request(url)
 
108
                if result is None:
 
109
                        return None
 
110
                return json.loads(result)
 
111
 
 
112
        def _search_album(self, query, page = 1):
 
113
                url = "http://ws.spotify.com/search/%s/album.json?q=%s" % (page, query)
 
114
                result = self._do_request(url)
 
115
                if result is None:
 
116
                        return None
 
117
                return json.loads(result)
 
118
 
 
119
        def _do_request(self, url):
 
120
                headers = {'User-Agent': 'Unity Scope Spotify'}
 
121
                req = Request(url, None, headers)
 
122
                try:
 
123
                        url = urlopen(req)
 
124
                        return url.read()
 
125
                except HTTPError, e:
 
126
                        print "Connection to Spotify Metadata failed: %s" % (e.code)
 
127
                        return None
 
128
                except:
 
129
                        return None
 
130
 
 
131
        def _get_thumb_path(self, uri):
 
132
                turi = uri
 
133
                try:
 
134
                        uri = uri.split(':')
 
135
                        url = urlopen("http://open.spotify.com/%s/%s" % (uri[1], uri[2])).read()
 
136
                        thumb_start = url.find('<meta property="og:image" content="')
 
137
                        thumb_end = url.find('" />',thumb_start)
 
138
                        url = url[thumb_start+35:thumb_end]
 
139
                        return url
 
140
                except URLError, e:
 
141
                        # Some requests are getting 503. Try again.
 
142
                        return self._get_thumb_path(turi)
 
143
 
 
144
        def _genre(self, g):
 
145
                # Transform the genre filter to corresponding spotify filters.
 
146
                if g == "blues":
 
147
                        return "genre:blues"
 
148
                elif g == "country":
 
149
                        return "genre:country"
 
150
                elif g == "disco":
 
151
                        return "genre:post-disco"
 
152
                elif g == "funk":
 
153
                        return "genre:funk"
 
154
                elif g == "rock":
 
155
                        return "genre:rock"
 
156
                elif g == "metal":
 
157
                        return "genre:metal"
 
158
                elif g == "hip-hop":
 
159
                        return "genre:hip-hop%20OR%20genre:rap"
 
160
                elif g == "house":
 
161
                        return "genre:house"
 
162
                elif g == "new-wave":
 
163
                        return "genre:new-wave"
 
164
                elif g == "r-and-b":
 
165
                        return "genre:R%26amp%3BB"
 
166
                elif g == "punk":
 
167
                        return "genre:punk"
 
168
                elif g == "jazz":
 
169
                        return "genre:jazz"
 
170
                elif g == "pop":
 
171
                        return "genre:pop"
 
172
                elif g == "raggae":
 
173
                        return "genre:reggae"
 
174
                elif g == "soul":
 
175
                        return "genre:soul"
 
176
                elif g == "techno":
 
177
                        return "genre:techno"
 
178
                elif g == "classic":
 
179
                        return "genre:classical"                
 
180
        
 
181
if __name__ == "__main__":
 
182
        session_bus_connection = Gio.bus_get_sync (Gio.BusType.SESSION, None)
 
183
        session_bus = Gio.DBusProxy.new_sync (session_bus_connection, 0, None,
 
184
                                              'org.freedesktop.DBus',
 
185
                                              '/org/freedesktop/DBus',
 
186
                                              'org.freedesktop.DBus', None)
 
187
        result = session_bus.call_sync('RequestName',
 
188
                                       GLib.Variant ("(su)", ("net.launchpad.scope.music.spotify", 0x4)),
 
189
                                       0, -1, None)
 
190
                                       
 
191
        # Unpack variant response with signature "(u)". 1 means we got it.
 
192
        result = result.unpack()[0]
 
193
        
 
194
        if result != 1 :
 
195
                print >> sys.stderr, "Failed to own name %s. Bailing out." % BUS_NAME
 
196
                raise SystemExit (1)
 
197
        
 
198
        daemon = Daemon()
 
199
        GObject.MainLoop().run()