diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 3427acf..fc9ac3b 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -98,4 +98,5 @@ from .annotation import * from .chat import * from .search import * from .playlists import * +from .jukebox import * from .unsupported import * diff --git a/supysonic/api/jukebox.py b/supysonic/api/jukebox.py new file mode 100644 index 0000000..5cd77d8 --- /dev/null +++ b/supysonic/api/jukebox.py @@ -0,0 +1,94 @@ +# coding: utf-8 +# +# This file is part of Supysonic. +# Supysonic is a Python implementation of the Subsonic server API. +# +# Copyright (C) 2019 Alban 'spl0k' FĂ©ron +# +# Distributed under terms of the GNU AGPLv3 license. + +import uuid + +from flask import current_app, request +from pony.orm import ObjectNotFound + +from ..daemon import DaemonClient +from ..daemon.exceptions import DaemonUnavailableError +from ..db import Track + +from . import api +from .exceptions import GenericError, MissingParameter + + +@api.route("/jukeboxControl.view", methods=["GET", "POST"]) +def jukebox_control(): + action = request.values["action"] + + index = request.values.get("index") + offset = request.values.get("offset") + id = request.values.getlist("id") + gain = request.values.get("gain") + + if action not in ( + "get", + "status", + "set", + "start", + "stop", + "skip", + "add", + "clear", + "remove", + "shuffle", + "setGain", + ): + raise GenericError("Unknown action") + + arg = None + if action == "set": + if not id: + arg = [] + else: + arg = [uuid.UUID(i) for i in id] + elif action == "skip": + if not index: + raise MissingParameter("index") + else: + arg = int(index) + elif action == "add": + if not id: + raise MissingParameter("id") + else: + arg = [uuid.UUID(i) for i in id] + elif action == "remove": + if not index: + raise MissingParameter("index") + else: + arg = int(index) + elif action == "setGain": + if not gain: + raise MissingParameter("gain") + else: + arg = float(gain) + + try: + status = DaemonClient(current_app.config["DAEMON"]["socket"]).jukebox_control( + action, arg + ) + except DaemonUnavailableError: + raise GenericError("Jukebox unavaliable") + + rv = dict(currentIndex=status.index, playing=status.playing, gain=status.gain) + if action == "get": + playlist = [] + for path in status.playlist: + try: + playlist.append(Track.get(path=path)) + except ObjectNotFound: + pass + rv["entry"] = [ + t.as_subsonic_child(request.user, request.client) for t in playlist + ] + return request.formatter("jukeboxPlaylist", rv) + else: + return request.formatter("jukeboxStatus", rv) diff --git a/supysonic/daemon/client.py b/supysonic/daemon/client.py index b81d66d..2e342a5 100644 --- a/supysonic/daemon/client.py +++ b/supysonic/daemon/client.py @@ -119,6 +119,7 @@ class JukeboxResult(DaemonCommandResult): self.playing = jukebox.playing self.index = jukebox.index self.gain = jukebox.gain + self.playlist = () class DaemonClient(object): @@ -159,9 +160,9 @@ class DaemonClient(object): with self.__get_connection() as c: c.send(ScannerStartCommand(folders, force)) - def jukebox_control(self, action, *args): + def jukebox_control(self, action, arg): if not isinstance(action, strtype): raise TypeError("Expecting string, got " + str(type(action))) with self.__get_connection() as c: - c.send(JukeboxCommand(action, args)) + c.send(JukeboxCommand(action, arg)) return c.recv() diff --git a/supysonic/daemon/server.py b/supysonic/daemon/server.py index 1bc7412..fa2d07b 100644 --- a/supysonic/daemon/server.py +++ b/supysonic/daemon/server.py @@ -37,7 +37,7 @@ class Daemon(object): watcher = property(lambda self: self.__watcher) scanner = property(lambda self: self.__scanner) - jukbox = property(lambda self: self.__jukebox) + jukebox = property(lambda self: self.__jukebox) def __handle_connection(self, connection): cmd = connection.recv() diff --git a/supysonic/jukebox.py b/supysonic/jukebox.py index 89d0078..1077437 100644 --- a/supysonic/jukebox.py +++ b/supysonic/jukebox.py @@ -11,7 +11,7 @@ import logging import shlex import time -from pony.orm import select +from pony.orm import db_session, ObjectNotFound from random import shuffle from subprocess import Popen, DEVNULL from threading import Thread, Event, RLock @@ -59,20 +59,22 @@ class Jukebox(object): self.__stop.set() def skip(self, index): - if not self.playing: - return - if index < 0 or index >= len(self.__playlist): raise IndexError() with self.__lock: self.__index = index - 1 self.__skip.set() + self.start() def add(self, tracks): - paths = select(t.path for t in Track if t.id in tracks) with self.__lock: - self.__playlist += paths[:] + with db_session: + for t in tracks: + try: + self.__playlist.append(Track[t].path) + except ObjectNotFound: + pass def clear(self): with self.__lock: @@ -101,10 +103,11 @@ class Jukebox(object): self.__thread.join() def __play_thread(self): + proc = None while not self.__stop.is_set(): if self.__skip.is_set(): proc.terminate() - proc.join() + proc.wait() self.__skip.clear() if proc is None or proc.poll() is not None: