Skip to content

Commit

Permalink
server now uses pyweb
Browse files Browse the repository at this point in the history
  • Loading branch information
Maximilian Köhl committed Feb 12, 2012
1 parent 61b792d commit 2a0262b
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 215 deletions.
255 changes: 79 additions & 176 deletions grooveshark/server/server.py
Expand Up @@ -13,48 +13,28 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import sys
import os.path
import time
import json
import mimetypes
import threading
import time
if sys.version[0] == '3':
import http.server as httpserver
import socketserver
from urllib.parse import urlparse, parse_qs, quote_plus
else:
import BaseHTTPServer as httpserver
import SocketServer as socketserver
from urllib import quote_plus
from urlparse import urlparse, parse_qs

import grooveshark.core.client
from grooveshark.classes import *
from wsgiref.util import FileWrapper

STATE_READING = 0
STATE_FINISHED = 1
STATE_CANCELED = 2
from pyweb.core.server import simple_server
from pyweb.core.application import Application
from pyweb.handlers.directory import Directory

class Cache(object):
'''
Reads out the whole file object to avoid for example stream timeouts.
Use :class:`Player`'s :meth:`play_cache` method to play this object.
Attention: This starts a new thread for reading.
If you want to cancel this thread you have to call the :meth:`cancel` method.
If you call :class:`Player`'s :meth:`stop` method the :meth:`cancel` method is automatically called.
:param fileobj: file object to cache
:param size: size to calculate state of caching (and playing)
:param seekable: file object is seekable (not implemented yet)
:param blocksize: size of blocks for reading and caching
'''
from grooveshark.core.client import Client, SEARCH_TYPE_SONGS, SEARCH_TYPE_ALBUMS, SEARCH_TYPE_ARTISTS
from grooveshark.classes.song import Song

class Cache():
STATE_READING = 0
STATE_FINISHED = 1
STATE_CANCELED = 2
def __init__(self, fileobj, size, blocksize=2048):
self._fileobj = fileobj
self.size = size
self._blocksize = blocksize
self.state = STATE_READING
self.state = self.STATE_READING
self._memory = []
self._current = 0
self._active = True
Expand All @@ -69,7 +49,7 @@ def _read(self):
self.bytes_read += len(data)
data = self._fileobj.read(self._blocksize)
if self._active:
self.state = STATE_FINISHED
self.state = self.STATE_FINISHED
self._fileobj.close()

def reset(self):
Expand All @@ -84,19 +64,11 @@ def offset(self, offset):
self._current = offset

def cancel(self):
'''
Cancels the reading thread.
'''
if self.state == STATE_READING:
if self.state == self.STATE_READING:
self._active = False
self.state = STATE_CANCELED
self.state = self.STATE_CANCELED

def read(self, size=None):
'''
Reads in the internal cache.
This method should not be used directly.
The :class:`Player` class uses this method to read data for playing.
'''
start_block, start_bytes = divmod(self._current, self._blocksize)
if size:
if size > self.size - self._current:
Expand All @@ -116,85 +88,63 @@ def read(self, size=None):
result.append(self._memory[end_block][:end_bytes])
if start_bytes > 0 and result:
result[0] = result[0][start_bytes:]
return b''.join(result)

class Server(httpserver.BaseHTTPRequestHandler):
def _respond_json(self, data):
data = json.dumps(data).encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=UTF-8')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
return b''.join(result)

class Grooveshark(Application):
__URLS__ = {'/desktop/.*|/icons/.*' : 'www',
'/request/popular' : 'popular',
'/request/search' : 'search',
'/request/stream' : 'stream'}
www = Directory(os.path.join(os.path.dirname(__file__), 'www'))
def __init__(self):
super().__init__()
self.client = Client()
self.client.init()
self._cache = {}
self._cache['streams'] = {}

def _bad_request(self, message):
data = json.dumps({'status' : 'error', 'result' : message}).encode('utf-8')
self.send_response(400)
self.send_header('Content-Type', 'application/json; charset=UTF-8')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def _respond_json(self, data, response):
response.headers['Content-Type'] = 'application/json; charset=UTF-8'
response.body.append(json.dumps(data).encode('utf-8'))

def _www(self):
document = os.path.join(self.server.www, self.url.path[1:])
if os.path.isdir(document):
document += '/index.html'
if os.path.isfile(document):
self.send_response(200)
self.send_header('Content-Type', mimetypes.guess_type(document))
self.send_header('Content-Length', str(os.path.getsize(document)))
self.end_headers()
with open(document, 'rb') as input_document:
data = input_document.read(2048)
while data:
self.wfile.write(data)
data = input_document.read(2048)
else:
data = '<span style="font-size:50px"><b>404 Not Found</b></span>'.encode('utf-8')
self.send_response(404)
self.send_header('Content-Type', 'text/html; charset=UTF-8')
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def _bad_request(self, message, response):
response.status = 400
response.message = 'ERROR'
response.headers['Content-Type'] = 'application/json; charset=UTF-8'
response.body.append(json.dumps({'status' : 'error', 'result' : message}).encode('utf-8'))

def _command_popular(self, query):
self.send_response(200)
result = [song.export() for song in self.server.client.popular()]
self._respond_json({'status' : 'success', 'result' : result})
def popular(self, request, response):
if not 'popular' in self._cache:
self._cache['popular'] = (time.time(), [song.export() for song in self.client.popular()])
if time.time() - self._cache['popular'][0] > 7200:
self._cache['popular'] = (time.time(), [song.export() for song in self.client.popular()])
self._respond_json({'status' : 'success', 'result' : self._cache['popular'][1]}, response)

def _command_search(self, query):
if not 'type' in query:
query['type'] = [grooveshark.core.client.SEARCH_TYPE_SONGS]
if 'query' in query:
if not query['type'][0] in [grooveshark.core.client.SEARCH_TYPE_SONGS,
grooveshark.core.client.SEARCH_TYPE_ALBUMS,
grooveshark.core.client.SEARCH_TYPE_ARTISTS]:
self._bad_request('unknown type')
def search(self, request, response):
if not 'type' in request.query:
request.qery['type'] = [SEARCH_TYPE_SONGS]
if 'query' in request.query:
if not request.query['type'][0] in (SEARCH_TYPE_SONGS,
SEARCH_TYPE_ALBUMS,
SEARCH_TYPE_ARTISTS):
self._bad_request('unknown type', response)
else:
result = [object.export() for object in self.server.client.search(query['query'][0], query['type'][0])]
self._respond_json({'status' : 'success', 'result' : result})
else:
self._bad_request('missing query argument')

def _command_radio(self, query):
if 'tag' in query:
radio = self.server.client.radio(query['tag'][0])
self._respond_json({'status' : 'success', 'result' : radio.export()})
result = [object.export() for object in self.client.search(request.query['query'][0], request.query['type'][0])]
self._respond_json({'status' : 'success', 'result' : result}, response)
else:
self._bad_request('missing tag argument')
self._bad_request('missing query argument', response)

def _command_stream(self, query):
song = Song.from_export(json.loads(query['song'][0]), self.server.client.connection)
if song.id in self.server.streams:
stream, cache = self.server.streams[song.id]
def stream(self, request, response):
song = Song.from_export(json.loads(request.query['song'][0]), self.client.connection)
if song.id in self._cache['streams']:
stream, cache = self._cache['streams'][song.id]
else:
self.log_message('GS Stream Request')
stream = song.stream
cache = Cache(stream.data, stream.size)
self.server.streams[song.id] = stream, cache
if 'Range' in self.headers:
self.send_response(206)
start_byte, end_byte = self.headers['Range'].replace('bytes=', '').split('-')
self._cache['streams'][song.id] = stream, cache
if 'Range' in request.headers:
response.status = 206
start_byte, end_byte = request.headers['Range'].replace('bytes=', '').split('-')
if start_byte:
start_byte = int(start_byte)
else:
Expand All @@ -204,70 +154,23 @@ def _command_stream(self, query):
else:
end_byte = stream.size
cache.offset = start_byte
self.send_response(206)
if 'download' in query:
self.send_header('Content-Disposition', 'attachment; filename="%s - %s - %s.mp3"' % (song.name, song.album.name, song.artist.name))
self.send_header('Accept-Ranges', 'bytes')
self.send_header('Content-Type', stream.data.info()['Content-Type'])
self.send_header('Content-Length', stream.data.info()['Content-Length'])
self.send_header('Content-Range', 'bytes %i-%i/%i' % (start_byte, end_byte, stream.size))
self.end_headers()
sended_bytes = 0
while sended_bytes < end_byte:
if end_byte - sended_bytes < 2048:
data = cache.read(end_byte - sended_bytes)
else:
data = cache.read(2048)
sended_bytes += len(data)
self.wfile.write(data)
if 'download' in request.query:
response.headers['Content-Disposition'] = 'attachment; filename="{} - {} - {}.mp3"'.format(song.name, song.album.name, song.artist.name)
response.headers['Accept-Ranges'] = 'bytes'
response.headers['Content-Type'] = stream.data.info()['Content-Type']
response.headers['Content-Length'] = str(stream.size)
response.headers['Content-Range'] = 'bytes {}-{}/{}'.format(start_byte, end_byte, stream.size)
response.body = FileWrapper(cache)
else:
cache.reset()
self.send_response(200)
if 'download' in query:
self.send_header('Content-Disposition', 'attachment; filename="%s - %s - %s.mp3"' % (song.name, song.album.name, song.artist.name))
self.send_header('Accept-Ranges', 'bytes')
self.send_header('Content-Type', stream.data.info()['Content-Type'])
self.send_header('Content-Length', stream.data.info()['Content-Length'])
self.end_headers()
data = cache.read(2048)
while data:
self.wfile.write(data)
data = cache.read(2048)

def _request(self):
query = parse_qs(self.url.query)
if 'command' in query:
command = query['command'][0]
if hasattr(self, '_command_%s' % (command)):
getattr(self, '_command_%s' % (command))(query)
else:
self._bad_request('unknown command "%s"' % (command))
else:
self._bad_request('please specify a command')
if 'download' in request.query:
response.headers['Content-Disposition'] = 'attachment; filename="{} - {} - {}.mp3"'.format(song.name, song.album.name, song.artist.name)
response.headers['Accept-Ranges'] = 'bytes'
response.headers['Content-Type'] = stream.data.info()['Content-Type']
response.headers['Content-Length'] = str(stream.size)
response.body = FileWrapper(cache)

def _handle(self):
self.url = urlparse(self.path)
if self.url.path == '/request':
self._request()
else:
self._www()

do_GET = _handle
do_POST = _handle

class ThreadingHTTPServer(socketserver.ThreadingMixIn, httpserver.HTTPServer): pass

def main(address=('0.0.0.0', 8181)):
'''
Starts own grooveshark service.
'''
client = grooveshark.core.client.Client()
print(client.init())
server = ThreadingHTTPServer(address, Server)
server.www = os.path.join(os.path.dirname(__file__), 'www')
server.streams = {}
server.client = client
server.serve_forever()

if __name__ == '__main__':
main()
simple_server(Grooveshark())


0 comments on commit 2a0262b

Please sign in to comment.