mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Pulled upstream changes
This commit is contained in:
commit
1247adc0e8
@ -19,3 +19,4 @@ src/calibre/gui2/pictureflow/debug/
|
||||
src/calibre/gui2/pictureflow/pictureflow_resource.rc
|
||||
src/calibre/gui2/pictureflow/release/
|
||||
src/calibre/translations/compiled.py
|
||||
installer/windows/calibre/build.log
|
||||
|
46
installer/windows/build_installer.py
Normal file
46
installer/windows/build_installer.py
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
'''
|
||||
import sys, time, subprocess, os
|
||||
from calibre import __appname__, __version__
|
||||
|
||||
cmdline = [
|
||||
'/usr/local/installjammer/installjammer',
|
||||
'--build-dir', '/tmp/calibre-installjammer',
|
||||
'-DAppName', __appname__,
|
||||
'-DShortAppName', __appname__,
|
||||
'-DApplicationURL', 'http://%s.kovidgoyal.net'%__appname__,
|
||||
'-DCopyright', time.strftime('%Y Kovid Goyal'),
|
||||
'-DPackageDescription', '%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to a few e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading.'%__appname__,
|
||||
'-DPackageSummary', '%s: E-book library management'%__appname__,
|
||||
'-DVersion', __version__,
|
||||
'-DInstallVersion', __version__ + '.0',
|
||||
'-DLicense', open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'LICENSE')).read().replace('\n', '\r\n'),
|
||||
'--output-dir', os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'dist'),
|
||||
'--platform', 'Windows',
|
||||
]
|
||||
|
||||
def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_for_release=True):
|
||||
global cmdline
|
||||
mpi = os.path.abspath(os.path.join(os.path.dirname(__file__), 'calibre', 'calibre.mpi'))
|
||||
cmdline.extend(['-DWindows,Executable', installer_name])
|
||||
compression = 'zlib'
|
||||
if build_for_release:
|
||||
cmdline += ['--build-for-release']
|
||||
compression = 'lzma (solid)'
|
||||
cmdline += ['-DCompressionMethod', compression]
|
||||
cmdline += ['--build', mpi]
|
||||
#print 'Running installjammer with cmdline:'
|
||||
#print cmdline
|
||||
subprocess.check_call(cmdline)
|
||||
|
||||
def main(args=sys.argv):
|
||||
run_install_jammer(build_for_release=False)
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
2265
installer/windows/calibre/calibre.mpi
Normal file
2265
installer/windows/calibre/calibre.mpi
Normal file
File diff suppressed because it is too large
Load Diff
207
installer/windows/freeze.py
Normal file
207
installer/windows/freeze.py
Normal file
@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Freeze app into executable using py2exe.
|
||||
'''
|
||||
QT_DIR = 'C:\\Qt\\4.4.0'
|
||||
DEVCON = 'C:\\devcon\\i386\\devcon.exe'
|
||||
LIBUSB_DIR = 'C:\\libusb'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
CLIT = 'C:\\clit\\clit.exe'
|
||||
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
|
||||
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
||||
FONTCONFIG_DIR = 'C:\\fontconfig'
|
||||
|
||||
|
||||
import sys, os, py2exe, shutil, zipfile, glob, subprocess
|
||||
from distutils.core import setup
|
||||
from distutils.filelist import FileList
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
from setup import VERSION, APPNAME, entry_points, scripts, basenames
|
||||
sys.path.remove(BASE_DIR)
|
||||
|
||||
PY2EXE_DIR = os.path.join(BASE_DIR, 'build','py2exe')
|
||||
|
||||
class BuildEXE(py2exe.build_exe.py2exe):
|
||||
manifest_resource_id = 0
|
||||
|
||||
MANIFEST_TEMPLATE = '''
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="%(version)s"
|
||||
processorArchitecture="x86"
|
||||
name="net.kovidgoyal.%(prog)s"
|
||||
type="win32"
|
||||
/>
|
||||
<description>Ebook management application</description>
|
||||
<!-- Identify the application security requirements. -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel
|
||||
level="asInvoker"
|
||||
uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
'''
|
||||
def build_plugins(self):
|
||||
cwd = os.getcwd()
|
||||
dd = os.path.join(cwd, self.dist_dir)
|
||||
try:
|
||||
os.chdir(os.path.join('src', 'calibre', 'gui2', 'pictureflow'))
|
||||
if os.path.exists('.build'):
|
||||
shutil.rmtree('.build')
|
||||
os.mkdir('.build')
|
||||
os.chdir('.build')
|
||||
subprocess.check_call(['qmake', '../pictureflow.pro'])
|
||||
subprocess.check_call(['mingw32-make', '-f', 'Makefile.Release'])
|
||||
shutil.copyfile('release\\pictureflow0.dll', os.path.join(dd, 'pictureflow0.dll'))
|
||||
os.chdir('..\\PyQt')
|
||||
if not os.path.exists('.build'):
|
||||
os.mkdir('.build')
|
||||
os.chdir('.build')
|
||||
subprocess.check_call(['python', '..\\configure.py'])
|
||||
subprocess.check_call(['mingw32-make', '-f', 'Makefile'])
|
||||
shutil.copyfile('pictureflow.pyd', os.path.join(dd, 'pictureflow.pyd'))
|
||||
os.chdir('..')
|
||||
shutil.rmtree('.build', True)
|
||||
os.chdir('..')
|
||||
shutil.rmtree('.build', True)
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
def run(self):
|
||||
if not os.path.exists(self.dist_dir):
|
||||
os.makedirs(self.dist_dir)
|
||||
print 'Building custom plugins...'
|
||||
self.build_plugins()
|
||||
py2exe.build_exe.py2exe.run(self)
|
||||
qtsvgdll = None
|
||||
for other in self.other_depends:
|
||||
if 'qtsvg4.dll' in other.lower():
|
||||
qtsvgdll = other
|
||||
break
|
||||
shutil.copyfile('LICENSE', os.path.join(self.dist_dir, 'LICENSE'))
|
||||
print
|
||||
if qtsvgdll:
|
||||
print 'Adding', qtsvgdll
|
||||
shutil.copyfile(qtsvgdll, os.path.join(self.dist_dir, os.path.basename(qtsvgdll)))
|
||||
qtxmldll = os.path.join(os.path.dirname(qtsvgdll), 'QtXml4.dll')
|
||||
print 'Adding', qtxmldll
|
||||
shutil.copyfile(qtxmldll,
|
||||
os.path.join(self.dist_dir, os.path.basename(qtxmldll)))
|
||||
print 'Adding plugins...',
|
||||
qt_prefix = QT_DIR
|
||||
if qtsvgdll:
|
||||
qt_prefix = os.path.dirname(os.path.dirname(qtsvgdll))
|
||||
plugdir = os.path.join(qt_prefix, 'plugins')
|
||||
for d in ('imageformats', 'codecs', 'iconengines'):
|
||||
print d,
|
||||
imfd = os.path.join(plugdir, d)
|
||||
tg = os.path.join(self.dist_dir, d)
|
||||
if os.path.exists(tg):
|
||||
shutil.rmtree(tg)
|
||||
shutil.copytree(imfd, tg)
|
||||
|
||||
print
|
||||
print 'Adding main scripts'
|
||||
f = zipfile.ZipFile(os.path.join(PY2EXE_DIR, 'library.zip'), 'a', zipfile.ZIP_DEFLATED)
|
||||
for i in scripts['console'] + scripts['gui']:
|
||||
f.write(i, i.partition('\\')[-1])
|
||||
f.close()
|
||||
|
||||
print
|
||||
print 'Adding third party dependencies'
|
||||
print '\tAdding devcon'
|
||||
tdir = os.path.join(PY2EXE_DIR, 'driver')
|
||||
os.makedirs(tdir)
|
||||
for pat in ('*.dll', '*.sys', '*.cat', '*.inf'):
|
||||
for f in glob.glob(os.path.join(LIBUSB_DIR, pat)):
|
||||
shutil.copyfile(f, os.path.join(tdir, os.path.basename(f)))
|
||||
shutil.copyfile(DEVCON, os.path.join(tdir, os.path.basename(DEVCON)))
|
||||
print '\tAdding unrar'
|
||||
shutil.copyfile(LIBUNRAR, os.path.join(PY2EXE_DIR, os.path.basename(LIBUNRAR)))
|
||||
print '\tAdding ConvertLIT'
|
||||
shutil.copyfile(CLIT, os.path.join(PY2EXE_DIR, os.path.basename(CLIT)))
|
||||
print '\tAdding pdftohtml'
|
||||
shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)))
|
||||
print '\tAdding ImageMagick'
|
||||
shutil.copytree(IMAGEMAGICK_DIR, os.path.join(PY2EXE_DIR, 'ImageMagick'))
|
||||
print '\tCopying fontconfig'
|
||||
for f in glob.glob(os.path.join(FONTCONFIG_DIR, '*')):
|
||||
tgt = os.path.join(PY2EXE_DIR, os.path.basename(f))
|
||||
if os.path.isdir(f):
|
||||
shutil.copytree(f, tgt)
|
||||
else:
|
||||
shutil.copyfile(f, tgt)
|
||||
|
||||
print
|
||||
print 'Doing DLL redirection' # See http://msdn.microsoft.com/en-us/library/ms682600(VS.85).aspx
|
||||
for f in glob.glob(os.path.join(PY2EXE_DIR, '*.exe')):
|
||||
open(f + '.local', 'w').write('\n')
|
||||
|
||||
|
||||
@classmethod
|
||||
def manifest(cls, prog):
|
||||
cls.manifest_resource_id += 1
|
||||
return (24, cls.manifest_resource_id,
|
||||
cls.MANIFEST_TEMPLATE % dict(prog=prog, version=VERSION+'.0'))
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
sys.argv[1:2] = ['py2exe']
|
||||
if os.path.exists(PY2EXE_DIR):
|
||||
shutil.rmtree(PY2EXE_DIR)
|
||||
|
||||
console = [dict(dest_base=basenames['console'][i], script=scripts['console'][i])
|
||||
for i in range(len(scripts['console']))]
|
||||
|
||||
setup(
|
||||
cmdclass = {'py2exe': BuildEXE},
|
||||
windows = [
|
||||
{'script' : scripts['gui'][0],
|
||||
'dest_base' : APPNAME,
|
||||
'icon_resources' : [(1, os.path.join(BASE_DIR, 'icons', 'library.ico'))],
|
||||
'other_resources' : [BuildEXE.manifest(APPNAME)],
|
||||
},
|
||||
{'script' : scripts['gui'][1],
|
||||
'dest_base' : 'lrfviewer',
|
||||
'icon_resources' : [(1, os.path.join(BASE_DIR, 'icons', 'viewer.ico'))],
|
||||
'other_resources' : [BuildEXE.manifest('lrfviewer')],
|
||||
},
|
||||
],
|
||||
console = console,
|
||||
options = { 'py2exe' : {'compressed': 1,
|
||||
'optimize' : 2,
|
||||
'dist_dir' : PY2EXE_DIR,
|
||||
'includes' : [
|
||||
'sip', 'pkg_resources', 'PyQt4.QtSvg',
|
||||
'mechanize', 'ClientForm', 'wmi',
|
||||
'win32file', 'pythoncom', 'rtf2xml',
|
||||
'win32process', 'win32api', 'msvcrt',
|
||||
'win32event', 'calibre.ebooks.lrf.any.*',
|
||||
'calibre.ebooks.lrf.feeds.*',
|
||||
'lxml', 'lxml._elementpath', 'genshi',
|
||||
'path', 'pydoc', 'IPython.Extensions.*',
|
||||
'calibre.web.feeds.recipes.*',
|
||||
'PyQt4.QtWebKit', 'PyQt4.QtNetwork',
|
||||
],
|
||||
'packages' : ['PIL'],
|
||||
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
||||
"_imagingtk", "ImageTk", "FixTk"
|
||||
],
|
||||
'dll_excludes' : ['mswsock.dll'],
|
||||
},
|
||||
},
|
||||
|
||||
)
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -6,12 +6,50 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
Embedded console for debugging.
|
||||
'''
|
||||
|
||||
import sys
|
||||
import sys, os, re
|
||||
from calibre import OptionParser, iswindows
|
||||
from calibre.libunzip import update
|
||||
|
||||
def option_parser():
|
||||
parser = OptionParser(usage='''\
|
||||
%prog [options]
|
||||
|
||||
Run an embedded python interpreter.
|
||||
''')
|
||||
parser.add_option('--update-module', help='Update the specified module in the frozen library. '+
|
||||
'Module specifications are of the form full.name.of.module,path_to_module.py', default=None
|
||||
)
|
||||
parser.add_option('-c', help='Run python code.', default=None, dest='command')
|
||||
return parser
|
||||
|
||||
def update_zipfile(zipfile, mod, path):
|
||||
pat = re.compile(mod.replace('.', '/')+r'\.py[co]*')
|
||||
name = mod.replace('.', '/') + os.path.splitext(path)[-1]
|
||||
update(zipfile, [pat], [path], [name])
|
||||
|
||||
|
||||
def update_module(mod, path):
|
||||
if not hasattr(sys, 'frozen'):
|
||||
raise RuntimeError('Modules can only be updated in frozen installs.')
|
||||
if True or iswindows:
|
||||
zp = os.path.join(os.path.dirname(sys.executable), 'library.zip')
|
||||
update_zipfile(zp, mod, path)
|
||||
else:
|
||||
raise ValueError('Updating modules is not supported on this platform.')
|
||||
|
||||
def main(args=sys.argv):
|
||||
from IPython.Shell import IPShellEmbed
|
||||
ipshell = IPShellEmbed()
|
||||
ipshell()
|
||||
opts, args = option_parser().parse_args(args)
|
||||
if opts.update_module:
|
||||
mod, path = opts.update_module.partition(',')[0], opts.update_module.partition(',')[-1]
|
||||
update_module(mod, os.path.expanduser(path))
|
||||
elif opts.command:
|
||||
exec opts.command
|
||||
else:
|
||||
from IPython.Shell import IPShellEmbed
|
||||
ipshell = IPShellEmbed()
|
||||
ipshell()
|
||||
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -222,6 +222,7 @@ class HTMLConverter(object, LoggingInterface):
|
||||
self.memory = [] #: Used to ensure that duplicate CSS unhandled erros are not reported
|
||||
self.tops = {} #: element representing the top of each HTML file in the LRF file
|
||||
self.previous_text = '' #: Used to figure out when to lstrip
|
||||
self.stripped_space = ''
|
||||
self.preserve_block_style = False #: Used so that <p> tags in <blockquote> elements are handled properly
|
||||
self.avoid_page_break = False
|
||||
self.current_page = book.create_page()
|
||||
@ -864,6 +865,10 @@ class HTMLConverter(object, LoggingInterface):
|
||||
|
||||
if collapse_whitespace:
|
||||
src = re.sub(r'\s{1,}', ' ', src)
|
||||
if self.stripped_space and len(src) == len(src.lstrip(u' \n\r\t')):
|
||||
src = self.stripped_space + src
|
||||
src, orig = src.rstrip(u' \n\r\t'), src
|
||||
self.stripped_space = orig[len(src):]
|
||||
if len(self.previous_text) != len(self.previous_text.rstrip(u' \n\r\t')):
|
||||
src = src.lstrip(u' \n\r\t')
|
||||
if len(src):
|
||||
|
@ -5,7 +5,7 @@ This module provides a thin ctypes based wrapper around libunrar.
|
||||
|
||||
See ftp://ftp.rarlabs.com/rar/unrarsrc-3.7.5.tar.gz
|
||||
"""
|
||||
import os, ctypes
|
||||
import os, ctypes, sys
|
||||
from ctypes import Structure, c_char_p, c_uint, c_void_p, POINTER, \
|
||||
byref, c_wchar_p, c_int, c_char, c_wchar
|
||||
from StringIO import StringIO
|
||||
@ -18,6 +18,8 @@ if iswindows:
|
||||
Structure._pack_ = 1
|
||||
_librar_name = 'unrar'
|
||||
cdll = ctypes.windll
|
||||
if hasattr(sys, 'frozen') and iswindows:
|
||||
_libunrar = cdll.LoadLibrary(os.path.join(os.path.dirname(sys.executable), 'unrar.dll'))
|
||||
_libunrar = load_library(_librar_name, cdll)
|
||||
|
||||
RAR_OM_LIST = 0
|
||||
|
@ -1,8 +1,41 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, zipfile
|
||||
import os
|
||||
from cStringIO import StringIO
|
||||
from calibre.utils import zipfile
|
||||
|
||||
def update(pathtozip, patterns, filepaths, names, compression=zipfile.ZIP_DEFLATED, verbose=True):
|
||||
'''
|
||||
Update files in the zip file at `pathtozip` matching the given
|
||||
`patterns` with the given `filepaths`. If more than
|
||||
one file matches, all of the files are replaced.
|
||||
|
||||
:param patterns: A list of compiled regular expressions
|
||||
:param filepaths: A list of paths to the replacement files. Must have the
|
||||
same length as `patterns`.
|
||||
:param names: A list of archive names for each file in filepaths.
|
||||
A name can be `None` in which case the name of the existing
|
||||
file in the archive is used.
|
||||
:param compression: The compression to use when replacing files. Can be
|
||||
either `zipfile.ZIP_DEFLATED` or `zipfile.ZIP_STORED`.
|
||||
'''
|
||||
assert len(patterns) == len(filepaths) == len(names)
|
||||
z = zipfile.ZipFile(pathtozip, mode='a')
|
||||
for name in z.namelist():
|
||||
for pat, fname, new_name in zip(patterns, filepaths, names):
|
||||
if pat.search(name):
|
||||
if verbose:
|
||||
print 'Updating %s with %s' % (name, fname)
|
||||
if new_name is None:
|
||||
z.replace(fname, arcname=name, compress_type=compression)
|
||||
else:
|
||||
z.delete(name)
|
||||
z.write(fname, new_name, compress_type=compression)
|
||||
break
|
||||
z.close()
|
||||
|
||||
def extract(filename, dir):
|
||||
"""
|
||||
|
@ -167,7 +167,9 @@ def setup_completion(fatal_errors):
|
||||
f = open_file('/etc/bash_completion.d/libprs500')
|
||||
f.close()
|
||||
os.remove(f.name)
|
||||
manifest = []
|
||||
f = open_file('/etc/bash_completion.d/calibre')
|
||||
manifest.append(f.name)
|
||||
|
||||
f.write('# calibre Bash Shell Completion\n')
|
||||
f.write(opts_and_exts('html2lrf', htmlop,
|
||||
@ -275,18 +277,22 @@ complete -o nospace -F _prs500 prs500
|
||||
print 'failed'
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return manifest
|
||||
|
||||
def setup_udev_rules(group_file, reload, fatal_errors):
|
||||
print 'Trying to setup udev rules...'
|
||||
manifest = []
|
||||
sys.stdout.flush()
|
||||
groups = open(group_file, 'rb').read()
|
||||
group = 'plugdev' if 'plugdev' in groups else 'usb'
|
||||
udev = open_file('/etc/udev/rules.d/95-calibre.rules')
|
||||
manifest.append(udev.name)
|
||||
udev.write('''# Sony Reader PRS-500\n'''
|
||||
'''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%s"\n'''%(group,)
|
||||
)
|
||||
udev.close()
|
||||
fdi = open_file('/usr/share/hal/fdi/policy/20thirdparty/10-calibre.fdi')
|
||||
manifest.append(fdi.name)
|
||||
fdi.write('<?xml version="1.0" encoding="UTF-8"?>\n\n<deviceinfo version="0.2">\n')
|
||||
for cls in DEVICES:
|
||||
fdi.write(\
|
||||
@ -326,6 +332,7 @@ def setup_udev_rules(group_file, reload, fatal_errors):
|
||||
if fatal_errors:
|
||||
raise Exception("Couldn't reload udev, you may have to reboot")
|
||||
print >>sys.stderr, "Couldn't reload udev, you may have to reboot"
|
||||
return manifest
|
||||
|
||||
def option_parser():
|
||||
from optparse import OptionParser
|
||||
@ -340,6 +347,8 @@ def option_parser():
|
||||
help='If set, do not check if we are root.')
|
||||
parser.add_option('--make-errors-fatal', action='store_true', default=False,
|
||||
dest='fatal_errors', help='If set die on errors.')
|
||||
parser.add_option('--save-manifest-to', default=None,
|
||||
help='Save a manifest of all installed files to the specified location')
|
||||
return parser
|
||||
|
||||
def install_man_pages(fatal_errors):
|
||||
@ -350,6 +359,7 @@ def install_man_pages(fatal_errors):
|
||||
f = open_file('/tmp/man_extra', 'wb')
|
||||
f.write('[see also]\nhttp://%s.kovidgoyal.net\n'%__appname__)
|
||||
f.close()
|
||||
manifest = []
|
||||
for src in entry_points['console_scripts']:
|
||||
prog = src[:src.index('=')].strip()
|
||||
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
|
||||
@ -360,6 +370,7 @@ def install_man_pages(fatal_errors):
|
||||
'--section', '1', '--no-info', '--include',
|
||||
f.name, '--manual', __appname__)
|
||||
manfile = os.path.join(manpath, prog+'.1'+__appname__+'.bz2')
|
||||
print '\tInstalling MAN page for', prog
|
||||
try:
|
||||
p = subprocess.Popen(help2man, stdout=subprocess.PIPE)
|
||||
except OSError, err:
|
||||
@ -372,10 +383,10 @@ def install_man_pages(fatal_errors):
|
||||
if not raw.strip():
|
||||
print 'Unable to create MAN page for', prog
|
||||
continue
|
||||
open_file(manfile).write(compress(raw))
|
||||
|
||||
|
||||
|
||||
f2 = open_file(manfile)
|
||||
manifest.append(f2.name)
|
||||
f2.write(compress(raw))
|
||||
return manifest
|
||||
|
||||
def post_install():
|
||||
parser = option_parser()
|
||||
@ -387,19 +398,21 @@ def post_install():
|
||||
|
||||
global use_destdir
|
||||
use_destdir = opts.destdir
|
||||
|
||||
setup_udev_rules(opts.group_file, not opts.dont_reload, opts.fatal_errors)
|
||||
setup_completion(opts.fatal_errors)
|
||||
manifest = []
|
||||
manifest += setup_udev_rules(opts.group_file, not opts.dont_reload, opts.fatal_errors)
|
||||
manifest += setup_completion(opts.fatal_errors)
|
||||
setup_desktop_integration(opts.fatal_errors)
|
||||
install_man_pages(opts.fatal_errors)
|
||||
manifest += install_man_pages(opts.fatal_errors)
|
||||
|
||||
try:
|
||||
from PyQt4 import Qt
|
||||
if Qt.PYQT_VERSION < int('0x40301', 16):
|
||||
print 'WARNING: You need PyQt >= 4.3.1 for the GUI. You have', Qt.PYQT_VERSION_STR, '\nYou may experience crashes or other strange behavior.'
|
||||
if Qt.PYQT_VERSION < int('0x40402', 16):
|
||||
print 'WARNING: You need PyQt >= 4.4.2 for the GUI. You have', Qt.PYQT_VERSION_STR, '\nYou may experience crashes or other strange behavior.'
|
||||
except ImportError:
|
||||
print 'WARNING: You do not have PyQt4 installed. The GUI will not work.'
|
||||
|
||||
if opts.save_manifest_to:
|
||||
open(opts.save_manifest_to, 'wb').write('\n'.join(manifest)+'\n')
|
||||
|
||||
|
||||
VIEWER = '''\
|
||||
|
@ -221,6 +221,7 @@ def extract_tarball(tar, destdir):
|
||||
tarfile.open(tar, 'r').extractall(destdir)
|
||||
|
||||
def create_launchers(destdir, bindir='/usr/bin'):
|
||||
manifest = []
|
||||
for launcher in open(os.path.join(destdir, 'manifest')).readlines():
|
||||
if 'postinstall' in launcher:
|
||||
continue
|
||||
@ -229,15 +230,20 @@ def create_launchers(destdir, bindir='/usr/bin'):
|
||||
print 'Creating', lp
|
||||
open(lp, 'wb').write(LAUNCHER%(destdir, launcher))
|
||||
os.chmod(lp, stat.S_IXUSR|stat.S_IXOTH|stat.S_IXGRP|stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH)
|
||||
manifest.append(lp)
|
||||
return manifest
|
||||
|
||||
def do_postinstall(destdir):
|
||||
cwd = os.getcwd()
|
||||
t = tempfile.NamedTemporaryFile()
|
||||
try:
|
||||
os.chdir(destdir)
|
||||
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
||||
subprocess.call((os.path.join(destdir, 'calibre_postinstall'),))
|
||||
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
t.seek(0)
|
||||
return list(t.readlines())
|
||||
|
||||
def download_tarball():
|
||||
try:
|
||||
@ -271,8 +277,37 @@ def main(args=sys.argv):
|
||||
|
||||
print 'Extracting...'
|
||||
extract_tarball(f, destdir)
|
||||
create_launchers(destdir)
|
||||
do_postinstall(destdir)
|
||||
manifest = create_launchers(destdir)
|
||||
manifest += do_postinstall(destdir)
|
||||
|
||||
manifest += ['/usr/bin/calibre-uninstall']
|
||||
|
||||
UNINSTALLER = '''\
|
||||
#!/usr/bin/env python
|
||||
import os, sys
|
||||
if os.geteuid() != 0:
|
||||
print 'You must run this uninstaller as root'
|
||||
sys.exit(0)
|
||||
manifest = %s
|
||||
failures = []
|
||||
for path in manifest:
|
||||
print 'Deleting', path
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
failures.append(path)
|
||||
|
||||
print 'Uninstalling complete.'
|
||||
if failures:
|
||||
print 'Failed to remove the following files:'
|
||||
for f in failures: print f
|
||||
'''%repr(manifest)
|
||||
|
||||
open('/usr/bin/calibre-uninstall', 'wb').write(UNINSTALLER)
|
||||
os.chmod('/usr/bin/calibre-uninstall',
|
||||
stat.S_IXUSR|stat.S_IXOTH|stat.S_IXGRP|stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH)
|
||||
|
||||
print 'You can uninstall calibre by running sudo calibre-uninstall'
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -32,6 +32,7 @@ class cmd_commit(_cmd_commit):
|
||||
return url.replace('//', '//%s:%s@'%(username, password))+'/login/xmlrpc'
|
||||
|
||||
def get_trac_summary(self, bug, url):
|
||||
print 'Getting bug summary for bug #%s'%bug
|
||||
server = xmlrpclib.ServerProxy(url)
|
||||
try:
|
||||
attributes = server.ticket.get(int(bug))[-1]
|
||||
@ -101,6 +102,7 @@ class cmd_commit(_cmd_commit):
|
||||
return ret
|
||||
|
||||
def close_bug(self, bug, action, url, config):
|
||||
print 'Closing bug #%s'% bug
|
||||
nick = config.get_nickname()
|
||||
suffix = config.get_user_option('bug_close_comment')
|
||||
if suffix is None:
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
976
src/calibre/utils/zipfile.py
Normal file
976
src/calibre/utils/zipfile.py
Normal file
@ -0,0 +1,976 @@
|
||||
"""
|
||||
Read and write ZIP files. Modified by Kovid Goyal to allow replacing of files
|
||||
in the ZIP archive.
|
||||
"""
|
||||
import struct, os, time, sys
|
||||
import binascii, cStringIO
|
||||
|
||||
try:
|
||||
import zlib # We may need its compression method
|
||||
except ImportError:
|
||||
zlib = None
|
||||
|
||||
__all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile",
|
||||
"ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile" ]
|
||||
|
||||
class BadZipfile(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LargeZipFile(Exception):
|
||||
"""
|
||||
Raised when writing a zipfile, the zipfile requires ZIP64 extensions
|
||||
and those extensions are disabled.
|
||||
"""
|
||||
|
||||
error = BadZipfile # The exception raised by this module
|
||||
|
||||
ZIP64_LIMIT= (1 << 31) - 1
|
||||
|
||||
# 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 = "<4s4B4HlLL5HLL"# 19 items, central directory, 46 bytes
|
||||
stringCentralDir = "PK\001\002" # magic number for central directory
|
||||
structFileHeader = "<4s2B4HlLL2H" # 12 items, file header record, 30 bytes
|
||||
stringFileHeader = "PK\003\004" # magic number for file header
|
||||
structEndArchive64Locator = "<4slql" # 4 items, locate Zip64 header, 20 bytes
|
||||
stringEndArchive64Locator = "PK\x06\x07" # magic token for locator header
|
||||
structEndArchive64 = "<4sqhhllqqqq" # 10 items, end of archive (Zip64), 56 bytes
|
||||
stringEndArchive64 = "PK\x06\x06" # magic token for Zip64 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
|
||||
|
||||
def is_zipfile(filename):
|
||||
"""Quickly see if file is a ZIP file by checking the magic number."""
|
||||
try:
|
||||
fpin = open(filename, "rb")
|
||||
endrec = _EndRecData(fpin)
|
||||
fpin.close()
|
||||
if endrec:
|
||||
return True # file has correct magic number
|
||||
except IOError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _EndRecData64(fpin, offset, endrec):
|
||||
"""
|
||||
Read the ZIP64 end-of-archive records and use that to update endrec
|
||||
"""
|
||||
locatorSize = struct.calcsize(structEndArchive64Locator)
|
||||
fpin.seek(offset - locatorSize, 2)
|
||||
data = fpin.read(locatorSize)
|
||||
sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data)
|
||||
if sig != stringEndArchive64Locator:
|
||||
return endrec
|
||||
|
||||
if diskno != 0 or disks != 1:
|
||||
raise BadZipfile("zipfiles that span multiple disks are not supported")
|
||||
|
||||
# Assume no 'zip64 extensible data'
|
||||
endArchiveSize = struct.calcsize(structEndArchive64)
|
||||
fpin.seek(offset - locatorSize - endArchiveSize, 2)
|
||||
data = fpin.read(endArchiveSize)
|
||||
sig, sz, create_version, read_version, disk_num, disk_dir, \
|
||||
dircount, dircount2, dirsize, diroffset = \
|
||||
struct.unpack(structEndArchive64, data)
|
||||
if sig != stringEndArchive64:
|
||||
return endrec
|
||||
|
||||
# Update the original endrec using data from the ZIP64 record
|
||||
endrec[1] = disk_num
|
||||
endrec[2] = disk_dir
|
||||
endrec[3] = dircount
|
||||
endrec[4] = dircount2
|
||||
endrec[5] = dirsize
|
||||
endrec[6] = diroffset
|
||||
return endrec
|
||||
|
||||
|
||||
def _EndRecData(fpin):
|
||||
"""Return data from the "End of Central Directory" record, or None.
|
||||
|
||||
The data is a list of the nine items in the ZIP "End of central dir"
|
||||
record followed by a tenth item, the file seek offset of this record."""
|
||||
fpin.seek(-22, 2) # Assume no archive comment.
|
||||
filesize = fpin.tell() + 22 # Get file size
|
||||
data = fpin.read()
|
||||
if data[0:4] == stringEndArchive and data[-2:] == "\000\000":
|
||||
endrec = struct.unpack(structEndArchive, data)
|
||||
endrec = list(endrec)
|
||||
endrec.append("") # Append the archive comment
|
||||
endrec.append(filesize - 22) # Append the record start offset
|
||||
if endrec[-4] == -1 or endrec[-4] == 0xffffffff:
|
||||
return _EndRecData64(fpin, -22, endrec)
|
||||
return endrec
|
||||
# Search the last END_BLOCK bytes of the file for the record signature.
|
||||
# The comment is appended to the ZIP file and has a 16 bit length.
|
||||
# So the comment may be up to 64K long. We limit the search for the
|
||||
# signature to a few Kbytes at the end of the file for efficiency.
|
||||
# also, the signature must not appear in the comment.
|
||||
END_BLOCK = min(filesize, 1024 * 4)
|
||||
fpin.seek(filesize - END_BLOCK, 0)
|
||||
data = fpin.read()
|
||||
start = data.rfind(stringEndArchive)
|
||||
if start >= 0: # Correct signature string was found
|
||||
endrec = struct.unpack(structEndArchive, data[start:start+22])
|
||||
endrec = list(endrec)
|
||||
comment = data[start+22:]
|
||||
if endrec[7] == len(comment): # Comment length checks out
|
||||
# Append the archive comment and start offset
|
||||
endrec.append(comment)
|
||||
endrec.append(filesize - END_BLOCK + start)
|
||||
if endrec[-4] == -1 or endrec[-4] == 0xffffffff:
|
||||
return _EndRecData64(fpin, - END_BLOCK + start, endrec)
|
||||
return endrec
|
||||
return # Error, return None
|
||||
|
||||
|
||||
class ZipInfo (object):
|
||||
"""Class with attributes describing each file in the ZIP archive."""
|
||||
|
||||
__slots__ = (
|
||||
'orig_filename',
|
||||
'filename',
|
||||
'date_time',
|
||||
'compress_type',
|
||||
'comment',
|
||||
'extra',
|
||||
'create_system',
|
||||
'create_version',
|
||||
'extract_version',
|
||||
'reserved',
|
||||
'flag_bits',
|
||||
'volume',
|
||||
'internal_attr',
|
||||
'external_attr',
|
||||
'header_offset',
|
||||
'CRC',
|
||||
'compress_size',
|
||||
'file_size',
|
||||
'file_offset',
|
||||
)
|
||||
|
||||
def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
|
||||
self.orig_filename = filename # Original file name in archive
|
||||
|
||||
# Terminate the file name at the first null byte. Null bytes in file
|
||||
# names are used as tricks by viruses in archives.
|
||||
null_byte = filename.find(chr(0))
|
||||
if null_byte >= 0:
|
||||
filename = filename[0:null_byte]
|
||||
# This is used to ensure paths in generated ZIP files always use
|
||||
# forward slashes as the directory separator, as required by the
|
||||
# ZIP format specification.
|
||||
if os.sep != "/" and os.sep in filename:
|
||||
filename = filename.replace(os.sep, "/")
|
||||
|
||||
self.filename = filename # Normalized file name
|
||||
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
|
||||
if sys.platform == 'win32':
|
||||
self.create_system = 0 # System which created ZIP archive
|
||||
else:
|
||||
# Assume everything else is unix-y
|
||||
self.create_system = 3 # 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
|
||||
self.file_offset = 0
|
||||
# Other attributes are set by class ZipFile:
|
||||
# header_offset Byte offset to the file header
|
||||
# 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
|
||||
|
||||
extra = self.extra
|
||||
|
||||
if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT:
|
||||
# File is larger than what fits into a 4 byte integer,
|
||||
# fall back to the ZIP64 extension
|
||||
fmt = '<hhqq'
|
||||
extra = extra + struct.pack(fmt,
|
||||
1, struct.calcsize(fmt)-4, file_size, compress_size)
|
||||
file_size = 0xffffffff # -1
|
||||
compress_size = 0xffffffff # -1
|
||||
self.extract_version = max(45, self.extract_version)
|
||||
self.create_version = max(45, self.extract_version)
|
||||
|
||||
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(extra))
|
||||
return header + self.filename + extra
|
||||
|
||||
def _decodeExtra(self):
|
||||
# Try to decode the extra field.
|
||||
extra = self.extra
|
||||
unpack = struct.unpack
|
||||
while extra:
|
||||
tp, ln = unpack('<hh', extra[:4])
|
||||
if tp == 1:
|
||||
if ln >= 24:
|
||||
counts = unpack('<qqq', extra[4:28])
|
||||
elif ln == 16:
|
||||
counts = unpack('<qq', extra[4:20])
|
||||
elif ln == 8:
|
||||
counts = unpack('<q', extra[4:12])
|
||||
elif ln == 0:
|
||||
counts = ()
|
||||
else:
|
||||
raise RuntimeError, "Corrupt extra field %s"%(ln,)
|
||||
|
||||
idx = 0
|
||||
|
||||
# ZIP64 extension (large files and/or large archives)
|
||||
if self.file_size == -1 or self.file_size == 0xFFFFFFFFL:
|
||||
self.file_size = counts[idx]
|
||||
idx += 1
|
||||
|
||||
if self.compress_size == -1 or self.compress_size == 0xFFFFFFFFL:
|
||||
self.compress_size = counts[idx]
|
||||
idx += 1
|
||||
|
||||
if self.header_offset == -1 or self.header_offset == 0xffffffffL:
|
||||
self.header_offset = counts[idx]
|
||||
idx+=1
|
||||
|
||||
extra = extra[ln+4:]
|
||||
|
||||
|
||||
class ZipFile:
|
||||
""" Class with methods to open, read, write, close, list and update
|
||||
zip files.
|
||||
|
||||
z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True)
|
||||
|
||||
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).
|
||||
allowZip64: if True ZipFile will create files with ZIP64 extensions when
|
||||
needed, otherwise it will raise an exception when this would
|
||||
be necessary.
|
||||
|
||||
"""
|
||||
|
||||
fp = None # Set here since __del__ checks it
|
||||
|
||||
def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False):
|
||||
"""Open the ZIP file with mode read "r", write "w" or append "a"."""
|
||||
self._allowZip64 = allowZip64
|
||||
self._didModify = False
|
||||
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 = 0 # 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.replace('b', '')[0]
|
||||
|
||||
# Check if we were passed a file-like object
|
||||
if isinstance(file, basestring):
|
||||
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':
|
||||
try: # See if file is a zip file
|
||||
self._RealGetContents()
|
||||
self._calculate_file_offsets()
|
||||
# seek to start of directory and overwrite
|
||||
self.fp.seek(self.start_dir, 0)
|
||||
except BadZipfile: # file is not a zip file, just append
|
||||
self.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
|
||||
endrec = _EndRecData(fp)
|
||||
if not endrec:
|
||||
raise BadZipfile, "File is not a zip file"
|
||||
if self.debug > 1:
|
||||
print endrec
|
||||
size_cd = endrec[5] # bytes in central directory
|
||||
offset_cd = endrec[6] # offset of central directory
|
||||
self.comment = endrec[8] # archive comment
|
||||
# endrec[9] is the offset of the "End of Central Dir" record
|
||||
if endrec[9] > ZIP64_LIMIT:
|
||||
x = endrec[9] - size_cd - 56 - 20
|
||||
else:
|
||||
x = endrec[9] - 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)
|
||||
data = fp.read(size_cd)
|
||||
fp = cStringIO.StringIO(data)
|
||||
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]
|
||||
(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 )
|
||||
|
||||
x._decodeExtra()
|
||||
x.header_offset = x.header_offset + concat
|
||||
self.filelist.append(x)
|
||||
self.NameToInfo[x.filename] = x
|
||||
if self.debug > 2:
|
||||
print "total", total
|
||||
|
||||
|
||||
def _calculate_file_offsets(self):
|
||||
for zip_info in self.filelist:
|
||||
self.fp.seek(zip_info.header_offset, 0)
|
||||
fheader = self.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
|
||||
file_offset = (zip_info.header_offset + 30
|
||||
+ fheader[_FH_FILENAME_LENGTH]
|
||||
+ fheader[_FH_EXTRA_FIELD_LENGTH])
|
||||
fname = self.fp.read(fheader[_FH_FILENAME_LENGTH])
|
||||
if fname != zip_info.orig_filename:
|
||||
raise RuntimeError, \
|
||||
'File name in directory "%s" and header "%s" differ.' % (
|
||||
zip_info.orig_filename, fname)
|
||||
|
||||
zip_info.file_offset = file_offset
|
||||
|
||||
|
||||
def replace(self, filename, arcname=None, compress_type=None):
|
||||
"""Delete arcname, and put the bytes from filename into the
|
||||
archive under the name arcname."""
|
||||
deleteName = arcname
|
||||
if deleteName is None:
|
||||
deleteName = filename
|
||||
self.delete(deleteName)
|
||||
self.write(filename, arcname, compress_type)
|
||||
|
||||
def replacestr(self, zinfo, bytes):
|
||||
"""Delete zinfo.filename, and write a new file into the archive. The
|
||||
contents is the string 'bytes'."""
|
||||
self.delete(zinfo.filename)
|
||||
self.writestr(zinfo, bytes)
|
||||
|
||||
def delete(self, name):
|
||||
"""Delete the file from the archive. If it appears multiple
|
||||
times only the first instance will be deleted."""
|
||||
for i in range (0, len(self.filelist)):
|
||||
if self.filelist[i].filename == name:
|
||||
if self.debug:
|
||||
print "Removing", name
|
||||
deleted_offset = self.filelist[i].header_offset
|
||||
deleted_size = (self.filelist[i].file_offset - self.filelist[i].header_offset) + self.filelist[i].compress_size
|
||||
zinfo_size = struct.calcsize(structCentralDir) + len(self.filelist[i].filename) + len(self.filelist[i].extra)
|
||||
# Remove the file's data from the archive.
|
||||
current_offset = self.fp.tell()
|
||||
self.fp.seek(0, 2)
|
||||
archive_size = self.fp.tell()
|
||||
self.fp.seek(deleted_offset + deleted_size)
|
||||
buf = self.fp.read()
|
||||
self.fp.seek(deleted_offset)
|
||||
self.fp.write(buf)
|
||||
self.fp.truncate(archive_size - deleted_size - zinfo_size)
|
||||
if current_offset > deleted_offset + deleted_size:
|
||||
current_offset -= deleted_size
|
||||
elif current_offset > deleted_offset:
|
||||
current_offset = deleted_offset
|
||||
self.fp.seek(current_offset, 0)
|
||||
# Remove file from central directory.
|
||||
del self.filelist[i]
|
||||
# Adjust the remaining offsets in the central directory.
|
||||
for j in range (i, len(self.filelist)):
|
||||
if self.filelist[j].header_offset > deleted_offset:
|
||||
self.filelist[j].header_offset -= deleted_size
|
||||
if self.filelist[j].file_offset > deleted_offset:
|
||||
self.filelist[j].file_offset -= deleted_size
|
||||
return
|
||||
if self.debug:
|
||||
print name, "not in archive"
|
||||
|
||||
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[:6]
|
||||
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 BadZipfile:
|
||||
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.header_offset, 0)
|
||||
|
||||
# Skip the file header:
|
||||
fheader = self.fp.read(30)
|
||||
if fheader[0:4] != stringFileHeader:
|
||||
raise BadZipfile, "Bad magic number for file header"
|
||||
|
||||
fheader = struct.unpack(structFileHeader, fheader)
|
||||
fname = self.fp.read(fheader[_FH_FILENAME_LENGTH])
|
||||
if fheader[_FH_EXTRA_FIELD_LENGTH]:
|
||||
self.fp.read(fheader[_FH_EXTRA_FIELD_LENGTH])
|
||||
|
||||
if fname != zinfo.orig_filename:
|
||||
raise BadZipfile, \
|
||||
'File name in directory "%s" and header "%s" differ.' % (
|
||||
zinfo.orig_filename, fname)
|
||||
|
||||
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 = binascii.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 zinfo.filename in self.NameToInfo:
|
||||
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"
|
||||
if zinfo.file_size > ZIP64_LIMIT:
|
||||
if not self._allowZip64:
|
||||
raise LargeZipFile("Filesize would require ZIP64 extensions")
|
||||
if zinfo.header_offset > ZIP64_LIMIT:
|
||||
if not self._allowZip64:
|
||||
raise LargeZipFile("Zipfile size would require ZIP64 extensions")
|
||||
|
||||
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.st_mtime)
|
||||
date_time = mtime[0:6]
|
||||
# Create ZipInfo instance to store file information
|
||||
if arcname is None:
|
||||
arcname = filename
|
||||
arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
|
||||
while arcname[0] in (os.sep, os.altsep):
|
||||
arcname = arcname[1:]
|
||||
zinfo = ZipInfo(arcname, date_time)
|
||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes
|
||||
if compress_type is None:
|
||||
zinfo.compress_type = self.compression
|
||||
else:
|
||||
zinfo.compress_type = compress_type
|
||||
|
||||
zinfo.file_size = st.st_size
|
||||
zinfo.flag_bits = 0x00
|
||||
zinfo.header_offset = self.fp.tell() # Start of header bytes
|
||||
|
||||
self._writecheck(zinfo)
|
||||
self._didModify = True
|
||||
fp = open(filename, "rb")
|
||||
# Must overwrite CRC and sizes with correct data later
|
||||
zinfo.CRC = CRC = 0
|
||||
zinfo.compress_size = compress_size = 0
|
||||
zinfo.file_size = file_size = 0
|
||||
self.fp.write(zinfo.FileHeader())
|
||||
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 = binascii.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
|
||||
# Seek backwards and write CRC and file sizes
|
||||
position = self.fp.tell() # Preserve current position in file
|
||||
self.fp.seek(zinfo.header_offset + 14, 0)
|
||||
self.fp.write(struct.pack("<lLL", zinfo.CRC, zinfo.compress_size,
|
||||
zinfo.file_size))
|
||||
self.fp.seek(position, 0)
|
||||
self.filelist.append(zinfo)
|
||||
self.NameToInfo[zinfo.filename] = zinfo
|
||||
|
||||
def writestr(self, zinfo_or_arcname, bytes):
|
||||
"""Write a file into the archive. The contents is the string
|
||||
'bytes'. 'zinfo_or_arcname' is either a ZipInfo instance or
|
||||
the name of the file in the archive."""
|
||||
if not isinstance(zinfo_or_arcname, ZipInfo):
|
||||
zinfo = ZipInfo(filename=zinfo_or_arcname,
|
||||
date_time=time.localtime(time.time())[:6])
|
||||
zinfo.compress_type = self.compression
|
||||
else:
|
||||
zinfo = zinfo_or_arcname
|
||||
zinfo.file_size = len(bytes) # Uncompressed size
|
||||
zinfo.header_offset = self.fp.tell() # Start of header bytes
|
||||
self._writecheck(zinfo)
|
||||
self._didModify = True
|
||||
zinfo.CRC = binascii.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())
|
||||
self.fp.write(bytes)
|
||||
self.fp.flush()
|
||||
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."""
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close the file, and for mode "w" and "a" write the ending
|
||||
records."""
|
||||
if self.fp is None:
|
||||
return
|
||||
|
||||
if self.mode in ("w", "a") and self._didModify: # 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)
|
||||
extra = []
|
||||
if zinfo.file_size > ZIP64_LIMIT \
|
||||
or zinfo.compress_size > ZIP64_LIMIT:
|
||||
extra.append(zinfo.file_size)
|
||||
extra.append(zinfo.compress_size)
|
||||
file_size = 0xffffffff #-1
|
||||
compress_size = 0xffffffff #-1
|
||||
else:
|
||||
file_size = zinfo.file_size
|
||||
compress_size = zinfo.compress_size
|
||||
|
||||
if zinfo.header_offset > ZIP64_LIMIT:
|
||||
extra.append(zinfo.header_offset)
|
||||
header_offset = -1 # struct "l" format: 32 one bits
|
||||
else:
|
||||
header_offset = zinfo.header_offset
|
||||
|
||||
extra_data = zinfo.extra
|
||||
if extra:
|
||||
# Append a ZIP64 field to the extra's
|
||||
extra_data = struct.pack(
|
||||
'<hh' + 'q'*len(extra),
|
||||
1, 8*len(extra), *extra) + extra_data
|
||||
|
||||
extract_version = max(45, zinfo.extract_version)
|
||||
create_version = max(45, zinfo.create_version)
|
||||
else:
|
||||
extract_version = zinfo.extract_version
|
||||
create_version = zinfo.create_version
|
||||
|
||||
centdir = struct.pack(structCentralDir,
|
||||
stringCentralDir, create_version,
|
||||
zinfo.create_system, extract_version, zinfo.reserved,
|
||||
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
|
||||
zinfo.CRC, compress_size, file_size,
|
||||
len(zinfo.filename), len(extra_data), len(zinfo.comment),
|
||||
0, zinfo.internal_attr, zinfo.external_attr,
|
||||
header_offset)
|
||||
self.fp.write(centdir)
|
||||
self.fp.write(zinfo.filename)
|
||||
self.fp.write(extra_data)
|
||||
self.fp.write(zinfo.comment)
|
||||
|
||||
pos2 = self.fp.tell()
|
||||
# Write end-of-zip-archive record
|
||||
if pos1 > ZIP64_LIMIT:
|
||||
# Need to write the ZIP64 end-of-archive records
|
||||
zip64endrec = struct.pack(
|
||||
structEndArchive64, stringEndArchive64,
|
||||
44, 45, 45, 0, 0, count, count, pos2 - pos1, pos1)
|
||||
self.fp.write(zip64endrec)
|
||||
|
||||
zip64locrec = struct.pack(
|
||||
structEndArchive64Locator,
|
||||
stringEndArchive64Locator, 0, pos2, 1)
|
||||
self.fp.write(zip64locrec)
|
||||
|
||||
endrec = struct.pack(structEndArchive, stringEndArchive,
|
||||
0, 0, count, count, pos2 - pos1, -1, 0)
|
||||
self.fp.write(endrec)
|
||||
|
||||
else:
|
||||
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)
|
||||
ext = os.path.splitext(filename)[-1]
|
||||
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)
|
||||
ext = os.path.splitext(filename)[-1]
|
||||
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).st_mtime >= os.stat(file_py).st_mtime:
|
||||
fname = file_pyo # Use .pyo file
|
||||
elif not os.path.isfile(file_pyc) or \
|
||||
os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
|
||||
import py_compile
|
||||
if self.debug:
|
||||
print "Compiling", file_py
|
||||
try:
|
||||
py_compile.compile(file_py, file_pyc, None, True)
|
||||
except py_compile.PyCompileError,err:
|
||||
print err.msg
|
||||
fname = file_pyc
|
||||
else:
|
||||
fname = file_pyc
|
||||
archivename = os.path.split(fname)[1]
|
||||
if basename:
|
||||
archivename = "%s/%s" % (basename, archivename)
|
||||
return (fname, archivename)
|
||||
|
||||
|
||||
def main(args = None):
|
||||
import textwrap
|
||||
USAGE=textwrap.dedent("""\
|
||||
Usage:
|
||||
zipfile.py -l zipfile.zip # Show listing of a zipfile
|
||||
zipfile.py -t zipfile.zip # Test if a zipfile is valid
|
||||
zipfile.py -e zipfile.zip target # Extract zipfile into target dir
|
||||
zipfile.py -c zipfile.zip src ... # Create zipfile from sources
|
||||
""")
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
if not args or args[0] not in ('-l', '-c', '-e', '-t'):
|
||||
print USAGE
|
||||
sys.exit(1)
|
||||
|
||||
if args[0] == '-l':
|
||||
if len(args) != 2:
|
||||
print USAGE
|
||||
sys.exit(1)
|
||||
zf = ZipFile(args[1], 'r')
|
||||
zf.printdir()
|
||||
zf.close()
|
||||
|
||||
elif args[0] == '-t':
|
||||
if len(args) != 2:
|
||||
print USAGE
|
||||
sys.exit(1)
|
||||
zf = ZipFile(args[1], 'r')
|
||||
zf.testzip()
|
||||
print "Done testing"
|
||||
|
||||
elif args[0] == '-e':
|
||||
if len(args) != 3:
|
||||
print USAGE
|
||||
sys.exit(1)
|
||||
|
||||
zf = ZipFile(args[1], 'r')
|
||||
out = args[2]
|
||||
for path in zf.namelist():
|
||||
if path.startswith('./'):
|
||||
tgt = os.path.join(out, path[2:])
|
||||
else:
|
||||
tgt = os.path.join(out, path)
|
||||
|
||||
tgtdir = os.path.dirname(tgt)
|
||||
if not os.path.exists(tgtdir):
|
||||
os.makedirs(tgtdir)
|
||||
fp = open(tgt, 'wb')
|
||||
fp.write(zf.read(path))
|
||||
fp.close()
|
||||
zf.close()
|
||||
|
||||
elif args[0] == '-c':
|
||||
if len(args) < 3:
|
||||
print USAGE
|
||||
sys.exit(1)
|
||||
|
||||
def addToZip(zf, path, zippath):
|
||||
if os.path.isfile(path):
|
||||
zf.write(path, zippath, ZIP_DEFLATED)
|
||||
elif os.path.isdir(path):
|
||||
for nm in os.listdir(path):
|
||||
addToZip(zf,
|
||||
os.path.join(path, nm), os.path.join(zippath, nm))
|
||||
# else: ignore
|
||||
|
||||
zf = ZipFile(args[1], 'w', allowZip64=True)
|
||||
for src in args[2:]:
|
||||
addToZip(zf, src, os.path.basename(src))
|
||||
|
||||
zf.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
17
upload.py
17
upload.py
@ -4,7 +4,7 @@ sys.path.append('src')
|
||||
import subprocess
|
||||
from subprocess import check_call as _check_call
|
||||
from functools import partial
|
||||
#from pyvix.vix import Host, VIX_SERVICEPROVIDER_VMWARE_WORKSTATION
|
||||
|
||||
def get_ip_address(ifname):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
@ -66,12 +66,19 @@ def start_vm(vm, ssh_host, build_script, sleep=75):
|
||||
def build_windows(shutdown=True):
|
||||
installer = installer_name('exe')
|
||||
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
||||
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','windows_installer.py'))
|
||||
subprocess.check_call(('scp', 'windows:build/%s/dist/*.exe'%PROJECT, 'dist'))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','installer\\\\windows\\\\freeze.py'))
|
||||
subprocess.check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%PROJECT, 'build'))
|
||||
if not os.path.exists('build/py2exe'):
|
||||
raise Exception('Failed to run py2exe')
|
||||
if shutdown:
|
||||
subprocess.Popen(('ssh', 'windows', 'shutdown', '-s', '-t', '0'))
|
||||
ibp = os.path.abspath('installer/windows')
|
||||
sys.path.insert(0, ibp)
|
||||
import build_installer
|
||||
sys.path.remove(ibp)
|
||||
build_installer.run_install_jammer(installer_name=os.path.basename(installer))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to run installjammer')
|
||||
return os.path.basename(installer)
|
||||
|
||||
def build_osx(shutdown=True):
|
||||
|
Loading…
x
Reference in New Issue
Block a user