Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/sage_bootstrap/download/transfer.py
7406 views
1
# -*- coding: utf-8 -*-
2
"""
3
Download files from the internet
4
"""
5
6
7
#*****************************************************************************
8
# Copyright (C) 2015 Volker Braun <[email protected]>
9
#
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation, either version 2 of the License, or
13
# (at your option) any later version.
14
# http://www.gnu.org/licenses/
15
#*****************************************************************************
16
17
18
import sys
19
import logging
20
log = logging.getLogger()
21
22
from sage_bootstrap.stdio import flush
23
import urllib
24
from urllib.request import build_opener, install_opener, urlretrieve
25
26
27
class ProgressBar(object):
28
"""
29
Progress bar as urllib reporthook
30
"""
31
32
def __init__(self, stream, length=70):
33
self.length = length
34
self.progress = 0
35
self.stream = stream
36
37
def start(self):
38
flush() # make sure to not interleave stdout/stderr
39
self.stream.write('[')
40
self.stream.flush()
41
42
def __call__(self, chunks_so_far, chunk_size, total_size):
43
if total_size == -1: # we do not know size
44
n = 0 if chunks_so_far == 0 else self.length // 2
45
else:
46
n = chunks_so_far * chunk_size * self.length // total_size
47
if n > self.length:
48
# If there is a Content-Length, this will be sent as the last progress
49
return
50
# n ranges from 0 to length*total (exclude), so we'll print at most length dots
51
if n >= self.progress:
52
self.stream.write('.' * (n-self.progress))
53
self.stream.flush()
54
self.progress = n
55
56
def stop(self):
57
missing = '.' * (self.length - self.progress)
58
self.stream.write(missing + ']\n')
59
self.stream.flush()
60
61
def error_stop(self):
62
missing = 'x' * (self.length - self.progress)
63
self.stream.write(missing + ']\n')
64
self.stream.flush()
65
66
67
class DownloadError(IOError):
68
pass
69
70
71
class Download(object):
72
"""
73
Download URL
74
75
Right now, only via HTTP
76
77
This should work for FTP as well but, in fact, hangs on python <
78
3.4, see http://bugs.python.org/issue16270
79
80
INPUT:
81
82
- ``url`` -- string. The URL to download.
83
84
- ``destination`` -- string or ``None`` (default). The destination
85
file name to save to. If not specified, the file is written to
86
stdout.
87
88
- ``progress`` -- boolean (default: ``True``). Whether to print a
89
progress bar to stderr. For testing, this can also be a stream
90
to which the progress bar is being sent.
91
92
- ``ignore_errors`` -- boolean (default: ``False``). Catch network
93
errors (a message is still being logged).
94
"""
95
96
def __init__(self, url, destination=None, progress=True, ignore_errors=False):
97
self.url = url
98
self.destination = destination or '/dev/stdout'
99
self.progress = (progress is not False)
100
self.progress_stream = sys.stderr if isinstance(progress, bool) else progress
101
self.ignore_errors = ignore_errors
102
103
def http_error_default(self, url, fp, errcode, errmsg, headers):
104
"""
105
Callback for the URLopener to raise an exception on HTTP errors
106
"""
107
fp.close()
108
raise DownloadError(errcode, errmsg, url)
109
110
def start_progress_bar(self):
111
if self.progress:
112
self.progress_bar = ProgressBar(self.progress_stream)
113
self.progress_bar.start()
114
115
def success_progress_bar(self):
116
if self.progress:
117
self.progress_bar.stop()
118
119
def error_progress_bar(self):
120
if self.progress:
121
self.progress_bar.error_stop()
122
123
def run(self):
124
opener = build_opener()
125
install_opener(opener)
126
127
opener.http_error_default = self.http_error_default
128
self.start_progress_bar()
129
try:
130
if self.progress:
131
urlretrieve(self.url, self.destination, reporthook=self.progress_bar)
132
else:
133
urlretrieve(self.url, self.destination)
134
135
except IOError as error:
136
self.error_progress_bar()
137
log.error(error)
138
if not self.ignore_errors:
139
raise error
140
self.success_progress_bar()
141
142