Skip to content

Commit 4e1d796

Browse files
committed
Remove gevent dependency, move back to Python threading
1 parent f1fa700 commit 4e1d796

File tree

9 files changed

+68
-51
lines changed

9 files changed

+68
-51
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Experimental client
2525
Included is an experimental client called respotify, based on this API. It's almost identical to
2626
despotify-simple in terms of functionality. Be sure to have the following installed:
2727

28-
* requests 1.0.4
29-
* gevent 1.0 (install from Git, you'll also need cython and libev installed)
28+
* requests >= 1.0
3029
* cherrypy
30+
* ws4py
3131

3232
Once you've installed these, you can run the client like this:
3333

clients/respotify/respotify-helper.py

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import sys; sys.path.append("../..")
44
from spotify_web.friendly import Spotify
55
import cherrypy
6-
from gevent import wsgi
76

87
class SpotifyURIHandler(object):
98
def default(self, uri = None):

clients/respotify/respotify.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import sys; sys.path.append("../..")
44
from spotify_web.friendly import Spotify, SpotifyTrack, SpotifyUserlist
5-
from gevent.fileobject import FileObject
6-
from threading import Lock
5+
from threading import Thread, Lock, Event
76
from mpd import MPDClient
8-
import os, subprocess, gevent
7+
import os, subprocess, time
98

109
playing_playlist = None
1110
current_playlist = None
@@ -185,8 +184,11 @@ def command_help(*args):
185184
for k, v in command_map.items():
186185
print k+"\t\t"+v[1]
187186

187+
quitting = False
188188
def command_quit(*args):
189189
spotify.logout()
190+
global quitting
191+
quitting = True
190192

191193
def command_current_playlist(*args):
192194
display_playlist(playing_playlist)
@@ -210,8 +212,7 @@ def command_current_playlist(*args):
210212
def command_loop():
211213
header()
212214
command_help()
213-
sys.stdin = FileObject(sys.stdin)
214-
while spotify.api.disconnecting == False:
215+
while spotify.api.disconnecting == False and quitting == False:
215216
sys.stdout.write("\n> ")
216217
sys.stdout.flush()
217218
command = raw_input().split(" ")
@@ -223,11 +224,12 @@ def command_loop():
223224
else:
224225
command_help()
225226

227+
heartbeat_marker = Event()
226228
def heartbeat_handler():
227229
while client != None:
228230
with client:
229231
client.status()
230-
gevent.sleep(15)
232+
heartbeat_marker.get(timeout=15)
231233

232234
if len(sys.argv) < 2:
233235
print "Usage: "+sys.argv[0]+" <username> <password>"
@@ -239,13 +241,14 @@ def heartbeat_handler():
239241
uri_resolver = subprocess.Popen([sys.executable, "respotify-helper.py", sys.argv[1], sys.argv[2]])
240242
with client:
241243
client.connect(host="localhost", port="6600")
242-
gevent.spawn(heartbeat_handler)
244+
Thread(target=heartbeat_handler).start()
243245
command_loop()
244246
os.system("clear")
245247
with client:
246248
client.clear()
247249
client.disconnect()
248250
client = None
251+
heartbeat_marker.set()
249252
uri_resolver.kill()
250253
else:
251254
print "Login failed"

examples/blocking.py

+2
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,5 @@ def display_playlist(playlist):
6868
print "Track is available!"
6969
else:
7070
print "Track is NOT available! Double-check this using the official client"
71+
72+
sp.disconnect()

examples/nonblocking.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ def login_callback(sp, logged_in):
2525
else:
2626
sp = SpotifyAPI(login_callback)
2727
sp.connect(sys.argv[1], sys.argv[2])
28+

install-deps.sh

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,4 @@ VER=$TRAVIS_PYTHON_VERSION
55

66
apt-get update -yqq || true
77
apt-get install -yqq python$VER
8-
curl -sSLO --retry 5 --fail https://github.com/downloads/denik/packages/python2.7-cython_0.17.1_i386.deb
9-
dpkg -i python2.7-cython_0.17.1_i386.deb
10-
pip install -q requests greenlet protobuf cherrypy ws4py python-mpd2 lxml web.py --upgrade
11-
pip install -q git+https://github.com/SiteSupport/gevent.git --upgrade
8+
pip install -q requests protobuf cherrypy ws4py python-mpd2 lxml web.py --upgrade

spotify_web/friendly.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from spotify_web.proto import mercury_pb2, metadata_pb2
33
from functools import partial
44
from lxml import etree
5+
from threading import Thread
56
import sys
67

78
class Cache(object):
@@ -317,7 +318,7 @@ def getPlaylists(self, username = None):
317318
playlist_uris += ["spotify:user:"+username+":starred"]
318319

319320
playlist_uris += [playlist.uri for playlist in self.api.playlists_request(username).contents.items]
320-
return [self.objectFromURI(playlist_uri) for playlist_uri in playlist_uris]
321+
return self.objectFromURI(playlist_uris)
321322

322323
def getUserToplist(self, toplist_content_type = "track", username = None):
323324
return SpotifyToplist(self, toplist_content_type, "user", username, None)
@@ -360,7 +361,17 @@ def objectFromURI(self, uris, asArray = False):
360361
if uri_type == False:
361362
return None
362363
elif uri_type == "playlist":
363-
results = [SpotifyPlaylist(self, uri=uri) for uri in uris]
364+
if len(uris) == 1:
365+
results = [SpotifyPlaylist(self, uri=uris[0])]
366+
else:
367+
results = []
368+
def worker(spotify, uri, results):
369+
results.append(SpotifyPlaylist(spotify, uri=uri))
370+
threads = [Thread(target=worker, args=(self, uri, results)) for uri in uris]
371+
for thread in threads:
372+
thread.start()
373+
for thread in threads:
374+
thread.join()
364375
elif uri_type in ["track", "album", "artist"]:
365376
uris = [uri for uri in uris if not SpotifyUtil.is_local(uri)]
366377
objs = self.api.metadata_request(uris)

spotify_web/spotify.py

+36-35
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/python
22

3-
from gevent import monkey; monkey.patch_all()
4-
from ws4py.client.geventclient import WebSocketClient
5-
import base64, binascii, json, pprint, re, requests, string, sys, time, gevent, operator
3+
from ws4py.client.threadedclient import WebSocketClient
4+
from threading import Thread, Event
5+
import base64, binascii, json, re, requests, sys, operator
66

77
from .proto import mercury_pb2, metadata_pb2
88
from .proto import playlist4changes_pb2, playlist4content_pb2
@@ -38,7 +38,7 @@ class WrapAsync():
3838
timeout = 10
3939

4040
def __init__(self, callback, func, *args):
41-
self.marker = gevent.event.AsyncResult()
41+
self.marker = Event()
4242

4343
if callback == None:
4444
callback = self.callback
@@ -50,17 +50,20 @@ def __init__(self, callback, func, *args):
5050
func(*args, callback=callback)
5151

5252
def callback(self, *args):
53-
self.marker.set(args)
53+
self.data = args
54+
self.marker.set()
5455

5556
def get_data(self):
5657
try:
57-
data = self.marker.get(timeout = self.timeout)
58+
self.marker.wait(timeout = self.timeout)
5859

59-
if len(data) > 0 and type(data[0] == SpotifyAPI):
60-
data = data[1:]
60+
if len(self.data) > 0 and type(self.data[0] == SpotifyAPI):
61+
self.data = self.data[1:]
6162

62-
return data if len(data) > 1 else data[0]
63+
return self.data if len(self.data) > 1 else self.data[0]
6364
except:
65+
print "There was an error, disconnecting! Details below:"
66+
print sys.exc_info()
6467
return False
6568

6669
class SpotifyClient(WebSocketClient):
@@ -70,6 +73,12 @@ def set_api(self, api):
7073
def opened(self):
7174
self.api_object.login()
7275

76+
def received_message(self, m):
77+
self.api_object.recv_packet(m)
78+
79+
def closed(self, code, message):
80+
self.api_object.shutdown()
81+
7382
class SpotifyUtil():
7483
@staticmethod
7584
def gid2id(gid):
@@ -127,7 +136,8 @@ class SpotifyAPI():
127136
def __init__(self, login_callback_func = False):
128137
self.auth_server = "play.spotify.com"
129138

130-
self.logged_in_marker = gevent.event.AsyncResult()
139+
self.logged_in_marker = Event()
140+
self.heartbeat_marker = Event()
131141
self.username = None
132142
self.password = None
133143
self.account_type = None
@@ -186,9 +196,6 @@ def auth(self, username, password):
186196

187197
self.settings = resp.json()["config"]
188198

189-
def auth_from_json(self, json):
190-
self.settings = json
191-
192199
def populate_userdata_callback(self, sp, resp, callback_data):
193200
self.username = resp["user"]
194201
self.country = resp["country"]
@@ -197,7 +204,7 @@ def populate_userdata_callback(self, sp, resp, callback_data):
197204
if self.login_callback != False:
198205
self.do_login_callback(True)
199206
else:
200-
self.logged_in_marker.set(True)
207+
self.logged_in_marker.set()
201208
self.chain_callback(sp, resp, callback_data)
202209

203210
def logged_in(self, sp, resp):
@@ -213,9 +220,9 @@ def login(self):
213220

214221
def do_login_callback(self, result):
215222
if self.login_callback != False:
216-
gevent.spawn(self.login_callback, self, result)
223+
Thread(target=self.login_callback, args=(self, result)).start()
217224
else:
218-
self.logged_in_marker.set(False)
225+
self.logged_in_marker.set()
219226

220227
def track_uri(self, track, callback = False):
221228
tid = self.recurse_alternatives(track)
@@ -599,18 +606,10 @@ def handle_error(self, err):
599606
else:
600607
Logging.error(major_str + " - " + minor_str)
601608

602-
def event_handler(self):
603-
while self.disconnecting == False:
604-
m = self.ws.receive()
605-
if m is not None:
606-
self.recv_packet(str(m))
607-
else:
608-
break
609-
610609
def heartbeat_handler(self):
611610
while self.disconnecting == False:
612-
gevent.sleep(15)
613611
self.heartbeat()
612+
self.heartbeat_marker.wait(timeout=15)
614613

615614
def connect(self, username, password, timeout = 10):
616615
if self.settings == None:
@@ -620,27 +619,29 @@ def connect(self, username, password, timeout = 10):
620619
self.password = password
621620

622621
Logging.notice("Connecting to "+self.settings["aps"]["ws"][0])
623-
622+
624623
try:
625624
self.ws = SpotifyClient(self.settings["aps"]["ws"][0])
626625
self.ws.set_api(self)
627626
self.ws.connect()
628-
self.greenlets = [
629-
gevent.spawn(self.event_handler),
630-
gevent.spawn(self.heartbeat_handler)
631-
]
627+
Thread(target=self.heartbeat_handler).start()
632628
if self.login_callback != False:
633-
gevent.joinall(self.greenlets)
629+
return
634630
else:
635631
try:
636-
return self.logged_in_marker.get(timeout=timeout)
632+
self.logged_in_marker.wait(timeout=timeout)
633+
return self.is_logged_in
637634
except:
638635
return False
639636
except:
640637
self.disconnect()
638+
print "There was an error, disconnecting! Details below:"
639+
print sys.exc_info()
641640
return False
642641

643-
def disconnect(self):
642+
def shutdown(self):
644643
self.disconnecting = True
645-
gevent.sleep(1)
646-
gevent.killall(self.greenlets)
644+
self.heartbeat_marker.set()
645+
646+
def disconnect(self):
647+
self.ws.close()

test.py

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ def setUp(self):
1010
if self.spotify.logged_in() != True:
1111
print "Login failed"
1212

13+
def tearDown(self):
14+
self.spotify.logout()
15+
1316
def test_get_track_by_uri(self):
1417
test_uris = {
1518
"spotify:track:4DoiEk7AaubTkIkYencvx7": {

0 commit comments

Comments
 (0)