Skip to content

Commit 2286f0f

Browse files
committed
ConfigManager with caching
1 parent a409d5d commit 2286f0f

File tree

2 files changed

+104
-54
lines changed

2 files changed

+104
-54
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ active_requests.json
5555
working_proxies.json
5656
start.sh
5757
.DS_Store
58-
GUI/db.sqlite3
58+
GUI/db.sqlite3
59+
console.log

StreamingCommunity/Util/config_json.py

Lines changed: 102 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import json
66
import logging
77
import requests
8-
from typing import Any, List
8+
from typing import Any, List, Dict
99

1010

1111
# External library
@@ -19,23 +19,20 @@
1919
class 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

Comments
 (0)