--- yum/deltamd.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yum/yumRepo.py | 26 ++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 yum/deltamd.py
diff --git a/yum/deltamd.py b/yum/deltamd.py new file mode 100644 index 0000000..686eacd --- /dev/null +++ b/yum/deltamd.py @@ -0,0 +1,116 @@ +# Delta metadata support +# Copyright 2013 Zdenek Pavlas + +# 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 + +from yum.Errors import MiscError +from yum.misc import _decompress_chunked + +def splitter(read, pattern): + ''' Read the stream, splitting data at each pattern instance. + Split at the last '</', too, so XML with N elements below + the root yields exactly N+2 items. + ''' + buf = '' + while True: + more = read(0x4000) + if not more: break + buf += more + i = 0 + while True: + j = buf.find(pattern, i + len(pattern)) + if j == -1: break + yield buf[i:j] + i = j + buf = buf[i:] + i = buf.rfind('</') + if i != -1: + yield buf[:i] + buf = buf[i:] + yield buf + +from hashlib import sha1 as hash_func + +def apply_delta(old, delta, new, opt='', pattern=None): + ''' Use the compressed delta file to update from old to new. + Delta is line-oriented stream of literals (encoded as +<N>\n + followed by N bytes) and old data references (guaranteed not + to start with the '+' sign). Supported options are: + + [h]ashrefs: reference old data with hash values, not ordinals. + This is more flexible but less efficient. + + [s]equential: delta uses each piece of old data at most once + and in the original order. This implies that we don't need + to store them for later use, and may use prefix matching. + ''' + + hash_refs = 'h' in opt + sequential = 's' in opt + + # copying uncompressed old to new.. + next = splitter(open(old, 'rb').read, pattern or '<package ').next + write = open(new, 'wb').write + + n = -1 # last index used + lookup = {} if hash_refs else [] + f = _decompress_chunked(delta, None, None) + while True: + l = f.readline() + if not l: break + if l[0] == '+': + + # literal chunk + write(f.read(int(l[1:]))) + continue + + if hash_refs: + if sequential: + hint = int(l[0], 16) + needle = l[1:-1] + while True: + data = next() + if len(data) & 0xf != hint: + continue + if hash_func(data).hexdigest().startswith(needle): + break + else: + needle = l[:-1] + while needle not in lookup: + data = next() + hv = hash_func(data).hexdigest() + lookup[hv] = data + data = lookup[needle] + else: + if sequential: + if l != '\n': + skip = int(l) + while skip: + skip -= 1 + next() + data = next() + else: + n += 1 + if l != '\n': + n = int(l) + while len(lookup) <= n: + data = next() + lookup.append(data) + data = lookup[n] + + # copy from old + write(data) diff --git a/yum/yumRepo.py b/yum/yumRepo.py index f409485..3b49f56 100644 --- a/yum/yumRepo.py +++ b/yum/yumRepo.py @@ -51,6 +51,7 @@ import shutil import stat import errno import tempfile +from yum.deltamd import apply_delta # This is unused now, probably nothing uses it but it was global/public. skip_old_DBMD_check = False @@ -1627,11 +1628,34 @@ Insufficient space in download directory %s newmdfiles.append(self._get_mdtype_fname(ndata, False)) return downloading + def _applyDelta(self, deltamd): + """ Apply delta metadata file and checksum the destination """ + + full, opt = deltamd.delta + local = self._get_mdtype_fname(deltamd) + new = self.cachedir + '/gen/%s.xml' % full + try: + apply_delta(new + '.old.tmp', local, new, opt) + self._checkMD(new, full, openchecksum=True) + except (Errors.MiscError, URLGrabError), e: + logger.warning(_('deltamd %s/%s failure: %s'), self, deltamd.type, e) + return False + ts = int(self.repoXML.getData(full).timestamp) + os.utime(new, (ts, ts)) + os.unlink(local) + return True + def _commonRetrieveDataMD_done(self, downloading): """ Uncompress the downloaded metadata """ for (ndata, nmdtype) in downloading: - local = self._get_mdtype_fname(ndata, False) + if ndata.delta and not self._applyDelta(ndata): + # fallback to full metadata or revert + full, opt = deltamd.delta + if not self._retrieveMD(full, retrieve_can_fail=True): + self._revertOldRepoXML() + return False + self._doneOldRepoXML() return True -- 1.7.11.7 _______________________________________________ Yum-devel mailing list Yum-devel@lists.baseurl.org http://lists.baseurl.org/mailman/listinfo/yum-devel