55import json
66import logging
77import requests
8- from typing import Any , List
8+ from typing import Any , List , Dict
99
1010
1111# External library
1919class ConfigManager :
2020 def __init__ (self , file_name : str = 'config.json' ) -> None :
2121 """
22- Initialize the ConfigManager.
22+ Initialize the ConfigManager with caching .
2323
2424 Args:
2525 file_name (str, optional): Configuration file name. Default: 'config.json'.
2626 """
27- # Determine the base path - use the current working directory
27+ self . base_path = None
2828 if getattr (sys , 'frozen' , False ):
29- # If the application is frozen (e.g., PyInstaller)
30- base_path = os .path .dirname (sys .executable )
31-
29+ self .base_path = os .path .dirname (sys .executable ) # PyInstaller
3230 else :
33- # Use the current working directory where the script is executed
34- base_path = os .getcwd ()
31+ self .base_path = os .getcwd ()
3532
3633 # Initialize file paths
37- self .file_path = os .path .join (base_path , file_name )
38- self .domains_path = os .path .join (base_path , 'domains.json' )
34+ self .file_path = os .path .join (self . base_path , file_name )
35+ self .domains_path = os .path .join (self . base_path , 'domains.json' )
3936
4037 # Display the actual file path for debugging
4138 console .print (f"[cyan]Config path: [green]{ self .file_path } " )
@@ -46,8 +43,11 @@ def __init__(self, file_name: str = 'config.json') -> None:
4643 # Initialize data structures
4744 self .config = {}
4845 self .configSite = {}
49- self .cache = {}
50-
46+
47+ # Enhanced caching system
48+ self .cache : Dict [str , Any ] = {}
49+ self ._cache_enabled = True
50+
5151 # Load the configuration
5252 self .fetch_domain_online = True
5353 self .load_config ()
@@ -64,6 +64,9 @@ def load_config(self) -> None:
6464 with open (self .file_path , 'r' ) as f :
6565 self .config = json .load (f )
6666
67+ # Pre-cache commonly used configuration values
68+ self ._precache_common_configs ()
69+
6770 # Update settings from the configuration
6871 self ._update_settings_from_config ()
6972
@@ -78,6 +81,41 @@ def load_config(self) -> None:
7881 console .print (f"[red]Error loading configuration: { str (e )} " )
7982 self ._handle_config_error ()
8083
84+ def _precache_common_configs (self ) -> None :
85+ common_keys = [
86+ ('DEFAULT' , 'debug' , bool ),
87+ ('M3U8_CONVERSION' , 'use_gpu' , bool ),
88+ ('M3U8_CONVERSION' , 'param_video' , str ),
89+ ('M3U8_CONVERSION' , 'param_audio' , str ),
90+ ('M3U8_CONVERSION' , 'param_final' , str ),
91+ ('M3U8_CONVERSION' , 'extension' , str ),
92+ ('M3U8_CONVERSION' , 'force_resolution' , bool ),
93+ ('M3U8_DOWNLOAD' , 'cleanup_tmp_folder' , bool ),
94+ ('M3U8_DOWNLOAD' , 'default_video_workers' , int ),
95+ ('M3U8_DOWNLOAD' , 'default_audio_workers' , int ),
96+ ('M3U8_DOWNLOAD' , 'segment_timeout' , int ),
97+ ('M3U8_DOWNLOAD' , 'enable_retry' , bool ),
98+ ('M3U8_DOWNLOAD' , 'merge_subs' , bool ),
99+ ('REQUESTS' , 'verify' , bool ),
100+ ('REQUESTS' , 'timeout' , int ),
101+ ('REQUESTS' , 'max_retry' , int ),
102+ ('OUT_FOLDER' , 'map_episode_name' , str )
103+ ]
104+
105+ cached_count = 0
106+ for section , key , data_type in common_keys :
107+ try :
108+ cache_key = f"config.{ section } .{ key } "
109+
110+ if section in self .config and key in self .config [section ]:
111+ value = self .config [section ][key ]
112+ converted_value = self ._convert_to_data_type (value , data_type )
113+ self .cache [cache_key ] = converted_value
114+ cached_count += 1
115+
116+ except Exception as e :
117+ logging .warning (f"Failed to precache { section } .{ key } : { e } " )
118+
81119 def _handle_config_error (self ) -> None :
82120 """Handle configuration errors by downloading the reference version."""
83121 console .print ("[yellow]Attempting to retrieve reference configuration..." )
@@ -87,8 +125,12 @@ def _handle_config_error(self) -> None:
87125 try :
88126 with open (self .file_path , 'r' ) as f :
89127 self .config = json .load (f )
128+
129+ # Pre-cache after reload
130+ self ._precache_common_configs ()
90131 self ._update_settings_from_config ()
91132 console .print ("[green]Reference configuration loaded successfully" )
133+
92134 except Exception as e :
93135 console .print (f"[red]Critical configuration error: { str (e )} " )
94136 console .print ("[red]Unable to proceed. The application will terminate." )
@@ -159,32 +201,20 @@ def _load_site_data_online(self) -> None:
159201
160202 def _save_domains_to_appropriate_location (self ) -> None :
161203 """Save domains to the appropriate location based on existing files."""
162- if getattr (sys , 'frozen' , False ):
163- # If the application is frozen (e.g., PyInstaller)
164- base_path = os .path .dirname (sys .executable )
165- else :
166- # Use the current working directory where the script is executed
167- base_path = os .getcwd ()
168-
169- # Check for GitHub structure first
170- github_domains_path = os .path .join (base_path , '.github' , '.domain' , 'domains.json' )
204+ github_domains_path = os .path .join (self .base_path , '.github' , '.domain' , 'domains.json' )
171205 console .print (f"[cyan]Domain path: [green]{ github_domains_path } " )
172206
173207 try :
174208 if os .path .exists (github_domains_path ):
175-
176- # Update existing GitHub structure file
177209 with open (github_domains_path , 'w' , encoding = 'utf-8' ) as f :
178210 json .dump (self .configSite , f , indent = 4 , ensure_ascii = False )
179211
180212 elif not os .path .exists (self .domains_path ):
181- # Save to root only if it doesn't exist and GitHub structure doesn't exist
182213 with open (self .domains_path , 'w' , encoding = 'utf-8' ) as f :
183214 json .dump (self .configSite , f , indent = 4 , ensure_ascii = False )
184215 console .print (f"[green]Domains saved to: { self .domains_path } " )
185216
186217 else :
187- # Root file exists, don't overwrite it
188218 console .print (f"[yellow]Local domains.json already exists, not overwriting: { self .domains_path } " )
189219 console .print ("[yellow]Tip: Delete the file if you want to recreate it from GitHub" )
190220
@@ -203,18 +233,7 @@ def _save_domains_to_appropriate_location(self) -> None:
203233 def _load_site_data_from_file (self ) -> None :
204234 """Load site data from local domains.json file."""
205235 try :
206- # Determine the base path
207- if getattr (sys , 'frozen' , False ):
208-
209- # If the application is frozen (e.g., PyInstaller)
210- base_path = os .path .dirname (sys .executable )
211- else :
212-
213- # Use the current working directory where the script is executed
214- base_path = os .getcwd ()
215-
216- # Check for GitHub structure first
217- github_domains_path = os .path .join (base_path , '.github' , '.domain' , 'domains.json' )
236+ github_domains_path = os .path .join (self .base_path , '.github' , '.domain' , 'domains.json' )
218237
219238 if os .path .exists (github_domains_path ):
220239 console .print (f"[cyan]Domain path: [green]{ github_domains_path } " )
@@ -243,17 +262,7 @@ def _load_site_data_from_file(self) -> None:
243262
244263 def _handle_site_data_fallback (self ) -> None :
245264 """Handle site data fallback in case of error."""
246- # Determine the base path
247- if getattr (sys , 'frozen' , False ):
248-
249- # If the application is frozen (e.g., PyInstaller)
250- base_path = os .path .dirname (sys .executable )
251- else :
252- # Use the current working directory where the script is executed
253- base_path = os .getcwd ()
254-
255- # Check for GitHub structure first
256- github_domains_path = os .path .join (base_path , '.github' , '.domain' , 'domains.json' )
265+ github_domains_path = os .path .join (self .base_path , '.github' , '.domain' , 'domains.json' )
257266
258267 if os .path .exists (github_domains_path ):
259268 console .print ("[yellow]Attempting fallback to GitHub structure domains.json file..." )
@@ -278,9 +287,45 @@ def _handle_site_data_fallback(self) -> None:
278287 console .print ("[red]No local domains.json file available for fallback" )
279288 self .configSite = {}
280289
290+ def clear_cache (self ) -> None :
291+ """Clear the entire configuration cache."""
292+ self .cache .clear ()
293+ console .print ("[green]Configuration cache cleared" )
294+
295+ def invalidate_cache_key (self , section : str , key : str , from_site : bool = False ) -> None :
296+ """
297+ Invalidate a specific cache entry.
298+
299+ Args:
300+ section (str): Section in the configuration
301+ key (str): Key to invalidate
302+ from_site (bool, optional): Whether to invalidate from site config. Default: False
303+ """
304+ cache_key = f"{ 'site' if from_site else 'config' } .{ section } .{ key } "
305+ if cache_key in self .cache :
306+ del self .cache [cache_key ]
307+ logging .info (f"Cache invalidated for key: { cache_key } " )
308+
309+ def get_cache_stats (self ) -> Dict [str , int ]:
310+ """
311+ Get cache statistics.
312+
313+ Returns:
314+ Dict with cache size and memory usage estimate
315+ """
316+ import sys
317+ cache_size = len (self .cache )
318+ memory_bytes = sum (sys .getsizeof (k ) + sys .getsizeof (v ) for k , v in self .cache .items ())
319+
320+ return {
321+ 'entries' : cache_size ,
322+ 'memory_bytes' : memory_bytes ,
323+ 'memory_kb' : round (memory_bytes / 1024 , 2 )
324+ }
325+
281326 def get (self , section : str , key : str , data_type : type = str , from_site : bool = False , default : Any = None ) -> Any :
282327 """
283- Read a value from the configuration.
328+ Read a value from the configuration with caching .
284329
285330 Args:
286331 section (str): Section in the configuration
@@ -293,12 +338,14 @@ def get(self, section: str, key: str, data_type: type = str, from_site: bool = F
293338 Any: The key value converted to the specified data type, or default if not found
294339 """
295340 cache_key = f"{ 'site' if from_site else 'config' } .{ section } .{ key } "
296- logging .info (f"Reading key: { cache_key } " )
297341
298342 # Check if the value is in the cache
299- if cache_key in self .cache :
343+ if self . _cache_enabled and cache_key in self .cache :
300344 return self .cache [cache_key ]
301345
346+ # Log only if not in cache
347+ logging .info (f"Reading key: { cache_key } " )
348+
302349 # Choose the appropriate source
303350 config_source = self .configSite if from_site else self .config
304351
@@ -320,7 +367,8 @@ def get(self, section: str, key: str, data_type: type = str, from_site: bool = F
320367 converted_value = self ._convert_to_data_type (value , data_type )
321368
322369 # Save in cache
323- self .cache [cache_key ] = converted_value
370+ if self ._cache_enabled :
371+ self .cache [cache_key ] = converted_value
324372
325373 return converted_value
326374
@@ -361,6 +409,7 @@ def _convert_to_data_type(self, value: Any, data_type: type) -> Any:
361409 raise ValueError (f"Cannot convert { type (value ).__name__ } to dict" )
362410 else :
363411 return value
412+
364413 except Exception as e :
365414 logging .error (f"Error converting: { data_type .__name__ } to value '{ value } ' with error: { e } " )
366415 raise ValueError (f"Error converting: { data_type .__name__ } to value '{ value } ' with error: { e } " )
@@ -393,7 +442,7 @@ def get_site(self, section: str, key: str) -> Any:
393442
394443 def set_key (self , section : str , key : str , value : Any , to_site : bool = False ) -> None :
395444 """
396- Set a key in the configuration.
445+ Set a key in the configuration and update cache .
397446
398447 Args:
399448 section (str): Section in the configuration
0 commit comments