mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-01-17 23:25:40 -08:00
feat: implement session management functionality with save/load capabilities and error handling
This commit is contained in:
@@ -2,8 +2,9 @@ import importlib.util
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, List
|
||||
from typing import Callable, List
|
||||
|
||||
import click
|
||||
|
||||
@@ -14,6 +15,7 @@ from ...libs.players.base import BasePlayer
|
||||
from ...libs.providers.anime.base import BaseAnimeProvider
|
||||
from ...libs.selectors.base import BaseSelector
|
||||
from ..config import ConfigLoader
|
||||
from ..utils.session_manager import SessionManager
|
||||
from .state import ControlFlow, State
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -59,6 +61,8 @@ class Session:
|
||||
self._context: Context | None = None
|
||||
self._history: List[State] = []
|
||||
self._menus: dict[str, Menu] = {}
|
||||
self._session_manager = SessionManager()
|
||||
self._auto_save_enabled = True
|
||||
|
||||
def _load_context(self, config: AppConfig):
|
||||
"""Initializes all shared services based on the provided configuration."""
|
||||
@@ -152,14 +156,60 @@ class Session:
|
||||
config: The initial application configuration.
|
||||
resume_path: Optional path to a saved session file to resume from.
|
||||
"""
|
||||
from ..utils.feedback import create_feedback_manager
|
||||
|
||||
feedback = create_feedback_manager(True) # Always use icons for session messages
|
||||
|
||||
self._load_context(config)
|
||||
|
||||
# Handle session recovery
|
||||
if resume_path:
|
||||
self.resume(resume_path)
|
||||
elif not self._history:
|
||||
# Start with the main menu if history is empty
|
||||
self.resume(resume_path, feedback)
|
||||
elif self._session_manager.has_crash_backup():
|
||||
# Offer to resume from crash backup
|
||||
if feedback.confirm(
|
||||
"Found a crash backup from a previous session. Would you like to resume?",
|
||||
default=True
|
||||
):
|
||||
crash_history = self._session_manager.load_crash_backup(feedback)
|
||||
if crash_history:
|
||||
self._history = crash_history
|
||||
feedback.info("Session restored from crash backup")
|
||||
# Clear the crash backup after successful recovery
|
||||
self._session_manager.clear_crash_backup()
|
||||
elif self._session_manager.has_auto_save():
|
||||
# Offer to resume from auto-save
|
||||
if feedback.confirm(
|
||||
"Found an auto-saved session. Would you like to resume?",
|
||||
default=False
|
||||
):
|
||||
auto_history = self._session_manager.load_auto_save(feedback)
|
||||
if auto_history:
|
||||
self._history = auto_history
|
||||
feedback.info("Session restored from auto-save")
|
||||
|
||||
# Start with main menu if no history
|
||||
if not self._history:
|
||||
self._history.append(State(menu_name="MAIN"))
|
||||
|
||||
# Create crash backup before starting
|
||||
if self._auto_save_enabled:
|
||||
self._session_manager.create_crash_backup(self._history)
|
||||
|
||||
try:
|
||||
self._run_main_loop()
|
||||
except KeyboardInterrupt:
|
||||
feedback.warning("Session interrupted by user")
|
||||
self._handle_session_exit(feedback, interrupted=True)
|
||||
except Exception as e:
|
||||
feedback.error("Session crashed unexpectedly", str(e))
|
||||
self._handle_session_exit(feedback, crashed=True)
|
||||
raise
|
||||
else:
|
||||
self._handle_session_exit(feedback, normal_exit=True)
|
||||
|
||||
def _run_main_loop(self):
|
||||
"""Run the main session loop."""
|
||||
while self._history:
|
||||
current_state = self._history[-1]
|
||||
menu_to_run = self._menus.get(current_state.menu_name)
|
||||
@@ -170,6 +220,10 @@ class Session:
|
||||
)
|
||||
break
|
||||
|
||||
# Auto-save periodically (every 5 state changes)
|
||||
if self._auto_save_enabled and len(self._history) % 5 == 0:
|
||||
self._session_manager.auto_save_session(self._history)
|
||||
|
||||
# Execute the menu function, which returns the next step.
|
||||
next_step = menu_to_run.execute(self._context, current_state)
|
||||
|
||||
@@ -187,37 +241,109 @@ class Session:
|
||||
# if the state is main menu we should reset the history
|
||||
if next_step.menu_name == "MAIN":
|
||||
self._history = [next_step]
|
||||
# A new state was returned, push it to history for the next loop.
|
||||
self._history.append(next_step)
|
||||
else:
|
||||
# A new state was returned, push it to history for the next loop.
|
||||
self._history.append(next_step)
|
||||
else:
|
||||
logger.error(
|
||||
f"Menu '{current_state.menu_name}' returned invalid type: {type(next_step)}"
|
||||
)
|
||||
break
|
||||
|
||||
def _handle_session_exit(self, feedback, normal_exit=False, interrupted=False, crashed=False):
|
||||
"""Handle session cleanup on exit."""
|
||||
if self._auto_save_enabled and self._history:
|
||||
if normal_exit:
|
||||
# Clear auto-save on normal exit
|
||||
self._session_manager.clear_auto_save()
|
||||
self._session_manager.clear_crash_backup()
|
||||
feedback.info("Session completed normally")
|
||||
elif interrupted:
|
||||
# Save session on interruption
|
||||
self._session_manager.auto_save_session(self._history)
|
||||
feedback.info("Session auto-saved due to interruption")
|
||||
elif crashed:
|
||||
# Keep crash backup on crash
|
||||
feedback.error("Session backup maintained for recovery")
|
||||
|
||||
click.echo("Exiting interactive session.")
|
||||
|
||||
def save(self, file_path: Path):
|
||||
"""Serializes the session history to a JSON file."""
|
||||
history_dicts = [state.model_dump(mode="json") for state in self._history]
|
||||
try:
|
||||
file_path.write_text(str(history_dicts))
|
||||
logger.info(f"Session saved to {file_path}")
|
||||
except IOError as e:
|
||||
logger.error(f"Failed to save session: {e}")
|
||||
def save(self, file_path: Path, session_name: str = None, description: str = None):
|
||||
"""
|
||||
Save session history to a file with comprehensive metadata and error handling.
|
||||
|
||||
Args:
|
||||
file_path: Path to save the session
|
||||
session_name: Optional name for the session
|
||||
description: Optional description for the session
|
||||
"""
|
||||
from ..utils.feedback import create_feedback_manager
|
||||
|
||||
feedback = create_feedback_manager(True)
|
||||
return self._session_manager.save_session(
|
||||
self._history,
|
||||
file_path,
|
||||
session_name=session_name,
|
||||
description=description,
|
||||
feedback=feedback
|
||||
)
|
||||
|
||||
def resume(self, file_path: Path):
|
||||
"""Loads a session history from a JSON file."""
|
||||
if not file_path.exists():
|
||||
logger.warning(f"Resume file not found: {file_path}")
|
||||
return
|
||||
try:
|
||||
history_dicts = file_path.read_text()
|
||||
self._history = [State.model_validate(d) for d in history_dicts]
|
||||
logger.info(f"Session resumed from {file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to resume session: {e}")
|
||||
self._history = [] # Reset history on failure
|
||||
def resume(self, file_path: Path, feedback=None):
|
||||
"""
|
||||
Load session history from a file with comprehensive error handling.
|
||||
|
||||
Args:
|
||||
file_path: Path to the session file
|
||||
feedback: Optional feedback manager for user notifications
|
||||
"""
|
||||
if not feedback:
|
||||
from ..utils.feedback import create_feedback_manager
|
||||
feedback = create_feedback_manager(True)
|
||||
|
||||
history = self._session_manager.load_session(file_path, feedback)
|
||||
if history:
|
||||
self._history = history
|
||||
return True
|
||||
return False
|
||||
|
||||
def list_saved_sessions(self):
|
||||
"""List all saved sessions with their metadata."""
|
||||
return self._session_manager.list_saved_sessions()
|
||||
|
||||
def cleanup_old_sessions(self, max_sessions: int = 10):
|
||||
"""Clean up old session files, keeping only the most recent ones."""
|
||||
return self._session_manager.cleanup_old_sessions(max_sessions)
|
||||
|
||||
def enable_auto_save(self, enabled: bool = True):
|
||||
"""Enable or disable auto-save functionality."""
|
||||
self._auto_save_enabled = enabled
|
||||
|
||||
def get_session_stats(self) -> dict:
|
||||
"""Get statistics about the current session."""
|
||||
return {
|
||||
"current_states": len(self._history),
|
||||
"current_menu": self._history[-1].menu_name if self._history else None,
|
||||
"auto_save_enabled": self._auto_save_enabled,
|
||||
"has_auto_save": self._session_manager.has_auto_save(),
|
||||
"has_crash_backup": self._session_manager.has_crash_backup()
|
||||
}
|
||||
|
||||
def create_manual_backup(self, backup_name: str = None):
|
||||
"""Create a manual backup of the current session."""
|
||||
from ..utils.feedback import create_feedback_manager
|
||||
from ...core.constants import APP_DIR
|
||||
|
||||
feedback = create_feedback_manager(True)
|
||||
backup_name = backup_name or f"manual_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
backup_path = APP_DIR / "sessions" / f"{backup_name}.json"
|
||||
|
||||
return self._session_manager.save_session(
|
||||
self._history,
|
||||
backup_path,
|
||||
session_name=backup_name,
|
||||
description="Manual backup created by user",
|
||||
feedback=feedback
|
||||
)
|
||||
|
||||
@property
|
||||
def menu(self) -> Callable[[MenuFunction], MenuFunction]:
|
||||
|
||||
Reference in New Issue
Block a user