1
"Read and write ZIP files"
2
# Written by James C. Ahlstrom jim@interet.com
3
# All rights transferred to CNRI pursuant to the Python contribution agreement
5
import struct, os, time
6
import binascii, py_compile
9
import zlib # We may need its compression method
12
except AttributeError:
13
binascii.crc32 = zlib.crc32
17
class _BadZipfile(Exception):
19
error = _BadZipfile # The exception raised by this module
21
# constants for Zip file compression methods
24
# Other ZIP compression methods not supported
26
# Here are some struct module formats for reading headers
27
structEndArchive = "<4s4H2lH" # 9 items, end of archive, 22 bytes
28
stringEndArchive = "PK\005\006" # magic number for end of archive record
29
structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
30
stringCentralDir = "PK\001\002" # magic number for central directory
31
structFileHeader = "<4s2B4H3l2H" # 12 items, file header record, 30 bytes
32
stringFileHeader = "PK\003\004" # magic number for file header
34
def is_zipfile(filename):
35
"""Quickly see if file is a ZIP file by checking the magic number.
37
Will not accept a ZIP archive with an ending comment."""
39
fpin = open(filename, "rb")
40
fpin.seek(-22, 2) # Seek to end-of-file record
43
if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
44
return 1 # file has correct magic number
49
"Class with attributes describing each file in the ZIP archive"
50
def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
51
self.filename = filename # Name of the file in the archive
52
self.date_time = date_time # year, month, day, hour, min, sec
54
self.compress_type = ZIP_STORED # Type of compression for the file
55
self.comment = "" # Comment for each file
56
self.extra = "" # ZIP extra data
57
self.create_system = 0 # System which created ZIP archive
58
self.create_version = 20 # Version which created ZIP archive
59
self.extract_version = 20 # Version needed to extract archive
60
self.reserved = 0 # Must be zero
61
self.flag_bits = 0 # ZIP flag bits
62
self.volume = 0 # Volume number of file header
63
self.internal_attr = 0 # Internal attributes
64
self.external_attr = 0 # External file attributes
65
# Other attributes are set by class ZipFile:
66
# header_offset Byte offset to the file header
67
# file_offset Byte offset to the start of the file data
68
# CRC CRC-32 of the uncompressed file
69
# compress_size Size of the compressed file
70
# file_size Size of the uncompressed file
73
'Return the per-file header as a string'
75
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
76
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
77
if self.flag_bits & 0x08:
78
# Set these to zero because we write them after the file data
79
CRC = compress_size = file_size = 0
82
compress_size = self.compress_size
83
file_size = self.file_size
84
header = struct.pack(structFileHeader, stringFileHeader,
85
self.extract_version, self.reserved, self.flag_bits,
86
self.compress_type, dostime, dosdate, CRC,
87
compress_size, file_size,
88
len(self.filename), len(self.extra))
89
return header + self.filename + self.extra
93
"Class with methods to open, read, write, close, list zip files"
94
def __init__(self, filename, mode="r", compression=ZIP_STORED):
95
'Open the ZIP file with mode read "r", write "w" or append "a".'
96
if compression == ZIP_STORED:
98
elif compression == ZIP_DEFLATED:
101
"Compression requires the (missing) zlib module"
103
raise RuntimeError, "That compression method is not supported"
104
self.debug = 0 # Level of printing: 0 through 3
105
self.NameToInfo = {} # Find file info given name
106
self.filelist = [] # List of ZipInfo instances for archive
107
self.compression = compression # Method of compression
108
self.filename = filename
109
self.mode = key = mode[0]
111
self.fp = open(filename, "rb")
114
self.fp = open(filename, "wb")
116
fp = self.fp = open(filename, "r+b")
117
fp.seek(-22, 2) # Seek to end-of-file record
119
if endrec[0:4] == stringEndArchive and \
120
endrec[-2:] == "\000\000":
121
self._GetContents() # file is a zip file
122
# seek to start of directory and overwrite
123
fp.seek(self.start_dir, 0)
124
else: # file is not a zip file, just append
127
raise RuntimeError, 'Mode must be "r", "w" or "a"'
129
def _GetContents(self):
130
"Read in the table of contents for the zip file"
132
fp.seek(-22, 2) # Start of end-of-archive record
133
filesize = fp.tell() + 22 # Get file size
134
endrec = fp.read(22) # Archive must not end with a comment!
135
if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
136
raise error, "File is not a zip file, or ends with a comment"
137
endrec = struct.unpack(structEndArchive, endrec)
140
size_cd = endrec[5] # bytes in central directory
141
offset_cd = endrec[6] # offset of central directory
142
x = filesize - 22 - size_cd
143
# "concat" is zero, unless zip was concatenated to another file
144
concat = x - offset_cd
146
print "given, inferred, offset", offset_cd, x, concat
147
# self.start_dir: Position of start of central directory
148
self.start_dir = offset_cd + concat
149
fp.seek(self.start_dir, 0)
151
while total < size_cd:
152
centdir = fp.read(46)
154
if centdir[0:4] != stringCentralDir:
155
raise error, "Bad magic number for central directory"
156
centdir = struct.unpack(structCentralDir, centdir)
159
filename = fp.read(centdir[12])
160
# Create ZipInfo instance to store file information
161
x = ZipInfo(filename)
162
x.extra = fp.read(centdir[13])
163
x.comment = fp.read(centdir[14])
164
total = total + centdir[12] + centdir[13] + centdir[14]
165
x.header_offset = centdir[18] + concat
166
x.file_offset = x.header_offset + 30 + centdir[12] + centdir[13]
167
(x.create_version, x.create_system, x.extract_version, x.reserved,
168
x.flag_bits, x.compress_type, t, d,
169
x.CRC, x.compress_size, x.file_size) = centdir[1:12]
170
x.volume, x.internal_attr, x.external_attr = centdir[15:18]
171
# Convert date/time code to (year, month, day, hour, min, sec)
172
x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
173
t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
174
self.filelist.append(x)
175
self.NameToInfo[x.filename] = x
178
for data in self.filelist:
179
fp.seek(data.header_offset, 0)
180
fheader = fp.read(30)
181
if fheader[0:4] != stringFileHeader:
182
raise error, "Bad magic number for file header"
183
fheader = struct.unpack(structFileHeader, fheader)
184
fname = fp.read(fheader[10])
185
if fname != data.filename:
186
raise RuntimeError, \
187
'File name in Central Directory "%s" and File Header "%s" differ.' % (
188
data.filename, fname)
191
"Return a list of file names in the archive"
193
for data in self.filelist:
194
l.append(data.filename)
198
"Return a list of class ZipInfo instances for files in the archive"
202
"Print a table of contents for the zip file"
203
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
204
for zinfo in self.filelist:
205
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
206
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
209
"Read all the files and check the CRC"
210
for zinfo in self.filelist:
212
self.read(zinfo.filename) # Check CRC-32
214
return zinfo.filename
216
def getinfo(self, name):
217
'Return the instance of ZipInfo given "name"'
218
return self.NameToInfo[name]
220
def read(self, name):
221
"Return file bytes (as a string) for name"
222
if self.mode not in ("r", "a"):
223
raise RuntimeError, 'read() requires mode "r" or "a"'
225
raise RuntimeError, \
226
"Attempt to read ZIP archive that was already closed"
227
zinfo = self.getinfo(name)
228
filepos = self.fp.tell()
229
self.fp.seek(zinfo.file_offset, 0)
230
bytes = self.fp.read(zinfo.compress_size)
231
self.fp.seek(filepos, 0)
232
if zinfo.compress_type == ZIP_STORED:
234
elif zinfo.compress_type == ZIP_DEFLATED:
236
raise RuntimeError, \
237
"De-compression requires the (missing) zlib module"
238
# zlib compress/decompress code by Jeremy Hylton of CNRI
239
dc = zlib.decompressobj(-15)
240
bytes = dc.decompress(bytes)
241
# need to feed in unused pad byte so that zlib won't choke
242
ex = dc.decompress('Z') + dc.flush()
247
"Unsupported compression method %d for file %s" % \
248
(zinfo.compress_type, name)
249
crc = binascii.crc32(bytes)
251
raise error, "Bad CRC-32 for file %s" % name
254
def _writecheck(self, zinfo):
255
'Check for errors before writing a file to the archive'
256
if self.NameToInfo.has_key(zinfo.filename):
257
if self.debug: # Warning for duplicate names
258
print "Duplicate name:", zinfo.filename
259
if self.mode not in ("w", "a"):
260
raise RuntimeError, 'write() requires mode "w" or "a"'
262
raise RuntimeError, \
263
"Attempt to write ZIP archive that was already closed"
264
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
265
raise RuntimeError, \
266
"Compression requires the (missing) zlib module"
267
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
268
raise RuntimeError, \
269
"That compression method is not supported"
271
def write(self, filename, arcname=None, compress_type=None):
272
'Put the bytes from filename into the archive under the name arcname.'
273
st = os.stat(filename)
274
mtime = time.localtime(st[8])
275
date_time = mtime[0:6]
276
# Create ZipInfo instance to store file information
278
zinfo = ZipInfo(filename, date_time)
280
zinfo = ZipInfo(arcname, date_time)
281
zinfo.external_attr = st[0] << 16 # Unix attributes
282
if compress_type is None:
283
zinfo.compress_type = self.compression
285
zinfo.compress_type = compress_type
286
self._writecheck(zinfo)
287
fp = open(filename, "rb")
288
zinfo.flag_bits = 0x08
289
zinfo.header_offset = self.fp.tell() # Start of header bytes
290
self.fp.write(zinfo.FileHeader())
291
zinfo.file_offset = self.fp.tell() # Start of file bytes
295
if zinfo.compress_type == ZIP_DEFLATED:
296
cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
301
buf = fp.read(1024 * 8)
304
file_size = file_size + len(buf)
305
CRC = binascii.crc32(buf, CRC)
307
buf = cmpr.compress(buf)
308
compress_size = compress_size + len(buf)
313
compress_size = compress_size + len(buf)
315
zinfo.compress_size = compress_size
317
zinfo.compress_size = file_size
319
zinfo.file_size = file_size
320
# Write CRC and file sizes after the file data
321
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
323
self.filelist.append(zinfo)
324
self.NameToInfo[zinfo.filename] = zinfo
326
def writestr(self, zinfo, bytes):
327
'Write a file into the archive. The contents is the string "bytes"'
328
self._writecheck(zinfo)
329
zinfo.file_size = len(bytes) # Uncompressed size
330
zinfo.CRC = binascii.crc32(bytes) # CRC-32 checksum
331
if zinfo.compress_type == ZIP_DEFLATED:
332
co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
334
bytes = co.compress(bytes) + co.flush()
335
zinfo.compress_size = len(bytes) # Compressed size
337
zinfo.compress_size = zinfo.file_size
338
zinfo.header_offset = self.fp.tell() # Start of header bytes
339
self.fp.write(zinfo.FileHeader())
340
zinfo.file_offset = self.fp.tell() # Start of file bytes
342
if zinfo.flag_bits & 0x08:
343
# Write CRC and file sizes after the file data
344
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
346
self.filelist.append(zinfo)
347
self.NameToInfo[zinfo.filename] = zinfo
350
'Call the "close()" method in case the user forgot'
356
'Close the file, and for mode "w" and "a" write the ending records'
357
if self.mode in ("w", "a"): # write ending records
359
pos1 = self.fp.tell()
360
for zinfo in self.filelist: # write central directory
363
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
364
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
365
centdir = struct.pack(structCentralDir,
366
stringCentralDir, zinfo.create_version,
367
zinfo.create_system, zinfo.extract_version, zinfo.reserved,
368
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
369
zinfo.CRC, zinfo.compress_size, zinfo.file_size,
370
len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
371
0, zinfo.internal_attr, zinfo.external_attr,
373
self.fp.write(centdir)
374
self.fp.write(zinfo.filename)
375
self.fp.write(zinfo.extra)
376
self.fp.write(zinfo.comment)
377
pos2 = self.fp.tell()
378
# Write end-of-zip-archive record
379
endrec = struct.pack(structEndArchive, stringEndArchive,
380
0, 0, count, count, pos2 - pos1, pos1, 0)
381
self.fp.write(endrec)
386
class PyZipFile(ZipFile):
387
"Class to create ZIP archives with Python library files and packages"
388
def writepy(self, pathname, basename = ""):
389
"""Add all files from "pathname" to the ZIP archive.
391
If pathname is a package directory, search the directory and all
392
package subdirectories recursively for all *.py and enter the modules into
393
the archive. If pathname is a plain directory, listdir *.py and enter all
394
modules. Else, pathname must be a Python *.py file and the module will be
395
put into the archive. Added modules are always module.pyo or module.pyc.
396
This method will compile the module.py into module.pyc if necessary."""
397
dir, name = os.path.split(pathname)
398
if os.path.isdir(pathname):
399
initname = os.path.join(pathname, "__init__.py")
400
if os.path.isfile(initname):
401
# This is a package directory, add it
403
basename = "%s/%s" % (basename, name)
407
print "Adding package in", pathname, "as", basename
408
fname, arcname = self._get_codename(initname[0:-3], basename)
410
print "Adding", arcname
411
self.write(fname, arcname)
412
dirlist = os.listdir(pathname)
413
dirlist.remove("__init__.py")
414
# Add all *.py files and package subdirectories
415
for filename in dirlist:
416
path = os.path.join(pathname, filename)
417
root, ext = os.path.splitext(filename)
418
if os.path.isdir(path):
419
if os.path.isfile(os.path.join(path, "__init__.py")):
420
# This is a package directory, add it
421
self.writepy(path, basename) # Recursive call
423
fname, arcname = self._get_codename(path[0:-3],
426
print "Adding", arcname
427
self.write(fname, arcname)
429
# This is NOT a package directory, add its files at top level
431
print "Adding files from directory", pathname
432
for filename in os.listdir(pathname):
433
path = os.path.join(pathname, filename)
434
root, ext = os.path.splitext(filename)
436
fname, arcname = self._get_codename(path[0:-3],
439
print "Adding", arcname
440
self.write(fname, arcname)
442
if pathname[-3:] != ".py":
443
raise RuntimeError, \
444
'Files added with writepy() must end with ".py"'
445
fname, arcname = self._get_codename(pathname[0:-3], basename)
447
print "Adding file", arcname
448
self.write(fname, arcname)
450
def _get_codename(self, pathname, basename):
451
"""Return (filename, archivename) for the path.
453
Given a module name path, return the correct file path and archive name,
454
compiling if necessary. For example, given /python/lib/string,
455
return (/python/lib/string.pyc, string)"""
456
file_py = pathname + ".py"
457
file_pyc = pathname + ".pyc"
458
file_pyo = pathname + ".pyo"
459
if os.path.isfile(file_pyo) and \
460
os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
461
fname = file_pyo # Use .pyo file
462
elif not os.path.isfile(file_pyc) or \
463
os.stat(file_pyc)[8] < os.stat(file_py)[8]:
465
print "Compiling", file_py
466
py_compile.compile(file_py, file_pyc)
470
archivename = os.path.split(fname)[1]
472
archivename = "%s/%s" % (basename, archivename)
473
return (fname, archivename)
1
"Read and write ZIP files"
2
# Written by James C. Ahlstrom jim@interet.com
3
# All rights transferred to CNRI pursuant to the Python contribution agreement
5
import struct, os, time
6
import binascii, py_compile
9
import zlib # We may need its compression method
12
except AttributeError:
13
binascii.crc32 = zlib.crc32
17
class _BadZipfile(Exception):
19
error = _BadZipfile # The exception raised by this module
21
# constants for Zip file compression methods
24
# Other ZIP compression methods not supported
26
# Here are some struct module formats for reading headers
27
structEndArchive = "<4s4H2lH" # 9 items, end of archive, 22 bytes
28
stringEndArchive = "PK\005\006" # magic number for end of archive record
29
structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
30
stringCentralDir = "PK\001\002" # magic number for central directory
31
structFileHeader = "<4s2B4H3l2H" # 12 items, file header record, 30 bytes
32
stringFileHeader = "PK\003\004" # magic number for file header
34
def is_zipfile(filename):
35
"""Quickly see if file is a ZIP file by checking the magic number.
37
Will not accept a ZIP archive with an ending comment."""
39
fpin = open(filename, "rb")
40
fpin.seek(-22, 2) # Seek to end-of-file record
43
if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
44
return 1 # file has correct magic number
49
"Class with attributes describing each file in the ZIP archive"
50
def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
51
self.filename = filename # Name of the file in the archive
52
self.date_time = date_time # year, month, day, hour, min, sec
54
self.compress_type = ZIP_STORED # Type of compression for the file
55
self.comment = "" # Comment for each file
56
self.extra = "" # ZIP extra data
57
self.create_system = 0 # System which created ZIP archive
58
self.create_version = 20 # Version which created ZIP archive
59
self.extract_version = 20 # Version needed to extract archive
60
self.reserved = 0 # Must be zero
61
self.flag_bits = 0 # ZIP flag bits
62
self.volume = 0 # Volume number of file header
63
self.internal_attr = 0 # Internal attributes
64
self.external_attr = 0 # External file attributes
65
# Other attributes are set by class ZipFile:
66
# header_offset Byte offset to the file header
67
# file_offset Byte offset to the start of the file data
68
# CRC CRC-32 of the uncompressed file
69
# compress_size Size of the compressed file
70
# file_size Size of the uncompressed file
73
'Return the per-file header as a string'
75
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
76
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
77
if self.flag_bits & 0x08:
78
# Set these to zero because we write them after the file data
79
CRC = compress_size = file_size = 0
82
compress_size = self.compress_size
83
file_size = self.file_size
84
header = struct.pack(structFileHeader, stringFileHeader,
85
self.extract_version, self.reserved, self.flag_bits,
86
self.compress_type, dostime, dosdate, CRC,
87
compress_size, file_size,
88
len(self.filename), len(self.extra))
89
return header + self.filename + self.extra
93
"Class with methods to open, read, write, close, list zip files"
94
def __init__(self, filename, mode="r", compression=ZIP_STORED):
95
'Open the ZIP file with mode read "r", write "w" or append "a".'
96
if compression == ZIP_STORED:
98
elif compression == ZIP_DEFLATED:
101
"Compression requires the (missing) zlib module"
103
raise RuntimeError, "That compression method is not supported"
104
self.debug = 0 # Level of printing: 0 through 3
105
self.NameToInfo = {} # Find file info given name
106
self.filelist = [] # List of ZipInfo instances for archive
107
self.compression = compression # Method of compression
108
self.filename = filename
109
self.mode = key = mode[0]
111
self.fp = open(filename, "rb")
114
self.fp = open(filename, "wb")
116
fp = self.fp = open(filename, "r+b")
117
fp.seek(-22, 2) # Seek to end-of-file record
119
if endrec[0:4] == stringEndArchive and \
120
endrec[-2:] == "\000\000":
121
self._GetContents() # file is a zip file
122
# seek to start of directory and overwrite
123
fp.seek(self.start_dir, 0)
124
else: # file is not a zip file, just append
127
raise RuntimeError, 'Mode must be "r", "w" or "a"'
129
def _GetContents(self):
130
"Read in the table of contents for the zip file"
132
fp.seek(-22, 2) # Start of end-of-archive record
133
filesize = fp.tell() + 22 # Get file size
134
endrec = fp.read(22) # Archive must not end with a comment!
135
if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
136
raise error, "File is not a zip file, or ends with a comment"
137
endrec = struct.unpack(structEndArchive, endrec)
140
size_cd = endrec[5] # bytes in central directory
141
offset_cd = endrec[6] # offset of central directory
142
x = filesize - 22 - size_cd
143
# "concat" is zero, unless zip was concatenated to another file
144
concat = x - offset_cd
146
print "given, inferred, offset", offset_cd, x, concat
147
# self.start_dir: Position of start of central directory
148
self.start_dir = offset_cd + concat
149
fp.seek(self.start_dir, 0)
151
while total < size_cd:
152
centdir = fp.read(46)
154
if centdir[0:4] != stringCentralDir:
155
raise error, "Bad magic number for central directory"
156
centdir = struct.unpack(structCentralDir, centdir)
159
filename = fp.read(centdir[12])
160
# Create ZipInfo instance to store file information
161
x = ZipInfo(filename)
162
x.extra = fp.read(centdir[13])
163
x.comment = fp.read(centdir[14])
164
total = total + centdir[12] + centdir[13] + centdir[14]
165
x.header_offset = centdir[18] + concat
166
x.file_offset = x.header_offset + 30 + centdir[12] + centdir[13]
167
(x.create_version, x.create_system, x.extract_version, x.reserved,
168
x.flag_bits, x.compress_type, t, d,
169
x.CRC, x.compress_size, x.file_size) = centdir[1:12]
170
x.volume, x.internal_attr, x.external_attr = centdir[15:18]
171
# Convert date/time code to (year, month, day, hour, min, sec)
172
x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
173
t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
174
self.filelist.append(x)
175
self.NameToInfo[x.filename] = x
178
for data in self.filelist:
179
fp.seek(data.header_offset, 0)
180
fheader = fp.read(30)
181
if fheader[0:4] != stringFileHeader:
182
raise error, "Bad magic number for file header"
183
fheader = struct.unpack(structFileHeader, fheader)
184
fname = fp.read(fheader[10])
185
if fname != data.filename:
186
raise RuntimeError, \
187
'File name in Central Directory "%s" and File Header "%s" differ.' % (
188
data.filename, fname)
191
"Return a list of file names in the archive"
193
for data in self.filelist:
194
l.append(data.filename)
198
"Return a list of class ZipInfo instances for files in the archive"
202
"Print a table of contents for the zip file"
203
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
204
for zinfo in self.filelist:
205
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
206
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
209
"Read all the files and check the CRC"
210
for zinfo in self.filelist:
212
self.read(zinfo.filename) # Check CRC-32
214
return zinfo.filename
216
def getinfo(self, name):
217
'Return the instance of ZipInfo given "name"'
218
return self.NameToInfo[name]
220
def read(self, name):
221
"Return file bytes (as a string) for name"
222
if self.mode not in ("r", "a"):
223
raise RuntimeError, 'read() requires mode "r" or "a"'
225
raise RuntimeError, \
226
"Attempt to read ZIP archive that was already closed"
227
zinfo = self.getinfo(name)
228
filepos = self.fp.tell()
229
self.fp.seek(zinfo.file_offset, 0)
230
bytes = self.fp.read(zinfo.compress_size)
231
self.fp.seek(filepos, 0)
232
if zinfo.compress_type == ZIP_STORED:
234
elif zinfo.compress_type == ZIP_DEFLATED:
236
raise RuntimeError, \
237
"De-compression requires the (missing) zlib module"
238
# zlib compress/decompress code by Jeremy Hylton of CNRI
239
dc = zlib.decompressobj(-15)
240
bytes = dc.decompress(bytes)
241
# need to feed in unused pad byte so that zlib won't choke
242
ex = dc.decompress('Z') + dc.flush()
247
"Unsupported compression method %d for file %s" % \
248
(zinfo.compress_type, name)
249
crc = binascii.crc32(bytes)
251
raise error, "Bad CRC-32 for file %s" % name
254
def _writecheck(self, zinfo):
255
'Check for errors before writing a file to the archive'
256
if self.NameToInfo.has_key(zinfo.filename):
257
if self.debug: # Warning for duplicate names
258
print "Duplicate name:", zinfo.filename
259
if self.mode not in ("w", "a"):
260
raise RuntimeError, 'write() requires mode "w" or "a"'
262
raise RuntimeError, \
263
"Attempt to write ZIP archive that was already closed"
264
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
265
raise RuntimeError, \
266
"Compression requires the (missing) zlib module"
267
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
268
raise RuntimeError, \
269
"That compression method is not supported"
271
def write(self, filename, arcname=None, compress_type=None):
272
'Put the bytes from filename into the archive under the name arcname.'
273
st = os.stat(filename)
274
mtime = time.localtime(st[8])
275
date_time = mtime[0:6]
276
# Create ZipInfo instance to store file information
278
zinfo = ZipInfo(filename, date_time)
280
zinfo = ZipInfo(arcname, date_time)
281
zinfo.external_attr = st[0] << 16 # Unix attributes
282
if compress_type is None:
283
zinfo.compress_type = self.compression
285
zinfo.compress_type = compress_type
286
self._writecheck(zinfo)
287
fp = open(filename, "rb")
288
zinfo.flag_bits = 0x08
289
zinfo.header_offset = self.fp.tell() # Start of header bytes
290
self.fp.write(zinfo.FileHeader())
291
zinfo.file_offset = self.fp.tell() # Start of file bytes
295
if zinfo.compress_type == ZIP_DEFLATED:
296
cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
301
buf = fp.read(1024 * 8)
304
file_size = file_size + len(buf)
305
CRC = binascii.crc32(buf, CRC)
307
buf = cmpr.compress(buf)
308
compress_size = compress_size + len(buf)
313
compress_size = compress_size + len(buf)
315
zinfo.compress_size = compress_size
317
zinfo.compress_size = file_size
319
zinfo.file_size = file_size
320
# Write CRC and file sizes after the file data
321
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
323
self.filelist.append(zinfo)
324
self.NameToInfo[zinfo.filename] = zinfo
326
def writestr(self, zinfo, bytes):
327
'Write a file into the archive. The contents is the string "bytes"'
328
self._writecheck(zinfo)
329
zinfo.file_size = len(bytes) # Uncompressed size
330
zinfo.CRC = binascii.crc32(bytes) # CRC-32 checksum
331
if zinfo.compress_type == ZIP_DEFLATED:
332
co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
334
bytes = co.compress(bytes) + co.flush()
335
zinfo.compress_size = len(bytes) # Compressed size
337
zinfo.compress_size = zinfo.file_size
338
zinfo.header_offset = self.fp.tell() # Start of header bytes
339
self.fp.write(zinfo.FileHeader())
340
zinfo.file_offset = self.fp.tell() # Start of file bytes
342
if zinfo.flag_bits & 0x08:
343
# Write CRC and file sizes after the file data
344
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
346
self.filelist.append(zinfo)
347
self.NameToInfo[zinfo.filename] = zinfo
350
'Call the "close()" method in case the user forgot'
356
'Close the file, and for mode "w" and "a" write the ending records'
357
if self.mode in ("w", "a"): # write ending records
359
pos1 = self.fp.tell()
360
for zinfo in self.filelist: # write central directory
363
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
364
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
365
centdir = struct.pack(structCentralDir,
366
stringCentralDir, zinfo.create_version,
367
zinfo.create_system, zinfo.extract_version, zinfo.reserved,
368
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
369
zinfo.CRC, zinfo.compress_size, zinfo.file_size,
370
len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
371
0, zinfo.internal_attr, zinfo.external_attr,
373
self.fp.write(centdir)
374
self.fp.write(zinfo.filename)
375
self.fp.write(zinfo.extra)
376
self.fp.write(zinfo.comment)
377
pos2 = self.fp.tell()
378
# Write end-of-zip-archive record
379
endrec = struct.pack(structEndArchive, stringEndArchive,
380
0, 0, count, count, pos2 - pos1, pos1, 0)
381
self.fp.write(endrec)
386
class PyZipFile(ZipFile):
387
"Class to create ZIP archives with Python library files and packages"
388
def writepy(self, pathname, basename = ""):
389
"""Add all files from "pathname" to the ZIP archive.
391
If pathname is a package directory, search the directory and all
392
package subdirectories recursively for all *.py and enter the modules into
393
the archive. If pathname is a plain directory, listdir *.py and enter all
394
modules. Else, pathname must be a Python *.py file and the module will be
395
put into the archive. Added modules are always module.pyo or module.pyc.
396
This method will compile the module.py into module.pyc if necessary."""
397
dir, name = os.path.split(pathname)
398
if os.path.isdir(pathname):
399
initname = os.path.join(pathname, "__init__.py")
400
if os.path.isfile(initname):
401
# This is a package directory, add it
403
basename = "%s/%s" % (basename, name)
407
print "Adding package in", pathname, "as", basename
408
fname, arcname = self._get_codename(initname[0:-3], basename)
410
print "Adding", arcname
411
self.write(fname, arcname)
412
dirlist = os.listdir(pathname)
413
dirlist.remove("__init__.py")
414
# Add all *.py files and package subdirectories
415
for filename in dirlist:
416
path = os.path.join(pathname, filename)
417
root, ext = os.path.splitext(filename)
418
if os.path.isdir(path):
419
if os.path.isfile(os.path.join(path, "__init__.py")):
420
# This is a package directory, add it
421
self.writepy(path, basename) # Recursive call
423
fname, arcname = self._get_codename(path[0:-3],
426
print "Adding", arcname
427
self.write(fname, arcname)
429
# This is NOT a package directory, add its files at top level
431
print "Adding files from directory", pathname
432
for filename in os.listdir(pathname):
433
path = os.path.join(pathname, filename)
434
root, ext = os.path.splitext(filename)
436
fname, arcname = self._get_codename(path[0:-3],
439
print "Adding", arcname
440
self.write(fname, arcname)
442
if pathname[-3:] != ".py":
443
raise RuntimeError, \
444
'Files added with writepy() must end with ".py"'
445
fname, arcname = self._get_codename(pathname[0:-3], basename)
447
print "Adding file", arcname
448
self.write(fname, arcname)
450
def _get_codename(self, pathname, basename):
451
"""Return (filename, archivename) for the path.
453
Given a module name path, return the correct file path and archive name,
454
compiling if necessary. For example, given /python/lib/string,
455
return (/python/lib/string.pyc, string)"""
456
file_py = pathname + ".py"
457
file_pyc = pathname + ".pyc"
458
file_pyo = pathname + ".pyo"
459
if os.path.isfile(file_pyo) and \
460
os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
461
fname = file_pyo # Use .pyo file
462
elif not os.path.isfile(file_pyc) or \
463
os.stat(file_pyc)[8] < os.stat(file_py)[8]:
465
print "Compiling", file_py
466
py_compile.compile(file_py, file_pyc)
470
archivename = os.path.split(fname)[1]
472
archivename = "%s/%s" % (basename, archivename)
473
return (fname, archivename)