improve downloader retry behavior

- only retry download on 5xx and 429 status codes
- immediately fail on 4xx status codes
pull/54/head
Mike Fährmann 7 years ago
parent 5ee8ca0319
commit 79bcaa8726
No known key found for this signature in database
GPG Key ID: 5680CA389D365A88

@ -1,5 +1,8 @@
# Changelog
## Unreleased
- Improved retry behavior for failed HTTP downloads
## 1.0.1 - 2017-11-10
- Added support for:
- `xvideos` - https://www.xvideos.com/ ([#45](https://github.com/mikf/gallery-dl/issues/45))

@ -11,7 +11,7 @@
import os
import time
import logging
from .. import config, util
from .. import config, util, exception
from requests.exceptions import RequestException
@ -65,7 +65,7 @@ class DownloaderBase():
self.out.error(pathfmt.path, msg, tries, self.retries)
if tries >= self.retries:
return False
time.sleep(1)
time.sleep(tries)
tries += 1
# check for .part file
@ -74,6 +74,11 @@ class DownloaderBase():
# connect to (remote) source
try:
offset, size = self.connect(url, filesize)
except exception.DownloadError as exc:
self.out.error(pathfmt.path, exc, 0, 0)
return False
except exception.DownloadComplete:
break
except Exception as exc:
msg = exc
continue
@ -83,8 +88,6 @@ class DownloaderBase():
mode = "wb"
if filesize:
self.log.info("Unable to resume partial download")
elif offset == -1:
break # early finish
else:
mode = "ab"
self.log.info("Resuming download at byte %d", offset)
@ -124,8 +127,7 @@ class DownloaderBase():
Returns a 2-tuple containing the actual offset and expected filesize.
If the returned offset-value is greater than zero, all received data
will be appended to the existing .part file. If it is '-1', the
download will finish early and be considered successfull.
will be appended to the existing .part file.
Return '0' as second tuple-field to indicate an unknown filesize.
"""

@ -10,7 +10,7 @@
import mimetypes
from .common import DownloaderBase
from .. import util
from .. import util, exception
class Downloader(DownloaderBase):
@ -33,14 +33,17 @@ class Downloader(DownloaderBase):
timeout=self.timeout, verify=self.verify)
code = self.response.status_code
if code == 200:
if code == 200: # OK
offset = 0
size = self.response.headers.get("Content-Length")
elif code == 206:
elif code == 206: # Partial Content
size = self.response.headers["Content-Range"].rpartition("/")[2]
elif code == 416:
# file is already complete
return -1, 0
elif code == 416: # Requested Range Not Satisfiable
raise exception.DownloadComplete()
elif 400 <= code < 500 and code != 429: # Client Error
raise exception.DownloadError(
"{} Client Error: {} for url: {}".format(
code, self.response.reason, url))
else:
self.response.raise_for_status()

@ -17,6 +17,8 @@ Exception
| +-- AuthorizationError
| +-- NotFoundError
| +-- HttpError
+-- DownloadError
+-- DownloadComplete
+-- NoExtractorError
+-- FormatError
+-- FilterError
@ -48,6 +50,14 @@ class HttpError(ExtractionError):
"""HTTP request during extraction failed"""
class DownloadError(GalleryDLException):
"""Error during file download"""
class DownloadComplete(GalleryDLException):
"""Output file of attempted download is already complete"""
class NoExtractorError(GalleryDLException):
"""No extractor can handle the given URL"""

@ -89,9 +89,10 @@ class TerminalOutput(NullOutput):
if tries <= 1 and path:
print("\r", end="")
safeprint(self.shorten(CHAR_ERROR + path))
if max_tries > 1:
error = "{} ({}/{})".format(error, tries, max_tries)
print("\r[Error] ", end="")
safeprint(error, end="")
print(" (", tries, "/", max_tries, ")", sep="")
safeprint(error)
def shorten(self, txt):
"""Reduce the length of 'txt' to the width of the terminal"""
@ -119,8 +120,9 @@ class ColorOutput(TerminalOutput):
def error(self, path, error, tries, max_tries):
if tries <= 1 and path:
print("\r\033[1;31m", self.shorten(path), sep="")
print("\r\033[0;31m[Error]\033[0m ", error,
" (", tries, "/", max_tries, ")", sep="")
if max_tries > 1:
error = "{} ({}/{})".format(error, tries, max_tries)
print("\r\033[0;31m[Error]\033[0m", error)
if os.name == "nt":

@ -6,4 +6,4 @@
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
__version__ = "1.0.1"
__version__ = "1.0.2-dev"

Loading…
Cancel
Save