blob: 5a7345ba70d6abe99f2eb57854cd33b4551ed562 [file] [log] [blame]
Fred Drake484d7352000-10-02 21:14:52 +00001"Read and write ZIP files."
Guido van Rossum32abe6f2000-03-31 17:30:02 +00002# Written by James C. Ahlstrom jim@interet.com
3# All rights transferred to CNRI pursuant to the Python contribution agreement
4
5import struct, os, time
Fred Drake484d7352000-10-02 21:14:52 +00006import binascii
Guido van Rossum32abe6f2000-03-31 17:30:02 +00007
8try:
Tim Peterse1190062001-01-15 03:34:38 +00009 import zlib # We may need its compression method
Guido van Rossum32abe6f2000-03-31 17:30:02 +000010except:
11 zlib = None
12
Fred Drake5db246d2000-09-29 20:44:48 +000013class BadZipfile(Exception):
Guido van Rossum32abe6f2000-03-31 17:30:02 +000014 pass
Tim Peterse1190062001-01-15 03:34:38 +000015error = BadZipfile # The exception raised by this module
Guido van Rossum32abe6f2000-03-31 17:30:02 +000016
17# constants for Zip file compression methods
18ZIP_STORED = 0
19ZIP_DEFLATED = 8
20# Other ZIP compression methods not supported
21
22# Here are some struct module formats for reading headers
23structEndArchive = "<4s4H2lH" # 9 items, end of archive, 22 bytes
24stringEndArchive = "PK\005\006" # magic number for end of archive record
25structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
26stringCentralDir = "PK\001\002" # magic number for central directory
27structFileHeader = "<4s2B4H3l2H" # 12 items, file header record, 30 bytes
28stringFileHeader = "PK\003\004" # magic number for file header
29
Fred Drake3e038e52001-02-28 17:56:26 +000030# indexes of entries in the central directory structure
31_CD_SIGNATURE = 0
32_CD_CREATE_VERSION = 1
33_CD_CREATE_SYSTEM = 2
34_CD_EXTRACT_VERSION = 3
35_CD_EXTRACT_SYSTEM = 4 # is this meaningful?
36_CD_FLAG_BITS = 5
37_CD_COMPRESS_TYPE = 6
38_CD_TIME = 7
39_CD_DATE = 8
40_CD_CRC = 9
41_CD_COMPRESSED_SIZE = 10
42_CD_UNCOMPRESSED_SIZE = 11
43_CD_FILENAME_LENGTH = 12
44_CD_EXTRA_FIELD_LENGTH = 13
45_CD_COMMENT_LENGTH = 14
46_CD_DISK_NUMBER_START = 15
47_CD_INTERNAL_FILE_ATTRIBUTES = 16
48_CD_EXTERNAL_FILE_ATTRIBUTES = 17
49_CD_LOCAL_HEADER_OFFSET = 18
50
51# indexes of entries in the local file header structure
52_FH_SIGNATURE = 0
53_FH_EXTRACT_VERSION = 1
54_FH_EXTRACT_SYSTEM = 2 # is this meaningful?
55_FH_GENERAL_PURPOSE_FLAG_BITS = 3
56_FH_COMPRESSION_METHOD = 4
57_FH_LAST_MOD_TIME = 5
58_FH_LAST_MOD_DATE = 6
59_FH_CRC = 7
60_FH_COMPRESSED_SIZE = 8
61_FH_UNCOMPRESSED_SIZE = 9
62_FH_FILENAME_LENGTH = 10
63_FH_EXTRA_FIELD_LENGTH = 11
64
Fred Drake484d7352000-10-02 21:14:52 +000065
Guido van Rossum32abe6f2000-03-31 17:30:02 +000066def is_zipfile(filename):
67 """Quickly see if file is a ZIP file by checking the magic number.
68
Fred Drake484d7352000-10-02 21:14:52 +000069 Will not accept a ZIP archive with an ending comment.
70 """
Guido van Rossum32abe6f2000-03-31 17:30:02 +000071 try:
72 fpin = open(filename, "rb")
Tim Peterse1190062001-01-15 03:34:38 +000073 fpin.seek(-22, 2) # Seek to end-of-file record
Guido van Rossum32abe6f2000-03-31 17:30:02 +000074 endrec = fpin.read()
75 fpin.close()
76 if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
Tim Peterse1190062001-01-15 03:34:38 +000077 return 1 # file has correct magic number
Guido van Rossum32abe6f2000-03-31 17:30:02 +000078 except:
79 pass
80
Fred Drake484d7352000-10-02 21:14:52 +000081
Guido van Rossum32abe6f2000-03-31 17:30:02 +000082class ZipInfo:
Fred Drake484d7352000-10-02 21:14:52 +000083 """Class with attributes describing each file in the ZIP archive."""
84
Guido van Rossum32abe6f2000-03-31 17:30:02 +000085 def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
Tim Peterse1190062001-01-15 03:34:38 +000086 self.filename = filename # Name of the file in the archive
87 self.date_time = date_time # year, month, day, hour, min, sec
Guido van Rossum32abe6f2000-03-31 17:30:02 +000088 # Standard values:
Tim Peterse1190062001-01-15 03:34:38 +000089 self.compress_type = ZIP_STORED # Type of compression for the file
90 self.comment = "" # Comment for each file
91 self.extra = "" # ZIP extra data
92 self.create_system = 0 # System which created ZIP archive
93 self.create_version = 20 # Version which created ZIP archive
94 self.extract_version = 20 # Version needed to extract archive
95 self.reserved = 0 # Must be zero
96 self.flag_bits = 0 # ZIP flag bits
97 self.volume = 0 # Volume number of file header
98 self.internal_attr = 0 # Internal attributes
99 self.external_attr = 0 # External file attributes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000100 # Other attributes are set by class ZipFile:
Tim Peterse1190062001-01-15 03:34:38 +0000101 # header_offset Byte offset to the file header
102 # file_offset Byte offset to the start of the file data
103 # CRC CRC-32 of the uncompressed file
104 # compress_size Size of the compressed file
105 # file_size Size of the uncompressed file
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000106
107 def FileHeader(self):
Fred Drake484d7352000-10-02 21:14:52 +0000108 """Return the per-file header as a string."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000109 dt = self.date_time
110 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
111 dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
112 if self.flag_bits & 0x08:
Tim Peterse1190062001-01-15 03:34:38 +0000113 # Set these to zero because we write them after the file data
114 CRC = compress_size = file_size = 0
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000115 else:
Tim Peterse1190062001-01-15 03:34:38 +0000116 CRC = self.CRC
117 compress_size = self.compress_size
118 file_size = self.file_size
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000119 header = struct.pack(structFileHeader, stringFileHeader,
120 self.extract_version, self.reserved, self.flag_bits,
121 self.compress_type, dostime, dosdate, CRC,
122 compress_size, file_size,
123 len(self.filename), len(self.extra))
124 return header + self.filename + self.extra
125
126
127class ZipFile:
Fred Drake484d7352000-10-02 21:14:52 +0000128 """Class with methods to open, read, write, close, list zip files."""
129
Fred Drake90eac282001-02-28 05:29:34 +0000130 fp = None # Set here since __del__ checks it
131
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000132 def __init__(self, filename, mode="r", compression=ZIP_STORED):
Fred Drake484d7352000-10-02 21:14:52 +0000133 """Open the ZIP file with mode read "r", write "w" or append "a"."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000134 if compression == ZIP_STORED:
135 pass
136 elif compression == ZIP_DEFLATED:
137 if not zlib:
138 raise RuntimeError,\
Fred Drake5db246d2000-09-29 20:44:48 +0000139 "Compression requires the (missing) zlib module"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000140 else:
141 raise RuntimeError, "That compression method is not supported"
Tim Peterse1190062001-01-15 03:34:38 +0000142 self.debug = 0 # Level of printing: 0 through 3
143 self.NameToInfo = {} # Find file info given name
144 self.filelist = [] # List of ZipInfo instances for archive
145 self.compression = compression # Method of compression
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000146 self.filename = filename
147 self.mode = key = mode[0]
148 if key == 'r':
149 self.fp = open(filename, "rb")
150 self._GetContents()
151 elif key == 'w':
152 self.fp = open(filename, "wb")
153 elif key == 'a':
154 fp = self.fp = open(filename, "r+b")
Tim Peterse1190062001-01-15 03:34:38 +0000155 fp.seek(-22, 2) # Seek to end-of-file record
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000156 endrec = fp.read()
157 if endrec[0:4] == stringEndArchive and \
158 endrec[-2:] == "\000\000":
Tim Peterse1190062001-01-15 03:34:38 +0000159 self._GetContents() # file is a zip file
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000160 # seek to start of directory and overwrite
161 fp.seek(self.start_dir, 0)
Tim Peterse1190062001-01-15 03:34:38 +0000162 else: # file is not a zip file, just append
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000163 fp.seek(0, 2)
164 else:
165 raise RuntimeError, 'Mode must be "r", "w" or "a"'
166
167 def _GetContents(self):
Fred Drake484d7352000-10-02 21:14:52 +0000168 """Read in the table of contents for the ZIP file."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000169 fp = self.fp
Tim Peterse1190062001-01-15 03:34:38 +0000170 fp.seek(-22, 2) # Start of end-of-archive record
171 filesize = fp.tell() + 22 # Get file size
172 endrec = fp.read(22) # Archive must not end with a comment!
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000173 if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
174 raise BadZipfile, "File is not a zip file, or ends with a comment"
175 endrec = struct.unpack(structEndArchive, endrec)
176 if self.debug > 1:
177 print endrec
Tim Peterse1190062001-01-15 03:34:38 +0000178 size_cd = endrec[5] # bytes in central directory
179 offset_cd = endrec[6] # offset of central directory
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000180 x = filesize - 22 - size_cd
181 # "concat" is zero, unless zip was concatenated to another file
182 concat = x - offset_cd
183 if self.debug > 2:
184 print "given, inferred, offset", offset_cd, x, concat
185 # self.start_dir: Position of start of central directory
186 self.start_dir = offset_cd + concat
187 fp.seek(self.start_dir, 0)
188 total = 0
189 while total < size_cd:
190 centdir = fp.read(46)
191 total = total + 46
192 if centdir[0:4] != stringCentralDir:
193 raise BadZipfile, "Bad magic number for central directory"
194 centdir = struct.unpack(structCentralDir, centdir)
195 if self.debug > 2:
196 print centdir
Fred Drake3e038e52001-02-28 17:56:26 +0000197 filename = fp.read(centdir[_CD_FILENAME_LENGTH])
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000198 # Create ZipInfo instance to store file information
199 x = ZipInfo(filename)
Fred Drake3e038e52001-02-28 17:56:26 +0000200 x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
201 x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
202 total = (total + centdir[_CD_FILENAME_LENGTH]
203 + centdir[_CD_EXTRA_FIELD_LENGTH]
204 + centdir[_CD_COMMENT_LENGTH])
205 x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] + concat
206 # file_offset must be computed below...
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000207 (x.create_version, x.create_system, x.extract_version, x.reserved,
208 x.flag_bits, x.compress_type, t, d,
209 x.CRC, x.compress_size, x.file_size) = centdir[1:12]
210 x.volume, x.internal_attr, x.external_attr = centdir[15:18]
211 # Convert date/time code to (year, month, day, hour, min, sec)
212 x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
Fred Drake414ca662000-06-13 18:49:53 +0000213 t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000214 self.filelist.append(x)
215 self.NameToInfo[x.filename] = x
216 if self.debug > 2:
217 print "total", total
218 for data in self.filelist:
219 fp.seek(data.header_offset, 0)
220 fheader = fp.read(30)
221 if fheader[0:4] != stringFileHeader:
222 raise BadZipfile, "Bad magic number for file header"
223 fheader = struct.unpack(structFileHeader, fheader)
Fred Drake3e038e52001-02-28 17:56:26 +0000224 # file_offset is computed here, since the extra field for
225 # the central directory and for the local file header
226 # refer to different fields, and they can have different
227 # lengths
228 data.file_offset = (data.header_offset + 30
229 + fheader[_FH_FILENAME_LENGTH]
230 + fheader[_FH_EXTRA_FIELD_LENGTH])
231 fname = fp.read(fheader[_FH_FILENAME_LENGTH])
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000232 if fname != data.filename:
233 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000234 'File name in directory "%s" and header "%s" differ.' % (
235 data.filename, fname)
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000236
237 def namelist(self):
Fred Drake484d7352000-10-02 21:14:52 +0000238 """Return a list of file names in the archive."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000239 l = []
240 for data in self.filelist:
241 l.append(data.filename)
242 return l
243
244 def infolist(self):
Fred Drake484d7352000-10-02 21:14:52 +0000245 """Return a list of class ZipInfo instances for files in the
246 archive."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000247 return self.filelist
248
249 def printdir(self):
Fred Drake484d7352000-10-02 21:14:52 +0000250 """Print a table of contents for the zip file."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000251 print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
252 for zinfo in self.filelist:
253 date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
254 print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
255
256 def testzip(self):
Fred Drake484d7352000-10-02 21:14:52 +0000257 """Read all the files and check the CRC."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000258 for zinfo in self.filelist:
259 try:
Tim Peterse1190062001-01-15 03:34:38 +0000260 self.read(zinfo.filename) # Check CRC-32
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000261 except:
262 return zinfo.filename
263
264 def getinfo(self, name):
Fred Drake484d7352000-10-02 21:14:52 +0000265 """Return the instance of ZipInfo given 'name'."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000266 return self.NameToInfo[name]
267
268 def read(self, name):
Fred Drake484d7352000-10-02 21:14:52 +0000269 """Return file bytes (as a string) for name."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000270 if self.mode not in ("r", "a"):
271 raise RuntimeError, 'read() requires mode "r" or "a"'
272 if not self.fp:
273 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000274 "Attempt to read ZIP archive that was already closed"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000275 zinfo = self.getinfo(name)
276 filepos = self.fp.tell()
277 self.fp.seek(zinfo.file_offset, 0)
278 bytes = self.fp.read(zinfo.compress_size)
279 self.fp.seek(filepos, 0)
280 if zinfo.compress_type == ZIP_STORED:
281 pass
282 elif zinfo.compress_type == ZIP_DEFLATED:
283 if not zlib:
284 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000285 "De-compression requires the (missing) zlib module"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000286 # zlib compress/decompress code by Jeremy Hylton of CNRI
287 dc = zlib.decompressobj(-15)
288 bytes = dc.decompress(bytes)
289 # need to feed in unused pad byte so that zlib won't choke
290 ex = dc.decompress('Z') + dc.flush()
291 if ex:
292 bytes = bytes + ex
293 else:
294 raise BadZipfile, \
Fred Drake5db246d2000-09-29 20:44:48 +0000295 "Unsupported compression method %d for file %s" % \
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000296 (zinfo.compress_type, name)
297 crc = binascii.crc32(bytes)
298 if crc != zinfo.CRC:
299 raise BadZipfile, "Bad CRC-32 for file %s" % name
300 return bytes
301
302 def _writecheck(self, zinfo):
Fred Drake484d7352000-10-02 21:14:52 +0000303 """Check for errors before writing a file to the archive."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000304 if self.NameToInfo.has_key(zinfo.filename):
Tim Peterse1190062001-01-15 03:34:38 +0000305 if self.debug: # Warning for duplicate names
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000306 print "Duplicate name:", zinfo.filename
307 if self.mode not in ("w", "a"):
308 raise RuntimeError, 'write() requires mode "w" or "a"'
309 if not self.fp:
310 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000311 "Attempt to write ZIP archive that was already closed"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000312 if zinfo.compress_type == ZIP_DEFLATED and not zlib:
313 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000314 "Compression requires the (missing) zlib module"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000315 if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
316 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000317 "That compression method is not supported"
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000318
319 def write(self, filename, arcname=None, compress_type=None):
Fred Drake484d7352000-10-02 21:14:52 +0000320 """Put the bytes from filename into the archive under the name
321 arcname."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000322 st = os.stat(filename)
323 mtime = time.localtime(st[8])
324 date_time = mtime[0:6]
325 # Create ZipInfo instance to store file information
326 if arcname is None:
Tim Peterse1190062001-01-15 03:34:38 +0000327 zinfo = ZipInfo(filename, date_time)
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000328 else:
Tim Peterse1190062001-01-15 03:34:38 +0000329 zinfo = ZipInfo(arcname, date_time)
330 zinfo.external_attr = st[0] << 16 # Unix attributes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000331 if compress_type is None:
Tim Peterse1190062001-01-15 03:34:38 +0000332 zinfo.compress_type = self.compression
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000333 else:
Tim Peterse1190062001-01-15 03:34:38 +0000334 zinfo.compress_type = compress_type
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000335 self._writecheck(zinfo)
336 fp = open(filename, "rb")
337 zinfo.flag_bits = 0x08
Tim Peterse1190062001-01-15 03:34:38 +0000338 zinfo.header_offset = self.fp.tell() # Start of header bytes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000339 self.fp.write(zinfo.FileHeader())
Tim Peterse1190062001-01-15 03:34:38 +0000340 zinfo.file_offset = self.fp.tell() # Start of file bytes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000341 CRC = 0
342 compress_size = 0
343 file_size = 0
344 if zinfo.compress_type == ZIP_DEFLATED:
345 cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
346 zlib.DEFLATED, -15)
347 else:
348 cmpr = None
349 while 1:
350 buf = fp.read(1024 * 8)
351 if not buf:
352 break
353 file_size = file_size + len(buf)
354 CRC = binascii.crc32(buf, CRC)
355 if cmpr:
356 buf = cmpr.compress(buf)
357 compress_size = compress_size + len(buf)
358 self.fp.write(buf)
359 fp.close()
360 if cmpr:
361 buf = cmpr.flush()
362 compress_size = compress_size + len(buf)
363 self.fp.write(buf)
364 zinfo.compress_size = compress_size
365 else:
366 zinfo.compress_size = file_size
367 zinfo.CRC = CRC
368 zinfo.file_size = file_size
369 # Write CRC and file sizes after the file data
370 self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
371 zinfo.file_size))
372 self.filelist.append(zinfo)
373 self.NameToInfo[zinfo.filename] = zinfo
374
375 def writestr(self, zinfo, bytes):
Fred Drake484d7352000-10-02 21:14:52 +0000376 """Write a file into the archive. The contents is the string
377 'bytes'."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000378 self._writecheck(zinfo)
Tim Peterse1190062001-01-15 03:34:38 +0000379 zinfo.file_size = len(bytes) # Uncompressed size
380 zinfo.CRC = binascii.crc32(bytes) # CRC-32 checksum
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000381 if zinfo.compress_type == ZIP_DEFLATED:
382 co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
383 zlib.DEFLATED, -15)
384 bytes = co.compress(bytes) + co.flush()
Tim Peterse1190062001-01-15 03:34:38 +0000385 zinfo.compress_size = len(bytes) # Compressed size
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000386 else:
387 zinfo.compress_size = zinfo.file_size
Tim Peterse1190062001-01-15 03:34:38 +0000388 zinfo.header_offset = self.fp.tell() # Start of header bytes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000389 self.fp.write(zinfo.FileHeader())
Tim Peterse1190062001-01-15 03:34:38 +0000390 zinfo.file_offset = self.fp.tell() # Start of file bytes
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000391 self.fp.write(bytes)
392 if zinfo.flag_bits & 0x08:
Tim Peterse1190062001-01-15 03:34:38 +0000393 # Write CRC and file sizes after the file data
394 self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
395 zinfo.file_size))
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000396 self.filelist.append(zinfo)
397 self.NameToInfo[zinfo.filename] = zinfo
398
399 def __del__(self):
Fred Drake484d7352000-10-02 21:14:52 +0000400 """Call the "close()" method in case the user forgot."""
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000401 if self.fp:
402 self.fp.close()
403 self.fp = None
404
405 def close(self):
Fred Drake484d7352000-10-02 21:14:52 +0000406 """Close the file, and for mode "w" and "a" write the ending
407 records."""
Tim Peterse1190062001-01-15 03:34:38 +0000408 if self.mode in ("w", "a"): # write ending records
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000409 count = 0
410 pos1 = self.fp.tell()
Tim Peterse1190062001-01-15 03:34:38 +0000411 for zinfo in self.filelist: # write central directory
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000412 count = count + 1
413 dt = zinfo.date_time
414 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
415 dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
416 centdir = struct.pack(structCentralDir,
417 stringCentralDir, zinfo.create_version,
418 zinfo.create_system, zinfo.extract_version, zinfo.reserved,
419 zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
420 zinfo.CRC, zinfo.compress_size, zinfo.file_size,
421 len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
422 0, zinfo.internal_attr, zinfo.external_attr,
423 zinfo.header_offset)
424 self.fp.write(centdir)
425 self.fp.write(zinfo.filename)
426 self.fp.write(zinfo.extra)
427 self.fp.write(zinfo.comment)
428 pos2 = self.fp.tell()
429 # Write end-of-zip-archive record
430 endrec = struct.pack(structEndArchive, stringEndArchive,
431 0, 0, count, count, pos2 - pos1, pos1, 0)
432 self.fp.write(endrec)
433 self.fp.close()
434 self.fp = None
435
436
437class PyZipFile(ZipFile):
Fred Drake484d7352000-10-02 21:14:52 +0000438 """Class to create ZIP archives with Python library files and packages."""
439
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000440 def writepy(self, pathname, basename = ""):
441 """Add all files from "pathname" to the ZIP archive.
442
Fred Drake484d7352000-10-02 21:14:52 +0000443 If pathname is a package directory, search the directory and
444 all package subdirectories recursively for all *.py and enter
445 the modules into the archive. If pathname is a plain
446 directory, listdir *.py and enter all modules. Else, pathname
447 must be a Python *.py file and the module will be put into the
448 archive. Added modules are always module.pyo or module.pyc.
449 This method will compile the module.py into module.pyc if
450 necessary.
451 """
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000452 dir, name = os.path.split(pathname)
453 if os.path.isdir(pathname):
454 initname = os.path.join(pathname, "__init__.py")
455 if os.path.isfile(initname):
456 # This is a package directory, add it
457 if basename:
458 basename = "%s/%s" % (basename, name)
459 else:
460 basename = name
461 if self.debug:
462 print "Adding package in", pathname, "as", basename
463 fname, arcname = self._get_codename(initname[0:-3], basename)
464 if self.debug:
465 print "Adding", arcname
466 self.write(fname, arcname)
467 dirlist = os.listdir(pathname)
468 dirlist.remove("__init__.py")
469 # Add all *.py files and package subdirectories
470 for filename in dirlist:
471 path = os.path.join(pathname, filename)
472 root, ext = os.path.splitext(filename)
473 if os.path.isdir(path):
474 if os.path.isfile(os.path.join(path, "__init__.py")):
475 # This is a package directory, add it
476 self.writepy(path, basename) # Recursive call
477 elif ext == ".py":
478 fname, arcname = self._get_codename(path[0:-3],
479 basename)
480 if self.debug:
481 print "Adding", arcname
482 self.write(fname, arcname)
483 else:
484 # This is NOT a package directory, add its files at top level
485 if self.debug:
486 print "Adding files from directory", pathname
487 for filename in os.listdir(pathname):
488 path = os.path.join(pathname, filename)
489 root, ext = os.path.splitext(filename)
490 if ext == ".py":
491 fname, arcname = self._get_codename(path[0:-3],
492 basename)
493 if self.debug:
494 print "Adding", arcname
495 self.write(fname, arcname)
496 else:
497 if pathname[-3:] != ".py":
498 raise RuntimeError, \
Fred Drake5db246d2000-09-29 20:44:48 +0000499 'Files added with writepy() must end with ".py"'
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000500 fname, arcname = self._get_codename(pathname[0:-3], basename)
501 if self.debug:
502 print "Adding file", arcname
503 self.write(fname, arcname)
504
505 def _get_codename(self, pathname, basename):
506 """Return (filename, archivename) for the path.
507
Fred Drake484d7352000-10-02 21:14:52 +0000508 Given a module name path, return the correct file path and
509 archive name, compiling if necessary. For example, given
510 /python/lib/string, return (/python/lib/string.pyc, string).
511 """
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000512 file_py = pathname + ".py"
513 file_pyc = pathname + ".pyc"
514 file_pyo = pathname + ".pyo"
515 if os.path.isfile(file_pyo) and \
516 os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
Tim Peterse1190062001-01-15 03:34:38 +0000517 fname = file_pyo # Use .pyo file
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000518 elif not os.path.isfile(file_pyc) or \
Fred Drake484d7352000-10-02 21:14:52 +0000519 os.stat(file_pyc)[8] < os.stat(file_py)[8]:
520 import py_compile
Guido van Rossum32abe6f2000-03-31 17:30:02 +0000521 if self.debug:
522 print "Compiling", file_py
523 py_compile.compile(file_py, file_pyc)
524 fname = file_pyc
525 else:
526 fname = file_pyc
527 archivename = os.path.split(fname)[1]
528 if basename:
529 archivename = "%s/%s" % (basename, archivename)
530 return (fname, archivename)