wip web playback
This commit is contained in:
parent
ed16ebdd0e
commit
53e102c8b2
|
@ -9,12 +9,12 @@ from slugify import slugify
|
||||||
from rich import print
|
from rich import print
|
||||||
import rich.table
|
import rich.table
|
||||||
|
|
||||||
from groove import webserver
|
|
||||||
from groove.shell import interactive_shell
|
from groove.shell import interactive_shell
|
||||||
from groove.playlist import Playlist
|
from groove.playlist import Playlist
|
||||||
from groove import db
|
from groove import db
|
||||||
from groove.db.manager import database_manager
|
from groove.db.manager import database_manager
|
||||||
from groove.db.scanner import media_scanner
|
from groove.db.scanner import media_scanner
|
||||||
|
from groove.webserver import webserver
|
||||||
|
|
||||||
playlist_app = typer.Typer()
|
playlist_app = typer.Typer()
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
5
groove/exceptions.py
Normal file
5
groove/exceptions.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
class APIHandlingException(Exception):
|
||||||
|
"""
|
||||||
|
An API reqeust could not be encoded or decoded.
|
||||||
|
"""
|
|
@ -35,9 +35,12 @@ class Playlist:
|
||||||
@property
|
@property
|
||||||
def exists(self) -> bool:
|
def exists(self) -> bool:
|
||||||
if self.deleted:
|
if self.deleted:
|
||||||
|
logging.debug("Playlist has been deleted.")
|
||||||
return False
|
return False
|
||||||
if not self._record:
|
if not self._record:
|
||||||
return (self._create_ok and self.record)
|
if self._create_ok:
|
||||||
|
return True and self.record
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -163,6 +166,10 @@ class Playlist:
|
||||||
if self._create_ok or create_ok:
|
if self._create_ok or create_ok:
|
||||||
return self.save()
|
return self.save()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
self.get_or_create(create_ok=False)
|
||||||
|
return self
|
||||||
|
|
||||||
def save(self) -> Row:
|
def save(self) -> Row:
|
||||||
keys = {'slug': self.slug, 'name': self._name, 'description': self._description}
|
keys = {'slug': self.slug, 'name': self._name, 'description': self._description}
|
||||||
stmt = db.playlist.update(keys) if self._record else db.playlist.insert(keys)
|
stmt = db.playlist.update(keys) if self._record else db.playlist.insert(keys)
|
||||||
|
|
0
groove/webserver/__init__.py
Normal file
0
groove/webserver/__init__.py
Normal file
39
groove/webserver/requests.py
Normal file
39
groove/webserver/requests.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from hashlib import blake2b
|
||||||
|
from hmac import compare_digest
|
||||||
|
from typing import List
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def encode(args: List, uri: str) -> str:
|
||||||
|
"""
|
||||||
|
Encode a request and cryptographically sign it. This serves two purposes:
|
||||||
|
First, it enables the handler that receives the request to verify that the
|
||||||
|
request was meant for it, preventing various routing and relay-induced bugs.
|
||||||
|
Second, it ensures the request wasn't corrutped or tampered with during
|
||||||
|
transmission.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args (List): a list of parameters to pass along with the request.
|
||||||
|
uri (String): the URI of the intended handler for the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String: A cryptographically signed request.
|
||||||
|
"""
|
||||||
|
return sign(uri + '\0' + '\0'.join(args))
|
||||||
|
|
||||||
|
|
||||||
|
def sign(request):
|
||||||
|
"""
|
||||||
|
Sign a request with a cryptographic hash. Returns the hex digest.
|
||||||
|
"""
|
||||||
|
h = blake2b(digest_size=16, key=bytes(os.environ['SECRET_KEY'].encode()))
|
||||||
|
h.update(request.encode())
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def verify(request, digest):
|
||||||
|
return compare_digest(request, digest)
|
||||||
|
|
||||||
|
|
||||||
|
def url():
|
||||||
|
return f"http://{os.environ['HOST']}:{os.environ['PORT']}"
|
|
@ -1,15 +1,19 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import bottle
|
import bottle
|
||||||
from bottle import HTTPResponse
|
from bottle import HTTPResponse, template, static_file
|
||||||
from bottle.ext import sqlalchemy
|
from bottle.ext import sqlalchemy
|
||||||
|
|
||||||
|
import groove.db
|
||||||
from groove.auth import is_authenticated
|
from groove.auth import is_authenticated
|
||||||
from groove.db.manager import database_manager
|
from groove.db.manager import database_manager
|
||||||
from groove.db import metadata
|
|
||||||
from groove.playlist import Playlist
|
from groove.playlist import Playlist
|
||||||
|
from groove.webserver import requests
|
||||||
|
|
||||||
|
from groove.exceptions import APIHandlingException
|
||||||
|
|
||||||
server = bottle.Bottle()
|
server = bottle.Bottle()
|
||||||
|
|
||||||
|
@ -23,7 +27,7 @@ def start(host: str, port: int, debug: bool) -> None: # pragma: no cover
|
||||||
with database_manager() as manager:
|
with database_manager() as manager:
|
||||||
server.install(sqlalchemy.Plugin(
|
server.install(sqlalchemy.Plugin(
|
||||||
manager.engine,
|
manager.engine,
|
||||||
metadata,
|
groove.db.metadata,
|
||||||
keyword='db',
|
keyword='db',
|
||||||
create=True,
|
create=True,
|
||||||
commit=True,
|
commit=True,
|
||||||
|
@ -49,16 +53,48 @@ def build():
|
||||||
return "Authenticated. Groovy."
|
return "Authenticated. Groovy."
|
||||||
|
|
||||||
|
|
||||||
|
@bottle.auth_basic(is_authenticated)
|
||||||
|
@server.route('/build/search/playlist')
|
||||||
|
def search_playlist(slug, db):
|
||||||
|
playlist = Playlist(slug=slug, session=db, create_ok=False)
|
||||||
|
response = json.dumps(playlist.as_dict)
|
||||||
|
logging.debug(response)
|
||||||
|
return HTTPResponse(status=200, content_type='application/json', body=response)
|
||||||
|
|
||||||
|
|
||||||
|
@server.route('/track/<request>/<track_id>')
|
||||||
|
def serve_track(request, track_id, db):
|
||||||
|
|
||||||
|
expected = requests.encode([track_id], '/track')
|
||||||
|
if not requests.verify(request, expected):
|
||||||
|
return HTTPResponse(status=404, body="Not found")
|
||||||
|
|
||||||
|
track_id = int(track_id)
|
||||||
|
track = db.query(groove.db.track).filter(
|
||||||
|
groove.db.track.c.id == track_id
|
||||||
|
).one()
|
||||||
|
|
||||||
|
path = Path(os.environ['MEDIA_ROOT']) / Path(track['relpath'])
|
||||||
|
return static_file(path.name, root=path.parent)
|
||||||
|
|
||||||
|
|
||||||
@server.route('/playlist/<slug>')
|
@server.route('/playlist/<slug>')
|
||||||
def get_playlist(slug, db):
|
def serve_playlist(slug, db):
|
||||||
"""
|
"""
|
||||||
Retrieve a playlist and its entries by a slug.
|
Retrieve a playlist and its entries by a slug.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"Looking up playlist: {slug}...")
|
logging.debug(f"Looking up playlist: {slug}...")
|
||||||
playlist = Playlist(slug=slug, session=db, create_ok=False)
|
playlist = Playlist(slug=slug, session=db, create_ok=False).load()
|
||||||
print(playlist.record)
|
if not playlist.record:
|
||||||
if not playlist.exists:
|
logging.debug(f"Playist {slug} doesn't exist.")
|
||||||
return HTTPResponse(status=404, body="Not found")
|
return HTTPResponse(status=404, body="Not found")
|
||||||
response = json.dumps(playlist.as_dict)
|
logging.debug(f"Loaded {playlist.record}")
|
||||||
logging.debug(response)
|
logging.debug(playlist.as_dict['entries'])
|
||||||
return HTTPResponse(status=200, content_type='application/json', body=response)
|
|
||||||
|
args = [
|
||||||
|
(requests.encode([str(entry['track_id'])], uri='/track'), entry['track_id'])
|
||||||
|
for entry in playlist.as_dict['entries']
|
||||||
|
]
|
||||||
|
|
||||||
|
template_path = Path(os.environ['TEMPLATE_PATH']) / Path('playlist.tpl')
|
||||||
|
return template(str(template_path), url=requests.url(), playlist=playlist.as_dict, args=args)
|
6
web-templates/playlist.tpl
Normal file
6
web-templates/playlist.tpl
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<audio id="audio" preload="none" tabindex="0">
|
||||||
|
% for sig, track_id in args:
|
||||||
|
<source src="/track/{{sig}}/{{track_id}}">
|
||||||
|
% end
|
||||||
|
Your browser does not support HTML5 audio.
|
||||||
|
</audio>
|
Loading…
Reference in New Issue
Block a user