diff --git a/server/djrandom/database.py b/server/djrandom/database.py index b22413d037f569f3015e1b2c7ddf574a03ee0040..5b2fc49e8ee81c4d9ab34083f0e3419ed170428e 100644 --- a/server/djrandom/database.py +++ b/server/djrandom/database.py @@ -18,7 +18,7 @@ class SetTextFactory(PoolListener): def init_db(uri): - from djrandom.model import mp3 + from djrandom.model import mp3, playlist engine = create_engine(uri, listeners=[SetTextFactory()]) Session.configure(bind=engine) Base.metadata.create_all(engine) diff --git a/server/djrandom/frontend/frontend.py b/server/djrandom/frontend/frontend.py index 8a2a05ab18a9fd4b2fc7a12322ec5dd39e46bcab..e90b7fba5e640da980a625edf1529030f12152c9 100644 --- a/server/djrandom/frontend/frontend.py +++ b/server/djrandom/frontend/frontend.py @@ -2,7 +2,8 @@ import sys import os import optparse import logging -from flask import Flask, request, Response, abort, jsonify, render_template +from flask import Flask, request, Response, abort, jsonify, \ + render_template, session from djrandom import daemonize from djrandom import utils from djrandom.model.mp3 import MP3 @@ -13,9 +14,38 @@ from sqlalchemy import distinct log = logging.getLogger(__name__) app = Flask(__name__) +app.secret_key = 'J@9als[13- "!>0@!!zWz}=' storage_root = None searcher = 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 + + +@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(): @@ -59,6 +89,30 @@ def play_callback(sha1): return jsonify(status=True) +@app.route('/json/playlist/save', methods=['POST']) +def save_playlist(): + hashes = request.form.get('h', '') + uuid = request.form.get('uuid') + if uuid: + playlist = Playlist.query.get(uuid) + if not playlist: + abort(404) + else: + playlist = Playlist(uuid=uuid, userid=session['userid']) + playlist.modified_at = datetime.now() + Session.add(playlist) + Session.commit() + return jsonify(uuid=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') @@ -92,7 +146,7 @@ def songs_fragment(): @app.route('/') def homepage(): - return render_template('index.html') + return render_template('index.html', userid=session['userid']) def fileiter(path, pos, end): diff --git a/server/djrandom/frontend/static/djrandom.js b/server/djrandom/frontend/static/djrandom.js index 63a546403071a90411f6643d92437c79b86d07ec..92f5f076ae154440b86f19a4e683fa6697643f98 100644 --- a/server/djrandom/frontend/static/djrandom.js +++ b/server/djrandom/frontend/static/djrandom.js @@ -3,7 +3,9 @@ djr = {}; // Global state. -djr.state = {}; +djr.state = { + userid: null +}; // Debugging. djr.debug = function(msg) { @@ -92,8 +94,10 @@ djr.doSearch = function() { hashes.push(item.sha1); } }); + // Clear the current playlist and save a new one. + djr.state.player.clearPlaylist(); djr.state.player.setPlaylist(hashes); - djr.debug('new playlist: ' + hashes.join(', ')); + djr.state.player.savePlaylist(); // Load the HTML rendering of the playlist. djr.loadPlaylistHtml(); @@ -102,7 +106,9 @@ djr.doSearch = function() { // Initialization. -djr.init = function() { +djr.init = function(userid) { + djr.state.userid = userid; + // Restore state if the URL changes. $(window).bind('hashchange', djr.history.restore); diff --git a/server/djrandom/frontend/static/player.js b/server/djrandom/frontend/static/player.js index 0b367fe740e3f75d15cbc5b119c98e831fee5d21..923014f33e0029b791fd9267550f21744fd3dd08 100644 --- a/server/djrandom/frontend/static/player.js +++ b/server/djrandom/frontend/static/player.js @@ -3,6 +3,7 @@ djr.Player = function(selector) { this.player = $(selector); this.curPlaylist = Array(); + this.curPlaylistId = null; this.curIdx = -1; this.curSong = null; @@ -28,15 +29,45 @@ djr.Player.prototype.removeFromPlaylist = function(sha1) { // Empty the current playlist. djr.Player.prototype.clearPlaylist = function() { this.curPlaylist = Array(); + this.curPlaylistId = null; this.curIdx = -1; }; // Set the playlist to a new set of songs. djr.Player.prototype.setPlaylist = function(hashes) { + djr.debug('new playlist: ' + hashes.join(', ')); this.curPlaylist = hashes; this.curIdx = -1; }; +// Save the current playlist. +djr.Player.prototype.savePlaylist = function() { + $.ajax({url: '/json/playlist/save', + data: {uuid: this.curPlaylistId, + h: this.curPlaylist.join(',')}, + dataType: 'json', + type: 'POST', + context: this, + success: function(data, status, jqxhr) { + if (data.status && data.uuid != this.curPlaylistId) { + this.curPlaylistId = data.uuid; + djr.debug('created new playlist, UUID=' + data.uuid); + } + } + }); +}; + +// Load a playlist. +djr.Player.prototype.loadPlaylist = function(uuid) { + $.ajax({url: '/json/playlist/get/' + uuid, + dataType: 'json', + context: this, + success: function(data, status, jqxhr) { + this.setPlaylist(data.songs); + } + }); +}; + // Start playing the next song in the playlist. djr.Player.prototype.nextSong = function() { var next_idx = this.curIdx + 1; diff --git a/server/djrandom/frontend/templates/index.html b/server/djrandom/frontend/templates/index.html index 0f339cbc060644313c2b94c8b3f5757dcc14ad7e..32cf04315eeddc1e0932811667ca82ab78ccb1e1 100644 --- a/server/djrandom/frontend/templates/index.html +++ b/server/djrandom/frontend/templates/index.html @@ -29,7 +29,9 @@ $(document).ready(function() { }); $('#queryField').focus(); $('#searchForm').submit(do_search); - djr.init(); + + // Initialize DJR with the current userid. + djr.init('{{ userid }}'); }); </script> {% endblock %} diff --git a/server/djrandom/model/mp3.py b/server/djrandom/model/mp3.py index 4d7337b34e137d696e9b98210a1987d2ec866601..4a69385e7068873cdeae19f7be5e1130134c24c0 100644 --- a/server/djrandom/model/mp3.py +++ b/server/djrandom/model/mp3.py @@ -21,7 +21,7 @@ class MP3(Base): album = Column(Unicode(256)) genre = Column(Unicode(64)) uploaded_at = Column(DateTime()) - play_count = Column(Integer(default=0)) + play_count = Column(Integer(), default=0) IDX_SHA1 = 0 IDX_PATH = 1 diff --git a/server/djrandom/model/playlist.py b/server/djrandom/model/playlist.py new file mode 100644 index 0000000000000000000000000000000000000000..e60c568542474ad1488454d0198d159408713c0d --- /dev/null +++ b/server/djrandom/model/playlist.py @@ -0,0 +1,26 @@ +import os +import hashlib +from sqlalchemy import * +from djrandom import utils +from djrandom.database import Base + + +class Playlist(Base): + + __tablename__ = 'playlists' + + uuid = Column(String(40), primary_key=True) + userid = Column(String(40), index=True) + modified_at = Column(DateTime()) + play_count = Column(Integer(), default=0) + contents = Column(Text()) + + def __init__(self, **kw): + if kw and 'uuid' not in kw: + kw['uuid'] = utils.random_token() + for k, v in kw.items(): + setattr(self, k, v) + + def to_dict(self): + return {'uuid': self.uuid, + 'songs': self.contents.split(',')} diff --git a/server/djrandom/utils.py b/server/djrandom/utils.py index 3b350e3823236c9e142d3bfd31bbef8228dce7c8..0f8c8593342a2b08c24a3d54906a326b944c6eb8 100644 --- a/server/djrandom/utils.py +++ b/server/djrandom/utils.py @@ -1,18 +1,22 @@ import hashlib import os -NESTING = 2 +PATH_NESTING = 2 def generate_path(base_dir, sha1): dir_parts = [base_dir] - dir_parts.extend(sha1[:NESTING]) + dir_parts.extend(sha1[:PATH_NESTING]) base_path = os.path.join(*dir_parts) if not os.path.isdir(base_path): os.makedirs(base_path) return os.path.join(base_path, sha1) +def random_token(): + return hashlib.sha1(os.urandom(20)).hexdigest() + + def sha1_of_file(path): with open(path, 'r') as fd: sha = hashlib.sha1()