1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-13 21:52:18 +00:00

Playlists!

This commit is contained in:
spl0k 2013-06-25 22:07:49 +02:00
parent ad84f6435d
commit 5b04bf8119
5 changed files with 168 additions and 4 deletions

127
api/playlists.py Executable file
View File

@ -0,0 +1,127 @@
# coding: utf-8
from flask import request
from sqlalchemy import or_
import uuid
from web import app
from db import Playlist, User, Track, session
from . import get_entity
@app.route('/rest/getPlaylists.view', methods = [ 'GET', 'POST' ])
def list_playlists():
query = Playlist.query.filter(or_(Playlist.user_id == request.user.id, Playlist.public == True))
username = request.args.get('username')
if username:
if not request.user.admin:
return request.error_formatter(50, 'Restricted to admins')
query = Playlist.query.join(User).filter(User.name == username)
return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist() for p in query ] } })
@app.route('/rest/getPlaylist.view', methods = [ 'GET', 'POST' ])
def show_playlist():
status, res = get_entity(request, Playlist)
if not status:
return res
info = res.as_subsonic_playlist()
info['entry'] = [ t.as_subsonic_child(request.user) for t in res.tracks ]
return request.formatter({ 'playlist': info })
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
def create_playlist():
# Only(?) method where the android client uses form data rather than GET params
playlist_id, name = map(lambda x: request.args.get(x) or request.form.get(x), [ 'playlistId', 'name' ])
# songId actually doesn't seem to be required
songs = request.args.getlist('songId') or request.form.getlist('songId')
try:
playlist_id = uuid.UUID(playlist_id)
songs = set(map(uuid.UUID, songs))
except:
return request.error_formatter(0, 'Invalid parameter')
if playlist_id:
playlist = Playlist.query.get(playlist_id)
if not playlist:
return request.error_formatter(70, 'Unknwon playlist')
if playlist.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours")
playlist.tracks = []
if name:
playlist.name = name
elif name:
playlist = Playlist(user = request.user, name = name)
session.add(playlist)
else:
return request.error_formatter(10, 'Missing playlist id or name')
for sid in songs:
track = Track.query.get(sid)
if not track:
return request.error_formatter(70, 'Unknown song')
playlist.tracks.append(track)
session.commit()
return request.formatter({})
@app.route('/rest/deletePlaylist.view', methods = [ 'GET', 'POST' ])
def delete_playlist():
status, res = get_entity(request, Playlist)
if not status:
return res
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
session.delete(res)
session.commit()
return request.formatter({})
@app.route('/rest/updatePlaylist.view', methods = [ 'GET', 'POST' ])
def update_playlist():
status, res = get_entity(request, Playlist, 'playlistId')
if not status:
return res
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
playlist = res
name, comment, public = map(request.args.get, [ 'name', 'comment', 'public' ])
to_add, to_remove = map(request.args.getlist, [ 'songIdToAdd', 'songIndexToRemove' ])
try:
to_add = set(map(uuid.UUID, to_add))
to_remove = sorted(set(map(int, to_remove)))
except:
return request.error_formatter(0, 'Invalid parameter')
if name:
playlist.name = name
if comment:
playlist.comment = comment
if public:
playlist.public = public in (True, 'True', 'true', 1, '1')
for sid in to_add:
track = Track.query.get(sid)
if not track:
return request.error_formatter(70, 'Unknown song')
if track not in playlist.tracks:
playlist.tracks.append(track)
offset = 0
for idx in to_remove:
idx = idx - offset
if idx < 0 or idx >= len(playlist.tracks):
return request.error_formatter(0, 'Index out of range')
playlist.tracks.pop(idx)
offset += 1
session.commit()
return request.formatter({})

View File

@ -4,7 +4,7 @@ from flask import request
from web import app from web import app
from db import Folder, Track, Artist, Album from db import Folder, Track, Artist, Album
@app.route('/rest/search.view') @app.route('/rest/search.view', methods = [ 'GET', 'POST' ])
def old_search(): def old_search():
artist, album, title, anyf, count, offset, newer_than = map(request.args.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ]) artist, album, title, anyf, count, offset, newer_than = map(request.args.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ])
try: try:
@ -43,7 +43,7 @@ def old_search():
'match': [ r.as_subsonic_child(request.user) for r in query.slice(offset, offset + count) ] 'match': [ r.as_subsonic_child(request.user) for r in query.slice(offset, offset + count) ]
}}) }})
@app.route('/rest/search2.view') @app.route('/rest/search2.view', methods = [ 'GET', 'POST' ])
def new_search(): def new_search():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
@ -71,7 +71,7 @@ def new_search():
'song': [ t.as_subsonic_child(request.user) for t in song_query ] 'song': [ t.as_subsonic_child(request.user) for t in song_query ]
}}) }})
@app.route('/rest/search3.view') @app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
def search_id3(): def search_id3():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])

34
db.py
View File

@ -2,7 +2,7 @@
import config import config
from sqlalchemy import create_engine, Column, ForeignKey, func from sqlalchemy import create_engine, Table, Column, ForeignKey, func
from sqlalchemy import Integer, String, Boolean, DateTime from sqlalchemy import Integer, String, Boolean, DateTime
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -332,6 +332,38 @@ class ChatMessage(Base):
'message': self.message 'message': self.message
} }
playlist_track_assoc = Table('playlist_track', Base.metadata,
Column('playlist_id', UUID, ForeignKey('playlist.id')),
Column('track_id', UUID, ForeignKey('track.id'))
)
class Playlist(Base):
__tablename__ = 'playlist'
id = UUID.gen_id_column()
user_id = Column(UUID, ForeignKey('user.id'))
name = Column(String)
comment = Column(String, nullable = True)
public = Column(Boolean, default = False)
created = Column(DateTime, default = now)
user = relationship('User')
tracks = relationship('Track', secondary = playlist_track_assoc)
def as_subsonic_playlist(self):
info = {
'id': str(self.id),
'name': self.name,
'owner': self.user.name,
'public': self.public,
'songCount': len(self.tracks),
'duration': sum(map(lambda t: t.duration, self.tracks)),
'created': self.created.isoformat()
}
if self.comment:
info['comment'] = self.comment
return info
def init_db(): def init_db():
Base.metadata.create_all(bind = engine) Base.metadata.create_all(bind = engine)

View File

@ -30,6 +30,10 @@ class Scanner:
for track in [ t for t in self.__tracks if t.root_folder.id == folder.id and not os.path.exists(t.path) ]: for track in [ t for t in self.__tracks if t.root_folder.id == folder.id and not os.path.exists(t.path) ]:
track.album.tracks.remove(track) track.album.tracks.remove(track)
track.folder.tracks.remove(track) track.folder.tracks.remove(track)
# As we don't have a track -> playlists relationship, SQLAlchemy doesn't know it has to remove tracks
# from playlists as well, so let's help it
for playlist in db.Playlist.query.filter(db.Playlist.tracks.contains(track)):
playlist.tracks.remove(track)
self.__session.delete(track) self.__session.delete(track)
self.__deleted_tracks += 1 self.__deleted_tracks += 1

1
web.py
View File

@ -62,4 +62,5 @@ import api.media
import api.annotation import api.annotation
import api.chat import api.chat
import api.search import api.search
import api.playlists