~mikaelsahlstrom/onehundredscopes/spotify

« back to all changes in this revision

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