%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