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