patool/patoolib/__init__.py

707 lines
24 KiB
Python
Raw Normal View History

2010-02-21 11:14:57 +00:00
# -*- coding: utf-8 -*-
2013-02-20 20:04:48 +00:00
# Copyright (C) 2010-2013 Bastian Kleineidam
2010-02-21 11:14:57 +00:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2012-11-19 19:58:42 +00:00
from __future__ import print_function
2012-05-25 20:15:29 +00:00
import sys
2012-11-19 19:58:42 +00:00
if not hasattr(sys, "version_info") or sys.version_info < (2, 7, 0, "final", 0):
raise SystemExit("This program requires Python 2.7 or later.")
2010-02-21 11:14:57 +00:00
import os
import shutil
2010-03-06 14:23:16 +00:00
import stat
2012-11-19 19:58:42 +00:00
import importlib
2012-05-25 20:15:29 +00:00
from . import util
2010-02-21 11:14:57 +00:00
# Supported archive commands
2010-02-21 12:45:41 +00:00
ArchiveCommands = ('list', 'extract', 'test', 'create')
2010-02-21 11:14:57 +00:00
# Supported archive formats
ArchiveFormats = (
'7z', 'ace', 'adf', 'alzip', 'ape', 'ar', 'arc', 'arj',
2012-11-19 21:27:54 +00:00
'bzip2', 'cab', 'chm', 'compress', 'cpio', 'deb', 'dms',
'flac', 'gzip', 'lrzip', 'lzh', 'lzip', 'lzma', 'lzop',
'rar', 'rpm', 'rzip', 'shar', 'shn', 'tar', 'xz',
2012-05-17 12:28:12 +00:00
'zip', 'zoo')
2010-02-21 11:14:57 +00:00
# Supported compressions (used with tar for example)
# Note that all compressions must also be archive formats
ArchiveCompressions = ('bzip2', 'compress', 'gzip', 'lzip', 'lzma', 'xz')
2010-02-21 11:14:57 +00:00
# Map MIME types to archive format
ArchiveMimetypes = {
'application/x-adf': 'adf',
2010-02-21 11:14:57 +00:00
'application/x-bzip2': 'bzip2',
'application/x-tar': 'tar',
'application/x-gzip': 'gzip',
'application/zip': 'zip',
2010-11-20 13:08:01 +00:00
'application/x-zip-compressed': 'zip',
2010-02-21 11:14:57 +00:00
'application/java-archive': 'zip',
'application/x-7z-compressed': '7z',
'application/x-compress': 'compress',
'application/x-rar': 'rar',
'application/rar': 'rar',
'application/x-cab': 'cab',
'application/vnd.ms-cab-compressed': 'cab',
'application/x-arj': 'arj',
2012-11-19 21:27:54 +00:00
'application/x-chm': 'chm',
2010-02-21 11:14:57 +00:00
'application/x-cpio': 'cpio',
'application/x-redhat-package-manager': 'rpm',
'application/x-rpm': 'rpm',
'application/x-debian-package': 'deb',
'application/x-lzop': 'lzop',
2010-02-22 16:34:08 +00:00
'application/x-lzma': 'lzma',
'application/x-xz': 'xz',
2010-03-04 15:42:37 +00:00
'application/x-lzip': 'lzip',
2010-03-06 14:47:00 +00:00
'application/x-ace': 'ace',
2010-03-06 15:52:13 +00:00
'application/x-archive': 'ar',
'application/x-lha': 'lzh',
'application/x-lzh': 'lzh',
'application/x-alzip': 'alzip',
2010-03-08 17:28:11 +00:00
'application/x-arc': 'arc',
2010-03-08 18:58:39 +00:00
'application/x-lrzip': 'lrzip',
2010-03-20 10:34:40 +00:00
'application/x-rzip': 'rzip',
2010-03-21 14:39:02 +00:00
'application/x-zoo': 'zoo',
2011-01-24 20:28:29 +00:00
'application/x-dms': 'dms',
2012-05-25 20:07:22 +00:00
'application/x-shar': 'shar',
'audio/x-ape': 'ape',
2012-05-17 12:28:12 +00:00
'audio/x-shn': 'shn',
2012-05-17 12:59:33 +00:00
'audio/flac': 'flac',
2010-02-21 11:14:57 +00:00
}
2013-02-20 20:16:32 +00:00
try:
# use Python 3 lzma module if available
import lzma
py_lzma = ('py_lzma',)
except ImportError:
py_lzma = ()
# List of programs supporting the given archive format and command.
# If command is None, the program supports all commands (list, extract, ...)
# Programs starting with "py_" are Python modules.
2010-02-21 11:14:57 +00:00
ArchivePrograms = {
2010-03-06 14:47:00 +00:00
'ace': {
'extract': ('unace',),
'test': ('unace',),
'list': ('unace',),
},
'adf': {
'extract': ('unadf',),
'test': ('unadf',),
'list': ('unadf',),
},
'alzip': {
'extract': ('unalz',),
'test': ('unalz',),
'list': ('unalz',),
},
'ape': {
2012-05-17 09:01:06 +00:00
'create': ('mac',),
'extract': ('mac',),
'list': ('py_echo',),
'test': ('mac',),
},
2010-03-06 15:52:13 +00:00
'ar': {
None: ('ar',),
},
2010-03-08 17:28:11 +00:00
'arc': {
None: ('arc',),
'extract': ('nomarch',),
'test': ('nomarch',),
'list': ('nomarch',),
2010-03-08 17:28:11 +00:00
},
2010-02-21 11:14:57 +00:00
'bzip2': {
2012-05-24 21:10:05 +00:00
'extract': ('pbzip2', 'lbzip2', 'bzip2', '7z', '7za', 'py_bz2'),
'test': ('pbzip2', 'lbzip2', 'bzip2', '7z', '7za'),
'create': ('pbzip2', 'lbzip2', 'bzip2', 'py_bz2'),
2012-05-24 21:10:05 +00:00
'list': ('py_echo', '7z', '7za'),
2010-02-21 11:14:57 +00:00
},
2012-05-24 21:17:37 +00:00
'cab': {
2012-08-04 12:09:08 +00:00
'extract': ('cabextract', '7z'),
2012-05-24 21:17:37 +00:00
'create': ('lcab',),
'list': ('cabextract', '7z'),
'test': ('cabextract', '7z'),
},
2012-11-19 21:27:54 +00:00
'chm': {
2012-11-19 21:39:50 +00:00
'extract': ('archmage', 'extract_chmLib'),
2012-11-19 21:27:54 +00:00
'test': ('archmage',),
},
2012-05-17 12:59:33 +00:00
'flac': {
2012-05-17 19:22:56 +00:00
'extract': ('flac',),
'test': ('flac',),
'create': ('flac',),
'list': ('py_echo',),
2012-05-17 12:59:33 +00:00
},
2010-02-21 11:14:57 +00:00
'tar': {
2012-05-23 16:35:54 +00:00
None: ('tar', 'star', 'bsdtar', 'py_tarfile'),
2010-02-21 11:14:57 +00:00
},
'zip': {
None: ('7z', '7za', 'py_zipfile'),
'extract': ('unzip',),
'list': ('unzip',),
2012-11-18 19:43:04 +00:00
'test': ('zip', 'unzip',),
'create': ('zip',),
2010-02-21 11:14:57 +00:00
},
'gzip': {
2012-05-25 20:07:22 +00:00
None: ('7z', '7za', 'pigz', 'gzip'),
'extract': ('py_gzip',),
'create': ('py_gzip',),
2010-02-21 11:14:57 +00:00
},
'lzh': {
None: ('lha',),
'extract': ('lhasa',),
},
2010-03-04 15:42:37 +00:00
'lzip': {
2012-05-11 05:14:49 +00:00
'extract': ('plzip', 'lzip', 'clzip', 'pdlzip'),
'list': ('py_echo',),
2012-05-11 05:14:49 +00:00
'test': ('plzip', 'lzip', 'clzip', 'pdlzip'),
'create': ('plzip', 'lzip', 'clzip', 'pdlzip'),
2010-03-04 15:42:37 +00:00
},
2010-03-08 18:58:39 +00:00
'lrzip': {
'extract': ('lrzip',),
'list': ('py_echo',),
2010-03-08 18:58:39 +00:00
'test': ('lrzip',),
'create': ('lrzip',),
},
2010-02-21 11:14:57 +00:00
'compress': {
'extract': ('gzip', '7z', '7za', 'uncompress.real'),
'list': ('7z', '7za', 'py_echo',),
'test': ('gzip', '7z', '7za'),
2010-02-21 14:48:52 +00:00
'create': ('compress',),
2010-02-21 11:14:57 +00:00
},
'7z': {
None: ('7z', '7za'),
2010-02-21 11:14:57 +00:00
},
'rar': {
None: ('rar',),
'extract': ('unrar', '7z'),
'list': ('unrar', '7z'),
2010-02-21 12:40:42 +00:00
'test': ('unrar', '7z'),
2010-02-21 11:14:57 +00:00
},
'arj': {
2010-02-21 14:48:52 +00:00
None: ('arj',),
'extract': ('7z',),
'list': ('7z',),
'test': ('7z',),
2010-02-21 11:14:57 +00:00
},
'cpio': {
2012-05-23 16:22:16 +00:00
'extract': ('cpio', 'bsdcpio', '7z'),
'list': ('cpio', 'bsdcpio', '7z'),
'test': ('cpio', 'bsdcpio', '7z',),
'create': ('cpio', 'bsdcpio'),
2010-02-21 11:14:57 +00:00
},
'rpm': {
'extract': ('rpm2cpio', '7z'),
'list': ('rpm', '7z', '7za'),
2010-02-21 12:40:42 +00:00
'test': ('rpm', '7z'),
2010-02-21 11:14:57 +00:00
},
'deb': {
'extract': ('dpkg-deb', '7z'),
'list': ('dpkg-deb', '7z'),
2010-02-21 12:40:42 +00:00
'test': ('dpkg-deb', '7z'),
2010-02-21 11:14:57 +00:00
},
'lzop': {
None: ('lzop',),
},
2010-02-22 16:21:55 +00:00
'lzma': {
2013-02-20 20:16:32 +00:00
'extract': ('lzma',) + py_lzma,
'list': ('py_echo',),
2010-02-22 16:21:55 +00:00
'test': ('lzma',),
2013-02-20 20:16:32 +00:00
'create': ('lzma',) + py_lzma,
2010-02-22 16:21:55 +00:00
},
2010-03-20 10:34:40 +00:00
'rzip': {
'extract': ('rzip',),
'list': ('py_echo',),
2010-03-20 10:34:40 +00:00
'create': ('rzip',),
},
2012-05-25 20:07:22 +00:00
'shar': {
'create': ('shar',),
'extract': ('unshar',),
},
2012-05-17 12:28:12 +00:00
'shn': {
'extract': ('shorten',),
'list': ('py_echo',),
'create': ('shorten',),
},
2010-02-22 16:34:08 +00:00
'xz': {
2011-10-19 07:10:17 +00:00
None: ('xz',),
2013-02-22 17:36:45 +00:00
'extract': py_lzma,
'create': py_lzma,
2010-02-22 16:34:08 +00:00
},
2010-03-21 14:39:02 +00:00
'zoo': {
None: ('zoo',),
},
2011-01-24 20:28:29 +00:00
'dms': {
'extract': ('xdms',),
'list': ('xdms',),
'test': ('xdms',),
},
2010-02-21 11:14:57 +00:00
}
# List those programs that have different python module names because of
# Python module naming restrictions.
2010-02-21 11:14:57 +00:00
ProgramModules = {
'7z': 'p7zip',
'7za': 'p7azip',
2010-02-21 11:14:57 +00:00
'uncompress.real': 'uncompress',
'dpkg-deb': 'dpkg',
2012-11-19 21:39:50 +00:00
'extract_chmlib': 'chmlib',
2010-02-21 11:14:57 +00:00
}
def get_archive_format (filename):
"""Detect filename archive format and optional compression."""
mime, compression = util.guess_mime(filename)
if not (mime or compression):
2010-02-21 11:14:57 +00:00
raise util.PatoolError("unknown archive format for file `%s'" % filename)
if mime in ArchiveMimetypes:
format = ArchiveMimetypes[mime]
else:
raise util.PatoolError("unknown archive format for file `%s' (mime-type is `%s')" % (filename, mime))
if format == compression:
# file cannot be in same format compressed
compression = None
return format, compression
2010-02-21 11:14:57 +00:00
def check_archive_format (format, compression):
"""Make sure format and compression is known."""
2010-02-21 11:14:57 +00:00
if format not in ArchiveFormats:
raise util.PatoolError("unknown archive format `%s'" % format)
if compression is not None and compression not in ArchiveCompressions:
raise util.PatoolError("unkonwn archive compression `%s'" % compression)
2010-02-21 11:14:57 +00:00
def check_archive_command (command):
2010-03-01 15:03:31 +00:00
"""Make sure archive command is valid."""
if command not in ArchiveCommands:
raise util.PatoolError("invalid archive command `%s'" % command)
2010-02-21 11:14:57 +00:00
def find_archive_program (format, command):
2010-02-21 11:14:57 +00:00
"""Find suitable archive program for given format and mode."""
commands = ArchivePrograms[format]
2010-02-21 11:14:57 +00:00
programs = []
# first try the universal programs with key None
for key in (None, command):
if key in commands:
programs.extend(commands[key])
2010-02-21 14:48:52 +00:00
if not programs:
raise util.PatoolError("%s archive format `%s' is not supported" % (command, format))
2010-02-21 11:14:57 +00:00
# return the first existing program
for program in programs:
if program.startswith('py_'):
# it's a Python module and therefore always supported
return program
exe = util.find_program(program)
2010-02-21 11:14:57 +00:00
if exe:
if program == '7z' and format == 'rar' and not util.p7zip_supports_rar():
continue
2010-02-21 11:14:57 +00:00
return exe
# no programs found
raise util.PatoolError("could not find an executable program to %s format %s; candidates are (%s)," % (command, format, ",".join(programs)))
2010-02-21 11:14:57 +00:00
def program_supports_compression (program, compression):
"""Decide if the given program supports the compression natively.
@return: True iff the program supports the given compression format
natively, else False.
"""
2012-05-23 16:35:54 +00:00
if program in ('tar', 'star', 'bsdtar', 'py_tarfile'):
return compression in ('gzip', 'bzip2')
return False
2010-02-21 11:14:57 +00:00
def list_formats ():
"""Print information about available archive formats to stdout."""
2010-02-21 11:14:57 +00:00
for format in ArchiveFormats:
2012-11-19 19:58:42 +00:00
print(format, "files:")
for command in ArchiveCommands:
2010-02-22 20:55:16 +00:00
programs = ArchivePrograms[format]
if command not in programs and None not in programs:
2012-11-19 19:58:42 +00:00
print(" %8s: - (not supported)" % command)
2010-02-22 20:55:16 +00:00
continue
2010-02-21 15:04:09 +00:00
try:
program = find_archive_program(format, command)
2012-11-19 19:58:42 +00:00
print(" %8s: %s" % (command, program), end=' ')
2010-02-22 20:55:16 +00:00
if format == 'tar':
encs = [x for x in ArchiveCompressions if util.find_program(x)]
2010-02-22 20:55:16 +00:00
if encs:
2012-11-19 19:58:42 +00:00
print("(supported compressions: %s)" % ", ".join(encs), end=' ')
2010-02-22 20:55:16 +00:00
elif format == '7z':
if util.p7zip_supports_rar():
2012-11-19 19:58:42 +00:00
print("(rar archives supported)", end=' ')
2010-02-22 20:55:16 +00:00
else:
2012-11-19 19:58:42 +00:00
print("(rar archives not supported)", end=' ')
print()
2010-02-21 15:04:09 +00:00
except util.PatoolError:
handlers = programs.get(None, programs.get(command))
2012-11-19 19:58:42 +00:00
print(" %8s: - (no program found; install %s)" %
(command, util.strlist_with_or(handlers)))
2010-02-21 11:14:57 +00:00
return 0
2010-03-06 10:33:44 +00:00
AllowedConfigKeys = ("verbose", "program")
def clean_config_keys (kwargs):
"""Remove invalid configuration keys from arguments."""
config_kwargs = dict(kwargs)
for key in kwargs:
if key not in AllowedConfigKeys:
del config_kwargs[key]
return config_kwargs
def parse_config (archive, format, compression, command, **kwargs):
2010-02-21 11:14:57 +00:00
"""The configuration determines which program to use for which
archive format for the given command.
@raises: PatoolError if command for given format and compression
is not supported.
2010-02-21 11:14:57 +00:00
"""
config = {
'verbose': False,
}
config['program'] = find_archive_program(format, command)
2010-02-21 11:14:57 +00:00
for key, value in kwargs.items():
if value is not None:
if key == 'program':
program = util.find_program(value)
if program:
value = program
2010-02-21 11:14:57 +00:00
config[key] = value
program = os.path.basename(config['program'])
if compression:
# check if compression is supported
if not program_supports_compression(program, compression):
if command == 'create':
comp_command = command
else:
comp_command = 'extract'
comp_prog = find_archive_program(compression, comp_command)
if not comp_prog:
msg = "cannot %s archive `%s': compression `%s' not supported"
raise util.PatoolError(msg % (command, archive, compression))
2010-02-21 11:14:57 +00:00
return config
2010-03-06 10:33:44 +00:00
def move_outdir_orphan (outdir):
2010-02-21 11:14:57 +00:00
"""Move a single file or directory inside outdir a level up.
2010-03-06 10:33:44 +00:00
Never overwrite files.
2010-02-21 11:14:57 +00:00
Return (True, outfile) if successful, (False, reason) if not."""
entries = os.listdir(outdir)
if len(entries) == 1:
src = os.path.join(outdir, entries[0])
dst = os.path.join(os.path.dirname(outdir), entries[0])
if os.path.exists(dst):
2010-03-06 10:33:44 +00:00
return (False, "local file exists")
2010-02-21 11:14:57 +00:00
shutil.move(src, dst)
os.rmdir(outdir)
return (True, entries[0])
return (False, "multiple files in root")
def run_archive_cmdlist (archive_cmdlist):
2010-03-01 15:03:31 +00:00
"""Run archive command."""
# archive_cmdlist is a command list with optional keyword arguments
if isinstance(archive_cmdlist, tuple):
cmdlist, runkwargs = archive_cmdlist
2010-02-21 11:14:57 +00:00
else:
cmdlist, runkwargs = archive_cmdlist, {}
2010-03-11 16:54:09 +00:00
util.run_checked(cmdlist, **runkwargs)
2010-02-21 11:14:57 +00:00
2010-03-06 14:23:16 +00:00
def make_file_readable (filename):
"""Make file user readable if it is not a link."""
if not os.path.islink(filename):
util.set_mode(filename, stat.S_IRUSR)
def make_dir_readable (filename):
"""Make directory user readable and executable."""
util.set_mode(filename, stat.S_IRUSR|stat.S_IXUSR)
def make_user_readable (directory):
"""Make all files in given directory user readable. Also recurse into
subdirectories."""
for root, dirs, files in os.walk(directory, onerror=util.log_error):
for filename in files:
make_file_readable(os.path.join(root, filename))
for dirname in dirs:
make_dir_readable(os.path.join(root, dirname))
def cleanup_outdir (outdir, archive):
"""Cleanup outdir after extraction and return target file name and
result string."""
2010-03-06 14:23:16 +00:00
make_user_readable(outdir)
# move single directory or file in outdir
(success, msg) = move_outdir_orphan(outdir)
if success:
# msg is a single directory or filename
return msg, "`%s'" % msg
# outdir remains unchanged
# rename it to something more user-friendly (basically the archive
# name without extension)
outdir2 = util.get_single_outfile("", archive)
os.rename(outdir, outdir2)
return outdir2, "`%s' (%s)" % (outdir2, msg)
2010-02-21 11:14:57 +00:00
def check_archive_arguments (archive, command, *args):
"""Check for invalid archive command arguments."""
2012-05-11 18:07:50 +00:00
if command == 'create':
util.check_archive_filelist(args)
util.check_new_filename(archive)
elif command == 'repack':
util.check_existing_filename(archive)
if not args:
raise util.PatoolError("missing target archive filename for repack")
util.check_new_filename(args[0])
elif command == 'diff':
util.check_existing_filename(archive)
if not args:
raise util.PatoolError("missing second archive filename for diff")
util.check_existing_filename(args[0])
2013-02-22 17:38:52 +00:00
elif command == 'search':
if not archive:
# archive is the search pattern
raise util.PatoolError("empty search pattern")
for arg in args:
util.check_existing_filename(arg)
2012-05-11 18:07:50 +00:00
else:
util.check_existing_filename(archive)
def _handle_archive (archive, command, *args, **kwargs):
2012-05-24 21:10:05 +00:00
"""Handle archive command; raising PatoolError on errors.
@return: output directory if command is 'extract', else None
"""
format, compression = kwargs.get("format"), kwargs.get("compression")
if format is None:
format, compression = get_archive_format(archive)
check_archive_format(format, compression)
check_archive_command(command)
config_kwargs = clean_config_keys(kwargs)
config = parse_config(archive, format, compression, command, **config_kwargs)
2010-03-06 10:33:44 +00:00
# check if archive already exists
if command == 'create' and os.path.exists(archive):
raise util.PatoolError("archive `%s' already exists" % archive)
program = config['program']
2011-10-19 07:10:17 +00:00
get_archive_cmdlist = get_archive_cmdlist_func(program, command, format)
# prepare keyword arguments for command list
cmd_kwargs = dict(verbose=config['verbose'])
2010-03-08 17:28:11 +00:00
origarchive = None
if command == 'extract':
if "outdir" in kwargs:
cmd_kwargs["outdir"] = kwargs["outdir"]
do_cleanup_outdir = False
else:
cmd_kwargs['outdir'] = util.tmpdir(dir=".")
do_cleanup_outdir = True
2010-03-08 17:28:11 +00:00
elif command == 'create' and os.path.basename(program) == 'arc' and \
".arc" in archive and not archive.endswith(".arc"):
# the arc program mangles the archive name if it contains ".arc"
origarchive = archive
archive = util.tmpfile(dir=os.path.dirname(archive), suffix=".arc")
2010-02-21 11:14:57 +00:00
try:
cmdlist = get_archive_cmdlist(archive, compression, program, *args, **cmd_kwargs)
if cmdlist:
# an empty command list means the get_archive_cmdlist() function
2012-05-24 21:10:05 +00:00
# already handled the command (eg. when it's a builtin Python
# function)
run_archive_cmdlist(cmdlist)
if command == 'extract':
if do_cleanup_outdir:
target, msg = cleanup_outdir(cmd_kwargs["outdir"], archive)
util.log_info("%s extracted to %s" % (archive, msg))
else:
target, msg = cmd_kwargs["outdir"], "`%s'" % cmd_kwargs["outdir"]
return target
2010-03-08 17:28:11 +00:00
elif command == 'create' and origarchive:
shutil.move(archive, origarchive)
2010-02-21 11:14:57 +00:00
finally:
if command == "extract":
2010-02-21 11:14:57 +00:00
try:
os.rmdir(cmd_kwargs["outdir"])
2010-02-21 11:14:57 +00:00
except OSError:
pass
2011-10-19 07:10:17 +00:00
def get_archive_cmdlist_func (program, command, format):
2012-12-17 17:14:58 +00:00
"""Get the Python function that executes the given program."""
2011-10-19 07:10:17 +00:00
# get python module for given archive program
key = util.stripext(os.path.basename(program).lower())
2012-11-19 19:58:42 +00:00
modulename = ".programs." + ProgramModules.get(key, key)
# import the module
2011-10-19 07:10:17 +00:00
try:
2012-11-19 19:58:42 +00:00
module = importlib.import_module(modulename, __name__)
except ImportError as msg:
raise util.PatoolError(msg)
# get archive handler function (eg. patoolib.programs.star.extract_tar)
try:
return getattr(module, '%s_%s' % (command, format))
except AttributeError as msg:
raise util.PatoolError(msg)
2011-10-19 07:10:17 +00:00
def rmtree_log_error (func, path, exc):
2010-04-05 00:56:59 +00:00
"""Error function for shutil.rmtree(). Raises a PatoolError."""
msg = "Error in %s(%s): %s" % (func.__name__, path, str(exc[1]))
util.log_error(msg)
def _diff_archives (archive1, archive2, **kwargs):
2010-03-11 17:33:58 +00:00
"""Show differences between two archives."""
2012-05-11 18:07:50 +00:00
if util.is_same_file(archive1, archive2):
msg = "no differences found: archive `%s' and `%s' are the same files"
2013-02-21 16:52:51 +00:00
util.log_info(msg % (archive1, archive2))
2012-05-11 18:07:50 +00:00
return 0
diff = util.find_program("diff")
if not diff:
2012-05-11 18:07:50 +00:00
msg = "The diff(1) program is required for showing archive differences, please install it."
raise util.PatoolError(msg)
tmpdir1 = util.tmpdir()
try:
path1 = _handle_archive(archive1, 'extract', outdir=tmpdir1, **kwargs)
tmpdir2 = util.tmpdir()
try:
path2 = _handle_archive(archive2, 'extract', outdir=tmpdir2, **kwargs)
return util.run([diff, "-urN", path1, path2])
finally:
shutil.rmtree(tmpdir2, onerror=rmtree_log_error)
finally:
shutil.rmtree(tmpdir1, onerror=rmtree_log_error)
2010-03-11 17:33:58 +00:00
2013-02-22 17:38:52 +00:00
def _search_archives(pattern, *archives, **kwargs):
"""Search for given pattern in all archives."""
grep = util.find_program("grep")
if not grep:
msg = "The grep(1) program is required for searching archive contents, please install it."
raise util.PatoolError(msg)
errors = 0
for archive in archives:
try:
errors += _search_archive(grep, pattern, archive, **kwargs)
except util.PatoolError as msg:
util.log_error("grep error: %s" % msg)
errors += 1
return errors
def _search_archive(grep, pattern, archive, **kwargs):
"""Extract one archive and search for given pattern in extracted files."""
tmpdir = util.tmpdir()
try:
_handle_archive(archive, 'extract', outdir=tmpdir, **kwargs)
return util.run([grep, "-r", "-e", pattern, "."], cwd=tmpdir)
finally:
shutil.rmtree(tmpdir, onerror=rmtree_log_error)
def _repack_archive (archive1, archive2, **kwargs):
2010-03-11 17:33:58 +00:00
"""Repackage an archive to a different format."""
2013-02-21 16:51:34 +00:00
format1, compression1 = get_archive_format(archive1)
format2, compression2 = get_archive_format(archive2)
if format1 == format2 and compression1 == compression2:
# same format and compression allows to copy the file
try:
util.link_or_copy(archive1, archive2, verbose=kwargs.get('verbose'))
return 0
except OSError:
return 1
2010-03-11 17:33:58 +00:00
tmpdir = util.tmpdir()
try:
2013-02-21 16:51:34 +00:00
same_format = (format1 == format2 and compression1 and compression2)
if same_format:
# only decompress since the format is the same
kwargs['format'] = compression1
_handle_archive(archive1, 'extract', outdir=tmpdir, **kwargs)
2010-03-11 17:33:58 +00:00
archive = os.path.abspath(archive2)
files = tuple(os.listdir(tmpdir))
2013-02-21 16:51:34 +00:00
olddir = os.getcwd()
2010-03-11 17:33:58 +00:00
os.chdir(tmpdir)
2013-02-21 16:51:34 +00:00
try:
if same_format:
# only compress since the format is the same
kwargs['format'] = compression2
_handle_archive(archive, 'create', *files, **kwargs)
finally:
os.chdir(olddir)
return 0
2010-03-11 17:33:58 +00:00
finally:
shutil.rmtree(tmpdir, onerror=rmtree_log_error)
def handle_archive (archive, command, *args, **kwargs):
"""Handle archive file command; with nice error reporting."""
check_archive_arguments(archive, command, *args)
try:
if command == "diff":
res = _diff_archives(archive, args[0], **kwargs)
2013-02-22 17:38:52 +00:00
elif command == "search":
res = _search_archives(archive, *args, **kwargs)
elif command == "repack":
res = _repack_archive(archive, args[0], **kwargs)
else:
_handle_archive(archive, command, *args, **kwargs)
res = 0
except KeyboardInterrupt:
util.log_error("aborted")
res = 1
2012-11-19 19:58:42 +00:00
except util.PatoolError as msg:
util.log_error(msg)
res = 1
2012-11-19 19:58:42 +00:00
except Exception as msg:
util.log_internal_error()
res = 1
return res
# convenience functions
def extract (archive, verbose=False, outdir=None):
"""Extract given archive."""
return handle_archive(archive, 'extract', verbose=verbose, outdir=outdir)
def list (archive, verbose=False):
"""List given archive."""
return handle_archive(archive, 'list', verbose=verbose)
def test (archive, verbose=False):
"""Test given archive."""
return handle_archive(archive, 'test', verbose=verbose)
2012-05-11 17:33:42 +00:00
def create (archive, *filenames, **kwargs):
"""Create given archive with given files."""
2012-05-11 17:33:42 +00:00
return handle_archive(archive, 'create', *filenames, **kwargs)
def diff (archive1, archive2, verbose=False):
"""Print differences between two archives."""
return handle_archive(archive1, 'diff', archive2, verbose=verbose)
2013-02-22 17:38:52 +00:00
def search(pattern, *archives, **kwargs):
"""Search pattern in archive members."""
return handle_archive(pattern, 'search', *archives, **kwargs)
def repack (archive1, archive2, verbose=False):
2012-11-19 21:27:54 +00:00
"""Repack archive to different file and/or format."""
return handle_archive(archive1, 'repack', archive2, verbose=verbose)