197 lines
7.1 KiB
Python
Executable File
197 lines
7.1 KiB
Python
Executable File
"""
|
|
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
|