improve downloader retry behavior
- only retry download on 5xx and 429 status codes - immediately fail on 4xx status codes
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
- Improved retry behavior for failed HTTP downloads
|
||||||
|
|
||||||
## 1.0.1 - 2017-11-10
|
## 1.0.1 - 2017-11-10
|
||||||
- Added support for:
|
- Added support for:
|
||||||
- `xvideos` - https://www.xvideos.com/ ([#45](https://github.com/mikf/gallery-dl/issues/45))
|
- `xvideos` - https://www.xvideos.com/ ([#45](https://github.com/mikf/gallery-dl/issues/45))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from .. import config, util
|
from .. import config, util, exception
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class DownloaderBase():
|
|||||||
self.out.error(pathfmt.path, msg, tries, self.retries)
|
self.out.error(pathfmt.path, msg, tries, self.retries)
|
||||||
if tries >= self.retries:
|
if tries >= self.retries:
|
||||||
return False
|
return False
|
||||||
time.sleep(1)
|
time.sleep(tries)
|
||||||
tries += 1
|
tries += 1
|
||||||
|
|
||||||
# check for .part file
|
# check for .part file
|
||||||
@@ -74,6 +74,11 @@ class DownloaderBase():
|
|||||||
# connect to (remote) source
|
# connect to (remote) source
|
||||||
try:
|
try:
|
||||||
offset, size = self.connect(url, filesize)
|
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:
|
except Exception as exc:
|
||||||
msg = exc
|
msg = exc
|
||||||
continue
|
continue
|
||||||
@@ -83,8 +88,6 @@ class DownloaderBase():
|
|||||||
mode = "wb"
|
mode = "wb"
|
||||||
if filesize:
|
if filesize:
|
||||||
self.log.info("Unable to resume partial download")
|
self.log.info("Unable to resume partial download")
|
||||||
elif offset == -1:
|
|
||||||
break # early finish
|
|
||||||
else:
|
else:
|
||||||
mode = "ab"
|
mode = "ab"
|
||||||
self.log.info("Resuming download at byte %d", offset)
|
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.
|
Returns a 2-tuple containing the actual offset and expected filesize.
|
||||||
If the returned offset-value is greater than zero, all received data
|
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
|
will be appended to the existing .part file.
|
||||||
download will finish early and be considered successfull.
|
|
||||||
Return '0' as second tuple-field to indicate an unknown filesize.
|
Return '0' as second tuple-field to indicate an unknown filesize.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from .common import DownloaderBase
|
from .common import DownloaderBase
|
||||||
from .. import util
|
from .. import util, exception
|
||||||
|
|
||||||
|
|
||||||
class Downloader(DownloaderBase):
|
class Downloader(DownloaderBase):
|
||||||
@@ -33,14 +33,17 @@ class Downloader(DownloaderBase):
|
|||||||
timeout=self.timeout, verify=self.verify)
|
timeout=self.timeout, verify=self.verify)
|
||||||
|
|
||||||
code = self.response.status_code
|
code = self.response.status_code
|
||||||
if code == 200:
|
if code == 200: # OK
|
||||||
offset = 0
|
offset = 0
|
||||||
size = self.response.headers.get("Content-Length")
|
size = self.response.headers.get("Content-Length")
|
||||||
elif code == 206:
|
elif code == 206: # Partial Content
|
||||||
size = self.response.headers["Content-Range"].rpartition("/")[2]
|
size = self.response.headers["Content-Range"].rpartition("/")[2]
|
||||||
elif code == 416:
|
elif code == 416: # Requested Range Not Satisfiable
|
||||||
# file is already complete
|
raise exception.DownloadComplete()
|
||||||
return -1, 0
|
elif 400 <= code < 500 and code != 429: # Client Error
|
||||||
|
raise exception.DownloadError(
|
||||||
|
"{} Client Error: {} for url: {}".format(
|
||||||
|
code, self.response.reason, url))
|
||||||
else:
|
else:
|
||||||
self.response.raise_for_status()
|
self.response.raise_for_status()
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Exception
|
|||||||
| +-- AuthorizationError
|
| +-- AuthorizationError
|
||||||
| +-- NotFoundError
|
| +-- NotFoundError
|
||||||
| +-- HttpError
|
| +-- HttpError
|
||||||
|
+-- DownloadError
|
||||||
|
+-- DownloadComplete
|
||||||
+-- NoExtractorError
|
+-- NoExtractorError
|
||||||
+-- FormatError
|
+-- FormatError
|
||||||
+-- FilterError
|
+-- FilterError
|
||||||
@@ -48,6 +50,14 @@ class HttpError(ExtractionError):
|
|||||||
"""HTTP request during extraction failed"""
|
"""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):
|
class NoExtractorError(GalleryDLException):
|
||||||
"""No extractor can handle the given URL"""
|
"""No extractor can handle the given URL"""
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,10 @@ class TerminalOutput(NullOutput):
|
|||||||
if tries <= 1 and path:
|
if tries <= 1 and path:
|
||||||
print("\r", end="")
|
print("\r", end="")
|
||||||
safeprint(self.shorten(CHAR_ERROR + path))
|
safeprint(self.shorten(CHAR_ERROR + path))
|
||||||
|
if max_tries > 1:
|
||||||
|
error = "{} ({}/{})".format(error, tries, max_tries)
|
||||||
print("\r[Error] ", end="")
|
print("\r[Error] ", end="")
|
||||||
safeprint(error, end="")
|
safeprint(error)
|
||||||
print(" (", tries, "/", max_tries, ")", sep="")
|
|
||||||
|
|
||||||
def shorten(self, txt):
|
def shorten(self, txt):
|
||||||
"""Reduce the length of 'txt' to the width of the terminal"""
|
"""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):
|
def error(self, path, error, tries, max_tries):
|
||||||
if tries <= 1 and path:
|
if tries <= 1 and path:
|
||||||
print("\r\033[1;31m", self.shorten(path), sep="")
|
print("\r\033[1;31m", self.shorten(path), sep="")
|
||||||
print("\r\033[0;31m[Error]\033[0m ", error,
|
if max_tries > 1:
|
||||||
" (", tries, "/", max_tries, ")", sep="")
|
error = "{} ({}/{})".format(error, tries, max_tries)
|
||||||
|
print("\r\033[0;31m[Error]\033[0m", error)
|
||||||
|
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
|
|||||||
@@ -6,4 +6,4 @@
|
|||||||
# it under the terms of the GNU General Public License version 2 as
|
# it under the terms of the GNU General Public License version 2 as
|
||||||
# published by the Free Software Foundation.
|
# published by the Free Software Foundation.
|
||||||
|
|
||||||
__version__ = "1.0.1"
|
__version__ = "1.0.2-dev"
|
||||||
|
|||||||
Reference in New Issue
Block a user