From 197e43a142978907afa6bc2103d54a62ee672c34 Mon Sep 17 00:00:00 2001 From: spl0k Date: Wed, 12 Jun 2013 21:29:42 +0200 Subject: [PATCH] Starring support --- api/albums_songs.py | 32 +++++++++++- api/annotation.py | 125 ++++++++++++++++++++++++++++++++++++++++++++ api/media.py | 39 +++----------- db.py | 41 +++++++++++++++ web.py | 1 + 5 files changed, 205 insertions(+), 33 deletions(-) create mode 100755 api/annotation.py diff --git a/api/albums_songs.py b/api/albums_songs.py index 9930e0f..5c1520c 100755 --- a/api/albums_songs.py +++ b/api/albums_songs.py @@ -1,13 +1,13 @@ # coding: utf-8 from flask import request -from sqlalchemy import desc, func, Interval +from sqlalchemy import desc, func from sqlalchemy.orm import aliased import random import uuid from web import app -from db import Track, Folder, Album, Artist, User, now +from db import * @app.route('/rest/getRandomSongs.view', methods = [ 'GET', 'POST' ]) def rand_songs(): @@ -134,3 +134,31 @@ def now_playing(): } }) +@app.route('/rest/getStarred.view', methods = [ 'GET', 'POST' ]) +def get_starred(): + username = request.args.get('u') + if not username: + username = request.authorization.username + + return request.formatter({ + 'starred': { + 'artist': [ { 'id': sf.starred.name, 'name': sf.starred_id } for sf in StarredFolder.query.join(User).join(Folder).filter(User.name == username).filter(~ Folder.tracks.any()) ], + 'album': [ sf.starred.as_subsonic_child() for sf in StarredFolder.query.join(User).join(Folder).filter(User.name == username).filter(Folder.tracks.any()) ], + 'song': [ st.starred.as_subsonic_child() for st in StarredTrack.query.join(User).filter(User.name == username) ] + } + }) + +@app.route('/rest/getStarred2.view', methods = [ 'GET', 'POST' ]) +def get_starred_id3(): + username = request.args.get('u') + if not username: + username = request.authorization.username + + return request.formatter({ + 'starred2': { + 'artist': [ sa.starred.as_subsonic_artist() for sa in StarredArtist.query.join(User).filter(User.name == username) ], + 'album': [ sa.starred.as_subsonic_album() for sa in StarredAlbum.query.join(User).filter(User.name == username) ], + 'song': [ st.starred.as_subsonic_child() for st in StarredTrack.query.join(User).filter(User.name == username) ] + } + }) + diff --git a/api/annotation.py b/api/annotation.py new file mode 100755 index 0000000..0af3771 --- /dev/null +++ b/api/annotation.py @@ -0,0 +1,125 @@ +# coding: utf-8 + +from time import time +import uuid +from flask import request +from web import app +from api import get_entity +from lastfm import LastFm +from db import * + +@app.route('/rest/star.view', methods = [ 'GET', 'POST' ]) +def star(): + id, albumId, artistId = map(request.args.getlist, [ 'id', 'albumId', 'artistId' ]) + + username = request.args.get('u') + if not username: + username = request.authorization.username + user = User.query.filter(User.name == username).one() + + def try_star(ent, starred_ent, eid): + try: + uid = uuid.UUID(eid) + except: + return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__) + + if starred_ent.query.filter(starred_ent.user_id == user.id).filter(starred_ent.starred_id == uid).first(): + return 2, request.error_formatter(0, '%s already starred' % ent.__name__) + e = ent.query.get(uid) + if e: + session.add(starred_ent(user = user, starred = e)) + else: + return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__) + + return 0, None + + for eid in id: + err, ferror = try_star(Track, StarredTrack, eid) + if err == 1: + err, ferror = try_star(Folder, StarredFolder, eid) + if err: + return ferror + elif err == 2: + return ferror + + for alId in albumId: + err, ferror = try_star(Album, StarredAlbum, alId) + if err: + return ferror + + for arId in artistId: + err, ferror = try_star(Artist, StarredArtist, arId) + if err: + return ferror + + session.commit() + return request.formatter({}) + +@app.route('/rest/unstar.view', methods = [ 'GET', 'POST' ]) +def unstar(): + id, albumId, artistId = map(request.args.getlist, [ 'id', 'albumId', 'artistId' ]) + + username = request.args.get('u') + if not username: + username = request.authorization.username + user = User.query.filter(User.name == username).one() + + def try_unstar(ent, eid): + try: + uid = uuid.UUID(eid) + except: + return request.error_formatter(0, 'Invalid id') + + ent.query.filter(ent.user_id == user.id).filter(ent.starred_id == uid).delete() + return None + + for eid in id: + err = try_unstar(StarredTrack, eid) + if err: + return err + err = try_unstar(StarredFolder, eid) + if err: + return err + + for alId in albumId: + err = try_unstar(StarredAlbum, alId) + if err: + return err + + for arId in artistId: + err = try_unstar(StarredArtist, arId) + if err: + return err + + session.commit() + return request.formatter({}) + +@app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ]) +def scrobble(): + status, res = get_entity(request, Track) + if not status: + return res + + t, submission, u = map(request.args.get, [ 'time', 'submission', 'u' ]) + + if t: + try: + t = int(t) / 1000 + except: + return request.error_formatter(0, 'Invalid time value') + else: + t = int(time()) + + if u: + user = User.query.filter(User.name == u).one() + else: + user = User.query.filter(User.name == request.authorization.username).one() + lfm = LastFm(user, app.logger) + + if submission in (None, '', True, 'true', 'True', 1, '1'): + lfm.scrobble(res, t) + else: + lfm.now_playing(res) + + return request.formatter({}) + diff --git a/api/media.py b/api/media.py index b9f4b84..0292694 100755 --- a/api/media.py +++ b/api/media.py @@ -3,13 +3,11 @@ from flask import request, send_file import os.path from PIL import Image -from time import time as now import config from web import app from db import Track, Folder, User, now, session from api import get_entity -from lastfm import LastFm @app.route('/rest/stream.view', methods = [ 'GET', 'POST' ]) def stream_media(): @@ -49,6 +47,14 @@ def stream_media(): return send_file(res.path) +@app.route('/rest/download.view', methods = [ 'GET', 'POST' ]) +def download_media(): + status, res = get_entity(request, Track) + if not status: + return res + + return send_file(res.path) + @app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ]) def cover_art(): status, res = get_entity(request, Folder) @@ -82,32 +88,3 @@ def cover_art(): im.save(path, 'JPEG') return send_file(path) -@app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ]) -def scrobble(): - status, res = get_entity(request, Track) - if not status: - return res - - time, submission, u = map(request.args.get, [ 'time', 'submission', 'u' ]) - - if time: - try: - time = int(time) / 1000 - except: - return request.error_formatter(0, 'Invalid time value') - else: - time = int(now()) - - if u: - user = User.query.filter(User.name == u).one() - else: - user = User.query.filter(User.name == request.authorization.username).one() - lfm = LastFm(user, app.logger) - - if submission in (None, '', True, 'true', 'True', 1, '1'): - lfm.scrobble(res, time) - else: - lfm.now_playing(res) - - return request.formatter({}) - diff --git a/db.py b/db.py index 4f7ccb2..cd09959 100755 --- a/db.py +++ b/db.py @@ -83,6 +83,7 @@ class Folder(Base): 'id': str(self.id), 'isDir': True, 'title': self.name, + 'album': self.name, 'created': self.created.isoformat() } if not self.root: @@ -209,6 +210,46 @@ class Track(Base): def sort_key(self): return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() +class StarredFolder(Base): + __tablename__ = 'starred_folder' + + user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) + starred_id = Column(UUID, ForeignKey('folder.id'), primary_key = True) + date = Column(DateTime, default = now) + + user = relationship('User') + starred = relationship('Folder') + +class StarredArtist(Base): + __tablename__ = 'starred_artist' + + user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) + starred_id = Column(UUID, ForeignKey('artist.id'), primary_key = True) + date = Column(DateTime, default = now) + + user = relationship('User') + starred = relationship('Artist') + +class StarredAlbum(Base): + __tablename__ = 'starred_album' + + user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) + starred_id = Column(UUID, ForeignKey('album.id'), primary_key = True) + date = Column(DateTime, default = now) + + user = relationship('User') + starred = relationship('Album') + +class StarredTrack(Base): + __tablename__ = 'starred_track' + + user_id = Column(UUID, ForeignKey('user.id'), primary_key = True) + starred_id = Column(UUID, ForeignKey('track.id'), primary_key = True) + date = Column(DateTime, default = now) + + user = relationship('User') + starred = relationship('Track') + def init_db(): Base.metadata.create_all(bind = engine) diff --git a/web.py b/web.py index c45037f..3a935d8 100755 --- a/web.py +++ b/web.py @@ -59,4 +59,5 @@ import api.browse import api.user import api.albums_songs import api.media +import api.annotation