diff --git a/modules/active_modifier.py b/modules/active_modifier.py index f5d31a3..26550af 100644 --- a/modules/active_modifier.py +++ b/modules/active_modifier.py @@ -1,5 +1,7 @@ +from modules import BaseIMCModule, InterModuleCommunication from . import ActiveModifier, log95, Track, Path import os, glob, datetime +from threading import Lock from typing import TextIO _log_out: TextIO @@ -15,6 +17,7 @@ class Module(ActiveModifier): self.limit_tracks = False self.can_limit_tracks = False self.morning_start = self.day_end = 0 + self.file_lock = Lock() def on_new_playlist(self, playlist: list[Track]): self.playlist = playlist @@ -25,8 +28,10 @@ class Module(ActiveModifier): self.can_limit_tracks = self.limit_tracks def play(self, index: int, track: Track, next_track: Track | None): if not self.playlist: return (track, next_track), False - if not os.path.exists("/tmp/radioPlayer_toplay"): open("/tmp/radioPlayer_toplay", "a").close() - with open("/tmp/radioPlayer_toplay", "r") as f: songs = [s.strip() for s in f.readlines() if s.strip()] + + with self.file_lock: + if not os.path.exists("/tmp/radioPlayer_toplay"): open("/tmp/radioPlayer_toplay", "a").close() + with open("/tmp/radioPlayer_toplay", "r") as f: songs = [s.strip() for s in f.readlines() if s.strip()] songs[:] = [f for s in songs for f in glob.glob(s) if os.path.isfile(f)] # expand glob @@ -98,4 +103,24 @@ class Module(ActiveModifier): if last_track_duration: logger.info("Track ends at", repr(future)) return (self.last_track, next_track), False + def imc(self, imc: InterModuleCommunication) -> None: + super().imc(imc) + self._imc.register(self, "activemod") + def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool) -> object: + if not isinstance(data, dict) or broadcast: return + + if data.get("action") == "add_to_toplay": + songs_to_add = data.get("songs") + if isinstance(songs_to_add, list): + logger.info(f"Received request to add {len(songs_to_add)} items to toplay queue from '{source_name or 'unnamed'}'") + + with self.file_lock: + with open("/tmp/radioPlayer_toplay", "a") as f: + for song_path in songs_to_add: f.write(f"{song_path}\n") + return {"status": "ok", "message": f"{len(songs_to_add)} songs added."} + elif data.get("action") == "get_toplay": + with self.file_lock: + with open("/tmp/radioPlayer_toplay", "r") as f: + return {"status": "ok", "data": f.readlines()} + activemod = Module() \ No newline at end of file diff --git a/modules/web.py b/modules/web.py index 593841e..5892d08 100644 --- a/modules/web.py +++ b/modules/web.py @@ -20,34 +20,52 @@ class APIHandler(BaseHTTPRequestHandler): self.send_header("Content-Type", "application/json") self.end_headers() - if self.path == "/api/playlist": - rdata = json.loads(self.data.get("playlist", "[]")) - elif self.path == "/api/track": - rdata = json.loads(self.data.get("track", "{}")) - else: - rdata = {"error": "not found"} - + if self.path == "/api/playlist": rdata = json.loads(self.data.get("playlist", "[]")) + elif self.path == "/api/track": rdata = json.loads(self.data.get("track", "{}")) + else: rdata = {"error": "not found"} + self.wfile.write(json.dumps(rdata).encode('utf-8')) def do_POST(self): + response = {"error": "not found"} + code = 404 + if self.path == "/api/skip": self.imc_q.put({"name": "procman", "data": {"op": 2}}) response = {"status": "ok", "action": "skip requested"} code = 200 - else: - response = {"error": "not found"} - code = 404 + elif self.path == "/api/put": + try: + body = json.loads(self.rfile.read(int(self.headers['Content-Length']))) + + songs = body.get("songs") + if songs is None or not isinstance(songs, list): raise ValueError("Request body must be a JSON object with a 'songs' key containing a list of strings.") + + self.imc_q.put({"name": "activemod", "data": {"action": "add_to_toplay", "songs": songs}}) + + response = {"status": "ok", "message": f"{len(songs)} song(s) were added to the high-priority queue."} + code = 200 + except json.JSONDecodeError: + response = {"error": "Invalid JSON in request body."} + code = 400 + except (ValueError, KeyError, TypeError) as e: + response = {"error": f"Invalid request format: {e}"} + code = 400 + except Exception as e: + response = {"error": f"An unexpected server error occurred: {e}"} + code = 500 self.send_response(code) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode('utf-8')) + def send_response(self, code, message=None): + self.send_response_only(code, message) + self.send_header('Server', self.version_string()) + self.send_header('Date', self.date_time_string()) def web_server_process(data, imc_q): - handler = partial(APIHandler, data, imc_q) - httpd = ThreadingHTTPServer(("0.0.0.0", 3001), handler) - httpd.serve_forever() - + ThreadingHTTPServer(("0.0.0.0", 3001), partial(APIHandler, data, imc_q)).serve_forever() class Module(PlayerModule): def __init__(self): @@ -69,13 +87,9 @@ class Module(PlayerModule): while self.ipc_thread_running: try: message = self.imc_q.get() - - if message is None: - break - + if message is None: break self._imc.send(self, message["name"], message["data"]) - except Exception as e: - pass + except Exception: pass def on_new_playlist(self, playlist: list[Track]) -> None: api_data = [] @@ -87,13 +101,10 @@ class Module(PlayerModule): track_data = {"path": str(track.path), "fade_out": track.fade_out, "fade_in": track.fade_in, "official": track.official, "args": track.args, "offset": track.offset} if next_track: next_track_data = {"path": str(next_track.path), "fade_out": next_track.fade_out, "fade_in": next_track.fade_in, "official": next_track.official, "args": next_track.args, "offset": next_track.offset} - else: - next_track_data = None + else: next_track_data = None self.data["track"] = json.dumps({"index": index, "track": track_data, "next_track": next_track_data}) def shutdown(self): - print("Shutting down Web API module...") - self.ipc_thread_running = False self.imc_q.put(None) self.ipc_thread.join(timeout=2) @@ -101,8 +112,7 @@ class Module(PlayerModule): if self.web_process.is_alive(): self.web_process.terminate() self.web_process.join(timeout=2) - - if self.web_process.is_alive(): - self.web_process.kill() - + + if self.web_process.is_alive(): self.web_process.kill() + module = Module() \ No newline at end of file