import os import json import requests import bcrypt import re from logging_config import Logger def is_playlist_up_to_date(local_playlist_path, config): """ Compare the version of the local playlist with the server playlist. Returns True if up-to-date, False otherwise. """ import json if not os.path.exists(local_playlist_path): Logger.info(f"Local playlist file not found: {local_playlist_path}") return False with open(local_playlist_path, 'r') as f: local_data = json.load(f) local_version = local_data.get('version', 0) server_data = fetch_server_playlist(config) server_version = server_data.get('version', 0) Logger.info(f"Local playlist version: {local_version}, Server playlist version: {server_version}") return local_version == server_version def fetch_server_playlist(config): """Fetch the updated playlist from the server using a config dict.""" server = config.get("server_ip", "") host = config.get("screen_name", "") quick = config.get("quickconnect_key", "") port = config.get("port", "") try: ip_pattern = r'^\d+\.\d+\.\d+\.\d+$' if re.match(ip_pattern, server): server_url = f'http://{server}:{port}/api/playlists' else: server_url = f'http://{server}/api/playlists' params = { 'hostname': host, 'quickconnect_code': quick } Logger.info(f"Fetching playlist from URL: {server_url} with params: {params}") response = requests.get(server_url, params=params) if response.status_code == 200: response_data = response.json() Logger.info(f"Server response: {response_data}") playlist = response_data.get('playlist', []) version = response_data.get('playlist_version', None) hashed_quickconnect = response_data.get('hashed_quickconnect', None) if version is not None and hashed_quickconnect is not None: if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')): Logger.info("Fetched updated playlist from server.") return {'playlist': playlist, 'version': version} else: Logger.error("Quickconnect code validation failed.") else: Logger.error("Failed to retrieve playlist or hashed quickconnect from the response.") else: Logger.error(f"Failed to fetch playlist. Status Code: {response.status_code}") except requests.exceptions.RequestException as e: Logger.error(f"Failed to fetch playlist: {e}") return {'playlist': [], 'version': 0} def save_playlist_with_version(playlist_data, playlist_dir): version = playlist_data.get('version', 0) playlist_file = os.path.join(playlist_dir, f'server_playlist_v{version}.json') with open(playlist_file, 'w') as f: json.dump(playlist_data, f, indent=2) print(f"Playlist saved to {playlist_file}") return playlist_file def download_media_files(playlist, media_dir): """Download media files from the server and save them to media_dir.""" if not os.path.exists(media_dir): os.makedirs(media_dir) Logger.info(f"Created directory {media_dir} for media files.") updated_playlist = [] for media in playlist: file_name = media.get('file_name', '') file_url = media.get('url', '') duration = media.get('duration', 10) local_path = os.path.join(media_dir, file_name) Logger.info(f"Preparing to download {file_name} from {file_url}...") if os.path.exists(local_path): Logger.info(f"File {file_name} already exists. Skipping download.") else: try: response = requests.get(file_url, timeout=10) if response.status_code == 200: with open(local_path, 'wb') as file: file.write(response.content) Logger.info(f"Successfully downloaded {file_name} to {local_path}") else: Logger.error(f"Failed to download {file_name}. Status Code: {response.status_code}") continue except requests.exceptions.RequestException as e: Logger.error(f"Error downloading {file_name}: {e}") continue updated_media = { 'file_name': file_name, 'url': os.path.relpath(local_path, os.path.dirname(media_dir)), 'duration': duration } updated_playlist.append(updated_media) return updated_playlist def delete_old_playlists_and_media(current_version, playlist_dir, media_dir, keep_versions=1): """ Delete old playlist files and media files not referenced by the latest playlist version. keep_versions: number of latest versions to keep (default 1) """ # Find all playlist files playlist_files = [f for f in os.listdir(playlist_dir) if f.startswith('server_playlist_v') and f.endswith('.json')] # Keep only the latest N versions versions = sorted([int(f.split('_v')[-1].split('.json')[0]) for f in playlist_files], reverse=True) keep = set(versions[:keep_versions]) # Delete old playlist files for f in playlist_files: v = int(f.split('_v')[-1].split('.json')[0]) if v not in keep: os.remove(os.path.join(playlist_dir, f)) # Collect all media files referenced by the kept playlists referenced = set() for v in keep: path = os.path.join(playlist_dir, f'server_playlist_v{v}.json') if os.path.exists(path): with open(path, 'r') as f: data = json.load(f) for item in data.get('playlist', []): referenced.add(item.get('file_name')) # Delete media files not referenced for f in os.listdir(media_dir): if f not in referenced: try: os.remove(os.path.join(media_dir, f)) except Exception as e: Logger.warning(f"Failed to delete media file {f}: {e}") def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_dir): """ Fetch the server playlist once, compare versions, and update if needed. Returns True if updated, False if already up to date. """ import json server_data = fetch_server_playlist(config) server_version = server_data.get('version', 0) if not os.path.exists(local_playlist_path): local_version = 0 else: with open(local_playlist_path, 'r') as f: local_data = json.load(f) local_version = local_data.get('version', 0) Logger.info(f"Local playlist version: {local_version}, Server playlist version: {server_version}") if local_version != server_version: if server_data and server_data.get('playlist'): updated_playlist = download_media_files(server_data['playlist'], media_dir) server_data['playlist'] = updated_playlist save_playlist_with_version(server_data, playlist_dir) # Delete old playlists and unreferenced media delete_old_playlists_and_media(server_version, playlist_dir, media_dir) return True else: Logger.warning("No playlist data fetched from server or playlist is empty.") return False else: Logger.info("Local playlist is already up to date.") return False