Back to index

enigmail  1.4.3
MozZipFile.py
Go to the documentation of this file.
00001 # ***** BEGIN LICENSE BLOCK *****
00002 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003 #
00004 # The contents of this file are subject to the Mozilla Public License Version
00005 # 1.1 (the "License"); you may not use this file except in compliance with
00006 # the License. You may obtain a copy of the License at
00007 # http://www.mozilla.org/MPL/
00008 #
00009 # Software distributed under the License is distributed on an "AS IS" basis,
00010 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011 # for the specific language governing rights and limitations under the
00012 # License.
00013 #
00014 # The Original Code is Mozilla build system.
00015 #
00016 # The Initial Developer of the Original Code is
00017 # Mozilla Foundation.
00018 # Portions created by the Initial Developer are Copyright (C) 2007
00019 # the Initial Developer. All Rights Reserved.
00020 #
00021 # Contributor(s):
00022 #  Axel Hecht <axel@pike.org>
00023 #
00024 # Alternatively, the contents of this file may be used under the terms of
00025 # either the GNU General Public License Version 2 or later (the "GPL"), or
00026 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027 # in which case the provisions of the GPL or the LGPL are applicable instead
00028 # of those above. If you wish to allow use of your version of this file only
00029 # under the terms of either the GPL or the LGPL, and not to allow others to
00030 # use your version of this file under the terms of the MPL, indicate your
00031 # decision by deleting the provisions above and replace them with the notice
00032 # and other provisions required by the GPL or the LGPL. If you do not delete
00033 # the provisions above, a recipient may use your version of this file under
00034 # the terms of any one of the MPL, the GPL or the LGPL.
00035 #
00036 # ***** END LICENSE BLOCK *****
00037 
00038 import zipfile
00039 import time
00040 import binascii, struct
00041 import zlib
00042 import os
00043 from utils import lockFile
00044 
00045 class ZipFile(zipfile.ZipFile):
00046   """ Class with methods to open, read, write, close, list zip files.
00047 
00048   Subclassing zipfile.ZipFile to allow for overwriting of existing
00049   entries, though only for writestr, not for write.
00050   """
00051   def __init__(self, file, mode="r", compression=zipfile.ZIP_STORED,
00052                lock = False):
00053     if lock:
00054       assert isinstance(file, basestring)
00055       self.lockfile = lockFile(file + '.lck')
00056     else:
00057       self.lockfile = None
00058 
00059     if mode == 'a' and lock:
00060       # appending to a file which doesn't exist fails, but we can't check
00061       # existence util we hold the lock
00062       if (not os.path.isfile(file)) or os.path.getsize(file) == 0:
00063         mode = 'w'
00064 
00065     zipfile.ZipFile.__init__(self, file, mode, compression)
00066     self._remove = []
00067     self.end = self.fp.tell()
00068     self.debug = 0
00069 
00070   def writestr(self, zinfo_or_arcname, bytes):
00071     """Write contents into the archive.
00072 
00073     The contents is the argument 'bytes',  'zinfo_or_arcname' is either
00074     a ZipInfo instance or the name of the file in the archive.
00075     This method is overloaded to allow overwriting existing entries.
00076     """
00077     if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
00078       zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname,
00079                               date_time=time.localtime(time.time()))
00080       zinfo.compress_type = self.compression
00081       # Add some standard UNIX file access permissions (-rw-r--r--).
00082       zinfo.external_attr = (0x81a4 & 0xFFFF) << 16L
00083     else:
00084       zinfo = zinfo_or_arcname
00085 
00086     # Now to the point why we overwrote this in the first place,
00087     # remember the entry numbers if we already had this entry.
00088     # Optimizations:
00089     # If the entry to overwrite is the last one, just reuse that.
00090     # If we store uncompressed and the new content has the same size
00091     # as the old, reuse the existing entry.
00092 
00093     doSeek = False # store if we need to seek to the eof after overwriting
00094     if self.NameToInfo.has_key(zinfo.filename):
00095       # Find the last ZipInfo with our name.
00096       # Last, because that's catching multiple overwrites
00097       i = len(self.filelist)
00098       while i > 0:
00099         i -= 1
00100         if self.filelist[i].filename == zinfo.filename:
00101           break
00102       zi = self.filelist[i]
00103       if ((zinfo.compress_type == zipfile.ZIP_STORED
00104            and zi.compress_size == len(bytes))
00105           or (i + 1) == len(self.filelist)):
00106         # make sure we're allowed to write, otherwise done by writestr below
00107         self._writecheck(zi)
00108         # overwrite existing entry
00109         self.fp.seek(zi.header_offset)
00110         if (i + 1) == len(self.filelist):
00111           # this is the last item in the file, just truncate
00112           self.fp.truncate()
00113         else:
00114           # we need to move to the end of the file afterwards again
00115           doSeek = True
00116         # unhook the current zipinfo, the writestr of our superclass
00117         # will add a new one
00118         self.filelist.pop(i)
00119         self.NameToInfo.pop(zinfo.filename)
00120       else:
00121         # Couldn't optimize, sadly, just remember the old entry for removal
00122         self._remove.append(self.filelist.pop(i))
00123     zipfile.ZipFile.writestr(self, zinfo, bytes)
00124     self.filelist.sort(lambda l, r: cmp(l.header_offset, r.header_offset))
00125     if doSeek:
00126       self.fp.seek(self.end)
00127     self.end = self.fp.tell()
00128 
00129   def close(self):
00130     """Close the file, and for mode "w" and "a" write the ending
00131     records.
00132 
00133     Overwritten to compact overwritten entries.
00134     """
00135     if not self._remove:
00136       # we don't have anything special to do, let's just call base
00137       r = zipfile.ZipFile.close(self)
00138       self.lockfile = None
00139       return r
00140 
00141     if self.fp.mode != 'r+b':
00142       # adjust file mode if we originally just wrote, now we rewrite
00143       self.fp.close()
00144       self.fp = open(self.filename, 'r+b')
00145     all = map(lambda zi: (zi, True), self.filelist) + \
00146         map(lambda zi: (zi, False), self._remove)
00147     all.sort(lambda l, r: cmp(l[0].header_offset, r[0].header_offset))
00148     # empty _remove for multiple closes
00149     self._remove = []
00150 
00151     lengths = [all[i+1][0].header_offset - all[i][0].header_offset
00152                for i in xrange(len(all)-1)]
00153     lengths.append(self.end - all[-1][0].header_offset)
00154     to_pos = 0
00155     for (zi, keep), length in zip(all, lengths):
00156       if not keep:
00157         continue
00158       oldoff = zi.header_offset
00159       # python <= 2.4 has file_offset
00160       if hasattr(zi, 'file_offset'):
00161         zi.file_offset = zi.file_offset + to_pos - oldoff
00162       zi.header_offset = to_pos
00163       self.fp.seek(oldoff)
00164       content = self.fp.read(length)
00165       self.fp.seek(to_pos)
00166       self.fp.write(content)
00167       to_pos += length
00168     self.fp.truncate()
00169     zipfile.ZipFile.close(self)
00170     self.lockfile = None