Bug 1467936 [wpt PR 11436] - Split up editing/run/* with `variant`, a=testonly
[gecko.git] / config / MozZipFile.py
blobd483614355fabeec12afed43c78247f5071a7204
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 import os
6 import time
7 import zipfile
9 from mozbuild.util import lock_file
12 class ZipFile(zipfile.ZipFile):
13 """ Class with methods to open, read, write, close, list zip files.
15 Subclassing zipfile.ZipFile to allow for overwriting of existing
16 entries, though only for writestr, not for write.
17 """
19 def __init__(self, file, mode="r", compression=zipfile.ZIP_STORED,
20 lock=False):
21 if lock:
22 assert isinstance(file, basestring)
23 self.lockfile = lock_file(file + '.lck')
24 else:
25 self.lockfile = None
27 if mode == 'a' and lock:
28 # appending to a file which doesn't exist fails, but we can't check
29 # existence util we hold the lock
30 if (not os.path.isfile(file)) or os.path.getsize(file) == 0:
31 mode = 'w'
33 zipfile.ZipFile.__init__(self, file, mode, compression)
34 self._remove = []
35 self.end = self.fp.tell()
36 self.debug = 0
38 def writestr(self, zinfo_or_arcname, bytes):
39 """Write contents into the archive.
41 The contents is the argument 'bytes', 'zinfo_or_arcname' is either
42 a ZipInfo instance or the name of the file in the archive.
43 This method is overloaded to allow overwriting existing entries.
44 """
45 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
46 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname,
47 date_time=time.localtime(time.time()))
48 zinfo.compress_type = self.compression
49 # Add some standard UNIX file access permissions (-rw-r--r--).
50 zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
51 else:
52 zinfo = zinfo_or_arcname
54 # Now to the point why we overwrote this in the first place,
55 # remember the entry numbers if we already had this entry.
56 # Optimizations:
57 # If the entry to overwrite is the last one, just reuse that.
58 # If we store uncompressed and the new content has the same size
59 # as the old, reuse the existing entry.
61 doSeek = False # store if we need to seek to the eof after overwriting
62 if zinfo.filename in self.NameToInfo:
63 # Find the last ZipInfo with our name.
64 # Last, because that's catching multiple overwrites
65 i = len(self.filelist)
66 while i > 0:
67 i -= 1
68 if self.filelist[i].filename == zinfo.filename:
69 break
70 zi = self.filelist[i]
71 if ((zinfo.compress_type == zipfile.ZIP_STORED
72 and zi.compress_size == len(bytes))
73 or (i + 1) == len(self.filelist)):
74 # make sure we're allowed to write, otherwise done by writestr below
75 self._writecheck(zi)
76 # overwrite existing entry
77 self.fp.seek(zi.header_offset)
78 if (i + 1) == len(self.filelist):
79 # this is the last item in the file, just truncate
80 self.fp.truncate()
81 else:
82 # we need to move to the end of the file afterwards again
83 doSeek = True
84 # unhook the current zipinfo, the writestr of our superclass
85 # will add a new one
86 self.filelist.pop(i)
87 self.NameToInfo.pop(zinfo.filename)
88 else:
89 # Couldn't optimize, sadly, just remember the old entry for removal
90 self._remove.append(self.filelist.pop(i))
91 zipfile.ZipFile.writestr(self, zinfo, bytes)
92 self.filelist.sort(lambda l, r: cmp(l.header_offset, r.header_offset))
93 if doSeek:
94 self.fp.seek(self.end)
95 self.end = self.fp.tell()
97 def close(self):
98 """Close the file, and for mode "w" and "a" write the ending
99 records.
101 Overwritten to compact overwritten entries.
103 if not self._remove:
104 # we don't have anything special to do, let's just call base
105 r = zipfile.ZipFile.close(self)
106 self.lockfile = None
107 return r
109 if self.fp.mode != 'r+b':
110 # adjust file mode if we originally just wrote, now we rewrite
111 self.fp.close()
112 self.fp = open(self.filename, 'r+b')
113 all = map(lambda zi: (zi, True), self.filelist) + \
114 map(lambda zi: (zi, False), self._remove)
115 all.sort(lambda l, r: cmp(l[0].header_offset, r[0].header_offset))
116 # empty _remove for multiple closes
117 self._remove = []
119 lengths = [all[i+1][0].header_offset - all[i][0].header_offset
120 for i in xrange(len(all)-1)]
121 lengths.append(self.end - all[-1][0].header_offset)
122 to_pos = 0
123 for (zi, keep), length in zip(all, lengths):
124 if not keep:
125 continue
126 oldoff = zi.header_offset
127 # python <= 2.4 has file_offset
128 if hasattr(zi, 'file_offset'):
129 zi.file_offset = zi.file_offset + to_pos - oldoff
130 zi.header_offset = to_pos
131 self.fp.seek(oldoff)
132 content = self.fp.read(length)
133 self.fp.seek(to_pos)
134 self.fp.write(content)
135 to_pos += length
136 self.fp.truncate()
137 zipfile.ZipFile.close(self)
138 self.lockfile = None