mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06: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 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
34
db.py
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user