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