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