Path: blob/develop/build/sage_bootstrap/download/transfer.py
7406 views
# -*- coding: utf-8 -*-1"""2Download files from the internet3"""456#*****************************************************************************7# Copyright (C) 2015 Volker Braun <[email protected]>8#9# This program is free software: you can redistribute it and/or modify10# it under the terms of the GNU General Public License as published by11# the Free Software Foundation, either version 2 of the License, or12# (at your option) any later version.13# http://www.gnu.org/licenses/14#*****************************************************************************151617import sys18import logging19log = logging.getLogger()2021from sage_bootstrap.stdio import flush22import urllib23from urllib.request import build_opener, install_opener, urlretrieve242526class ProgressBar(object):27"""28Progress bar as urllib reporthook29"""3031def __init__(self, stream, length=70):32self.length = length33self.progress = 034self.stream = stream3536def start(self):37flush() # make sure to not interleave stdout/stderr38self.stream.write('[')39self.stream.flush()4041def __call__(self, chunks_so_far, chunk_size, total_size):42if total_size == -1: # we do not know size43n = 0 if chunks_so_far == 0 else self.length // 244else:45n = chunks_so_far * chunk_size * self.length // total_size46if n > self.length:47# If there is a Content-Length, this will be sent as the last progress48return49# n ranges from 0 to length*total (exclude), so we'll print at most length dots50if n >= self.progress:51self.stream.write('.' * (n-self.progress))52self.stream.flush()53self.progress = n5455def stop(self):56missing = '.' * (self.length - self.progress)57self.stream.write(missing + ']\n')58self.stream.flush()5960def error_stop(self):61missing = 'x' * (self.length - self.progress)62self.stream.write(missing + ']\n')63self.stream.flush()646566class DownloadError(IOError):67pass686970class Download(object):71"""72Download URL7374Right now, only via HTTP7576This should work for FTP as well but, in fact, hangs on python <773.4, see http://bugs.python.org/issue162707879INPUT:8081- ``url`` -- string. The URL to download.8283- ``destination`` -- string or ``None`` (default). The destination84file name to save to. If not specified, the file is written to85stdout.8687- ``progress`` -- boolean (default: ``True``). Whether to print a88progress bar to stderr. For testing, this can also be a stream89to which the progress bar is being sent.9091- ``ignore_errors`` -- boolean (default: ``False``). Catch network92errors (a message is still being logged).93"""9495def __init__(self, url, destination=None, progress=True, ignore_errors=False):96self.url = url97self.destination = destination or '/dev/stdout'98self.progress = (progress is not False)99self.progress_stream = sys.stderr if isinstance(progress, bool) else progress100self.ignore_errors = ignore_errors101102def http_error_default(self, url, fp, errcode, errmsg, headers):103"""104Callback for the URLopener to raise an exception on HTTP errors105"""106fp.close()107raise DownloadError(errcode, errmsg, url)108109def start_progress_bar(self):110if self.progress:111self.progress_bar = ProgressBar(self.progress_stream)112self.progress_bar.start()113114def success_progress_bar(self):115if self.progress:116self.progress_bar.stop()117118def error_progress_bar(self):119if self.progress:120self.progress_bar.error_stop()121122def run(self):123opener = build_opener()124install_opener(opener)125126opener.http_error_default = self.http_error_default127self.start_progress_bar()128try:129if self.progress:130urlretrieve(self.url, self.destination, reporthook=self.progress_bar)131else:132urlretrieve(self.url, self.destination)133134except IOError as error:135self.error_progress_bar()136log.error(error)137if not self.ignore_errors:138raise error139self.success_progress_bar()140141142