config/MozZipFile.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/config/MozZipFile.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,137 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import os
     1.9 +import time
    1.10 +import zipfile
    1.11 +
    1.12 +from mozbuild.util import lock_file
    1.13 +
    1.14 +
    1.15 +class ZipFile(zipfile.ZipFile):
    1.16 +  """ Class with methods to open, read, write, close, list zip files.
    1.17 +
    1.18 +  Subclassing zipfile.ZipFile to allow for overwriting of existing
    1.19 +  entries, though only for writestr, not for write.
    1.20 +  """
    1.21 +  def __init__(self, file, mode="r", compression=zipfile.ZIP_STORED,
    1.22 +               lock = False):
    1.23 +    if lock:
    1.24 +      assert isinstance(file, basestring)
    1.25 +      self.lockfile = lock_file(file + '.lck')
    1.26 +    else:
    1.27 +      self.lockfile = None
    1.28 +
    1.29 +    if mode == 'a' and lock:
    1.30 +      # appending to a file which doesn't exist fails, but we can't check
    1.31 +      # existence util we hold the lock
    1.32 +      if (not os.path.isfile(file)) or os.path.getsize(file) == 0:
    1.33 +        mode = 'w'
    1.34 +
    1.35 +    zipfile.ZipFile.__init__(self, file, mode, compression)
    1.36 +    self._remove = []
    1.37 +    self.end = self.fp.tell()
    1.38 +    self.debug = 0
    1.39 +
    1.40 +  def writestr(self, zinfo_or_arcname, bytes):
    1.41 +    """Write contents into the archive.
    1.42 +
    1.43 +    The contents is the argument 'bytes',  'zinfo_or_arcname' is either
    1.44 +    a ZipInfo instance or the name of the file in the archive.
    1.45 +    This method is overloaded to allow overwriting existing entries.
    1.46 +    """
    1.47 +    if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
    1.48 +      zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname,
    1.49 +                              date_time=time.localtime(time.time()))
    1.50 +      zinfo.compress_type = self.compression
    1.51 +      # Add some standard UNIX file access permissions (-rw-r--r--).
    1.52 +      zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
    1.53 +    else:
    1.54 +      zinfo = zinfo_or_arcname
    1.55 +
    1.56 +    # Now to the point why we overwrote this in the first place,
    1.57 +    # remember the entry numbers if we already had this entry.
    1.58 +    # Optimizations:
    1.59 +    # If the entry to overwrite is the last one, just reuse that.
    1.60 +    # If we store uncompressed and the new content has the same size
    1.61 +    # as the old, reuse the existing entry.
    1.62 +
    1.63 +    doSeek = False # store if we need to seek to the eof after overwriting
    1.64 +    if self.NameToInfo.has_key(zinfo.filename):
    1.65 +      # Find the last ZipInfo with our name.
    1.66 +      # Last, because that's catching multiple overwrites
    1.67 +      i = len(self.filelist)
    1.68 +      while i > 0:
    1.69 +        i -= 1
    1.70 +        if self.filelist[i].filename == zinfo.filename:
    1.71 +          break
    1.72 +      zi = self.filelist[i]
    1.73 +      if ((zinfo.compress_type == zipfile.ZIP_STORED
    1.74 +           and zi.compress_size == len(bytes))
    1.75 +          or (i + 1) == len(self.filelist)):
    1.76 +        # make sure we're allowed to write, otherwise done by writestr below
    1.77 +        self._writecheck(zi)
    1.78 +        # overwrite existing entry
    1.79 +        self.fp.seek(zi.header_offset)
    1.80 +        if (i + 1) == len(self.filelist):
    1.81 +          # this is the last item in the file, just truncate
    1.82 +          self.fp.truncate()
    1.83 +        else:
    1.84 +          # we need to move to the end of the file afterwards again
    1.85 +          doSeek = True
    1.86 +        # unhook the current zipinfo, the writestr of our superclass
    1.87 +        # will add a new one
    1.88 +        self.filelist.pop(i)
    1.89 +        self.NameToInfo.pop(zinfo.filename)
    1.90 +      else:
    1.91 +        # Couldn't optimize, sadly, just remember the old entry for removal
    1.92 +        self._remove.append(self.filelist.pop(i))
    1.93 +    zipfile.ZipFile.writestr(self, zinfo, bytes)
    1.94 +    self.filelist.sort(lambda l, r: cmp(l.header_offset, r.header_offset))
    1.95 +    if doSeek:
    1.96 +      self.fp.seek(self.end)
    1.97 +    self.end = self.fp.tell()
    1.98 +
    1.99 +  def close(self):
   1.100 +    """Close the file, and for mode "w" and "a" write the ending
   1.101 +    records.
   1.102 +
   1.103 +    Overwritten to compact overwritten entries.
   1.104 +    """
   1.105 +    if not self._remove:
   1.106 +      # we don't have anything special to do, let's just call base
   1.107 +      r = zipfile.ZipFile.close(self)
   1.108 +      self.lockfile = None
   1.109 +      return r
   1.110 +
   1.111 +    if self.fp.mode != 'r+b':
   1.112 +      # adjust file mode if we originally just wrote, now we rewrite
   1.113 +      self.fp.close()
   1.114 +      self.fp = open(self.filename, 'r+b')
   1.115 +    all = map(lambda zi: (zi, True), self.filelist) + \
   1.116 +        map(lambda zi: (zi, False), self._remove)
   1.117 +    all.sort(lambda l, r: cmp(l[0].header_offset, r[0].header_offset))
   1.118 +    # empty _remove for multiple closes
   1.119 +    self._remove = []
   1.120 +
   1.121 +    lengths = [all[i+1][0].header_offset - all[i][0].header_offset
   1.122 +               for i in xrange(len(all)-1)]
   1.123 +    lengths.append(self.end - all[-1][0].header_offset)
   1.124 +    to_pos = 0
   1.125 +    for (zi, keep), length in zip(all, lengths):
   1.126 +      if not keep:
   1.127 +        continue
   1.128 +      oldoff = zi.header_offset
   1.129 +      # python <= 2.4 has file_offset
   1.130 +      if hasattr(zi, 'file_offset'):
   1.131 +        zi.file_offset = zi.file_offset + to_pos - oldoff
   1.132 +      zi.header_offset = to_pos
   1.133 +      self.fp.seek(oldoff)
   1.134 +      content = self.fp.read(length)
   1.135 +      self.fp.seek(to_pos)
   1.136 +      self.fp.write(content)
   1.137 +      to_pos += length
   1.138 +    self.fp.truncate()
   1.139 +    zipfile.ZipFile.close(self)
   1.140 +    self.lockfile = None

mercurial