Path: blob/develop/build/sage_bootstrap/download/transfer.py
4055 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 flush22from sage_bootstrap.compat import urllib232425class ProgressBar(object):26"""27Progress bar as urllib reporthook28"""2930def __init__(self, stream, length=70):31self.length = length32self.progress = 033self.stream = stream3435def start(self):36flush() # make sure to not interleave stdout/stderr37self.stream.write('[')38self.stream.flush()3940def __call__(self, chunks_so_far, chunk_size, total_size):41if total_size == -1: # we do not know size42n = 0 if chunks_so_far == 0 else self.length // 243else:44n = chunks_so_far * chunk_size * self.length // total_size45if n > self.length:46# If there is a Content-Length, this will be sent as the last progress47return48# n ranges from 0 to length*total (exclude), so we'll print at most length dots49if n >= self.progress:50self.stream.write('.' * (n-self.progress))51self.stream.flush()52self.progress = n5354def stop(self):55missing = '.' * (self.length - self.progress)56self.stream.write(missing + ']\n')57self.stream.flush()5859def error_stop(self):60missing = 'x' * (self.length - self.progress)61self.stream.write(missing + ']\n')62self.stream.flush()636465class DownloadError(IOError):66pass676869class Download(object):70"""71Download URL7273Right now, only via HTTP7475This should work for FTP as well but, in fact, hangs on python <763.4, see http://bugs.python.org/issue162707778INPUT:7980- ``url`` -- string. The URL to download.8182- ``destination`` -- string or ``None`` (default). The destination83file name to save to. If not specified, the file is written to84stdout.8586- ``progress`` -- boolean (default: ``True``). Whether to print a87progress bar to stderr. For testing, this can also be a stream88to which the progress bar is being sent.8990- ``ignore_errors`` -- boolean (default: ``False``). Catch network91errors (a message is still being logged).92"""9394def __init__(self, url, destination=None, progress=True, ignore_errors=False):95self.url = url96self.destination = destination or '/dev/stdout'97self.progress = (progress is not False)98self.progress_stream = sys.stderr if isinstance(progress, bool) else progress99self.ignore_errors = ignore_errors100101def http_error_default(self, url, fp, errcode, errmsg, headers):102"""103Callback for the URLopener to raise an exception on HTTP errors104"""105fp.close()106raise DownloadError(errcode, errmsg, url)107108def start_progress_bar(self):109if self.progress:110self.progress_bar = ProgressBar(self.progress_stream)111self.progress_bar.start()112113def success_progress_bar(self):114if self.progress:115self.progress_bar.stop()116117def error_progress_bar(self):118if self.progress:119self.progress_bar.error_stop()120121def run(self):122opener = urllib.FancyURLopener()123opener.http_error_default = self.http_error_default124self.start_progress_bar()125try:126if self.progress:127filename, info = opener.retrieve(128self.url, self.destination, self.progress_bar)129else:130filename, info = opener.retrieve(131self.url, self.destination)132except IOError as error:133self.error_progress_bar()134log.error(error)135if not self.ignore_errors:136raise error137self.success_progress_bar()138139140