feat: Add names to playlists
Fixes #63. Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
@@ -213,6 +213,10 @@
|
|||||||
<option value="audioplay">Audioplay (no shuffle, start at previous track)</option>
|
<option value="audioplay">Audioplay (no shuffle, start at previous track)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Playlist name</label>
|
||||||
|
<input type="text" placeholder="Playlist name" id="playlist-edit-name" />
|
||||||
|
</div>
|
||||||
<div class="flex-horizontal">
|
<div class="flex-horizontal">
|
||||||
<div style="flex-grow: 1">
|
<div style="flex-grow: 1">
|
||||||
<label>Tracks</label>
|
<label>Tracks</label>
|
||||||
@@ -493,7 +497,7 @@
|
|||||||
document.getElementById('playlist-exist-button-delete')
|
document.getElementById('playlist-exist-button-delete')
|
||||||
.addEventListener('click', (e) => {
|
.addEventListener('click', (e) => {
|
||||||
if (lastSelected === null) return;
|
if (lastSelected === null) return;
|
||||||
const tagid = lastSelected.innerText;
|
const tagid = lastSelected.getAttribute('data-tag');
|
||||||
if(confirm(`Really delete playlist ${tagid}?`)) {
|
if(confirm(`Really delete playlist ${tagid}?`)) {
|
||||||
fetch(`/api/v1/playlist/${tagid}`, {
|
fetch(`/api/v1/playlist/${tagid}`, {
|
||||||
method: 'DELETE'})
|
method: 'DELETE'})
|
||||||
@@ -504,7 +508,7 @@
|
|||||||
.addEventListener('click', selectLastTag);
|
.addEventListener('click', selectLastTag);
|
||||||
document.getElementById('playlist-exist-button-edit').addEventListener("click", (e) => {
|
document.getElementById('playlist-exist-button-edit').addEventListener("click", (e) => {
|
||||||
if (lastSelected !== null)
|
if (lastSelected !== null)
|
||||||
showScreen("playlist_edit", {load: lastSelected.innerText});
|
showScreen("playlist_edit", {load: lastSelected.getAttribute('data-tag')});
|
||||||
});
|
});
|
||||||
document.getElementById('playlist-exist-list').addEventListener("click", (e) => {
|
document.getElementById('playlist-exist-list').addEventListener("click", (e) => {
|
||||||
const node = e.target.closest("li");
|
const node = e.target.closest("li");
|
||||||
@@ -559,8 +563,9 @@
|
|||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
for (const playlist of playlists) {
|
for (const playlist of playlists) {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.innerHTML = playlist;
|
li.innerHTML = `${playlist.name} (${playlist.tag})`;
|
||||||
li.className = "node"
|
li.className = "node"
|
||||||
|
li.setAttribute('data-tag', playlist.tag);
|
||||||
container.appendChild(li)
|
container.appendChild(li)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,7 +590,7 @@
|
|||||||
document.getElementById('playlist-exist-list')
|
document.getElementById('playlist-exist-list')
|
||||||
.querySelectorAll("li")
|
.querySelectorAll("li")
|
||||||
.forEach(n => {
|
.forEach(n => {
|
||||||
if (n.innerText == tagtext) {
|
if (n.getAttribute('data-tag') == tagtext) {
|
||||||
n.classList.add("selected");
|
n.classList.add("selected");
|
||||||
lastSelected = n;
|
lastSelected = n;
|
||||||
} else {
|
} else {
|
||||||
@@ -687,6 +692,8 @@
|
|||||||
} else if (playlist.persist === "track" && playlist.shuffle === "no") {
|
} else if (playlist.persist === "track" && playlist.shuffle === "no") {
|
||||||
playlisttype.value = "audioplay";
|
playlisttype.value = "audioplay";
|
||||||
}
|
}
|
||||||
|
const playlistname = document.getElementById('playlist-edit-name');
|
||||||
|
playlistname.value = playlist.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
@@ -709,6 +716,8 @@
|
|||||||
playlistData.shuffle = "no";
|
playlistData.shuffle = "no";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
const playlistname = document.getElementById('playlist-edit-name');
|
||||||
|
playlistData.name = playlistname.value;
|
||||||
const container = document.getElementById('playlist-edit-list');
|
const container = document.getElementById('playlist-edit-list');
|
||||||
playlistData.paths = [...container.querySelectorAll("li")].map((node) => node.innerText);
|
playlistData.paths = [...container.querySelectorAll("li")].map((node) => node.innerText);
|
||||||
const saveRes = await fetch(`/api/v1/playlist/${playlistId}`,
|
const saveRes = await fetch(`/api/v1/playlist/${playlistId}`,
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ class BTreeDB(IPlaylistDB):
|
|||||||
PERSIST_OFFSET = b'offset'
|
PERSIST_OFFSET = b'offset'
|
||||||
|
|
||||||
class Playlist(IPlaylist):
|
class Playlist(IPlaylist):
|
||||||
def __init__(self, parent: "BTreeDB", tag: bytes, pos: int, persist, shuffle):
|
def __init__(self, parent: "BTreeDB", tag: bytes, pos: int, persist, shuffle, name):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.pos = pos
|
self.pos = pos
|
||||||
self.persist = persist
|
self.persist = persist
|
||||||
self.shuffle = shuffle
|
self.shuffle = shuffle
|
||||||
|
self.name = name
|
||||||
self.length = self.parent._getPlaylistLength(self.tag)
|
self.length = self.parent._getPlaylistLength(self.tag)
|
||||||
self._shuffle()
|
self._shuffle()
|
||||||
|
|
||||||
@@ -168,6 +169,10 @@ class BTreeDB(IPlaylistDB):
|
|||||||
return (b''.join([tag, b'/playlist/']),
|
return (b''.join([tag, b'/playlist/']),
|
||||||
b''.join([tag, b'/playlist0']))
|
b''.join([tag, b'/playlist0']))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _keyPlaylistName(tag):
|
||||||
|
return b''.join([tag, b'/playlistname'])
|
||||||
|
|
||||||
def _flush(self):
|
def _flush(self):
|
||||||
"""
|
"""
|
||||||
Flush the database and call the flush_func if it was provided.
|
Flush the database and call the flush_func if it was provided.
|
||||||
@@ -222,12 +227,13 @@ class BTreeDB(IPlaylistDB):
|
|||||||
raise RuntimeError("Malformed playlist key")
|
raise RuntimeError("Malformed playlist key")
|
||||||
return int(elements[2])+1
|
return int(elements[2])+1
|
||||||
|
|
||||||
def _savePlaylist(self, tag, entries, persist, shuffle, flush=True):
|
def _savePlaylist(self, tag, entries, persist, shuffle, name, flush=True):
|
||||||
self._deletePlaylist(tag, False)
|
self._deletePlaylist(tag, False)
|
||||||
for idx, entry in enumerate(entries):
|
for idx, entry in enumerate(entries):
|
||||||
self.db[self._keyPlaylistEntry(tag, idx)] = entry
|
self.db[self._keyPlaylistEntry(tag, idx)] = entry
|
||||||
self.db[self._keyPlaylistPersist(tag)] = persist
|
self.db[self._keyPlaylistPersist(tag)] = persist
|
||||||
self.db[self._keyPlaylistShuffle(tag)] = shuffle
|
self.db[self._keyPlaylistShuffle(tag)] = shuffle
|
||||||
|
self.db[self._keyPlaylistName(tag)] = name.encode()
|
||||||
if flush:
|
if flush:
|
||||||
self._flush()
|
self._flush()
|
||||||
|
|
||||||
@@ -240,7 +246,7 @@ class BTreeDB(IPlaylistDB):
|
|||||||
pass
|
pass
|
||||||
for k in (self._keyPlaylistPos(tag), self._keyPlaylistPosOffset(tag),
|
for k in (self._keyPlaylistPos(tag), self._keyPlaylistPosOffset(tag),
|
||||||
self._keyPlaylistPersist(tag), self._keyPlaylistShuffle(tag),
|
self._keyPlaylistPersist(tag), self._keyPlaylistShuffle(tag),
|
||||||
self._keyPlaylistShuffleSeed(tag)):
|
self._keyPlaylistShuffleSeed(tag), self._keyPlaylistName(tag)):
|
||||||
try:
|
try:
|
||||||
del self.db[k]
|
del self.db[k]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -248,15 +254,18 @@ class BTreeDB(IPlaylistDB):
|
|||||||
if flush:
|
if flush:
|
||||||
self._flush()
|
self._flush()
|
||||||
|
|
||||||
def getPlaylistTags(self):
|
def getPlaylists(self):
|
||||||
"""
|
"""
|
||||||
Get a keys-only dict of all defined playlists. Playlists currently do not have names, but are identified by
|
Get a list of all defined playlists with their tag and names.
|
||||||
their tag.
|
|
||||||
"""
|
"""
|
||||||
playlist_tags = set()
|
playlist_tags = set()
|
||||||
for item in self.db:
|
for item in self.db:
|
||||||
playlist_tags.add(item.split(b'/')[0])
|
playlist_tags.add(item.split(b'/')[0])
|
||||||
return playlist_tags
|
playlists = []
|
||||||
|
for tag in playlist_tags:
|
||||||
|
name = self.db.get(self._keyPlaylistName(tag), b'').decode()
|
||||||
|
playlists.append({'tag': tag, 'name': name})
|
||||||
|
return playlists
|
||||||
|
|
||||||
def getPlaylistForTag(self, tag: bytes):
|
def getPlaylistForTag(self, tag: bytes):
|
||||||
"""
|
"""
|
||||||
@@ -275,18 +284,19 @@ class BTreeDB(IPlaylistDB):
|
|||||||
return None
|
return None
|
||||||
if self._keyPlaylistEntry(tag, pos) not in self.db:
|
if self._keyPlaylistEntry(tag, pos) not in self.db:
|
||||||
pos = 0
|
pos = 0
|
||||||
|
name = self.db.get(self._keyPlaylistName(tag), b'').decode()
|
||||||
shuffle = self.db.get(self._keyPlaylistShuffle(tag), self.SHUFFLE_NO)
|
shuffle = self.db.get(self._keyPlaylistShuffle(tag), self.SHUFFLE_NO)
|
||||||
return self.Playlist(self, tag, pos, persist, shuffle)
|
return self.Playlist(self, tag, pos, persist, shuffle, name)
|
||||||
|
|
||||||
def createPlaylistForTag(self, tag: bytes, entries: typing.Iterable[bytes], persist=PERSIST_TRACK,
|
def createPlaylistForTag(self, tag: bytes, entries: typing.Iterable[bytes], persist=PERSIST_TRACK,
|
||||||
shuffle=SHUFFLE_NO):
|
shuffle=SHUFFLE_NO, name: str = ''):
|
||||||
"""
|
"""
|
||||||
Create and save a playlist for 'tag' and return the Playlist object. If a playlist already existed for 'tag' it
|
Create and save a playlist for 'tag' and return the Playlist object. If a playlist already existed for 'tag' it
|
||||||
is overwritten.
|
is overwritten.
|
||||||
"""
|
"""
|
||||||
assert persist in (self.PERSIST_NO, self.PERSIST_TRACK, self.PERSIST_OFFSET)
|
assert persist in (self.PERSIST_NO, self.PERSIST_TRACK, self.PERSIST_OFFSET)
|
||||||
assert shuffle in (self.SHUFFLE_NO, self.SHUFFLE_YES)
|
assert shuffle in (self.SHUFFLE_NO, self.SHUFFLE_YES)
|
||||||
self._savePlaylist(tag, entries, persist, shuffle)
|
self._savePlaylist(tag, entries, persist, shuffle, name)
|
||||||
return self.getPlaylistForTag(tag)
|
return self.getPlaylistForTag(tag)
|
||||||
|
|
||||||
def deletePlaylistForTag(self, tag: bytes):
|
def deletePlaylistForTag(self, tag: bytes):
|
||||||
@@ -370,6 +380,14 @@ class BTreeDB(IPlaylistDB):
|
|||||||
_ = int(val)
|
_ = int(val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
fail(f' Bad playlistposoffset value for {last_tag}: {val!r}')
|
fail(f' Bad playlistposoffset value for {last_tag}: {val!r}')
|
||||||
|
elif fields[1] == b'playlistname':
|
||||||
|
val = self.db[k]
|
||||||
|
try:
|
||||||
|
name = val.decode()
|
||||||
|
if dump:
|
||||||
|
print(f'\tName: {name}')
|
||||||
|
except UnicodeError:
|
||||||
|
fail(f' Bad playlistname for {last_tag}: Not valid unicode')
|
||||||
else:
|
else:
|
||||||
fail(f'Unknown key {k!r}')
|
fail(f'Unknown key {k!r}')
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ async def static(request, path):
|
|||||||
|
|
||||||
@webapp.route('/api/v1/playlists', methods=['GET'])
|
@webapp.route('/api/v1/playlists', methods=['GET'])
|
||||||
async def playlists_get(request):
|
async def playlists_get(request):
|
||||||
return sorted(playlist_db.getPlaylistTags())
|
return playlist_db.getPlaylists()
|
||||||
|
|
||||||
|
|
||||||
def is_hex(s):
|
def is_hex(s):
|
||||||
@@ -133,10 +133,11 @@ async def playlist_get(request, tag):
|
|||||||
return None, 404
|
return None, 404
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'shuffle': playlist.__dict__.get('shuffle'),
|
'shuffle': playlist.shuffle,
|
||||||
'persist': playlist.__dict__.get('persist'),
|
'persist': playlist.persist,
|
||||||
'paths': [(p[len(fsroot):] if p.startswith(fsroot) else p).decode()
|
'paths': [(p[len(fsroot):] if p.startswith(fsroot) else p).decode()
|
||||||
for p in playlist.getPaths()],
|
for p in playlist.getPaths()],
|
||||||
|
'name': playlist.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -156,7 +157,8 @@ async def playlist_put(request, tag):
|
|||||||
playlist_db.createPlaylistForTag(tag.encode(),
|
playlist_db.createPlaylistForTag(tag.encode(),
|
||||||
(fsroot + path.encode() for path in playlist.get('paths', [])),
|
(fsroot + path.encode() for path in playlist.get('paths', [])),
|
||||||
playlist.get('persist', 'track').encode(),
|
playlist.get('persist', 'track').encode(),
|
||||||
playlist.get('shuffle', 'no').encode())
|
playlist.get('shuffle', 'no').encode(),
|
||||||
|
playlist.get('name', ''))
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user