Skip to content

Commit 03859cb

Browse files
committed
Resume incomplete download
1 parent dc7abca commit 03859cb

File tree

1 file changed

+36
-5
lines changed

1 file changed

+36
-5
lines changed

src/pip/_internal/network/download.py

+36-5
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,36 @@ def _get_http_response_size(resp: Response) -> Optional[int]:
2626
except (ValueError, KeyError, TypeError):
2727
return None
2828

29+
def _get_http_accept_ranges(resp: Response) -> Optional[str]:
30+
return resp.headers.get("accept-ranges", None)
31+
32+
def _resume_if_incomplete(
33+
resp: Response,
34+
session: PipSession,
35+
link: Link
36+
) -> Iterable[bytes]:
37+
chunks = response_chunks(resp, CONTENT_CHUNK_SIZE)
38+
received_length = 0
39+
40+
for chunk in chunks:
41+
received_length += len(chunk)
42+
yield chunk
43+
44+
total_length = _get_http_response_size(resp)
45+
accept_ranges = _get_http_accept_ranges(resp)
46+
47+
if total_length is not None and accept_ranges == 'bytes':
48+
while received_length < total_length:
49+
logger.info("Resuming incomplete download (%s received)", format_size(received_length))
50+
resume_resp = _http_get_download(session, link, range_start=received_length)
51+
resume_chunks = response_chunks(resume_resp, CONTENT_CHUNK_SIZE)
52+
for chunk in resume_chunks:
53+
received_length += len(chunk)
54+
yield chunk
2955

3056
def _prepare_download(
3157
resp: Response,
58+
session: PipSession,
3259
link: Link,
3360
progress_bar: str,
3461
) -> Iterable[bytes]:
@@ -60,7 +87,7 @@ def _prepare_download(
6087
else:
6188
show_progress = False
6289

63-
chunks = response_chunks(resp, CONTENT_CHUNK_SIZE)
90+
chunks = _resume_if_incomplete(resp, session, link)
6491

6592
if not show_progress:
6693
return chunks
@@ -112,9 +139,13 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
112139
return filename
113140

114141

115-
def _http_get_download(session: PipSession, link: Link) -> Response:
142+
def _http_get_download(session: PipSession, link: Link, range_start: Optional[int] = None) -> Response:
116143
target_url = link.url.split("#", 1)[0]
117-
resp = session.get(target_url, headers=HEADERS, stream=True)
144+
if range_start is not None:
145+
headers = {**HEADERS, "Range": "bytes={}-".format(range_start)}
146+
else:
147+
headers = HEADERS
148+
resp = session.get(target_url, headers=headers, stream=True)
118149
raise_for_status(resp)
119150
return resp
120151

@@ -142,7 +173,7 @@ def __call__(self, link: Link, location: str) -> Tuple[str, str]:
142173
filename = _get_http_response_filename(resp, link)
143174
filepath = os.path.join(location, filename)
144175

145-
chunks = _prepare_download(resp, link, self._progress_bar)
176+
chunks = _prepare_download(resp, self._session, link, self._progress_bar)
146177
with open(filepath, "wb") as content_file:
147178
for chunk in chunks:
148179
content_file.write(chunk)
@@ -178,7 +209,7 @@ def __call__(
178209
filename = _get_http_response_filename(resp, link)
179210
filepath = os.path.join(location, filename)
180211

181-
chunks = _prepare_download(resp, link, self._progress_bar)
212+
chunks = _prepare_download(resp, self._session, link, self._progress_bar)
182213
with open(filepath, "wb") as content_file:
183214
for chunk in chunks:
184215
content_file.write(chunk)

0 commit comments

Comments
 (0)