From 9ec11c2691231ec9d76e98a2a8fa488ddfe22412 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Mon, 26 Apr 2021 00:18:50 +0100 Subject: [PATCH] Add initial file preloading algorithm. --- file_manager.py | 65 +++++++++++++++++++++++++++++++++++++++--- helpers/myradio_api.py | 14 ++++----- player_handler.py | 4 ++- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/file_manager.py b/file_manager.py index b4ab37a..49eac11 100644 --- a/file_manager.py +++ b/file_manager.py @@ -1,21 +1,28 @@ -import time +from helpers.state_manager import StateManager from helpers.os_environment import resolve_external_file_path from typing import List from setproctitle import setproctitle from multiprocessing import current_process, Queue from time import sleep import os +import json +from syncer import sync from helpers.logging_manager import LoggingManager from helpers.the_terminator import Terminator +from helpers.myradio_api import MyRadioAPI +from baps_types.plan import PlanItem class FileManager: logger: LoggingManager + api: MyRadioAPI - def __init__(self, channel_from_q: List[Queue], server_config): + def __init__(self, channel_from_q: List[Queue], server_config: StateManager): self.logger = LoggingManager("FileManager") + self.api = MyRadioAPI(self.logger, server_config) + process_title = "File Manager" setproctitle(process_title) current_process().name = process_title @@ -23,6 +30,9 @@ class FileManager: terminator = Terminator() channel_count = len(channel_from_q) channel_received = None + last_known_show_plan = [None]*channel_count + next_channel_preload = 0 + last_known_item_ids = [[]]*channel_count try: while not terminator.terminate: @@ -34,7 +44,9 @@ class FileManager: try: message = channel_from_q[channel].get_nowait() #source = message.split(":")[0] - command = message.split(":",2)[1] + command = message.split(":",2)[1] + + # If we have requested a new show plan, empty the music-tmp directory for the previous show. if command == "GET_PLAN": if channel_received != [False]*channel_count and channel_received[channel] != True: @@ -57,11 +69,56 @@ class FileManager: os.remove(path+"/"+file) channel_received[channel] = True + # If we receive a new status message, let's check for files which have not been pre-loaded. + if command == "STATUS": + extra = message.split(":",3) + if extra[2] != "OKAY": + continue + + status = json.loads(extra[3]) + show_plan = status["show_plan"] + item_ids = [] + for item in show_plan: + item_ids += item["timeslotitemid"] + + # If the new status update has a different order / list of items, let's update the show plan we know about + # This will trigger the chunk below to do the rounds again and preload any new files. + if item_ids != last_known_item_ids[channel]: + last_known_item_ids[channel] = item_ids + last_known_show_plan[channel] = show_plan except Exception: pass - sleep(1) + + # Right, let's have a quick check in the status for shows without filenames, to preload them. + delay = True + for i in range(len(last_known_show_plan[next_channel_preload])): + + item_obj = PlanItem(last_known_show_plan[next_channel_preload][i]) + if not item_obj.filename: + print("Checking pre-load on channel {}, weight {}: {}".format(next_channel_preload, item_obj.weight, item_obj.name)) + + # Getting the file name will only pull the new file if the file doesn't already exist, so this is not too inefficient. + item_obj.filename,did_download = sync(self.api.get_filename(item_obj, True)) + # Alright, we've done one, now let's give back control to process new statuses etc. + # Given we probably took some time to download, let's not sleep in the loop. + + # Save back the resulting item back in regular dict form + last_known_show_plan[next_channel_preload][i] = item_obj.__dict__ + + if did_download: + delay = False + break + else: + # We didn't download anything this time, file was already loaded. + # Let's try the next one. + continue + next_channel_preload += 1 + if next_channel_preload >= channel_count: + next_channel_preload = 0 + if delay: + sleep(0.1) except Exception as e: self.logger.log.exception( "Received unexpected exception: {}".format(e)) diff --git a/helpers/myradio_api.py b/helpers/myradio_api.py index aa1ba45..709454b 100644 --- a/helpers/myradio_api.py +++ b/helpers/myradio_api.py @@ -201,7 +201,7 @@ class MyRadioAPI: # Audio Library - async def get_filename(self, item: PlanItem): + async def get_filename(self, item: PlanItem, did_download: bool = False): format = "mp3" # TODO: Maybe we want this customisable? if item.trackid: itemType = "track" @@ -214,7 +214,7 @@ class MyRadioAPI: url = "/NIPSWeb/managed_play?managedid={}".format(id) else: - return None + return (None, False) if did_download else None # Now check if the file already exists path: str = resolve_external_file_path("/music-tmp/") @@ -225,29 +225,29 @@ class MyRadioAPI: os.mkdir(path) except Exception as e: self._logException("Failed to create music-tmp folder: {}".format(e)) - return None + return (None, False) if did_download else None filename: str = resolve_external_file_path( "/music-tmp/{}-{}.{}".format(itemType, id, format) ) if os.path.isfile(filename): - return filename + return (filename, False) if did_download else filename # File doesn't exist, download it. request = await self.async_api_call(url, api_version="non") if not request: - return None + return (None, False) if did_download else None try: with open(filename, "wb") as file: file.write(await request) except Exception as e: self._logException("Failed to write music file: {}".format(e)) - return None + return (None, False) if did_download else None - return filename + return (filename, True) if did_download else filename # Gets the list of managed music playlists. async def get_playlist_music(self): diff --git a/player_handler.py b/player_handler.py index 1377f23..5f1446f 100644 --- a/player_handler.py +++ b/player_handler.py @@ -26,7 +26,9 @@ class PlayerHandler: message = channel_from_q[channel].get_nowait() source = message.split(":")[0] command = message.split(":")[1] - if command == "GET_PLAN": + + # Let the file manager manage the files based on status and loading new show plan triggers. + if command == "GET_PLAN" or command == "STATUS": file_to_q[channel].put(message)