diff --git a/StreamingCommunity/Upload/version.py b/StreamingCommunity/Upload/version.py index 5cb064d..c245a82 100644 --- a/StreamingCommunity/Upload/version.py +++ b/StreamingCommunity/Upload/version.py @@ -1,5 +1,5 @@ __title__ = 'StreamingCommunity' -__version__ = '2.9.7' +__version__ = '2.9.8' __author__ = 'Arrowar' __description__ = 'A command-line program to download film' __copyright__ = 'Copyright 2024' diff --git a/StreamingCommunity/Util/config_json.py b/StreamingCommunity/Util/config_json.py index e770fd5..cf78b03 100644 --- a/StreamingCommunity/Util/config_json.py +++ b/StreamingCommunity/Util/config_json.py @@ -20,110 +20,183 @@ validate_github_config = True class ConfigManager: def __init__(self, file_name: str = 'config.json') -> None: - """Initialize the ConfigManager. - - Parameters: - - file_name (str, optional): The name of the configuration file. Default is 'config.json'. """ + Initialize the ConfigManager. + + Args: + file_name (str, optional): Configuration file name. Default: 'config.json'. + """ + # Determine the base path - use the current working directory if getattr(sys, 'frozen', False): - base_path = os.path.join(".") + # If the application is frozen (e.g., PyInstaller) + base_path = os.path.dirname(sys.executable) + else: - base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) + # Use the current directory where the script is executed + base_path = os.getcwd() + # Initialize file paths self.file_path = os.path.join(base_path, file_name) self.domains_path = os.path.join(base_path, 'domains.json') + + # Display the actual file path for debugging + console.print(f"[bold cyan]Configuration file path:[/bold cyan] [green]{self.file_path}[/green]") + + # Reference repository URL + self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json' + + # Initialize data structures self.config = {} self.configSite = {} self.cache = {} - self.reference_config_url = 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/config.json' - - # Read initial config to get use_api setting - self._read_initial_config() - - # Validate and update config before proceeding (if enabled) - if validate_github_config: - self._validate_and_update_config() - - console.print(f"[bold cyan]ConfigManager initialized:[/bold cyan] [green]{self.file_path}[/green]") - def _read_initial_config(self) -> None: - """Read initial configuration to get use_api setting.""" + self.use_api = False + self.download_site_data = False + self.validate_github_config = False + + console.print(f"[bold cyan]Initializing ConfigManager:[/bold cyan] [green]{self.file_path}[/green]") + + # Load the configuration + self.load_config() + + def load_config(self) -> None: + """Load the configuration and initialize all settings.""" + if not os.path.exists(self.file_path): + console.print(f"[bold red]WARNING: Configuration file not found:[/bold red] {self.file_path}") + console.print(f"[bold yellow]Attempting to download from reference repository...[/bold yellow]") + self._download_reference_config() + + # Load the configuration file try: - if os.path.exists(self.file_path): - with open(self.file_path, 'r') as f: - self.config = json.load(f) - - self.use_api = self.config.get('DEFAULT', {}).get('use_api', True) - console.print(f"[bold cyan]API usage setting:[/bold cyan] [{'green' if self.use_api else 'yellow'}]{self.use_api}[/{'green' if self.use_api else 'yellow'}]") - console.print(f"[bold cyan]Download site data:[/bold cyan] [{'green' if download_site_data else 'yellow'}]{download_site_data}[/{'green' if download_site_data else 'yellow'}]") - console.print(f"[bold cyan]Validate GitHub config:[/bold cyan] [{'green' if validate_github_config else 'yellow'}]{validate_github_config}[/{'green' if validate_github_config else 'yellow'}]") - + with open(self.file_path, 'r') as f: + self.config = json.load(f) + console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys") + + # Update settings from the configuration + self._update_settings_from_config() + + # Validate and update the configuration if requested + if self.validate_github_config: + self._validate_and_update_config() else: - self.use_api = True - console.print("[bold yellow]Configuration file not found. Using default API setting: True[/bold yellow]") - console.print(f"[bold yellow]Download site data: {download_site_data}[/bold yellow]") - console.print(f"[bold yellow]Validate GitHub config: {validate_github_config}[/bold yellow]") + console.print("[bold yellow]GitHub validation disabled[/bold yellow]") + + # Load site data if requested + if self.download_site_data: + self._load_site_data() + else: + console.print("[bold yellow]Site data download disabled[/bold yellow]") + + except json.JSONDecodeError as e: + console.print(f"[bold red]Error parsing JSON:[/bold red] {str(e)}") + self._handle_config_error() except Exception as e: - self.use_api = True - console.print("[bold red]Error reading API setting. Using default: True[/bold red]") - - def _validate_and_update_config(self) -> None: - """Validate local config against reference config and update missing keys.""" + console.print(f"[bold red]Error loading configuration:[/bold red] {str(e)}") + self._handle_config_error() + + def _handle_config_error(self) -> None: + """Handle configuration errors by downloading the reference version.""" + console.print("[bold yellow]Attempting to retrieve reference configuration...[/bold yellow]") + self._download_reference_config() + + # Reload the configuration try: - # Load local config if exists - local_config = {} - if os.path.exists(self.file_path): - with open(self.file_path, 'r') as f: - local_config = json.load(f) - console.print(f"[bold cyan]Local configuration loaded:[/bold cyan] [green]{len(local_config)} keys found[/green]") + with open(self.file_path, 'r') as f: + self.config = json.load(f) + self._update_settings_from_config() + console.print("[bold green]Reference configuration loaded successfully[/bold green]") + except Exception as e: + console.print(f"[bold red]Critical configuration error:[/bold red] {str(e)}") + console.print("[bold red]Unable to proceed. The application will terminate.[/bold red]") + sys.exit(1) + + def _update_settings_from_config(self) -> None: + """Update internal settings from loaded configurations.""" + default_section = self.config.get('DEFAULT', {}) + + # Save local values in temporary variables + temp_use_api = default_section.get('use_api', False) + temp_download_site_data = default_section.get('download_site_data', False) + temp_validate_github_config = default_section.get('validate_github_config', False) + + # Update settings with found values (False by default) + self.use_api = temp_use_api + self.download_site_data = temp_download_site_data + self.validate_github_config = temp_validate_github_config + + console.print(f"[bold cyan]API Usage:[/bold cyan] [{'green' if self.use_api else 'yellow'}]{self.use_api}[/{'green' if self.use_api else 'yellow'}]") + console.print(f"[bold cyan]Site data download:[/bold cyan] [{'green' if self.download_site_data else 'yellow'}]{self.download_site_data}[/{'green' if self.download_site_data else 'yellow'}]") + console.print(f"[bold cyan]GitHub configuration validation:[/bold cyan] [{'green' if self.validate_github_config else 'yellow'}]{self.validate_github_config}[/{'green' if self.validate_github_config else 'yellow'}]") + + def _download_reference_config(self) -> None: + """Download the reference configuration from GitHub.""" + console.print(f"[bold cyan]Downloading reference configuration:[/bold cyan] [green]{self.reference_config_url}[/green]") - # Download reference config - console.print(f"[bold cyan]Downloading reference config from:[/bold cyan] [green]{self.reference_config_url}[/green]") + try: response = requests.get(self.reference_config_url, timeout=10) + + if response.status_code == 200: + with open(self.file_path, 'wb') as f: + f.write(response.content) + file_size = len(response.content) / 1024 + console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(self.file_path)} ({file_size:.2f} KB)") + else: + error_msg = f"HTTP Error: {response.status_code}, Response: {response.text[:100]}" + console.print(f"[bold red]Download failed:[/bold red] {error_msg}") + raise Exception(error_msg) + + except Exception as e: + console.print(f"[bold red]Download error:[/bold red] {str(e)}") + raise + + def _validate_and_update_config(self) -> None: + """Validate the local configuration against the reference one and update missing keys.""" + try: + # Download the reference configuration + console.print(f"[bold cyan]Validating configuration with GitHub...[/bold cyan]") + response = requests.get(self.reference_config_url, timeout=10) + if not response.ok: - raise Exception(f"Failed to download reference config. Status code: {response.status_code}") + raise Exception(f"Error downloading reference configuration. Code: {response.status_code}") reference_config = response.json() - console.print(f"[bold cyan]Reference config downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]") - - # Compare and update missing keys - merged_config = self._deep_merge_configs(local_config, reference_config) + console.print(f"[bold cyan]Reference configuration downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]") - if merged_config != local_config: - added_keys = self._get_added_keys(local_config, merged_config) - - # Save the merged config + # Compare and update missing keys + merged_config = self._deep_merge_configs(self.config, reference_config) + + if merged_config != self.config: + added_keys = self._get_added_keys(self.config, merged_config) + + # Save the merged configuration with open(self.file_path, 'w') as f: json.dump(merged_config, f, indent=4) - console.print(f"[bold green]Configuration updated with {len(added_keys)} new keys:[/bold green] {', '.join(added_keys[:5])}{' and more...' if len(added_keys) > 5 else ''}") - + + key_examples = ', '.join(added_keys[:5]) + if len(added_keys) > 5: + key_examples += ' and others...' + + console.print(f"[bold green]Configuration updated with {len(added_keys)} new keys:[/bold green] {key_examples}") + + # Update the configuration in memory + self.config = merged_config + self._update_settings_from_config() else: - console.print("[bold green]Configuration is up to date.[/bold green]") - - self.config = merged_config - + console.print("[bold green]The configuration is up to date.[/bold green]") + except Exception as e: - console.print(f"[bold red]Configuration validation error:[/bold red] {str(e)}") - - if not self.config: - console.print("[bold yellow]Falling back to reference configuration...[/bold yellow]") - self.download_requirements(self.reference_config_url, self.file_path) - - with open(self.file_path, 'r') as f: - self.config = json.load(f) - - console.print(f"[bold green]Reference config loaded successfully with {len(self.config)} keys[/bold green]") - + console.print(f"[bold red]Error validating configuration:[/bold red] {str(e)}") + def _get_added_keys(self, old_config: dict, new_config: dict, prefix="") -> list: """ - Get list of keys added in the new config compared to old config. + Get the list of keys added in the new configuration compared to the old one. Args: - old_config (dict): The original configuration - new_config (dict): The new configuration - prefix (str): Key prefix for nested keys + old_config (dict): Original configuration + new_config (dict): New configuration + prefix (str): Prefix for nested keys Returns: list: List of added key names @@ -139,14 +212,14 @@ class ConfigManager: added_keys.extend(self._get_added_keys(old_config[key], value, full_key)) return added_keys - + def _deep_merge_configs(self, local_config: dict, reference_config: dict) -> dict: """ - Recursively merge reference config into local config, preserving local values. + Recursively merge the reference configuration into the local one, preserving local values. Args: - local_config (dict): The local configuration - reference_config (dict): The reference configuration + local_config (dict): Local configuration + reference_config (dict): Reference configuration Returns: dict: Merged configuration @@ -155,272 +228,350 @@ class ConfigManager: for key, value in reference_config.items(): if key not in merged: + + # Create the key if it doesn't exist merged[key] = value elif isinstance(value, dict) and isinstance(merged[key], dict): - merged[key] = self._deep_merge_configs(merged[key], value) + + # Handle the DEFAULT section specially + if key == 'DEFAULT': + + # Make sure control keys maintain local values + merged_section = self._deep_merge_configs(merged[key], value) + + # Preserve local values for the three critical settings + if 'use_api' in merged[key]: + merged_section['use_api'] = merged[key]['use_api'] + if 'download_site_data' in merged[key]: + merged_section['download_site_data'] = merged[key]['download_site_data'] + if 'validate_github_config' in merged[key]: + merged_section['validate_github_config'] = merged[key]['validate_github_config'] + + merged[key] = merged_section + else: + + # Normal merge for other sections + merged[key] = self._deep_merge_configs(merged[key], value) return merged - - def read_config(self) -> None: - """Read the configuration file.""" + + def _load_site_data(self) -> None: + """Load site data from API or local file.""" + if self.use_api: + self._load_site_data_from_api() + else: + self._load_site_data_from_file() + + def _load_site_data_from_api(self) -> None: + """Load site data from API.""" + headers = { + "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", + "Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", + "Content-Type": "application/json" + } + try: - logging.info(f"Reading file: {self.file_path}") - - # Check if file exists - if os.path.exists(self.file_path): - with open(self.file_path, 'r') as f: - self.config = json.load(f) - console.print(f"[bold green]Configuration loaded:[/bold green] {len(self.config)} keys, {sum(1 for _ in json.dumps(self.config))} bytes") - + console.print("[bold cyan]Retrieving site data from API...[/bold cyan]") + response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10) + + if response.ok: + data = response.json() + if data and len(data) > 0: + self.configSite = data[0]['data'] + + site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0 + console.print(f"[bold green]Site data retrieved:[/bold green] {site_count} streaming services available") + + # Show some sites as examples + if site_count > 0: + examples = list(self.configSite.items())[:3] + sites_info = [] + for site, info in examples: + url = info.get('full_url', 'N/A') + console.print(f" • [cyan]{site}[/cyan]: {url}") + else: + console.print("[bold yellow]API returned an empty data set[/bold yellow]") else: - console.print(f"[bold yellow]Configuration file not found at:[/bold yellow] {self.file_path}") - console.print(f"[bold cyan]Downloading from:[/bold cyan] {self.reference_config_url}") - self.download_requirements(self.reference_config_url, self.file_path) - - # Load the downloaded config.json into the config attribute - with open(self.file_path, 'r') as f: - self.config = json.load(f) - console.print(f"[bold green]Configuration downloaded and saved:[/bold green] {len(self.config)} keys") - - # Read API setting again in case it was updated in the downloaded config - self.use_api = self.config.get('DEFAULT', {}).get('use_api', self.use_api) - - # Update site configuration separately if enabled - if download_site_data: - self.update_site_config() - else: - console.print("[bold yellow]Site data download is disabled[/bold yellow]") - - console.print("[bold cyan]Configuration processing complete[/bold cyan]") - + console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}") + self._handle_site_data_fallback() + except Exception as e: - logging.error(f"Error reading configuration file: {e}") - console.print(f"[bold red]Failed to read configuration:[/bold red] {str(e)}") - sys.exit(0) + console.print(f"[bold red]API connection error:[/bold red] {str(e)}") + self._handle_site_data_fallback() + + def _load_site_data_from_file(self) -> None: + """Load site data from local file.""" + try: + if os.path.exists(self.domains_path): + console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}") + with open(self.domains_path, 'r') as f: + self.configSite = json.load(f) + + site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0 + console.print(f"[bold green]Site data loaded from file:[/bold green] {site_count} streaming services") - def download_requirements(self, url: str, filename: str) -> None: + else: + error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled" + console.print(f"[bold red]Configuration error:[/bold red] {error_msg}") + self._handle_site_data_fallback() + + except Exception as e: + console.print(f"[bold red]Domain file error:[/bold red] {str(e)}") + self._handle_site_data_fallback() + + def _handle_site_data_fallback(self) -> None: + """Handle site data fallback in case of error.""" + if self.use_api and os.path.exists(self.domains_path): + console.print("[bold yellow]Attempting fallback to local domains.json file...[/bold yellow]") + + try: + with open(self.domains_path, 'r') as f: + self.configSite = json.load(f) + console.print("[bold green]Fallback to local data successful[/bold green]") + except Exception as fallback_error: + console.print(f"[bold red]Fallback also failed:[/bold red] {str(fallback_error)}") + self.configSite = {} + else: + + # Initialize with an empty dictionary if there are no alternatives + self.configSite = {} + + def download_file(self, url: str, filename: str) -> None: """ - Download a file from the specified URL if not found locally using requests. - + Download a file from the specified URL. + Args: - url (str): The URL to download the file from. - filename (str): The local filename to save the file as. + url (str): URL to download from + filename (str): Local filename to save to """ try: logging.info(f"Downloading {filename} from {url}...") - console.print(f"[bold cyan]Downloading file:[/bold cyan] {os.path.basename(filename)}") + console.print(f"[bold cyan]File download:[/bold cyan] {os.path.basename(filename)}") response = requests.get(url, timeout=10) - + if response.status_code == 200: with open(filename, 'wb') as f: f.write(response.content) file_size = len(response.content) / 1024 - console.print(f"[bold green]Download successful:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)") - + console.print(f"[bold green]Download complete:[/bold green] {os.path.basename(filename)} ({file_size:.2f} KB)") + else: error_msg = f"HTTP Status: {response.status_code}, Response: {response.text[:100]}" console.print(f"[bold red]Download failed:[/bold red] {error_msg}") - logging.error(f"Failed to download {filename}. {error_msg}") - sys.exit(0) - + logging.error(f"Download of {filename} failed. {error_msg}") + raise Exception(error_msg) + except Exception as e: console.print(f"[bold red]Download error:[/bold red] {str(e)}") - logging.error(f"Failed to download {filename}: {e}") - sys.exit(0) - - def update_site_config(self) -> None: - """Fetch and update the site configuration with data from the API or local file.""" - if self.use_api: - headers = { - "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", - "Authorization": f"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE", - "Content-Type": "application/json" - } - - try: - console.print("[bold cyan]Fetching site data from API...[/bold cyan]") - response = requests.get("https://zvfngpoxwrgswnzytadh.supabase.co/rest/v1/public", headers=headers, timeout=10) - - if response.ok: - data = response.json() - if data and len(data) > 0: - self.configSite = data[0]['data'] - - # Display available sites and their domains - site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0 - console.print(f"[bold green]Site data fetched:[/bold green] {site_count} streaming services available") - - # List a few sites as examples - if site_count > 0: - examples = list(self.configSite.items())[:3] - sites_info = [] - for site, info in examples: - sites_info.append(f"[cyan]{site}[/cyan]: {info.get('full_url', 'N/A')}") - - else: - console.print("[bold yellow]API returned empty data set[/bold yellow]") - else: - console.print(f"[bold red]API request failed:[/bold red] HTTP {response.status_code}, {response.text[:100]}") - - except Exception as e: - console.print(f"[bold red]API connection error:[/bold red] {str(e)}") - else: - try: - if os.path.exists(self.domains_path): - console.print(f"[bold cyan]Reading domains from:[/bold cyan] {self.domains_path}") - with open(self.domains_path, 'r') as f: - self.configSite = json.load(f) - - else: - error_msg = f"domains.json not found at {self.domains_path} and API usage is disabled" - console.print(f"[bold red]Configuration error:[/bold red] {error_msg}") - raise FileNotFoundError(error_msg) - - except Exception as e: - console.print(f"[bold red]Domains file error:[/bold red] {str(e)}") - raise + logging.error(f"Download of {filename} failed: {e}") + raise + + def get(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any: + """ + Read a value from the configuration. + + Args: + section (str): Section in the configuration + key (str): Key to read + data_type (type, optional): Expected data type. Default: str + from_site (bool, optional): Whether to read from the site configuration. Default: False - def read_key(self, section: str, key: str, data_type: type = str, from_site: bool = False) -> Any: - """Read a key from the configuration. - - Parameters: - - section (str): The section in the configuration. - - key (str): The key to be read. - - data_type (type, optional): The expected data type of the key's value. Default is str. - - from_site (bool, optional): Whether to read from site config. Default is False. - Returns: - The value of the key converted to the specified data type. + Any: The key value converted to the specified data type """ cache_key = f"{'site' if from_site else 'config'}.{section}.{key}" - logging.info(f"Read key: {cache_key}") - + logging.info(f"Reading key: {cache_key}") + + # Check if the value is in the cache if cache_key in self.cache: return self.cache[cache_key] - + + # Choose the appropriate source config_source = self.configSite if from_site else self.config - if section in config_source and key in config_source[section]: - value = config_source[section][key] - else: - raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} config") - - value = self._convert_to_data_type(value, data_type) - self.cache[cache_key] = value - - return value - - def _convert_to_data_type(self, value: str, data_type: type) -> Any: - """Convert the value to the specified data type. - - Parameters: - - value (str): The value to be converted. - - data_type (type): The expected data type. - - Returns: - The value converted to the specified data type. + # Check if the section and key exist + if section not in config_source: + raise ValueError(f"Section '{section}' not found in {'site' if from_site else 'main'} configuration") + + if key not in config_source[section]: + raise ValueError(f"Key '{key}' not found in section '{section}' of {'site' if from_site else 'main'} configuration") + + # Get and convert the value + value = config_source[section][key] + converted_value = self._convert_to_data_type(value, data_type) + + # Save in cache + self.cache[cache_key] = converted_value + + return converted_value + + def _convert_to_data_type(self, value: Any, data_type: type) -> Any: """ - if data_type == int: - return int(value) - elif data_type == bool: - return bool(value) - elif data_type == list: - return value if isinstance(value, list) else [item.strip() for item in value.split(',')] - elif data_type == type(None): - return None - else: - return value - - # Main config getters - def get(self, section: str, key: str) -> Any: - """Read a value from the main configuration.""" - return self.read_key(section, key) - + Convert the value to the specified data type. + + Args: + value (Any): Value to convert + data_type (type): Target data type + + Returns: + Any: Converted value + """ + try: + if data_type == int: + return int(value) + elif data_type == float: + return float(value) + elif data_type == bool: + if isinstance(value, str): + return value.lower() in ("yes", "true", "t", "1") + return bool(value) + elif data_type == list: + if isinstance(value, list): + return value + if isinstance(value, str): + return [item.strip() for item in value.split(',')] + return [value] + elif data_type == dict: + if isinstance(value, dict): + return value + raise ValueError(f"Cannot convert {type(value).__name__} to dict") + else: + return value + except Exception as e: + logging.error(f"Error converting to {data_type.__name__}: {e}") + raise ValueError(f"Cannot convert '{value}' to {data_type.__name__}: {str(e)}") + + # Getters for main configuration + def get_string(self, section: str, key: str) -> str: + """Read a string from the main configuration.""" + return self.get(section, key, str) + def get_int(self, section: str, key: str) -> int: - """Read an integer value from the main configuration.""" - return self.read_key(section, key, int) - + """Read an integer from the main configuration.""" + return self.get(section, key, int) + def get_float(self, section: str, key: str) -> float: - """Read a float value from the main configuration.""" - return self.read_key(section, key, float) - + """Read a float from the main configuration.""" + return self.get(section, key, float) + def get_bool(self, section: str, key: str) -> bool: - """Read a boolean value from the main configuration.""" - return self.read_key(section, key, bool) - + """Read a boolean from the main configuration.""" + return self.get(section, key, bool) + def get_list(self, section: str, key: str) -> List[str]: - """Read a list value from the main configuration.""" - return self.read_key(section, key, list) - + """Read a list from the main configuration.""" + return self.get(section, key, list) + def get_dict(self, section: str, key: str) -> dict: - """Read a dictionary value from the main configuration.""" - return self.read_key(section, key, dict) - - # Site config getters + """Read a dictionary from the main configuration.""" + return self.get(section, key, dict) + + # Getters for site configuration def get_site(self, section: str, key: str) -> Any: """Read a value from the site configuration.""" - return self.read_key(section, key, from_site=True) - + return self.get(section, key, str, True) + + def get_site_string(self, section: str, key: str) -> str: + """Read a string from the site configuration.""" + return self.get(section, key, str, True) + def get_site_int(self, section: str, key: str) -> int: - """Read an integer value from the site configuration.""" - return self.read_key(section, key, int, from_site=True) - + """Read an integer from the site configuration.""" + return self.get(section, key, int, True) + def get_site_float(self, section: str, key: str) -> float: - """Read a float value from the site configuration.""" - return self.read_key(section, key, float, from_site=True) - + """Read a float from the site configuration.""" + return self.get(section, key, float, True) + def get_site_bool(self, section: str, key: str) -> bool: - """Read a boolean value from the site configuration.""" - return self.read_key(section, key, bool, from_site=True) - + """Read a boolean from the site configuration.""" + return self.get(section, key, bool, True) + def get_site_list(self, section: str, key: str) -> List[str]: - """Read a list value from the site configuration.""" - return self.read_key(section, key, list, from_site=True) - + """Read a list from the site configuration.""" + return self.get(section, key, list, True) + def get_site_dict(self, section: str, key: str) -> dict: - """Read a dictionary value from the site configuration.""" - return self.read_key(section, key, dict, from_site=True) - + """Read a dictionary from the site configuration.""" + return self.get(section, key, dict, True) + def set_key(self, section: str, key: str, value: Any, to_site: bool = False) -> None: - """Set a key in the configuration. - - Parameters: - - section (str): The section in the configuration. - - key (str): The key to be set. - - value (Any): The value to be associated with the key. - - to_site (bool, optional): Whether to set in site config. Default is False. + """ + Set a key in the configuration. + + Args: + section (str): Section in the configuration + key (str): Key to set + value (Any): Value to associate with the key + to_site (bool, optional): Whether to set in the site configuration. Default: False """ try: config_target = self.configSite if to_site else self.config if section not in config_target: config_target[section] = {} - + config_target[section][key] = value + + # Update the cache cache_key = f"{'site' if to_site else 'config'}.{section}.{key}" self.cache[cache_key] = value - + + logging.info(f"Key '{key}' set in section '{section}' of {'site' if to_site else 'main'} configuration") + except Exception as e: - print(f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} config: {e}") - - def write_config(self) -> None: - """Write the main configuration to the file.""" + error_msg = f"Error setting key '{key}' in section '{section}' of {'site' if to_site else 'main'} configuration: {e}" + logging.error(error_msg) + console.print(f"[bold red]{error_msg}[/bold red]") + + def save_config(self) -> None: + """Save the main configuration to file.""" try: with open(self.file_path, 'w') as f: json.dump(self.config, f, indent=4) + + logging.info(f"Configuration saved to: {self.file_path}") + except Exception as e: - print(f"Error writing configuration file: {e}") + error_msg = f"Error saving configuration: {e}" + console.print(f"[bold red]{error_msg}[/bold red]") + logging.error(error_msg) + + def get_all_sites(self) -> List[str]: + """ + Get the list of all available sites. + + Returns: + List[str]: List of site names + """ + return list(self.configSite.keys()) + + def has_section(self, section: str, in_site: bool = False) -> bool: + """ + Check if a section exists in the configuration. + + Args: + section (str): Section name + in_site (bool, optional): Whether to check in the site configuration. Default: False + + Returns: + bool: True if the section exists, False otherwise + """ + config_source = self.configSite if in_site else self.config + return section in config_source -config_manager = ConfigManager() -config_manager.read_config() - +# Helper function to check the platform def get_use_large_bar(): """ - Determines whether the large bar feature should be enabled. - + Determine if the large bar feature should be enabled. + Returns: - bool: True if running on a PC (Windows, macOS, Linux), + bool: True if running on PC (Windows, macOS, Linux), False if running on Android or iOS. """ - return not any(platform in sys.platform for platform in ("android", "ios")) \ No newline at end of file + return not any(platform in sys.platform for platform in ("android", "ios")) + + +# Initialize the ConfigManager when the module is imported +config_manager = ConfigManager() \ No newline at end of file diff --git a/StreamingCommunity/run.py b/StreamingCommunity/run.py index fb7e69d..bcfa757 100644 --- a/StreamingCommunity/run.py +++ b/StreamingCommunity/run.py @@ -296,7 +296,7 @@ def main(script_id = 0): section, option = key.split('.') config_manager.set_key(section, option, value) - config_manager.write_config() + config_manager.save_config() # Check if global search is requested if getattr(args, 'global'): diff --git a/setup.py b/setup.py index 448be1e..4eab08a 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ with open(os.path.join(os.path.dirname(__file__), "requirements.txt"), "r", enco setup( name="StreamingCommunity", - version="2.9.7", + version="2.9.8", long_description=read_readme(), long_description_content_type="text/markdown", author="Lovi-0",