Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/sage_bootstrap/download/transfer.py
4055 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
from sage_bootstrap.compat import urllib
24
25
26
class ProgressBar(object):
27
"""
28
Progress bar as urllib reporthook
29
"""
30
31
def __init__(self, stream, length=70):
32
self.length = length
33
self.progress = 0
34
self.stream = stream
35
36
def start(self):
37
flush() # make sure to not interleave stdout/stderr
38
self.stream.write('[')
39
self.stream.flush()
40
41
def __call__(self, chunks_so_far, chunk_size, total_size):
42
if total_size == -1: # we do not know size
43
n = 0 if chunks_so_far == 0 else self.length // 2
44
else:
45
n = chunks_so_far * chunk_size * self.length // total_size
46
if n > self.length:
47
# If there is a Content-Length, this will be sent as the last progress
48
return
49
# n ranges from 0 to length*total (exclude), so we'll print at most length dots
50
if n >= self.progress:
51
self.stream.write('.' * (n-self.progress))
52
self.stream.flush()
53
self.progress = n
54
55
def stop(self):
56
missing = '.' * (self.length - self.progress)
57
self.stream.write(missing + ']\n')
58
self.stream.flush()
59
60
def error_stop(self):
61
missing = 'x' * (self.length - self.progress)
62
self.stream.write(missing + ']\n')
63
self.stream.flush()
64
65
66
class DownloadError(IOError):
67
pass
68
69
70
class Download(object):
71
"""
72
Download URL
73
74
Right now, only via HTTP
75
76
This should work for FTP as well but, in fact, hangs on python <
77
3.4, see http://bugs.python.org/issue16270
78
79
INPUT:
80
81
- ``url`` -- string. The URL to download.
82
83
- ``destination`` -- string or ``None`` (default). The destination
84
file name to save to. If not specified, the file is written to
85
stdout.
86
87
- ``progress`` -- boolean (default: ``True``). Whether to print a
88
progress bar to stderr. For testing, this can also be a stream
89
to which the progress bar is being sent.
90
91
- ``ignore_errors`` -- boolean (default: ``False``). Catch network
92
errors (a message is still being logged).
93
"""
94
95
def __init__(self, url, destination=None, progress=True, ignore_errors=False):
96
self.url = url
97
self.destination = destination or '/dev/stdout'
98
self.progress = (progress is not False)
99
self.progress_stream = sys.stderr if isinstance(progress, bool) else progress
100
self.ignore_errors = ignore_errors
101
102
def http_error_default(self, url, fp, errcode, errmsg, headers):
103
"""
104
Callback for the URLopener to raise an exception on HTTP errors
105
"""
106
fp.close()
107
raise DownloadError(errcode, errmsg, url)
108
109
def start_progress_bar(self):
110
if self.progress:
111
self.progress_bar = ProgressBar(self.progress_stream)
112
self.progress_bar.start()
113
114
def success_progress_bar(self):
115
if self.progress:
116
self.progress_bar.stop()
117
118
def error_progress_bar(self):
119
if self.progress:
120
self.progress_bar.error_stop()
121
122
def run(self):
123
opener = urllib.FancyURLopener()
124
opener.http_error_default = self.http_error_default
125
self.start_progress_bar()
126
try:
127
if self.progress:
128
filename, info = opener.retrieve(
129
self.url, self.destination, self.progress_bar)
130
else:
131
filename, info = opener.retrieve(
132
self.url, self.destination)
133
except IOError as error:
134
self.error_progress_bar()
135
log.error(error)
136
if not self.ignore_errors:
137
raise error
138
self.success_progress_bar()
139
140