| Index: third_party/mozdownload/mozdownload/scraper.py
|
| ===================================================================
|
| --- third_party/mozdownload/mozdownload/scraper.py (revision 0)
|
| +++ third_party/mozdownload/mozdownload/scraper.py (revision 0)
|
| @@ -0,0 +1,835 @@
|
| +#!/usr/bin/env python
|
| +
|
| +# This Source Code Form is subject to the terms of the Mozilla Public
|
| +# License, v. 2.0. If a copy of the MPL was not distributed with this
|
| +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| +
|
| +"""Module to handle downloads for different types of Firefox and Thunderbird builds."""
|
| +
|
| +
|
| +from datetime import datetime
|
| +from optparse import OptionParser, OptionGroup
|
| +import os
|
| +import re
|
| +import sys
|
| +import time
|
| +import urllib
|
| +import urllib2
|
| +
|
| +import mozinfo
|
| +
|
| +from parser import DirectoryParser
|
| +from timezones import PacificTimezone
|
| +
|
| +
|
| +APPLICATIONS = ['b2g', 'firefox', 'thunderbird']
|
| +
|
| +# Base URL for the path to all builds
|
| +BASE_URL = 'https://ftp.mozilla.org/pub/mozilla.org'
|
| +
|
| +PLATFORM_FRAGMENTS = {'linux': 'linux-i686',
|
| + 'linux64': 'linux-x86_64',
|
| + 'mac': 'mac',
|
| + 'mac64': 'mac64',
|
| + 'win32': 'win32',
|
| + 'win64': 'win64-x86_64'}
|
| +
|
| +DEFAULT_FILE_EXTENSIONS = {'linux': 'tar.bz2',
|
| + 'linux64': 'tar.bz2',
|
| + 'mac': 'dmg',
|
| + 'mac64': 'dmg',
|
| + 'win32': 'exe',
|
| + 'win64': 'exe'}
|
| +
|
| +class NotFoundException(Exception):
|
| + """Exception for a resource not being found (e.g. no logs)"""
|
| + def __init__(self, message, location):
|
| + self.location = location
|
| + Exception.__init__(self, ': '.join([message, location]))
|
| +
|
| +
|
| +class Scraper(object):
|
| + """Generic class to download an application from the Mozilla server"""
|
| +
|
| + def __init__(self, directory, version, platform=None,
|
| + application='firefox', locale='en-US', extension=None,
|
| + authentication=None, retry_attempts=3, retry_delay=10):
|
| +
|
| + # Private properties for caching
|
| + self._target = None
|
| + self._binary = None
|
| +
|
| + self.directory = directory
|
| + self.locale = locale
|
| + self.platform = platform or self.detect_platform()
|
| + self.version = version
|
| + self.extension = extension or DEFAULT_FILE_EXTENSIONS[self.platform]
|
| + self.authentication = authentication
|
| + self.retry_attempts = retry_attempts
|
| + self.retry_delay = retry_delay
|
| +
|
| + # build the base URL
|
| + self.application = application
|
| + self.base_url = '/'.join([BASE_URL, self.application])
|
| +
|
| +
|
| + @property
|
| + def binary(self):
|
| + """Return the name of the build"""
|
| +
|
| + if self._binary is None:
|
| + # Retrieve all entries from the remote virtual folder
|
| + parser = DirectoryParser(self.path)
|
| + if not parser.entries:
|
| + raise NotFoundException('No entries found', self.path)
|
| +
|
| + # Download the first matched directory entry
|
| + pattern = re.compile(self.binary_regex, re.IGNORECASE)
|
| + for entry in parser.entries:
|
| + try:
|
| + self._binary = pattern.match(entry).group()
|
| + break
|
| + except:
|
| + # No match, continue with next entry
|
| + continue
|
| +
|
| + if self._binary is None:
|
| + raise NotFoundException("Binary not found in folder", self.path)
|
| + else:
|
| + return self._binary
|
| +
|
| +
|
| + @property
|
| + def binary_regex(self):
|
| + """Return the regex for the binary filename"""
|
| +
|
| + raise NotImplementedError(sys._getframe(0).f_code.co_name)
|
| +
|
| +
|
| + @property
|
| + def final_url(self):
|
| + """Return the final URL of the build"""
|
| +
|
| + return '/'.join([self.path, self.binary])
|
| +
|
| +
|
| + @property
|
| + def path(self):
|
| + """Return the path to the build"""
|
| +
|
| + return '/'.join([self.base_url, self.path_regex])
|
| +
|
| +
|
| + @property
|
| + def path_regex(self):
|
| + """Return the regex for the path to the build"""
|
| +
|
| + raise NotImplementedError(sys._getframe(0).f_code.co_name)
|
| +
|
| +
|
| + @property
|
| + def platform_regex(self):
|
| + """Return the platform fragment of the URL"""
|
| +
|
| + return PLATFORM_FRAGMENTS[self.platform];
|
| +
|
| +
|
| + @property
|
| + def target(self):
|
| + """Return the target file name of the build"""
|
| +
|
| + if self._target is None:
|
| + self._target = os.path.join(self.directory,
|
| + self.build_filename(self.binary))
|
| + return self._target
|
| +
|
| +
|
| + def build_filename(self, binary):
|
| + """Return the proposed filename with extension for the binary"""
|
| +
|
| + raise NotImplementedError(sys._getframe(0).f_code.co_name)
|
| +
|
| +
|
| + def detect_platform(self):
|
| + """Detect the current platform"""
|
| +
|
| + # For Mac and Linux 32bit we do not need the bits appended
|
| + if mozinfo.os == 'mac' or (mozinfo.os == 'linux' and mozinfo.bits == 32):
|
| + return mozinfo.os
|
| + else:
|
| + return "%s%d" % (mozinfo.os, mozinfo.bits)
|
| +
|
| +
|
| + def download(self):
|
| + """Download the specified file"""
|
| +
|
| + attempts = 0
|
| +
|
| + if not os.path.isdir(self.directory):
|
| + os.makedirs(self.directory)
|
| +
|
| + # Don't re-download the file
|
| + if os.path.isfile(os.path.abspath(self.target)):
|
| + print "File has already been downloaded: %s" % (self.target)
|
| + return
|
| +
|
| + print 'Downloading from: %s' % (urllib.unquote(self.final_url))
|
| + tmp_file = self.target + ".part"
|
| +
|
| + if self.authentication \
|
| + and self.authentication['username'] \
|
| + and self.authentication['password']:
|
| + password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
| + password_mgr.add_password(None,
|
| + self.final_url,
|
| + self.authentication['username'],
|
| + self.authentication['password'])
|
| + handler = urllib2.HTTPBasicAuthHandler(password_mgr)
|
| + opener = urllib2.build_opener(urllib2.HTTPHandler, handler)
|
| + urllib2.install_opener(opener)
|
| +
|
| + while True:
|
| + attempts += 1
|
| + try:
|
| + r = urllib2.urlopen(self.final_url)
|
| + CHUNK = 16 * 1024
|
| + with open(tmp_file, 'wb') as f:
|
| + for chunk in iter(lambda: r.read(CHUNK), ''):
|
| + f.write(chunk)
|
| + break
|
| + except (urllib2.HTTPError, urllib2.URLError):
|
| + if tmp_file and os.path.isfile(tmp_file):
|
| + os.remove(tmp_file)
|
| + print 'Download failed! Retrying... (attempt %s)' % attempts
|
| + if attempts >= self.retry_attempts:
|
| + raise
|
| + time.sleep(self.retry_delay)
|
| +
|
| + os.rename(tmp_file, self.target)
|
| +
|
| +
|
| +class DailyScraper(Scraper):
|
| + """Class to download a daily build from the Mozilla server"""
|
| +
|
| + def __init__(self, branch='mozilla-central', build_id=None, date=None,
|
| + build_number=None, *args, **kwargs):
|
| +
|
| + Scraper.__init__(self, *args, **kwargs)
|
| + self.branch = branch
|
| +
|
| + # Internally we access builds via index
|
| + if build_number is not None:
|
| + self.build_index = int(build_number) - 1
|
| + else:
|
| + self.build_index = None
|
| +
|
| + if build_id:
|
| + # A build id has been specified. Split up its components so the date
|
| + # and time can be extracted: '20111212042025' -> '2011-12-12 04:20:25'
|
| + self.date = datetime.strptime(build_id, '%Y%m%d%H%M%S')
|
| + self.builds, self.build_index = self.get_build_info_for_date(self.date,
|
| + has_time=True)
|
| +
|
| + elif date:
|
| + # A date (without time) has been specified. Use its value and the
|
| + # build index to find the requested build for that day.
|
| + self.date = datetime.strptime(date, '%Y-%m-%d')
|
| + self.builds, self.build_index = self.get_build_info_for_date(self.date,
|
| + build_index=self.build_index)
|
| +
|
| + else:
|
| + # If no build id nor date have been specified the lastest available
|
| + # build of the given branch has to be identified. We also have to
|
| + # retrieve the date of the build via its build id.
|
| + url = '%s/nightly/latest-%s/' % (self.base_url, self.branch)
|
| +
|
| + print 'Retrieving the build status file from %s' % url
|
| + parser = DirectoryParser(url)
|
| + parser.entries = parser.filter(r'.*%s\.txt' % self.platform_regex)
|
| + if not parser.entries:
|
| + message = 'Status file for %s build cannot be found' % self.platform_regex
|
| + raise NotFoundException(message, url)
|
| +
|
| + # Read status file for the platform, retrieve build id, and convert to a date
|
| + status_file = url + parser.entries[-1]
|
| + f = urllib.urlopen(status_file)
|
| + self.date = datetime.strptime(f.readline().strip(), '%Y%m%d%H%M%S')
|
| + self.builds, self.build_index = self.get_build_info_for_date(self.date,
|
| + has_time=True)
|
| +
|
| +
|
| + def get_build_info_for_date(self, date, has_time=False, build_index=None):
|
| + url = '/'.join([self.base_url, self.monthly_build_list_regex])
|
| +
|
| + print 'Retrieving list of builds from %s' % url
|
| + parser = DirectoryParser(url)
|
| + regex = r'%(DATE)s-(\d+-)+%(BRANCH)s%(L10N)s$' % {
|
| + 'DATE': date.strftime('%Y-%m-%d'),
|
| + 'BRANCH': self.branch,
|
| + 'L10N': '' if self.locale == 'en-US' else '-l10n'}
|
| + parser.entries = parser.filter(regex)
|
| + if not parser.entries:
|
| + message = 'Folder for builds on %s has not been found' % self.date.strftime('%Y-%m-%d')
|
| + raise NotFoundException(message, url)
|
| +
|
| + if has_time:
|
| + # If a time is included in the date, use it to determine the build's index
|
| + regex = r'.*%s.*' % date.strftime('%H-%M-%S')
|
| + build_index = parser.entries.index(parser.filter(regex)[0])
|
| + else:
|
| + # If no index has been given, set it to the last build of the day.
|
| + if build_index is None:
|
| + build_index = len(parser.entries) - 1
|
| +
|
| + return (parser.entries, build_index)
|
| +
|
| +
|
| + @property
|
| + def binary_regex(self):
|
| + """Return the regex for the binary"""
|
| +
|
| + regex_base_name = r'^%(APP)s-.*\.%(LOCALE)s\.%(PLATFORM)s'
|
| + regex_suffix = {'linux': r'\.%(EXT)s$',
|
| + 'linux64': r'\.%(EXT)s$',
|
| + 'mac': r'\.%(EXT)s$',
|
| + 'mac64': r'\.%(EXT)s$',
|
| + 'win32': r'(\.installer)\.%(EXT)s$',
|
| + 'win64': r'(\.installer)\.%(EXT)s$'}
|
| + regex = regex_base_name + regex_suffix[self.platform]
|
| +
|
| + return regex % {'APP': self.application,
|
| + 'LOCALE': self.locale,
|
| + 'PLATFORM': self.platform_regex,
|
| + 'EXT': self.extension}
|
| +
|
| +
|
| + def build_filename(self, binary):
|
| + """Return the proposed filename with extension for the binary"""
|
| +
|
| + try:
|
| + # Get exact timestamp of the build to build the local file name
|
| + folder = self.builds[self.build_index]
|
| + timestamp = re.search('([\d\-]+)-\D.*', folder).group(1)
|
| + except:
|
| + # If it's not available use the build's date
|
| + timestamp = self.date.strftime('%Y-%m-%d')
|
| +
|
| + return '%(TIMESTAMP)s-%(BRANCH)s-%(NAME)s' % {
|
| + 'TIMESTAMP': timestamp,
|
| + 'BRANCH': self.branch,
|
| + 'NAME': binary}
|
| +
|
| +
|
| + @property
|
| + def monthly_build_list_regex(self):
|
| + """Return the regex for the folder which contains the builds of a month."""
|
| +
|
| + # Regex for possible builds for the given date
|
| + return r'nightly/%(YEAR)s/%(MONTH)s/' % {
|
| + 'YEAR': self.date.year,
|
| + 'MONTH': str(self.date.month).zfill(2) }
|
| +
|
| +
|
| + @property
|
| + def path_regex(self):
|
| + """Return the regex for the path"""
|
| +
|
| + try:
|
| + return self.monthly_build_list_regex + self.builds[self.build_index]
|
| + except:
|
| + raise NotFoundException("Specified sub folder cannot be found",
|
| + self.base_url + self.monthly_build_list_regex)
|
| +
|
| +
|
| +class DirectScraper(Scraper):
|
| + """Class to download a file from a specified URL"""
|
| +
|
| + def __init__(self, url, *args, **kwargs):
|
| + Scraper.__init__(self, *args, **kwargs)
|
| +
|
| + self.url = url
|
| +
|
| + @property
|
| + def target(self):
|
| + return urllib.splitquery(self.final_url)[0].rpartition('/')[-1]
|
| +
|
| + @property
|
| + def final_url(self):
|
| + return self.url
|
| +
|
| +
|
| +class ReleaseScraper(Scraper):
|
| + """Class to download a release build from the Mozilla server"""
|
| +
|
| + def __init__(self, *args, **kwargs):
|
| + Scraper.__init__(self, *args, **kwargs)
|
| +
|
| + @property
|
| + def binary_regex(self):
|
| + """Return the regex for the binary"""
|
| +
|
| + regex = {'linux': r'^%(APP)s-.*\.%(EXT)s$',
|
| + 'linux64': r'^%(APP)s-.*\.%(EXT)s$',
|
| + 'mac': r'^%(APP)s.*\.%(EXT)s$',
|
| + 'mac64': r'^%(APP)s.*\.%(EXT)s$',
|
| + 'win32': r'^%(APP)s.*\.%(EXT)s$',
|
| + 'win64': r'^%(APP)s.*\.%(EXT)s$'}
|
| + return regex[self.platform] % {'APP': self.application,
|
| + 'EXT': self.extension}
|
| +
|
| +
|
| + @property
|
| + def path_regex(self):
|
| + """Return the regex for the path"""
|
| +
|
| + regex = r'releases/%(VERSION)s/%(PLATFORM)s/%(LOCALE)s'
|
| + return regex % {'LOCALE': self.locale,
|
| + 'PLATFORM': self.platform_regex,
|
| + 'VERSION': self.version}
|
| +
|
| +
|
| + def build_filename(self, binary):
|
| + """Return the proposed filename with extension for the binary"""
|
| +
|
| + template = '%(APP)s-%(VERSION)s.%(LOCALE)s.%(PLATFORM)s.%(EXT)s'
|
| + return template % {'APP': self.application,
|
| + 'VERSION': self.version,
|
| + 'LOCALE': self.locale,
|
| + 'PLATFORM': self.platform,
|
| + 'EXT': self.extension}
|
| +
|
| +
|
| +class ReleaseCandidateScraper(ReleaseScraper):
|
| + """Class to download a release candidate build from the Mozilla server"""
|
| +
|
| + def __init__(self, build_number=None, no_unsigned=False, *args, **kwargs):
|
| + Scraper.__init__(self, *args, **kwargs)
|
| +
|
| + # Internally we access builds via index
|
| + if build_number is not None:
|
| + self.build_index = int(build_number) - 1
|
| + else:
|
| + self.build_index = None
|
| +
|
| + self.builds, self.build_index = self.get_build_info_for_version(self.version, self.build_index)
|
| +
|
| + self.no_unsigned = no_unsigned
|
| + self.unsigned = False
|
| +
|
| +
|
| + def get_build_info_for_version(self, version, build_index=None):
|
| + url = '/'.join([self.base_url, self.candidate_build_list_regex])
|
| +
|
| + print 'Retrieving list of candidate builds from %s' % url
|
| + parser = DirectoryParser(url)
|
| + if not parser.entries:
|
| + message = 'Folder for specific candidate builds at has not been found'
|
| + raise NotFoundException(message, url)
|
| +
|
| + # If no index has been given, set it to the last build of the given version.
|
| + if build_index is None:
|
| + build_index = len(parser.entries) - 1
|
| +
|
| + return (parser.entries, build_index)
|
| +
|
| +
|
| + @property
|
| + def candidate_build_list_regex(self):
|
| + """Return the regex for the folder which contains the builds of
|
| + a candidate build."""
|
| +
|
| + # Regex for possible builds for the given date
|
| + return r'nightly/%(VERSION)s-candidates/' % {
|
| + 'VERSION': self.version }
|
| +
|
| +
|
| + @property
|
| + def path_regex(self):
|
| + """Return the regex for the path"""
|
| +
|
| + regex = r'%(PREFIX)s%(BUILD)s/%(UNSIGNED)s%(PLATFORM)s/%(LOCALE)s'
|
| + return regex % {'PREFIX': self.candidate_build_list_regex,
|
| + 'BUILD': self.builds[self.build_index],
|
| + 'LOCALE': self.locale,
|
| + 'PLATFORM': self.platform_regex,
|
| + 'UNSIGNED': "unsigned/" if self.unsigned else ""}
|
| +
|
| +
|
| + def build_filename(self, binary):
|
| + """Return the proposed filename with extension for the binary"""
|
| +
|
| + template = '%(APP)s-%(VERSION)s-build%(BUILD)s.%(LOCALE)s.%(PLATFORM)s.%(EXT)s'
|
| + return template % {'APP': self.application,
|
| + 'VERSION': self.version,
|
| + 'BUILD': self.builds[self.build_index],
|
| + 'LOCALE': self.locale,
|
| + 'PLATFORM': self.platform,
|
| + 'EXT': self.extension}
|
| +
|
| +
|
| + def download(self):
|
| + """Download the specified file"""
|
| +
|
| + try:
|
| + # Try to download the signed candidate build
|
| + Scraper.download(self)
|
| + except NotFoundException, e:
|
| + print str(e)
|
| +
|
| + # If the signed build cannot be downloaded and unsigned builds are
|
| + # allowed, try to download the unsigned build instead
|
| + if self.no_unsigned:
|
| + raise
|
| + else:
|
| + print "Signed build has not been found. Falling back to unsigned build."
|
| + self.unsigned = True
|
| + Scraper.download(self)
|
| +
|
| +
|
| +class TinderboxScraper(Scraper):
|
| + """Class to download a tinderbox build from the Mozilla server.
|
| +
|
| + There are two ways to specify a unique build:
|
| + 1. If the date (%Y-%m-%d) is given and build_number is given where
|
| + the build_number is the index of the build on the date
|
| + 2. If the build timestamp (UNIX) is given, and matches a specific build.
|
| + """
|
| +
|
| + def __init__(self, branch='mozilla-central', build_number=None, date=None,
|
| + debug_build=False, *args, **kwargs):
|
| + Scraper.__init__(self, *args, **kwargs)
|
| +
|
| + self.branch = branch
|
| + self.debug_build = debug_build
|
| + self.locale_build = self.locale != 'en-US'
|
| + self.timestamp = None
|
| +
|
| + # Currently any time in RelEng is based on the Pacific time zone.
|
| + self.timezone = PacificTimezone();
|
| +
|
| + # Internally we access builds via index
|
| + if build_number is not None:
|
| + self.build_index = int(build_number) - 1
|
| + else:
|
| + self.build_index = None
|
| +
|
| + if date is not None:
|
| + try:
|
| + self.date = datetime.fromtimestamp(float(date), self.timezone)
|
| + self.timestamp = date
|
| + except:
|
| + self.date = datetime.strptime(date, '%Y-%m-%d')
|
| + else:
|
| + self.date = None
|
| +
|
| + # For localized builds we do not have to retrieve the list of builds
|
| + # because only the last build is available
|
| + if not self.locale_build:
|
| + self.builds, self.build_index = self.get_build_info(self.build_index)
|
| +
|
| + try:
|
| + self.timestamp = self.builds[self.build_index]
|
| + except:
|
| + raise NotFoundException("Specified sub folder cannot be found",
|
| + self.base_url + self.monthly_build_list_regex)
|
| +
|
| +
|
| + @property
|
| + def binary_regex(self):
|
| + """Return the regex for the binary"""
|
| +
|
| + regex_base_name = r'^%(APP)s-.*\.%(LOCALE)s\.'
|
| + regex_suffix = {'linux': r'.*\.%(EXT)s$',
|
| + 'linux64': r'.*\.%(EXT)s$',
|
| + 'mac': r'.*\.%(EXT)s$',
|
| + 'mac64': r'.*\.%(EXT)s$',
|
| + 'win32': r'.*(\.installer)\.%(EXT)s$',
|
| + 'win64': r'.*(\.installer)\.%(EXT)s$'}
|
| +
|
| + regex = regex_base_name + regex_suffix[self.platform]
|
| +
|
| + return regex % {'APP': self.application,
|
| + 'LOCALE': self.locale,
|
| + 'EXT': self.extension}
|
| +
|
| +
|
| + def build_filename(self, binary):
|
| + """Return the proposed filename with extension for the binary"""
|
| +
|
| + return '%(TIMESTAMP)s%(BRANCH)s%(DEBUG)s-%(NAME)s' % {
|
| + 'TIMESTAMP': self.timestamp + '-' if self.timestamp else '',
|
| + 'BRANCH': self.branch,
|
| + 'DEBUG': '-debug' if self.debug_build else '',
|
| + 'NAME': binary}
|
| +
|
| +
|
| + @property
|
| + def build_list_regex(self):
|
| + """Return the regex for the folder which contains the list of builds"""
|
| +
|
| + regex = 'tinderbox-builds/%(BRANCH)s-%(PLATFORM)s%(L10N)s%(DEBUG)s'
|
| +
|
| + return regex % {'BRANCH': self.branch,
|
| + 'PLATFORM': '' if self.locale_build else self.platform_regex,
|
| + 'L10N': 'l10n' if self.locale_build else '',
|
| + 'DEBUG': '-debug' if self.debug_build else ''}
|
| +
|
| +
|
| + def date_matches(self, timestamp):
|
| + """Determines whether the timestamp date is equal to the argument date"""
|
| +
|
| + if self.date is None:
|
| + return False
|
| +
|
| + timestamp = datetime.fromtimestamp(float(timestamp), self.timezone)
|
| + if self.date.date() == timestamp.date():
|
| + return True
|
| +
|
| + return False
|
| +
|
| +
|
| + @property
|
| + def date_validation_regex(self):
|
| + """Return the regex for a valid date argument value"""
|
| +
|
| + return r'^\d{4}-\d{1,2}-\d{1,2}$|^\d+$'
|
| +
|
| +
|
| + def detect_platform(self):
|
| + """Detect the current platform"""
|
| +
|
| + platform = Scraper.detect_platform(self)
|
| +
|
| + # On OS X we have to special case the platform detection code and fallback
|
| + # to 64 bit builds for the en-US locale
|
| + if mozinfo.os == 'mac' and self.locale == 'en-US' and mozinfo.bits == 64:
|
| + platform = "%s%d" % (mozinfo.os, mozinfo.bits)
|
| +
|
| + return platform
|
| +
|
| +
|
| + def get_build_info(self, build_index=None):
|
| + url = '/'.join([self.base_url, self.build_list_regex])
|
| +
|
| + print 'Retrieving list of builds from %s' % url
|
| +
|
| + # If a timestamp is given, retrieve just that build
|
| + regex = '^' + self.timestamp + '$' if self.timestamp else r'^\d+$'
|
| +
|
| + parser = DirectoryParser(url)
|
| + parser.entries = parser.filter(regex)
|
| +
|
| + # If date is given, retrieve the subset of builds on that date
|
| + if self.date is not None:
|
| + parser.entries = filter(self.date_matches, parser.entries)
|
| +
|
| + if not parser.entries:
|
| + message = 'No builds have been found'
|
| + raise NotFoundException(message, url)
|
| +
|
| + # If no index has been given, set it to the last build of the day.
|
| + if build_index is None:
|
| + build_index = len(parser.entries) - 1
|
| +
|
| + return (parser.entries, build_index)
|
| +
|
| +
|
| + @property
|
| + def path_regex(self):
|
| + """Return the regex for the path"""
|
| +
|
| + if self.locale_build:
|
| + return self.build_list_regex
|
| +
|
| + return '/'.join([self.build_list_regex, self.builds[self.build_index]])
|
| +
|
| +
|
| + @property
|
| + def platform_regex(self):
|
| + """Return the platform fragment of the URL"""
|
| +
|
| + PLATFORM_FRAGMENTS = {'linux': 'linux',
|
| + 'linux64': 'linux64',
|
| + 'mac': 'macosx',
|
| + 'mac64': 'macosx64',
|
| + 'win32': 'win32',
|
| + 'win64': 'win64'}
|
| +
|
| + return PLATFORM_FRAGMENTS[self.platform]
|
| +
|
| +
|
| +def cli():
|
| + """Main function for the downloader"""
|
| +
|
| + BUILD_TYPES = {'release': ReleaseScraper,
|
| + 'candidate': ReleaseCandidateScraper,
|
| + 'daily': DailyScraper,
|
| + 'tinderbox': TinderboxScraper }
|
| +
|
| + usage = 'usage: %prog [options]'
|
| + parser = OptionParser(usage=usage, description=__doc__)
|
| + parser.add_option('--application', '-a',
|
| + dest='application',
|
| + choices=APPLICATIONS,
|
| + default='firefox',
|
| + metavar='APPLICATION',
|
| + help='The name of the application to download, '
|
| + 'default: "%default"')
|
| + parser.add_option('--directory', '-d',
|
| + dest='directory',
|
| + default=os.getcwd(),
|
| + metavar='DIRECTORY',
|
| + help='Target directory for the download, default: '
|
| + 'current working directory')
|
| + parser.add_option('--build-number',
|
| + dest='build_number',
|
| + default=None,
|
| + type="int",
|
| + metavar='BUILD_NUMBER',
|
| + help='Number of the build (for candidate, daily, '
|
| + 'and tinderbox builds)')
|
| + parser.add_option('--locale', '-l',
|
| + dest='locale',
|
| + default='en-US',
|
| + metavar='LOCALE',
|
| + help='Locale of the application, default: "%default"')
|
| + parser.add_option('--platform', '-p',
|
| + dest='platform',
|
| + choices=PLATFORM_FRAGMENTS.keys(),
|
| + metavar='PLATFORM',
|
| + help='Platform of the application')
|
| + parser.add_option('--type', '-t',
|
| + dest='type',
|
| + choices=BUILD_TYPES.keys(),
|
| + default='release',
|
| + metavar='BUILD_TYPE',
|
| + help='Type of build to download, default: "%default"')
|
| + parser.add_option('--url',
|
| + dest='url',
|
| + default=None,
|
| + metavar='URL',
|
| + help='URL to download.')
|
| + parser.add_option('--version', '-v',
|
| + dest='version',
|
| + metavar='VERSION',
|
| + help='Version of the application to be used by release and\
|
| + candidate builds, i.e. "3.6"')
|
| + parser.add_option('--extension',
|
| + dest='extension',
|
| + default=None,
|
| + metavar='EXTENSION',
|
| + help='File extension of the build (e.g. "zip"), default:\
|
| + the standard build extension on the platform.')
|
| + parser.add_option('--username',
|
| + dest='username',
|
| + default=None,
|
| + metavar='USERNAME',
|
| + help='Username for basic HTTP authentication.')
|
| + parser.add_option('--password',
|
| + dest='password',
|
| + default=None,
|
| + metavar='PASSWORD',
|
| + help='Password for basic HTTP authentication.')
|
| + parser.add_option('--retry-attempts',
|
| + dest='retry_attempts',
|
| + default=3,
|
| + type=int,
|
| + metavar='RETRY_ATTEMPTS',
|
| + help='Number of times the download will be attempted in '
|
| + 'the event of a failure, default: %default')
|
| + parser.add_option('--retry-delay',
|
| + dest='retry_delay',
|
| + default=10,
|
| + type=int,
|
| + metavar='RETRY_DELAY',
|
| + help='Amount of time (in seconds) to wait between retry '
|
| + 'attempts, default: %default')
|
| +
|
| + # Option group for candidate builds
|
| + group = OptionGroup(parser, "Candidate builds",
|
| + "Extra options for candidate builds.")
|
| + group.add_option('--no-unsigned',
|
| + dest='no_unsigned',
|
| + action="store_true",
|
| + help="Don't allow to download unsigned builds if signed\
|
| + builds are not available")
|
| + parser.add_option_group(group)
|
| +
|
| + # Option group for daily builds
|
| + group = OptionGroup(parser, "Daily builds",
|
| + "Extra options for daily builds.")
|
| + group.add_option('--branch',
|
| + dest='branch',
|
| + default='mozilla-central',
|
| + metavar='BRANCH',
|
| + help='Name of the branch, default: "%default"')
|
| + group.add_option('--build-id',
|
| + dest='build_id',
|
| + default=None,
|
| + metavar='BUILD_ID',
|
| + help='ID of the build to download')
|
| + group.add_option('--date',
|
| + dest='date',
|
| + default=None,
|
| + metavar='DATE',
|
| + help='Date of the build, default: latest build')
|
| + parser.add_option_group(group)
|
| +
|
| + # Option group for tinderbox builds
|
| + group = OptionGroup(parser, "Tinderbox builds",
|
| + "Extra options for tinderbox builds.")
|
| + group.add_option('--debug-build',
|
| + dest='debug_build',
|
| + action="store_true",
|
| + help="Download a debug build")
|
| + parser.add_option_group(group)
|
| +
|
| + # TODO: option group for nightly builds
|
| + (options, args) = parser.parse_args()
|
| +
|
| + # Check for required options and arguments
|
| + # Note: Will be optional when ini file support has been landed
|
| + if not options.url \
|
| + and not options.type in ['daily', 'tinderbox'] \
|
| + and not options.version:
|
| + parser.error('The version of the application to download has not been specified.')
|
| +
|
| + # Instantiate scraper and download the build
|
| + scraper_keywords = {'application': options.application,
|
| + 'locale': options.locale,
|
| + 'platform': options.platform,
|
| + 'version': options.version,
|
| + 'directory': options.directory,
|
| + 'extension': options.extension,
|
| + 'authentication': {
|
| + 'username': options.username,
|
| + 'password': options.password},
|
| + 'retry_attempts': options.retry_attempts,
|
| + 'retry_delay': options.retry_delay}
|
| + scraper_options = {'candidate': {
|
| + 'build_number': options.build_number,
|
| + 'no_unsigned': options.no_unsigned},
|
| + 'daily': {
|
| + 'branch': options.branch,
|
| + 'build_number': options.build_number,
|
| + 'build_id': options.build_id,
|
| + 'date': options.date},
|
| + 'tinderbox': {
|
| + 'branch': options.branch,
|
| + 'build_number': options.build_number,
|
| + 'date': options.date,
|
| + 'debug_build': options.debug_build}
|
| + }
|
| +
|
| + kwargs = scraper_keywords.copy()
|
| + kwargs.update(scraper_options.get(options.type, {}))
|
| +
|
| + if options.url:
|
| + build = DirectScraper(options.url, **kwargs)
|
| + else:
|
| + build = BUILD_TYPES[options.type](**kwargs)
|
| +
|
| + build.download()
|
| +
|
| +if __name__ == "__main__":
|
| + cli()
|
|
|
| Property changes on: third_party/mozdownload/mozdownload/scraper.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
| Added: svn:executable
|
| + *
|
|
|
|
|