fix(downloads): don't classify per-item download 404s as source-level Not Found
gallery-dl logs `[download][error] Failed to download <file>.part` when a single post attachment's media URL expires or is deleted, then recovers and proceeds with the rest of the run. Our error categorizer was letting a urllib3 debug line like `"HEAD /media-u/v3/<id> HTTP/1.1" 404 0` match the NOT_FOUND_PATTERNS list, flagging the whole source as deleted even though gallery-dl kept downloading subsequent items successfully. Adds "failed to download" to the per-item allowlist so the mask flips when skip activity is present, matching the shape of the campaign-ID fix. Total-failure runs still classify as failures. Also renames the Download Details modal's "Error Log" panel to "Verbose Log (stderr)" — gallery-dl's `-v` flag sends all logging (including [debug] lines) to stderr, so the original label was misleading. Drops the blanket red text color for the same reason.
This commit is contained in:
@@ -441,11 +441,15 @@ class GalleryDLService:
|
|||||||
# - "failed to extract campaign id": logged when the vanity-URL HTML path
|
# - "failed to extract campaign id": logged when the vanity-URL HTML path
|
||||||
# fails, but gallery-dl falls back to the /cw/<vanity> redirect and
|
# fails, but gallery-dl falls back to the /cw/<vanity> redirect and
|
||||||
# recovers the campaign_id on its own.
|
# recovers the campaign_id on its own.
|
||||||
|
# - "failed to download": per-item download failure (e.g., a single
|
||||||
|
# Patreon attachment whose media URL expired / was deleted). The
|
||||||
|
# downloader logs [download][error] and moves to the next item.
|
||||||
if has_actual_error and (skip_line_count > 0 or has_skip_text):
|
if has_actual_error and (skip_line_count > 0 or has_skip_text):
|
||||||
per_item_patterns = [
|
per_item_patterns = [
|
||||||
"not allowed to view",
|
"not allowed to view",
|
||||||
"unable to get post",
|
"unable to get post",
|
||||||
"failed to extract campaign id",
|
"failed to extract campaign id",
|
||||||
|
"failed to download",
|
||||||
]
|
]
|
||||||
error_lines = [
|
error_lines = [
|
||||||
line for line in combined.split('\n')
|
line for line in combined.split('\n')
|
||||||
|
|||||||
@@ -56,3 +56,40 @@ def test_patreon_campaign_id_error_alongside_real_error_still_fails():
|
|||||||
error_type, _message = service._categorize_error(1, stdout, stderr)
|
error_type, _message = service._categorize_error(1, stdout, stderr)
|
||||||
|
|
||||||
assert error_type == ErrorType.AUTH_ERROR
|
assert error_type == ErrorType.AUTH_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
def test_patreon_per_item_download_404_with_skips_is_no_new_content():
|
||||||
|
"""Per-item 'Failed to download' + urllib3 404 HEAD + skips → no_new_content.
|
||||||
|
|
||||||
|
Real-world trigger: a Patreon post's media URL (e.g., /media-u/v3/<id>)
|
||||||
|
expired or was deleted. gallery-dl logs [download][error] and moves on;
|
||||||
|
other items skip cleanly. We shouldn't classify the whole source NOT_FOUND
|
||||||
|
just because one attachment 404'd.
|
||||||
|
"""
|
||||||
|
service = _make_service()
|
||||||
|
stderr = (
|
||||||
|
"[download][error] Failed to download 02_46004845.part\n"
|
||||||
|
'[urllib3.connectionpool][debug] "HEAD /media-u/v3/46004845 HTTP/1.1" 404 0\n'
|
||||||
|
"[downloader.http][warning] '404 OK' for 'https://www.patreon.com/media-u/v3/46004845'\n"
|
||||||
|
"[download][error] Failed to download 03_46004845.part\n"
|
||||||
|
"[patreon][debug] skipping https://c10.patreonusercontent.com/4/patreon-media/p/post/30953341/.../1.png (abc image_large)\n"
|
||||||
|
)
|
||||||
|
stdout = ""
|
||||||
|
|
||||||
|
error_type, _message = service._categorize_error(1, stdout, stderr)
|
||||||
|
|
||||||
|
assert error_type == ErrorType.NO_NEW_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
def test_patreon_per_item_download_failure_without_skips_still_fails():
|
||||||
|
"""Same 'Failed to download' error but no skip activity → real failure."""
|
||||||
|
service = _make_service()
|
||||||
|
stderr = (
|
||||||
|
"[download][error] Failed to download 02_46004845.part\n"
|
||||||
|
'[urllib3.connectionpool][debug] "HEAD /media-u/v3/46004845 HTTP/1.1" 404 0\n'
|
||||||
|
)
|
||||||
|
stdout = ""
|
||||||
|
|
||||||
|
error_type, _message = service._categorize_error(1, stdout, stderr)
|
||||||
|
|
||||||
|
assert error_type != ErrorType.NO_NEW_CONTENT
|
||||||
|
|||||||
@@ -334,9 +334,9 @@
|
|||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
<v-expansion-panel v-if="selectedDownload.metadata.stderr">
|
<v-expansion-panel v-if="selectedDownload.metadata.stderr">
|
||||||
<v-expansion-panel-title>Error Log</v-expansion-panel-title>
|
<v-expansion-panel-title>Verbose Log (stderr)</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<pre class="text-caption text-error" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ selectedDownload.metadata.stderr }}</pre>
|
<pre class="text-caption" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ selectedDownload.metadata.stderr }}</pre>
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
|
|||||||
Reference in New Issue
Block a user