""" Aria2 Error Parser - Parse and categorize aria2 errors with actionable solutions. Based on official aria2 documentation: https://aria2.github.io/manual/en/html/aria2c.html """ from typing import Optional, Dict, Any from enum import Enum import re class Aria2ErrorCategory(Enum): """Categories of aria2 errors for better handling""" FILE_CONFLICT = "file_conflict" # File exists, control file missing NETWORK = "network" # Connection, timeout, DNS errors HTTP = "http" # HTTP status codes AUTH = "auth" # Authentication failures CORRUPTION = "corruption" # Checksum, torrent parsing errors CONFIG = "config" # Invalid options RESOURCE = "resource" # Disk full, permission denied UNKNOWN = "unknown" class Aria2Error: """Parsed aria2 error with category and actionable solution""" def __init__( self, category: Aria2ErrorCategory, original_message: str, user_message: str, suggested_action: str, technical_details: Optional[Dict[str, Any]] = None ): self.category = category self.original_message = original_message self.user_message = user_message self.suggested_action = suggested_action self.technical_details = technical_details or {} def to_dict(self) -> Dict[str, Any]: return { 'category': self.category.value, 'original_message': self.original_message, 'user_message': self.user_message, 'suggested_action': self.suggested_action, 'technical_details': self.technical_details } # Error patterns with solutions ERROR_PATTERNS = [ # File exists but control file missing (--allow-overwrite=false) { 'pattern': r'File .+ exists, but a control file.*does not exist', 'category': Aria2ErrorCategory.FILE_CONFLICT, 'user_message': 'Tệp đã tồn tại từ lần tải trước (chưa hoàn thành hoặc đã hoàn thành)', 'suggested_action': 'Xóa tệp cũ và tệp .aria2 (nếu có) để tải lại từ đầu, hoặc bỏ qua nếu tệp đã hoàn thành', 'extract_file': lambda msg: (m.group(1) if (m := re.search(r'File (.+?) exists', msg)) else None) }, # Connection timeout { 'pattern': r'Read timed out|Connection timed out', 'category': Aria2ErrorCategory.NETWORK, 'user_message': 'Kết nối bị timeout (quá chậm hoặc không phản hồi)', 'suggested_action': 'Kiểm tra kết nối mạng, giảm số connections (--max-connection-per-server), hoặc tăng timeout' }, # Connection refused { 'pattern': r'Connection refused|Could not connect', 'category': Aria2ErrorCategory.NETWORK, 'user_message': 'Không thể kết nối đến server', 'suggested_action': 'Kiểm tra URL, firewall, hoặc server có thể đang offline' }, # HTTP 404 { 'pattern': r'404|Not Found', 'category': Aria2ErrorCategory.HTTP, 'user_message': 'Tệp không tồn tại trên server (404)', 'suggested_action': 'Link download có thể đã hết hạn hoặc tệp đã bị xóa' }, # HTTP 403 { 'pattern': r'403|Forbidden', 'category': Aria2ErrorCategory.HTTP, 'user_message': 'Không có quyền truy cập (403)', 'suggested_action': 'Kiểm tra cookies, session, hoặc referer header' }, # HTTP 401 { 'pattern': r'401|Unauthorized', 'category': Aria2ErrorCategory.AUTH, 'user_message': 'Yêu cầu xác thực (401)', 'suggested_action': 'Cần đăng nhập lại hoặc refresh token/session' }, # Checksum mismatch { 'pattern': r'Checksum validation failed|piece hash check failed', 'category': Aria2ErrorCategory.CORRUPTION, 'user_message': 'Dữ liệu bị lỗi (checksum không khớp)', 'suggested_action': 'Xóa tệp .aria2 và tải lại từ đầu' }, # Disk full { 'pattern': r'No space left on device|Disk full', 'category': Aria2ErrorCategory.RESOURCE, 'user_message': 'Không đủ dung lượng ổ đĩa', 'suggested_action': 'Giải phóng dung lượng hoặc thay đổi thư mục đích' }, # Permission denied { 'pattern': r'Permission denied', 'category': Aria2ErrorCategory.RESOURCE, 'user_message': 'Không có quyền ghi file', 'suggested_action': 'Kiểm tra quyền truy cập thư mục đích' }, # Too many redirects { 'pattern': r'Too many redirects', 'category': Aria2ErrorCategory.HTTP, 'user_message': 'Quá nhiều lần chuyển hướng', 'suggested_action': 'URL có thể bị lỗi hoặc redirect loop' } ] def parse_aria2_error(error_message: str) -> Aria2Error: """ Parse aria2 error message and return structured error with solution. Args: error_message: Raw error message from aria2 Returns: Aria2Error object with category and suggested action """ # Try to match known patterns for pattern_info in ERROR_PATTERNS: if re.search(pattern_info['pattern'], error_message, re.IGNORECASE): technical_details = {} # Extract file path if available if 'extract_file' in pattern_info: file_path = pattern_info['extract_file'](error_message) if file_path: technical_details['file_path'] = file_path return Aria2Error( category=pattern_info['category'], original_message=error_message, user_message=pattern_info['user_message'], suggested_action=pattern_info['suggested_action'], technical_details=technical_details ) # Unknown error - return generic error return Aria2Error( category=Aria2ErrorCategory.UNKNOWN, original_message=error_message, user_message='Lỗi không xác định từ aria2', suggested_action='Xem log chi tiết để biết thêm thông tin', technical_details={'raw_error': error_message} ) def format_error_for_user(error: Aria2Error, include_technical: bool = False) -> str: """ Format error message for user display. Args: error: Parsed Aria2Error object include_technical: Whether to include technical details Returns: Formatted error string """ message = f"❌ {error.user_message}\n" message += f"💡 Giải pháp: {error.suggested_action}" if include_technical: message += f"\n🔧 Chi tiết kỹ thuật: {error.original_message}" if error.technical_details: for key, value in error.technical_details.items(): message += f"\n - {key}: {value}" return message