Compare commits
7 Commits
b46473e51a
...
8aea47e1ce
| Author | SHA1 | Date | |
|---|---|---|---|
| 8aea47e1ce | |||
| 67a3ebdfdf | |||
| 8bfc409473 | |||
| e2ca9e5139 | |||
| 070cf887ab | |||
| 28846c9274 | |||
| 51cb2c3a68 |
@@ -193,3 +193,6 @@ class PlayerApp:
|
|||||||
|
|
||||||
def get_nfc(self):
|
def get_nfc(self):
|
||||||
return self.nfc
|
return self.nfc
|
||||||
|
|
||||||
|
def get_playlist_db(self):
|
||||||
|
return self.playlist_db
|
||||||
|
|||||||
@@ -248,6 +248,16 @@ class BTreeDB(IPlaylistDB):
|
|||||||
if flush:
|
if flush:
|
||||||
self._flush()
|
self._flush()
|
||||||
|
|
||||||
|
def getPlaylistTags(self):
|
||||||
|
"""
|
||||||
|
Get a keys-only dict of all defined playlists. Playlists currently do not have names, but are identified by
|
||||||
|
their tag.
|
||||||
|
"""
|
||||||
|
playlist_tags = set()
|
||||||
|
for item in self.db:
|
||||||
|
playlist_tags.add(item.split(b'/')[0])
|
||||||
|
return playlist_tags
|
||||||
|
|
||||||
def getPlaylistForTag(self, tag: bytes):
|
def getPlaylistForTag(self, tag: bytes):
|
||||||
"""
|
"""
|
||||||
Lookup the playlist for 'tag' and return the Playlist object. Return None if no playlist exists for the given
|
Lookup the playlist for 'tag' and return the Playlist object. Return None if no playlist exists for the given
|
||||||
@@ -279,6 +289,9 @@ class BTreeDB(IPlaylistDB):
|
|||||||
self._savePlaylist(tag, entries, persist, shuffle)
|
self._savePlaylist(tag, entries, persist, shuffle)
|
||||||
return self.getPlaylistForTag(tag)
|
return self.getPlaylistForTag(tag)
|
||||||
|
|
||||||
|
def deletePlaylistForTag(self, tag: bytes):
|
||||||
|
self._deletePlaylist(tag)
|
||||||
|
|
||||||
def validate(self, dump=False):
|
def validate(self, dump=False):
|
||||||
"""
|
"""
|
||||||
Validate the structure of the playlist database.
|
Validate the structure of the playlist database.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ Copyright (c) 2024-2025 Stefan Kratochwil <Kratochwil-LA@gmx.de>
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
from microdot import Microdot, redirect, send_file
|
from microdot import Microdot, redirect, send_file
|
||||||
|
|
||||||
@@ -12,14 +14,16 @@ server = None
|
|||||||
config = None
|
config = None
|
||||||
app = None
|
app = None
|
||||||
nfc = None
|
nfc = None
|
||||||
|
playlist_db = None
|
||||||
|
|
||||||
|
|
||||||
def start_webserver(config_, app_):
|
def start_webserver(config_, app_):
|
||||||
global server, config, app, nfc
|
global server, config, app, nfc, playlist_db
|
||||||
server = asyncio.create_task(webapp.start_server(port=80))
|
server = asyncio.create_task(webapp.start_server(port=80))
|
||||||
config = config_
|
config = config_
|
||||||
app = app_
|
app = app_
|
||||||
nfc = app.get_nfc()
|
nfc = app.get_nfc()
|
||||||
|
playlist_db = app.get_playlist_db()
|
||||||
|
|
||||||
|
|
||||||
@webapp.before_request
|
@webapp.before_request
|
||||||
@@ -90,3 +94,88 @@ async def static(request, path):
|
|||||||
# directory traversal is not allowed
|
# directory traversal is not allowed
|
||||||
return 'Not found', 404
|
return 'Not found', 404
|
||||||
return send_file('/frontend/static/' + path, max_age=86400)
|
return send_file('/frontend/static/' + path, max_age=86400)
|
||||||
|
|
||||||
|
|
||||||
|
@webapp.route('/api/v1/playlists', methods=['GET'])
|
||||||
|
async def playlists_get(request):
|
||||||
|
return sorted(playlist_db.getPlaylistTags())
|
||||||
|
|
||||||
|
|
||||||
|
def is_hex(s):
|
||||||
|
hex_chars = '0123456789abcdef'
|
||||||
|
return all(c in hex_chars for c in s)
|
||||||
|
|
||||||
|
|
||||||
|
fsroot = b'/sd'
|
||||||
|
|
||||||
|
|
||||||
|
@webapp.route('/api/v1/playlist/<tag>', methods=['GET'])
|
||||||
|
async def playlist_get(request, tag):
|
||||||
|
if not is_hex(tag):
|
||||||
|
return 'invalid tag', 400
|
||||||
|
|
||||||
|
playlist = playlist_db.getPlaylistForTag(tag.encode())
|
||||||
|
if playlist is None:
|
||||||
|
return None, 404
|
||||||
|
|
||||||
|
return {
|
||||||
|
'shuffle': playlist.__dict__.get('shuffle'),
|
||||||
|
'persist': playlist.__dict__.get('persist'),
|
||||||
|
'paths': [(p[len(fsroot):] if p.startswith(fsroot) else p).decode()
|
||||||
|
for p in playlist.getPaths()],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@webapp.route('/api/v1/playlist/<tag>', methods=['PUT'])
|
||||||
|
async def playlist_put(request, tag):
|
||||||
|
if not is_hex(tag):
|
||||||
|
return 'invalid tag', 400
|
||||||
|
|
||||||
|
playlist = request.json
|
||||||
|
if 'persist' in playlist and \
|
||||||
|
playlist['persist'] not in ['no', 'track', 'offset']:
|
||||||
|
return "Invalid 'persist' setting", 400
|
||||||
|
if 'shuffle' in playlist and \
|
||||||
|
playlist['shuffle'] not in ['no', 'yes']:
|
||||||
|
return "Invalid 'shuffle' setting", 400
|
||||||
|
|
||||||
|
playlist_db.createPlaylistForTag(tag.encode(),
|
||||||
|
(fsroot + path.encode() for path in playlist.get('paths', [])),
|
||||||
|
playlist.get('persist', 'track').encode(),
|
||||||
|
playlist.get('shuffle', 'no').encode())
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
|
||||||
|
@webapp.route('/api/v1/playlist/<tag>', methods=['DELETE'])
|
||||||
|
async def playlist_delete(request, tag):
|
||||||
|
if not is_hex(tag):
|
||||||
|
return 'invalid tag', 400
|
||||||
|
playlist_db.deletePlaylistForTag(tag.encode())
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
|
||||||
|
@webapp.route('/api/v1/audiofiles', methods=['GET'])
|
||||||
|
async def audiofiles_get(request):
|
||||||
|
def directory_iterator():
|
||||||
|
yield '['
|
||||||
|
first = True
|
||||||
|
dirstack = [fsroot]
|
||||||
|
while dirstack:
|
||||||
|
current_dir = dirstack.pop()
|
||||||
|
for entry in os.ilistdir(current_dir):
|
||||||
|
name = entry[0]
|
||||||
|
type_ = entry[1]
|
||||||
|
current_path = current_dir + b'/' + name
|
||||||
|
if type_ == 0x4000:
|
||||||
|
dirstack.append(current_path)
|
||||||
|
elif type_ == 0x8000:
|
||||||
|
if name.lower().endswith('.mp3'):
|
||||||
|
jsonpath = json.dumps(current_path[len(fsroot):])
|
||||||
|
if not first:
|
||||||
|
yield ','+jsonpath
|
||||||
|
else:
|
||||||
|
yield jsonpath
|
||||||
|
first = False
|
||||||
|
yield ']'
|
||||||
|
|
||||||
|
return directory_iterator(), {'Content-Type': 'application/json; charset=UTF-8'}
|
||||||
|
|||||||
Reference in New Issue
Block a user