mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 08:56:17 +00:00
Playlists!
This commit is contained in:
parent
ad84f6435d
commit
5b04bf8119
127
api/playlists.py
Executable file
127
api/playlists.py
Executable 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({})
|
||||
|
@ -4,7 +4,7 @@ from flask import request
|
||||
from web import app
|
||||
from db import Folder, Track, Artist, Album
|
||||
|
||||
@app.route('/rest/search.view')
|
||||
@app.route('/rest/search.view', methods = [ 'GET', 'POST' ])
|
||||
def old_search():
|
||||
artist, album, title, anyf, count, offset, newer_than = map(request.args.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ])
|
||||
try:
|
||||
@ -43,7 +43,7 @@ def old_search():
|
||||
'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():
|
||||
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
|
||||
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 ]
|
||||
}})
|
||||
|
||||
@app.route('/rest/search3.view')
|
||||
@app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
|
||||
def search_id3():
|
||||
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
|
||||
request.args.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
|
||||
|
34
db.py
34
db.py
@ -2,7 +2,7 @@
|
||||
|
||||
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.orm import scoped_session, sessionmaker, relationship, backref
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
@ -332,6 +332,38 @@ class ChatMessage(Base):
|
||||
'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():
|
||||
Base.metadata.create_all(bind = engine)
|
||||
|
||||
|
@ -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) ]:
|
||||
track.album.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.__deleted_tracks += 1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user