mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 08:56:17 +00:00
Playlists improvements
They don't mess up the the track order anymore A same track can now be added more than once to a playlist Closes #61
This commit is contained in:
parent
4d3809a835
commit
4eb7386c99
0
schema/migration/20161030.sqlite.sql
Executable file → Normal file
0
schema/migration/20161030.sqlite.sql
Executable file → Normal file
11
schema/migration/20171022.mysql.sql
Normal file
11
schema/migration/20171022.mysql.sql
Normal file
@ -0,0 +1,11 @@
|
||||
START TRANSACTION;
|
||||
|
||||
ALTER TABLE playlist ADD tracks TEXT;
|
||||
UPDATE playlist SET tracks = (
|
||||
SELECT GROUP_CONCAT(track_id SEPARATOR ',')
|
||||
FROM playlist_track
|
||||
WHERE playlist_id = playlist.id);
|
||||
DROP TABLE playlist_track;
|
||||
|
||||
COMMIT;
|
||||
|
11
schema/migration/20171022.postgresql.sql
Normal file
11
schema/migration/20171022.postgresql.sql
Normal file
@ -0,0 +1,11 @@
|
||||
START TRANSACTION;
|
||||
|
||||
ALTER TABLE playlist ADD tracks TEXT;
|
||||
UPDATE playlist SET tracks = (
|
||||
SELECT array_to_string(array_agg(track_id), ',')
|
||||
FROM playlist_track
|
||||
WHERE playlist_id = playlist.id);
|
||||
DROP TABLE playlist_track;
|
||||
|
||||
COMMIT;
|
||||
|
37
schema/migration/20171022.sqlite.sql
Normal file
37
schema/migration/20171022.sqlite.sql
Normal file
@ -0,0 +1,37 @@
|
||||
-- PRAGMA foreign_keys = OFF;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE playlist RENAME TO playlist_old;
|
||||
CREATE TABLE playlist (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL REFERENCES user,
|
||||
name VARCHAR(256) NOT NULL COLLATE NOCASE,
|
||||
comment VARCHAR(256),
|
||||
public BOOLEAN NOT NULL,
|
||||
created DATETIME NOT NULL,
|
||||
tracks TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE TMP_playlist_tracks (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
tracks TEXT
|
||||
);
|
||||
|
||||
INSERT INTO TMP_playlist_tracks(id, tracks)
|
||||
SELECT id, GROUP_CONCAT(track_id, ',')
|
||||
FROM playlist_old, playlist_track
|
||||
WHERE id = playlist_id
|
||||
GROUP BY id;
|
||||
|
||||
INSERT INTO playlist(id, user_id, name, comment, public, created, tracks)
|
||||
SELECT p.id, user_id, name, comment, public, created, tracks
|
||||
FROM playlist_old p, TMP_playlist_tracks pt
|
||||
WHERE p.id = pt.id;
|
||||
|
||||
DROP TABLE TMP_playlist_tracks;
|
||||
DROP TABLE playlist_track;
|
||||
DROP TABLE playlist_old;
|
||||
|
||||
COMMIT;
|
||||
-- PRAGMA foreign_keys = ON;
|
||||
|
@ -117,12 +117,7 @@ CREATE TABLE playlist (
|
||||
name VARCHAR(256) NOT NULL,
|
||||
comment VARCHAR(256),
|
||||
public BOOLEAN NOT NULL,
|
||||
created DATETIME NOT NULL
|
||||
) DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE playlist_track (
|
||||
playlist_id CHAR(36) NOT NULL REFERENCES playlist,
|
||||
track_id CHAR(36) NOT NULL REFERENCES track,
|
||||
PRIMARY KEY(playlist_id, track_id)
|
||||
created DATETIME NOT NULL,
|
||||
tracks TEXT
|
||||
) DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
|
||||
|
@ -117,12 +117,7 @@ CREATE TABLE playlist (
|
||||
name VARCHAR(256) NOT NULL,
|
||||
comment VARCHAR(256),
|
||||
public BOOLEAN NOT NULL,
|
||||
created TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE playlist_track (
|
||||
playlist_id UUID NOT NULL REFERENCES playlist,
|
||||
track_id UUID NOT NULL REFERENCES track,
|
||||
PRIMARY KEY(playlist_id, track_id)
|
||||
created TIMESTAMP NOT NULL,
|
||||
tracks TEXT
|
||||
);
|
||||
|
||||
|
@ -117,12 +117,7 @@ CREATE TABLE playlist (
|
||||
name VARCHAR(256) NOT NULL COLLATE NOCASE,
|
||||
comment VARCHAR(256),
|
||||
public BOOLEAN NOT NULL,
|
||||
created DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE playlist_track (
|
||||
playlist_id CHAR(36) NOT NULL REFERENCES playlist,
|
||||
track_id CHAR(36) NOT NULL REFERENCES track,
|
||||
PRIMARY KEY(playlist_id, track_id)
|
||||
created DATETIME NOT NULL,
|
||||
tracks TEXT
|
||||
);
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
# This file is part of Supysonic.
|
||||
#
|
||||
# Supysonic is a Python implementation of the Subsonic server API.
|
||||
# Copyright (C) 2013 Alban 'spl0k' Féron
|
||||
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
@ -45,7 +45,7 @@ def show_playlist():
|
||||
return res
|
||||
|
||||
info = res.as_subsonic_playlist(request.user)
|
||||
info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.tracks ]
|
||||
info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ]
|
||||
return request.formatter({ 'playlist': info })
|
||||
|
||||
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
|
||||
@ -56,7 +56,7 @@ def create_playlist():
|
||||
songs = request.values.getlist('songId')
|
||||
try:
|
||||
playlist_id = uuid.UUID(playlist_id) if playlist_id else None
|
||||
songs = set(map(uuid.UUID, songs))
|
||||
songs = map(uuid.UUID, songs)
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid parameter')
|
||||
|
||||
@ -68,7 +68,7 @@ def create_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.clear()
|
||||
playlist.clear()
|
||||
if name:
|
||||
playlist.name = name
|
||||
elif name:
|
||||
@ -84,7 +84,7 @@ def create_playlist():
|
||||
if not track:
|
||||
return request.error_formatter(70, 'Unknown song')
|
||||
|
||||
playlist.tracks.add(track)
|
||||
playlist.add(track)
|
||||
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
@ -98,7 +98,6 @@ def delete_playlist():
|
||||
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")
|
||||
|
||||
res.tracks.clear()
|
||||
store.remove(res)
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
@ -116,8 +115,8 @@ def update_playlist():
|
||||
name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ])
|
||||
to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ])
|
||||
try:
|
||||
to_add = set(map(uuid.UUID, to_add))
|
||||
to_remove = sorted(set(map(int, to_remove)))
|
||||
to_add = map(uuid.UUID, to_add)
|
||||
to_remove = map(int, to_remove)
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid parameter')
|
||||
|
||||
@ -128,19 +127,13 @@ def update_playlist():
|
||||
if public:
|
||||
playlist.public = public in (True, 'True', 'true', 1, '1')
|
||||
|
||||
tracks = list(playlist.tracks)
|
||||
|
||||
for sid in to_add:
|
||||
track = store.get(Track, sid)
|
||||
if not track:
|
||||
return request.error_formatter(70, 'Unknown song')
|
||||
if track not in playlist.tracks:
|
||||
playlist.tracks.add(track)
|
||||
playlist.add(track)
|
||||
|
||||
for idx in to_remove:
|
||||
if idx < 0 or idx >= len(tracks):
|
||||
return request.error_formatter(0, 'Index out of range')
|
||||
playlist.tracks.remove(tracks[idx])
|
||||
playlist.remove_at_indexes(to_remove)
|
||||
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
|
@ -3,7 +3,7 @@
|
||||
# This file is part of Supysonic.
|
||||
#
|
||||
# Supysonic is a Python implementation of the Subsonic server API.
|
||||
# Copyright (C) 2013, 2014 Alban 'spl0k' Féron
|
||||
# Copyright (C) 2013-2017 Alban 'spl0k' Féron
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
@ -350,31 +350,74 @@ class Playlist(object):
|
||||
comment = Unicode() # nullable
|
||||
public = Bool(default = False)
|
||||
created = DateTime(default_factory = now)
|
||||
tracks = Unicode()
|
||||
|
||||
user = Reference(user_id, User.id)
|
||||
|
||||
def as_subsonic_playlist(self, user):
|
||||
tracks = self.get_tracks()
|
||||
info = {
|
||||
'id': str(self.id),
|
||||
'name': self.name if self.user_id == user.id else '[%s] %s' % (self.user.name, self.name),
|
||||
'owner': self.user.name,
|
||||
'public': self.public,
|
||||
'songCount': self.tracks.count(),
|
||||
'duration': self.tracks.find().sum(Track.duration),
|
||||
'songCount': len(tracks),
|
||||
'duration': sum(map(lambda t: t.duration, tracks)),
|
||||
'created': self.created.isoformat()
|
||||
}
|
||||
if self.comment:
|
||||
info['comment'] = self.comment
|
||||
return info
|
||||
|
||||
class PlaylistTrack(object):
|
||||
__storm_table__ = 'playlist_track'
|
||||
__storm_primary__ = 'playlist_id', 'track_id'
|
||||
def get_tracks(self):
|
||||
if not self.tracks:
|
||||
return []
|
||||
|
||||
playlist_id = UUID()
|
||||
track_id = UUID()
|
||||
tracks = []
|
||||
should_fix = False
|
||||
store = Store.of(self)
|
||||
|
||||
Playlist.tracks = ReferenceSet(Playlist.id, PlaylistTrack.playlist_id, PlaylistTrack.track_id, Track.id)
|
||||
for t in self.tracks.split(','):
|
||||
try:
|
||||
tid = uuid.UUID(t)
|
||||
track = store.get(Track, tid)
|
||||
if track:
|
||||
tracks.append(track)
|
||||
else:
|
||||
should_fix = True
|
||||
except:
|
||||
should_fix = True
|
||||
|
||||
if should_fix:
|
||||
self.tracks = ','.join(map(lambda t: str(t.id), tracks))
|
||||
store.commit()
|
||||
|
||||
return tracks
|
||||
|
||||
def clear(self):
|
||||
self.tracks = ""
|
||||
|
||||
def add(self, track):
|
||||
if isinstance(track, uuid.UUID):
|
||||
tid = track
|
||||
elif isinstance(track, Track):
|
||||
tid = track.id
|
||||
elif isinstance(track, basestring):
|
||||
tid = uuid.UUID(track)
|
||||
|
||||
if self.tracks and len(self.tracks) > 0:
|
||||
self.tracks = "{},{}".format(self.tracks, tid)
|
||||
else:
|
||||
self.tracks = str(tid)
|
||||
|
||||
def remove_at_indexes(self, indexes):
|
||||
tracks = self.tracks.split(',')
|
||||
for i in indexes:
|
||||
if i < 0 or i >= len(tracks):
|
||||
continue
|
||||
tracks[i] = None
|
||||
|
||||
self.tracks = ','.join(t for t in tracks if t)
|
||||
|
||||
def get_store(database_uri):
|
||||
database = create_database(database_uri)
|
||||
|
@ -26,7 +26,7 @@ from storm.expr import ComparableExpr, compile, Like
|
||||
from storm.exceptions import NotSupportedError
|
||||
|
||||
from supysonic import config
|
||||
from supysonic.db import Folder, Artist, Album, Track, User, PlaylistTrack
|
||||
from supysonic.db import Folder, Artist, Album, Track, User
|
||||
from supysonic.db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack
|
||||
from supysonic.db import RatingFolder, RatingTrack
|
||||
|
||||
@ -201,7 +201,7 @@ class Scanner:
|
||||
|
||||
self.__store.find(StarredTrack, StarredTrack.starred_id == tr.id).remove()
|
||||
self.__store.find(RatingTrack, RatingTrack.rated_id == tr.id).remove()
|
||||
self.__store.find(PlaylistTrack, PlaylistTrack.track_id == tr.id).remove()
|
||||
# Playlist autofix themselves
|
||||
self.__store.find(User, User.last_play_id == tr.id).set(last_play_id = None)
|
||||
|
||||
self.__folders_to_check.add(tr.folder)
|
||||
|
Loading…
Reference in New Issue
Block a user