%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/py7zr/ |
| Current File : //lib/python3/dist-packages/py7zr/cli.py |
#!/usr/bin/env python
#
# Pure python p7zr implementation
# Copyright (C) 2019, 2020 Hiroshi Miura
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import argparse
import getpass
import lzma
import os
import pathlib
import platform
import re
import shutil
import sys
from lzma import CHECK_CRC64, CHECK_SHA256, is_check_supported
from typing import Any, List, Optional
import _lzma # type: ignore
import texttable # type: ignore
import py7zr
from py7zr.callbacks import ExtractCallback
from py7zr.compressor import SupportedMethods
from py7zr.helpers import Local
from py7zr.properties import COMMAND_HELP_STRING, READ_BLOCKSIZE
try:
from importlib import metadata as importlib_metadata # type: ignore
except ImportError:
import importlib_metadata # type: ignore # for python < 3.8
class CliExtractCallback(ExtractCallback):
def __init__(self, total_bytes, ofd=sys.stdout):
self.ofd = ofd
self.archive_total = total_bytes
self.total_bytes = 0
self.columns, _ = shutil.get_terminal_size(fallback=(80, 24))
self.pwidth = 0
def report_start_preparation(self):
pass
def report_start(self, processing_file_path, processing_bytes):
self.ofd.write('- {}'.format(processing_file_path))
self.pwidth += len(processing_file_path) + 2
def report_end(self, processing_file_path, wrote_bytes):
self.total_bytes += int(wrote_bytes)
plest = self.columns - self.pwidth
progress = self.total_bytes / self.archive_total
msg = '({:.0%})\n'.format(progress)
if plest - len(msg) > 0:
self.ofd.write(msg.rjust(plest))
else:
self.ofd.write(msg)
self.pwidth = 0
def report_postprocess(self):
pass
def report_warning(self, message):
pass
class Cli():
dunits = {'b': 1, 'B': 1, 'k': 1024, 'K': 1024, 'm': 1024 * 1024, 'M': 1024 * 1024,
'g': 1024 * 1024 * 1024, 'G': 1024 * 1024 * 1024}
def __init__(self):
self.parser = self._create_parser()
self.unit_pattern = re.compile(r'^([0-9]+)([bkmg]?)$', re.IGNORECASE)
def run(self, arg: Optional[Any] = None) -> int:
args = self.parser.parse_args(arg)
if args.version:
return self.show_version()
return args.func(args)
def _create_parser(self):
parser = argparse.ArgumentParser(prog='py7zr', description='py7zr',
formatter_class=argparse.RawTextHelpFormatter, add_help=True)
subparsers = parser.add_subparsers(title='subcommands', help=COMMAND_HELP_STRING)
list_parser = subparsers.add_parser('l')
list_parser.set_defaults(func=self.run_list)
list_parser.add_argument("arcfile", help="7z archive file")
list_parser.add_argument("--verbose", action="store_true", help="verbose output")
extract_parser = subparsers.add_parser('x')
extract_parser.set_defaults(func=self.run_extract)
extract_parser.add_argument("arcfile", help="7z archive file")
extract_parser.add_argument("odir", nargs="?", help="output directory")
extract_parser.add_argument("-P", "--password", action="store_true",
help="Password protected archive(you will be asked a password).")
extract_parser.add_argument("--verbose", action="store_true", help="verbose output")
create_parser = subparsers.add_parser('c')
create_parser.set_defaults(func=self.run_create)
create_parser.add_argument("arcfile", help="7z archive file")
create_parser.add_argument("filenames", nargs="+", help="filenames to archive")
create_parser.add_argument("-v", "--volume", nargs=1, help="Create volumes.")
append_parser = subparsers.add_parser('a')
append_parser.set_defaults(func=self.run_append)
append_parser.add_argument("arcfile", help="7z archive file")
append_parser.add_argument("filenames", nargs="+", help="filenames to archive")
test_parser = subparsers.add_parser('t')
test_parser.set_defaults(func=self.run_test)
test_parser.add_argument("arcfile", help="7z archive file")
info_parser = subparsers.add_parser("i")
info_parser.set_defaults(func=self.run_info)
parser.add_argument("--version", action="store_true", help="Show version")
parser.set_defaults(func=self.show_help)
return parser
def show_version(self):
print(self._get_version())
@staticmethod
def _get_version():
dist = importlib_metadata.distribution('py7zr')
module_name = dist.entry_points[0].name
py_version = platform.python_version()
py_impl = platform.python_implementation()
py_build = platform.python_compiler()
return "{} Version {} : {} (Python {} [{} {}])".format(module_name, py7zr.__version__, py7zr.__copyright__,
py_version, py_impl, py_build)
def show_help(self, args):
self.show_version()
self.parser.print_help()
return 0
def run_info(self, args):
self.show_version()
print("\nFormats:")
table = texttable.Texttable()
table.set_deco(texttable.Texttable.HEADER)
table.set_cols_dtype(['t', 't'])
table.set_cols_align(["l", "r"])
for f in SupportedMethods.formats:
m = ''.join(' {:02x}'.format(x) for x in f['magic'])
table.add_row([f['name'], m])
print(table.draw())
print("\nCodecs and hashes:")
table = texttable.Texttable()
table.set_deco(texttable.Texttable.HEADER)
table.set_cols_dtype(['t', 't'])
table.set_cols_align(["l", "r"])
for c in SupportedMethods.methods:
m = ''.join('{:02x}'.format(x) for x in c['id'])
table.add_row([m, c['name']])
table.add_row(['0', 'CRC32'])
if is_check_supported(CHECK_SHA256):
table.add_row(['0', 'SHA256'])
if is_check_supported(CHECK_CRC64):
table.add_row(['0', 'CRC64'])
print(table.draw())
def run_list(self, args):
"""Print a table of contents to file. """
target = args.arcfile
verbose = args.verbose
if not py7zr.is_7zfile(target):
print('not a 7z file')
return 1
with open(target, 'rb') as f:
a = py7zr.SevenZipFile(f)
file = sys.stdout
archive_info = a.archiveinfo()
archive_list = a.list()
if verbose:
file.write("Listing archive: {}\n".format(target))
file.write("--\n")
file.write("Path = {}\n".format(archive_info.filename))
file.write("Type = 7z\n")
fstat = os.stat(archive_info.filename)
file.write("Phisical Size = {}\n".format(fstat.st_size))
file.write("Headers Size = {}\n".format(archive_info.header_size))
file.write("Method = {}\n".format(archive_info.method_names))
if archive_info.solid:
file.write("Solid = {}\n".format('+'))
else:
file.write("Solid = {}\n".format('-'))
file.write("Blocks = {}\n".format(archive_info.blocks))
file.write('\n')
file.write(
'total %d files and directories in %sarchive\n' % (len(archive_list),
(archive_info.solid and 'solid ') or ''))
file.write(' Date Time Attr Size Compressed Name\n')
file.write('------------------- ----- ------------ ------------ ------------------------\n')
for f in archive_list:
if f.creationtime is not None:
lastwritedate = f.creationtime.astimezone(Local).strftime("%Y-%m-%d")
lastwritetime = f.creationtime.astimezone(Local).strftime("%H:%M:%S")
else:
lastwritedate = ' '
lastwritetime = ' '
if f.is_directory:
attrib = 'D...'
else:
attrib = '....'
if f.archivable:
attrib += 'A'
else:
attrib += '.'
if f.is_directory:
extra = ' 0 '
elif f.compressed is None:
extra = ' '
else:
extra = '%12d ' % (f.compressed)
file.write('%s %s %s %12d %s %s\n' % (lastwritedate, lastwritetime, attrib,
f.uncompressed, extra, f.filename))
file.write('------------------- ----- ------------ ------------ ------------------------\n')
return 0
@staticmethod
def print_archiveinfo(archive, file):
file.write("--\n")
file.write("Path = {}\n".format(archive.filename))
file.write("Type = 7z\n")
fstat = os.stat(archive.filename)
file.write("Phisical Size = {}\n".format(fstat.st_size))
file.write("Headers Size = {}\n".format(archive.header.size)) # fixme.
file.write("Method = {}\n".format(archive._get_method_names()))
if archive._is_solid():
file.write("Solid = {}\n".format('+'))
else:
file.write("Solid = {}\n".format('-'))
file.write("Blocks = {}\n".format(len(archive.header.main_streams.unpackinfo.folders)))
def run_test(self, args):
target = args.arcfile
if not py7zr.is_7zfile(target):
print('not a 7z file')
return 1
with open(target, 'rb') as f:
try:
a = py7zr.SevenZipFile(f)
file = sys.stdout
file.write("Testing archive: {}\n".format(a.filename))
self.print_archiveinfo(archive=a, file=file)
file.write('\n')
if a.testzip() is None:
file.write('Everything is Ok\n')
return 0
else:
file.write('Bad 7zip file\n')
return 1
except py7zr.exceptions.Bad7zFile:
print('Header is corrupted. Cannot read as 7z file.')
return 1
except py7zr.exceptions.PasswordRequired:
print('The archive is encrypted but password is not given. FAILED.')
return 1
def run_extract(self, args: argparse.Namespace) -> int:
target = args.arcfile
verbose = args.verbose
if not py7zr.is_7zfile(target):
print('not a 7z file')
return 1
if not args.password:
password = None # type: Optional[str]
else:
try:
password = getpass.getpass()
except getpass.GetPassWarning:
sys.stderr.write('Warning: your password may be shown.\n')
return 1
try:
a = py7zr.SevenZipFile(target, 'r', password=password)
except py7zr.exceptions.Bad7zFile:
print('Header is corrupted. Cannot read as 7z file.')
return 1
except py7zr.exceptions.PasswordRequired:
print('The archive is encrypted, but password is not given. ABORT.')
return 1
except lzma.LZMAError or _lzma.LZMAError:
if password is None:
print('The archive is corrupted. ABORT.')
else:
print('The archive is corrupted, or password is wrong. ABORT.')
return 1
cb = None # Optional[ExtractCallback]
if verbose:
archive_info = a.archiveinfo()
cb = CliExtractCallback(total_bytes=archive_info.uncompressed, ofd=sys.stderr)
try:
if args.odir:
a.extractall(path=args.odir, callback=cb)
else:
a.extractall(callback=cb)
except py7zr.exceptions.UnsupportedCompressionMethodError:
print("Unsupported compression method is used in archive. ABORT.")
return 1
except py7zr.exceptions.DecompressionError:
print("Error has been occurred during decompression. ABORT.")
return 1
except py7zr.exceptions.PasswordRequired:
print('The archive is encrypted, but password is not given. ABORT.')
return 1
except lzma.LZMAError or _lzma.LZMAError:
if password is None:
print('The archive is corrupted. ABORT.')
else:
print('The archive is corrupted, or password is wrong. ABORT.')
return 1
else:
return 0
def _check_volumesize_valid(self, size: str) -> bool:
if self.unit_pattern.match(size):
return True
else:
return False
def _volumesize_unitconv(self, size: str) -> int:
m = self.unit_pattern.match(size)
num = m.group(1)
unit = m.group(2)
return int(num) if unit is None else int(num) * self.dunits[unit]
def run_create(self, args):
sztarget = args.arcfile # type: str
filenames = args.filenames # type: List[str]
volume_size = args.volume[0] if getattr(args, 'volume', None) is not None else None
if volume_size is not None and not self._check_volumesize_valid(volume_size):
sys.stderr.write('Error: Specified volume size is invalid.\n')
self.show_help(args)
exit(1)
if not sztarget.endswith('.7z'):
sztarget += '.7z'
target = pathlib.Path(sztarget)
if target.exists():
sys.stderr.write('Archive file exists!\n')
self.show_help(args)
exit(1)
with py7zr.SevenZipFile(target, 'w') as szf:
for path in filenames:
src = pathlib.Path(path)
if src.is_dir():
szf.writeall(src)
else:
szf.write(src)
if volume_size is None:
return 0
size = self._volumesize_unitconv(volume_size)
self._split_file(target, size)
target.unlink()
return 0
def run_append(self, args):
sztarget = args.arcfile # type: str
filenames = args.filenames # type: List[str]
if not sztarget.endswith('.7z'):
sys.stderr.write("Error: specified archive file is invalid.")
self.show_help(args)
exit(1)
target = pathlib.Path(sztarget)
if not target.exists():
sys.stderr.write('Archive file does not exists!\n')
self.show_help(args)
exit(1)
with py7zr.SevenZipFile(target, 'a') as szf:
for path in filenames:
src = pathlib.Path(path)
if src.is_dir():
szf.writeall(src)
else:
szf.write(src)
return 0
def _split_file(self, filepath, size):
chapters = 0
written = [0, 0]
total_size = filepath.stat().st_size
with filepath.open('rb') as src:
while written[0] <= total_size:
with open(str(filepath) + '.%03d' % chapters, 'wb') as tgt:
written[1] = 0
while written[1] < size:
read_size = min(READ_BLOCKSIZE, size - written[1])
tgt.write(src.read(read_size))
written[1] += read_size
written[0] += read_size
chapters += 1