Skip to content

Commit

Permalink
Use resumable-urlretrieve to resume interrupted downloads
Browse files Browse the repository at this point in the history
it's a dependency selected with extras_require for compatible Python versions, so it'll degrade gracefully on older Pythons
to the normal code that will restart the download every time
  • Loading branch information
berdario committed Nov 10, 2015
1 parent b66fe7e commit df90eef
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 34 deletions.
73 changes: 49 additions & 24 deletions pythonz/downloader.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
import sys
from mmap import mmap as _mmap, ACCESS_READ
from hashlib import sha256
import os

from pythonz.util import PY3K
from pythonz.log import logger

if PY3K:
mmap = _mmap
from urllib.request import Request, urlopen, urlretrieve
from urllib.request import Request, urlopen
else:
from contextlib import closing
mmap = lambda *args, **kwargs: closing(_mmap(*args, **kwargs))
from urllib import urlretrieve
from urllib2 import urlopen, Request

try:
from resumable import urlretrieve, DownloadError, sha256
except ImportError:
from mmap import mmap as _mmap, ACCESS_READ
import hashlib
if PY3K:
from urllib.request import urlretrieve as _urlretrieve
mmap = _mmap
else:
from contextlib import closing
mmap = lambda *args, **kwargs: closing(_mmap(*args, **kwargs))
from urllib import urlretrieve as _urlretrieve

class DownloadError(Exception):
"""Exception during download"""

def sha256(filename):
with open(filename, 'rb') as f:
with mmap(f.fileno(), 0, access=ACCESS_READ) as m:
return hashlib.sha256(m).hexdigest()


def validate_sha256(filename, sha256sum):
if sha256sum is not None:
return sha256(filename) == sha256sum
else:
logger.warning("sha256sum unavailable, skipping verification.\nMake "
"sure that the server you're downloading from is trusted")
return True


def urlretrieve(url, filename, reporthook, sha256sum):
try:
_urlretrieve(url, filename, reporthook)
if not validate_sha256(filename, sha256sum):
raise DownloadError("Corrupted download, the sha256 doesn't match")
except BaseException:
os.unlink(filename)
raise



class ProgressBar(object):
def __init__(self, out=sys.stdout):
Expand Down Expand Up @@ -48,10 +84,6 @@ def get_method(self):
return "HEAD"


class DownloadError(Exception):
"""Exception during download"""


class Downloader(object):

@classmethod
Expand All @@ -76,22 +108,15 @@ def read_head_info(cls, url):
return res.info()

@classmethod
def fetch(cls, url, filename):
def fetch(cls, url, filename, expected_sha256):
b = ProgressBar()
try:
urlretrieve(url, filename, b.reporthook)
urlretrieve(url, filename, b.reporthook, sha256sum=expected_sha256)
sys.stdout.write('\n')
except DownloadError:
if os.path.exists(filename):
os.unlink(filename)
raise
except IOError:
sys.stdout.write('\n')
raise DownloadError('Failed to fetch %s from %s' % (filename, url))


def validate_sha256(filename, sha256sum):
if sha256sum is not None:
with open(filename, 'rb') as f:
with mmap(f.fileno(), 0, access=ACCESS_READ) as m:
return sha256(m).hexdigest() == sha256sum
else:
logger.warning("sha256sum unavailable, skipping verification.\nMake "
"sure that the server you're downloading from is trusted")
return True
13 changes: 4 additions & 9 deletions pythonz/installer/pythoninstaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
get_macosx_deployment_target, Version, is_python25, is_python24, is_python33
from pythonz.define import PATH_BUILD, PATH_DISTS, PATH_PYTHONS, PATH_LOG, \
PATH_PATCHES_ALL, PATH_PATCHES_OSX
from pythonz.downloader import Downloader, DownloadError, validate_sha256
from pythonz.downloader import Downloader, DownloadError, sha256
from pythonz.log import logger


Expand Down Expand Up @@ -100,22 +100,17 @@ def expected_sha256(self):
return self.supported_versions.get(self.pkg.version)

def download(self):
if os.path.isfile(self.download_file):
if os.path.isfile(self.download_file) and sha256(self.download_file) == self.expected_sha256:
logger.info("Use the previously fetched %s" % (self.download_file))
else:
base_url = Link(self.download_url).base_url
logger.info("Downloading %s as %s" % (base_url, self.download_file))
try:
Downloader.fetch(self.download_url, self.download_file)
Downloader.fetch(self.download_url, self.download_file, self.expected_sha256)
except DownloadError:
unlink(self.download_file)
logger.error("Failed to download.\n%s" % (sys.exc_info()[1]))
sys.exit(1)
except:
unlink(self.download_file)
raise
if not validate_sha256(self.download_file, self.expected_sha256):
raise DownloadError("Corrupted download, the sha256 doesn't match")


def install(self):
raise NotImplementedError
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def get_version():
url = 'https://github.com/saghul/pythonz',
license = 'MIT',
packages = find_packages('pythonz'),
extras_require = {':python_version>="3.5"': ['resumable-urlretrieve']},
include_package_data = True,
entry_points = dict(console_scripts=['pythonz=pythonz:main',
'pythonz_install=pythonz.installer:install_pythonz']),
Expand All @@ -40,4 +41,3 @@ def get_version():
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
])

0 comments on commit df90eef

Please sign in to comment.