diabolical

This commit is contained in:
pacnpal
2024-11-18 05:17:37 +00:00
parent fc06e54d8a
commit df9099f2c8
75 changed files with 744 additions and 719 deletions

View File

@@ -8,25 +8,29 @@ from enum import Enum, auto
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Any, Optional, TypedDict, ClassVar
#try:
# Try relative imports first
from ..utils.file_ops import cleanup_downloads
from ..utils.exceptions import CleanupError, ErrorContext, ErrorSeverity
#except ImportError:
# Fall back to absolute imports if relative imports fail
# from videoarchiver.utils.file_ops import cleanup_downloads
# from videoarchiver.utils.exceptions import CleanupError, ErrorContext, ErrorSeverity
# try:
# Try relative imports first
from utils.file_ops import cleanup_downloads
from utils.exceptions import CleanupError, ErrorContext, ErrorSeverity
# except ImportError:
# Fall back to absolute imports if relative imports fail
# from videoarchiver.utils.file_ops import cleanup_downloads
# from videoarchiver.utils.exceptions import CleanupError, ErrorContext, ErrorSeverity
if TYPE_CHECKING:
#try:
# try:
from .base import VideoArchiver
#except ImportError:
# from videoarchiver.core.base import VideoArchiver
# except ImportError:
# from videoarchiver.core.base import VideoArchiver
logger = logging.getLogger("VideoArchiver")
class CleanupPhase(Enum):
"""Cleanup phases"""
INITIALIZATION = auto()
UPDATE_CHECKER = auto()
PROCESSOR = auto()
@@ -36,21 +40,26 @@ class CleanupPhase(Enum):
DOWNLOADS = auto()
REFERENCES = auto()
class CleanupStatus(Enum):
"""Cleanup status"""
SUCCESS = auto()
TIMEOUT = auto()
ERROR = auto()
SKIPPED = auto()
class CleanupResult(TypedDict):
"""Type definition for cleanup result"""
phase: CleanupPhase
status: CleanupStatus
error: Optional[str]
duration: float
timestamp: str
class CleanupManager:
"""Manages cleanup operations"""
@@ -65,7 +74,7 @@ class CleanupManager:
phase: CleanupPhase,
status: CleanupStatus,
error: Optional[str] = None,
duration: float = 0.0
duration: float = 0.0,
) -> None:
"""Record result of a cleanup phase"""
self.results[phase] = CleanupResult(
@@ -73,20 +82,21 @@ class CleanupManager:
status=status,
error=error,
duration=duration,
timestamp=datetime.utcnow().isoformat()
timestamp=datetime.utcnow().isoformat(),
)
def get_results(self) -> Dict[CleanupPhase, CleanupResult]:
"""Get cleanup results"""
return self.results.copy()
async def cleanup_resources(cog: "VideoArchiver") -> None:
"""
Clean up all resources with proper handling.
Args:
cog: VideoArchiver cog instance
Raises:
CleanupError: If cleanup fails
"""
@@ -102,11 +112,13 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
try:
logger.info("Cancelling initialization task")
cog._init_task.cancel()
await asyncio.wait_for(cog._init_task, timeout=cleanup_manager.CLEANUP_TIMEOUT)
await asyncio.wait_for(
cog._init_task, timeout=cleanup_manager.CLEANUP_TIMEOUT
)
cleanup_manager.record_result(
CleanupPhase.INITIALIZATION,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except (asyncio.TimeoutError, asyncio.CancelledError) as e:
logger.warning("Initialization task cancellation timed out")
@@ -114,7 +126,7 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
CleanupPhase.INITIALIZATION,
CleanupStatus.TIMEOUT,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Stop update checker
@@ -123,13 +135,12 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
try:
logger.info("Stopping update checker")
await asyncio.wait_for(
cog.update_checker.stop(),
timeout=cleanup_manager.CLEANUP_TIMEOUT
cog.update_checker.stop(), timeout=cleanup_manager.CLEANUP_TIMEOUT
)
cleanup_manager.record_result(
CleanupPhase.UPDATE_CHECKER,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except asyncio.TimeoutError as e:
logger.warning("Update checker stop timed out")
@@ -137,7 +148,7 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
CleanupPhase.UPDATE_CHECKER,
CleanupStatus.TIMEOUT,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
cog.update_checker = None
@@ -147,13 +158,12 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
try:
logger.info("Cleaning up processor")
await asyncio.wait_for(
cog.processor.cleanup(),
timeout=cleanup_manager.CLEANUP_TIMEOUT
cog.processor.cleanup(), timeout=cleanup_manager.CLEANUP_TIMEOUT
)
cleanup_manager.record_result(
CleanupPhase.PROCESSOR,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except asyncio.TimeoutError as e:
logger.warning("Processor cleanup timed out, forcing cleanup")
@@ -162,7 +172,7 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
CleanupPhase.PROCESSOR,
CleanupStatus.TIMEOUT,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
cog.processor = None
@@ -172,13 +182,12 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
try:
logger.info("Cleaning up queue manager")
await asyncio.wait_for(
cog.queue_manager.cleanup(),
timeout=cleanup_manager.CLEANUP_TIMEOUT
cog.queue_manager.cleanup(), timeout=cleanup_manager.CLEANUP_TIMEOUT
)
cleanup_manager.record_result(
CleanupPhase.QUEUE_MANAGER,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except asyncio.TimeoutError as e:
logger.warning("Queue manager cleanup timed out, forcing stop")
@@ -187,7 +196,7 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
CleanupPhase.QUEUE_MANAGER,
CleanupStatus.TIMEOUT,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
cog.queue_manager = None
@@ -214,14 +223,14 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
CleanupPhase.COMPONENTS,
status,
"\n".join(errors) if errors else None,
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.COMPONENTS,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Kill any FFmpeg processes
@@ -233,7 +242,7 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
cog.ffmpeg_mgr = None
# Kill any remaining FFmpeg processes system-wide
if os.name != 'nt': # Unix-like systems
if os.name != "nt": # Unix-like systems
os.system("pkill -9 ffmpeg")
else: # Windows
os.system("taskkill /F /IM ffmpeg.exe")
@@ -241,14 +250,14 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
cleanup_manager.record_result(
CleanupPhase.FFMPEG,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.FFMPEG,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Clean up download directory
@@ -258,21 +267,21 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
logger.info("Cleaning up download directory")
await asyncio.wait_for(
cleanup_downloads(str(cog.download_path)),
timeout=cleanup_manager.CLEANUP_TIMEOUT
timeout=cleanup_manager.CLEANUP_TIMEOUT,
)
if cog.download_path.exists():
cog.download_path.rmdir()
cleanup_manager.record_result(
CleanupPhase.DOWNLOADS,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.DOWNLOADS,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
@@ -284,8 +293,8 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
"Cleanup",
"cleanup_resources",
{"duration": (datetime.utcnow() - start_time).total_seconds()},
ErrorSeverity.HIGH
)
ErrorSeverity.HIGH,
),
)
finally:
logger.info("Clearing ready flag")
@@ -294,17 +303,18 @@ async def cleanup_resources(cog: "VideoArchiver") -> None:
# Log cleanup results
for phase, result in cleanup_manager.get_results().items():
status_str = f"{result['status'].name}"
if result['error']:
if result["error"]:
status_str += f" ({result['error']})"
logger.info(
f"Cleanup phase {phase.name}: {status_str} "
f"(Duration: {result['duration']:.2f}s)"
)
async def force_cleanup_resources(cog: "VideoArchiver") -> None:
"""
Force cleanup of resources when timeout occurs.
Args:
cog: VideoArchiver cog instance
"""
@@ -323,14 +333,14 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cleanup_manager.record_result(
CleanupPhase.PROCESSOR,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.PROCESSOR,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
cog.processor = None
@@ -343,14 +353,14 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cleanup_manager.record_result(
CleanupPhase.QUEUE_MANAGER,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.QUEUE_MANAGER,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
cog.queue_manager = None
@@ -363,7 +373,7 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cog.ffmpeg_mgr = None
# Force kill any remaining FFmpeg processes system-wide
if os.name != 'nt': # Unix-like systems
if os.name != "nt": # Unix-like systems
os.system("pkill -9 ffmpeg")
else: # Windows
os.system("taskkill /F /IM ffmpeg.exe")
@@ -371,14 +381,14 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cleanup_manager.record_result(
CleanupPhase.FFMPEG,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.FFMPEG,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Clean up download directory
@@ -388,21 +398,21 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
logger.info("Force cleaning download directory")
await asyncio.wait_for(
cleanup_downloads(str(cog.download_path)),
timeout=cleanup_manager.FORCE_CLEANUP_TIMEOUT
timeout=cleanup_manager.FORCE_CLEANUP_TIMEOUT,
)
if cog.download_path.exists():
cog.download_path.rmdir()
cleanup_manager.record_result(
CleanupPhase.DOWNLOADS,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.DOWNLOADS,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Clear all components
@@ -414,14 +424,14 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cleanup_manager.record_result(
CleanupPhase.COMPONENTS,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.COMPONENTS,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
@@ -443,25 +453,25 @@ async def force_cleanup_resources(cog: "VideoArchiver") -> None:
cog.db = None
cog._init_task = None
cog._cleanup_task = None
if hasattr(cog, '_queue_task'):
if hasattr(cog, "_queue_task"):
cog._queue_task = None
cleanup_manager.record_result(
CleanupPhase.REFERENCES,
CleanupStatus.SUCCESS,
duration=(datetime.utcnow() - phase_start).total_seconds()
duration=(datetime.utcnow() - phase_start).total_seconds(),
)
except Exception as e:
cleanup_manager.record_result(
CleanupPhase.REFERENCES,
CleanupStatus.ERROR,
str(e),
(datetime.utcnow() - phase_start).total_seconds()
(datetime.utcnow() - phase_start).total_seconds(),
)
# Log cleanup results
for phase, result in cleanup_manager.get_results().items():
status_str = f"{result['status'].name}"
if result['error']:
if result["error"]:
status_str += f" ({result['error']})"
logger.info(
f"Force cleanup phase {phase.name}: {status_str} "