diff --git a/backend/app/services/gallery_dl.py b/backend/app/services/gallery_dl.py index 8b70954..cf78560 100644 --- a/backend/app/services/gallery_dl.py +++ b/backend/app/services/gallery_dl.py @@ -441,11 +441,15 @@ class GalleryDLService: # - "failed to extract campaign id": logged when the vanity-URL HTML path # fails, but gallery-dl falls back to the /cw/ redirect and # 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): per_item_patterns = [ "not allowed to view", "unable to get post", "failed to extract campaign id", + "failed to download", ] error_lines = [ line for line in combined.split('\n') diff --git a/backend/tests/services/test_gallery_dl_categorize.py b/backend/tests/services/test_gallery_dl_categorize.py index 6740dbe..0cbaaa1 100644 --- a/backend/tests/services/test_gallery_dl_categorize.py +++ b/backend/tests/services/test_gallery_dl_categorize.py @@ -56,3 +56,40 @@ def test_patreon_campaign_id_error_alongside_real_error_still_fails(): error_type, _message = service._categorize_error(1, stdout, stderr) 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/) + 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 diff --git a/frontend/src/views/Downloads.vue b/frontend/src/views/Downloads.vue index 6844362..dfeb02e 100644 --- a/frontend/src/views/Downloads.vue +++ b/frontend/src/views/Downloads.vue @@ -334,9 +334,9 @@ - Error Log + Verbose Log (stderr) -
{{ selectedDownload.metadata.stderr }}
+
{{ selectedDownload.metadata.stderr }}