tuning error recognition

This commit is contained in:
Bryan Van Deusen
2026-01-30 22:04:07 -05:00
parent 097ab16c50
commit 3ee7de7ecd
7 changed files with 546 additions and 31 deletions
+79 -27
View File
@@ -316,23 +316,52 @@ class GalleryDLService:
return config
def _categorize_error(self, return_code: int, stdout: str, stderr: str) -> tuple[ErrorType, str]:
"""Categorize the error based on output and return code."""
"""Categorize the error based on output and return code.
Priority order:
1. Check for skip messages (# prefixed lines or text patterns) → NO_NEW_CONTENT
2. Check for actual errors (auth, rate limit, 404, network, etc.)
3. Fall back to UNKNOWN_ERROR
This ordering prevents false positives where debug output matches error patterns.
"""
combined = f"{stdout} {stderr}".lower()
# Check for "no new content" FIRST for any non-zero return code
# Gallery-dl returns 1 when all files are skipped or no new content found
# This check must run before other error checks to avoid false positives
skip_indicators = ["skipping", "skip", "already exists", "archive"]
if any(indicator in combined for indicator in skip_indicators):
# If we see skip messages and no actual error messages, it's just "no new content"
actual_errors = ["error", "failed", "exception", "traceback"]
if not any(err in combined for err in actual_errors):
return ErrorType.NO_NEW_CONTENT, "No new content to download"
# === PHASE 1: Detect skip/no-new-content scenarios ===
# Gallery-dl indicates skipped files two ways:
# 1. Lines starting with '#' in stdout (e.g., "# /data/downloads/Artist/file.png")
# 2. Text patterns like "skipping", "already exists"
# Count lines starting with '#' (skip messages)
skip_line_count = len([
line for line in stdout.split('\n')
if line.strip().startswith('#')
])
# Text-based skip indicators
skip_text_indicators = ["skipping", "already exists", "archive"]
has_skip_text = any(ind in combined for ind in skip_text_indicators)
# Actual error indicators that override skip detection
actual_error_indicators = [
"][error]", # gallery-dl error log level: [patreon][error]
"exception", # Python exceptions
"traceback", # Python tracebacks
"download failed",
"extraction failed",
]
has_actual_error = any(err in combined for err in actual_error_indicators)
# If we have skip evidence and no real errors, it's no_new_content
if (skip_line_count > 0 or has_skip_text) and not has_actual_error:
return ErrorType.NO_NEW_CONTENT, "No new content to download"
# Empty output with low return codes often means nothing to download
if return_code in (0, 1) and not stdout.strip():
return ErrorType.NO_NEW_CONTENT, "No new content to download"
# === PHASE 2: Check for specific error types ===
# Auth errors - look for error context, not just HTTP codes that appear in debug logs
# HTTP debug lines look like: '"GET /path HTTP/1.1" 401 None' - we need to detect actual errors
auth_error_patterns = [
@@ -370,29 +399,50 @@ class GalleryDLService:
if any(pattern in combined for pattern in rate_limit_patterns):
return ErrorType.RATE_LIMITED, "Rate limited by server - try increasing sleep time"
# 404 Not Found - check for HTTP 404 responses, not just "404" anywhere
# 404 Not Found - check for HTTP 404 responses with error context
# Be specific to avoid matching "not found" in debug messages like "archive entry not found"
not_found_patterns = [
'" 404 ', # HTTP response
"error 404",
"status: 404",
"not found",
"does not exist",
"no longer available",
'" 404 ', # HTTP response log line
"error 404", # Explicit error 404
"status: 404", # Status code indicator
"status code: 404", # Status code indicator
"404 not found", # Standard 404 message
"page not found", # Common 404 page text
"user not found", # User doesn't exist
"creator not found",
"artist not found",
"content no longer available",
"has been deleted",
"account.*deleted",
"profile.*not.*exist",
]
if any(pattern in combined for pattern in not_found_patterns):
# Also require error-level context for generic patterns to avoid false positives
generic_not_found = ["does not exist", "no longer available"]
has_specific_404 = any(pattern in combined for pattern in not_found_patterns)
has_generic_with_error = any(
pattern in combined and "][error]" in combined
for pattern in generic_not_found
)
if has_specific_404 or has_generic_with_error:
return ErrorType.NOT_FOUND, "URL not found - artist may have changed username or deleted content"
# Network errors
# Network errors - use specific patterns to avoid matching config values
# (e.g., "timeout": 30.0 in debug output should not trigger network_error)
network_error_patterns = [
"timeout",
"timed out",
"timed out", # Actual timeout occurred
"read timed out", # urllib3/requests timeout
"connect timed out", # Connection timeout
"connection timed out",
"connection refused",
"connection reset",
"network unreachable",
"name resolution",
"dns",
"ssl error",
"certificate verify",
"name resolution failed",
"name or service not known",
"nodename nor servname provided",
"ssl: certificate_verify_failed",
"ssl: wrong_version_number",
"certificate verify failed",
"[errno", # Socket errors like [Errno 111]
]
if any(pattern in combined for pattern in network_error_patterns):
return ErrorType.NETWORK_ERROR, "Network error - check internet connection"
@@ -418,10 +468,12 @@ class GalleryDLService:
if "no suitable" in combined or "unsupported" in combined or "no extractor" in combined:
return ErrorType.UNSUPPORTED_URL, "URL format not supported by gallery-dl"
# If we got here with return code 1 and saw some activity, it might just be no new content
if return_code == 1:
# If we got here with return code 1 and saw some activity but no error patterns matched,
# it's likely just no new content (gallery-dl returns 1 when nothing to download)
if return_code == 1 and stdout.strip() and not has_actual_error:
return ErrorType.NO_NEW_CONTENT, "No new content to download"
# Final fallback - genuine unknown error
return ErrorType.UNKNOWN_ERROR, f"Unknown error (return code: {return_code})"
def _count_downloaded_files(self, stdout: str) -> int: