Commit 82d4bd25 authored by Romain Courteaud's avatar Romain Courteaud

Add ZipFolder product

Simplify import of JS components
parent a24f580e
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
#
# taken from utilities/load_site.py
#
import string
from sgmllib import SGMLParser
def join_attrs(attrs):
attr_list = []
for attrname, value in attrs:
attr_list.append('%s="%s"' % (attrname, string.strip(value)))
if attr_list:
s = " " + string.join(attr_list, " ")
else:
s = ""
return s
class HeadParser(SGMLParser):
def __init__(self):
SGMLParser.__init__(self)
self.seen_starthead = 0
self.seen_endhead = 0
self.seen_startbody = 0
self.head = ""
self.title = ""
self.accumulator = ""
def handle_data(self, data):
if data:
self.accumulator = self.accumulator + data
def handle_comment(self, data):
if data:
self.handle_data("<!--%s-->" % data)
def handle_charref(self, ref):
self.handle_data("&#%s;" % ref)
def handle_entityref(self, ref):
self.handle_data("&%s;" % ref)
def start_head(self, attrs):
if not self.seen_starthead:
self.seen_starthead = 1
self.head = ""
self.title = ""
self.accumulator = ""
def end_head(self):
if not self.seen_endhead:
self.seen_endhead = 1
self.head = self.head + self.accumulator
self.accumulator = ""
def start_title(self, attrs):
self.head = self.head + self.accumulator
self.accumulator = ""
def end_title(self):
self.title = self.accumulator
self.accumulator = ""
def start_body(self, attrs):
if not self.seen_startbody:
self.seen_startbody = 1
self.accumulator = ""
def end_body(self): pass # Do not put </BODY> and </HTML>
def end_html(self): pass # into output stream
# Pass other tags unmodified
def unknown_starttag(self, tag, attrs):
self.accumulator = self.accumulator + "<%s%s>" % (string.upper(tag), join_attrs(attrs))
def unknown_endtag(self, tag):
self.accumulator = self.accumulator + "</%s>" % string.upper(tag)
MANIFEST
README.txt
refresh.txt
version.txt
HeadParser.py
ZipFolder.py
ZipFolder_icon.png
ZipImporter.py
__init__.py
zipfile152.py
zipfolderAdd.dtml
zipfolderUpload.dtml
help/ZipFolder_Add.stx
help/ZipFolder_Upload.stx
locales/de/zipfolderAdd.dtml
locales/de/zipfolderUpload.dtml
ZipFolder is a folder,
plus:
- manage_upload(Form) to upload a zip file
The zip file's content will be used to
create Document/File/Folder/Image objects inside the
folder
- manage_addZipFolder accepts an optional zip file,
to upload a zip file directly
minus:
- doesn't create index_html or acl_users in constructor
Some problems still exist:
- zipfile.py doesn't handle all zipfile formats yet
- zipfile.py of Python 2.0 is broken
- zipfile.py for Python < 2.0 does not exist
If you are using Python 1.52, or experiencing problems
unpacking zip files with Python 2.0, copy zipfile152.py
in this package to zipfile.py.
This is a modified version of the Python zipfile.py (release 1.11
or later):
- it requires zlib to run on python 1.52
- it contains a fix for a file header problem (fixed in Python 2.1)
- doesn't seem to work with zope-win32 (binary distribution)
from OFS import Folder,Image
from Globals import HTMLFile, MessageDialog
from AccessControl import getSecurityManager, Permissions
import os
import re
import string
import tempfile
import types
import StringIO
import zipfile
import ZipImporter
# check zip file names for:
filenamepattern=re.compile(r'[A-Za-z0-9][\w\_\-\.]*$')
MINZIPFILESIZE=22
manage_addZipFolderForm=HTMLFile('zipfolderAdd', globals())
def manage_addZipFolder(self, id, title='',
file=None,
subfolders=0,
REQUEST=None):
"""Add a new ZipFolder object with id *id*, uploading file.
"""
id=str(id)
id, title = Image.cookId(id, '', file)
ob=ZipFolder()
ob.id=id
self._setObject(id, ob)
ob=self._getOb(id)
checkPermission=getSecurityManager().checkPermission
if file is not None:
if not checkPermission('Add Documents, Images, and Files', ob):
raise 'Unauthorized', (
'You are not authorized to add DTML Documents.'
)
return ob.manage_upload(file,subfolders,REQUEST=REQUEST)
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
class ZipFolder(Folder.Folder, ZipImporter.ZipImporter):
"""Ein Ordner mit einer Erweiterung, um seinen Inhalt
und seine Attribute in Form einer ZIP-Datei hochladen
zu konnen."""
meta_type="ZipFolder"
__ac_permissions__=(
(Permissions.view_management_screens,
('manage_uploadForm',)),
('Change ZipFolders',
('manage_upload','manage_uploadForm')))
manage_options=(Folder.Folder.manage_options+
({ "label": "Upload",
"action": "manage_uploadForm"},)
)
manage_uploadForm=HTMLFile('zipfolderUpload', globals())
def manage_upload(self,file='',subfolders=0,replace=0,REQUEST=None, RESPONSE=None):
"""Accept a file and load it up"""
if isinstance(file,types.StringType):
if REQUEST: return MessageDialog(
title ='Success!',
message='But no ZIP file.',
action ='manage_main')
return # anyway
# check file sizes
file.seek(0,2)
fsize=file.tell()
file.seek(0,0)
if fsize == 0:
if RESPONSE:
RESPONSE.redirect('manage_main')
return # anyway
if replace:
if subfolders:
self._remove_objects(metatypes=('DTML Document','Image',
'File','Folder'))
else:
self._remove_objects(metatypes=('DTML Document','Image',
'File'))
self._v_skipped=[]
self._v_subfolders=subfolders
self.import_zipfile(file)
if REQUEST:
if len(self._v_skipped)>0:
return MessageDialog(
title ='Success!',
message=('ZIP file uploaded successfully, '+
'some files/folders ignored:<br>'+
string.join(self._v_skipped,'<br>')),
action ='manage_main')
else:
return MessageDialog(
title ='Success!',
message='ZIP file uploaded successfully',
action ='manage_main')
if len(self._v_skipped) > 0:
return self._v_skipped
return None
def _remove_objects(self,metatypes):
"""Removes old objects from the folder"""
removelist=self.objectIds(metatypes)
self.manage_delObjects(ids=removelist)
def add_file(self,path,basename,sf):
if not filenamepattern.match(basename):
return # skip ugly files
suffix=os.path.splitext(basename)[1]
folder=self
if self._v_subfolders:
folder=self._get_subfolder(path)
if folder:
if string.lower(suffix) in ['.htm', '.html']:
# this is ugly
sfx=StringIO.StringIO(self._edit_html(sf.getvalue()))
sf.close()
folder.manage_addDTMLDocument(id=basename, title='', file=sfx)
elif string.lower(suffix) in ['.gif', '.jpg', '.jpeg', '.png']:
folder.manage_addImage(id=basename, title='', file=sf)
elif len(suffix)==0 and basename[-5:] == "_html":
folder.manage_addDTMLDocument(id=basename, title='', file=sf)
else:
self._add_other_file(folder,path,basename,sf)
def _get_subfolder(self,path):
dirlist = []
rest = ""
while path != rest:
rest=path
(path,dir)=os.path.split(path)
if dir != "":
dirlist.append(dir)
dirlist.reverse()
folder=self
path=""
for dir in dirlist:
if self.check_filename(path,dir):
if dir in folder.objectIds():
if not folder[dir].isPrincipiaFolderish:
raise RuntimeError,("Object '%s' already exists "+
"and is no folder: Cannot "+
"create or use folder") % dir
else:
folder.manage_addFolder(id=dir)
folder=folder[dir]
else:
return None # no folder, no files in there...
if path:
path=path + "/" + dir
else:
path=dir
return folder
def _edit_html(self, data):
# a hook for filters to edit the imported html files
return data
def _add_other_file(self,folder,path,basename,sf):
# Hook for other known file types, default: add file objects
folder.manage_addFile(id=basename, title='', file=sf)
def check_filename(self,pathname,basename):
# return 0 to skip file, raise an exception to annoy...
match=filenamepattern.match(basename) != None;
if not match and basename != '':
self._v_skipped.append(os.path.join(pathname,basename))
return match
import os
import string
import tempfile
import types
import StringIO
import zipfile
MINZIPFILESIZE=22
class ZipImporter:
"""A mix-in class to add zipfile-import-support to folders"""
def import_zipfile(self,file):
# minimum/maximum size check
file.seek(0,2)
fsize=file.tell()
file.seek(0,0)
if fsize > self.max_zipfile_size():
raise RuntimeError,'ZIP file is too big'
if fsize < MINZIPFILESIZE:
raise RuntimeError,'ZIP file is too small'
zf=zipfile.ZipFile(file,'r')
try:
self.check_zip(zf)
for name in zf.namelist():
self._add_file_from_zip(zf,name)
finally:
zf.close()
def _add_file_from_zip(self,zipfile,name):
basename=os.path.basename(name)
pathname=os.path.dirname(name)
if not self.check_filename(pathname,basename):
return # skip ugly files
sf=StringIO.StringIO(zipfile.read(name))
self.add_file(pathname,basename,sf)
sf.close()
#
#
# To be overwritten:
def max_zipfile_size(self):
"""return maximum size for zip files. Default 4MB"""
return 4*1024*1024;
def check_zip(self,zipfile):
# a hook to check zipfile before import loop
# maybe: Look for a special file in the zip...
pass
def check_filename(self,pathname,basename):
# return 0 to skip file, raise an exception to annoy...
return 1;
def add_file(self,pathname,basename,file):
#add file to whatever...
#is called with path,basename and file handle
# overwrite this!
pass
from ZipFolder import *
from OFS import Folder
__doc__="""ZipFolder initialization module"""
__version__= '0.2'
def initialize(context):
"""Initialize the PaketFolder product"""
context.registerClass(
ZipFolder,
constructors = (manage_addZipFolderForm,
manage_addZipFolder
),
icon='ZipFolder_icon.png'
)
context.registerHelp()
ZipFolder - Add: Add a ZipFolder.
Description
This view allows you to create a new ZipFolder.
ZipFolder can be updated at once using zip archive files.
Controls
'Id' -- The id of the Folder.
'Upload' -- Select an optional zip file for upload.
'Create subfolders' -- Create folders to hold objects, if the zip file contains folders
'Add' -- Creates a new Folder.
ZipFolder - Upload: Upload a new zip file.
Description
This view allows you to upload another zip file.
The contents of the zipfile will optionally replace all existing
Documents, images, files and folders in the folder.
Controls
'Upload' -- Select an optional zip file for upload.
'Update' -- Add uploaded zip's contents to folder. To replace, check the "Replace folder contents" option.
'Create subfolders' -- Create folders to hold objects, if the zip file contains folders
'Replace folder contents' -- Remove all old objects before adding any new objects from the uploaded zip file
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='ZipFolder hinzufgen',
help_product='ZipFolder',
help_topic='ZipFolder_Add.stx'
)">
<P class="form-help">
Ein Zip-Ordner enthlt, wie ein normaler Ordner, Objekte.
Ein Zip-Ordner kann jedoch mit Objekten bestckt werden, indem
eine einzige Zip-Datei mit allen Inhalten auf den Server bertragen wird.
</P>
<P>
Mit dem angezeigten Formular knnen Sie einen Zip-Ordner anlegen.
Mit der Upload-Mglichkeit knnen Sie direkt Inhalte in den Ordner
bringen.
</P>
<FORM ACTION="manage_addZipFolder" METHOD="POST" enctype="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<STRONG>Id</STRONG>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="TEXT" NAME="id" SIZE="40">
</TD>
</TR>
<dtml-if "_.SecurityCheckPermission('Add Documents, Images, and Files',this())">
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
Die Datei
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="FILE" NAME="file" SIZE="20">
</TD>
</TR>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="subfolders:int" value="1" checked="1"
id="cbsubFolders">
<label for="cbsubFolders">ggf. Unter-Ordner anlegen</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
</dtml-if>
<TR>
<TD></TD>
<TD>
<BR><INPUT TYPE="SUBMIT" VALUE=" ZIP-Ordner einfgen ">
</TD>
</TR>
</TABLE>
</FORM>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-if manage_tabs><dtml-var "manage_tabs">
<dtml-else>
<dtml-var "manage_form_title(this(), _,
form_title='Neue ZIP-Datei hochladen',
help_product='ZipFolder',
help_topic='ZipFolder_Upload.stx'
)">
</dtml-if>
<P class="form-help">
Mit diesem Formular knnen Sie eine neue ZIP-Datei hochladen. Beachten Sie
bitte, das vor dem Hochladen alle bestehenden Dokumente, Bilder und Dateien aus
dem Ordner gelscht werden.
</P>
<FORM ACTION="manage_upload" METHOD="POST" enctype="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
Die ZIP-Datei
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="FILE" NAME="file" SIZE="20">
</TD>
</TR>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="subfolders:int" value="1" checked="1"
id="cbsubFolders">
<label for="cbsubFolders">ggf. Unter-Ordner anlegen</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="replace:int" value="1"
id="cbreplace">
<label for="cbreplace">Alle alten Inhalte des Ordners lschen</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
<TR>
<TD></TD>
<TD>
<BR><INPUT TYPE="SUBMIT" VALUE=" ZIP-Datei bertragen ">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
ZipFolder is a refreshable product.
"Read and write ZIP files."
# Written by James C. Ahlstrom jim@interet.com
# All rights transferred to CNRI pursuant to the Python contribution agreement
# Modifications by J.Quade: <jq@jquade.de>
# - backport to python 1.52:
# binascii.crc32 might not exist yet, use zlib's version
# based on zipfile.py rev 1.11
## PLEASE DO NOT USE THIS VERSION WITH PYTHON >= 2.0,
## DOWNLOAD THE CURRENT REVISION FROM SOURCEFORGE INSTEAD!
import struct, os, time
import binascii
crc32=None
if hasattr(binascii,'crc32'):
crc32=binascii.crc32
try:
import zlib # We may need its compression method
if hasattr(zlib,'crc32') and crc32 is None:
crc32=zlib.crc32
except:
zlib = None
if crc32 is None:
raise RuntimeError,"No crc32 function found in binascii or zlib"
__all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile",
"ZipInfo", "ZipFile", "PyZipFile"]
class BadZipfile(Exception):
pass
error = BadZipfile # The exception raised by this module
# constants for Zip file compression methods
ZIP_STORED = 0
ZIP_DEFLATED = 8
# Other ZIP compression methods not supported
# Here are some struct module formats for reading headers
structEndArchive = "<4s4H2lH" # 9 items, end of archive, 22 bytes
stringEndArchive = "PK\005\006" # magic number for end of archive record
structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
stringCentralDir = "PK\001\002" # magic number for central directory
structFileHeader = "<4s2B4H3l2H" # 12 items, file header record, 30 bytes
stringFileHeader = "PK\003\004" # magic number for file header
# indexes of entries in the central directory structure
_CD_SIGNATURE = 0
_CD_CREATE_VERSION = 1
_CD_CREATE_SYSTEM = 2
_CD_EXTRACT_VERSION = 3
_CD_EXTRACT_SYSTEM = 4 # is this meaningful?
_CD_FLAG_BITS = 5
_CD_COMPRESS_TYPE = 6
_CD_TIME = 7
_CD_DATE = 8
_CD_CRC = 9
_CD_COMPRESSED_SIZE = 10
_CD_UNCOMPRESSED_SIZE = 11
_CD_FILENAME_LENGTH = 12
_CD_EXTRA_FIELD_LENGTH = 13
_CD_COMMENT_LENGTH = 14
_CD_DISK_NUMBER_START = 15
_CD_INTERNAL_FILE_ATTRIBUTES = 16
_CD_EXTERNAL_FILE_ATTRIBUTES = 17
_CD_LOCAL_HEADER_OFFSET = 18
# indexes of entries in the local file header structure
_FH_SIGNATURE = 0
_FH_EXTRACT_VERSION = 1
_FH_EXTRACT_SYSTEM = 2 # is this meaningful?
_FH_GENERAL_PURPOSE_FLAG_BITS = 3
_FH_COMPRESSION_METHOD = 4
_FH_LAST_MOD_TIME = 5
_FH_LAST_MOD_DATE = 6
_FH_CRC = 7
_FH_COMPRESSED_SIZE = 8
_FH_UNCOMPRESSED_SIZE = 9
_FH_FILENAME_LENGTH = 10
_FH_EXTRA_FIELD_LENGTH = 11
# Used to compare file passed to ZipFile
_STRING_TYPES = (type('s'),)
def is_zipfile(filename):
"""Quickly see if file is a ZIP file by checking the magic number.
Will not accept a ZIP archive with an ending comment.
"""
try:
fpin = open(filename, "rb")
fpin.seek(-22, 2) # Seek to end-of-file record
endrec = fpin.read()
fpin.close()
if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
return 1 # file has correct magic number
except:
pass
class ZipInfo:
"""Class with attributes describing each file in the ZIP archive."""
def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
self.filename = filename # Name of the file in the archive
self.date_time = date_time # year, month, day, hour, min, sec
# Standard values:
self.compress_type = ZIP_STORED # Type of compression for the file
self.comment = "" # Comment for each file
self.extra = "" # ZIP extra data
self.create_system = 0 # System which created ZIP archive
self.create_version = 20 # Version which created ZIP archive
self.extract_version = 20 # Version needed to extract archive
self.reserved = 0 # Must be zero
self.flag_bits = 0 # ZIP flag bits
self.volume = 0 # Volume number of file header
self.internal_attr = 0 # Internal attributes
self.external_attr = 0 # External file attributes
# Other attributes are set by class ZipFile:
# header_offset Byte offset to the file header
# file_offset Byte offset to the start of the file data
# CRC CRC-32 of the uncompressed file
# compress_size Size of the compressed file
# file_size Size of the uncompressed file
def FileHeader(self):
"""Return the per-file header as a string."""
dt = self.date_time
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
if self.flag_bits & 0x08:
# Set these to zero because we write them after the file data
CRC = compress_size = file_size = 0
else:
CRC = self.CRC
compress_size = self.compress_size
file_size = self.file_size
header = struct.pack(structFileHeader, stringFileHeader,
self.extract_version, self.reserved, self.flag_bits,
self.compress_type, dostime, dosdate, CRC,
compress_size, file_size,
len(self.filename), len(self.extra))
return header + self.filename + self.extra
class ZipFile:
""" Class with methods to open, read, write, close, list zip files.
z = ZipFile(file, mode="r", compression=ZIP_STORED)
file: Either the path to the file, or a file-like object.
If it is a path, the file will be opened and closed by ZipFile.
mode: The mode can be either read "r", write "w" or append "a".
compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib).
"""
fp = None # Set here since __del__ checks it
def __init__(self, file, mode="r", compression=ZIP_STORED):
"""Open the ZIP file with mode read "r", write "w" or append "a"."""
if compression == ZIP_STORED:
pass
elif compression == ZIP_DEFLATED:
if not zlib:
raise RuntimeError,\
"Compression requires the (missing) zlib module"
else:
raise RuntimeError, "That compression method is not supported"
self.debug = 3 # Level of printing: 0 through 3
self.NameToInfo = {} # Find file info given name
self.filelist = [] # List of ZipInfo instances for archive
self.compression = compression # Method of compression
self.mode = key = mode[0]
# Check if we were passed a file-like object
if type(file) in _STRING_TYPES:
self._filePassed = 0
self.filename = file
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
self.fp = open(file, modeDict[mode])
else:
self._filePassed = 1
self.fp = file
self.filename = getattr(file, 'name', None)
if key == 'r':
self._GetContents()
elif key == 'w':
pass
elif key == 'a':
fp = self.fp
fp.seek(-22, 2) # Seek to end-of-file record
endrec = fp.read()
if endrec[0:4] == stringEndArchive and \
endrec[-2:] == "\000\000":
self._GetContents() # file is a zip file
# seek to start of directory and overwrite
fp.seek(self.start_dir, 0)
else: # file is not a zip file, just append
fp.seek(0, 2)
else:
if not self._filePassed:
self.fp.close()
self.fp = None
raise RuntimeError, 'Mode must be "r", "w" or "a"'
def _GetContents(self):
"""Read the directory, making sure we close the file if the format
is bad."""
try:
self._RealGetContents()
except BadZipfile:
if not self._filePassed:
self.fp.close()
self.fp = None
raise
def _RealGetContents(self):
"""Read in the table of contents for the ZIP file."""
fp = self.fp
fp.seek(-22, 2) # Start of end-of-archive record
filesize = fp.tell() + 22 # Get file size
endrec = fp.read(22) # Archive must not end with a comment!
if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
raise BadZipfile, "File is not a zip file, or ends with a comment"
endrec = struct.unpack(structEndArchive, endrec)
if self.debug > 1:
print endrec
size_cd = endrec[5] # bytes in central directory
offset_cd = endrec[6] # offset of central directory
x = filesize - 22 - size_cd
# "concat" is zero, unless zip was concatenated to another file
concat = x - offset_cd
if self.debug > 2:
print "given, inferred, offset", offset_cd, x, concat
# self.start_dir: Position of start of central directory
self.start_dir = offset_cd + concat
fp.seek(self.start_dir, 0)
total = 0
while total < size_cd:
centdir = fp.read(46)
total = total + 46
if centdir[0:4] != stringCentralDir:
raise BadZipfile, "Bad magic number for central directory"
centdir = struct.unpack(structCentralDir, centdir)
if self.debug > 2:
print centdir
filename = fp.read(centdir[_CD_FILENAME_LENGTH])
# Create ZipInfo instance to store file information
x = ZipInfo(filename)
x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
total = (total + centdir[_CD_FILENAME_LENGTH]
+ centdir[_CD_EXTRA_FIELD_LENGTH]
+ centdir[_CD_COMMENT_LENGTH])
x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] + concat
# file_offset must be computed below...
(x.create_version, x.create_system, x.extract_version, x.reserved,
x.flag_bits, x.compress_type, t, d,
x.CRC, x.compress_size, x.file_size) = centdir[1:12]
x.volume, x.internal_attr, x.external_attr = centdir[15:18]
# Convert date/time code to (year, month, day, hour, min, sec)
x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
self.filelist.append(x)
self.NameToInfo[x.filename] = x
if self.debug > 2:
print "total", total
for data in self.filelist:
fp.seek(data.header_offset, 0)
fheader = fp.read(30)
if fheader[0:4] != stringFileHeader:
raise BadZipfile, "Bad magic number for file header"
fheader = struct.unpack(structFileHeader, fheader)
# file_offset is computed here, since the extra field for
# the central directory and for the local file header
# refer to different fields, and they can have different
# lengths
data.file_offset = (data.header_offset + 30
+ fheader[_FH_FILENAME_LENGTH]
+ fheader[_FH_EXTRA_FIELD_LENGTH])
fname = fp.read(fheader[_FH_FILENAME_LENGTH])
if fname != data.filename:
raise RuntimeError, \
'File name in directory "%s" and header "%s" differ.' % (
data.filename, fname)
def namelist(self):
"""Return a list of file names in the archive."""
l = []
for data in self.filelist:
l.append(data.filename)
return l
def infolist(self):
"""Return a list of class ZipInfo instances for files in the
archive."""
return self.filelist
def printdir(self):
"""Print a table of contents for the zip file."""
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
for zinfo in self.filelist:
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
def testzip(self):
"""Read all the files and check the CRC."""
for zinfo in self.filelist:
try:
self.read(zinfo.filename) # Check CRC-32
except:
return zinfo.filename
def getinfo(self, name):
"""Return the instance of ZipInfo given 'name'."""
return self.NameToInfo[name]
def read(self, name):
"""Return file bytes (as a string) for name."""
if self.mode not in ("r", "a"):
raise RuntimeError, 'read() requires mode "r" or "a"'
if not self.fp:
raise RuntimeError, \
"Attempt to read ZIP archive that was already closed"
zinfo = self.getinfo(name)
filepos = self.fp.tell()
self.fp.seek(zinfo.file_offset, 0)
bytes = self.fp.read(zinfo.compress_size)
self.fp.seek(filepos, 0)
if zinfo.compress_type == ZIP_STORED:
pass
elif zinfo.compress_type == ZIP_DEFLATED:
if not zlib:
raise RuntimeError, \
"De-compression requires the (missing) zlib module"
# zlib compress/decompress code by Jeremy Hylton of CNRI
dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
# need to feed in unused pad byte so that zlib won't choke
ex = dc.decompress('Z') + dc.flush()
if ex:
bytes = bytes + ex
else:
raise BadZipfile, \
"Unsupported compression method %d for file %s" % \
(zinfo.compress_type, name)
crc = crc32(bytes)
if crc != zinfo.CRC:
raise BadZipfile, "Bad CRC-32 for file %s" % name
return bytes
def _writecheck(self, zinfo):
"""Check for errors before writing a file to the archive."""
if self.NameToInfo.has_key(zinfo.filename):
if self.debug: # Warning for duplicate names
print "Duplicate name:", zinfo.filename
if self.mode not in ("w", "a"):
raise RuntimeError, 'write() requires mode "w" or "a"'
if not self.fp:
raise RuntimeError, \
"Attempt to write ZIP archive that was already closed"
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
raise RuntimeError, \
"Compression requires the (missing) zlib module"
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
raise RuntimeError, \
"That compression method is not supported"
def write(self, filename, arcname=None, compress_type=None):
"""Put the bytes from filename into the archive under the name
arcname."""
st = os.stat(filename)
mtime = time.localtime(st[8])
date_time = mtime[0:6]
# Create ZipInfo instance to store file information
if arcname is None:
zinfo = ZipInfo(filename, date_time)
else:
zinfo = ZipInfo(arcname, date_time)
zinfo.external_attr = st[0] << 16 # Unix attributes
if compress_type is None:
zinfo.compress_type = self.compression
else:
zinfo.compress_type = compress_type
self._writecheck(zinfo)
fp = open(filename, "rb")
zinfo.flag_bits = 0x08
zinfo.header_offset = self.fp.tell() # Start of header bytes
self.fp.write(zinfo.FileHeader())
zinfo.file_offset = self.fp.tell() # Start of file bytes
CRC = 0
compress_size = 0
file_size = 0
if zinfo.compress_type == ZIP_DEFLATED:
cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
zlib.DEFLATED, -15)
else:
cmpr = None
while 1:
buf = fp.read(1024 * 8)
if not buf:
break
file_size = file_size + len(buf)
CRC = crc32(buf, CRC)
if cmpr:
buf = cmpr.compress(buf)
compress_size = compress_size + len(buf)
self.fp.write(buf)
fp.close()
if cmpr:
buf = cmpr.flush()
compress_size = compress_size + len(buf)
self.fp.write(buf)
zinfo.compress_size = compress_size
else:
zinfo.compress_size = file_size
zinfo.CRC = CRC
zinfo.file_size = file_size
# Write CRC and file sizes after the file data
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
zinfo.file_size))
self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo
def writestr(self, zinfo, bytes):
"""Write a file into the archive. The contents is the string
'bytes'."""
self._writecheck(zinfo)
zinfo.file_size = len(bytes) # Uncompressed size
zinfo.CRC = crc32(bytes) # CRC-32 checksum
if zinfo.compress_type == ZIP_DEFLATED:
co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
zlib.DEFLATED, -15)
bytes = co.compress(bytes) + co.flush()
zinfo.compress_size = len(bytes) # Compressed size
else:
zinfo.compress_size = zinfo.file_size
zinfo.header_offset = self.fp.tell() # Start of header bytes
self.fp.write(zinfo.FileHeader())
zinfo.file_offset = self.fp.tell() # Start of file bytes
self.fp.write(bytes)
if zinfo.flag_bits & 0x08:
# Write CRC and file sizes after the file data
self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
zinfo.file_size))
self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo
def __del__(self):
"""Call the "close()" method in case the user forgot."""
if self.fp and not self._filePassed:
self.fp.close()
self.fp = None
def close(self):
"""Close the file, and for mode "w" and "a" write the ending
records."""
if self.mode in ("w", "a"): # write ending records
count = 0
pos1 = self.fp.tell()
for zinfo in self.filelist: # write central directory
count = count + 1
dt = zinfo.date_time
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
centdir = struct.pack(structCentralDir,
stringCentralDir, zinfo.create_version,
zinfo.create_system, zinfo.extract_version, zinfo.reserved,
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
zinfo.CRC, zinfo.compress_size, zinfo.file_size,
len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
0, zinfo.internal_attr, zinfo.external_attr,
zinfo.header_offset)
self.fp.write(centdir)
self.fp.write(zinfo.filename)
self.fp.write(zinfo.extra)
self.fp.write(zinfo.comment)
pos2 = self.fp.tell()
# Write end-of-zip-archive record
endrec = struct.pack(structEndArchive, stringEndArchive,
0, 0, count, count, pos2 - pos1, pos1, 0)
self.fp.write(endrec)
self.fp.flush()
if not self._filePassed:
self.fp.close()
self.fp = None
class PyZipFile(ZipFile):
"""Class to create ZIP archives with Python library files and packages."""
def writepy(self, pathname, basename = ""):
"""Add all files from "pathname" to the ZIP archive.
If pathname is a package directory, search the directory and
all package subdirectories recursively for all *.py and enter
the modules into the archive. If pathname is a plain
directory, listdir *.py and enter all modules. Else, pathname
must be a Python *.py file and the module will be put into the
archive. Added modules are always module.pyo or module.pyc.
This method will compile the module.py into module.pyc if
necessary.
"""
dir, name = os.path.split(pathname)
if os.path.isdir(pathname):
initname = os.path.join(pathname, "__init__.py")
if os.path.isfile(initname):
# This is a package directory, add it
if basename:
basename = "%s/%s" % (basename, name)
else:
basename = name
if self.debug:
print "Adding package in", pathname, "as", basename
fname, arcname = self._get_codename(initname[0:-3], basename)
if self.debug:
print "Adding", arcname
self.write(fname, arcname)
dirlist = os.listdir(pathname)
dirlist.remove("__init__.py")
# Add all *.py files and package subdirectories
for filename in dirlist:
path = os.path.join(pathname, filename)
root, ext = os.path.splitext(filename)
if os.path.isdir(path):
if os.path.isfile(os.path.join(path, "__init__.py")):
# This is a package directory, add it
self.writepy(path, basename) # Recursive call
elif ext == ".py":
fname, arcname = self._get_codename(path[0:-3],
basename)
if self.debug:
print "Adding", arcname
self.write(fname, arcname)
else:
# This is NOT a package directory, add its files at top level
if self.debug:
print "Adding files from directory", pathname
for filename in os.listdir(pathname):
path = os.path.join(pathname, filename)
root, ext = os.path.splitext(filename)
if ext == ".py":
fname, arcname = self._get_codename(path[0:-3],
basename)
if self.debug:
print "Adding", arcname
self.write(fname, arcname)
else:
if pathname[-3:] != ".py":
raise RuntimeError, \
'Files added with writepy() must end with ".py"'
fname, arcname = self._get_codename(pathname[0:-3], basename)
if self.debug:
print "Adding file", arcname
self.write(fname, arcname)
def _get_codename(self, pathname, basename):
"""Return (filename, archivename) for the path.
Given a module name path, return the correct file path and
archive name, compiling if necessary. For example, given
/python/lib/string, return (/python/lib/string.pyc, string).
"""
file_py = pathname + ".py"
file_pyc = pathname + ".pyc"
file_pyo = pathname + ".pyo"
if os.path.isfile(file_pyo) and \
os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
fname = file_pyo # Use .pyo file
elif not os.path.isfile(file_pyc) or \
os.stat(file_pyc)[8] < os.stat(file_py)[8]:
import py_compile
if self.debug:
print "Compiling", file_py
py_compile.compile(file_py, file_pyc)
fname = file_pyc
else:
fname = file_pyc
archivename = os.path.split(fname)[1]
if basename:
archivename = "%s/%s" % (basename, archivename)
return (fname, archivename)
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add ZipFolder',
help_product='ZipFolder',
help_topic='ZipFolder_Add.stx'
)">
<P class="form-help">
A Folder contains other objects. Use Folders to organize your
web objects in to logical groups. ZIPFolders enable you to
upload their content in one single zip file package.
</P>
<P class="form-help">
The form below allows you to create
a ZIPFolder. The Upload option enables you to upload a zip file
containing the folder's content.</P>
<FORM ACTION="manage_addZipFolder" METHOD="POST" enctype="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<STRONG>Id</STRONG>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="TEXT" NAME="id" SIZE="40">
</TD>
</TR>
<dtml-if "_.SecurityCheckPermission('Add Documents, Images, and Files',this())">
<TR>
<TD ALIGN="LEFT" VALIGN="CENTER">
Upload
<TD ALIGN="LEFT" VALIGN="CENTER">
<INPUT TYPE="FILE" NAME="file" SIZE="20">
</TD>
</TR>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="subfolders:int" value="1" checked="1"
id="cbsubFolders">
<label for="cbsubFolders">Create subfolders if necessary</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
<dtml-else>
<TR><i>You are not allowed to add documents, images or files. You may, however,
create an empty ZIPFolder.</i></TR>
</dtml-if>
<TR>
<TD></TD>
<TD>
<BR><INPUT TYPE="SUBMIT" VALUE=" Add ">
</TD>
</TR>
</TABLE>
</FORM>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-if manage_tabs><dtml-var "manage_tabs">
<dtml-else>
<dtml-var "manage_form_title(this(), _,
form_title='Manage workflow',
help_product='ZipFolder',
help_topic='ZipFolder_Upload.stx'
)">
</dtml-if>
<P class="form-help">
The form below allows you to upload a ZIP file containg the folder's
contents. All documents already in the folder will be removed before adding the
contents of the zip file.
</P>
<FORM ACTION="manage_upload" METHOD="POST" enctype="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TD ALIGN="LEFT" VALIGN="CENTER">
<B>Upload</B>
<TD ALIGN="LEFT" VALIGN="CENTER">
<INPUT TYPE="FILE" NAME="file" SIZE="20">
</TD>
</TR>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="subfolders:int" value="1" checked="1"
id="cbsubFolders">
<label for="cbsubFolders">Create subfolders</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-text">
<input type="checkbox" name="replace:int" value="1"
id="cbreplace">
<label for="cbreplace">Replace folder contents (delete everything old)</label>
</div>
</td>
</tr>
<TR><TD COLSPAN="2"><BR></TD></TR>
<TR>
<TD></TD>
<TD>
<BR><INPUT TYPE="SUBMIT" VALUE=" Update ">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment