mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-12 05:42:59 +00:00
attempting cache improvements to see if that is affecting ability to see recent odds updates
This commit is contained in:
@@ -23,60 +23,61 @@ class CacheManager:
|
|||||||
# Initialize logger first
|
# Initialize logger first
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Get the actual user's home directory, even when running with sudo
|
# Determine the most reliable writable directory
|
||||||
try:
|
self.cache_dir = self._get_writable_cache_dir()
|
||||||
# Try to get the real user's home directory
|
if self.cache_dir:
|
||||||
real_user = os.environ.get('SUDO_USER') or os.environ.get('USER')
|
self.logger.info(f"Using cache directory: {self.cache_dir}")
|
||||||
if real_user:
|
|
||||||
home_dir = f"/home/{real_user}"
|
|
||||||
else:
|
|
||||||
home_dir = os.path.expanduser('~')
|
|
||||||
except Exception:
|
|
||||||
home_dir = os.path.expanduser('~')
|
|
||||||
|
|
||||||
# Determine the appropriate cache directory
|
|
||||||
if os.geteuid() == 0: # Running as root/sudo
|
|
||||||
self.cache_dir = "/var/cache/ledmatrix"
|
|
||||||
else:
|
else:
|
||||||
self.cache_dir = os.path.join(home_dir, '.ledmatrix_cache')
|
# This is a critical failure, as caching is essential.
|
||||||
|
self.logger.error("Could not find or create a writable cache directory. Caching will be disabled.")
|
||||||
|
self.cache_dir = None
|
||||||
|
|
||||||
self._memory_cache = {} # In-memory cache for faster access
|
self._memory_cache = {} # In-memory cache for faster access
|
||||||
self._memory_cache_timestamps = {}
|
self._memory_cache_timestamps = {}
|
||||||
self._cache_lock = threading.Lock()
|
self._cache_lock = threading.Lock()
|
||||||
|
|
||||||
# Ensure cache directory exists after logger is initialized
|
def _get_writable_cache_dir(self) -> Optional[str]:
|
||||||
self._ensure_cache_dir()
|
"""Tries to find or create a writable cache directory in a few common locations."""
|
||||||
|
# Attempt 1: User's home directory (handling sudo)
|
||||||
def _ensure_cache_dir(self):
|
|
||||||
"""Ensure the cache directory exists with proper permissions."""
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.cache_dir, exist_ok=True)
|
real_user = os.environ.get('SUDO_USER') or os.environ.get('USER', 'default')
|
||||||
# Set permissions to allow both root and the user to access
|
if real_user and real_user != 'root':
|
||||||
if os.geteuid() == 0: # Running as root/sudo
|
home_dir = os.path.expanduser(f"~{real_user}")
|
||||||
os.chmod(self.cache_dir, 0o777) # Full permissions for all users
|
else:
|
||||||
# Also set ownership to the real user if we're running as root
|
home_dir = os.path.expanduser('~')
|
||||||
real_user = os.environ.get('SUDO_USER')
|
|
||||||
if real_user:
|
|
||||||
try:
|
|
||||||
import pwd
|
|
||||||
uid = pwd.getpwnam(real_user).pw_uid
|
|
||||||
gid = pwd.getpwnam(real_user).pw_gid
|
|
||||||
os.chown(self.cache_dir, uid, gid)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not set cache directory ownership: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to create cache directory: {e}")
|
|
||||||
# Fallback to temp directory if we can't create the cache directory
|
|
||||||
self.cache_dir = os.path.join(tempfile.gettempdir(), 'ledmatrix_cache')
|
|
||||||
try:
|
|
||||||
os.makedirs(self.cache_dir, exist_ok=True)
|
|
||||||
self.logger.info(f"Using fallback cache directory: {self.cache_dir}")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to create fallback cache directory: {e}")
|
|
||||||
raise # Re-raise if we can't create any cache directory
|
|
||||||
|
|
||||||
def _get_cache_path(self, key: str) -> str:
|
user_cache_dir = os.path.join(home_dir, '.ledmatrix_cache')
|
||||||
|
os.makedirs(user_cache_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Test writability
|
||||||
|
test_file = os.path.join(user_cache_dir, '.writetest')
|
||||||
|
with open(test_file, 'w') as f:
|
||||||
|
f.write('test')
|
||||||
|
os.remove(test_file)
|
||||||
|
return user_cache_dir
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not use user-specific cache directory: {e}")
|
||||||
|
|
||||||
|
# Attempt 2: System-wide temporary directory
|
||||||
|
try:
|
||||||
|
system_cache_dir = os.path.join(tempfile.gettempdir(), 'ledmatrix_cache')
|
||||||
|
os.makedirs(system_cache_dir, exist_ok=True)
|
||||||
|
if os.access(system_cache_dir, os.W_OK):
|
||||||
|
return system_cache_dir
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not use system-wide temporary cache directory: {e}")
|
||||||
|
|
||||||
|
# Return None if no directory is writable
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _ensure_cache_dir(self):
|
||||||
|
"""This method is deprecated and no longer needed."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_cache_path(self, key: str) -> Optional[str]:
|
||||||
"""Get the path for a cache file."""
|
"""Get the path for a cache file."""
|
||||||
|
if not self.cache_dir:
|
||||||
|
return None
|
||||||
return os.path.join(self.cache_dir, f"{key}.json")
|
return os.path.join(self.cache_dir, f"{key}.json")
|
||||||
|
|
||||||
def get_cached_data(self, key: str, max_age: int = 300) -> Optional[Dict]:
|
def get_cached_data(self, key: str, max_age: int = 300) -> Optional[Dict]:
|
||||||
@@ -112,15 +113,16 @@ class CacheManager:
|
|||||||
data: Data to cache
|
data: Data to cache
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Save to file
|
# Update memory cache first
|
||||||
cache_path = self._get_cache_path(key)
|
|
||||||
with self._cache_lock:
|
|
||||||
with open(cache_path, 'w') as f:
|
|
||||||
json.dump(data, f, indent=4, cls=DateTimeEncoder)
|
|
||||||
|
|
||||||
# Update memory cache
|
|
||||||
self._memory_cache[key] = data
|
self._memory_cache[key] = data
|
||||||
self._memory_cache_timestamps[key] = time.time()
|
self._memory_cache_timestamps[key] = time.time()
|
||||||
|
|
||||||
|
# Save to file if a cache directory is available
|
||||||
|
cache_path = self._get_cache_path(key)
|
||||||
|
if cache_path:
|
||||||
|
with self._cache_lock:
|
||||||
|
with open(cache_path, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4, cls=DateTimeEncoder)
|
||||||
|
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
self.logger.error(f"Failed to save cache for key '{key}': {e}")
|
self.logger.error(f"Failed to save cache for key '{key}': {e}")
|
||||||
@@ -143,7 +145,7 @@ class CacheManager:
|
|||||||
del self._memory_cache_timestamps[key]
|
del self._memory_cache_timestamps[key]
|
||||||
|
|
||||||
cache_path = self._get_cache_path(key)
|
cache_path = self._get_cache_path(key)
|
||||||
if not os.path.exists(cache_path):
|
if not cache_path or not os.path.exists(cache_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -173,15 +175,16 @@ class CacheManager:
|
|||||||
del self._memory_cache[key]
|
del self._memory_cache[key]
|
||||||
del self._memory_cache_timestamps[key]
|
del self._memory_cache_timestamps[key]
|
||||||
cache_path = self._get_cache_path(key)
|
cache_path = self._get_cache_path(key)
|
||||||
if os.path.exists(cache_path):
|
if cache_path and os.path.exists(cache_path):
|
||||||
os.remove(cache_path)
|
os.remove(cache_path)
|
||||||
else:
|
else:
|
||||||
# Clear all keys
|
# Clear all keys
|
||||||
self._memory_cache.clear()
|
self._memory_cache.clear()
|
||||||
self._memory_cache_timestamps.clear()
|
self._memory_cache_timestamps.clear()
|
||||||
for file in os.listdir(self.cache_dir):
|
if self.cache_dir:
|
||||||
if file.endswith('.json'):
|
for file in os.listdir(self.cache_dir):
|
||||||
os.remove(os.path.join(self.cache_dir, file))
|
if file.endswith('.json'):
|
||||||
|
os.remove(os.path.join(self.cache_dir, file))
|
||||||
|
|
||||||
def has_data_changed(self, data_type: str, new_data: Dict[str, Any]) -> bool:
|
def has_data_changed(self, data_type: str, new_data: Dict[str, Any]) -> bool:
|
||||||
"""Check if data has changed from cached version."""
|
"""Check if data has changed from cached version."""
|
||||||
|
|||||||
@@ -988,20 +988,16 @@ class MLBRecentManager(BaseMLBManager):
|
|||||||
def update(self):
|
def update(self):
|
||||||
"""Update recent games data."""
|
"""Update recent games data."""
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if current_time - self.last_log_time > self.log_interval:
|
self.logger.info(f"Checking for recent MLB games. Last update was at {self.last_update}")
|
||||||
self.logger.info(f"Checking for recent MLB games. Last update was at {self.last_update}")
|
|
||||||
self.last_log_time = current_time
|
|
||||||
|
|
||||||
if self.last_update != 0 and (current_time - self.last_update < self.update_interval):
|
if self.last_update != 0 and (current_time - self.last_update < self.update_interval):
|
||||||
if current_time - self.last_log_time > self.log_interval:
|
self.logger.info(f"Skipping recent games update, interval not reached. Next update in {self.update_interval - (current_time - self.last_update):.0f} seconds.")
|
||||||
self.logger.info(f"Skipping recent games update, interval not reached. Next update in {self.update_interval - (current_time - self.last_update):.0f} seconds.")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Fetch data from MLB API
|
# Fetch data from MLB API
|
||||||
games = self._fetch_mlb_api_data()
|
games = self._fetch_mlb_api_data()
|
||||||
if not games:
|
if not games:
|
||||||
logger.warning("[MLB] No games returned from API")
|
logger.warning("[MLB] No games returned from API for recent games update.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Process games
|
# Process games
|
||||||
@@ -1009,9 +1005,10 @@ class MLBRecentManager(BaseMLBManager):
|
|||||||
now = datetime.now(timezone.utc) # Make timezone-aware
|
now = datetime.now(timezone.utc) # Make timezone-aware
|
||||||
recent_cutoff = now - timedelta(hours=self.recent_hours)
|
recent_cutoff = now - timedelta(hours=self.recent_hours)
|
||||||
|
|
||||||
logger.info(f"[MLB] Time window: {recent_cutoff} to {now}")
|
self.logger.info(f"[MLB] Recent games time window: {recent_cutoff} to {now}")
|
||||||
|
|
||||||
for game_id, game in games.items():
|
for game_id, game in games.items():
|
||||||
|
self.logger.info(f"[MLB] Processing game {game_id} for recent games...")
|
||||||
# Convert game time to UTC datetime
|
# Convert game time to UTC datetime
|
||||||
game_time_str = game['start_time'].replace('Z', '+00:00')
|
game_time_str = game['start_time'].replace('Z', '+00:00')
|
||||||
game_time = datetime.fromisoformat(game_time_str)
|
game_time = datetime.fromisoformat(game_time_str)
|
||||||
@@ -1022,25 +1019,28 @@ class MLBRecentManager(BaseMLBManager):
|
|||||||
is_favorite_game = (game['home_team'] in self.favorite_teams or
|
is_favorite_game = (game['home_team'] in self.favorite_teams or
|
||||||
game['away_team'] in self.favorite_teams)
|
game['away_team'] in self.favorite_teams)
|
||||||
|
|
||||||
if is_favorite_game:
|
if not is_favorite_game:
|
||||||
logger.info(f"[MLB] Checking favorite team game: {game['away_team']} @ {game['home_team']}")
|
self.logger.info(f"[MLB] Skipping game {game_id} - not a favorite team.")
|
||||||
logger.info(f"[MLB] Game time (UTC): {game_time}")
|
continue
|
||||||
logger.info(f"[MLB] Game status: {game['status']}, State: {game['status_state']}")
|
|
||||||
|
self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']}")
|
||||||
|
self.logger.info(f"[MLB] Game time (UTC): {game_time}")
|
||||||
|
self.logger.info(f"[MLB] Game status: {game['status']}, State: {game['status_state']}")
|
||||||
|
|
||||||
# Use status_state to determine if game is final
|
# Use status_state to determine if game is final
|
||||||
is_final = game['status_state'] in ['post', 'final', 'completed']
|
is_final = game['status_state'] in ['post', 'final', 'completed']
|
||||||
is_within_time = recent_cutoff <= game_time <= now
|
is_within_time = recent_cutoff <= game_time <= now
|
||||||
|
|
||||||
if is_favorite_game:
|
self.logger.info(f"[MLB] Is final: {is_final}")
|
||||||
logger.info(f"[MLB] Is final: {is_final}")
|
self.logger.info(f"[MLB] Is within time window: {is_within_time}")
|
||||||
logger.info(f"[MLB] Is within time window: {is_within_time}")
|
|
||||||
logger.info(f"[MLB] Time comparison: {recent_cutoff} <= {game_time} <= {now}")
|
|
||||||
|
|
||||||
# Only add favorite team games that are final and within time window
|
# Only add favorite team games that are final and within time window
|
||||||
if is_favorite_game and is_final and is_within_time:
|
if is_final and is_within_time:
|
||||||
|
self.logger.info(f"[MLB] Adding game {game_id} to recent games list.")
|
||||||
self._fetch_odds(game)
|
self._fetch_odds(game)
|
||||||
new_recent_games.append(game)
|
new_recent_games.append(game)
|
||||||
logger.info(f"[MLB] Added favorite team game to recent list: {game['away_team']} @ {game['home_team']}")
|
else:
|
||||||
|
self.logger.info(f"[MLB] Skipping game {game_id} - does not meet criteria for recent games.")
|
||||||
|
|
||||||
if new_recent_games:
|
if new_recent_games:
|
||||||
logger.info(f"[MLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}")
|
logger.info(f"[MLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}")
|
||||||
@@ -1107,80 +1107,82 @@ class MLBUpcomingManager(BaseMLBManager):
|
|||||||
def update(self):
|
def update(self):
|
||||||
"""Update upcoming games data."""
|
"""Update upcoming games data."""
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if current_time - self.last_log_time > self.log_interval:
|
self.logger.info(f"Checking for upcoming MLB games. Last update was at {self.last_update}")
|
||||||
self.logger.info(f"Checking for upcoming MLB games. Last update was at {self.last_update}")
|
|
||||||
self.last_log_time = current_time
|
|
||||||
|
|
||||||
if self.last_update != 0 and (current_time - self.last_update < self.update_interval):
|
if self.last_update != 0 and (current_time - self.last_update < self.update_interval):
|
||||||
if current_time - self.last_log_time > self.log_interval:
|
self.logger.info(f"Skipping upcoming games update, interval not reached. Next update in {self.update_interval - (current_time - self.last_update):.0f} seconds.")
|
||||||
self.logger.info(f"Skipping upcoming games update, interval not reached. Next update in {self.update_interval - (current_time - self.last_update):.0f} seconds.")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Fetch data from MLB API
|
# Fetch data from MLB API
|
||||||
games = self._fetch_mlb_api_data()
|
games = self._fetch_mlb_api_data()
|
||||||
if games:
|
if not games:
|
||||||
# Process games
|
self.logger.warning("[MLB] No games returned from API for upcoming games update.")
|
||||||
new_upcoming_games = []
|
return
|
||||||
now = datetime.now(timezone.utc) # Make timezone-aware
|
|
||||||
upcoming_cutoff = now + timedelta(hours=24)
|
# Process games
|
||||||
|
new_upcoming_games = []
|
||||||
|
now = datetime.now(timezone.utc) # Make timezone-aware
|
||||||
|
upcoming_cutoff = now + timedelta(hours=24)
|
||||||
|
|
||||||
|
self.logger.info(f"[MLB] Upcoming games time window: {now} to {upcoming_cutoff}")
|
||||||
|
|
||||||
|
for game_id, game in games.items():
|
||||||
|
self.logger.info(f"[MLB] Processing game {game_id} for upcoming games...")
|
||||||
|
# Check if this is a favorite team game first
|
||||||
|
is_favorite_game = (game['home_team'] in self.favorite_teams or
|
||||||
|
game['away_team'] in self.favorite_teams)
|
||||||
|
|
||||||
logger.info(f"Looking for games between {now} and {upcoming_cutoff}")
|
if not is_favorite_game:
|
||||||
|
self.logger.info(f"[MLB] Skipping game {game_id} - not a favorite team.")
|
||||||
for game in games.values():
|
continue
|
||||||
# Check if this is a favorite team game first
|
|
||||||
is_favorite_game = (game['home_team'] in self.favorite_teams or
|
|
||||||
game['away_team'] in self.favorite_teams)
|
|
||||||
|
|
||||||
if not is_favorite_game:
|
|
||||||
continue # Skip non-favorite team games
|
|
||||||
|
|
||||||
game_time = datetime.fromisoformat(game['start_time'].replace('Z', '+00:00'))
|
game_time = datetime.fromisoformat(game['start_time'].replace('Z', '+00:00'))
|
||||||
# Ensure game_time is timezone-aware (UTC)
|
# Ensure game_time is timezone-aware (UTC)
|
||||||
if game_time.tzinfo is None:
|
if game_time.tzinfo is None:
|
||||||
game_time = game_time.replace(tzinfo=timezone.utc)
|
game_time = game_time.replace(tzinfo=timezone.utc)
|
||||||
logger.info(f"Checking favorite team game: {game['away_team']} @ {game['home_team']} at {game_time}")
|
|
||||||
logger.info(f"Game status: {game['status']}, State: {game['status_state']}")
|
self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']} at {game_time}")
|
||||||
|
self.logger.info(f"[MLB] Game status: {game['status']}, State: {game['status_state']}")
|
||||||
# Check if game is within our time window
|
|
||||||
is_within_time = now <= game_time <= upcoming_cutoff
|
|
||||||
|
|
||||||
# For upcoming games, we'll consider any game that:
|
|
||||||
# 1. Is within our time window
|
|
||||||
# 2. Is not final (not 'post' or 'final' state)
|
|
||||||
# 3. Has a future start time
|
|
||||||
is_upcoming = (
|
|
||||||
is_within_time and
|
|
||||||
game['status_state'] not in ['post', 'final', 'completed'] and
|
|
||||||
game_time > now
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Within time window: {is_within_time}")
|
|
||||||
logger.info(f"Is upcoming: {is_upcoming}")
|
|
||||||
logger.info(f"Game time > now: {game_time > now}")
|
|
||||||
logger.info(f"Status state not final: {game['status_state'] not in ['post', 'final', 'completed']}")
|
|
||||||
|
|
||||||
if is_upcoming:
|
|
||||||
self._fetch_odds(game)
|
|
||||||
new_upcoming_games.append(game)
|
|
||||||
logger.info(f"Added favorite team game to upcoming list: {game['away_team']} @ {game['home_team']}")
|
|
||||||
|
|
||||||
# Filter for favorite teams (though we already filtered above, this is a safety check)
|
# Check if game is within our time window
|
||||||
new_team_games = [game for game in new_upcoming_games
|
is_within_time = now <= game_time <= upcoming_cutoff
|
||||||
|
|
||||||
|
# For upcoming games, we'll consider any game that:
|
||||||
|
# 1. Is within our time window
|
||||||
|
# 2. Is not final (not 'post' or 'final' state)
|
||||||
|
# 3. Has a future start time
|
||||||
|
is_upcoming = (
|
||||||
|
is_within_time and
|
||||||
|
game['status_state'] not in ['post', 'final', 'completed'] and
|
||||||
|
game_time > now
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(f"[MLB] Is within time window: {is_within_time}")
|
||||||
|
self.logger.info(f"[MLB] Is upcoming: {is_upcoming}")
|
||||||
|
|
||||||
|
if is_upcoming:
|
||||||
|
self.logger.info(f"[MLB] Adding game {game_id} to upcoming games list.")
|
||||||
|
self._fetch_odds(game)
|
||||||
|
new_upcoming_games.append(game)
|
||||||
|
else:
|
||||||
|
self.logger.info(f"[MLB] Skipping game {game_id} - does not meet criteria for upcoming games.")
|
||||||
|
|
||||||
|
# Filter for favorite teams (though we already filtered above, this is a safety check)
|
||||||
|
new_team_games = [game for game in new_upcoming_games
|
||||||
if game['home_team'] in self.favorite_teams or
|
if game['home_team'] in self.favorite_teams or
|
||||||
game['away_team'] in self.favorite_teams]
|
game['away_team'] in self.favorite_teams]
|
||||||
|
|
||||||
if new_team_games:
|
if new_team_games:
|
||||||
logger.info(f"[MLB] Found {len(new_team_games)} upcoming games for favorite teams")
|
logger.info(f"[MLB] Found {len(new_team_games)} upcoming games for favorite teams")
|
||||||
self.upcoming_games = new_team_games
|
self.upcoming_games = new_team_games
|
||||||
if not self.current_game:
|
if not self.current_game:
|
||||||
self.current_game = self.upcoming_games[0]
|
self.current_game = self.upcoming_games[0]
|
||||||
else:
|
else:
|
||||||
logger.info("[MLB] No upcoming games found for favorite teams")
|
logger.info("[MLB] No upcoming games found for favorite teams")
|
||||||
self.upcoming_games = []
|
self.upcoming_games = []
|
||||||
self.current_game = None
|
self.current_game = None
|
||||||
|
|
||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[MLB] Error updating upcoming games: {e}", exc_info=True)
|
logger.error(f"[MLB] Error updating upcoming games: {e}", exc_info=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user