from gevent import monkey monkey.patch_all() import sys import os import optparse import logging from datetime import datetime from flask import Flask, request, Response, abort, jsonify, \ render_template, session from flask.helpers import send_file from djrandom import daemonize from djrandom import utils from djrandom.model.mp3 import MP3, PlayLog from djrandom.model.playlist import Playlist from djrandom.model.external import AlbumImageRetriever from djrandom.database import Session, init_db from djrandom.frontend import search from sqlalchemy import distinct log = logging.getLogger(__name__) app = Flask(__name__) app.secret_key = 'J@9als[13- "!>0@!!zWz}=' storage_root = None searcher = None album_images = None USERID_COOKIE = 'USERID' @app.before_request def check_session_before_request(): # Recover the user id from the long-term cookie, if present, # otherwise just generate a new one. if 'userid' not in session: if USERID_COOKIE in request.cookies: session['userid'] = request.cookies[USERID_COOKIE] else: session['userid'] = utils.random_token() session['_please_save_userid'] = True log.info('created new userid: %s' % session['userid']) @app.after_request def save_session_after_request(resp): if session.get('_please_save_userid'): del session['_please_save_userid'] resp.set_cookie(USERID_COOKIE, value=session['userid'], expires=(2<<30) - 1) return resp @app.teardown_request def shutdown_dbsession(exception=None): Session.remove() @app.route('/json/artists') def all_artists_json(): artists = [x[0] for x in Session.query(distinct(MP3.artist)) if x[0]] return jsonify(artists=artists) @app.route('/json/albums/<artist>') def artist_albums_json(artist): albums = [x[0] for x in Session.query(distinct(MP3.album)).filter_by(artist=artist)] return jsonify(albums=albums) @app.route('/json/album/<artist>/<album>') def album_songs_json(artist, album): songs = [x.to_dict() for x in MP3.query.filter_by( error=False, ready=True, artist=artist, album=album)] return jsonify(songs=songs) @app.route('/json/song/<sha1>') def song_info_json(sha1): mp3 = MP3.query.get(sha1) if not mp3: abort(404) return jsonify(mp3.to_dict()) @app.route('/json/playing/<sha1>') def play_callback(sha1): mp3 = MP3.query.get(sha1) if not mp3: abort(404) # Increase play count. if mp3.play_count: mp3.play_count += 1 else: mp3.play_count = 1 # Add a playlog entry. plog = PlayLog(sha1=sha1, userid=session['userid'], stamp=datetime.now()) Session.add(plog) Session.add(mp3) Session.commit() return jsonify(status=True) @app.route('/album_image/<artist>/<album>') def get_album_image(artist, album): img_file = album_images.get_album_image( urllib.urlunquote_plus(artist), urllib.urlunquote_plus(album)) if not img_file: abort(404) return send_file(img_file, mimetype='image/jpeg', conditional=True) @app.route('/json/playlist/save', methods=['POST']) def save_playlist(): hashes = request.form.get('h', '') uuid = request.form.get('uuid') if uuid == 'null': uuid = None playlist = None if uuid: playlist = Playlist.query.get(uuid) if not playlist: playlist = Playlist(uuid=uuid, userid=session['userid']) playlist.modified_at = datetime.now() playlist.contents = hashes Session.add(playlist) Session.commit() return jsonify(uuid=playlist.uuid, status=True) @app.route('/json/playlist/get/<uuid>') def playlist_info_json(uuid): playlist = Playlist.query.get(uuid) if not playlist: abort(404) return jsonify(playlist.to_dict()) @app.route('/json/search') def search_json(): query = request.args.get('q') num_results = int(request.args.get('n', 100)) results = [] if query: resultiter = searcher.search(query, n=num_results) results = [{'score': score, 'sha1': sha1} for (score, sha1) in resultiter] return jsonify(results=results) @app.route('/autocomplete') def autocomplete_search(): term = request.args.get('term') if not term: abort(400) possible_artists = Session.query(distinct(MP3.artist)).filter(MP3.artist.like('%s%%' % term)) return jsonify(results=[x[0] for x in possible_artists]) @app.route('/fragment/songs', methods=['POST']) def songs_fragment(): hashes = request.form.get('h', '') if hashes: mp3s = [MP3.query.get(h) for h in hashes.split(',')] else: mp3s = [] return render_template('songs_fragment.html', songs=mp3s) @app.route('/') def homepage(): return render_template('index.html', userid=session['userid']) def fileiter(path, pos, end): with open(path, 'r') as fd: fd.seek(pos) while True: n = min(65536, end - pos) chunk = fd.read(n) if not chunk: break yield chunk pos += len(chunk) @app.route('/dl/<sha1>') def download_song(sha1): mp3 = MP3.query.get(sha1) if not mp3: abort(404) # Parse 'Range' HTTP header. start = 0 end = os.path.getsize(mp3.path) partial = False if 'Range' in request.headers: partial = True hrange = request.headers['Range'] if hrange.startswith('bytes='): hrange = hrange[6:] if '-' in hrange: sstart, send = hrange.split('-') if sstart: start = int(sstart) if send: end = int(send) else: end = int(hrange) response = Response(fileiter(mp3.path, start, end), status=(partial and 206 or 200), direct_passthrough=True) response.headers['Accept-Ranges'] = 'bytes' if partial: response.headers['Content-Range'] = 'bytes %d-%d/%d' % ( start, end - 1, os.path.getsize(mp3.path)) response.content_type = 'audio/mpeg' response.content_length = (end - start) return response def run_frontend(port, storage_dir, solr_url, db_url, lastfm_api_key, album_art_dir): global storage_root, searcher, album_images storage_root = storage_dir searcher = search.Searcher(solr_url) album_images = AlbumImageRetriever(lastfm_api_key, album_art_dir) init_db(db_url) from gevent.wsgi import WSGIServer http_server = WSGIServer(('0.0.0.0', port), app) http_server.serve_forever() def main(): parser = optparse.OptionParser() parser.add_option('--solr_url', default='http://localhost:8080/solr') parser.add_option('--port', type='int', default=3003) parser.add_option('--storage_dir') parser.add_option('--db_url') parser.add_option('--lastfm_api_key') parser.add_option('--album_art_dir', default='/var/tmp/album-image-cache') daemonize.add_standard_options(parser) utils.read_config_defaults( parser, os.getenv('DJRANDOM_CONF', '/etc/djrandom.conf')) opts, args = parser.parse_args() if not opts.storage_dir: parser.error('Must provide --storage_dir') if not opts.db_url: parser.error('Must provide --db_url') if args: parser.error('Too many arguments') daemonize.daemonize(opts, run_frontend, (opts.port, opts.storage_dir, opts.solr_url, opts.db_url, opts.lastfm_api_key, opts.album_art_dir), support_gevent=True) if __name__ == '__main__': main()