mirror of
https://github.com/openSUSE/osc.git
synced 2024-12-28 10:46:15 +01:00
Merge branch 'python3_prep' of https://github.com/lethliel/osc
Get rid of the urlgrabber dependency. The current implementation of the progress bar is quite "noisy" if the line length exceeds the size of the terminal window, but that's something we could fix later. (The superfluous error message will be fixed in a subsequent commit.)
This commit is contained in:
commit
bec52a7917
4
README
4
README
@ -26,10 +26,6 @@ Alternatively, you can directly use osc-wrapper.py from the source dir
|
||||
|
||||
The program needs the cElementTree python module installed. On SUSE, the
|
||||
respective package is called python-elementtree (before 10.2: python-xml).
|
||||
For local building, you will need python-urlgrabber in addition. Those are
|
||||
standard package on SUSE Linux since a while. If your version is too old, you
|
||||
can find python-elementtree and python-urlgrabber here:
|
||||
http://download.opensuse.org/repositories/devel:/languages:/python/
|
||||
|
||||
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
__all__ = ['babysitter', 'core', 'commandline', 'oscerr', 'othermethods', 'build', 'fetch', 'meter']
|
||||
__all__ = ['babysitter', 'core', 'commandline', 'oscerr', 'build', 'fetch', 'meter', 'grabber']
|
||||
|
||||
# vim: sw=4 et
|
||||
|
@ -11,7 +11,6 @@ import pdb
|
||||
import sys
|
||||
import signal
|
||||
import traceback
|
||||
from urlgrabber.grabber import URLGrabError
|
||||
|
||||
from osc import oscerr
|
||||
from .oscsslexcp import NoSecureSSLError
|
||||
@ -132,8 +131,6 @@ def run(prg, argv=None):
|
||||
print(e, file=sys.stderr)
|
||||
except URLError as e:
|
||||
print('Failed to reach a server:\n', e.reason, file=sys.stderr)
|
||||
except URLGrabError as e:
|
||||
print('Failed to grab %s: %s' % (e.url, e.strerror), file=sys.stderr)
|
||||
except IOError as e:
|
||||
# ignore broken pipe
|
||||
if e.errno != errno.EPIPE:
|
||||
|
16
osc/build.py
16
osc/build.py
@ -256,18 +256,8 @@ class Pac:
|
||||
def makeurls(self, cachedir, urllist):
|
||||
|
||||
self.urllist = []
|
||||
|
||||
# build up local URL
|
||||
# by using the urlgrabber with local urls, we basically build up a cache.
|
||||
# the cache has no validation, since the package servers don't support etags,
|
||||
# or if-modified-since, so the caching is simply name-based (on the assumption
|
||||
# that the filename is suitable as identifier)
|
||||
self.localdir = '%s/%s/%s/%s' % (cachedir, self.project, self.repository, self.arch)
|
||||
self.fullfilename = os.path.join(self.localdir, self.canonname)
|
||||
self.url_local = 'file://%s' % self.fullfilename
|
||||
|
||||
# first, add the local URL
|
||||
self.urllist.append(self.url_local)
|
||||
|
||||
# remote URLs
|
||||
for url in urllist:
|
||||
@ -319,14 +309,14 @@ def get_preinstall_image(apiurl, arch, cache_dir, img_info):
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if sys.stdout.isatty() and TextMeter:
|
||||
progress_obj = TextMeter(fo=sys.stdout)
|
||||
progress_obj = TextMeter()
|
||||
else:
|
||||
progress_obj = None
|
||||
gr = OscFileGrabber(progress_obj=progress_obj)
|
||||
try:
|
||||
gr.urlgrab(url, filename=ifile_path_part, text='fetching image')
|
||||
except URLGrabError as e:
|
||||
print("Failed to download! ecode:%i errno:%i" % (e.code, e.errno))
|
||||
except HTTPError as e:
|
||||
print("Failed to download! ecode:%i reason:%i" % (e.code, e.reason))
|
||||
return ('', '', [])
|
||||
# download ok, rename partial file to final file name
|
||||
os.rename(ifile_path_part, ifile_path)
|
||||
|
@ -167,7 +167,7 @@ class Osc(cmdln.Cmdln):
|
||||
self.download_progress = None
|
||||
if conf.config.get('show_download_progress', False):
|
||||
from .meter import TextMeter
|
||||
self.download_progress = TextMeter(hide_finished=True)
|
||||
self.download_progress = TextMeter()
|
||||
|
||||
|
||||
def get_cmd_help(self, cmdname):
|
||||
@ -7785,9 +7785,12 @@ Please submit there instead, or use --nodevelproject to force direct submission.
|
||||
sys.exit(1)
|
||||
|
||||
if '://' in srpm:
|
||||
if srpm.endswith('/'):
|
||||
print('%s is not a valid link. It must not end with /' % srpm)
|
||||
sys.exit(1)
|
||||
print('trying to fetch', srpm)
|
||||
import urlgrabber
|
||||
urlgrabber.urlgrab(srpm)
|
||||
from .grabber import OscFileGrabber
|
||||
OscFileGrabber().urlgrab(srpm)
|
||||
srpm = os.path.basename(srpm)
|
||||
|
||||
srpm = os.path.abspath(srpm)
|
||||
|
@ -5992,8 +5992,11 @@ def streamfile(url, http_meth = http_GET, bufsize=8192, data=None, progress_obj=
|
||||
cl = int(cl)
|
||||
|
||||
if progress_obj:
|
||||
basename = os.path.basename(urlsplit(url)[2])
|
||||
progress_obj.start(basename=basename, text=text, size=cl)
|
||||
if not text:
|
||||
basename = os.path.basename(urlsplit(url)[2])
|
||||
else:
|
||||
basename = text
|
||||
progress_obj.start(basename, cl)
|
||||
|
||||
if bufsize == "line":
|
||||
bufsize = 8192
|
||||
@ -6012,7 +6015,7 @@ def streamfile(url, http_meth = http_GET, bufsize=8192, data=None, progress_obj=
|
||||
yield data
|
||||
|
||||
if progress_obj:
|
||||
progress_obj.end(read)
|
||||
progress_obj.end()
|
||||
f.close()
|
||||
|
||||
if not cl is None and read != cl:
|
||||
|
105
osc/fetch.py
105
osc/fetch.py
@ -15,64 +15,26 @@ except ImportError:
|
||||
from urllib import quote_plus
|
||||
from urllib2 import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, HTTPError
|
||||
|
||||
from urlgrabber.grabber import URLGrabber, URLGrabError
|
||||
from urlgrabber.mirror import MirrorGroup
|
||||
from .core import makeurl, streamfile, dgst
|
||||
from .grabber import OscFileGrabber, OscMirrorGroup
|
||||
from .util import packagequery, cpio
|
||||
from . import conf
|
||||
from . import oscerr
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
try:
|
||||
from .meter import TextMeter
|
||||
except:
|
||||
except ImportError:
|
||||
TextMeter = None
|
||||
|
||||
|
||||
def join_url(self, base_url, rel_url):
|
||||
"""to override _join_url of MirrorGroup, because we want to
|
||||
pass full URLs instead of base URL where relative_url is added later...
|
||||
IOW, we make MirrorGroup ignore relative_url
|
||||
"""
|
||||
return base_url
|
||||
|
||||
|
||||
class OscFileGrabber(URLGrabber):
|
||||
def __init__(self, progress_obj=None):
|
||||
# we cannot use super because we still have to support
|
||||
# older urlgrabber versions where URLGrabber is an old-style class
|
||||
URLGrabber.__init__(self)
|
||||
self.progress_obj = progress_obj
|
||||
|
||||
def urlgrab(self, url, filename, text=None, **kwargs):
|
||||
if url.startswith('file://'):
|
||||
f = url.replace('file://', '', 1)
|
||||
if os.path.isfile(f):
|
||||
return f
|
||||
else:
|
||||
raise URLGrabError(2, 'Local file \'%s\' does not exist' % f)
|
||||
with file(filename, 'wb') as f:
|
||||
try:
|
||||
for i in streamfile(url, progress_obj=self.progress_obj,
|
||||
text=text):
|
||||
f.write(i)
|
||||
except HTTPError as e:
|
||||
exc = URLGrabError(14, str(e))
|
||||
exc.url = url
|
||||
exc.exception = e
|
||||
exc.code = e.code
|
||||
raise exc
|
||||
except IOError as e:
|
||||
raise URLGrabError(4, str(e))
|
||||
return filename
|
||||
|
||||
|
||||
class Fetcher:
|
||||
def __init__(self, cachedir='/tmp', api_host_options={}, urllist=[],
|
||||
http_debug=False, cookiejar=None, offline=False, enable_cpio=True):
|
||||
# set up progress bar callback
|
||||
if sys.stdout.isatty() and TextMeter:
|
||||
self.progress_obj = TextMeter(fo=sys.stdout)
|
||||
self.progress_obj = TextMeter()
|
||||
else:
|
||||
self.progress_obj = None
|
||||
|
||||
@ -92,14 +54,6 @@ class Fetcher:
|
||||
openers += (HTTPCookieProcessor(cookiejar), )
|
||||
self.gr = OscFileGrabber(progress_obj=self.progress_obj)
|
||||
|
||||
def failureReport(self, errobj):
|
||||
"""failure output for failovers from urlgrabber"""
|
||||
if errobj.url.startswith('file://'):
|
||||
return {}
|
||||
print('%s/%s: attempting download from api, since not found at %s'
|
||||
% (self.curpac.project, self.curpac, errobj.url.split('/')[2]))
|
||||
return {}
|
||||
|
||||
def __add_cpio(self, pac):
|
||||
prpap = '%s/%s/%s/%s' % (pac.project, pac.repository, pac.repoarch, pac.repopackage)
|
||||
self.cpio.setdefault(prpap, {})[pac.repofilename] = pac
|
||||
@ -156,8 +110,8 @@ class Fetcher:
|
||||
raise oscerr.APIError('failed to fetch file \'%s\': '
|
||||
'missing in CPIO archive' %
|
||||
pac.repofilename)
|
||||
except URLGrabError as e:
|
||||
if e.errno != 14 or e.code != 414:
|
||||
except HTTPError as e:
|
||||
if e.code != 414:
|
||||
raise
|
||||
# query str was too large
|
||||
keys = list(pkgs.keys())
|
||||
@ -181,8 +135,7 @@ class Fetcher:
|
||||
# for use by the failure callback
|
||||
self.curpac = pac
|
||||
|
||||
MirrorGroup._join_url = join_url
|
||||
mg = MirrorGroup(self.gr, pac.urllist, failure_callback=(self.failureReport, (), {}))
|
||||
mg = OscMirrorGroup(self.gr, pac.urllist)
|
||||
|
||||
if self.http_debug:
|
||||
print('\nURLs to try for package \'%s\':' % pac, file=sys.stderr)
|
||||
@ -192,19 +145,22 @@ class Fetcher:
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(prefix='osc_build',
|
||||
delete=False) as tmpfile:
|
||||
mg.urlgrab(pac.filename, filename=tmpfile.name,
|
||||
mg_stat = mg.urlgrab(pac.filename, filename=tmpfile.name,
|
||||
text='%s(%s) %s' % (prefix, pac.project, pac.filename))
|
||||
self.move_package(tmpfile.name, pac.localdir, pac)
|
||||
except URLGrabError as e:
|
||||
if self.enable_cpio and e.errno == 256:
|
||||
self.__add_cpio(pac)
|
||||
return
|
||||
print()
|
||||
print('Error:', e.strerror, file=sys.stderr)
|
||||
print('Failed to retrieve %s from the following locations '
|
||||
'(in order):' % pac.filename, file=sys.stderr)
|
||||
print('\n'.join(pac.urllist), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if mg_stat:
|
||||
self.move_package(tmpfile.name, pac.localdir, pac)
|
||||
|
||||
if not mg_stat:
|
||||
if self.enable_cpio:
|
||||
print('%s/%s: attempting download from api, since not found'
|
||||
% (pac.project, pac.name))
|
||||
self.__add_cpio(pac)
|
||||
return
|
||||
print()
|
||||
print('Error: Failed to retrieve %s from the following locations '
|
||||
'(in order):' % pac.filename, file=sys.stderr)
|
||||
print('\n'.join(pac.urllist), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if os.path.exists(tmpfile.name):
|
||||
os.unlink(tmpfile.name)
|
||||
@ -285,12 +241,12 @@ class Fetcher:
|
||||
continue
|
||||
try:
|
||||
# if there isn't a progress bar, there is no output at all
|
||||
prefix = ''
|
||||
if not self.progress_obj:
|
||||
print('%d/%d (%s) %s' % (done, needed, i.project, i.filename))
|
||||
self.fetch(i)
|
||||
if self.progress_obj:
|
||||
print(" %d/%d\r" % (done, needed), end=' ')
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
prefix = '[%d/%d] ' % (done, needed)
|
||||
self.fetch(i, prefix=prefix)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('Cancelled by user (ctrl-c)')
|
||||
@ -308,10 +264,11 @@ class Fetcher:
|
||||
dest += '/_pubkey'
|
||||
|
||||
url = makeurl(buildinfo.apiurl, ['source', i, '_pubkey'])
|
||||
try_parent = False
|
||||
try:
|
||||
if self.offline and not os.path.exists(dest):
|
||||
# may need to try parent
|
||||
raise URLGrabError(2)
|
||||
try_parent = True
|
||||
elif not self.offline:
|
||||
OscFileGrabber().urlgrab(url, dest)
|
||||
# not that many keys usually
|
||||
@ -324,12 +281,14 @@ class Fetcher:
|
||||
if os.path.exists(dest):
|
||||
os.unlink(dest)
|
||||
sys.exit(0)
|
||||
except URLGrabError as e:
|
||||
except HTTPError as e:
|
||||
# Not found is okay, let's go to the next project
|
||||
if e.errno == 14 and e.code != 404:
|
||||
if e.code != 404:
|
||||
print("Invalid answer from server", e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try_parent = True
|
||||
|
||||
if try_parent:
|
||||
if self.http_debug:
|
||||
print("can't fetch key for %s: %s" % (i, e.strerror), file=sys.stderr)
|
||||
print("url: %s" % url, file=sys.stderr)
|
||||
|
49
osc/grabber.py
Normal file
49
osc/grabber.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Copyright (C) 2018 SUSE Linux. All rights reserved.
|
||||
# This program is free software; it may be used, copied, modified
|
||||
# and distributed under the terms of the GNU General Public Licence,
|
||||
# either version 2, or (at your option) any later version.
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
from .core import streamfile
|
||||
|
||||
try:
|
||||
from urllib.request import HTTPError
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import unquote
|
||||
except ImportError:
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse
|
||||
from urllib import unquote
|
||||
|
||||
|
||||
class OscFileGrabber(object):
|
||||
def __init__(self, progress_obj=None):
|
||||
self.progress_obj = progress_obj
|
||||
|
||||
def urlgrab(self, url, filename=None, text=None):
|
||||
if filename is None:
|
||||
parts = urlparse(url)
|
||||
filename = os.path.basename(unquote(parts[2]))
|
||||
with open(filename, 'wb') as f:
|
||||
for i in streamfile(url, progress_obj=self.progress_obj,
|
||||
text=text):
|
||||
f.write(i)
|
||||
|
||||
|
||||
class OscMirrorGroup(object):
|
||||
def __init__(self, grabber, mirrors):
|
||||
self._grabber = grabber
|
||||
self._mirrors = mirrors
|
||||
|
||||
def urlgrab(self, url, filename=None, text=None):
|
||||
tries = 0
|
||||
for mirror in self._mirrors:
|
||||
try:
|
||||
self._grabber.urlgrab(mirror, filename, text)
|
||||
return True
|
||||
except HTTPError as e:
|
||||
print('Error %s' % e.code)
|
||||
tries += 1
|
||||
|
||||
return False
|
112
osc/meter.py
112
osc/meter.py
@ -1,103 +1,27 @@
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330,
|
||||
# Boston, MA 02111-1307 USA
|
||||
# Copyright (C) 2018 SUSE Linux. All rights reserved.
|
||||
# This program is free software; it may be used, copied, modified
|
||||
# and distributed under the terms of the GNU General Public Licence,
|
||||
# either version 2, or (at your option) any later version.
|
||||
|
||||
# this is basically a copy of python-urlgrabber's TextMeter class,
|
||||
# with support added for dynamical sizing according to screen size.
|
||||
# it uses getScreenWidth() scrapped from smart.
|
||||
# 2007-04-24, poeml
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from urlgrabber.progress import BaseMeter, format_time, format_number
|
||||
import sys, os
|
||||
|
||||
def getScreenWidth():
|
||||
import termios, struct, fcntl
|
||||
s = struct.pack('HHHH', 0, 0, 0, 0)
|
||||
try:
|
||||
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
|
||||
except IOError:
|
||||
return 80
|
||||
return struct.unpack('HHHH', x)[1]
|
||||
import progressbar as pb
|
||||
|
||||
|
||||
class TextMeter(BaseMeter):
|
||||
def __init__(self, fo=sys.stderr, hide_finished=False):
|
||||
BaseMeter.__init__(self)
|
||||
self.fo = fo
|
||||
self.hide_finished = hide_finished
|
||||
try:
|
||||
width = int(os.environ['COLUMNS'])
|
||||
except (KeyError, ValueError):
|
||||
width = getScreenWidth()
|
||||
class TextMeter(object):
|
||||
|
||||
|
||||
#self.unsized_templ = '\r%-60.60s %5sB %s '
|
||||
self.unsized_templ = '\r%%-%s.%ss %%5sB %%s ' % (width *2/5, width*3/5)
|
||||
#self.sized_templ = '\r%-45.45s %3i%% |%-15.15s| %5sB %8s '
|
||||
self.bar_length = width/5
|
||||
self.sized_templ = '\r%%-%s.%ss %%3i%%%% |%%-%s.%ss| %%5sB %%8s ' % (width*4/10, width*4/10, self.bar_length, self.bar_length)
|
||||
|
||||
|
||||
def _do_start(self, *args, **kwargs):
|
||||
BaseMeter._do_start(self, *args, **kwargs)
|
||||
self._do_update(0)
|
||||
|
||||
def _do_update(self, amount_read, now=None):
|
||||
etime = self.re.elapsed_time()
|
||||
fetime = format_time(etime)
|
||||
fread = format_number(amount_read)
|
||||
#self.size = None
|
||||
if self.text is not None:
|
||||
text = self.text
|
||||
def start(self, basename, size=None):
|
||||
if size is None:
|
||||
widgets = [basename + ': ', pb.AnimatedMarker(), ' ', pb.Timer()]
|
||||
self.bar = pb.ProgressBar(widgets=widgets, maxval=pb.UnknownLength)
|
||||
else:
|
||||
text = self.basename
|
||||
if self.size is None:
|
||||
out = self.unsized_templ % \
|
||||
(text, fread, fetime)
|
||||
else:
|
||||
rtime = self.re.remaining_time()
|
||||
frtime = format_time(rtime)
|
||||
frac = self.re.fraction_read()
|
||||
bar = '='*int(self.bar_length * frac)
|
||||
widgets = [basename + ': ', pb.Percentage(), pb.Bar(), ' ',
|
||||
pb.ETA()]
|
||||
self.bar = pb.ProgressBar(widgets=widgets, maxval=size)
|
||||
self.bar.start()
|
||||
|
||||
out = self.sized_templ % \
|
||||
(text, frac*100, bar, fread, frtime) + 'ETA '
|
||||
def update(self, amount_read):
|
||||
self.bar.update(amount_read)
|
||||
|
||||
self.fo.write(out)
|
||||
self.fo.flush()
|
||||
|
||||
def _do_end(self, amount_read, now=None):
|
||||
total_time = format_time(self.re.elapsed_time())
|
||||
total_size = format_number(amount_read)
|
||||
if self.text is not None:
|
||||
text = self.text
|
||||
else:
|
||||
text = self.basename
|
||||
if self.size is None:
|
||||
out = self.unsized_templ % \
|
||||
(text, total_size, total_time)
|
||||
else:
|
||||
bar = '=' * self.bar_length
|
||||
out = self.sized_templ % \
|
||||
(text, 100, bar, total_size, total_time) + ' '
|
||||
if self.hide_finished:
|
||||
self.fo.write('\r'+ ' '*len(out) + '\r')
|
||||
else:
|
||||
self.fo.write(out + '\n')
|
||||
self.fo.flush()
|
||||
def end(self):
|
||||
self.bar.finish()
|
||||
|
||||
# vim: sw=4 et
|
||||
|
Loading…
Reference in New Issue
Block a user