import json from pathlib import Path from enum import Enum from datetime import datetime import threading class SongStatus(str, Enum): NOT_DOWNLOADED = "NOT_DOWNLOADED" DOWNLOADING = "DOWNLOADING" DOWNLOADED = "DOWNLOADED" PARTIAL = "PARTIAL" FAILED = "FAILED" CONVERTING = "CONVERTING" CONVERTED = "CONVERTED" class FormatType(str, Enum): MP4 = "MP4" MP3 = "MP3" OTHER = "OTHER" class TrackingManager: def __init__(self, tracking_file="data/karaoke_tracking.json", cache_file="data/channel_cache.json"): self.tracking_file = Path(tracking_file) self.cache_file = Path(cache_file) self.data = { "playlists": {}, "songs": {} } self.cache = {} self._lock = threading.Lock() self._load() self._load_cache() def _load(self): if self.tracking_file.exists(): try: with open(self.tracking_file, 'r', encoding='utf-8') as f: self.data = json.load(f) except Exception: self.data = {"playlists": {}, "songs": {}} def _save(self): with self._lock: with open(self.tracking_file, 'w', encoding='utf-8') as f: json.dump(self.data, f, indent=2, ensure_ascii=False) def force_save(self): self._save() def _load_cache(self): if self.cache_file.exists(): try: with open(self.cache_file, 'r', encoding='utf-8') as f: self.cache = json.load(f) except Exception: self.cache = {} def save_cache(self): with open(self.cache_file, 'w', encoding='utf-8') as f: json.dump(self.cache, f, indent=2, ensure_ascii=False) def get_statistics(self): total_songs = len(self.data['songs']) downloaded_songs = sum(1 for s in self.data['songs'].values() if s['status'] in [SongStatus.DOWNLOADED, SongStatus.CONVERTED]) failed_songs = sum(1 for s in self.data['songs'].values() if s['status'] == SongStatus.FAILED) partial_songs = sum(1 for s in self.data['songs'].values() if s['status'] == SongStatus.PARTIAL) total_playlists = len(self.data['playlists']) total_size_mb = sum(s.get('file_size', 0) for s in self.data['songs'].values() if s.get('file_size')) / (1024*1024) last_updated = max((s.get('last_updated') for s in self.data['songs'].values() if s.get('last_updated')), default=None) return { "total_songs": total_songs, "downloaded_songs": downloaded_songs, "failed_songs": failed_songs, "partial_songs": partial_songs, "total_playlists": total_playlists, "total_size_mb": round(total_size_mb, 2), "last_updated": last_updated } def get_playlist_songs(self, playlist_id): return [s for s in self.data['songs'].values() if s['playlist_id'] == playlist_id] def get_failed_songs(self, playlist_id=None): if playlist_id: return [s for s in self.data['songs'].values() if s['playlist_id'] == playlist_id and s['status'] == SongStatus.FAILED] return [s for s in self.data['songs'].values() if s['status'] == SongStatus.FAILED] def get_partial_downloads(self, playlist_id=None): if playlist_id: return [s for s in self.data['songs'].values() if s['playlist_id'] == playlist_id and s['status'] == SongStatus.PARTIAL] return [s for s in self.data['songs'].values() if s['status'] == SongStatus.PARTIAL] def cleanup_orphaned_files(self, downloads_dir): # Remove tracking entries for files that no longer exist orphaned = [] for song_id, song in list(self.data['songs'].items()): file_path = song.get('file_path') if file_path and not Path(file_path).exists(): orphaned.append(song_id) del self.data['songs'][song_id] self.force_save() return orphaned def get_cache_info(self): total_channels = len(self.cache) total_cached_videos = sum(len(v) for v in self.cache.values()) cache_duration_hours = 24 # default last_updated = None return { "total_channels": total_channels, "total_cached_videos": total_cached_videos, "cache_duration_hours": cache_duration_hours, "last_updated": last_updated } def clear_channel_cache(self, channel_id=None): if channel_id is None or channel_id == 'all': self.cache = {} else: self.cache.pop(channel_id, None) self.save_cache() def set_cache_duration(self, hours): # Placeholder for cache duration logic pass def export_playlist_report(self, playlist_id): playlist = self.data['playlists'].get(playlist_id) if not playlist: return f"Playlist '{playlist_id}' not found." songs = self.get_playlist_songs(playlist_id) report = { "playlist": playlist, "songs": songs } return json.dumps(report, indent=2, ensure_ascii=False)