diff --git a/aqt/archives.py b/aqt/archives.py index 3fd2553..41c9e0b 100644 --- a/aqt/archives.py +++ b/aqt/archives.py @@ -25,6 +25,7 @@ import requests import traceback import xml.etree.ElementTree as ElementTree from six import StringIO +import aqt.metalink class QtPackage: @@ -69,7 +70,7 @@ class QtArchives: # Get packages index update_xml_url = "{0}Updates.xml".format(archive_url) try: - r = requests.get(update_xml_url) + r = aqt.metalink.get(update_xml_url) except requests.exceptions.ConnectionError as e: print("Caught download error: %s" % e.args) exc_buffer = StringIO() diff --git a/aqt/installer.py b/aqt/installer.py index 195a30a..7d189ce 100644 --- a/aqt/installer.py +++ b/aqt/installer.py @@ -26,7 +26,7 @@ import platform import requests import sys import traceback -import xml.etree.ElementTree as ElementTree +import aqt.metalink from six import StringIO from multiprocessing.dummy import Pool if sys.version_info.major == 3: @@ -50,7 +50,7 @@ class QtInstaller: sys.stdout.write("\033[K") print("-Downloading {}...".format(url)) try: - r = _get(url, stream=True) + r = aqt.metalink.get(url, stream=True) except requests.exceptions.ConnectionError as e: print("Caught download error: %s" % e.args) exc_buffer = StringIO() @@ -111,57 +111,3 @@ class QtInstaller: traceback.print_exc(file=exc_buffer) logging.error('Error happened when writing configuration files:\n%s', exc_buffer.getvalue()) raise e - - -def _get(url, stream=False): - r = requests.get(url, stream=stream, allow_redirects=False) - if r.status_code == 302: - # tsinghua.edu.cn is problematic and it prohibit service to specific geo location. - # we will use another redirected location for that. - newurl = r.headers['Location'] - blacklist = ['http://mirrors.tuna.tsinghua.edu.cn'] - for b in blacklist: - if newurl.startswith(b): - mml = Metalink(url) - newurl = mml.altlink(blacklist=blacklist) - break - r = requests.get(newurl, stream=stream) - return r - - -class Metalink: - '''Download .meta4 metalink version4 xml file and parse it.''' - - def __init__(self, url, candidate=None): - self.mirrors = {} - self.url = url - self.candidate = candidate - m = requests.get(url + '.meta4') - mirror_xml = ElementTree.fromstring(m.text) - for f in mirror_xml.iter("{urn:ietf:params:xml:ns:metalink}file"): - for u in f.iter("{urn:ietf:params:xml:ns:metalink}url"): - pri = u.attrib['priority'] - self.mirrors[pri] = u.text - - def altlink(self, priority=None, blacklist=None): - if len(self.mirrors) == 0: - # no alternative - if self.candidate is not None: - return self.candidate - else: - return self.url - if priority is None: - if blacklist is not None: - for ind in range(len(self.mirrors)): - mirror = self.mirrors[str(ind + 1)] - if mirror in blacklist: - continue - return mirror - else: - for ind in range(len(self.mirrors)): - mirror = self.mirrors[str(ind + 1)] - if mirror == self.candidate: - continue - return mirror - else: - return self.mirrors[str(priority)] diff --git a/aqt/metalink.py b/aqt/metalink.py new file mode 100644 index 0000000..640d37a --- /dev/null +++ b/aqt/metalink.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 Linus Jahn +# Copyright (C) 2019 Hiroshi Miura +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import requests +import xml.etree.ElementTree as ElementTree + + +def get(url, stream=False): + r = requests.get(url, stream=stream, allow_redirects=False) + if r.status_code == 302: + # tsinghua.edu.cn is problematic and it prohibit service to specific geo location. + # we will use another redirected location for that. + newurl = r.headers['Location'] + blacklist = ['https://mirrors.tuna.tsinghua.edu.cn', 'http://mirrors.tuna.tsinghua.edu.cn'] + for b in blacklist: + if newurl.startswith(b): + mml = Metalink(url) + newurl = mml.altlink(blacklist=blacklist) + break + r = requests.get(newurl, stream=stream) + return r + + +class Metalink: + '''Download .meta4 metalink version4 xml file and parse it.''' + + def __init__(self, url, candidate=None): + self.mirrors = {} + self.url = url + self.candidate = candidate + m = requests.get(url + '.meta4') + mirror_xml = ElementTree.fromstring(m.text) + for f in mirror_xml.iter("{urn:ietf:params:xml:ns:metalink}file"): + for u in f.iter("{urn:ietf:params:xml:ns:metalink}url"): + pri = u.attrib['priority'] + self.mirrors[pri] = u.text + + def altlink(self, priority=None, blacklist=None): + if len(self.mirrors) == 0: + # no alternative + if self.candidate is not None: + return self.candidate + else: + return self.url + if priority is None: + if blacklist is not None: + for ind in range(len(self.mirrors)): + mirror = self.mirrors[str(ind + 1)] + if mirror in blacklist: + continue + return mirror + else: + for ind in range(len(self.mirrors)): + mirror = self.mirrors[str(ind + 1)] + if mirror == self.candidate: + continue + return mirror + else: + return self.mirrors[str(priority)]