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/.
7
import binascii, struct
10
from utils import lockFile
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.
18
def __init__(self, file, mode="r", compression=zipfile.ZIP_STORED,
21
assert isinstance(file, basestring)
22
self.lockfile = lockFile(file + '.lck')
26
if mode == 'a' and lock:
27
# appending to a file which doesn't exist fails, but we can't check
28
# existence util we hold the lock
29
if (not os.path.isfile(file)) or os.path.getsize(file) == 0:
32
zipfile.ZipFile.__init__(self, file, mode, compression)
34
self.end = self.fp.tell()
37
def writestr(self, zinfo_or_arcname, bytes):
38
"""Write contents into the archive.
40
The contents is the argument 'bytes', 'zinfo_or_arcname' is either
41
a ZipInfo instance or the name of the file in the archive.
42
This method is overloaded to allow overwriting existing entries.
44
if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
45
zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname,
46
date_time=time.localtime(time.time()))
47
zinfo.compress_type = self.compression
48
# Add some standard UNIX file access permissions (-rw-r--r--).
49
zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
51
zinfo = zinfo_or_arcname
53
# Now to the point why we overwrote this in the first place,
54
# remember the entry numbers if we already had this entry.
56
# If the entry to overwrite is the last one, just reuse that.
57
# If we store uncompressed and the new content has the same size
58
# as the old, reuse the existing entry.
60
doSeek = False # store if we need to seek to the eof after overwriting
61
if self.NameToInfo.has_key(zinfo.filename):
62
# Find the last ZipInfo with our name.
63
# Last, because that's catching multiple overwrites
64
i = len(self.filelist)
67
if self.filelist[i].filename == zinfo.filename:
70
if ((zinfo.compress_type == zipfile.ZIP_STORED
71
and zi.compress_size == len(bytes))
72
or (i + 1) == len(self.filelist)):
73
# make sure we're allowed to write, otherwise done by writestr below
75
# overwrite existing entry
76
self.fp.seek(zi.header_offset)
77
if (i + 1) == len(self.filelist):
78
# this is the last item in the file, just truncate
81
# we need to move to the end of the file afterwards again
83
# unhook the current zipinfo, the writestr of our superclass
86
self.NameToInfo.pop(zinfo.filename)
88
# Couldn't optimize, sadly, just remember the old entry for removal
89
self._remove.append(self.filelist.pop(i))
90
zipfile.ZipFile.writestr(self, zinfo, bytes)
91
self.filelist.sort(lambda l, r: cmp(l.header_offset, r.header_offset))
93
self.fp.seek(self.end)
94
self.end = self.fp.tell()
97
"""Close the file, and for mode "w" and "a" write the ending
100
Overwritten to compact overwritten entries.
103
# we don't have anything special to do, let's just call base
104
r = zipfile.ZipFile.close(self)
108
if self.fp.mode != 'r+b':
109
# adjust file mode if we originally just wrote, now we rewrite
111
self.fp = open(self.filename, 'r+b')
112
all = map(lambda zi: (zi, True), self.filelist) + \
113
map(lambda zi: (zi, False), self._remove)
114
all.sort(lambda l, r: cmp(l[0].header_offset, r[0].header_offset))
115
# empty _remove for multiple closes
118
lengths = [all[i+1][0].header_offset - all[i][0].header_offset
119
for i in xrange(len(all)-1)]
120
lengths.append(self.end - all[-1][0].header_offset)
122
for (zi, keep), length in zip(all, lengths):
125
oldoff = zi.header_offset
126
# python <= 2.4 has file_offset
127
if hasattr(zi, 'file_offset'):
128
zi.file_offset = zi.file_offset + to_pos - oldoff
129
zi.header_offset = to_pos
131
content = self.fp.read(length)
133
self.fp.write(content)
136
zipfile.ZipFile.close(self)